Compare commits

...

80 Commits
2.5 ... master

Author SHA1 Message Date
b53d8f3df7 Update the nuber of cores acording to the latest GitHub runners state 2025-03-21 06:30:23 +01:00
da62d0ba63 Use Qt 6.8.2 for OS X builds 2025-02-19 00:48:22 +01:00
baff6ac17d Remove all Qt5 < 5.15 workarounds.
Qt 5.15 is now the minimal required Qt version.
2025-02-19 00:44:36 +01:00
56e83ea6d9 Version++ 2025-02-19 00:44:06 +01:00
c9f7531f17 Fixed broken strings handling under Qt5 2025-02-13 23:34:36 +01:00
dc1655a2d7 Last protobuf leftovers 2025-01-25 09:05:13 +01:00
cb4919d5bd Silence clang-tidy warnings 2025-01-12 10:37:21 +01:00
affb818f4e Added missing include 2025-01-12 10:32:58 +01:00
851ed76e26 Version++ 2025-01-12 10:32:42 +01:00
08d211204e Specify the text layout requirements more precisely 2025-01-08 00:36:27 +01:00
9401f66ad7 Cosmetics 2025-01-08 00:21:57 +01:00
0c63b66544 Special case not needed 2025-01-07 09:34:45 +01:00
30fee38b25 Empty messages are valid 2025-01-07 09:32:30 +01:00
00c52d5ab7 Allow zero size lengths 2025-01-07 09:16:55 +01:00
57af4fca5b Added missing defaults 2025-01-07 09:11:36 +01:00
3c125c9a7a Added missing length size checks
+ code cleanup/refactoring
2025-01-07 08:52:40 +01:00
252ca8542a Fixed LEB128 decoding + types 2025-01-06 22:42:29 +01:00
14069640b4
Fixed changelog URL 2025-01-06 20:59:59 +01:00
4e2dfabc9d Cosmetics 2025-01-06 20:47:11 +01:00
8e49440a02 Remove remaining protobuf stuff 2025-01-06 20:13:55 +01:00
badd834b59 Fixed float/double values parsing 2025-01-06 20:12:28 +01:00
0a4543ddcf Added missing type checks 2025-01-06 19:48:54 +01:00
c12c2b4497 Fixed zigzag decode 2025-01-06 10:04:41 +01:00
b630d1d205 Added missing varint size check 2025-01-06 09:44:53 +01:00
cbc0a05341 Fixed artifact path 2025-01-06 09:26:52 +01:00
6ded86b080
Remove Google protobuf from documentation 2025-01-06 09:25:00 +01:00
3287e54411 Removed Google protobuf dependency 2025-01-06 09:19:36 +01:00
caf0a29b74 Added missing pkgconf package 2025-01-05 13:21:53 +01:00
4653525da8 Added build option to link using PKGCONFIG 2025-01-05 12:18:55 +01:00
cc2b03423b Fixed lib name 2025-01-05 10:08:02 +01:00
e21629f021 Link abseil on Windows if required by protobuf 2025-01-05 08:33:04 +01:00
b2ff37b3a2 Build only the release configurations of vcpkg dependencies 2025-01-04 00:53:33 +01:00
1f93100285 Use Qt 6.8 in Windows Qt6 builds 2025-01-03 19:28:52 +01:00
2b9cde99a5 Version++ 2025-01-03 19:28:34 +01:00
14dada50c4 Print non-critical errors as warnings 2024-10-06 13:24:08 +02:00
ab19e5770f Cosmetics 2024-10-06 13:14:33 +02:00
aea6c129dc Added filter value check/warning 2024-10-06 13:00:49 +02:00
497aa2e023 Use QByteArray instead of QString where possible for performance reasons 2024-10-06 12:45:04 +02:00
e889815402 Version++ 2024-10-06 12:44:01 +02:00
1d4640c85c Code cleanup 2024-06-15 06:01:04 +02:00
aeed3e3848 Fixed homebrew paths 2024-06-14 19:59:43 +02:00
7488fc8f98 protobuf debug 2024-06-14 19:45:44 +02:00
549eb18ec6 Trying to fix broken homebrew... 2024-06-14 19:37:07 +02:00
5ed06f90b7 Use the propper image format 2024-06-14 18:56:14 +02:00
3a0694323f Fixed build on Qt < 5.13 2024-06-14 18:45:40 +02:00
c8b7051eba Added support for SDF sprites + fixed symbol layout algorithms 2024-06-14 18:33:10 +02:00
5954562200 Use Qt 6.7 in Windows build 2024-04-22 08:15:45 +02:00
ee1b91329e Optimizations 2024-02-05 10:08:58 +01:00
97d5748d11
Fixed OpenMapTiles map source URL 2024-02-04 19:11:06 +01:00
559ade2bab Use the latest actions versions 2024-02-03 18:35:23 +01:00
1fe198f26e Use the latest actions versions 2024-02-03 18:18:10 +01:00
a7b83f5d78 Build on Ubuntu 22.04 2024-02-03 17:40:57 +01:00
071a68a0dd Copyright year update 2024-02-03 17:02:09 +01:00
aab4a5dc55 Version++ 2024-02-03 16:58:18 +01:00
ff3d11e8da Improved debug grid display 2024-02-01 09:48:36 +01:00
bca266cb86 Android build fix 2024-01-18 22:59:44 +01:00
bdffe48676 Remove unnecessary path element 2023-12-18 20:21:06 +01:00
cb86e30fab Fixed artifact path 2023-12-15 21:37:53 +01:00
407f543039 Use libpbf instead of pbf in Android target name 2023-12-15 21:35:49 +01:00
f24a8a47a9 Fix plugins path 2023-12-15 21:32:33 +01:00
ce7b3d9158 Use propper plugin target path 2023-12-15 21:25:38 +01:00
f064148a0f Use propper target name on Android 2023-12-15 21:21:10 +01:00
733375fb74 Fixed artifact file name 2023-12-15 17:11:06 +01:00
440f7f91a4 Added artifacts upload 2023-12-15 17:03:59 +01:00
dbcfd9b9d3 Added Google Protocol Buffers to Android build 2023-12-15 15:04:55 +01:00
672de3919b Fixed Android qmake step 2023-12-15 09:27:02 +01:00
0a696a6ab8 Added Android build workflow 2023-12-15 09:23:48 +01:00
d8a521623a
Unify the code samples to use QImage 2023-12-11 11:40:18 +01:00
f5f006dc7b Fixed artifact name/path 2023-12-11 08:16:07 +01:00
c86eb7bac2 Increase major version due to the API change
The API should be backward compatible but the overzoom extension should rather
be promoted with a major version number change.
2023-12-10 18:14:54 +01:00
3ef6c55d20
Code samples cleanup 2023-12-10 17:46:39 +01:00
98933deb0e
Added HiDPI and Overzoom sections 2023-12-10 15:53:19 +01:00
6e1bc09d62
Cosmetics 2023-12-10 15:19:35 +01:00
ea98da4a74 Added overzoom description 2023-12-10 15:16:23 +01:00
8261ee2e79 Keep the overzoom and scaled size separated 2023-12-10 15:12:13 +01:00
d5c315efbe Version++ 2023-12-10 14:44:36 +01:00
39ffdaf616 Added support for overzoom 2023-12-10 14:43:07 +01:00
38b6e2320b
Example codes cleanup 2023-12-10 08:18:16 +01:00
b36fb5fa92 Fixed maximal lines angle check 2023-10-22 23:53:39 +02:00
a7b7d16f4f Version++ 2023-10-22 23:47:28 +02:00
24 changed files with 785 additions and 374 deletions

View File

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

48
.github/workflows/android.yml vendored Normal file
View File

@ -0,0 +1,48 @@
name: Android
on:
push:
branches:
- master
jobs:
build:
name: QtPBFImagePlugin
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: set up JDK 11
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '11'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
with:
cmdline-tools-version: 9862592
- name: Install android platform, build-tools and ndk
run: ${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager --install "platforms;android-33" "build-tools;33.0.0" "ndk;23.1.7779620"
- name: Setup NDK path
run: echo "ANDROID_NDK_ROOT=${ANDROID_HOME}/ndk/23.1.7779620/" >> $GITHUB_ENV
- name: Install Qt (Desktop)
uses: jurplel/install-qt-action@v3
with:
aqtversion: '==3.1.*'
version: '6.4.0'
- name: Install Qt (Android)
uses: jurplel/install-qt-action@v3
with:
aqtversion: '==3.1.*'
version: '6.4.0'
target: 'android'
arch: 'android_arm64_v8a'
- name: Configure build
run: qmake pbfplugin.pro
- name: Build project
run: make -j4
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: libplugins_imageformats_libpbf_arm64-v8a.so
path: plugins/libplugins_imageformats_libpbf_arm64-v8a.so

View File

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

View File

@ -6,32 +6,22 @@ on:
- master
jobs:
qt5:
name: QtPBFImagePlugin Qt5 build
build:
name: QtPBFImagePlugin
runs-on: macos-latest
steps:
- name: Set environment variables
run: echo "PATH=/usr/local/opt/qt@5/bin:/usr/local/opt/protobuf@21/bin:$PATH" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v3
- name: Install dependencies
run: brew install qt@5 protobuf@21
uses: actions/checkout@v4
- name: Install Qt
uses: jurplel/install-qt-action@v4
with:
version: '6.8.2'
- name: Configure build
run: qmake PROTOBUF=/usr/local/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=/usr/local/opt/qt@6/bin:/usr/local/opt/protobuf@21/bin:$PATH" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v3
- name: Install dependencies
run: brew install qt@6 protobuf@21
- name: Configure build
run: qmake PROTOBUF=/usr/local/opt/protobuf@21 pbfplugin.pro
run: qmake pbfplugin.pro QMAKE_APPLE_DEVICE_ARCHS="x86_64h arm64"
- 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,17 +1,9 @@
# 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

@ -18,24 +18,39 @@ to fit the styles and available data (OpenMapTiles, Mapbox tiles).
Due to a major design flaw in the Mapbox vector tiles specification - the zoom
is not part of the PBF data - the plugin can not be used "as is", but passing
the zoom level is necessary. This is done by exploiting the optional *format*
parameter of the QImage constructor or the QImage::fromData() or
QPixmap::loadFromData() functions. The zoom number is passed as ASCII string
parameter of the QImage constructor or the *QImage::loadFromData()* or
*QPixmap::loadFromData()* functions. The zoom number is passed as ASCII string
to the functions:
```cpp
QPixmap pm;
pm.loadFromData(tileData, QString::number(zoom).toLatin1());
QImage img;
img.loadFromData(data, QByteArray::number(zoom));
```
The plugin supports vector scaling using QImageReader's setScaledSize() method,
For a complete code sample see the [pbf2png](https://github.com/tumic0/pbf2png)
conversion utility.
### HiDPI
The plugin supports vector scaling using QImageReader's *setScaledSize()* method,
so when used like in the following example:
```cpp
QImageReader reader(file, QString::number(zoom).toLatin1());
QImage img;
QImageReader reader(file, QByteArray::number(zoom));
reader.setScaledSize(QSize(1024, 1024));
reader.read(&image);
reader.read(&img);
```
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.
### Overzoom
Since version 3 of the plugin tile overzoom is supported. If you set *format*
to `$zoom;$overzoom`:
```cpp
QImage img;
QByteArray fmt(QByteArray::number(zoom) + ';' + QByteArray::number(overzoom));
img.loadFromData(data, fmt);
```
you will get (512<<overzoom)x(512<<overzoom)px tiles with a pixel ratio of 1.
When overzoom is combined with setScaledSize(), the base size is the overzoomed
tile size.
## Styles
The map style is loaded from the
@ -52,48 +67,45 @@ repository.
## Build
### Requirements
* Qt5 >= 5.11 or Qt6
* Google Protocol Buffers (protobuf-lite)
* Qt5 >= 5.15 or Qt6
* Zlib
### Build steps
#### Linux
#### Linux, OS X and Android
```shell
qmake pbfplugin.pro
make
```
#### Windows
```shell
qmake PROTOBUF=path/to/protobuf ZLIB=path/to/zlib pbfplugin.pro
qmake 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.
* 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. 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.
## Changelog
[Changelog](https://build.opensuse.org/package/view_file/home:tumic:QtPBFImagePlugin/QtPBFImagePlugin/libqt5-qtpbfimageformat.changes)
[Changelog](https://build.opensuse.org/projects/home:tumic:QtPBFImagePlugin/packages/QtPBFImagePlugin/files/qt6-qtpbfimageformat.changes)
## Status
A picture is worth a thousand words.
#### OpenMapTiles
* Data: [MapTiler](https://github.com/tumic0/GPXSee-maps/blob/master/World/MapTiler.tpl)
* Data: [MapTiler](https://github.com/tumic0/GPXSee-maps/blob/master/World/MapTiler-OpenMapTiles.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)

View File

@ -2,13 +2,10 @@ TARGET = pbf
TEMPLATE = lib
CONFIG += plugin
QT += gui
VERSION = 2.5
VERSION = 4.2
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 \
@ -23,6 +20,7 @@ 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 \
@ -39,25 +37,27 @@ RESOURCES += pbfplugin.qrc
DEFINES += QT_NO_DEPRECATED_WARNINGS
unix:!macx{
LIBS += -lprotobuf-lite \
-lz
unix:!macx:!android {
LIBS += -lz
target.path += $$[QT_INSTALL_PLUGINS]/imageformats
INSTALLS += target
}
win32 {
INCLUDEPATH += $$PROTOBUF/include \
$$ZLIB/include
LIBS += $$PROTOBUF/lib/libprotobuf-lite.lib \
$$ZLIB/lib/zlibstatic.lib
INCLUDEPATH += $$ZLIB/include
LIBS += $$ZLIB/lib/zlibstatic.lib
QMAKE_TARGET_PRODUCT = QtPBFImagePlugin
QMAKE_TARGET_DESCRIPTION = Qt $$QT_VERSION MVT/PBF image plugin
QMAKE_TARGET_COPYRIGHT = Copyright (c) 2023 Martin Tuma
QMAKE_TARGET_COPYRIGHT = Copyright (c) 2018-2025 Martin Tuma
}
macx {
INCLUDEPATH += $$PROTOBUF/include
LIBS += $$PROTOBUF/lib/libprotobuf-lite.a \
-lz
LIBS += -lz
}
android {
LIBS += -lz
target.path += $$[QT_INSTALL_PLUGINS]/imageformats
INSTALLS += target
top_builddir=$$shadowed($$PWD)
DESTDIR = $$top_builddir/plugins
TARGET = $$qt5LibraryTarget(libpbf, "plugins/imageformats/")
}

View File

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

@ -1,78 +0,0 @@
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;
}

354
src/data.cpp Normal file
View File

@ -0,0 +1,354 @@
#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);
}

47
src/data.h Normal file
View File

@ -0,0 +1,47 @@
#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,6 +6,7 @@
#include <QColor>
#include <QPair>
#include <QString>
#include <QJsonValue>
class QJsonObject;

View File

@ -4,7 +4,6 @@
#define LINE_TO 2
#define CLOSE_PATH 7
static inline qint32 zigzag32decode(quint32 value)
{
return static_cast<qint32>((value >> 1u) ^ static_cast<quint32>(
@ -16,37 +15,17 @@ static inline QPoint parameters(quint32 v1, quint32 v2)
return QPoint(zigzag32decode(v1), zigzag32decode(v2));
}
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 QVariant *PBF::Feature::value(const QByteArray &key) const
{
const KeyHash &keys(_layer->keys());
KeyHash::const_iterator it(keys.find(key));
if (it == keys.constEnd())
return 0;
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)));
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)));
return 0;
}
@ -56,15 +35,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(i);
for (int i = 0; i < _data->geometry.size(); i++) {
quint32 g = _data->geometry.at(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(i+1),
_data->geometry(i+2));
QPoint offset = parameters(_data->geometry.at(i+1),
_data->geometry.at(i+2));
i += 2;
cursor += offset;
path.moveTo(QPointF(cursor.x() * factor.width(),
@ -72,8 +51,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(i+1),
_data->geometry(i+2));
QPoint offset = parameters(_data->geometry.at(i+1),
_data->geometry.at(i+2));
i += 2;
cursor += offset;
path.lineTo(QPointF(cursor.x() * factor.width(),
@ -88,26 +67,23 @@ QPainterPath PBF::Feature::path(const QSizeF &factor) const
return path;
}
PBF::Layer::Layer(const vector_tile::Tile_Layer *data) : _data(data)
PBF::Layer::Layer(const Data::Layer *layer) : _data(layer)
{
_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)));
_keys.reserve(layer->keys.size());
for (int i = 0; i < layer->keys.size(); i++)
_keys.insert(layer->keys.at(i), i);
_features.reserve(data->features_size());
for (int i = 0; i < data->features_size(); i++)
_features.append(Feature(&(data->features(i)), this));
_features.reserve(layer->features.size());
for (int i = 0; i < layer->features.size(); i++)
_features.append(Feature(&(layer->features.at(i)), this));
std::sort(_features.begin(), _features.end());
}
PBF::PBF(const vector_tile::Tile &tile)
PBF::PBF(const Data &data)
{
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));
for (int i = 0; i < data.layers().size(); i++) {
const Data::Layer &layer = data.layers().at(i);
_layers.insert(layer.name, new Layer(&layer));
}
}

View File

@ -5,10 +5,10 @@
#include <QVector>
#include <QHash>
#include <QPainterPath>
#include "vector_tile.pb.h"
#include "data.h"
typedef QHash<QString, google::protobuf::uint32> KeyHash;
typedef QHash<QByteArray, quint32> KeyHash;
class PBF
{
@ -19,17 +19,17 @@ public:
{
public:
Feature() : _data(0), _layer(0) {}
Feature(const vector_tile::Tile_Feature *data, const Layer *layer)
Feature(const Data::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();}
const QVariant *value(const QByteArray &key) const;
Data::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 Data::Feature *_data;
const Layer *_layer;
};
@ -37,31 +37,30 @@ public:
{
public:
Layer(const vector_tile::Tile_Layer *data);
Layer(const Data::Layer *layer);
const QVector<Feature> &features() const {return _features;}
const QVector<QVariant> &values() const {return _values;}
const QVector<QVariant> &values() const {return _data->values;}
const KeyHash &keys() const {return _keys;}
const vector_tile::Tile_Layer *data() const {return _data;}
const Data::Layer *data() const {return _data;}
private:
const vector_tile::Tile_Layer *_data;
const Data::Layer *_data;
QVector<Feature> _features;
QVector<QVariant> _values;
KeyHash _keys;
};
PBF(const vector_tile::Tile &tile);
PBF(const Data &data);
~PBF();
const QHash<QString, Layer*> &layers() const {return _layers;}
const QHash<QByteArray, Layer*> &layers() const {return _layers;}
private:
QHash<QString, Layer*> _layers;
QHash<QByteArray, 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,6 +3,7 @@
#include <QtEndian>
#include <QDebug>
#include "gzip.h"
#include "data.h"
#include "tile.h"
#include "style.h"
#include "pbfhandler.h"
@ -73,22 +74,25 @@ bool PBFHandler::read(QImage *image)
}
} else if (isPlainPBF(magic))
ba = device()->readAll();
vector_tile::Tile data;
if (!data.ParseFromArray(ba.constData(), ba.size())) {
Data data;
if (!data.load(ba)) {
qCritical() << "Invalid PBF data";
return false;
}
bool ok;
int zoom = format().toInt(&ok);
QList<QByteArray> list(format().split(';'));
int zoom = list.size() ? list.first().toInt() : 0;
int overzoom = (list.size() > 1) ? list.at(1).toInt() : 0;
QSize scaledSize(_scaledSize.isValid()
? _scaledSize : QSize(TILE_SIZE, TILE_SIZE));
QSize size(scaledSize.width()<<overzoom,
scaledSize.height()<<overzoom);
QPointF scale((qreal)scaledSize.width() / TILE_SIZE,
(qreal)scaledSize.height() / TILE_SIZE);
QSize size = _scaledSize.isValid()
? _scaledSize : QSize(TILE_SIZE, TILE_SIZE);
QPointF scale = _scaledSize.isValid()
? QPointF((qreal)_scaledSize.width() / TILE_SIZE,
(qreal)_scaledSize.height() / TILE_SIZE) : QPointF(1.0, 1.0);
*image = QImage(size, QImage::Format_ARGB32_Premultiplied);
Tile tile(image, ok ? zoom : -1, scale);
Tile tile(image, zoom, scale);
_style->render(data, tile);

View File

@ -11,7 +11,7 @@ class Style;
class PBFHandler : public QImageIOHandler
{
public:
PBFHandler(const Style *style) : _style(style) {}
PBFHandler(Style *style) : _style(style) {}
~PBFHandler() {}
bool canRead() const;
@ -24,7 +24,7 @@ public:
static bool canRead(QIODevice *device);
private:
const Style *_style;
Style *_style;
QSize _scaledSize;
};

View File

@ -4,22 +4,28 @@
#include <QDebug>
#include "sprites.h"
/*
Loading the sprites atlas image must be deferred until all image plugins
are loaded, otherwise reading the image will cause a deadlock!
*/
static const QImage &atlas(const QString &fileName)
static QImage sdf2img(const QImage &sdf, const QColor &color)
{
static QImage img(fileName);
QImage img(sdf.convertToFormat(QImage::Format_ARGB32));
quint32 argb = color.rgba();
uchar *bits = img.bits();
int bpl = img.bytesPerLine();
for (int y = 0; y < img.height(); y++) {
for (int x = 0; x < img.width(); x++) {
quint32 *pixel = (quint32*)(bits + y * bpl + x * 4);
*pixel = ((*pixel >> 24) < 192) ? 0 : argb;
}
}
return img;
}
Sprites::Sprite::Sprite(const QJsonObject &json)
: _pixelRatio(1.0), _sdf(false)
{
int x, y, width, height;
if (json.contains("x") && json["x"].isDouble())
x = json["x"].toInt();
else
@ -39,17 +45,14 @@ 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();
}
bool Sprites::load(const QString &jsonFile, const QString &imageFile)
{
_imageFile = imageFile;
QFile file(jsonFile);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qCritical() << jsonFile << ": error opening file";
@ -78,27 +81,60 @@ bool Sprites::load(const QString &jsonFile, const QString &imageFile)
qWarning() << it.key() << ": invalid sprite definition";
}
// Loading the sprites atlas image must be deferred until all image plugins
// are loaded, otherwise reading the image will cause a deadlock in Qt!
_imageFile = imageFile;
return true;
}
QImage Sprites::icon(const QString &name) const
QImage Sprites::sprite(const Sprite &sprite, const QColor &color, qreal scale)
{
if (_imageFile.isEmpty())
_lock.lock();
if (_init <= 0) {
if (_init < 0) {
_lock.unlock();
return QImage();
}
_img = QImage(_imageFile);
if (_img.isNull()) {
qWarning() << _imageFile << ": invalid sprite atlas image";
_init = -1;
_lock.unlock();
return QImage();
}
_init = 1;
}
_lock.unlock();
if (!_img.rect().contains(sprite.rect()))
return QImage();
const QImage &img = atlas(_imageFile);
if (img.isNull())
QImage img(_img.copy(sprite.rect()));
img.setDevicePixelRatio(sprite.pixelRatio());
if (sprite.sdf()) {
if (scale != 1.0) {
QSize size(img.size().width() * scale, img.size().height() * scale);
QImage simg(img.scaled(size, Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
return sdf2img(simg, color);
} else
return sdf2img(img, color);
} else
return img;
}
QImage Sprites::icon(const QString &name, const QColor &color, qreal size)
{
if (name.isNull())
return QImage();
QMap<QString, Sprite>::const_iterator it = _sprites.find(name);
QMap<QString, Sprite>::const_iterator it = _sprites.constFind(name);
if (it == _sprites.constEnd())
return QImage();
if (!img.rect().contains(it->rect()))
return QImage();
QImage ret(img.copy(it->rect()));
ret.setDevicePixelRatio(it->pixelRatio());
return ret;
return sprite(*it, color, size);
}

View File

@ -5,16 +5,19 @@
#include <QMap>
#include <QImage>
#include <QString>
#include <QMutex>
class QJsonObject;
class Sprites
{
public:
Sprites() : _init(0) {}
bool load(const QString &jsonFile, const QString &imageFile);
bool isNull() const {return _imageFile.isNull();}
QImage icon(const QString &name) const;
QImage icon(const QString &name, const QColor &color = Qt::black,
qreal size = 1.0);
private:
class Sprite {
@ -23,14 +26,21 @@ private:
const QRect &rect() const {return _rect;}
qreal pixelRatio() const {return _pixelRatio;}
bool sdf() const {return _sdf;}
private:
QRect _rect;
qreal _pixelRatio;
bool _sdf;
};
QImage sprite(const Sprite &sprite, const QColor &color, qreal scale);
QMap<QString, Sprite> _sprites;
QImage _img;
QMutex _lock;
QString _imageFile;
int _init;
};
#endif // SPRITES_H

View File

@ -8,23 +8,36 @@
#include <QRegularExpression>
#include <QDebug>
#include "text.h"
#include "color.h"
#include "font.h"
#include "tile.h"
#include "pbf.h"
#include "style.h"
static vector_tile::Tile_GeomType geometryType(const QString &str)
static Data::GeomType geometryType(const QString &str)
{
if (str == "Point")
return vector_tile::Tile_GeomType_POINT;
return Data::GeomType::POINT;
else if (str == "LineString")
return vector_tile::Tile_GeomType_LINESTRING;
return Data::GeomType::LINESTRING;
else if (str == "Polygon")
return vector_tile::Tile_GeomType_POLYGON;
return Data::GeomType::POLYGON;
else
return vector_tile::Tile_GeomType_UNKNOWN;
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();
}
}
Style::Layer::Filter::Filter(const QJsonArray &json)
@ -36,50 +49,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<QString, QVariant>(QString(),
_kv = QPair<QByteArray, QVariant>(QByteArray(),
QVariant(geometryType(json.at(2).toString())));
} else {
_type = EQ;
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
variant(json.at(2)));
}
} else if (type == "!=") {
if (json.size() != 3)
INVALID_FILTER(json);
_type = NE;
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
variant(json.at(2)));
} else if (type == "<") {
if (json.size() != 3)
INVALID_FILTER(json);
_type = LT;
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
variant(json.at(2)));
} else if (type == "<=") {
if (json.size() != 3)
INVALID_FILTER(json);
_type = LE;
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
variant(json.at(2)));
} else if (type == ">") {
if (json.size() != 3)
INVALID_FILTER(json);
_type = GT;
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
variant(json.at(2)));
} else if (type == ">=") {
if (json.size() != 3)
INVALID_FILTER(json);
_type = GE;
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
variant(json.at(2)));
} else if (type == "all") {
_type = All;
for (int i = 1; i < json.size(); i++)
@ -92,28 +105,32 @@ Style::Layer::Filter::Filter(const QJsonArray &json)
if (json.size() < 3)
INVALID_FILTER(json);
_type = In;
_kv = QPair<QString, QVariant>(json.at(1).toString(), QVariant());
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
QVariant());
for (int i = 2; i < json.size(); i++)
_set.insert(json.at(i).toString());
_set.insert(json.at(i).toString().toUtf8());
} else if (type == "!in") {
if (json.size() < 3)
INVALID_FILTER(json);
_type = In;
_not = true;
_kv = QPair<QString, QVariant>(json.at(1).toString(), QVariant());
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
QVariant());
for (int i = 2; i < json.size(); i++)
_set.insert(json.at(i).toString());
_set.insert(json.at(i).toString().toUtf8());
} else if (type == "has") {
if (json.size() < 2)
INVALID_FILTER(json);
_type = Has;
_kv = QPair<QString, QVariant>(json.at(1).toString(), QVariant());
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
QVariant());
} else if (type == "!has") {
if (json.size() < 2)
INVALID_FILTER(json);
_type = Has;
_not = true;
_kv = QPair<QString, QVariant>(json.at(1).toString(), QVariant());
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(),
QVariant());
} else
INVALID_FILTER(json);
}
@ -183,7 +200,7 @@ bool Style::Layer::Filter::match(const PBF::Feature &feature) const
if (!(v = feature.value(_kv.first)))
return _not;
else
return _set.contains((*v).toString()) ^ _not;
return _set.contains((*v).toByteArray()) ^ _not;
case Has:
return (feature.value(_kv.first) ? true : false) ^ _not;
case All:
@ -205,7 +222,7 @@ bool Style::Layer::Filter::match(const PBF::Feature &feature) const
QString Style::Layer::Template::value(int zoom, const PBF::Feature &feature) const
{
QRegularExpression rx("\\{[^\\}]*\\}");
static QRegularExpression rx("\\{[^\\}]*\\}");
QString text(_field.value(zoom));
QRegularExpressionMatchIterator it = rx.globalMatch(text);
QStringList keys;
@ -217,7 +234,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);
const QVariant *val = feature.value(key.toUtf8());
text.replace(QString("{%1}").arg(key), val ? val->toString() : "");
}
@ -258,6 +275,9 @@ Style::Layer::Paint::Paint(const QJsonObject &json)
_textHaloColor = FunctionC(json["text-halo-color"], QColor());
_textHaloWidth = FunctionF(json["text-halo-width"]);
_textHaloBlur = FunctionF(json["text-halo-blur"]);
// icon
_iconColor = FunctionC(json["icon-color"]);
}
QPen Style::Layer::Paint::pen(Type type, int zoom) const
@ -293,7 +313,7 @@ QPen Style::Layer::Paint::pen(Type type, int zoom) const
return pen;
}
QBrush Style::Layer::Paint::brush(Type type, int zoom, const Sprites &sprites)
QBrush Style::Layer::Paint::brush(Type type, int zoom, Sprites &sprites)
const
{
QColor color;
@ -370,6 +390,7 @@ Style::Layer::Layout::Layout(const QJsonObject &json)
// icon
_icon = Template(FunctionS(json["icon-image"]));
_iconSize = FunctionF(json["icon-size"]);
// symbol
_symbolPlacement = FunctionS(json["symbol-placement"]);
@ -483,7 +504,7 @@ Style::Layer::Layer(const QJsonObject &json)
_type = Symbol;
// source-layer
_sourceLayer = json["source-layer"].toString();
_sourceLayer = json["source-layer"].toString().toUtf8();
// zooms
if (json.contains("minzoom") && json["minzoom"].isDouble())
@ -512,7 +533,7 @@ bool Style::Layer::match(int zoom, const PBF::Feature &feature) const
return _filter.match(feature);
}
void Style::Layer::setPathPainter(Tile &tile, const Sprites &sprites) const
void Style::Layer::setPathPainter(Tile &tile, Sprites &sprites) const
{
QPainter &p = tile.painter();
int zoom = tile.zoom();
@ -545,14 +566,18 @@ void Style::Layer::setTextProperties(Tile &tile) const
}
void Style::Layer::addSymbol(Tile &tile, const QPainterPath &path,
const PBF::Feature &feature, const Sprites &sprites) const
const PBF::Feature &feature, Sprites &sprites) const
{
QString text = _layout.text(tile.zoom(), feature);
if (text.isEmpty())
QString text(_layout.text(tile.zoom(), feature));
QString icon(_layout.icon(tile.zoom(), feature));
QColor color(_paint.iconColor(tile.zoom()));
qreal size(_layout.iconSize(tile.zoom()));
QImage img(sprites.icon(icon, color, size));
if (text.isEmpty() && img.isNull())
return;
QString icon = _layout.icon(tile.zoom(), feature);
tile.text().addLabel(text, sprites.icon(icon), path);
tile.text().addLabel(text, img, path);
}
static bool loadSprites(const QDir &styleDir, const QString &json,
@ -565,7 +590,7 @@ static bool loadSprites(const QDir &styleDir, const QString &json,
if (QFileInfo::exists(spritesImg))
return sprites.load(spritesJSON, spritesImg);
else {
qCritical() << spritesImg << ": no such file";
qWarning() << spritesImg << ": no such file";
return false;
}
}
@ -598,20 +623,20 @@ bool Style::load(const QString &fileName)
_layers.append(Layer(layers[i].toObject()));
}
QDir styleDir = QFileInfo(fileName).absoluteDir();
QDir styleDir(QFileInfo(fileName).absoluteDir());
loadSprites(styleDir, "sprite.json", "sprite.png", _sprites);
loadSprites(styleDir, "sprite@2x.json", "sprite@2x.png", _sprites2x);
return true;
}
const Sprites &Style::sprites(const QPointF &scale) const
Sprites &Style::sprites(const QPointF &scale)
{
return (scale.x() > 1.0 || scale.y() > 1.0)
&& !_sprites2x.isNull() ? _sprites2x : _sprites;
}
void Style::setupLayer(Tile &tile, const Layer &layer) const
void Style::setupLayer(Tile &tile, const Layer &layer)
{
if (layer.isSymbol())
layer.setTextProperties(tile);
@ -619,7 +644,7 @@ void Style::setupLayer(Tile &tile, const Layer &layer) const
layer.setPathPainter(tile, sprites(tile.scale()));
}
void Style::drawBackground(Tile &tile) const
void Style::drawBackground(Tile &tile)
{
QRectF rect(QPointF(0, 0), QSizeF(tile.size().width() / tile.scale().x(),
tile.size().height() / tile.scale().y()));
@ -634,13 +659,10 @@ void Style::drawBackground(Tile &tile) const
_layers.first().setPathPainter(tile, _sprites);
tile.painter().drawPath(path);
}
//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
Tile &tile, const QSizeF &factor)
{
if (!layer.match(tile.zoom(), feature))
return;
@ -656,17 +678,17 @@ void Style::drawFeature(const PBF::Feature &feature, const Layer &layer,
}
void Style::drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer,
Tile &tile) const
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);
@ -675,12 +697,12 @@ void Style::drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer,
tile.painter().restore();
}
void Style::render(const PBF &data, Tile &tile) const
void Style::render(const PBF &data, Tile &tile)
{
drawBackground(tile);
for (int i = 0; i < _layers.size(); i++) {
QHash<QString, PBF::Layer*>::const_iterator it = data.layers().find(
QHash<QByteArray, PBF::Layer*>::const_iterator it = data.layers().find(
_layers.at(i).sourceLayer());
if (it == data.layers().constEnd())
continue;
@ -689,4 +711,11 @@ void Style::render(const PBF &data, Tile &tile) const
}
tile.text().render(&tile.painter());
//QRectF rect(QPointF(0, 0), QSizeF(tile.size().width() / tile.scale().x(),
// tile.size().height() / tile.scale().y()));
//tile.painter().setPen(Qt::red);
//tile.painter().setBrush(Qt::NoBrush);
//tile.painter().setRenderHint(QPainter::Antialiasing, false);
//tile.painter().drawRect(rect);
}

View File

@ -29,7 +29,7 @@ public:
Style(QObject *parent = 0) : QObject(parent) {}
bool load(const QString &fileName);
void render(const PBF &data, Tile &tile) const;
void render(const PBF &data, Tile &tile);
private:
class Layer {
@ -37,17 +37,17 @@ private:
Layer() : _type(Unknown), _minZoom(0), _maxZoom(24) {}
Layer(const QJsonObject &json);
const QString &sourceLayer() const {return _sourceLayer;}
const QByteArray &sourceLayer() const {return _sourceLayer;}
bool isPath() const {return (_type == Line || _type == Fill);}
bool isBackground() const {return (_type == Background);}
bool isSymbol() const {return (_type == Symbol);}
bool isVisible() const {return (_layout.visible());}
bool match(int zoom, const PBF::Feature &feature) const;
void setPathPainter(Tile &tile, const Sprites &sprites) const;
void setPathPainter(Tile &tile, Sprites &sprites) const;
void setTextProperties(Tile &tile) const;
void addSymbol(Tile &tile, const QPainterPath &path,
const PBF::Feature &feature, const Sprites &sprites) const;
const PBF::Feature &feature, Sprites &sprites) const;
private:
enum Type {
@ -74,8 +74,8 @@ private:
Type _type;
bool _not;
QSet<QString> _set;
QPair<QString, QVariant> _kv;
QSet<QByteArray> _set;
QPair<QByteArray, QVariant> _kv;
QVector<Filter> _filters;
};
@ -103,6 +103,8 @@ private:
{return _text.value(zoom, feature).trimmed();}
QString icon(int zoom, const PBF::Feature &feature) const
{return _icon.value(zoom, feature);}
qreal iconSize(int zoom) const
{return _iconSize.value(zoom);}
QFont font(int zoom) const;
Qt::PenCapStyle lineCap(int zoom) const;
Qt::PenJoinStyle lineJoin(int zoom) const;
@ -116,6 +118,7 @@ private:
Template _text;
Template _icon;
FunctionF _iconSize;
FunctionF _textSize;
FunctionF _textMaxWidth;
FunctionF _textMaxAngle;
@ -135,13 +138,15 @@ private:
Paint(const QJsonObject &json);
QPen pen(Layer::Type type, int zoom) const;
QBrush brush(Layer::Type type, int zoom, const Sprites &sprites)
QBrush brush(Layer::Type type, int zoom, Sprites &sprites)
const;
qreal opacity(Layer::Type type, int zoom) const;
bool antialias(Layer::Type type, int zoom) const;
Text::Halo halo(int zoom) const
{return Text::Halo(_textHaloColor.value(zoom),
_textHaloWidth.value(zoom), _textHaloBlur.value(zoom));}
QColor iconColor(int zoom) const
{return _iconColor.value(zoom);}
private:
FunctionC _textColor;
@ -150,6 +155,7 @@ private:
FunctionC _fillColor;
FunctionC _fillOutlineColor;
FunctionC _backgroundColor;
FunctionC _iconColor;
FunctionF _fillOpacity;
FunctionF _lineOpacity;
FunctionF _lineWidth;
@ -161,21 +167,21 @@ private:
};
Type _type;
QString _sourceLayer;
QByteArray _sourceLayer;
int _minZoom, _maxZoom;
Filter _filter;
Layout _layout;
Paint _paint;
};
const Sprites &sprites(const QPointF &scale) const;
Sprites &sprites(const QPointF &scale);
void drawBackground(Tile &tile) const;
void setupLayer(Tile &tile, const Layer &layer) const;
void drawBackground(Tile &tile);
void setupLayer(Tile &tile, const Layer &layer);
void drawFeature(const PBF::Feature &feature, const Layer &layer,
Tile &tile, const QSizeF &factor) const;
Tile &tile, const QSizeF &factor);
void drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer,
Tile &tile) const;
Tile &tile);
QVector<Layer> _layers;
Sprites _sprites, _sprites2x;

View File

@ -14,15 +14,10 @@ Text::~Text()
void Text::render(QPainter *painter) const
{
QSet<QString> set;
for (int i = 0; i < _items.size(); i++) {
const TextItem *ti = _items.at(i);
if (ti->isVisible() && _sceneRect.intersects(ti->boundingRect())
&& !set.contains(ti->text())) {
if (ti->isVisible() && _sceneRect.intersects(ti->boundingRect()))
ti->paint(painter);
set.insert(ti->text());
}
}
}
@ -51,6 +46,8 @@ void Text::addLabel(const QString &text, const QImage &icon,
} else {
switch (_placement) {
case Line:
if (text.isEmpty())
return;
ti = new TextPathItem(text, path, _font, _maxAngle, _sceneRect);
break;
case LineCenter:
@ -73,7 +70,7 @@ void Text::addLabel(const QString &text, const QImage &icon,
ti->setHalo(_halo);
addItem(ti);
QList<TextItem*> ci = collidingItems(ti);
QList<TextItem*> ci(collidingItems(ti));
for (int i = 0; i < ci.size(); i++)
ci[i]->setVisible(false);
}

View File

@ -2,13 +2,6 @@
#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)
{
@ -30,10 +23,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);
@ -41,7 +34,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);
@ -49,7 +42,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);
@ -64,16 +57,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;
@ -171,6 +164,12 @@ static QList<QPolygonF> polyLines(const QPainterPath &path, const QRectF &rect)
return lines;
}
static qreal diff(qreal a1, qreal a2)
{
qreal d = qAbs(a1 - a2);
return (d > 180) ? 360 - d : d;
}
static QPainterPath textPath(const QPainterPath &path, qreal textWidth,
qreal maxAngle, qreal charWidth, const QRectF &tileRect)
{
@ -189,11 +188,16 @@ static QPainterPath textPath(const QPainterPath &path, qreal textWidth,
qreal sl = l.length();
qreal a = l.angle();
if ((sl < charWidth) || (j > 1 && qAbs(angle - a) > maxAngle)) {
if (sl < charWidth) {
if (length > textWidth)
return subpath(pl, last, j - 1, length - textWidth);
last = j;
length = 0;
} else if (j > 1 && diff(angle, a) > maxAngle) {
if (length > textWidth)
return subpath(pl, last, j - 1, length - textWidth);
last = j - 1;
length = sl;
} else
length += sl;

View File

@ -85,7 +85,7 @@ TextPointItem::TextPointItem(const QString &text, const QPointF &pos,
: TextItem(text, font), _pos(pos), _icon(icon), _maxWidth(maxWidth),
_anchor(anchor)
{
_textRect = fuzzyBoundingRect();
_textRect = text.isEmpty() ? QRectF() : fuzzyBoundingRect();
_boundingRect = moveTextRect(_textRect);
if (!_icon.isNull()) {

View File

@ -2265,7 +2265,6 @@
"source": "openmaptiles",
"source-layer": "building",
"minzoom": 13,
"maxzoom": 14,
"layout": {
"visibility": "visible"
},
@ -4059,4 +4058,4 @@
}
],
"id": "osm-liberty"
}
}