21 Commits
2.2 ... 2.4

Author SHA1 Message Date
12c878d8e2 Proprly initialize the tile with transparent background 2023-05-05 19:15:36 +02:00
44862114ab Added Ordnance Survey and Esri styles info 2022-06-08 01:02:04 +02:00
1065a27bfb Fixed cut&paste error 2022-01-04 08:36:18 +01:00
3c22cd7823 Added linux CI build 2022-01-04 08:34:24 +01:00
1afd242302 Get rid of TravisCI 2021-10-27 17:48:33 +02:00
ae8b2f00db Added GitHub actions OS X build 2021-10-27 17:42:47 +02:00
e22ea9aa12 Updated Maputnik editor URL 2021-07-16 22:23:58 +02:00
62a78fdaae protobufs are already installed 2021-05-28 08:00:28 +02:00
63f86a900e Trying to fix the OS X TravisCI build... 2021-05-28 07:29:40 +02:00
ea8819a381 Updated minimal Qt version
Qt 5.11 works as well
2020-12-29 10:50:49 +01:00
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
17 changed files with 211 additions and 112 deletions

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

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

22
.github/workflows/linux.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Linux
on:
push:
branches:
- master
jobs:
build:
name: QtPBFImagePlugin
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install qtbase5-dev qtbase5-dev-tools qt5-qmake libprotobuf-dev protobuf-compiler zlib1g-dev
- name: Configure build
run: qmake pbfplugin.pro
- name: Build project
run: make -j2

22
.github/workflows/osx.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: OS X
on:
push:
branches:
- master
jobs:
build:
name: QtPBFImagePlugin
runs-on: macos-10.15
steps:
- name: Set environment variables
run: echo "PATH=/usr/local/opt/qt@5/bin:$PATH" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v2
- name: Install dependencies
run: brew install qt5 protobuf
- name: Configure build
run: qmake PROTOBUF=/usr/local pbfplugin.pro
- name: Build project
run: make -j3

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,23 +0,0 @@
language: c++
dist: xenial
os:
- linux
- osx
env:
- QT_SELECT=qt5
before_install:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi
install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install qt protobuf; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install qtbase5-dev qtbase5-dev-tools qt5-qmake libprotobuf-dev protobuf-compiler; fi
script:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then PATH=/usr/local/opt/qt/bin/:${PATH}; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then qmake PROTOBUF=/usr/local pbfplugin.pro; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then qmake pbfplugin.pro; fi
- make

View File

@ -7,7 +7,7 @@ displaying raster MBTiles maps or raster XYZ online maps to also display
PBF(MVT) vector tiles without (almost, see usage) any application modifications.
Standard Mapbox GL Styles are used for styling the maps. Most relevant style
features used by [Maputnik](http://editor.openmaptiles.org) are supported.
features used by [Maputnik](https://maputnik.github.io/editor) are supported.
A default fallback style (OSM-Liberty) for OpenMapTiles is part of the plugin.
"Plain" PBF files as well as gzip compressed files (as used in MBTiles) are
@ -34,13 +34,17 @@ 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
file on plugin load. If the style uses a sprite, the sprite JSON file must
be named `sprite.json` and the sprite image `sprite.png` and both files must be
placed in the same directory as the style itself. *A style compatible with the
tiles data schema (Mapbox, OpenMapTiles, Tilezen) must be used.*
tiles data schema (Mapbox, OpenMapTiles, Tilezen, Ordnance Survey, Esri, ...)
must be used.*
For a list of "ready to use" styles see the
[QtPBFImagePlugin-styles](https://github.com/tumic0/QtPBFImagePlugin-styles)
@ -48,7 +52,7 @@ repository.
## Build
### Requirements
* Qt >= 5.4 (5.6 for HiDPI support)
* Qt5 >= 5.11 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;

View File

@ -11,7 +11,10 @@ public:
: _zoom(zoom), _size(img->size()), _scale(scale),
_text(QSize(img->size().width() / scale.x(),
img->size().height() / scale.y())), _painter(img)
{_painter.scale(scale.x(), scale.y());}
{
img->fill(Qt::transparent);
_painter.scale(scale.x(), scale.y());
}
int zoom() const {return _zoom;}
const QSize &size() const {return _size;}