27 Commits
2.0 ... 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
032c699e8b Code cleanup 2020-04-15 10:06:57 +02:00
a298cfc47d Only accept PBF gziped files in canRead() 2020-04-15 09:47:43 +02:00
402e1e3adc Consistently use mvt as the format 2020-04-13 19:47:41 +02:00
254e93e583 Added Tilezen examples 2020-04-11 11:49:37 +02:00
95d4fee453 Added examples data/style sources 2020-04-11 11:27:17 +02:00
a6547d27d3 Added style compatibility info 2020-04-10 13:13:20 +02:00
3e065f87fa Updated screenshots 2020-04-09 23:42:02 +02:00
61291bb202 Updated styles info 2020-04-09 22:47:21 +02:00
154114dd2a Added missing brew update to travis builds 2020-02-08 13:04:36 +01:00
c0a7d64a1d Silenced all the deprecated warnings 2020-01-31 22:20:57 +01:00
d8df5cb595 Added style expressions not supported info 2019-10-22 09:14:06 +02:00
543bf63017 Merge branch 'master' of https://github.com/tumic0/QtPBFImagePlugin 2019-10-06 13:27:10 +02:00
7ea19d260c Fixed broken interpolation of grayscale colors 2019-10-06 13:26:30 +02:00
0185cd904f Added change log link 2019-10-05 22:06:37 +02:00
ec6302d524 Updated scaling sample to fit v2.0 2019-10-05 18:49:34 +02:00
0916d6330c Added tile size info 2019-10-05 18:47:39 +02:00
17 changed files with 293 additions and 148 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++ language: c++
dist: xenial dist: focal
os: os:
- linux - linux
@ -10,6 +10,7 @@ env:
before_install: before_install:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi
install: install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install qt protobuf; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install qt protobuf; fi

View File

@ -8,15 +8,11 @@ PBF(MVT) vector tiles without (almost, see usage) any application modifications.
Standard Mapbox GL Styles are used for styling the maps. Most relevant style 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](http://editor.openmaptiles.org) are supported.
The style is loaded from the A default fallback style (OSM-Liberty) for OpenMapTiles is part of the plugin.
[$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 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 "Plain" PBF files as well as gzip compressed files (as used in MBTiles) are
supported by the plugin. supported by the plugin. The tile size is (since version 2.0 of the plugin) 512px
to fit the styles and available data (OpenMapTiles, Mapbox tiles).
## Usage ## Usage
Due to a major design flaw in the Mapbox vector tiles specification - the zoom Due to a major design flaw in the Mapbox vector tiles specification - the zoom
@ -33,14 +29,29 @@ The plugin supports vector scaling using QImageReader's setScaledSize() method,
so when used like in the following example: so when used like in the following example:
```cpp ```cpp
QImageReader reader(file, QString::number(zoom).toLatin1()); QImageReader reader(file, QString::number(zoom).toLatin1());
reader.setScaledSize(QSize(512, 512)); reader.setScaledSize(QSize(1024, 1024));
reader.read(&image); reader.read(&image);
``` ```
you will get 512x512px tiles with a pixel ratio of 2 (= HiDPI tiles). 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.*
For a list of "ready to use" styles see the
[QtPBFImagePlugin-styles](https://github.com/tumic0/QtPBFImagePlugin-styles)
repository.
## Build ## Build
### Requirements ### Requirements
* Qt >= 5.4 (5.6 for HiDPI support) * Qt5 >= 5.12 or Qt6
* Google Protocol Buffers (protobuf-lite) * Google Protocol Buffers (protobuf-lite)
* Zlib * Zlib
@ -72,22 +83,45 @@ for most common distros available on OBS.
style are ignored. style are ignored.
* Text PBF features must have a unique id (OpenMapTiles >= v3.7) for the text layout * Text PBF features must have a unique id (OpenMapTiles >= v3.7) for the text layout
algorithm to work properly. algorithm to work properly.
* Expressions are not supported in the styles, only property functions are implemented.
## Changelog
[Changelog](https://build.opensuse.org/package/view_file/home:tumic:QtPBFImagePlugin/QtPBFImagePlugin/libqt5-qtpbfimageformat.changes)
## Status ## Status
A picture is worth a thousand words. Data and styles from https://openmaptiles.org. A picture is worth a thousand words.
#### OSM-liberty #### OpenMapTiles
![osm-liberty 2](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-2.png)
* Data: [MapTiler](https://github.com/tumic0/GPXSee-maps/blob/master/World/MapTiler.tpl)
* Style: [OSM-liberty](https://github.com/tumic0/QtPBFImagePlugin-styles/blob/master/OpenMapTiles/osm-liberty/style.json)
![osm-liberty 5](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-5.png) ![osm-liberty 5](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-5.png)
![osm-liberty 8](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-8.png) ![osm-liberty 8](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-8.png)
![osm-liberty 11](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-11.png) ![osm-liberty 12](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-12.png)
![osm-liberty 14](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-14.png) ![osm-liberty 14](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-14.png)
![osm-liberty 15](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-15.png)
#### Klokantech-basic #### Mapbox
![klokantech-basic 2](https://tumic0.github.io/QtPBFImagePlugin/images/klokantech-basic-2.png)
![klokantech-basic 4](https://tumic0.github.io/QtPBFImagePlugin/images/klokantech-basic-4.png) * Data: [Mapbox](https://github.com/tumic0/GPXSee-maps/blob/master/World/Mapbox.tpl)
![klokantech-basic 8](https://tumic0.github.io/QtPBFImagePlugin/images/klokantech-basic-8.png) * Style: [Bright](https://github.com/tumic0/QtPBFImagePlugin-styles/blob/master/Mapbox/bright/style.json)
![klokantech-basic 13](https://tumic0.github.io/QtPBFImagePlugin/images/klokantech-basic-13.png)
![klokantech-basic 14](https://tumic0.github.io/QtPBFImagePlugin/images/klokantech-basic-14.png) ![bright 4](https://tumic0.github.io/QtPBFImagePlugin/images/bright-4.png)
![bright 6](https://tumic0.github.io/QtPBFImagePlugin/images/bright-6.png)
![bright 13](https://tumic0.github.io/QtPBFImagePlugin/images/bright-13.png)
![bright 15](https://tumic0.github.io/QtPBFImagePlugin/images/bright-15.png)
![bright 17](https://tumic0.github.io/QtPBFImagePlugin/images/bright-17.png)
#### Tilezen
* Data: [HERE](https://github.com/tumic0/GPXSee-maps/blob/master/World/here-vector.tpl)
* Style: [Apollo-Bright](https://github.com/tumic0/QtPBFImagePlugin-styles/blob/master/Tilezen/apollo-bright/style.json)
![apollo-bright 4](https://tumic0.github.io/QtPBFImagePlugin/images/apollo-bright-4.png)
![apollo-bright 6](https://tumic0.github.io/QtPBFImagePlugin/images/apollo-bright-6.png)
![apollo-bright 12](https://tumic0.github.io/QtPBFImagePlugin/images/apollo-bright-12.png)
![apollo-bright 15](https://tumic0.github.io/QtPBFImagePlugin/images/apollo-bright-15.png)
![apollo-bright 16](https://tumic0.github.io/QtPBFImagePlugin/images/apollo-bright-16.png)
## Applications using QtPBFImagePlugin ## Applications using QtPBFImagePlugin
* [GPXSee](https://www.gpxsee.org) * [GPXSee](https://www.gpxsee.org)

View File

@ -20,8 +20,7 @@ HEADERS += src/pbfhandler.h \
src/textpointitem.h \ src/textpointitem.h \
src/font.h \ src/font.h \
src/textitem.h \ src/textitem.h \
src/sprites.h \ src/sprites.h
src/config.h
SOURCES += src/pbfplugin.cpp \ SOURCES += src/pbfplugin.cpp \
src/pbfhandler.cpp \ src/pbfhandler.cpp \
src/gzip.cpp \ src/gzip.cpp \
@ -37,6 +36,8 @@ SOURCES += src/pbfplugin.cpp \
src/textitem.cpp src/textitem.cpp
RESOURCES += pbfplugin.qrc RESOURCES += pbfplugin.qrc
DEFINES += QT_NO_DEPRECATED_WARNINGS
unix:!macx{ unix:!macx{
LIBS += -lprotobuf-lite \ LIBS += -lprotobuf-lite \
-lz -lz

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,13 +34,19 @@ static QColor interpolate(const QPair<qreal, QColor> &p0,
? progress / difference ? progress / difference
: (pow(base, progress) - 1) / (pow(base, difference) - 1); : (pow(base, progress) - 1) / (pow(base, difference) - 1);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
qreal p0h, p0s, p0l, p0a; qreal p0h, p0s, p0l, p0a;
p0.second.getHslF(&p0h, &p0s, &p0l, &p0a);
qreal p1h, p1s, p1l, p1a; 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); p1.second.getHslF(&p1h, &p1s, &p1l, &p1a);
return QColor::fromHslF(f(p0h, p1h, ratio), f(p0s, p1s, ratio), /* Qt returns a hue of -1 for achromatic colors. We convert it to a hue of 1
using qAbs() to enable interpolation of grayscale colors */
return QColor::fromHslF(f(qAbs(p0h), qAbs(p1h), ratio), f(p0s, p1s, ratio),
f(p0l, p1l, ratio), f(p0a, p1a, ratio)); f(p0l, p1l, ratio), f(p0a, p1a, ratio));
} }

View File

@ -1,35 +1,58 @@
#include <QtEndian> #include <QIODevice>
#include <QDebug>
#include <zlib.h> #include <zlib.h>
#include "gzip.h" #include "gzip.h"
#define CHUNK 16384
QByteArray Gzip::uncompress(const QByteArray &data) QByteArray Gzip::uncompress(QIODevice *device, int limit)
{ {
int ret;
z_stream strm;
unsigned char in[CHUNK];
unsigned char out[CHUNK];
qint64 rs;
QByteArray uba; QByteArray uba;
z_stream stream;
quint32 *size = (quint32*)(data.constData() + data.size() - sizeof(quint32));
uba.resize(qFromLittleEndian(*size));
stream.zalloc = Z_NULL; strm.zalloc = Z_NULL;
stream.zfree = Z_NULL; strm.zfree = Z_NULL;
stream.opaque = Z_NULL; strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;
if (inflateInit2(&strm, MAX_WBITS + 16) != Z_OK)
return QByteArray();
stream.next_in = (Bytef*)data.constData(); do {
stream.avail_in = data.size(); rs = device->read((char*)in, CHUNK);
stream.next_out = (Bytef*)uba.data(); if (rs < 0) {
stream.avail_out = uba.size(); (void)inflateEnd(&strm);
return QByteArray();
} else if (rs == 0)
break;
else
strm.avail_in = (uInt)rs;
strm.next_in = in;
if (inflateInit2(&stream, MAX_WBITS + 16) != Z_OK) do {
return uba; strm.avail_out = CHUNK;
strm.next_out = out;
ret = inflate(&strm, Z_NO_FLUSH);
Q_ASSERT(ret != Z_STREAM_ERROR);
switch (ret) {
case Z_NEED_DICT:
case Z_DATA_ERROR:
case Z_MEM_ERROR:
(void)inflateEnd(&strm);
return QByteArray();
}
uba.append((char*)out, CHUNK - strm.avail_out);
if (limit && uba.size() >= limit) {
(void)inflateEnd(&strm);
return uba;
}
} while (!strm.avail_out);
} while (ret != Z_STREAM_END);
if (inflate(&stream, Z_NO_FLUSH) != Z_STREAM_END) { (void)inflateEnd(&strm);
qCritical() << "Invalid gzip data"; return (ret == Z_STREAM_END) ? uba : QByteArray();
uba = QByteArray();
}
inflateEnd(&stream);
return uba;
} }

View File

@ -3,9 +3,11 @@
#include <QByteArray> #include <QByteArray>
class QIODevice;
namespace Gzip namespace Gzip
{ {
QByteArray uncompress(const QByteArray &data); QByteArray uncompress(QIODevice *device, int limit = 0);
} }
#endif // GZIP_H #endif // GZIP_H

View File

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

View File

@ -10,22 +10,22 @@
#define TILE_SIZE 512 #define TILE_SIZE 512
#define GZIP_MAGIC 0x1F8B0800 #define GZIP_MAGIC 0x1F8B
#define GZIP_MAGIC_MASK 0xFFFFFF00 #define GZIP_MAGIC_MASK 0xFFFF
#define PBF_MAGIC 0x1A000000 #define PBF_MAGIC 0x1A00
#define PBF_MAGIC_MASK 0xFF000000 #define PBF_MAGIC_MASK 0xFF00
static bool isMagic(quint32 magic, quint32 mask, quint32 value) static bool isMagic(quint16 magic, quint16 mask, quint16 value)
{ {
return ((qFromBigEndian(value) & mask) == magic); return ((qFromBigEndian(value) & mask) == magic);
} }
static bool isGZIPPBF(quint32 magic) static bool isGZIPPBF(quint16 magic)
{ {
return isMagic(GZIP_MAGIC, GZIP_MAGIC_MASK, magic); return isMagic(GZIP_MAGIC, GZIP_MAGIC_MASK, magic);
} }
static bool isPlainPBF(quint32 magic) static bool isPlainPBF(quint16 magic)
{ {
return isMagic(PBF_MAGIC, PBF_MAGIC_MASK, magic); return isMagic(PBF_MAGIC, PBF_MAGIC_MASK, magic);
} }
@ -34,7 +34,7 @@ static bool isPlainPBF(quint32 magic)
bool PBFHandler::canRead() const bool PBFHandler::canRead() const
{ {
if (canRead(device())) { if (canRead(device())) {
setFormat("pbf"); setFormat("mvt");
return true; return true;
} else } else
return false; return false;
@ -42,27 +42,37 @@ bool PBFHandler::canRead() const
bool PBFHandler::canRead(QIODevice *device) bool PBFHandler::canRead(QIODevice *device)
{ {
quint32 magic; quint16 magic;
qint64 size = device->peek((char*)&magic, sizeof(magic)); qint64 size = device->peek((char*)&magic, sizeof(magic));
if (size != sizeof(magic)) if (size != sizeof(magic))
return false; return false;
return (isGZIPPBF(magic) || isPlainPBF(magic)); if (isPlainPBF(magic))
return true;
else if (isGZIPPBF(magic)) {
QByteArray ba(Gzip::uncompress(device, sizeof(magic)));
if (ba.size() < (int)sizeof(magic))
return false;
return isPlainPBF(*((quint16*)ba.constData()));
} else
return false;
} }
bool PBFHandler::read(QImage *image) bool PBFHandler::read(QImage *image)
{ {
quint32 magic; quint16 magic;
if (device()->peek((char*)&magic, sizeof(magic)) != sizeof(magic)) if (device()->peek((char*)&magic, sizeof(magic)) != sizeof(magic))
return false; return false;
QByteArray ba; QByteArray ba;
if (isGZIPPBF(magic)) if (isGZIPPBF(magic)) {
ba = Gzip::uncompress(device()->readAll()); ba = Gzip::uncompress(device());
else if (isPlainPBF(magic)) if (ba.isNull()) {
qCritical() << "Invalid gzip data";
return false;
}
} else if (isPlainPBF(magic))
ba = device()->readAll(); ba = device()->readAll();
if (ba.isNull())
return false;
vector_tile::Tile data; vector_tile::Tile data;
if (!data.ParseFromArray(ba.constData(), ba.size())) { if (!data.ParseFromArray(ba.constData(), ba.size())) {
qCritical() << "Invalid PBF data"; qCritical() << "Invalid PBF data";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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