29 Commits
1.0 ... 1.3

Author SHA1 Message Date
5ccb93bd59 Use the official IANA MIME-type and extension 2019-02-26 22:20:12 +01:00
c3df78a455 Added support for named (HTML) colors 2019-02-26 22:18:07 +01:00
f96b530fa3 Merge branch 'master' of https://github.com/tumic0/QtPBFImagePlugin 2019-02-22 08:47:29 +01:00
9fdfd35752 Removed obsolete code 2019-02-22 08:47:01 +01:00
20f2e7c7d2 Refer to PBF also as MVT 2019-01-03 01:44:57 +01:00
75e6da8903 Fixed build with older Google protocol buffers 2019-01-01 21:45:53 +01:00
c8e8f0013f Text rendering cosmetics 2019-01-01 21:27:54 +01:00
a7294b30f8 Improved performance 2019-01-01 21:27:07 +01:00
2a89ef8dfd Do not try to open empty filenames
(fixes warning)
2018-12-22 08:17:24 +01:00
54176640d0 cosmetics 2018-12-17 21:59:06 +01:00
4042b3ccdf New minimal Qt version is 5.4 2018-12-17 18:36:45 +01:00
c1af3d82a5 Added support for QT < 5.6 (and >= 5.4) 2018-12-17 18:31:26 +01:00
368c53307d Add protobuf path on OS X
Disable linux build until a host with QT >= 5.6 is available
2018-12-17 17:31:33 +01:00
75e61e956e 16.04 is not enaugh 2018-12-16 22:34:02 +01:00
094641eb4b Use Ubuntu 16.04 2018-12-16 22:29:50 +01:00
cdf40f774b Travis CI linux build fix 2018-12-16 22:22:25 +01:00
0950caae0d Merge branch 'master' of https://github.com/tumic0/QtPBFImagePlugin 2018-12-16 22:14:55 +01:00
0188273c29 Added Travis CI config 2018-12-16 22:14:10 +01:00
13d682bdf6 Added external layers info.
closes #3
2018-12-16 17:54:56 +01:00
738ada91c2 Added missing source file 2018-12-12 01:15:19 +01:00
6d02dafeb6 Improved fuzzy text bounding rect computation 2018-12-11 18:48:12 +01:00
a559b5237b Cosmetics 2018-12-09 11:25:27 +01:00
12b91c9c6f Added applications using the plugin 2018-12-09 11:22:33 +01:00
1677eb7a67 Added OBS link & info 2018-12-08 18:44:37 +01:00
03f4315a5b Removed obsolete code 2018-12-08 11:14:34 +01:00
a3d555bb6a Code cleanup 2018-12-07 00:50:11 +01:00
a83d4909ed Updated build requirements 2018-12-05 23:04:36 +01:00
e45e86e5d7 Some more missing includes 2018-12-05 22:34:26 +01:00
0476f5740e Added missing include 2018-12-05 22:20:29 +01:00
24 changed files with 440 additions and 306 deletions

22
.travis.yml Normal file
View File

@ -0,0 +1,22 @@
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
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

@ -3,8 +3,8 @@ Qt image plugin for displaying Mapbox vector tiles
## Description
QtPBFImagePlugin is a Qt image plugin that enables applications capable of
displaying raster MBTiles maps or raster XYZ online maps to also display PBF
vector tiles without (almost, see usage) any application modifications.
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.
@ -40,7 +40,7 @@ you will get 512x512px tiles with a pixel ratio of 2 (= HiDPI tiles).
## Build
### Requirements
* Qt 5
* Qt >= 5.4 (5.6 for HiDPI support)
* Google Protocol Buffers (protobuf-lite)
* Zlib
@ -63,10 +63,14 @@ make
## Install
Copy the plugin to the system Qt image plugins path to make it work. You may
also set the QT_PLUGIN_PATH system variable before starting the application.
also set the QT_PLUGIN_PATH system variable before starting the application. For
Linux, there are RPM and DEB [packages](https://build.opensuse.org/project/show/home:tumic:QtPBFImagePlugin)
for most common distros available on OBS.
## Limitations
Text PBF features must have a unique id (OpenMapTiles >= v3.7) for the text layout
* Only data that is part of the PBF file is displayed. External layers defined in the
style are ignored.
* Text PBF features must have a unique id (OpenMapTiles >= v3.7) for the text layout
algorithm to work properly.
## Status
@ -85,3 +89,5 @@ A picture is worth a thousand words. Data and styles from https://openmaptiles.o
![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)
## Applications using QtPBFImagePlugin
* [GPXSee](https://www.gpxsee.org)

View File

@ -1,4 +1,4 @@
{
"Keys": [ "pbf" ],
"MimeTypes": [ "image/pbf" ]
"Keys": [ "mvt" ],
"MimeTypes": [ "application/vnd.mapbox-vector-tile" ]
}

View File

@ -20,7 +20,8 @@ HEADERS += src/pbfhandler.h \
src/textpointitem.h \
src/font.h \
src/textitem.h \
src/sprites.h
src/sprites.h \
src/config.h
SOURCES += src/pbfplugin.cpp \
src/pbfhandler.cpp \
src/gzip.cpp \
@ -32,7 +33,8 @@ SOURCES += src/pbfplugin.cpp \
src/textpathitem.cpp \
src/textpointitem.cpp \
src/font.cpp \
src/sprites.cpp
src/sprites.cpp \
src/textitem.cpp
RESOURCES += pbfplugin.qrc
unix:!macx{

9
pkg/pbfplugin.xml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/vnd.mapbox-vector-tile">
<comment>Mapbox Vector Tile</comment>
<sub-class-of type="application/octet-stream"/>
<generic-icon name="application/octet-stream"/>
<glob pattern="*.mvt"/>
</mime-type>
</mime-info>

View File

@ -13,9 +13,7 @@ QColor Color::fromJsonString(const QString &str)
{
QColor ret;
if (str.startsWith('#'))
return QColor(str);
else if (str.startsWith("rgb(")) {
if (str.startsWith("rgb(")) {
QStringList comp(str.mid(4, str.size() - 5).split(','));
if (comp.size() != 3)
return QColor();
@ -39,7 +37,8 @@ QColor Color::fromJsonString(const QString &str)
return QColor();
ret = QColor::fromHslF(comp.at(0).toFloat() / 360.0, pval(comp.at(1)),
pval(comp.at(2)), comp.at(3).toFloat());
}
} else
ret = QColor(str);
if (!ret.isValid())
qWarning() << str << ": invalid color";

10
src/config.h Normal file
View File

@ -0,0 +1,10 @@
#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

@ -4,6 +4,8 @@
#include <QList>
#include <QPointF>
#include <QColor>
#include <QPair>
#include <QString>
class QJsonObject;

View File

@ -1,21 +1,20 @@
#include <QByteArray>
#include <QPainter>
#include <QDebug>
#include <QVariantHash>
#include "vector_tile.pb.h"
#include "style.h"
#include "tile.h"
#include "pbf.h"
#define MOVE_TO 1
#define LINE_TO 2
#define CLOSE_PATH 7
#define POLYGON vector_tile::Tile_GeomType_POLYGON
#define LINESTRING vector_tile::Tile_GeomType_LINESTRING
#define POINT vector_tile::Tile_GeomType_POINT
static inline qint32 zigzag32decode(quint32 value)
{
return static_cast<qint32>((value >> 1u) ^ static_cast<quint32>(
-static_cast<qint32>(value & 1u)));
}
static inline QPoint parameters(quint32 v1, quint32 v2)
{
return QPoint(zigzag32decode(v1), zigzag32decode(v2));
}
static QVariant value(const vector_tile::Tile_Value &val)
{
@ -37,100 +36,35 @@ static QVariant value(const vector_tile::Tile_Value &val)
return QVariant();
}
class Feature
const QVariant *PBF::Feature::value(const QString &key) const
{
public:
Feature() : _data(0) {}
Feature(const vector_tile::Tile_Feature *data, const QVector<QString> *keys,
const QVector<QVariant> *values) : _data(data)
{
for (int i = 0; i < data->tags_size(); i = i + 2)
_tags.insert(keys->at(data->tags(i)), values->at(data->tags(i+1)));
const KeyHash &keys(_layer->keys());
KeyHash::const_iterator it(keys.find(key));
if (it == keys.constEnd())
return 0;
switch (data->type()) {
case POLYGON:
_tags.insert("$type", QVariant("Polygon"));
break;
case LINESTRING:
_tags.insert("$type", QVariant("LineString"));
break;
case POINT:
_tags.insert("$type", QVariant("Point"));
break;
default:
break;
}
}
google::protobuf::uint32 index = *it;
for (int i = 0; i < _data->tags_size(); i = i + 2)
if (_data->tags(i) == index)
return &(_layer->values().at(_data->tags(i+1)));
const QVariantHash &tags() const {return _tags;}
const vector_tile::Tile_Feature *data() const {return _data;}
private:
QVariantHash _tags;
const vector_tile::Tile_Feature *_data;
};
bool cmp(const Feature &f1, const Feature &f2)
{
return f1.data()->id() < f2.data()->id();
return 0;
}
class Layer
QPainterPath PBF::Feature::path(const QSizeF &factor) const
{
public:
Layer(const vector_tile::Tile_Layer *data) : _data(data)
{
QVector<QString> keys;
QVector<QVariant> values;
for (int i = 0; i < data->keys_size(); i++)
keys.append(QString::fromStdString(data->keys(i)));
for (int i = 0; i < data->values_size(); i++)
values.append(value(data->values(i)));
_features.reserve(data->features_size());
for (int i = 0; i < data->features_size(); i++)
_features.append(Feature(&(data->features(i)), &keys, &values));
qSort(_features.begin(), _features.end(), cmp);
}
const QVector<Feature> &features() const {return _features;}
const vector_tile::Tile_Layer *data() const {return _data;}
private:
const vector_tile::Tile_Layer *_data;
QVector<Feature> _features;
};
static inline qint32 zigzag32decode(quint32 value)
{
return static_cast<qint32>((value >> 1u) ^ static_cast<quint32>(
-static_cast<qint32>(value & 1u)));
}
static inline QPoint parameters(quint32 v1, quint32 v2)
{
return QPoint(zigzag32decode(v1), zigzag32decode(v2));
}
static void drawFeature(const Feature &feature, Style *style, int styleLayer,
const QSizeF &factor, Tile &tile)
{
if (!style->match(tile.zoom(), styleLayer, feature.tags()))
return;
QPoint cursor;
QPainterPath path;
for (int i = 0; i < feature.data()->geometry_size(); i++) {
quint32 g = feature.data()->geometry(i);
for (int i = 0; i < _data->geometry_size(); i++) {
quint32 g = _data->geometry(i);
unsigned cmdId = g & 0x7;
unsigned cmdCount = g >> 3;
if (cmdId == MOVE_TO) {
for (unsigned j = 0; j < cmdCount; j++) {
QPoint offset = parameters(feature.data()->geometry(i+1),
feature.data()->geometry(i+2));
QPoint offset = parameters(_data->geometry(i+1),
_data->geometry(i+2));
i += 2;
cursor += offset;
path.moveTo(QPointF(cursor.x() * factor.width(),
@ -138,8 +72,8 @@ static void drawFeature(const Feature &feature, Style *style, int styleLayer,
}
} else if (cmdId == LINE_TO) {
for (unsigned j = 0; j < cmdCount; j++) {
QPoint offset = parameters(feature.data()->geometry(i+1),
feature.data()->geometry(i+2));
QPoint offset = parameters(_data->geometry(i+1),
_data->geometry(i+2));
i += 2;
cursor += offset;
path.lineTo(QPointF(cursor.x() * factor.width(),
@ -151,60 +85,35 @@ static void drawFeature(const Feature &feature, Style *style, int styleLayer,
}
}
if (path.elementCount())
style->drawFeature(tile, styleLayer, path, feature.tags());
return path;
}
static void drawLayer(const Layer &layer, Style *style, int styleLayer,
Tile &tile)
PBF::Layer::Layer(const vector_tile::Tile_Layer *data) : _data(data)
{
if (layer.data()->version() > 2)
return;
_keys.reserve(data->keys_size());
for (int i = 0; i < data->keys_size(); i++)
_keys.insert(QString::fromStdString(data->keys(i)), i);
_values.reserve(data->values_size());
for (int i = 0; i < data->values_size(); i++)
_values.append(value(data->values(i)));
QSizeF factor(tile.size().width() / tile.scale().x() /
(qreal)layer.data()->extent(), tile.size().height() / tile.scale().y()
/ (qreal)layer.data()->extent());
tile.painter().save();
style->setupLayer(tile, styleLayer);
for (int i = 0; i < layer.features().size(); i++)
drawFeature(layer.features().at(i), style, styleLayer, factor, tile);
tile.painter().restore();
_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());
}
bool PBF::render(const QByteArray &data, int zoom, Style *style,
const QPointF &scale, QImage *image)
PBF::PBF(const vector_tile::Tile &tile)
{
vector_tile::Tile tile;
if (!tile.ParseFromArray(data.constData(), data.size())) {
qCritical() << "Invalid tile protocol buffer data";
return false;
}
Tile t(image, zoom, scale);
style->drawBackground(t);
// Prepare source layers
QMap<QString, Layer> layers;
for (int i = 0; i < tile.layers_size(); i++) {
const vector_tile::Tile_Layer &layer = tile.layers(i);
QString name(QString::fromStdString(layer.name()));
if (style->sourceLayers().contains(name))
layers.insert(name, Layer(&layer));
_layers.insert(QString::fromStdString(layer.name()), new Layer(&layer));
}
// Process source layers in order of style layers
for (int i = 0; i < style->sourceLayers().size(); i++) {
QMap<QString, Layer>::const_iterator it = layers.find(
style->sourceLayers().at(i));
if (it == layers.constEnd())
continue;
drawLayer(*it, style, i, t);
}
t.text().render(&t.painter());
return true;
}
PBF::~PBF()
{
for (QHash<QString, Layer*>::iterator it = _layers.begin();
it != _layers.end(); it++)
delete *it;
}

View File

@ -1,15 +1,67 @@
#ifndef PBF_H
#define PBF_H
#include <QImage>
#include <QVariant>
#include <QVector>
#include <QHash>
#include <QPainterPath>
#include "vector_tile.pb.h"
class QByteArray;
class Style;
namespace PBF
typedef QHash<QString, google::protobuf::uint32> KeyHash;
class PBF
{
bool render(const QByteArray &data, int zoom, Style *style,
const QPointF &scale, QImage *render);
}
public:
class Layer;
class Feature
{
public:
Feature() : _data(0), _layer(0) {}
Feature(const vector_tile::Tile_Feature *data, const Layer *layer)
: _data(data), _layer(layer) {}
const QVariant *value(const QString &key) const;
vector_tile::Tile_GeomType type() const {return _data->type();}
QPainterPath path(const QSizeF &factor) const;
friend bool operator<(const Feature &f1, const Feature &f2);
private:
const vector_tile::Tile_Feature *_data;
const Layer *_layer;
};
class Layer
{
public:
Layer(const vector_tile::Tile_Layer *data);
const QVector<Feature> &features() const {return _features;}
const QVector<QVariant> &values() const {return _values;}
const KeyHash &keys() const {return _keys;}
const vector_tile::Tile_Layer *data() const {return _data;}
private:
const vector_tile::Tile_Layer *_data;
QVector<Feature> _features;
QVector<QVariant> _values;
KeyHash _keys;
};
PBF(const vector_tile::Tile &tile);
~PBF();
const QHash<QString, Layer*> &layers() const {return _layers;}
private:
QHash<QString, Layer*> _layers;
};
inline bool operator<(const PBF::Feature &f1, const PBF::Feature &f2)
{return f1._data->id() < f2._data->id();}
#endif // PBF_H

View File

@ -1,8 +1,10 @@
#include <QImage>
#include <QIODevice>
#include <QtEndian>
#include <QDebug>
#include "gzip.h"
#include "pbf.h"
#include "tile.h"
#include "style.h"
#include "pbfhandler.h"
@ -61,6 +63,11 @@ bool PBFHandler::read(QImage *image)
ba = device()->readAll();
if (ba.isNull())
return false;
vector_tile::Tile data;
if (!data.ParseFromArray(ba.constData(), ba.size())) {
qCritical() << "Invalid PBF data";
return false;
}
bool ok;
int zoom = format().toInt(&ok);
@ -71,8 +78,11 @@ bool PBFHandler::read(QImage *image)
? QPointF(_scaledSize.width() / TILE_SIZE, _scaledSize.height() / TILE_SIZE)
: QPointF(1.0, 1.0);
*image = QImage(size, QImage::Format_ARGB32_Premultiplied);
Tile tile(image, ok ? zoom : -1, scale);
return PBF::render(ba, ok ? zoom : -1, _style, scale, image);
_style->render(data, tile);
return true;
}
bool PBFHandler::supportsOption(ImageOption option) const

View File

@ -2,16 +2,16 @@
#define PBFHANDLER_H
#include <QImageIOHandler>
#include <QImage>
#include <QVariant>
#include <QSize>
class QImage;
class Style;
class PBFHandler : public QImageIOHandler
{
public:
PBFHandler(Style *style) : _style(style) {}
PBFHandler(const Style *style) : _style(style) {}
~PBFHandler() {}
bool canRead() const;
@ -19,12 +19,12 @@ public:
QVariant option(ImageOption option) const;
bool supportsOption(ImageOption option) const;
void setOption(QImageIOHandler::ImageOption option, const QVariant &value);
void setOption(ImageOption option, const QVariant &value);
static bool canRead(QIODevice *device);
private:
Style *_style;
const Style *_style;
QSize _scaledSize;
};

View File

@ -21,7 +21,7 @@ QImageIOPlugin::Capabilities PBFPlugin::capabilities(QIODevice *device,
const QByteArray &format) const
{
if (device == 0)
return (format == "pbf") ? Capabilities(CanRead) : Capabilities();
return (format == "mvt") ? Capabilities(CanRead) : Capabilities();
else
return (device->isReadable() && PBFHandler::canRead(device))
? Capabilities(CanRead) : Capabilities();

View File

@ -83,6 +83,9 @@ bool Sprites::load(const QString &jsonFile, const QString &imageFile)
QImage Sprites::icon(const QString &name) const
{
if (_imageFile.isEmpty())
return QImage();
const QImage *img = atlas(_imageFile);
if (img->isNull())
return QImage();

View File

@ -4,6 +4,9 @@
#include <QRect>
#include <QMap>
#include <QImage>
#include <QString>
class QJsonObject;
class Sprites
{

View File

@ -10,9 +10,22 @@
#include "color.h"
#include "font.h"
#include "tile.h"
#include "pbf.h"
#include "style.h"
static vector_tile::Tile_GeomType geometryType(const QString &str)
{
if (str == "Point")
return vector_tile::Tile_GeomType_POINT;
else if (str == "LineString")
return vector_tile::Tile_GeomType_LINESTRING;
else if (str == "Polygon")
return vector_tile::Tile_GeomType_POLYGON;
else
return vector_tile::Tile_GeomType_UNKNOWN;
}
Style::Layer::Filter::Filter(const QJsonArray &json)
: _type(Unknown), _not(false)
{
@ -27,9 +40,15 @@ Style::Layer::Filter::Filter(const QJsonArray &json)
if (type == "==") {
if (json.size() != 3)
INVALID_FILTER(json);
_type = EQ;
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
if (json.at(1).toString() == "$type") {
_type = GeometryType;
_kv = QPair<QString, QVariant>(QString(),
QVariant(geometryType(json.at(2).toString())));
} else {
_type = EQ;
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
}
} else if (type == "!=") {
if (json.size() != 3)
INVALID_FILTER(json);
@ -98,45 +117,68 @@ Style::Layer::Filter::Filter(const QJsonArray &json)
INVALID_FILTER(json);
}
bool Style::Layer::Filter::match(const QVariantHash &tags) const
bool Style::Layer::Filter::match(const PBF::Feature &feature) const
{
const QVariant *v;
switch (_type) {
case None:
return true;
case EQ:
return tags.value(_kv.first) == _kv.second;
if (!(v = feature.value(_kv.first)))
return false;
else
return *v == _kv.second;
case NE:
return tags.value(_kv.first) != _kv.second;
if (!(v = feature.value(_kv.first)))
return true;
else
return *v != _kv.second;
case GT:
return tags.value(_kv.first) > _kv.second;
if (!(v = feature.value(_kv.first)))
return false;
else
return *v > _kv.second;
case GE:
return tags.value(_kv.first) >= _kv.second;
if (!(v = feature.value(_kv.first)))
return false;
else
return *v >= _kv.second;
case LT:
return tags.value(_kv.first) < _kv.second;
if (!(v = feature.value(_kv.first)))
return false;
else
return *v < _kv.second;
case LE:
return tags.value(_kv.first) <= _kv.second;
if (!(v = feature.value(_kv.first)))
return false;
else
return *v <= _kv.second;
case In:
return _set.contains(tags.value(_kv.first).toString()) ^ _not;
if (!(v = feature.value(_kv.first)))
return _not;
else
return _set.contains((*v).toString()) ^ _not;
case Has:
return tags.contains(_kv.first) ^ _not;
return (feature.value(_kv.first) ? true : false) ^ _not;
case All:
for (int i = 0; i < _filters.size(); i++) {
if (!_filters.at(i).match(tags))
for (int i = 0; i < _filters.size(); i++)
if (!_filters.at(i).match(feature))
return false;
}
return true;
case Any:
for (int i = 0; i < _filters.size(); i++) {
if (_filters.at(i).match(tags))
for (int i = 0; i < _filters.size(); i++)
if (_filters.at(i).match(feature))
return true;
}
return false;
case GeometryType:
return feature.type() == _kv.second.toUInt();
default:
return false;
}
}
QString Style::Layer::Template::value(int zoom, const QVariantHash &tags) const
QString Style::Layer::Template::value(int zoom, const PBF::Feature &feature) const
{
QRegExp rx = QRegExp("\\{[^\\}]*\\}");
QString text(_field.value(zoom));
@ -150,8 +192,8 @@ QString Style::Layer::Template::value(int zoom, const QVariantHash &tags) const
}
for (int i = 0; i < keys.size(); i++) {
const QString &key = keys.at(i);
const QVariant val = tags.value(key);
text.replace(QString("{%1}").arg(key), val.toString());
const QVariant *val = feature.value(key);
text.replace(QString("{%1}").arg(key), val ? val->toString() : "");
}
return text;
@ -428,7 +470,7 @@ Style::Layer::Layer(const QJsonObject &json)
_paint = Paint(json["paint"].toObject());
}
bool Style::Layer::match(int zoom, const QVariantHash &tags) const
bool Style::Layer::match(int zoom, const PBF::Feature &feature) const
{
if (zoom >= 0) {
if (_minZoom > 0 && zoom < _minZoom)
@ -437,7 +479,7 @@ bool Style::Layer::match(int zoom, const QVariantHash &tags) const
return false;
}
return _filter.match(tags);
return _filter.match(feature);
}
void Style::Layer::setPathPainter(Tile &tile, const Sprites &sprites) const
@ -472,13 +514,13 @@ void Style::Layer::setTextProperties(Tile &tile) const
}
void Style::Layer::addSymbol(Tile &tile, const QPainterPath &path,
const QVariantHash &tags, const Sprites &sprites) const
const PBF::Feature &feature, const Sprites &sprites) const
{
QString text = _layout.text(tile.zoom(), tags);
QString text = _layout.text(tile.zoom(), feature);
if (text.isEmpty())
return;
QString icon = _layout.icon(tile.zoom(), tags);
QString icon = _layout.icon(tile.zoom(), feature);
tile.text().addLabel(text, sprites.icon(icon), path);
}
@ -525,39 +567,32 @@ bool Style::load(const QString &fileName)
_layers.append(Layer(layers[i].toObject()));
}
for (int i = 0; i < _layers.size(); i++)
_sourceLayers.append(_layers.at(i).sourceLayer());
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;
}
void Style::setupLayer(Tile &tile, int layer) const
const Sprites &Style::sprites(const QPointF &scale) const
{
const Layer &sl = _layers.at(layer);
const Sprites &sprites = (tile.scale().x() > 1.0 || tile.scale().y() > 1.0)
#ifdef ENABLE_HIDPI
return (scale.x() > 1.0 || scale.y() > 1.0)
&& !_sprites2x.isNull() ? _sprites2x : _sprites;
if (sl.isSymbol())
sl.setTextProperties(tile);
else if (sl.isPath())
sl.setPathPainter(tile, sprites);
#else // ENABLE_HIDPI
Q_UNUSED(scale);
return _sprites;
#endif // ENABLE_HIDPI
}
void Style::drawFeature(Tile &tile, int layer, const QPainterPath &path,
const QVariantHash &tags) const
void Style::setupLayer(Tile &tile, const Layer &layer) const
{
const Layer &sl = _layers.at(layer);
const Sprites &sprites = (tile.scale().x() > 1.0 || tile.scale().y() > 1.0)
&& !_sprites2x.isNull() ? _sprites2x : _sprites;
if (sl.isPath())
tile.painter().drawPath(path);
else if (sl.isSymbol())
sl.addSymbol(tile, path, tags, sprites);
if (layer.isSymbol())
layer.setTextProperties(tile);
else if (layer.isPath())
layer.setPathPainter(tile, sprites(tile.scale()));
}
void Style::drawBackground(Tile &tile) const
@ -578,3 +613,52 @@ void Style::drawBackground(Tile &tile) const
//tile.painter().setPen(Qt::red);
//tile.painter().drawRect(rect);
}
void Style::drawFeature(const PBF::Feature &feature, const Layer &layer,
Tile &tile, const QSizeF &factor) const
{
if (!layer.match(tile.zoom(), feature))
return;
QPainterPath path(feature.path(factor));
if (!path.elementCount())
return;
if (layer.isPath())
tile.painter().drawPath(path);
else if (layer.isSymbol())
layer.addSymbol(tile, path, feature, sprites(tile.scale()));
}
void Style::drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer,
Tile &tile) const
{
if (pbfLayer.data()->version() > 2)
return;
QSizeF factor(tile.size().width() / tile.scale().x() /
(qreal)pbfLayer.data()->extent(), tile.size().height() / tile.scale().y()
/ (qreal)pbfLayer.data()->extent());
tile.painter().save();
setupLayer(tile, styleLayer);
for (int i = 0; i < pbfLayer.features().size(); i++)
drawFeature(pbfLayer.features().at(i), styleLayer, tile, factor);
tile.painter().restore();
}
void Style::render(const PBF &data, Tile &tile) const
{
drawBackground(tile);
for (int i = 0; i < _layers.size(); i++) {
QHash<QString, PBF::Layer*>::const_iterator it = data.layers().find(
_layers.at(i).sourceLayer());
if (it == data.layers().constEnd())
continue;
drawLayer(**it, _layers.at(i), tile);
}
tile.text().render(&tile.painter());
}

View File

@ -3,12 +3,16 @@
#include <QObject>
#include <QString>
#include <QVector>
#include <QPair>
#include <QVariantHash>
#include <QStringList>
#include <QSet>
#include <QPen>
#include <QBrush>
#include <QFont>
#include "pbf.h"
#include "config.h"
#include "text.h"
#include "function.h"
#include "sprites.h"
@ -16,6 +20,8 @@
class QPainter;
class QPainterPath;
class QJsonArray;
class QJsonObject;
class Tile;
class Style : public QObject
@ -24,16 +30,7 @@ public:
Style(QObject *parent = 0) : QObject(parent) {}
bool load(const QString &fileName);
const QStringList &sourceLayers() const
{return _sourceLayers;}
bool match(int zoom, int layer, const QVariantHash &tags) const
{return _layers.at(layer).match(zoom, tags);}
void drawBackground(Tile &tile) const;
void setupLayer(Tile &tile, int layer) const;
void drawFeature(Tile &tile, int layer, const QPainterPath &path,
const QVariantHash &tags) const;
void render(const PBF &data, Tile &tile) const;
private:
class Layer {
@ -46,11 +43,11 @@ private:
bool isBackground() const {return (_type == Background);}
bool isSymbol() const {return (_type == Symbol);}
bool match(int zoom, const QVariantHash &tags) const;
bool match(int zoom, const PBF::Feature &feature) const;
void setPathPainter(Tile &tile, const Sprites &sprites) const;
void setTextProperties(Tile &tile) const;
void addSymbol(Tile &tile, const QPainterPath &path,
const QVariantHash &tags, const Sprites &sprites) const;
const PBF::Feature &feature, const Sprites &sprites) const;
private:
enum Type {
@ -66,13 +63,13 @@ private:
Filter() : _type(None) {}
Filter(const QJsonArray &json);
bool match(const QVariantHash &tags) const;
bool match(const PBF::Feature &feature) const;
private:
enum Type {
None, Unknown,
EQ, NE, GE, GT, LE, LT,
All, Any,
In, Has
In, Has, GeometryType
};
Type _type;
@ -86,7 +83,7 @@ private:
public:
Template() {}
Template(const FunctionS &str) : _field(str) {}
QString value(int zoom, const QVariantHash &tags) const;
QString value(int zoom, const PBF::Feature &feature) const;
private:
FunctionS _field;
@ -102,10 +99,10 @@ private:
{return _textMaxWidth.value(zoom);}
qreal maxTextAngle(int zoom) const
{return _textMaxAngle.value(zoom);}
QString text(int zoom, const QVariantHash &tags) const
{return _text.value(zoom, tags).trimmed();}
QString icon(int zoom, const QVariantHash &tags) const
{return _icon.value(zoom, tags);}
QString text(int zoom, const PBF::Feature &feature) const
{return _text.value(zoom, feature).trimmed();}
QString icon(int zoom, const PBF::Feature &feature) const
{return _icon.value(zoom, feature);}
QFont font(int zoom) const;
Qt::PenCapStyle lineCap(int zoom) const;
Qt::PenJoinStyle lineJoin(int zoom) const;
@ -140,8 +137,6 @@ private:
const;
qreal opacity(Layer::Type type, int zoom) const;
bool antialias(Layer::Type type, int zoom) const;
QString fillPattern(int zoom) const
{return _fillPattern.value(zoom);}
private:
FunctionC _textColor;
@ -165,9 +160,20 @@ private:
Paint _paint;
};
const Sprites &sprites(const QPointF &scale) const;
void drawBackground(Tile &tile) const;
void setupLayer(Tile &tile, const Layer &layer) const;
void drawFeature(const PBF::Feature &feature, const Layer &layer,
Tile &tile, const QSizeF &factor) const;
void drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer,
Tile &tile) const;
QVector<Layer> _layers;
QStringList _sourceLayers;
Sprites _sprites, _sprites2x;
Sprites _sprites;
#ifdef ENABLE_HIDPI
Sprites _sprites2x;
#endif // QT >= 5.6
};
#endif // STYLE_H

View File

@ -1,5 +1,6 @@
#include <QFontMetrics>
#include <QPainter>
#include <QSet>
#include "text.h"
#include "textpointitem.h"
#include "textpathitem.h"

35
src/textitem.cpp Normal file
View File

@ -0,0 +1,35 @@
#include "textitem.h"
bool TextItem::collidesWithItem(const TextItem *other) const
{
QRectF r1(boundingRect());
QRectF r2(other->boundingRect());
if (r1.isEmpty() || r2.isEmpty() || !r1.intersects(r2))
return false;
return other->shape().intersects(shape());
}
int TextItem::avgCharWidth() const
{
qreal ratio;
ushort cp = _text.at(0).unicode();
// CJK & East Asian scripts
if (cp >= 0x2E80)
ratio = 1.0;
// Greek & Cyrilic
else if (cp >= 0x03FF && cp <= 0x04FF) {
ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.75 : 0.67;
if (_font.bold())
ratio *= 1.1;
// The rest (Latin scripts, Arabic, ...)
} else {
ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.7 : 0.58;
if (_font.bold())
ratio *= 1.1;
}
return ratio * _font.pixelSize();
}

View File

@ -1,18 +1,23 @@
#ifndef TEXTITEM_H
#define TEXTITEM_H
#include <QString>
#include <QPainterPath>
#include <QPen>
#include <QFont>
#include <QRectF>
class QPainter;
class TextItem
{
public:
TextItem(const QString &text) : _text(text), _visible(true) {}
TextItem(const QString &text, const QFont &font)
: _text(text), _font(font), _visible(true) {}
virtual ~TextItem() {}
const QString &text() const {return _text;}
const QFont &font() const {return _font;}
void setFont(const QFont &font) {_font = font;}
const QPen &pen() const {return _pen;}
void setPen(const QPen &pen) {_pen = pen;}
@ -23,33 +28,10 @@ public:
bool isVisible() const {return _visible;}
void setVisible(bool visible) {_visible = visible;}
bool collidesWithItem(const TextItem *other) const
{
QRectF r1(boundingRect());
QRectF r2(other->boundingRect());
if (r1.isEmpty() || r2.isEmpty() || !r1.intersects(r2))
return false;
return other->shape().intersects(shape());
}
bool collidesWithItem(const TextItem *other) const;
protected:
static int avgCharWidth(const QString &str, const QFont &font)
{
qreal ratio;
if (str.at(0).unicode() >= 0x2E80)
ratio = 1.0;
else {
ratio = (font.capitalization() == QFont::AllUppercase)
? 0.66 : 0.55;
if (font.bold())
ratio *= 1.1;
}
return ratio * font.pixelSize();
}
int avgCharWidth() const;
private:
QString _text;

View File

@ -134,9 +134,9 @@ static bool reverse(const QPainterPath &path)
TextPathItem::TextPathItem(const QString &text, const QPainterPath &path,
const QFont &font, int maxAngle, const QRectF &tileRect)
: TextItem(text)
: TextItem(text, font)
{
int cw = avgCharWidth(text, font);
int cw = avgCharWidth();
int textWidth = text.size() * cw;
if (textWidth > path.length())
return;
@ -146,7 +146,6 @@ TextPathItem::TextPathItem(const QString &text, const QPainterPath &path,
return;
_path = reverse(tp) ? tp.toReversed() : tp;
setFont(font);
QPainterPathStroker s;
s.setWidth(font.pixelSize());

View File

@ -1,10 +1,7 @@
#ifndef TEXTPATHITEM_H
#define TEXTPATHITEM_H
#include <QFont>
#include <QString>
#include "textitem.h"
#include "text.h"
class TextPathItem : public TextItem
{

View File

@ -1,41 +1,37 @@
#include <QPainter>
#include <QtMath>
#include "config.h"
#include "textpointitem.h"
#define FLAGS (Qt::AlignCenter | Qt::TextWordWrap | Qt::TextDontClip)
QRectF TextPointItem::exactBoundingRect(const QString &str,
const QFont &font, int maxWidth)
QRectF TextPointItem::exactBoundingRect() const
{
QFontMetrics fm(font);
int limit = font.pixelSize() * maxWidth;
QFontMetrics fm(font());
int limit = font().pixelSize() * _maxWidth;
// Italic fonts overflow the computed bounding rect, so reduce it
// a little bit.
if (font.italic())
limit -= font.pixelSize() / 2.0;
if (font().italic())
limit -= font().pixelSize() / 2.0;
QRect br = fm.boundingRect(QRect(0, 0, limit, 0), FLAGS, str);
QRect br = fm.boundingRect(QRect(0, 0, limit, 0), FLAGS, text());
Q_ASSERT(br.isValid());
// Expand the bounding rect back to the real content size
if (font.italic())
br.adjust(-font.pixelSize() / 4.0, 0, font.pixelSize() / 4.0, 0);
if (font().italic())
br.adjust(-font().pixelSize() / 4.0, 0, font().pixelSize() / 4.0, 0);
return br;
}
QRectF TextPointItem::fuzzyBoundingRect(const QString &str,
const QFont &font, int maxWidth)
QRectF TextPointItem::fuzzyBoundingRect() const
{
int limit = font.pixelSize() * maxWidth;
qreal cw = avgCharWidth(str, font);
qreal lh = font.pixelSize() * 1.25;
int limit = font().pixelSize() * _maxWidth;
qreal cw = avgCharWidth();
qreal lh = font().pixelSize() * 1.25;
int width = 0, lines = 0;
if (font.italic())
limit -= font.pixelSize() / 2.0;
QStringList l(str.split('\n'));
QStringList l(text().split('\n'));
for (int i = 0; i < l.size(); i++) {
int lw = (int)(l.at(i).length() * cw);
if (lw > limit) {
@ -69,11 +65,16 @@ QRectF TextPointItem::fuzzyBoundingRect(const QString &str,
}
QRectF TextPointItem::computeTextRect(BoundingRectFunction brf) const
QRectF TextPointItem::computeTextRect(bool exact) const
{
#ifdef ENABLE_HIDPI
QRectF iconRect = _icon.isNull() ? QRectF()
: QRectF(QPointF(0, 0), QSizeF(_icon.size()) / _icon.devicePixelRatioF());
QRectF textRect = brf(text(), font(), _maxWidth);
#else // ENABLE_HIDPI
QRectF iconRect = _icon.isNull() ? QRectF() : QRectF(QPointF(0, 0),
QSizeF(_icon.size()));
#endif // ENABLE_HIDPI
QRectF textRect = exact ? exactBoundingRect() : fuzzyBoundingRect();
switch (_anchor) {
case Text::Center:
@ -102,15 +103,18 @@ QRectF TextPointItem::computeTextRect(BoundingRectFunction brf) const
TextPointItem::TextPointItem(const QString &text, const QPointF &pos,
const QFont &font, int maxWidth, Text::Anchor anchor, const QImage &icon)
: TextItem(text), _pos(pos), _icon(icon), _maxWidth(maxWidth),
: TextItem(text, font), _pos(pos), _icon(icon), _maxWidth(maxWidth),
_anchor(anchor)
{
setFont(font);
_boundingRect = computeTextRect(fuzzyBoundingRect);
_boundingRect = computeTextRect(false);
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;
}
@ -126,12 +130,18 @@ void TextPointItem::paint(QPainter *painter) const
QRectF textRect;
if (!_icon.isNull()) {
textRect = computeTextRect(exactBoundingRect);
textRect = (_anchor != Text::Center)
? computeTextRect(true) : _boundingRect;
#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 = computeTextRect(fuzzyBoundingRect);
textRect = _boundingRect;
painter->setFont(font());
painter->setPen(pen());

View File

@ -1,11 +1,9 @@
#ifndef TEXTPOINTITEM_H
#define TEXTPOINTITEM_H
#include <QPen>
#include <QFont>
#include <QString>
#include "textitem.h"
#include <QImage>
#include "text.h"
#include "textitem.h"
class TextPointItem : public TextItem
{
@ -18,14 +16,9 @@ public:
void paint(QPainter *painter) const;
private:
typedef QRectF (*BoundingRectFunction)(const QString &, const QFont &, int);
static QRectF exactBoundingRect(const QString &str, const QFont &font,
int maxWidth);
static QRectF fuzzyBoundingRect(const QString &str, const QFont &font,
int maxWidth);
QRectF computeTextRect(BoundingRectFunction brf) const;
QRectF exactBoundingRect() const;
QRectF fuzzyBoundingRect() const;
QRectF computeTextRect(bool exact) const;
QPointF _pos;
QPainterPath _shape;