1
0
mirror of https://github.com/tumic0/GPXSee.git synced 2024-11-24 11:45:53 +01:00

Added support for label shields

This commit is contained in:
Martin Tůma 2019-06-30 20:39:22 +02:00
parent 561fadb4f2
commit 537b1c3716
10 changed files with 298 additions and 85 deletions

View File

@ -180,7 +180,8 @@ HEADERS += src/common/config.h \
src/map/IMG/netfile.h \ src/map/IMG/netfile.h \
src/GUI/limitedcombobox.h \ src/GUI/limitedcombobox.h \
src/GUI/pathtickitem.h \ src/GUI/pathtickitem.h \
src/map/IMG/textitem.h src/map/IMG/textitem.h \
src/map/IMG/label.h
SOURCES += src/main.cpp \ SOURCES += src/main.cpp \
src/common/coordinates.cpp \ src/common/coordinates.cpp \
src/common/rectc.cpp \ src/common/rectc.cpp \

View File

@ -10,6 +10,7 @@
#include "common/rectc.h" #include "common/rectc.h"
#include "common/range.h" #include "common/range.h"
#include "style.h" #include "style.h"
#include "label.h"
class VectorTile; class VectorTile;
class SubFile; class SubFile;
@ -23,7 +24,7 @@ public:
ll2xy() the points in the IMG class as this can not be done in ll2xy() the points in the IMG class as this can not be done in
parallel. */ parallel. */
QVector<QPointF> points; QVector<QPointF> points;
QString label; Label label;
quint32 type; quint32 type;
bool operator<(const Poly &other) const bool operator<(const Poly &other) const
@ -34,7 +35,7 @@ public:
Point() : id(0) {} Point() : id(0) {}
Coordinates coordinates; Coordinates coordinates;
QString label; Label label;
quint32 type; quint32 type;
bool poi; bool poi;
quint64 id; quint64 id;

74
src/map/IMG/label.h Normal file
View File

@ -0,0 +1,74 @@
#ifndef LABEL_H
#define LABEL_H
#include <QString>
#include <QDebug>
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

View File

@ -7,7 +7,7 @@ static quint8 NORMAL_CHARS[] = {
' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', ' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
'X', 'Y', 'Z', '~', '~', '~', '~', '~', 'X', 'Y', 'Z', '~', '~', '~', ' ', ' ',
'0', '1', '2', '3', '4', '5', '6', '7', '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', '~', '~', '~', '~', '~', '~' '8', '9', '~', '~', '~', '~', '~', '~'
}; };
@ -48,6 +48,7 @@ static QString capitalize(const QString &str)
return ret; return ret;
} }
bool LBLFile::init() bool LBLFile::init()
{ {
Handle hdl; Handle hdl;
@ -73,45 +74,48 @@ bool LBLFile::init()
return true; 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; Charset curCharSet = Normal;
quint8 b1, b2, b3; quint8 b1, b2, b3;
if (!seek(hdl, offset)) if (!seek(hdl, offset))
return QString(); return Label();
while (true) { while (true) {
if (!(readByte(hdl, b1) && readByte(hdl, b2) && readByte(hdl, b3))) 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}; int c[]= {b1>>2, (b1&0x3)<<4|b2>>4, (b2&0xF)<<2|b3>>6, b3&0x3F};
for (int cpt = 0; cpt < 4; cpt++) { for (int cpt = 0; cpt < 4; cpt++) {
if (c[cpt] > 0x2F) if (c[cpt] > 0x2f || (curCharSet == Normal && c[cpt] == 0x1d))
return QString::fromLatin1(result); return Label(capitalize(QString::fromLatin1(label)),
Label::Shield(shieldType, shieldLabel));
switch (curCharSet) { switch (curCharSet) {
case Normal: case Normal:
if (c[cpt] == 0x1c) if (c[cpt] == 0x1c)
curCharSet = Symbol; curCharSet = Symbol;
else if (c[cpt] == 0x1b) else if (c[cpt] == 0x1b)
curCharSet = Special; curCharSet = Special;
else if(c[cpt] == 0x1d) else if (c[cpt] >= 0x2a && c[cpt] <= 0x2f) {
result.append('|'); shieldType = (Label::Shield::Type)(c[cpt] - 0x29);
else if (c[cpt] == 0x1f) bap = &shieldLabel;
result.append(' '); } else if (bap == &shieldLabel
else if (c[cpt] == 0x1e) && NORMAL_CHARS[c[cpt]] == ' ')
result.append(' '); bap = &label;
else else
result.append(NORMAL_CHARS[c[cpt]]); bap->append(NORMAL_CHARS[c[cpt]]);
break; break;
case Symbol: case Symbol:
result.append(SYMBOL_CHARS[c[cpt]]); bap->append(SYMBOL_CHARS[c[cpt]]);
curCharSet = Normal; curCharSet = Normal;
break; break;
case Special: case Special:
result.append(SPECIAL_CHARS[c[cpt]]); bap->append(SPECIAL_CHARS[c[cpt]]);
curCharSet = Normal; curCharSet = Normal;
break; 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; quint8 c;
if (!seek(hdl, offset)) if (!seek(hdl, offset))
return QString(); return Label();
while (true) { while (true) {
if (!readByte(hdl, c)) if (!readByte(hdl, c))
return QString(); return Label();
if (!c) if (!c || c == 0x1d)
break; break;
if (c >= 0x1B && c <= 0x1F) if ((c >= 0x1e && c <= 0x1f))
result.append(' '); bap->append(' ');
else if (c > 0x07) else if (c <= 0x07) {
result.append(c); 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) {
if (!(_init = init())) if (!(_init = init()))
@ -163,11 +176,11 @@ QString LBLFile::label(Handle &hdl, quint32 offset, bool poi)
switch (_encoding) { switch (_encoding) {
case 6: case 6:
return capitalize(label6b(hdl, labelOffset)); return label6b(hdl, labelOffset);
case 9: case 9:
case 10: case 10:
return capitalize(label8b(hdl, labelOffset)); return label8b(hdl, labelOffset);
default: default:
return QString(); return Label();
} }
} }

View File

@ -2,6 +2,7 @@
#define LBLFILE_H #define LBLFILE_H
#include "subfile.h" #include "subfile.h"
#include "label.h"
class QTextCodec; class QTextCodec;
@ -10,13 +11,13 @@ class LBLFile : public SubFile
public: public:
LBLFile(IMG *img, quint32 size) : SubFile(img, size), _init(false) {} 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: private:
bool init(); bool init();
QString label6b(Handle &hdl, quint32 offset) const; Label label6b(Handle &hdl, quint32 offset) const;
QString label8b(Handle &hdl, quint32 offset) const; Label label8b(Handle &hdl, quint32 offset) const;
quint32 _offset; quint32 _offset;
quint32 _size; quint32 _size;

View File

@ -980,6 +980,11 @@ 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) Style::POIClass Style::poiClass(quint32 type)
{ {
if ((type >= 0x2a00 && type < 0x2b00) || type == 0x2c0a || type == 0x2d02) if ((type >= 0x2a00 && type < 0x2b00) || type == 0x2c0a || type == 0x2d02)

View File

@ -106,6 +106,7 @@ public:
static bool isContourLine(quint32 type); static bool isContourLine(quint32 type);
static bool isSpot(quint32 type); static bool isSpot(quint32 type);
static bool isSummit(quint32 type); static bool isSummit(quint32 type);
static bool isMajorRoad(quint32 type);
static POIClass poiClass(quint32 type); static POIClass poiClass(quint32 type);
private: private:

View File

@ -7,29 +7,48 @@
#define FLAGS (Qt::AlignCenter | Qt::TextWordWrap | Qt::TextDontClip) #define FLAGS (Qt::AlignCenter | Qt::TextWordWrap | Qt::TextDontClip)
#define MAX_TEXT_WIDTH 8 #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, TextPointItem::TextPointItem(const QPoint &point, const QString *text,
const QFont *font, const QImage *img, const QColor *color) const QFont *font, const QImage *img, const QColor *color,
: _text(text), _font(font), _img(img), _color(color) const QColor *bgColor) : _text(text), _font(font), _img(img), _color(color),
_bgColor(bgColor)
{ {
QRect iconRect;
if (text) { if (text) {
QFontMetrics fm(*font); QFontMetrics fm(*font);
int limit = font->pixelSize() * MAX_TEXT_WIDTH; int limit = font->pixelSize() * MAX_TEXT_WIDTH;
_textRect = fm.boundingRect(QRect(0, 0, limit, 0), FLAGS, *text); _textRect = fm.boundingRect(QRect(0, 0, limit, 0), FLAGS, *text);
_textRect.adjust(0, 0, 1, 1); _textRect.adjust(0, 0, 1, 1);
} }
if (img) {
iconRect = QRect(QPoint(point.x() - img->width()/2, point.y() if (_bgColor && _textRect.width() < font->pixelSize() * MIN_BOX_WIDTH)
- img->height()/2), img->size()); expand(_textRect, font->pixelSize() * MIN_BOX_WIDTH);
_textRect.moveTopLeft(QPoint(point.x() + img->width(), point.y()
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)); - _textRect.height()/2));
} else } else
_textRect.moveCenter(point); _textRect.moveCenter(point);
_rect = _textRect | iconRect; _rect = _textRect | iconRect;
_shape.addRect(_rect); shape.addRect(_rect);
_shape = shape;
} }
void TextPointItem::paint(QPainter *painter) const 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);
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) { if (_color) {
painter->setFont(*_font); painter->setFont(*_font);
painter->setPen(*_color); painter->setPen(*_color);

View File

@ -16,7 +16,7 @@ class TextPointItem : public TextItem
public: public:
TextPointItem() : _text(0), _font(0), _img(0) {} TextPointItem() : _text(0), _font(0), _img(0) {}
TextPointItem(const QPoint &point, const QString *text, const QFont *font, 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();} bool isValid() const {return !_rect.isEmpty();}
@ -24,11 +24,13 @@ public:
QPainterPath shape() const {return _shape;} QPainterPath shape() const {return _shape;}
void paint(QPainter *painter) const; void paint(QPainter *painter) const;
void setPos(const QPoint &point);
private: private:
const QString *_text; const QString *_text;
const QFont *_font; const QFont *_font;
const QImage *_img; const QImage *_img;
const QColor *_color; const QColor *_color, *_bgColor;
QRect _rect, _textRect; QRect _rect, _textRect;
QPainterPath _shape; QPainterPath _shape;
}; };

View File

@ -73,12 +73,18 @@ private:
QList<IMG::Point> _points; QList<IMG::Point> _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; bool ok;
int number = str.toInt(&ok); int number = str.toInt(&ok);
if (ok) return ok ? QString::number(qRound(number * 0.3048)) : str;
str = QString::number(qRound(number * 0.3048));
} }
static int minPOIZoom(Style::POIClass cl) static int minPOIZoom(Style::POIClass cl)
@ -123,6 +129,40 @@ FONT(normalFont, NORMAL_FONT_SIZE)
FONT(smallFont, SMALL_FONT_SIZE) FONT(smallFont, SMALL_FONT_SIZE)
FONT(poiFont, POI_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) IMGMap::IMGMap(const QString &fileName, QObject *parent)
: Map(parent), _fileName(fileName), _img(fileName), : Map(parent), _fileName(fileName), _img(fileName),
@ -278,6 +318,8 @@ void IMGMap::processPolygons(QList<IMG::Poly> &polygons)
void IMGMap::processLines(QList<IMG::Poly> &lines, const QPoint &tile, void IMGMap::processLines(QList<IMG::Poly> &lines, const QPoint &tile,
QList<TextItem*> &textItems) QList<TextItem*> &textItems)
{ {
QRect tileRect(tile, QSize(TILE_SIZE, TILE_SIZE));
qStableSort(lines); qStableSort(lines);
for (int i = 0; i < lines.size(); i++) { for (int i = 0; i < lines.size(); i++) {
@ -288,20 +330,19 @@ void IMGMap::processLines(QList<IMG::Poly> &lines, const QPoint &tile,
} }
} }
if (_zoom < LINE_TEXT_MIN_ZOOM) if (_zoom >= LINE_TEXT_MIN_ZOOM) {
return;
for (int i = 0; i < lines.size(); i++) { for (int i = 0; i < lines.size(); i++) {
IMG::Poly &poly = lines[i]; IMG::Poly &poly = lines[i];
const Style::Line &style = _img.style().line(poly.type); const Style::Line &style = _img.style().line(poly.type);
if (style.img().isNull() && style.foreground() == Qt::NoPen) if (style.img().isNull() && style.foreground() == Qt::NoPen)
continue; continue;
if (poly.label.isEmpty() || style.textFontSize() == Style::None) if (poly.label.text().isEmpty()
|| style.textFontSize() == Style::None)
continue; continue;
if (Style::isContourLine(poly.type)) if (Style::isContourLine(poly.type))
convertUnits(poly.label); poly.label.setText(convertUnits(poly.label.text()));
const QFont *font; const QFont *font;
switch (style.textFontSize()) { switch (style.textFontSize()) {
@ -317,13 +358,60 @@ void IMGMap::processLines(QList<IMG::Poly> &lines, const QPoint &tile,
const QColor *color = style.textColor().isValid() const QColor *color = style.textColor().isValid()
? &style.textColor() : 0; ? &style.textColor() : 0;
TextPathItem *item = new TextPathItem(poly.points, &poly.label, TextPathItem *item = new TextPathItem(poly.points,
QRect(tile, QSize(TILE_SIZE, TILE_SIZE)), font, color); &poly.label.text(), tileRect, font, color);
if (item->isValid() && !item->collides(textItems)) if (item->isValid() && !item->collides(textItems))
textItems.append(item); textItems.append(item);
else else
delete item; delete item;
} }
}
for (int type = 1; type < 7; type++) {
if (minShieldZoom((Label::Shield::Type)type) > _zoom)
continue;
QSet<Label::Shield> 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;
}
}
}
} }
void IMGMap::processPoints(QList<IMG::Point> &points, void IMGMap::processPoints(QList<IMG::Point> &points,
@ -339,7 +427,8 @@ void IMGMap::processPoints(QList<IMG::Point> &points,
if (point.poi && _zoom < minPOIZoom(Style::poiClass(point.type))) if (point.poi && _zoom < minPOIZoom(Style::poiClass(point.type)))
continue; 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 QImage *img = style.img().isNull() ? 0 : &style.img();
const QFont *font = 0; const QFont *font = 0;
if (point.poi) { if (point.poi) {
@ -369,10 +458,10 @@ void IMGMap::processPoints(QList<IMG::Point> &points,
continue; continue;
if (Style::isSpot(point.type)) if (Style::isSpot(point.type))
convertUnits(point.label); point.label.setText(convertUnits(point.label.text()));
if (Style::isSummit(point.type) && !point.label.isEmpty()) { if (Style::isSummit(point.type) && !point.label.text().isEmpty()) {
QStringList list = point.label.split(" "); QStringList list = point.label.text().split(" ");
convertUnits(list.last()); list.last() = convertUnits(list.last());
point.label = list.join(" "); point.label = list.join(" ");
} }