11 Commits
2.2 ... 2.3

Author SHA1 Message Date
ee49c05eb9 Changed Travis build distro to focal 2020-12-22 23:51:30 +01:00
29d4008068 A better regexp handling 2020-12-22 22:30:05 +01:00
60691060ea Added support for Qt6
Minimal Qt version is now Qt 5.12
2020-12-22 22:17:00 +01:00
9b63d1d362 Trying to fix the travis-ci OS X build... 2020-09-02 22:28:36 +02:00
7822bdd42c Fixed uninitialized visibility property 2020-09-01 22:25:37 +02:00
fe97e9d238 Added link to pbf2png 2020-07-03 01:23:40 +02:00
fd02b83b47 Use a better scoped singleton 2020-07-01 21:11:09 +02:00
7684f4fdcf Code cleanup 2020-07-01 20:48:37 +02:00
9772c2f67f Added support for layout visibility style option 2020-06-13 17:15:49 +02:00
e3c940dd07 Added github FUNDING.yml file 2020-05-03 11:42:01 +02:00
e2bb9f5b14 Added gitignore file 2020-05-03 11:40:08 +02:00
14 changed files with 161 additions and 87 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
github: tumic0

17
.gitignore vendored Normal file
View File

@ -0,0 +1,17 @@
# Object files
*.o
# Protobuf stuff
protobuf/vector_tile.pb.cc
protobuf/vector_tile.pb.h
# Qt stuff
/.qmake.stash
moc_*.cpp
moc_*.h
qrc_*.cpp
Makefile*
# lib
libpbf.so
pbf.dylib

View File

@ -1,5 +1,5 @@
language: c++
dist: xenial
dist: focal
os:
- linux

View File

@ -34,6 +34,9 @@ reader.read(&image);
```
you will get 1024x1024px tiles with a pixel ratio of 2 (= HiDPI tiles).
For a sample code see the [pbf2png](https://github.com/tumic0/pbf2png)
conversion utility.
## Styles
The map style is loaded from the
[$AppDataLocation](http://doc.qt.io/qt-5/qstandardpaths.html)/style/style.json
@ -48,7 +51,7 @@ repository.
## Build
### Requirements
* Qt >= 5.4 (5.6 for HiDPI support)
* Qt5 >= 5.12 or Qt6
* Google Protocol Buffers (protobuf-lite)
* Zlib

View File

@ -20,8 +20,7 @@ HEADERS += src/pbfhandler.h \
src/textpointitem.h \
src/font.h \
src/textitem.h \
src/sprites.h \
src/config.h
src/sprites.h
SOURCES += src/pbfplugin.cpp \
src/pbfhandler.cpp \
src/gzip.cpp \

View File

@ -1,10 +0,0 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <QtGlobal>
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
#define ENABLE_HIDPI
#endif // QT >= 5.6
#endif // CONFIG_H

View File

@ -34,10 +34,14 @@ static QColor interpolate(const QPair<qreal, QColor> &p0,
? progress / difference
: (pow(base, progress) - 1) / (pow(base, difference) - 1);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
qreal p0h, p0s, p0l, p0a;
p0.second.getHslF(&p0h, &p0s, &p0l, &p0a);
qreal p1h, p1s, p1l, p1a;
#else // QT6
float p0h, p0s, p0l, p0a;
float p1h, p1s, p1l, p1a;
#endif // QT6
p0.second.getHslF(&p0h, &p0s, &p0l, &p0a);
p1.second.getHslF(&p1h, &p1s, &p1l, &p1a);
/* Qt returns a hue of -1 for achromatic colors. We convert it to a hue of 1

View File

@ -100,7 +100,7 @@ PBF::Layer::Layer(const vector_tile::Tile_Layer *data) : _data(data)
_features.reserve(data->features_size());
for (int i = 0; i < data->features_size(); i++)
_features.append(Feature(&(data->features(i)), this));
qSort(_features.begin(), _features.end());
std::sort(_features.begin(), _features.end());
}
PBF::PBF(const vector_tile::Tile &tile)
@ -113,7 +113,5 @@ PBF::PBF(const vector_tile::Tile &tile)
PBF::~PBF()
{
for (QHash<QString, Layer*>::iterator it = _layers.begin();
it != _layers.end(); it++)
delete *it;
qDeleteAll(_layers);
}

View File

@ -9,9 +9,9 @@
Loading the sprites atlas image must be deferred until all image plugins
are loaded, otherwise reading the image will cause a deadlock!
*/
static const QImage *atlas(const QString &fileName)
static const QImage &atlas(const QString &fileName)
{
static QImage *img = new QImage(fileName);
static QImage img(fileName);
return img;
}
@ -86,18 +86,18 @@ QImage Sprites::icon(const QString &name) const
if (_imageFile.isEmpty())
return QImage();
const QImage *img = atlas(_imageFile);
if (img->isNull())
const QImage &img = atlas(_imageFile);
if (img.isNull())
return QImage();
QMap<QString, Sprite>::const_iterator it = _sprites.find(name);
if (it == _sprites.constEnd())
return QImage();
if (!img->rect().contains(it->rect()))
if (!img.rect().contains(it->rect()))
return QImage();
QImage ret(img->copy(it->rect()));
QImage ret(img.copy(it->rect()));
ret.setDevicePixelRatio(it->pixelRatio());
return ret;

View File

@ -5,6 +5,7 @@
#include <QJsonArray>
#include <QFileInfo>
#include <QDir>
#include <QRegularExpression>
#include <QDebug>
#include "text.h"
#include "color.h"
@ -138,22 +139,46 @@ bool Style::Layer::Filter::match(const PBF::Feature &feature) const
if (!(v = feature.value(_kv.first)))
return false;
else
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
return *v > _kv.second;
#else // QT6
return (QVariant::compare(*v, _kv.second)
== QPartialOrdering::Greater);
#endif // QT6
case GE:
if (!(v = feature.value(_kv.first)))
{if (!(v = feature.value(_kv.first)))
return false;
else
else {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
return *v >= _kv.second;
#else // QT6
QPartialOrdering res = QVariant::compare(*v, _kv.second);
return (res == QPartialOrdering::Greater
|| res == QPartialOrdering::Equivalent);
#endif // QT6
}}
case LT:
if (!(v = feature.value(_kv.first)))
return false;
else
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
return *v < _kv.second;
#else // QT6
return (QVariant::compare(*v, _kv.second)
== QPartialOrdering::Less);
#endif // QT6
case LE:
if (!(v = feature.value(_kv.first)))
{if (!(v = feature.value(_kv.first)))
return false;
else
else {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
return *v <= _kv.second;
#else // QT6
QPartialOrdering res = QVariant::compare(*v, _kv.second);
return (res == QPartialOrdering::Less
|| res == QPartialOrdering::Equivalent);
#endif // QT6
}}
case In:
if (!(v = feature.value(_kv.first)))
return _not;
@ -180,15 +205,15 @@ bool Style::Layer::Filter::match(const PBF::Feature &feature) const
QString Style::Layer::Template::value(int zoom, const PBF::Feature &feature) const
{
QRegExp rx = QRegExp("\\{[^\\}]*\\}");
QRegularExpression rx("\\{[^\\}]*\\}");
QString text(_field.value(zoom));
QRegularExpressionMatchIterator it = rx.globalMatch(text);
QStringList keys;
int pos = 0;
while ((pos = rx.indexIn(text, pos)) != -1) {
QString match = rx.capturedTexts().first();
keys.append(match.mid(1, match.size() - 2));
pos += rx.matchedLength();
while (it.hasNext()) {
QRegularExpressionMatch match = it.next();
QString val = match.captured(0);
keys.append(val.mid(1, val.size() - 2));
}
for (int i = 0; i < keys.size(); i++) {
const QString &key = keys.at(i);
@ -348,6 +373,12 @@ Style::Layer::Layout::Layout(const QJsonObject &json)
// symbol
_symbolPlacement = FunctionS(json["symbol-placement"]);
// visibility
if (json.contains("visibility") && json["visibility"].isString())
_visible = !(json["visibility"].toString() == "none");
else
_visible = true;
}
QFont Style::Layer::Layout::font(int zoom) const
@ -569,22 +600,15 @@ bool Style::load(const QString &fileName)
QDir styleDir = QFileInfo(fileName).absoluteDir();
loadSprites(styleDir, "sprite.json", "sprite.png", _sprites);
#ifdef ENABLE_HIDPI
loadSprites(styleDir, "sprite@2x.json", "sprite@2x.png", _sprites2x);
#endif // ENABLE_HIDPI
return true;
}
const Sprites &Style::sprites(const QPointF &scale) const
{
#ifdef ENABLE_HIDPI
return (scale.x() > 1.0 || scale.y() > 1.0)
&& !_sprites2x.isNull() ? _sprites2x : _sprites;
#else // ENABLE_HIDPI
Q_UNUSED(scale);
return _sprites;
#endif // ENABLE_HIDPI
}
void Style::setupLayer(Tile &tile, const Layer &layer) const
@ -637,6 +661,9 @@ void Style::drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer,
if (pbfLayer.data()->version() > 2)
return;
if (!styleLayer.isVisible())
return;
QSizeF factor(tile.size().width() / tile.scale().x() /
(qreal)pbfLayer.data()->extent(), tile.size().height() / tile.scale().y()
/ (qreal)pbfLayer.data()->extent());

View File

@ -12,7 +12,6 @@
#include <QBrush>
#include <QFont>
#include "pbf.h"
#include "config.h"
#include "text.h"
#include "function.h"
#include "sprites.h"
@ -42,6 +41,7 @@ private:
bool isPath() const {return (_type == Line || _type == Fill);}
bool isBackground() const {return (_type == Background);}
bool isSymbol() const {return (_type == Symbol);}
bool isVisible() const {return (_layout.visible());}
bool match(int zoom, const PBF::Feature &feature) const;
void setPathPainter(Tile &tile, const Sprites &sprites) const;
@ -92,7 +92,7 @@ private:
class Layout {
public:
Layout() : _textSize(16), _textMaxWidth(10), _textMaxAngle(45),
_font("Open Sans") {}
_font("Open Sans"), _visible(true) {}
Layout(const QJsonObject &json);
qreal maxTextWidth(int zoom) const
@ -109,6 +109,7 @@ private:
Text::Anchor textAnchor(int zoom) const;
Text::SymbolPlacement symbolPlacement(int zoom) const;
Text::RotationAlignment textRotationAlignment(int zoom) const;
bool visible() const {return _visible;}
private:
QFont::Capitalization textTransform(int zoom) const;
@ -125,6 +126,7 @@ private:
FunctionS _symbolPlacement;
FunctionS _textRotationAlignment;
QFont _font;
bool _visible;
};
class Paint {
@ -176,10 +178,7 @@ private:
Tile &tile) const;
QVector<Layer> _layers;
Sprites _sprites;
#ifdef ENABLE_HIDPI
Sprites _sprites2x;
#endif // QT >= 5.6
Sprites _sprites, _sprites2x;
};
#endif // STYLE_H

View File

@ -9,8 +9,7 @@
Text::~Text()
{
for (int i = 0; i < _items.size(); i++)
delete _items[i];
qDeleteAll(_items);
}
void Text::render(QPainter *painter) const

View File

@ -3,25 +3,62 @@
#include "textpathitem.h"
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
#define INTERSECTS intersect
#else // QT 5.15
#define INTERSECTS intersects
#endif // QT 5.15
static bool intersection(const QLineF &line, const QRectF &rect,
QPointF *p)
{
if (line.intersect(QLineF(rect.topLeft(), rect.topRight()), p)
if (line.INTERSECTS(QLineF(rect.topLeft(), rect.topRight()), p)
== QLineF::BoundedIntersection)
return true;
if (line.intersect(QLineF(rect.topLeft(), rect.bottomLeft()), p)
if (line.INTERSECTS(QLineF(rect.topLeft(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection)
return true;
if (line.intersect(QLineF(rect.bottomRight(), rect.bottomLeft()), p)
if (line.INTERSECTS(QLineF(rect.bottomRight(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection)
return true;
if (line.intersect(QLineF(rect.bottomRight(), rect.topRight()), p)
if (line.INTERSECTS(QLineF(rect.bottomRight(), rect.topRight()), p)
== QLineF::BoundedIntersection)
return true;
return false;
}
static bool intersection(const QLineF &line, const QRectF &rect, QPointF *p1,
QPointF *p2)
{
QPointF *p = p1;
if (line.INTERSECTS(QLineF(rect.topLeft(), rect.topRight()), p)
== QLineF::BoundedIntersection)
p = p2;
if (line.INTERSECTS(QLineF(rect.topLeft(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection) {
if (p == p2)
return true;
p = p2;
}
if (line.INTERSECTS(QLineF(rect.bottomRight(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection) {
if (p == p2)
return true;
p = p2;
}
if (line.INTERSECTS(QLineF(rect.bottomRight(), rect.topRight()), p)
== QLineF::BoundedIntersection) {
if (p == p2)
return true;
}
Q_ASSERT(p != p2);
return false;
}
static QPainterPath subpath(const QList<QLineF> &lines, int start, int end,
qreal cut)
{
@ -62,8 +99,8 @@ static QList<QLineF> lineString(const QPainterPath &path,
const QRectF &boundingRect)
{
QList<QLineF> lines;
int start = 0, end = path.elementCount() - 1;
QPointF p;
int start = -1, end = -1;
for (int i = 0; i < path.elementCount(); i++) {
if (boundingRect.contains(path.elementAt(i))) {
@ -78,17 +115,31 @@ static QList<QLineF> lineString(const QPainterPath &path,
}
}
if (start > 0) {
QLineF l(path.elementAt(start-1), path.elementAt(start));
if (intersection(l, boundingRect, &p))
lines.append(QLineF(p, path.elementAt(start)));
}
for (int i = start + 1; i <= end; i++)
lines.append(QLineF(path.elementAt(i-1), path.elementAt(i)));
if (end < path.elementCount() - 1) {
QLineF l(path.elementAt(end), path.elementAt(end+1));
if (intersection(l, boundingRect, &p))
lines.append(QLineF(path.elementAt(end), p));
if (start < 0) {
QPointF p1, p2;
for (int i = 1; i < path.elementCount(); i++) {
QLineF l(path.elementAt(i-1), path.elementAt(i));
if (intersection(l, boundingRect, &p1, &p2)) {
lines.append(QLineF(p1, p2));
break;
}
}
} else {
QPointF p;
if (start > 0) {
QLineF l(path.elementAt(start-1), path.elementAt(start));
if (intersection(l, boundingRect, &p))
lines.append(QLineF(p, path.elementAt(start)));
}
for (int i = start + 1; i <= end; i++)
lines.append(QLineF(path.elementAt(i-1), path.elementAt(i)));
if (end < path.elementCount() - 1) {
QLineF l(path.elementAt(end), path.elementAt(end+1));
if (intersection(l, boundingRect, &p))
lines.append(QLineF(path.elementAt(end), p));
}
}
return lines;
@ -98,6 +149,8 @@ static QPainterPath textPath(const QPainterPath &path, qreal textWidth,
qreal maxAngle, qreal charWidth, const QRectF &tileRect)
{
QList<QLineF> lines(lineString(path, tileRect));
if (lines.isEmpty())
return QPainterPath();
qreal length = 0;
qreal angle = lines.first().angle();
int last = 0;
@ -159,7 +212,7 @@ void TextPathItem::paint(QPainter *painter) const
//painter->drawPath(_shape);
QFontMetrics fm(font());
int textWidth = fm.width(text());
int textWidth = fm.boundingRect(text()).width();
qreal factor = (textWidth) / qMax(_path.length(), (qreal)textWidth);
qreal percent = (1.0 - factor) / 2.0;
@ -186,7 +239,7 @@ void TextPathItem::paint(QPainter *painter) const
painter->drawText(QPoint(1, fm.descent()), text().at(i));
painter->setTransform(t);
int width = fm.charWidth(text(), i);
int width = fm.horizontalAdvance(text().at(i));
percent += ((qreal)width / (qreal)textWidth) * factor;
}
@ -203,7 +256,7 @@ void TextPathItem::paint(QPainter *painter) const
painter->drawText(QPoint(0, fm.descent()), text().at(i));
painter->setTransform(t);
int width = fm.charWidth(text(), i);
int width = fm.horizontalAdvance(text().at(i));
percent += ((qreal)width / (qreal)textWidth) * factor;
}
}

View File

@ -1,7 +1,6 @@
#include <QPainter>
#include <QtMath>
#include <QStaticText>
#include "config.h"
#include "textpointitem.h"
@ -50,16 +49,10 @@ QRectF TextPointItem::fuzzyBoundingRect() const
return QRectF(0, 0, width, lines * fs * 1.6);
}
QRectF TextPointItem::moveTextRect(const QRectF &rect) const
{
#ifdef ENABLE_HIDPI
QRectF iconRect = _icon.isNull() ? QRectF()
: QRectF(QPointF(0, 0), QSizeF(_icon.size()) / _icon.devicePixelRatioF());
#else // ENABLE_HIDPI
QRectF iconRect = _icon.isNull() ? QRectF() : QRectF(QPointF(0, 0),
QSizeF(_icon.size()));
#endif // ENABLE_HIDPI
QRectF textRect(rect);
switch (_anchor) {
@ -96,12 +89,8 @@ TextPointItem::TextPointItem(const QString &text, const QPointF &pos,
_boundingRect = moveTextRect(_textRect);
if (!_icon.isNull()) {
#ifdef ENABLE_HIDPI
QRectF iconRect(QPointF(0, 0), QSizeF(_icon.size())
/ _icon.devicePixelRatioF());
#else // ENABLE_HIDPI
QRectF iconRect(QPointF(0, 0), QSizeF(_icon.size()));
#endif // ENABLE_HIDPI
iconRect.moveCenter(pos);
_boundingRect |= iconRect;
}
@ -127,14 +116,9 @@ void TextPointItem::paint(QPainter *painter) const
if (!_icon.isNull()) {
textRect = moveTextRect(painter->boundingRect(_textRect, FLAGS, text()));
#ifdef ENABLE_HIDPI
painter->drawImage(_pos - QPointF(_icon.width()
/ _icon.devicePixelRatioF() / 2, _icon.height()
/ _icon.devicePixelRatioF() / 2), _icon);
#else // ENABLE_HIDPI
painter->drawImage(_pos - QPointF(_icon.width() / 2,
_icon.height() / 2), _icon);
#endif // ENABLE_HIDPI
} else
textRect = _boundingRect;