Compare commits

..

No commits in common. "master" and "3.1" have entirely different histories.
master ... 3.1

19 changed files with 305 additions and 556 deletions

View File

@ -1,4 +1,4 @@
version: 4.2.{build}
version: 3.1.{build}
configuration:
- Release
@ -7,25 +7,26 @@ image:
- Visual Studio 2022
environment:
VCPKGDIR: C:\tools\vcpkg\installed\x64-windows-static-md-release
VCPKGDIR: C:\tools\vcpkg\installed\x64-windows-static-md
matrix:
- QTDIR: C:\Qt\5.15\msvc2019_64
- QTDIR: C:\Qt\6.8\msvc2022_64
- QTDIR: C:\Qt\6.7\msvc2019_64
install:
- cmd: |-
set PATH=%QTDIR%\bin;%PATH%
set PATH=%QTDIR%\bin;%VCPKGDIR%\tools\protobuf;%PATH%
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat
vcpkg install zlib:x64-windows-static-md-release
vcpkg install protobuf:x64-windows-static-md
vcpkg install zlib:x64-windows-static-md
copy /y %VCPKGDIR%\lib\zlib.lib %VCPKGDIR%\lib\zlibstatic.lib
build_script:
- cmd: |-
qmake ZLIB=%VCPKGDIR% pbfplugin.pro
qmake PROTOBUF=%VCPKGDIR% ZLIB=%VCPKGDIR% pbfplugin.pro
nmake release
artifacts:
- path: release\pbf4.dll
- path: release\pbf3.dll
cache:
- C:\tools\vcpkg\installed\

View File

@ -12,6 +12,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install protobuf compiler
run: |
sudo apt-get update
sudo apt-get install protobuf-compiler
- name: set up JDK 11
uses: actions/setup-java@v4
with:
@ -37,10 +41,12 @@ jobs:
version: '6.4.0'
target: 'android'
arch: 'android_arm64_v8a'
- name: Install Android Google Protocol Buffers
run: git clone https://github.com/tumic0/android_protobuf.git
- name: Configure build
run: qmake pbfplugin.pro
run: qmake pbfplugin.pro PROTOBUF=android_protobuf
- name: Build project
run: make -j4
run: make -j2
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:

View File

@ -8,18 +8,15 @@ on:
jobs:
build:
name: QtPBFImagePlugin
runs-on: ubuntu-22.04
strategy:
matrix:
config: ['release', 'debug']
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install qtbase5-dev qtbase5-dev-tools qt5-qmake zlib1g-dev
sudo apt-get install qtbase5-dev qtbase5-dev-tools qt5-qmake libprotobuf-dev protobuf-compiler zlib1g-dev
- name: Configure build
run: qmake CONFIG+=${{ matrix.config }} pbfplugin.pro
run: qmake pbfplugin.pro
- name: Build project
run: make -j4
run: make -j2

View File

@ -6,22 +6,36 @@ on:
- master
jobs:
build:
name: QtPBFImagePlugin
qt5:
name: QtPBFImagePlugin Qt5 build
runs-on: macos-latest
steps:
- name: Set environment variables
run: echo "PATH=/opt/homebrew/opt/qt@5/bin:/opt/homebrew/opt/protobuf@21/bin:$PATH" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v4
- name: Install Qt
uses: jurplel/install-qt-action@v4
with:
version: '6.8.2'
- name: Install dependencies
run: |
brew update
brew install qt@5 protobuf@21
- name: Configure build
run: qmake pbfplugin.pro QMAKE_APPLE_DEVICE_ARCHS="x86_64h arm64"
run: qmake PROTOBUF=/opt/homebrew/opt/protobuf@21 pbfplugin.pro
- name: Build project
run: make -j3
qt6:
name: QtPBFImagePlugin Qt6 build
runs-on: macos-latest
steps:
- name: Set environment variables
run: echo "PATH=/opt/homebrew/opt/qt@6/bin:/opt/homebrew/opt/protobuf@21/bin:$PATH" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: |
brew update
brew install qt@6 protobuf@21
- name: Configure build
run: qmake PROTOBUF=/opt/homebrew/opt/protobuf@21 pbfplugin.pro
- name: Build project
run: make -j3
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
path: libpbf.dylib
name: libpbf.dylib

8
.gitignore vendored
View File

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

@ -67,39 +67,42 @@ repository.
## Build
### Requirements
* Qt5 >= 5.15 or Qt6
* Qt5 >= 5.11 or Qt6
* Google Protocol Buffers (protobuf-lite)
* Zlib
### Build steps
#### Linux, OS X and Android
#### Linux
```shell
qmake pbfplugin.pro
make
```
#### Windows
```shell
qmake ZLIB=path/to/zlib pbfplugin.pro
qmake PROTOBUF=path/to/protobuf ZLIB=path/to/zlib pbfplugin.pro
nmake
```
#### OS X
```shell
qmake PROTOBUF=path/to/protobuf pbfplugin.pro
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.
For Linux, there are RPM and DEB [packages](https://build.opensuse.org/project/show/home:tumic:QtPBFImagePlugin)
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
* 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. Additionally, the tile buffer must be large
enough to contain all neighboring text features overlapping to the tile bounds
(only data from the tile itself can be drawn to the resulting image).
* Expressions are not supported in the styles, only property functions are
implemented.
* 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.
* Expressions are not supported in the styles, only property functions are implemented.
## Changelog
[Changelog](https://build.opensuse.org/projects/home:tumic:QtPBFImagePlugin/packages/QtPBFImagePlugin/files/qt6-qtpbfimageformat.changes)
[Changelog](https://build.opensuse.org/package/view_file/home:tumic:QtPBFImagePlugin/QtPBFImagePlugin/libqt5-qtpbfimageformat.changes)
## Status
A picture is worth a thousand words.

View File

@ -2,10 +2,13 @@ TARGET = pbf
TEMPLATE = lib
CONFIG += plugin
QT += gui
VERSION = 4.2
VERSION = 3.1
PROTOS = protobuf/vector_tile.proto
include(protobuf/vector_tile.pri)
INCLUDEPATH += ./protobuf
HEADERS += src/pbfhandler.h \
src/data.h \
src/pbfplugin.h \
src/gzip.h \
src/pbf.h \
@ -20,7 +23,6 @@ HEADERS += src/pbfhandler.h \
src/textitem.h \
src/sprites.h
SOURCES += src/pbfplugin.cpp \
src/data.cpp \
src/pbfhandler.cpp \
src/gzip.cpp \
src/pbf.cpp \
@ -38,24 +40,31 @@ RESOURCES += pbfplugin.qrc
DEFINES += QT_NO_DEPRECATED_WARNINGS
unix:!macx:!android {
LIBS += -lz
LIBS += -lprotobuf-lite \
-lz
target.path += $$[QT_INSTALL_PLUGINS]/imageformats
INSTALLS += target
}
win32 {
INCLUDEPATH += $$ZLIB/include
LIBS += $$ZLIB/lib/zlibstatic.lib
INCLUDEPATH += $$PROTOBUF/include \
$$ZLIB/include
LIBS += $$PROTOBUF/lib/libprotobuf-lite.lib \
$$ZLIB/lib/zlibstatic.lib
QMAKE_TARGET_PRODUCT = QtPBFImagePlugin
QMAKE_TARGET_DESCRIPTION = Qt $$QT_VERSION MVT/PBF image plugin
QMAKE_TARGET_COPYRIGHT = Copyright (c) 2018-2025 Martin Tuma
QMAKE_TARGET_COPYRIGHT = Copyright (c) 2018-2024 Martin Tuma
}
macx {
LIBS += -lz
INCLUDEPATH += $$PROTOBUF/include
LIBS += $$PROTOBUF/lib/libprotobuf-lite.a \
-lz
}
android {
LIBS += -lz
INCLUDEPATH += $$PROTOBUF/include
LIBS += $$PROTOBUF/$$ANDROID_TARGET_ARCH/libprotobuf-lite.a \
-lz
top_builddir=$$shadowed($$PWD)
DESTDIR = $$top_builddir/plugins

17
protobuf/vector_tile.pri Normal file
View File

@ -0,0 +1,17 @@
INCLUDEPATH += $$PWD
DEPENDPATH += $$PWD
protobuf_decl.name = protobuf headers
protobuf_decl.input = PROTOS
protobuf_decl.output = ${QMAKE_FILE_IN_PATH}/${QMAKE_FILE_BASE}.pb.h
protobuf_decl.commands = protoc --cpp_out=${QMAKE_FILE_IN_PATH} --proto_path=${QMAKE_FILE_IN_PATH} ${QMAKE_FILE_NAME}
protobuf_decl.variable_out = HEADERS
QMAKE_EXTRA_COMPILERS += protobuf_decl
protobuf_impl.name = protobuf sources
protobuf_impl.input = PROTOS
protobuf_impl.output = ${QMAKE_FILE_IN_PATH}/${QMAKE_FILE_BASE}.pb.cc
protobuf_impl.depends = ${QMAKE_FILE_IN_PATH}/${QMAKE_FILE_BASE}.pb.h
protobuf_impl.commands = $$escape_expand(\n)
protobuf_impl.variable_out = SOURCES
QMAKE_EXTRA_COMPILERS += protobuf_impl

View File

@ -0,0 +1,78 @@
package vector_tile;
option optimize_for = LITE_RUNTIME;
message Tile {
// GeomType is described in section 4.3.4 of the specification
enum GeomType {
UNKNOWN = 0;
POINT = 1;
LINESTRING = 2;
POLYGON = 3;
}
// Variant type encoding
// The use of values is described in section 4.1 of the specification
message Value {
// Exactly one of these values must be present in a valid message
optional string string_value = 1;
optional float float_value = 2;
optional double double_value = 3;
optional int64 int_value = 4;
optional uint64 uint_value = 5;
optional sint64 sint_value = 6;
optional bool bool_value = 7;
extensions 8 to max;
}
// Features are described in section 4.2 of the specification
message Feature {
optional uint64 id = 1 [ default = 0 ];
// Tags of this feature are encoded as repeated pairs of
// integers.
// A detailed description of tags is located in sections
// 4.2 and 4.4 of the specification
repeated uint32 tags = 2 [ packed = true ];
// The type of geometry stored in this feature.
optional GeomType type = 3 [ default = UNKNOWN ];
// Contains a stream of commands and parameters (vertices).
// A detailed description on geometry encoding is located in
// section 4.3 of the specification.
repeated uint32 geometry = 4 [ packed = true ];
}
// Layers are described in section 4.1 of the specification
message Layer {
// Any compliant implementation must first read the version
// number encoded in this message and choose the correct
// implementation for this version number before proceeding to
// decode other parts of this message.
required uint32 version = 15 [ default = 1 ];
required string name = 1;
// The actual features in this tile.
repeated Feature features = 2;
// Dictionary encoding for keys
repeated string keys = 3;
// Dictionary encoding for values
repeated Value values = 4;
// Although this is an "optional" field it is required by the specification.
// See https://github.com/mapbox/vector-tile-spec/issues/47
optional uint32 extent = 5 [ default = 4096 ];
extensions 16 to max;
}
repeated Layer layers = 3;
extensions 16 to 8191;
}

View File

@ -1,354 +0,0 @@
#include "data.h"
#define TYPE(tag) (tag & 0x07)
#define FIELD(tag) (tag >> 3)
#define VARINT 0
#define I64 1
#define LEN 2
#define I32 5
struct CTX
{
CTX(const QByteArray &ba)
: bp(ba.constData()), be(bp + ba.size()), tag(0) {}
const char *bp;
const char *be;
quint32 tag;
};
static inline qint64 zigzag64decode(quint64 value)
{
return static_cast<qint64>((value >> 1u) ^ static_cast<quint64>(
-static_cast<qint64>(value & 1u)));
}
template<typename T>
static bool varint(CTX &ctx, T &val)
{
unsigned int shift = 0;
val = 0;
while (ctx.bp < ctx.be) {
val |= ((quint8)*ctx.bp & 0x7F) << shift;
shift += 7;
if (!((quint8)*ctx.bp++ & 0x80))
return true;
}
return false;
}
static bool length(CTX &ctx, qint32 &val)
{
if (TYPE(ctx.tag) != LEN)
return false;
if (!varint(ctx, val))
return false;
return (val >= 0);
}
static bool str(CTX &ctx, QByteArray &val)
{
qint32 len;
if (!length(ctx, len))
return false;
if (ctx.bp + len > ctx.be)
return false;
/* In Qt5 the (later) conversion to QString is broken when the QByteArray is
not nul terminated so we have to use the "deep copy" constructor that
nul-terminates the byte array when it is created. */
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
val = QByteArray(ctx.bp, len);
#else
val = QByteArray::fromRawData(ctx.bp, len);
#endif
ctx.bp += len;
return true;
}
static bool dbl(CTX &ctx, double &val)
{
if (TYPE(ctx.tag) != I64)
return false;
if (ctx.bp + sizeof(val) > ctx.be)
return false;
memcpy(&val, ctx.bp, sizeof(val));
ctx.bp += sizeof(val);
return true;
}
static bool flt(CTX &ctx, float &val)
{
if (TYPE(ctx.tag) != I32)
return false;
if (ctx.bp + sizeof(val) > ctx.be)
return false;
memcpy(&val, ctx.bp, sizeof(val));
ctx.bp += sizeof(val);
return true;
}
static bool packed(CTX &ctx, QVector<quint32> &vals)
{
quint32 v;
if (TYPE(ctx.tag) == LEN) {
qint32 len;
if (!varint(ctx, len) || len < 0)
return false;
const char *ee = ctx.bp + len;
if (ee > ctx.be)
return false;
while (ctx.bp < ee) {
if (!varint(ctx, v))
return false;
vals.append(v);
}
return (ctx.bp == ee);
} else if (TYPE(ctx.tag) == VARINT) {
if (!varint(ctx, v))
return false;
vals.append(v);
return true;
} else
return false;
}
static bool skip(CTX &ctx)
{
qint32 len = 0;
switch (TYPE(ctx.tag)) {
case VARINT:
return varint(ctx, len);
case I64:
len = 8;
break;
case LEN:
if (!varint(ctx, len) || len < 0)
return false;
break;
case I32:
len = 4;
break;
default:
return false;
}
if (ctx.bp + len > ctx.be)
return false;
ctx.bp += len;
return true;
}
static bool value(CTX &ctx, QVariant &val)
{
QByteArray ba;
quint64 num;
double dnum;
float fnum;
qint32 len;
if (!length(ctx, len))
return false;
const char *ee = ctx.bp + len;
if (ee > ctx.be)
return false;
while (ctx.bp < ee) {
if (!varint(ctx, ctx.tag))
return false;
switch (FIELD(ctx.tag)) {
case 1:
if (!str(ctx, ba))
return false;
val = QVariant(ba);
break;
case 2:
if (!flt(ctx, fnum))
return false;
val = QVariant(fnum);
break;
case 3:
if (!dbl(ctx, dnum))
return false;
val = QVariant(dnum);
break;
case 4:
if (TYPE(ctx.tag) != VARINT)
return false;
if (!varint(ctx, num))
return false;
val = QVariant(static_cast<qint64>(num));
break;
case 5:
if (TYPE(ctx.tag) != VARINT)
return false;
if (!varint(ctx, num))
return false;
val = QVariant(num);
break;
case 6:
if (TYPE(ctx.tag) != VARINT)
return false;
if (!varint(ctx, num))
return false;
val = QVariant(zigzag64decode(num));
break;
case 7:
if (TYPE(ctx.tag) != VARINT)
return false;
if (!varint(ctx, num))
return false;
val = QVariant(num ? true : false);
break;
default:
if (!skip(ctx))
return false;
}
}
return (ctx.bp == ee);
}
static bool feature(CTX &ctx, Data::Feature &f)
{
qint32 len;
quint32 e;
if (!length(ctx, len))
return false;
const char *ee = ctx.bp + len;
if (ee > ctx.be)
return false;
while (ctx.bp < ee) {
if (!varint(ctx, ctx.tag))
return false;
switch (FIELD(ctx.tag)) {
case 1:
if (TYPE(ctx.tag) != VARINT)
return false;
if (!varint(ctx, f.id))
return false;
break;
case 2:
if (!packed(ctx, f.tags))
return false;
break;
case 3:
if (TYPE(ctx.tag) != VARINT)
return false;
if (!varint(ctx, e))
return false;
if (e > Data::GeomType::POLYGON)
return false;
f.type = static_cast<Data::GeomType>(e);
break;
case 4:
if (!packed(ctx, f.geometry))
return false;
break;
default:
if (!skip(ctx))
return false;
}
}
return (ctx.bp == ee);
}
static bool layer(CTX &ctx, Data::Layer &l)
{
qint32 len;
if (!length(ctx, len))
return false;
const char *ee = ctx.bp + len;
if (ee > ctx.be)
return false;
while (ctx.bp < ee) {
if (!varint(ctx, ctx.tag))
return false;
switch (FIELD(ctx.tag)) {
case 1:
if (!str(ctx, l.name))
return false;
break;
case 2:
l.features.append(Data::Feature());
if (!feature(ctx, l.features.last()))
return false;
break;
case 3:
l.keys.append(QByteArray());
if (!str(ctx, l.keys.last()))
return false;
break;
case 4:
l.values.append(QVariant());
if (!value(ctx, l.values.last()))
return false;
break;
case 5:
if (TYPE(ctx.tag) != VARINT)
return false;
if (!varint(ctx, l.extent))
return false;
break;
case 15:
if (TYPE(ctx.tag) != VARINT)
return false;
if (!varint(ctx, l.version))
return false;
break;
default:
if (!skip(ctx))
return false;
}
}
return (ctx.bp == ee);
}
bool Data::load(const QByteArray &ba)
{
CTX ctx(ba);
while (ctx.bp < ctx.be) {
if (!varint(ctx, ctx.tag))
return false;
switch (FIELD(ctx.tag)) {
case 3:
_layers.append(Layer());
if (!layer(ctx, _layers.last()))
return false;
break;
default:
if (!skip(ctx))
return false;
}
}
return (ctx.bp == ctx.be);
}

View File

@ -1,47 +0,0 @@
#ifndef DATA_H
#define DATA_H
#include <QByteArray>
#include <QVector>
#include <QVariant>
class Data
{
public:
enum GeomType {
UNKNOWN = 0,
POINT = 1,
LINESTRING = 2,
POLYGON = 3
};
struct Feature
{
Feature() : id(0), type(UNKNOWN) {}
quint64 id;
QVector<quint32> tags;
GeomType type;
QVector<quint32> geometry;
};
struct Layer
{
Layer() : version(1), extent(4096) {}
quint32 version;
QByteArray name;
QVector<Feature> features;
QVector<QByteArray> keys;
QVector<QVariant> values;
quint32 extent;
};
bool load(const QByteArray &ba);
const QVector<Layer> &layers() const {return _layers;}
private:
QVector<Layer> _layers;
};
#endif // DATA_H

View File

@ -6,7 +6,6 @@
#include <QColor>
#include <QPair>
#include <QString>
#include <QJsonValue>
class QJsonObject;

View File

@ -4,6 +4,7 @@
#define LINE_TO 2
#define CLOSE_PATH 7
static inline qint32 zigzag32decode(quint32 value)
{
return static_cast<qint32>((value >> 1u) ^ static_cast<quint32>(
@ -15,17 +16,37 @@ static inline QPoint parameters(quint32 v1, quint32 v2)
return QPoint(zigzag32decode(v1), zigzag32decode(v2));
}
const QVariant *PBF::Feature::value(const QByteArray &key) const
static QVariant value(const vector_tile::Tile_Value &val)
{
if (val.has_bool_value())
return QVariant(val.bool_value());
else if (val.has_int_value())
return QVariant((qlonglong)val.int_value());
else if (val.has_sint_value())
return QVariant((qlonglong)val.sint_value());
else if (val.has_uint_value())
return QVariant((qulonglong)val.uint_value());
else if (val.has_float_value())
return QVariant(val.float_value());
else if (val.has_double_value())
return QVariant(val.double_value());
else if (val.has_string_value())
return QVariant(QString::fromStdString(val.string_value()));
else
return QVariant();
}
const QVariant *PBF::Feature::value(const QString &key) const
{
const KeyHash &keys(_layer->keys());
KeyHash::const_iterator it(keys.find(key));
if (it == keys.constEnd())
return 0;
quint32 index = *it;
for (int i = 0; i < _data->tags.size(); i = i + 2)
if (_data->tags.at(i) == index)
return &(_layer->values().at(_data->tags.at(i+1)));
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)));
return 0;
}
@ -35,15 +56,15 @@ QPainterPath PBF::Feature::path(const QSizeF &factor) const
QPoint cursor;
QPainterPath path;
for (int i = 0; i < _data->geometry.size(); i++) {
quint32 g = _data->geometry.at(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(_data->geometry.at(i+1),
_data->geometry.at(i+2));
QPoint offset = parameters(_data->geometry(i+1),
_data->geometry(i+2));
i += 2;
cursor += offset;
path.moveTo(QPointF(cursor.x() * factor.width(),
@ -51,8 +72,8 @@ QPainterPath PBF::Feature::path(const QSizeF &factor) const
}
} else if (cmdId == LINE_TO) {
for (unsigned j = 0; j < cmdCount; j++) {
QPoint offset = parameters(_data->geometry.at(i+1),
_data->geometry.at(i+2));
QPoint offset = parameters(_data->geometry(i+1),
_data->geometry(i+2));
i += 2;
cursor += offset;
path.lineTo(QPointF(cursor.x() * factor.width(),
@ -67,23 +88,26 @@ QPainterPath PBF::Feature::path(const QSizeF &factor) const
return path;
}
PBF::Layer::Layer(const Data::Layer *layer) : _data(layer)
PBF::Layer::Layer(const vector_tile::Tile_Layer *data) : _data(data)
{
_keys.reserve(layer->keys.size());
for (int i = 0; i < layer->keys.size(); i++)
_keys.insert(layer->keys.at(i), i);
_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)));
_features.reserve(layer->features.size());
for (int i = 0; i < layer->features.size(); i++)
_features.append(Feature(&(layer->features.at(i)), this));
_features.reserve(data->features_size());
for (int i = 0; i < data->features_size(); i++)
_features.append(Feature(&(data->features(i)), this));
std::sort(_features.begin(), _features.end());
}
PBF::PBF(const Data &data)
PBF::PBF(const vector_tile::Tile &tile)
{
for (int i = 0; i < data.layers().size(); i++) {
const Data::Layer &layer = data.layers().at(i);
_layers.insert(layer.name, new Layer(&layer));
for (int i = 0; i < tile.layers_size(); i++) {
const vector_tile::Tile_Layer &layer = tile.layers(i);
_layers.insert(QString::fromStdString(layer.name()), new Layer(&layer));
}
}

View File

@ -5,10 +5,10 @@
#include <QVector>
#include <QHash>
#include <QPainterPath>
#include "data.h"
#include "vector_tile.pb.h"
typedef QHash<QByteArray, quint32> KeyHash;
typedef QHash<QString, google::protobuf::uint32> KeyHash;
class PBF
{
@ -19,17 +19,17 @@ public:
{
public:
Feature() : _data(0), _layer(0) {}
Feature(const Data::Feature *data, const Layer *layer)
Feature(const vector_tile::Tile_Feature *data, const Layer *layer)
: _data(data), _layer(layer) {}
const QVariant *value(const QByteArray &key) const;
Data::GeomType type() const {return _data->type;}
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 Data::Feature *_data;
const vector_tile::Tile_Feature *_data;
const Layer *_layer;
};
@ -37,30 +37,31 @@ public:
{
public:
Layer(const Data::Layer *layer);
Layer(const vector_tile::Tile_Layer *data);
const QVector<Feature> &features() const {return _features;}
const QVector<QVariant> &values() const {return _data->values;}
const QVector<QVariant> &values() const {return _values;}
const KeyHash &keys() const {return _keys;}
const Data::Layer *data() const {return _data;}
const vector_tile::Tile_Layer *data() const {return _data;}
private:
const Data::Layer *_data;
const vector_tile::Tile_Layer *_data;
QVector<Feature> _features;
QVector<QVariant> _values;
KeyHash _keys;
};
PBF(const Data &data);
PBF(const vector_tile::Tile &tile);
~PBF();
const QHash<QByteArray, Layer*> &layers() const {return _layers;}
const QHash<QString, Layer*> &layers() const {return _layers;}
private:
QHash<QByteArray, Layer*> _layers;
QHash<QString, Layer*> _layers;
};
inline bool operator<(const PBF::Feature &f1, const PBF::Feature &f2)
{return f1._data->id < f2._data->id;}
{return f1._data->id() < f2._data->id();}
#endif // PBF_H

View File

@ -3,7 +3,6 @@
#include <QtEndian>
#include <QDebug>
#include "gzip.h"
#include "data.h"
#include "tile.h"
#include "style.h"
#include "pbfhandler.h"
@ -74,8 +73,8 @@ bool PBFHandler::read(QImage *image)
}
} else if (isPlainPBF(magic))
ba = device()->readAll();
Data data;
if (!data.load(ba)) {
vector_tile::Tile data;
if (!data.ParseFromArray(ba.constData(), ba.size())) {
qCritical() << "Invalid PBF data";
return false;
}

View File

@ -22,7 +22,6 @@ static QImage sdf2img(const QImage &sdf, const QColor &color)
}
Sprites::Sprite::Sprite(const QJsonObject &json)
: _pixelRatio(1.0), _sdf(false)
{
int x, y, width, height;
@ -45,10 +44,16 @@ Sprites::Sprite::Sprite(const QJsonObject &json)
_rect = QRect(x, y, width, height);
if (json.contains("pixelRatio") && json["pixelRatio"].isDouble())
_pixelRatio = json["pixelRatio"].toDouble();
else
_pixelRatio = 1.0;
if (json.contains("sdf") && json["sdf"].isBool())
_sdf = json["sdf"].toBool();
else
_sdf = false;
}
bool Sprites::load(const QString &jsonFile, const QString &imageFile)

View File

@ -14,30 +14,16 @@
#include "style.h"
static Data::GeomType geometryType(const QString &str)
static vector_tile::Tile_GeomType geometryType(const QString &str)
{
if (str == "Point")
return Data::GeomType::POINT;
return vector_tile::Tile_GeomType_POINT;
else if (str == "LineString")
return Data::GeomType::LINESTRING;
return vector_tile::Tile_GeomType_LINESTRING;
else if (str == "Polygon")
return Data::GeomType::POLYGON;
return vector_tile::Tile_GeomType_POLYGON;
else
return Data::GeomType::UNKNOWN;
}
static QVariant variant(const QJsonValue &val)
{
switch (val.type()) {
case QJsonValue::String:
return QVariant(val.toString().toUtf8());
case QJsonValue::Double:
case QJsonValue::Bool:
return val.toVariant();
default:
qWarning() << val << ": invalid filter value";
return QVariant();
}
return vector_tile::Tile_GeomType_UNKNOWN;
}
Style::Layer::Filter::Filter(const QJsonArray &json)
@ -49,50 +35,50 @@ Style::Layer::Filter::Filter(const QJsonArray &json)
if (json.isEmpty())
INVALID_FILTER(json);
QString type(json.at(0).toString());
QString type = json.at(0).toString();
if (type == "==") {
if (json.size() != 3)
INVALID_FILTER(json);
if (json.at(1).toString() == "$type") {
_type = GeometryType;
_kv = QPair<QByteArray, QVariant>(QByteArray(),
_kv = QPair<QString, QVariant>(QString(),
QVariant(geometryType(json.at(2).toString())));
} else {
_type = EQ;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
variant(json.at(2)));
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
}
} else if (type == "!=") {
if (json.size() != 3)
INVALID_FILTER(json);
_type = NE;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
variant(json.at(2)));
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
} else if (type == "<") {
if (json.size() != 3)
INVALID_FILTER(json);
_type = LT;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
variant(json.at(2)));
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
} else if (type == "<=") {
if (json.size() != 3)
INVALID_FILTER(json);
_type = LE;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
variant(json.at(2)));
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
} else if (type == ">") {
if (json.size() != 3)
INVALID_FILTER(json);
_type = GT;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
variant(json.at(2)));
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
} else if (type == ">=") {
if (json.size() != 3)
INVALID_FILTER(json);
_type = GE;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
variant(json.at(2)));
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
} else if (type == "all") {
_type = All;
for (int i = 1; i < json.size(); i++)
@ -105,32 +91,28 @@ Style::Layer::Filter::Filter(const QJsonArray &json)
if (json.size() < 3)
INVALID_FILTER(json);
_type = In;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
QVariant());
_kv = QPair<QString, QVariant>(json.at(1).toString(), QVariant());
for (int i = 2; i < json.size(); i++)
_set.insert(json.at(i).toString().toUtf8());
_set.insert(json.at(i).toString());
} else if (type == "!in") {
if (json.size() < 3)
INVALID_FILTER(json);
_type = In;
_not = true;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
QVariant());
_kv = QPair<QString, QVariant>(json.at(1).toString(), QVariant());
for (int i = 2; i < json.size(); i++)
_set.insert(json.at(i).toString().toUtf8());
_set.insert(json.at(i).toString());
} else if (type == "has") {
if (json.size() < 2)
INVALID_FILTER(json);
_type = Has;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
QVariant());
_kv = QPair<QString, QVariant>(json.at(1).toString(), QVariant());
} else if (type == "!has") {
if (json.size() < 2)
INVALID_FILTER(json);
_type = Has;
_not = true;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
QVariant());
_kv = QPair<QString, QVariant>(json.at(1).toString(), QVariant());
} else
INVALID_FILTER(json);
}
@ -200,7 +182,7 @@ bool Style::Layer::Filter::match(const PBF::Feature &feature) const
if (!(v = feature.value(_kv.first)))
return _not;
else
return _set.contains((*v).toByteArray()) ^ _not;
return _set.contains((*v).toString()) ^ _not;
case Has:
return (feature.value(_kv.first) ? true : false) ^ _not;
case All:
@ -234,7 +216,7 @@ QString Style::Layer::Template::value(int zoom, const PBF::Feature &feature) con
}
for (int i = 0; i < keys.size(); i++) {
const QString &key = keys.at(i);
const QVariant *val = feature.value(key.toUtf8());
const QVariant *val = feature.value(key);
text.replace(QString("{%1}").arg(key), val ? val->toString() : "");
}
@ -504,7 +486,7 @@ Style::Layer::Layer(const QJsonObject &json)
_type = Symbol;
// source-layer
_sourceLayer = json["source-layer"].toString().toUtf8();
_sourceLayer = json["source-layer"].toString();
// zooms
if (json.contains("minzoom") && json["minzoom"].isDouble())
@ -590,7 +572,7 @@ static bool loadSprites(const QDir &styleDir, const QString &json,
if (QFileInfo::exists(spritesImg))
return sprites.load(spritesJSON, spritesImg);
else {
qWarning() << spritesImg << ": no such file";
qCritical() << spritesImg << ": no such file";
return false;
}
}
@ -680,15 +662,15 @@ void Style::drawFeature(const PBF::Feature &feature, const Layer &layer,
void Style::drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer,
Tile &tile)
{
if (pbfLayer.data()->version > 2)
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);
(qreal)pbfLayer.data()->extent(), tile.size().height() / tile.scale().y()
/ (qreal)pbfLayer.data()->extent());
tile.painter().save();
setupLayer(tile, styleLayer);
@ -702,7 +684,7 @@ void Style::render(const PBF &data, Tile &tile)
drawBackground(tile);
for (int i = 0; i < _layers.size(); i++) {
QHash<QByteArray, PBF::Layer*>::const_iterator it = data.layers().find(
QHash<QString, PBF::Layer*>::const_iterator it = data.layers().find(
_layers.at(i).sourceLayer());
if (it == data.layers().constEnd())
continue;

View File

@ -37,7 +37,7 @@ private:
Layer() : _type(Unknown), _minZoom(0), _maxZoom(24) {}
Layer(const QJsonObject &json);
const QByteArray &sourceLayer() const {return _sourceLayer;}
const QString &sourceLayer() const {return _sourceLayer;}
bool isPath() const {return (_type == Line || _type == Fill);}
bool isBackground() const {return (_type == Background);}
bool isSymbol() const {return (_type == Symbol);}
@ -74,8 +74,8 @@ private:
Type _type;
bool _not;
QSet<QByteArray> _set;
QPair<QByteArray, QVariant> _kv;
QSet<QString> _set;
QPair<QString, QVariant> _kv;
QVector<Filter> _filters;
};
@ -167,7 +167,7 @@ private:
};
Type _type;
QByteArray _sourceLayer;
QString _sourceLayer;
int _minZoom, _maxZoom;
Filter _filter;
Layout _layout;

View File

@ -2,6 +2,13 @@
#include <QPainter>
#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 void swap(const QLineF &line, QPointF *p1, QPointF *p2)
{
@ -23,10 +30,10 @@ static bool intersection(const QLineF &line, const QRectF &rect, QPointF *p1,
{
QPointF *p = p1;
if (line.intersects(QLineF(rect.topLeft(), rect.topRight()), p)
if (line.INTERSECTS(QLineF(rect.topLeft(), rect.topRight()), p)
== QLineF::BoundedIntersection)
p = p2;
if (line.intersects(QLineF(rect.topLeft(), rect.bottomLeft()), p)
if (line.INTERSECTS(QLineF(rect.topLeft(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection) {
if (p == p2) {
swap(line, p1, p2);
@ -34,7 +41,7 @@ static bool intersection(const QLineF &line, const QRectF &rect, QPointF *p1,
}
p = p2;
}
if (line.intersects(QLineF(rect.bottomRight(), rect.bottomLeft()), p)
if (line.INTERSECTS(QLineF(rect.bottomRight(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection) {
if (p == p2) {
swap(line, p1, p2);
@ -42,7 +49,7 @@ static bool intersection(const QLineF &line, const QRectF &rect, QPointF *p1,
}
p = p2;
}
if (line.intersects(QLineF(rect.bottomRight(), rect.topRight()), p)
if (line.INTERSECTS(QLineF(rect.bottomRight(), rect.topRight()), p)
== QLineF::BoundedIntersection) {
if (p == p2) {
swap(line, p1, p2);
@ -57,16 +64,16 @@ static bool intersection(const QLineF &line, const QRectF &rect, QPointF *p1,
static bool intersection(const QLineF &line, const QRectF &rect, QPointF *p)
{
if (line.intersects(QLineF(rect.topLeft(), rect.topRight()), p)
if (line.INTERSECTS(QLineF(rect.topLeft(), rect.topRight()), p)
== QLineF::BoundedIntersection)
return true;
if (line.intersects(QLineF(rect.topLeft(), rect.bottomLeft()), p)
if (line.INTERSECTS(QLineF(rect.topLeft(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection)
return true;
if (line.intersects(QLineF(rect.bottomRight(), rect.bottomLeft()), p)
if (line.INTERSECTS(QLineF(rect.bottomRight(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection)
return true;
if (line.intersects(QLineF(rect.bottomRight(), rect.topRight()), p)
if (line.INTERSECTS(QLineF(rect.bottomRight(), rect.topRight()), p)
== QLineF::BoundedIntersection)
return true;