Compare commits

..

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

39 changed files with 609 additions and 1373 deletions

View File

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

1
.github/FUNDING.yml vendored
View File

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

View File

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

@ -1,25 +0,0 @@
name: Linux
on:
push:
branches:
- master
jobs:
build:
name: QtPBFImagePlugin
runs-on: ubuntu-22.04
strategy:
matrix:
config: ['release', 'debug']
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
- name: Configure build
run: qmake CONFIG+=${{ matrix.config }} pbfplugin.pro
- name: Build project
run: make -j4

View File

@ -1,27 +0,0 @@
name: OS X
on:
push:
branches:
- master
jobs:
build:
name: QtPBFImagePlugin
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Qt
uses: jurplel/install-qt-action@v4
with:
version: '6.8.2'
- name: Configure build
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

9
.gitignore vendored
View File

@ -1,9 +0,0 @@
# Object files
*.o
# Qt stuff
/.qmake.stash
moc_*.cpp
moc_*.h
qrc_*.cpp
Makefile*

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

135
README.md
View File

@ -3,138 +3,91 @@ Qt image plugin for displaying Mapbox vector tiles
## Description ## Description
QtPBFImagePlugin is a Qt image plugin that enables applications capable of QtPBFImagePlugin is a Qt image plugin that enables applications capable of
displaying raster MBTiles maps or raster XYZ online maps to also display displaying raster MBTiles maps or raster XYZ online maps to also display PBF
PBF(MVT) vector tiles without (almost, see usage) any application modifications. vector tiles without (almost, see usage) any application modifications.
Standard Mapbox GL Styles are used for styling the maps. Most relevant style Standard Mapbox GL Styles are used for styling the maps. Most relevant style
features used by [Maputnik](https://maputnik.github.io/editor) are supported. features used by [Maputnik](http://editor.openmaptiles.org) are supported.
A default fallback style (OSM-Liberty) for OpenMapTiles is part of the plugin. The style is loaded from the
[$AppDataLocation](http://doc.qt.io/qt-5/qstandardpaths.html)/style/style.json
file on plugin load. If the style uses a sprite, the sprite JSON file must
be named sprite.json and the sprite image sprite.png and both files must be
placed in the same directory as the style itself. A default fallback style
(OSM-Liberty) for OpenMapTiles is part of the plugin.
"Plain" PBF files as well as gzip compressed files (as used in MBTiles) are "Plain" PBF files as well as gzip compressed files (as used in MBTiles) are
supported by the plugin. The tile size is (since version 2.0 of the plugin) 512px supported by the plugin.
to fit the styles and available data (OpenMapTiles, Mapbox tiles).
## Usage ## Usage
Due to a major design flaw in the Mapbox vector tiles specification - the zoom Due to a major design flaw in the Mapbox vector tiles specification - the zoom
is not part of the PBF data - the plugin can not be used "as is", but passing 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* the zoom level is necessary. This is done by exploiting the optional *format*
parameter of the QImage constructor or the *QImage::loadFromData()* or parameter of the QImage constructor or the QImage::fromData() or
*QPixmap::loadFromData()* functions. The zoom number is passed as ASCII string QPixmap::loadFromData() functions. The zoom number is passed as ASCII string
to the functions: to the functions:
```cpp ```cpp
QImage img; QPixmap pm;
img.loadFromData(data, QByteArray::number(zoom)); pm.loadFromData(tileData, QString::number(zoom).toLatin1());
``` ```
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: so when used like in the following example:
```cpp ```cpp
QImage img; QImageReader reader(file, QString::number(zoom).toLatin1());
QImageReader reader(file, QByteArray::number(zoom)); reader.setScaledSize(QSize(512, 512));
reader.setScaledSize(QSize(1024, 1024)); reader.read(&image);
reader.read(&img);
``` ```
you will get 1024x1024px tiles with a pixel ratio of 2 (= HiDPI tiles). you will get 512x512px tiles with a pixel ratio of 2 (= HiDPI tiles).
### 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
[$AppDataLocation](http://doc.qt.io/qt-5/qstandardpaths.html)/style/style.json
file on plugin load. If the style uses a sprite, the sprite JSON file must
be named `sprite.json` and the sprite image `sprite.png` and both files must be
placed in the same directory as the style itself. *A style compatible with the
tiles data schema (Mapbox, OpenMapTiles, Tilezen, Ordnance Survey, Esri, ...)
must be used.*
For a list of "ready to use" styles see the
[QtPBFImagePlugin-styles](https://github.com/tumic0/QtPBFImagePlugin-styles)
repository.
## Build ## Build
### Requirements ### Requirements
* Qt5 >= 5.15 or Qt6 * Qt >= 5.4 (5.6 for HiDPI support)
* Google Protocol Buffers (protobuf-lite)
* Zlib * Zlib
### Build steps ### Build steps
#### Linux, OS X and Android #### Linux
```shell ```shell
qmake pbfplugin.pro qmake pbfplugin.pro
make make
``` ```
#### Windows #### Windows
```shell ```shell
qmake ZLIB=path/to/zlib pbfplugin.pro qmake PROTOBUF=path/to/protobuf ZLIB=path/to/zlib pbfplugin.pro
nmake nmake
``` ```
#### OS X
```shell
qmake PROTOBUF=path/to/protobuf pbfplugin.pro
make
```
## Install ## Install
Copy the plugin to the system Qt image plugins path to make it work. You may 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
For Linux, there are RPM and DEB [packages](https://build.opensuse.org/project/show/home:tumic:QtPBFImagePlugin) Linux, there are RPM and DEB [packages](https://build.opensuse.org/project/show/home:tumic:QtPBFImagePlugin)
for most common distros available on OBS. for most common distros available on OBS.
## Limitations ## Limitations
* Only data that is part of the PBF file is displayed. External layers defined * Only data that is part of the PBF file is displayed. External layers defined in the
in the style are ignored. style are ignored.
* Text PBF features must have a unique id (OpenMapTiles >= v3.7) for the text * Text PBF features must have a unique id (OpenMapTiles >= v3.7) for the text layout
layout algorithm to work properly. Additionally, the tile buffer must be large algorithm to work properly.
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/projects/home:tumic:QtPBFImagePlugin/packages/QtPBFImagePlugin/files/qt6-qtpbfimageformat.changes)
## Status ## Status
A picture is worth a thousand words. A picture is worth a thousand words. Data and styles from https://openmaptiles.org.
#### OpenMapTiles #### OSM-liberty
![osm-liberty 2](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-2.png)
* Data: [MapTiler](https://github.com/tumic0/GPXSee-maps/blob/master/World/MapTiler-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) ![osm-liberty 5](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-5.png)
![osm-liberty 8](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-8.png) ![osm-liberty 8](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-8.png)
![osm-liberty 12](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-12.png) ![osm-liberty 11](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-11.png)
![osm-liberty 14](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-14.png) ![osm-liberty 14](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-14.png)
![osm-liberty 15](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-15.png)
#### Mapbox #### Klokantech-basic
![klokantech-basic 2](https://tumic0.github.io/QtPBFImagePlugin/images/klokantech-basic-2.png)
* Data: [Mapbox](https://github.com/tumic0/GPXSee-maps/blob/master/World/Mapbox.tpl) ![klokantech-basic 4](https://tumic0.github.io/QtPBFImagePlugin/images/klokantech-basic-4.png)
* Style: [Bright](https://github.com/tumic0/QtPBFImagePlugin-styles/blob/master/Mapbox/bright/style.json) ![klokantech-basic 8](https://tumic0.github.io/QtPBFImagePlugin/images/klokantech-basic-8.png)
![klokantech-basic 13](https://tumic0.github.io/QtPBFImagePlugin/images/klokantech-basic-13.png)
![bright 4](https://tumic0.github.io/QtPBFImagePlugin/images/bright-4.png) ![klokantech-basic 14](https://tumic0.github.io/QtPBFImagePlugin/images/klokantech-basic-14.png)
![bright 6](https://tumic0.github.io/QtPBFImagePlugin/images/bright-6.png)
![bright 13](https://tumic0.github.io/QtPBFImagePlugin/images/bright-13.png)
![bright 15](https://tumic0.github.io/QtPBFImagePlugin/images/bright-15.png)
![bright 17](https://tumic0.github.io/QtPBFImagePlugin/images/bright-17.png)
#### Tilezen
* Data: [HERE](https://github.com/tumic0/GPXSee-maps/blob/master/World/here-vector.tpl)
* Style: [Apollo-Bright](https://github.com/tumic0/QtPBFImagePlugin-styles/blob/master/Tilezen/apollo-bright/style.json)
![apollo-bright 4](https://tumic0.github.io/QtPBFImagePlugin/images/apollo-bright-4.png)
![apollo-bright 6](https://tumic0.github.io/QtPBFImagePlugin/images/apollo-bright-6.png)
![apollo-bright 12](https://tumic0.github.io/QtPBFImagePlugin/images/apollo-bright-12.png)
![apollo-bright 15](https://tumic0.github.io/QtPBFImagePlugin/images/apollo-bright-15.png)
![apollo-bright 16](https://tumic0.github.io/QtPBFImagePlugin/images/apollo-bright-16.png)
## Applications using QtPBFImagePlugin ## Applications using QtPBFImagePlugin
* [GPXSee](https://www.gpxsee.org) * [GPXSee](https://www.gpxsee.org)

View File

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

View File

@ -2,10 +2,12 @@ TARGET = pbf
TEMPLATE = lib TEMPLATE = lib
CONFIG += plugin CONFIG += plugin
QT += gui QT += gui
VERSION = 4.2
PROTOS = protobuf/vector_tile.proto
include(protobuf/vector_tile.pri)
INCLUDEPATH += ./protobuf
HEADERS += src/pbfhandler.h \ HEADERS += src/pbfhandler.h \
src/data.h \
src/pbfplugin.h \ src/pbfplugin.h \
src/gzip.h \ src/gzip.h \
src/pbf.h \ src/pbf.h \
@ -18,9 +20,9 @@ HEADERS += src/pbfhandler.h \
src/textpointitem.h \ src/textpointitem.h \
src/font.h \ src/font.h \
src/textitem.h \ src/textitem.h \
src/sprites.h src/sprites.h \
src/config.h
SOURCES += src/pbfplugin.cpp \ SOURCES += src/pbfplugin.cpp \
src/data.cpp \
src/pbfhandler.cpp \ src/pbfhandler.cpp \
src/gzip.cpp \ src/gzip.cpp \
src/pbf.cpp \ src/pbf.cpp \
@ -35,29 +37,21 @@ SOURCES += src/pbfplugin.cpp \
src/textitem.cpp src/textitem.cpp
RESOURCES += pbfplugin.qrc RESOURCES += pbfplugin.qrc
DEFINES += QT_NO_DEPRECATED_WARNINGS unix:!macx{
LIBS += -lprotobuf-lite \
unix:!macx:!android { -lz
LIBS += -lz
target.path += $$[QT_INSTALL_PLUGINS]/imageformats
INSTALLS += target
} }
win32 { win32 {
INCLUDEPATH += $$ZLIB/include INCLUDEPATH += $$PROTOBUF/include \
LIBS += $$ZLIB/lib/zlibstatic.lib $$ZLIB/include
LIBS += $$PROTOBUF/lib/libprotobuf-lite.lib \
QMAKE_TARGET_PRODUCT = QtPBFImagePlugin $$ZLIB/lib/zlibstatic.lib
QMAKE_TARGET_DESCRIPTION = Qt $$QT_VERSION MVT/PBF image plugin
QMAKE_TARGET_COPYRIGHT = Copyright (c) 2018-2025 Martin Tuma
} }
macx { macx {
LIBS += -lz INCLUDEPATH += $$PROTOBUF/include
LIBS += $$PROTOBUF/lib/libprotobuf-lite.a \
-lz
} }
android {
LIBS += -lz
top_builddir=$$shadowed($$PWD) target.path += $$[QT_INSTALL_PLUGINS]/imageformats
DESTDIR = $$top_builddir/plugins INSTALLS += target
TARGET = $$qt5LibraryTarget(libpbf, "plugins/imageformats/")
}

View File

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

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

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

@ -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

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,14 @@
#include "pbf.h" #include "pbf.h"
#define POLYGON vector_tile::Tile_GeomType_POLYGON
#define LINESTRING vector_tile::Tile_GeomType_LINESTRING
#define POINT vector_tile::Tile_GeomType_POINT
#define MOVE_TO 1 #define MOVE_TO 1
#define LINE_TO 2 #define LINE_TO 2
#define CLOSE_PATH 7 #define CLOSE_PATH 7
static inline qint32 zigzag32decode(quint32 value) static inline qint32 zigzag32decode(quint32 value)
{ {
return static_cast<qint32>((value >> 1u) ^ static_cast<quint32>( return static_cast<qint32>((value >> 1u) ^ static_cast<quint32>(
@ -15,17 +20,37 @@ static inline QPoint parameters(quint32 v1, quint32 v2)
return QPoint(zigzag32decode(v1), zigzag32decode(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()); const KeyHash &keys(_layer->keys());
KeyHash::const_iterator it(keys.find(key)); KeyHash::const_iterator it(keys.find(key));
if (it == keys.constEnd()) if (it == keys.constEnd())
return 0; return 0;
quint32 index = *it; google::protobuf::uint32 index = *it;
for (int i = 0; i < _data->tags.size(); i = i + 2) for (int i = 0; i < _data->tags_size(); i = i + 2)
if (_data->tags.at(i) == index) if (_data->tags(i) == index)
return &(_layer->values().at(_data->tags.at(i+1))); return &(_layer->values().at(_data->tags(i+1)));
return 0; return 0;
} }
@ -35,15 +60,15 @@ QPainterPath PBF::Feature::path(const QSizeF &factor) const
QPoint cursor; QPoint cursor;
QPainterPath path; QPainterPath path;
for (int i = 0; i < _data->geometry.size(); i++) { for (int i = 0; i < _data->geometry_size(); i++) {
quint32 g = _data->geometry.at(i); quint32 g = _data->geometry(i);
unsigned cmdId = g & 0x7; unsigned cmdId = g & 0x7;
unsigned cmdCount = g >> 3; unsigned cmdCount = g >> 3;
if (cmdId == MOVE_TO) { if (cmdId == MOVE_TO) {
for (unsigned j = 0; j < cmdCount; j++) { for (unsigned j = 0; j < cmdCount; j++) {
QPoint offset = parameters(_data->geometry.at(i+1), QPoint offset = parameters(_data->geometry(i+1),
_data->geometry.at(i+2)); _data->geometry(i+2));
i += 2; i += 2;
cursor += offset; cursor += offset;
path.moveTo(QPointF(cursor.x() * factor.width(), path.moveTo(QPointF(cursor.x() * factor.width(),
@ -51,8 +76,8 @@ QPainterPath PBF::Feature::path(const QSizeF &factor) const
} }
} else if (cmdId == LINE_TO) { } else if (cmdId == LINE_TO) {
for (unsigned j = 0; j < cmdCount; j++) { for (unsigned j = 0; j < cmdCount; j++) {
QPoint offset = parameters(_data->geometry.at(i+1), QPoint offset = parameters(_data->geometry(i+1),
_data->geometry.at(i+2)); _data->geometry(i+2));
i += 2; i += 2;
cursor += offset; cursor += offset;
path.lineTo(QPointF(cursor.x() * factor.width(), path.lineTo(QPointF(cursor.x() * factor.width(),
@ -67,27 +92,32 @@ QPainterPath PBF::Feature::path(const QSizeF &factor) const
return path; 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()); _keys.reserve(data->keys_size());
for (int i = 0; i < layer->keys.size(); i++) for (int i = 0; i < data->keys_size(); i++)
_keys.insert(layer->keys.at(i), 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()); _features.reserve(data->features_size());
for (int i = 0; i < layer->features.size(); i++) for (int i = 0; i < data->features_size(); i++)
_features.append(Feature(&(layer->features.at(i)), this)); _features.append(Feature(&(data->features(i)), this));
std::sort(_features.begin(), _features.end()); qSort(_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++) { for (int i = 0; i < tile.layers_size(); i++) {
const Data::Layer &layer = data.layers().at(i); const vector_tile::Tile_Layer &layer = tile.layers(i);
_layers.insert(layer.name, new Layer(&layer)); _layers.insert(QString::fromStdString(layer.name()), new Layer(&layer));
} }
} }
PBF::~PBF() PBF::~PBF()
{ {
qDeleteAll(_layers); for (QHash<QString, Layer*>::iterator it = _layers.begin();
it != _layers.end(); it++)
delete *it;
} }

View File

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

View File

@ -3,30 +3,29 @@
#include <QtEndian> #include <QtEndian>
#include <QDebug> #include <QDebug>
#include "gzip.h" #include "gzip.h"
#include "data.h"
#include "tile.h" #include "tile.h"
#include "style.h" #include "style.h"
#include "pbfhandler.h" #include "pbfhandler.h"
#define TILE_SIZE 512 #define TILE_SIZE 256
#define GZIP_MAGIC 0x1F8B #define GZIP_MAGIC 0x1F8B0800
#define GZIP_MAGIC_MASK 0xFFFF #define GZIP_MAGIC_MASK 0xFFFFFF00
#define PBF_MAGIC 0x1A00 #define PBF_MAGIC 0x1A000000
#define PBF_MAGIC_MASK 0xFF00 #define PBF_MAGIC_MASK 0xFF000000
static bool isMagic(quint16 magic, quint16 mask, quint16 value) static bool isMagic(quint32 magic, quint32 mask, quint32 value)
{ {
return ((qFromBigEndian(value) & mask) == magic); return ((qFromBigEndian(value) & mask) == magic);
} }
static bool isGZIPPBF(quint16 magic) static bool isGZIPPBF(quint32 magic)
{ {
return isMagic(GZIP_MAGIC, GZIP_MAGIC_MASK, magic); return isMagic(GZIP_MAGIC, GZIP_MAGIC_MASK, magic);
} }
static bool isPlainPBF(quint16 magic) static bool isPlainPBF(quint32 magic)
{ {
return isMagic(PBF_MAGIC, PBF_MAGIC_MASK, magic); return isMagic(PBF_MAGIC, PBF_MAGIC_MASK, magic);
} }
@ -35,7 +34,7 @@ static bool isPlainPBF(quint16 magic)
bool PBFHandler::canRead() const bool PBFHandler::canRead() const
{ {
if (canRead(device())) { if (canRead(device())) {
setFormat("mvt"); setFormat("pbf");
return true; return true;
} else } else
return false; return false;
@ -43,56 +42,43 @@ bool PBFHandler::canRead() const
bool PBFHandler::canRead(QIODevice *device) bool PBFHandler::canRead(QIODevice *device)
{ {
quint16 magic; quint32 magic;
qint64 size = device->peek((char*)&magic, sizeof(magic)); qint64 size = device->peek((char*)&magic, sizeof(magic));
if (size != sizeof(magic)) if (size != sizeof(magic))
return false; return false;
if (isPlainPBF(magic)) return (isGZIPPBF(magic) || isPlainPBF(magic));
return true;
else if (isGZIPPBF(magic)) {
QByteArray ba(Gzip::uncompress(device, sizeof(magic)));
if (ba.size() < (int)sizeof(magic))
return false;
return isPlainPBF(*((quint16*)ba.constData()));
} else
return false;
} }
bool PBFHandler::read(QImage *image) bool PBFHandler::read(QImage *image)
{ {
quint16 magic; quint32 magic;
if (device()->peek((char*)&magic, sizeof(magic)) != sizeof(magic)) if (device()->peek((char*)&magic, sizeof(magic)) != sizeof(magic))
return false; return false;
QByteArray ba; QByteArray ba;
if (isGZIPPBF(magic)) { if (isGZIPPBF(magic))
ba = Gzip::uncompress(device()); ba = Gzip::uncompress(device()->readAll());
if (ba.isNull()) { else if (isPlainPBF(magic))
qCritical() << "Invalid gzip data";
return false;
}
} else if (isPlainPBF(magic))
ba = device()->readAll(); ba = device()->readAll();
Data data; if (ba.isNull())
if (!data.load(ba)) { return false;
vector_tile::Tile data;
if (!data.ParseFromArray(ba.constData(), ba.size())) {
qCritical() << "Invalid PBF data"; qCritical() << "Invalid PBF data";
return false; return false;
} }
QList<QByteArray> list(format().split(';')); bool ok;
int zoom = list.size() ? list.first().toInt() : 0; int zoom = format().toInt(&ok);
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(_scaledSize.width() / TILE_SIZE, _scaledSize.height() / TILE_SIZE)
: QPointF(1.0, 1.0);
*image = QImage(size, QImage::Format_ARGB32_Premultiplied); *image = QImage(size, QImage::Format_ARGB32_Premultiplied);
Tile tile(image, zoom, scale); Tile tile(image, ok ? zoom : -1, scale);
_style->render(data, tile); _style->render(data, tile);

View File

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

View File

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

View File

@ -4,28 +4,22 @@
#include <QDebug> #include <QDebug>
#include "sprites.h" #include "sprites.h"
static QImage sdf2img(const QImage &sdf, const QColor &color)
/*
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)
{ {
QImage img(sdf.convertToFormat(QImage::Format_ARGB32)); static QImage *img = new QImage(fileName);
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; return img;
} }
Sprites::Sprite::Sprite(const QJsonObject &json) Sprites::Sprite::Sprite(const QJsonObject &json)
: _pixelRatio(1.0), _sdf(false)
{ {
int x, y, width, height; int x, y, width, height;
if (json.contains("x") && json["x"].isDouble()) if (json.contains("x") && json["x"].isDouble())
x = json["x"].toInt(); x = json["x"].toInt();
else else
@ -45,14 +39,17 @@ Sprites::Sprite::Sprite(const QJsonObject &json)
_rect = QRect(x, y, width, height); _rect = QRect(x, y, width, height);
if (json.contains("pixelRatio") && json["pixelRatio"].isDouble()) if (json.contains("pixelRatio") && json["pixelRatio"].isDouble())
_pixelRatio = json["pixelRatio"].toDouble(); _pixelRatio = json["pixelRatio"].toDouble();
if (json.contains("sdf") && json["sdf"].isBool()) else
_sdf = json["sdf"].toBool(); _pixelRatio = 1.0;
} }
bool Sprites::load(const QString &jsonFile, const QString &imageFile) bool Sprites::load(const QString &jsonFile, const QString &imageFile)
{ {
_imageFile = imageFile;
QFile file(jsonFile); QFile file(jsonFile);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qCritical() << jsonFile << ": error opening file"; qCritical() << jsonFile << ": error opening file";
@ -81,60 +78,27 @@ bool Sprites::load(const QString &jsonFile, const QString &imageFile)
qWarning() << it.key() << ": invalid sprite definition"; 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; return true;
} }
QImage Sprites::sprite(const Sprite &sprite, const QColor &color, qreal scale) QImage Sprites::icon(const QString &name) const
{ {
_lock.lock(); if (_imageFile.isEmpty())
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(); return QImage();
QImage img(_img.copy(sprite.rect())); const QImage *img = atlas(_imageFile);
img.setDevicePixelRatio(sprite.pixelRatio()); if (img->isNull())
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(); return QImage();
QMap<QString, Sprite>::const_iterator it = _sprites.constFind(name); QMap<QString, Sprite>::const_iterator it = _sprites.find(name);
if (it == _sprites.constEnd()) if (it == _sprites.constEnd())
return QImage(); return QImage();
return sprite(*it, color, size); if (!img->rect().contains(it->rect()))
return QImage();
QImage ret(img->copy(it->rect()));
ret.setDevicePixelRatio(it->pixelRatio());
return ret;
} }

View File

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

View File

@ -5,39 +5,25 @@
#include <QJsonArray> #include <QJsonArray>
#include <QFileInfo> #include <QFileInfo>
#include <QDir> #include <QDir>
#include <QRegularExpression>
#include <QDebug> #include <QDebug>
#include "text.h" #include "text.h"
#include "color.h"
#include "font.h" #include "font.h"
#include "tile.h" #include "tile.h"
#include "pbf.h" #include "pbf.h"
#include "style.h" #include "style.h"
static Data::GeomType geometryType(const QString &str) static vector_tile::Tile_GeomType geometryType(const QString &str)
{ {
if (str == "Point") if (str == "Point")
return Data::GeomType::POINT; return vector_tile::Tile_GeomType_POINT;
else if (str == "LineString") else if (str == "LineString")
return Data::GeomType::LINESTRING; return vector_tile::Tile_GeomType_LINESTRING;
else if (str == "Polygon") else if (str == "Polygon")
return Data::GeomType::POLYGON; return vector_tile::Tile_GeomType_POLYGON;
else else
return Data::GeomType::UNKNOWN; return vector_tile::Tile_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) Style::Layer::Filter::Filter(const QJsonArray &json)
@ -49,50 +35,50 @@ Style::Layer::Filter::Filter(const QJsonArray &json)
if (json.isEmpty()) if (json.isEmpty())
INVALID_FILTER(json); INVALID_FILTER(json);
QString type(json.at(0).toString()); QString type = json.at(0).toString();
if (type == "==") { if (type == "==") {
if (json.size() != 3) if (json.size() != 3)
INVALID_FILTER(json); INVALID_FILTER(json);
if (json.at(1).toString() == "$type") { if (json.at(1).toString() == "$type") {
_type = GeometryType; _type = GeometryType;
_kv = QPair<QByteArray, QVariant>(QByteArray(), _kv = QPair<QString, QVariant>(QString(),
QVariant(geometryType(json.at(2).toString()))); QVariant(geometryType(json.at(2).toString())));
} else { } else {
_type = EQ; _type = EQ;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(), _kv = QPair<QString, QVariant>(json.at(1).toString(),
variant(json.at(2))); json.at(2).toVariant());
} }
} else if (type == "!=") { } else if (type == "!=") {
if (json.size() != 3) if (json.size() != 3)
INVALID_FILTER(json); INVALID_FILTER(json);
_type = NE; _type = NE;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(), _kv = QPair<QString, QVariant>(json.at(1).toString(),
variant(json.at(2))); json.at(2).toVariant());
} else if (type == "<") { } else if (type == "<") {
if (json.size() != 3) if (json.size() != 3)
INVALID_FILTER(json); INVALID_FILTER(json);
_type = LT; _type = LT;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(), _kv = QPair<QString, QVariant>(json.at(1).toString(),
variant(json.at(2))); json.at(2).toVariant());
} else if (type == "<=") { } else if (type == "<=") {
if (json.size() != 3) if (json.size() != 3)
INVALID_FILTER(json); INVALID_FILTER(json);
_type = LE; _type = LE;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(), _kv = QPair<QString, QVariant>(json.at(1).toString(),
variant(json.at(2))); json.at(2).toVariant());
} else if (type == ">") { } else if (type == ">") {
if (json.size() != 3) if (json.size() != 3)
INVALID_FILTER(json); INVALID_FILTER(json);
_type = GT; _type = GT;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(), _kv = QPair<QString, QVariant>(json.at(1).toString(),
variant(json.at(2))); json.at(2).toVariant());
} else if (type == ">=") { } else if (type == ">=") {
if (json.size() != 3) if (json.size() != 3)
INVALID_FILTER(json); INVALID_FILTER(json);
_type = GE; _type = GE;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(), _kv = QPair<QString, QVariant>(json.at(1).toString(),
variant(json.at(2))); json.at(2).toVariant());
} else if (type == "all") { } else if (type == "all") {
_type = All; _type = All;
for (int i = 1; i < json.size(); i++) for (int i = 1; i < json.size(); i++)
@ -105,32 +91,28 @@ Style::Layer::Filter::Filter(const QJsonArray &json)
if (json.size() < 3) if (json.size() < 3)
INVALID_FILTER(json); INVALID_FILTER(json);
_type = In; _type = In;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(), _kv = QPair<QString, QVariant>(json.at(1).toString(), QVariant());
QVariant());
for (int i = 2; i < json.size(); i++) 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") { } else if (type == "!in") {
if (json.size() < 3) if (json.size() < 3)
INVALID_FILTER(json); INVALID_FILTER(json);
_type = In; _type = In;
_not = true; _not = true;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(), _kv = QPair<QString, QVariant>(json.at(1).toString(), QVariant());
QVariant());
for (int i = 2; i < json.size(); i++) 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") { } else if (type == "has") {
if (json.size() < 2) if (json.size() < 2)
INVALID_FILTER(json); INVALID_FILTER(json);
_type = Has; _type = Has;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(), _kv = QPair<QString, QVariant>(json.at(1).toString(), QVariant());
QVariant());
} else if (type == "!has") { } else if (type == "!has") {
if (json.size() < 2) if (json.size() < 2)
INVALID_FILTER(json); INVALID_FILTER(json);
_type = Has; _type = Has;
_not = true; _not = true;
_kv = QPair<QByteArray, QVariant>(json.at(1).toString().toUtf8(), _kv = QPair<QString, QVariant>(json.at(1).toString(), QVariant());
QVariant());
} else } else
INVALID_FILTER(json); INVALID_FILTER(json);
} }
@ -156,51 +138,27 @@ bool Style::Layer::Filter::match(const PBF::Feature &feature) const
if (!(v = feature.value(_kv.first))) if (!(v = feature.value(_kv.first)))
return false; return false;
else else
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
return *v > _kv.second; return *v > _kv.second;
#else // QT6
return (QVariant::compare(*v, _kv.second)
== QPartialOrdering::Greater);
#endif // QT6
case GE: case GE:
{if (!(v = feature.value(_kv.first))) if (!(v = feature.value(_kv.first)))
return false; return false;
else { else
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
return *v >= _kv.second; return *v >= _kv.second;
#else // QT6
QPartialOrdering res = QVariant::compare(*v, _kv.second);
return (res == QPartialOrdering::Greater
|| res == QPartialOrdering::Equivalent);
#endif // QT6
}}
case LT: case LT:
if (!(v = feature.value(_kv.first))) if (!(v = feature.value(_kv.first)))
return false; return false;
else else
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
return *v < _kv.second; return *v < _kv.second;
#else // QT6
return (QVariant::compare(*v, _kv.second)
== QPartialOrdering::Less);
#endif // QT6
case LE: case LE:
{if (!(v = feature.value(_kv.first))) if (!(v = feature.value(_kv.first)))
return false; return false;
else { else
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
return *v <= _kv.second; return *v <= _kv.second;
#else // QT6
QPartialOrdering res = QVariant::compare(*v, _kv.second);
return (res == QPartialOrdering::Less
|| res == QPartialOrdering::Equivalent);
#endif // QT6
}}
case In: case In:
if (!(v = feature.value(_kv.first))) if (!(v = feature.value(_kv.first)))
return _not; return _not;
else else
return _set.contains((*v).toByteArray()) ^ _not; return _set.contains((*v).toString()) ^ _not;
case Has: case Has:
return (feature.value(_kv.first) ? true : false) ^ _not; return (feature.value(_kv.first) ? true : false) ^ _not;
case All: case All:
@ -214,7 +172,7 @@ bool Style::Layer::Filter::match(const PBF::Feature &feature) const
return true; return true;
return false; return false;
case GeometryType: case GeometryType:
return feature.type() == _kv.second.toInt(); return feature.type() == _kv.second.toUInt();
default: default:
return false; return false;
} }
@ -222,19 +180,19 @@ bool Style::Layer::Filter::match(const PBF::Feature &feature) const
QString Style::Layer::Template::value(int zoom, const PBF::Feature &feature) const QString Style::Layer::Template::value(int zoom, const PBF::Feature &feature) const
{ {
static QRegularExpression rx("\\{[^\\}]*\\}"); QRegExp rx = QRegExp("\\{[^\\}]*\\}");
QString text(_field.value(zoom)); QString text(_field.value(zoom));
QRegularExpressionMatchIterator it = rx.globalMatch(text);
QStringList keys; QStringList keys;
int pos = 0;
while (it.hasNext()) { while ((pos = rx.indexIn(text, pos)) != -1) {
QRegularExpressionMatch match = it.next(); QString match = rx.capturedTexts().first();
QString val = match.captured(0); keys.append(match.mid(1, match.size() - 2));
keys.append(val.mid(1, val.size() - 2)); pos += rx.matchedLength();
} }
for (int i = 0; i < keys.size(); i++) { for (int i = 0; i < keys.size(); i++) {
const QString &key = keys.at(i); const QString &key = keys.at(i);
const QVariant *val = feature.value(key.toUtf8()); const QVariant *val = feature.value(key);
text.replace(QString("{%1}").arg(key), val ? val->toString() : ""); text.replace(QString("{%1}").arg(key), val ? val->toString() : "");
} }
@ -272,12 +230,6 @@ Style::Layer::Paint::Paint(const QJsonObject &json)
// text // text
_textColor = FunctionC(json["text-color"]); _textColor = FunctionC(json["text-color"]);
_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 QPen Style::Layer::Paint::pen(Type type, int zoom) const
@ -313,7 +265,7 @@ QPen Style::Layer::Paint::pen(Type type, int zoom) const
return pen; return pen;
} }
QBrush Style::Layer::Paint::brush(Type type, int zoom, Sprites &sprites) QBrush Style::Layer::Paint::brush(Type type, int zoom, const Sprites &sprites)
const const
{ {
QColor color; QColor color;
@ -390,16 +342,9 @@ Style::Layer::Layout::Layout(const QJsonObject &json)
// icon // icon
_icon = Template(FunctionS(json["icon-image"])); _icon = Template(FunctionS(json["icon-image"]));
_iconSize = FunctionF(json["icon-size"]);
// symbol // symbol
_symbolPlacement = FunctionS(json["symbol-placement"]); _symbolPlacement = FunctionS(json["symbol-placement"]);
// visibility
if (json.contains("visibility") && json["visibility"].isString())
_visible = !(json["visibility"].toString() == "none");
else
_visible = true;
} }
QFont Style::Layer::Layout::font(int zoom) const QFont Style::Layer::Layout::font(int zoom) const
@ -489,7 +434,7 @@ Text::RotationAlignment Style::Layer::Layout::textRotationAlignment(int zoom)
} }
Style::Layer::Layer(const QJsonObject &json) Style::Layer::Layer(const QJsonObject &json)
: _type(Unknown), _minZoom(0), _maxZoom(24) : _type(Unknown), _minZoom(-1), _maxZoom(-1)
{ {
// type // type
QString type(json["type"].toString()); QString type(json["type"].toString());
@ -504,7 +449,7 @@ Style::Layer::Layer(const QJsonObject &json)
_type = Symbol; _type = Symbol;
// source-layer // source-layer
_sourceLayer = json["source-layer"].toString().toUtf8(); _sourceLayer = json["source-layer"].toString();
// zooms // zooms
if (json.contains("minzoom") && json["minzoom"].isDouble()) if (json.contains("minzoom") && json["minzoom"].isDouble())
@ -527,13 +472,17 @@ Style::Layer::Layer(const QJsonObject &json)
bool Style::Layer::match(int zoom, const PBF::Feature &feature) const bool Style::Layer::match(int zoom, const PBF::Feature &feature) const
{ {
if (zoom >= 0 && (zoom < _minZoom || zoom > _maxZoom)) if (zoom >= 0) {
return false; if (_minZoom > 0 && zoom < _minZoom)
return false;
if (_maxZoom > 0 && zoom > _maxZoom)
return false;
}
return _filter.match(feature); return _filter.match(feature);
} }
void Style::Layer::setPathPainter(Tile &tile, Sprites &sprites) const void Style::Layer::setPathPainter(Tile &tile, const Sprites &sprites) const
{ {
QPainter &p = tile.painter(); QPainter &p = tile.painter();
int zoom = tile.zoom(); int zoom = tile.zoom();
@ -562,22 +511,17 @@ void Style::Layer::setTextProperties(Tile &tile) const
t.setFont(_layout.font(zoom)); t.setFont(_layout.font(zoom));
t.setSymbolPlacement(_layout.symbolPlacement(zoom)); t.setSymbolPlacement(_layout.symbolPlacement(zoom));
t.setRotationAlignment(_layout.textRotationAlignment(zoom)); t.setRotationAlignment(_layout.textRotationAlignment(zoom));
t.setHalo(_paint.halo(zoom));
} }
void Style::Layer::addSymbol(Tile &tile, const QPainterPath &path, void Style::Layer::addSymbol(Tile &tile, const QPainterPath &path,
const PBF::Feature &feature, Sprites &sprites) const const PBF::Feature &feature, const Sprites &sprites) const
{ {
QString text(_layout.text(tile.zoom(), feature)); QString text = _layout.text(tile.zoom(), feature);
QString icon(_layout.icon(tile.zoom(), feature)); if (text.isEmpty())
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; return;
tile.text().addLabel(text, img, path); QString icon = _layout.icon(tile.zoom(), feature);
tile.text().addLabel(text, sprites.icon(icon), path);
} }
static bool loadSprites(const QDir &styleDir, const QString &json, static bool loadSprites(const QDir &styleDir, const QString &json,
@ -590,7 +534,7 @@ static bool loadSprites(const QDir &styleDir, const QString &json,
if (QFileInfo::exists(spritesImg)) if (QFileInfo::exists(spritesImg))
return sprites.load(spritesJSON, spritesImg); return sprites.load(spritesJSON, spritesImg);
else { else {
qWarning() << spritesImg << ": no such file"; qCritical() << spritesImg << ": no such file";
return false; return false;
} }
} }
@ -623,20 +567,27 @@ bool Style::load(const QString &fileName)
_layers.append(Layer(layers[i].toObject())); _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.json", "sprite.png", _sprites);
#ifdef ENABLE_HIDPI
loadSprites(styleDir, "sprite@2x.json", "sprite@2x.png", _sprites2x); loadSprites(styleDir, "sprite@2x.json", "sprite@2x.png", _sprites2x);
#endif // ENABLE_HIDPI
return true; return true;
} }
Sprites &Style::sprites(const QPointF &scale) const Sprites &Style::sprites(const QPointF &scale) const
{ {
#ifdef ENABLE_HIDPI
return (scale.x() > 1.0 || scale.y() > 1.0) return (scale.x() > 1.0 || scale.y() > 1.0)
&& !_sprites2x.isNull() ? _sprites2x : _sprites; && !_sprites2x.isNull() ? _sprites2x : _sprites;
#else // ENABLE_HIDPI
Q_UNUSED(scale);
return _sprites;
#endif // ENABLE_HIDPI
} }
void Style::setupLayer(Tile &tile, const Layer &layer) void Style::setupLayer(Tile &tile, const Layer &layer) const
{ {
if (layer.isSymbol()) if (layer.isSymbol())
layer.setTextProperties(tile); layer.setTextProperties(tile);
@ -644,10 +595,9 @@ void Style::setupLayer(Tile &tile, const Layer &layer)
layer.setPathPainter(tile, sprites(tile.scale())); layer.setPathPainter(tile, sprites(tile.scale()));
} }
void Style::drawBackground(Tile &tile) void Style::drawBackground(Tile &tile) const
{ {
QRectF rect(QPointF(0, 0), QSizeF(tile.size().width() / tile.scale().x(), QRectF rect(QPointF(0, 0), tile.size());
tile.size().height() / tile.scale().y()));
QPainterPath path; QPainterPath path;
path.addRect(rect); path.addRect(rect);
@ -659,10 +609,13 @@ void Style::drawBackground(Tile &tile)
_layers.first().setPathPainter(tile, _sprites); _layers.first().setPathPainter(tile, _sprites);
tile.painter().drawPath(path); tile.painter().drawPath(path);
} }
//tile.painter().setPen(Qt::red);
//tile.painter().drawRect(rect);
} }
void Style::drawFeature(const PBF::Feature &feature, const Layer &layer, void Style::drawFeature(const PBF::Feature &feature, const Layer &layer,
Tile &tile, const QSizeF &factor) Tile &tile, const QSizeF &factor) const
{ {
if (!layer.match(tile.zoom(), feature)) if (!layer.match(tile.zoom(), feature))
return; return;
@ -678,17 +631,14 @@ void Style::drawFeature(const PBF::Feature &feature, const Layer &layer,
} }
void Style::drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer, void Style::drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer,
Tile &tile) Tile &tile) const
{ {
if (pbfLayer.data()->version > 2) if (pbfLayer.data()->version() > 2)
return;
if (!styleLayer.isVisible())
return; return;
QSizeF factor(tile.size().width() / tile.scale().x() / QSizeF factor(tile.size().width() / tile.scale().x() /
(qreal)pbfLayer.data()->extent, tile.size().height() / tile.scale().y() (qreal)pbfLayer.data()->extent(), tile.size().height() / tile.scale().y()
/ (qreal)pbfLayer.data()->extent); / (qreal)pbfLayer.data()->extent());
tile.painter().save(); tile.painter().save();
setupLayer(tile, styleLayer); setupLayer(tile, styleLayer);
@ -697,12 +647,12 @@ void Style::drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer,
tile.painter().restore(); tile.painter().restore();
} }
void Style::render(const PBF &data, Tile &tile) void Style::render(const PBF &data, Tile &tile) const
{ {
drawBackground(tile); drawBackground(tile);
for (int i = 0; i < _layers.size(); i++) { 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()); _layers.at(i).sourceLayer());
if (it == data.layers().constEnd()) if (it == data.layers().constEnd())
continue; continue;
@ -711,11 +661,4 @@ void Style::render(const PBF &data, Tile &tile)
} }
tile.text().render(&tile.painter()); 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

@ -12,6 +12,7 @@
#include <QBrush> #include <QBrush>
#include <QFont> #include <QFont>
#include "pbf.h" #include "pbf.h"
#include "config.h"
#include "text.h" #include "text.h"
#include "function.h" #include "function.h"
#include "sprites.h" #include "sprites.h"
@ -29,25 +30,24 @@ public:
Style(QObject *parent = 0) : QObject(parent) {} Style(QObject *parent = 0) : QObject(parent) {}
bool load(const QString &fileName); bool load(const QString &fileName);
void render(const PBF &data, Tile &tile); void render(const PBF &data, Tile &tile) const;
private: private:
class Layer { class Layer {
public: public:
Layer() : _type(Unknown), _minZoom(0), _maxZoom(24) {} Layer() : _type(Unknown), _minZoom(-1), _maxZoom(-1) {}
Layer(const QJsonObject &json); 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 isPath() const {return (_type == Line || _type == Fill);}
bool isBackground() const {return (_type == Background);} bool isBackground() const {return (_type == Background);}
bool isSymbol() const {return (_type == Symbol);} bool isSymbol() const {return (_type == Symbol);}
bool isVisible() const {return (_layout.visible());}
bool match(int zoom, const PBF::Feature &feature) const; bool match(int zoom, const PBF::Feature &feature) const;
void setPathPainter(Tile &tile, Sprites &sprites) const; void setPathPainter(Tile &tile, const Sprites &sprites) const;
void setTextProperties(Tile &tile) const; void setTextProperties(Tile &tile) const;
void addSymbol(Tile &tile, const QPainterPath &path, void addSymbol(Tile &tile, const QPainterPath &path,
const PBF::Feature &feature, Sprites &sprites) const; const PBF::Feature &feature, const Sprites &sprites) const;
private: private:
enum Type { enum Type {
@ -74,8 +74,8 @@ private:
Type _type; Type _type;
bool _not; bool _not;
QSet<QByteArray> _set; QSet<QString> _set;
QPair<QByteArray, QVariant> _kv; QPair<QString, QVariant> _kv;
QVector<Filter> _filters; QVector<Filter> _filters;
}; };
@ -92,7 +92,7 @@ private:
class Layout { class Layout {
public: public:
Layout() : _textSize(16), _textMaxWidth(10), _textMaxAngle(45), Layout() : _textSize(16), _textMaxWidth(10), _textMaxAngle(45),
_font("Open Sans"), _visible(true) {} _font("Open Sans") {}
Layout(const QJsonObject &json); Layout(const QJsonObject &json);
qreal maxTextWidth(int zoom) const qreal maxTextWidth(int zoom) const
@ -103,22 +103,18 @@ private:
{return _text.value(zoom, feature).trimmed();} {return _text.value(zoom, feature).trimmed();}
QString icon(int zoom, const PBF::Feature &feature) const QString icon(int zoom, const PBF::Feature &feature) const
{return _icon.value(zoom, feature);} {return _icon.value(zoom, feature);}
qreal iconSize(int zoom) const
{return _iconSize.value(zoom);}
QFont font(int zoom) const; QFont font(int zoom) const;
Qt::PenCapStyle lineCap(int zoom) const; Qt::PenCapStyle lineCap(int zoom) const;
Qt::PenJoinStyle lineJoin(int zoom) const; Qt::PenJoinStyle lineJoin(int zoom) const;
Text::Anchor textAnchor(int zoom) const; Text::Anchor textAnchor(int zoom) const;
Text::SymbolPlacement symbolPlacement(int zoom) const; Text::SymbolPlacement symbolPlacement(int zoom) const;
Text::RotationAlignment textRotationAlignment(int zoom) const; Text::RotationAlignment textRotationAlignment(int zoom) const;
bool visible() const {return _visible;}
private: private:
QFont::Capitalization textTransform(int zoom) const; QFont::Capitalization textTransform(int zoom) const;
Template _text; Template _text;
Template _icon; Template _icon;
FunctionF _iconSize;
FunctionF _textSize; FunctionF _textSize;
FunctionF _textMaxWidth; FunctionF _textMaxWidth;
FunctionF _textMaxAngle; FunctionF _textMaxAngle;
@ -129,7 +125,6 @@ private:
FunctionS _symbolPlacement; FunctionS _symbolPlacement;
FunctionS _textRotationAlignment; FunctionS _textRotationAlignment;
QFont _font; QFont _font;
bool _visible;
}; };
class Paint { class Paint {
@ -138,53 +133,47 @@ private:
Paint(const QJsonObject &json); Paint(const QJsonObject &json);
QPen pen(Layer::Type type, int zoom) const; QPen pen(Layer::Type type, int zoom) const;
QBrush brush(Layer::Type type, int zoom, Sprites &sprites) QBrush brush(Layer::Type type, int zoom, const Sprites &sprites)
const; const;
qreal opacity(Layer::Type type, int zoom) const; qreal opacity(Layer::Type type, int zoom) const;
bool antialias(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: private:
FunctionC _textColor; FunctionC _textColor;
FunctionC _textHaloColor;
FunctionC _lineColor; FunctionC _lineColor;
FunctionC _fillColor; FunctionC _fillColor;
FunctionC _fillOutlineColor; FunctionC _fillOutlineColor;
FunctionC _backgroundColor; FunctionC _backgroundColor;
FunctionC _iconColor;
FunctionF _fillOpacity; FunctionF _fillOpacity;
FunctionF _lineOpacity; FunctionF _lineOpacity;
FunctionF _lineWidth; FunctionF _lineWidth;
FunctionF _textHaloWidth;
FunctionF _textHaloBlur;
FunctionB _fillAntialias; FunctionB _fillAntialias;
QVector<qreal> _lineDasharray; QVector<qreal> _lineDasharray;
FunctionS _fillPattern; FunctionS _fillPattern;
}; };
Type _type; Type _type;
QByteArray _sourceLayer; QString _sourceLayer;
int _minZoom, _maxZoom; int _minZoom, _maxZoom;
Filter _filter; Filter _filter;
Layout _layout; Layout _layout;
Paint _paint; Paint _paint;
}; };
Sprites &sprites(const QPointF &scale); const Sprites &sprites(const QPointF &scale) const;
void drawBackground(Tile &tile); void drawBackground(Tile &tile) const;
void setupLayer(Tile &tile, const Layer &layer); void setupLayer(Tile &tile, const Layer &layer) const;
void drawFeature(const PBF::Feature &feature, const Layer &layer, void drawFeature(const PBF::Feature &feature, const Layer &layer,
Tile &tile, const QSizeF &factor); Tile &tile, const QSizeF &factor) const;
void drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer, void drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer,
Tile &tile); Tile &tile) const;
QVector<Layer> _layers; QVector<Layer> _layers;
Sprites _sprites, _sprites2x; Sprites _sprites;
#ifdef ENABLE_HIDPI
Sprites _sprites2x;
#endif // QT >= 5.6
}; };
#endif // STYLE_H #endif // STYLE_H

View File

@ -1,7 +1,6 @@
#include <QFontMetrics> #include <QFontMetrics>
#include <QPainter> #include <QPainter>
#include <QSet> #include <QSet>
#include <QMap>
#include "text.h" #include "text.h"
#include "textpointitem.h" #include "textpointitem.h"
#include "textpathitem.h" #include "textpathitem.h"
@ -9,15 +8,21 @@
Text::~Text() Text::~Text()
{ {
qDeleteAll(_items); for (int i = 0; i < _items.size(); i++)
delete _items[i];
} }
void Text::render(QPainter *painter) const void Text::render(QPainter *painter) const
{ {
QSet<QString> set;
for (int i = 0; i < _items.size(); i++) { for (int i = 0; i < _items.size(); i++) {
const TextItem *ti = _items.at(i); const TextItem *ti = _items.at(i);
if (ti->isVisible() && _sceneRect.intersects(ti->boundingRect())) if (ti->isVisible() && _sceneRect.intersects(ti->boundingRect())
&& !set.contains(ti->text())) {
ti->paint(painter); ti->paint(painter);
set.insert(ti->text());
}
} }
} }
@ -26,38 +31,22 @@ void Text::addLabel(const QString &text, const QImage &icon,
{ {
TextItem *ti; TextItem *ti;
if (_alignment == Viewport) { switch (_placement) {
QMap<qreal, int> map; case Line:
for (int j = 0; j < path.elementCount(); j++) { if (_alignment == Viewport)
QLineF l(path.elementAt(j), _sceneRect.center());
map.insert(l.length(), j);
}
QMap<qreal, int>::const_iterator jt = map.constBegin();
ti = new TextPointItem(text, path.elementAt(jt.value()), _font,
_maxWidth, _anchor, icon);
while (true) {
if (_sceneRect.contains(ti->boundingRect()))
break;
if (++jt == map.constEnd())
break;
static_cast<TextPointItem*>(ti)->setPos(path.elementAt(
jt.value()));
}
} else {
switch (_placement) {
case Line:
if (text.isEmpty())
return;
ti = new TextPathItem(text, path, _font, _maxAngle, _sceneRect);
break;
case LineCenter:
ti = new TextPointItem(text, path.pointAtPercent(0.5), _font,
_maxWidth, _anchor, icon);
break;
default:
ti = new TextPointItem(text, path.elementAt(0), _font, ti = new TextPointItem(text, path.elementAt(0), _font,
_maxWidth, _anchor, icon); _maxWidth, _anchor, icon);
} else
ti = new TextPathItem(text, path, _font, _maxAngle, _sceneRect);
break;
case LineCenter:
ti = new TextPointItem(text, path.pointAtPercent(0.5), _font,
_maxWidth, _anchor, icon);
break;
default:
ti = new TextPointItem(text, path.elementAt(0), _font, _maxWidth,
_anchor, icon);
break;
} }
// Note: empty path == point geometry (single move) // Note: empty path == point geometry (single move)
@ -67,10 +56,9 @@ void Text::addLabel(const QString &text, const QImage &icon,
} }
ti->setPen(_pen); ti->setPen(_pen);
ti->setHalo(_halo);
addItem(ti); addItem(ti);
QList<TextItem*> ci(collidingItems(ti)); QList<TextItem*> ci = collidingItems(ti);
for (int i = 0; i < ci.size(); i++) for (int i = 0; i < ci.size(); i++)
ci[i]->setVisible(false); ci[i]->setVisible(false);
} }

View File

@ -34,22 +34,6 @@ public:
Auto Auto
}; };
class Halo {
public:
Halo() : _width(0), _blur(0) {}
Halo(const QColor &color, qreal width, qreal blur)
: _color(color), _width(width), _blur(blur) {}
const QColor &color() const {return _color;}
qreal width() const {return _width;}
qreal blur() const {return _blur;}
private:
QColor _color;
qreal _width;
qreal _blur;
};
Text(const QSize &size) : _sceneRect(QRectF(QPointF(0, 0), size)) {} Text(const QSize &size) : _sceneRect(QRectF(QPointF(0, 0), size)) {}
~Text(); ~Text();
@ -61,7 +45,6 @@ public:
void setSymbolPlacement(SymbolPlacement placement); void setSymbolPlacement(SymbolPlacement placement);
void setRotationAlignment(RotationAlignment alignment) void setRotationAlignment(RotationAlignment alignment)
{_alignment = alignment;} {_alignment = alignment;}
void setHalo(const Halo &halo) {_halo = halo;}
void addLabel(const QString &text, const QImage &icon, void addLabel(const QString &text, const QImage &icon,
const QPainterPath &path); const QPainterPath &path);
@ -82,7 +65,6 @@ private:
RotationAlignment _alignment; RotationAlignment _alignment;
QFont _font; QFont _font;
QPen _pen; QPen _pen;
Halo _halo;
}; };
#endif // TEXT_H #endif // TEXT_H

View File

@ -11,7 +11,7 @@ bool TextItem::collidesWithItem(const TextItem *other) const
return other->shape().intersects(shape()); return other->shape().intersects(shape());
} }
qreal TextItem::avgCharWidth() const int TextItem::avgCharWidth() const
{ {
qreal ratio; qreal ratio;
ushort cp = _text.at(0).unicode(); ushort cp = _text.at(0).unicode();
@ -21,18 +21,14 @@ qreal TextItem::avgCharWidth() const
ratio = 1.0; ratio = 1.0;
// Greek & Cyrilic // Greek & Cyrilic
else if (cp >= 0x03FF && cp <= 0x04FF) { else if (cp >= 0x03FF && cp <= 0x04FF) {
ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.80 : 0.70; ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.75 : 0.67;
if (_font.bold()) if (_font.bold())
ratio *= 1.1; ratio *= 1.1;
if (_font.italic())
ratio *= 0.9;
// The rest (Latin scripts, Arabic, ...) // The rest (Latin scripts, Arabic, ...)
} else { } else {
ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.75 : 0.60; ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.7 : 0.58;
if (_font.bold()) if (_font.bold())
ratio *= 1.1; ratio *= 1.1;
if (_font.italic())
ratio *= 0.9;
} }
return ratio * _font.pixelSize(); return ratio * _font.pixelSize();

View File

@ -6,7 +6,6 @@
#include <QPen> #include <QPen>
#include <QFont> #include <QFont>
#include <QRectF> #include <QRectF>
#include "text.h"
class QPainter; class QPainter;
@ -20,9 +19,7 @@ public:
const QString &text() const {return _text;} const QString &text() const {return _text;}
const QFont &font() const {return _font;} const QFont &font() const {return _font;}
const QPen &pen() const {return _pen;} const QPen &pen() const {return _pen;}
const Text::Halo &halo() const {return _halo;}
void setPen(const QPen &pen) {_pen = pen;} void setPen(const QPen &pen) {_pen = pen;}
void setHalo(const Text::Halo &halo) {_halo = halo;}
virtual QPainterPath shape() const = 0; virtual QPainterPath shape() const = 0;
virtual QRectF boundingRect() const = 0; virtual QRectF boundingRect() const = 0;
@ -34,13 +31,12 @@ public:
bool collidesWithItem(const TextItem *other) const; bool collidesWithItem(const TextItem *other) const;
protected: protected:
qreal avgCharWidth() const; int avgCharWidth() const;
private: private:
QString _text; QString _text;
QFont _font; QFont _font;
QPen _pen; QPen _pen;
Text::Halo _halo;
bool _visible; bool _visible;
}; };

View File

@ -2,95 +2,42 @@
#include <QPainter> #include <QPainter>
#include "textpathitem.h" #include "textpathitem.h"
static void swap(const QLineF &line, QPointF *p1, QPointF *p2)
static QPointF intersection(const QLineF &line, const QRectF &rect)
{ {
QPointF p;
if (line.intersect(QLineF(rect.topLeft(), rect.topRight()), &p)
== QLineF::BoundedIntersection)
return p;
if (line.intersect(QLineF(rect.topLeft(), rect.bottomLeft()), &p)
== QLineF::BoundedIntersection)
return p;
if (line.intersect(QLineF(rect.bottomRight(), rect.bottomLeft()), &p)
== QLineF::BoundedIntersection)
return p;
if (line.intersect(QLineF(rect.bottomRight(), rect.topRight()), &p)
== QLineF::BoundedIntersection)
return p;
QPointF lp1(line.p1()); return rect.center();
QPointF lp2(line.p2());
if ((lp1.rx() < lp2.rx() && p1->rx() > p2->rx())
|| (lp1.ry() < lp2.ry() && p1->ry() > p2->ry())
|| (lp1.rx() > lp2.rx() && p1->rx() < p2->rx())
|| (lp1.ry() > lp2.ry() && p1->ry() < p2->ry())) {
QPointF tmp(*p2);
*p2 = *p1;
*p1 = tmp;
}
} }
static bool intersection(const QLineF &line, const QRectF &rect, QPointF *p1, static QPainterPath subpath(const QList<QLineF> &lines, int start, int end,
QPointF *p2)
{
QPointF *p = p1;
if (line.intersects(QLineF(rect.topLeft(), rect.topRight()), p)
== QLineF::BoundedIntersection)
p = p2;
if (line.intersects(QLineF(rect.topLeft(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection) {
if (p == p2) {
swap(line, p1, p2);
return true;
}
p = p2;
}
if (line.intersects(QLineF(rect.bottomRight(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection) {
if (p == p2) {
swap(line, p1, p2);
return true;
}
p = p2;
}
if (line.intersects(QLineF(rect.bottomRight(), rect.topRight()), p)
== QLineF::BoundedIntersection) {
if (p == p2) {
swap(line, p1, p2);
return true;
}
}
Q_ASSERT(p != p2);
return false;
}
static bool intersection(const QLineF &line, const QRectF &rect, QPointF *p)
{
if (line.intersects(QLineF(rect.topLeft(), rect.topRight()), p)
== QLineF::BoundedIntersection)
return true;
if (line.intersects(QLineF(rect.topLeft(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection)
return true;
if (line.intersects(QLineF(rect.bottomRight(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection)
return true;
if (line.intersects(QLineF(rect.bottomRight(), rect.topRight()), p)
== QLineF::BoundedIntersection)
return true;
return false;
}
static QPainterPath subpath(const QPolygonF &path, int start, int end,
qreal cut) qreal cut)
{ {
qreal ss = 0, es = 0; qreal ss = 0, es = 0;
int si = start, ei = end; int si = start, ei = end;
for (int i = start; i < end; i++) { for (int i = start; i <= end; i++) {
QLineF l(path.at(i), path.at(i+1)); qreal len = lines.at(i).length();
qreal len = l.length();
if (ss + len < cut / 2) { if (ss + len < cut / 2) {
ss += len; ss += len;
si++; si++;
} else } else
break; break;
} }
for (int i = end; i > start; i--) { for (int i = end; i >= start; i--) {
QLineF l(path.at(i), path.at(i-1)); qreal len = lines.at(i).length();
qreal len = l.length();
if (es + len < cut / 2) { if (es + len < cut / 2) {
es += len; es += len;
ei--; ei--;
@ -98,117 +45,83 @@ static QPainterPath subpath(const QPolygonF &path, int start, int end,
break; break;
} }
QLineF sl(path.at(si+1), path.at(si)); QLineF sl(lines.at(si).p2(), lines.at(si).p1());
sl.setLength(sl.length() - (cut / 2 - ss)); sl.setLength(sl.length() - (cut / 2 - ss));
QLineF el(path.at(ei-1), path.at(ei)); QLineF el(lines.at(ei));
el.setLength(el.length() - (cut / 2 - es)); el.setLength(el.length() - (cut / 2 - es));
QPainterPath p(sl.p2()); QPainterPath p(sl.p2());
for (int i = si + 1; i < ei; i++) for (int i = si; i <= ei; i++)
p.lineTo(path.at(i)); p.lineTo(lines.at(i).p2());
p.lineTo(el.p2()); p.setElementPositionAt(p.elementCount() - 1, el.p2().x(), el.p2().y());
return p; return p;
} }
static QList<QPolygonF> polyLines(const QPainterPath &path, const QRectF &rect) static QList<QLineF> lineString(const QPainterPath &path,
const QRectF &boundingRect)
{ {
QList<QPolygonF> lines; QList<QLineF> lines;
QPolygonF line; int start = 0, end = path.elementCount() - 1;
bool lastIn = rect.contains(path.elementAt(0));
for (int i = 1; i < path.elementCount(); i++) { for (int i = 0; i < path.elementCount(); i++) {
if (rect.contains(path.elementAt(i))) { if (boundingRect.contains(path.elementAt(i))) {
if (lastIn) { start = i;
if (line.isEmpty()) break;
line.append(path.elementAt(i-1)); }
line.append(path.elementAt(i)); }
} else { for (int i = path.elementCount() - 1; i >= 0; i--) {
QPointF p; if (boundingRect.contains(path.elementAt(i))) {
QLineF l(path.elementAt(i-1), path.elementAt(i)); end = i;
break;
if (intersection(l, rect, &p))
line.append(p);
line.append(path.elementAt(i));
}
lastIn = true;
} else {
QLineF l(path.elementAt(i-1), path.elementAt(i));
if (lastIn) {
QPointF p;
if (line.isEmpty())
line.append(path.elementAt(i-1));
if (intersection(l, rect, &p))
line.append(p);
lines.append(line);
line.clear();
} else {
QPointF p1, p2;
if (intersection(l, rect, &p1, &p2)) {
line.append(p1);
line.append(p2);
lines.append(line);
line.clear();
}
}
lastIn = false;
} }
} }
if (!line.isEmpty()) if (start > 0) {
lines.append(line); QLineF l(path.elementAt(start-1), path.elementAt(start));
QPointF p(intersection(l, boundingRect));
if (p != boundingRect.center())
lines.append(QLineF(p, path.elementAt(start)));
}
for (int i = start + 1; i <= end; i++)
lines.append(QLineF(path.elementAt(i-1), path.elementAt(i)));
if (end < path.elementCount() - 1) {
QLineF l(path.elementAt(end), path.elementAt(end+1));
QPointF p(intersection(l, boundingRect));
if (p != boundingRect.center())
lines.append(QLineF(path.elementAt(end), p));
}
return lines; 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, static QPainterPath textPath(const QPainterPath &path, qreal textWidth,
qreal maxAngle, qreal charWidth, const QRectF &tileRect) qreal maxAngle, qreal charWidth, const QRectF &tileRect)
{ {
if (path.isEmpty()) QList<QLineF> lines(lineString(path, tileRect));
return QPainterPath(); qreal length = 0;
qreal angle = lines.first().angle();
QList<QPolygonF> lines(polyLines(path, tileRect)); int last = 0;
for (int i = 0; i < lines.size(); i++) { for (int i = 0; i < lines.size(); i++) {
const QPolygonF &pl = lines.at(i); qreal sl = lines.at(i).length();
qreal angle = 0, length = 0; qreal a = lines.at(i).angle();
int last = 0;
for (int j = 1; j < pl.size(); j ++) { if (!tileRect.contains(lines.at(i).p2()) || sl < charWidth
QLineF l(pl.at(j-1), pl.at(j)); || qAbs(angle - a) > maxAngle) {
qreal sl = l.length(); if (length > textWidth)
qreal a = l.angle(); return subpath(lines, last, i - 1, length - textWidth);
last = i;
length = 0;
} else
length += sl;
if (sl < charWidth) { angle = a;
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;
angle = a;
}
if (length > textWidth)
return subpath(pl, last, pl.size() - 1, length - textWidth);
} }
return QPainterPath(); return (length > textWidth)
? subpath(lines, last, lines.size() - 1, length - textWidth)
: QPainterPath();
} }
static bool reverse(const QPainterPath &path) static bool reverse(const QPainterPath &path)
@ -223,7 +136,7 @@ TextPathItem::TextPathItem(const QString &text, const QPainterPath &path,
const QFont &font, int maxAngle, const QRectF &tileRect) const QFont &font, int maxAngle, const QRectF &tileRect)
: TextItem(text, font) : TextItem(text, font)
{ {
qreal cw = avgCharWidth(); int cw = avgCharWidth();
int textWidth = text.size() * cw; int textWidth = text.size() * cw;
if (textWidth > path.length()) if (textWidth > path.length())
return; return;
@ -243,42 +156,20 @@ TextPathItem::TextPathItem(const QString &text, const QPainterPath &path,
void TextPathItem::paint(QPainter *painter) const void TextPathItem::paint(QPainter *painter) const
{ {
//painter->setPen(Qt::red);
//painter->drawPath(_shape);
QFontMetrics fm(font()); QFontMetrics fm(font());
int textWidth = fm.boundingRect(text()).width(); int textWidth = fm.width(text());
qreal factor = (textWidth) / qMax(_path.length(), (qreal)textWidth); qreal factor = (textWidth) / qMax(_path.length(), (qreal)textWidth);
qreal percent = (1.0 - factor) / 2.0; qreal percent = (1.0 - factor) / 2.0;
painter->setFont(font()); painter->setFont(font());
painter->setPen(pen());
QTransform t = painter->transform(); QTransform t = painter->transform();
if (halo().color().isValid() && halo().width() > 0) {
painter->setPen(halo().color());
for (int i = 0; i < text().size(); i++) {
QPointF point = _path.pointAtPercent(percent);
qreal angle = _path.angleAtPercent(percent);
painter->translate(point);
painter->rotate(-angle);
painter->drawText(QPoint(-1, fm.descent() - 1), text().at(i));
painter->drawText(QPoint(1, fm.descent() + 1), text().at(i));
painter->drawText(QPoint(-1, fm.descent() + 1), text().at(i));
painter->drawText(QPoint(1, fm.descent() -1), text().at(i));
painter->drawText(QPoint(0, fm.descent() - 1), text().at(i));
painter->drawText(QPoint(0, fm.descent() + 1), text().at(i));
painter->drawText(QPoint(-1, fm.descent()), text().at(i));
painter->drawText(QPoint(1, fm.descent()), text().at(i));
painter->setTransform(t);
int width = fm.horizontalAdvance(text().at(i));
percent += ((qreal)width / (qreal)textWidth) * factor;
}
percent = (1.0 - factor) / 2.0;
}
painter->setPen(pen());
for (int i = 0; i < text().size(); i++) { for (int i = 0; i < text().size(); i++) {
QPointF point = _path.pointAtPercent(percent); QPointF point = _path.pointAtPercent(percent);
qreal angle = _path.angleAtPercent(percent); qreal angle = _path.angleAtPercent(percent);
@ -288,12 +179,7 @@ void TextPathItem::paint(QPainter *painter) const
painter->drawText(QPoint(0, fm.descent()), text().at(i)); painter->drawText(QPoint(0, fm.descent()), text().at(i));
painter->setTransform(t); painter->setTransform(t);
int width = fm.horizontalAdvance(text().at(i)); int width = fm.charWidth(text(), i);
percent += ((qreal)width / (qreal)textWidth) * factor; percent += ((qreal)width / (qreal)textWidth) * factor;
} }
//painter->setBrush(Qt::NoBrush);
//painter->setPen(Qt::red);
//painter->setRenderHint(QPainter::Antialiasing, false);
//painter->drawPath(_shape);
} }

View File

@ -1,19 +1,34 @@
#include <QPainter> #include <QPainter>
#include <QtMath> #include <QtMath>
#include <QStaticText> #include "config.h"
#include "textpointitem.h" #include "textpointitem.h"
#define FLAGS (Qt::AlignCenter | Qt::TextWordWrap | Qt::TextDontClip) #define FLAGS (Qt::AlignCenter | Qt::TextWordWrap | Qt::TextDontClip)
QRectF TextPointItem::exactBoundingRect() const
{
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;
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);
return br;
}
QRectF TextPointItem::fuzzyBoundingRect() const QRectF TextPointItem::fuzzyBoundingRect() const
{ {
int fs = font().pixelSize(); int limit = font().pixelSize() * _maxWidth;
if (text().size() <= 3)
return QRectF(0, 0, text().size() * fs, fs * 1.6);
int limit = fs * _maxWidth;
qreal cw = avgCharWidth(); qreal cw = avgCharWidth();
qreal lh = font().pixelSize() * 1.25;
int width = 0, lines = 0; int width = 0, lines = 0;
QStringList l(text().split('\n')); QStringList l(text().split('\n'));
@ -29,7 +44,7 @@ QRectF TextPointItem::fuzzyBoundingRect() const
if (wl + pl < limit) { if (wl + pl < limit) {
pl += wl + cw; pl += wl + cw;
} else { } else {
if (wl >= limit) { if (wl > limit) {
if (pl > 0) if (pl > 0)
lines++; lines++;
} else } else
@ -46,26 +61,32 @@ QRectF TextPointItem::fuzzyBoundingRect() const
} }
} }
return QRectF(0, 0, width, lines * fs * 1.6); return QRectF(0, 0, width, lines * lh);
} }
QRectF TextPointItem::moveTextRect(const QRectF &rect) const
QRectF TextPointItem::computeTextRect(bool exact) const
{ {
#ifdef ENABLE_HIDPI
QRectF iconRect = _icon.isNull() ? QRectF() QRectF iconRect = _icon.isNull() ? QRectF()
: QRectF(QPointF(0, 0), QSizeF(_icon.size()) / _icon.devicePixelRatioF()); : QRectF(QPointF(0, 0), QSizeF(_icon.size()) / _icon.devicePixelRatioF());
QRectF textRect(rect); #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) { switch (_anchor) {
case Text::Center: case Text::Center:
textRect.moveCenter(_pos); textRect.moveCenter(_pos);
break; break;
case Text::Left: case Text::Left:
textRect.moveTopLeft(_pos - QPointF(-iconRect.width() / 2 textRect.moveTopLeft(_pos - QPointF(-iconRect.width() / 2,
- font().pixelSize()/4.0, textRect.height() / 2)); textRect.height() / 2));
break; break;
case Text::Right: case Text::Right:
textRect.moveTopRight(_pos - QPointF(iconRect.width() / 2 textRect.moveTopRight(_pos - QPointF(iconRect.width() / 2,
+ font().pixelSize()/4.0, textRect.height() / 2)); textRect.height() / 2));
break; break;
case Text::Bottom: case Text::Bottom:
textRect.moveTopLeft(_pos - QPointF(textRect.width() / 2, textRect.moveTopLeft(_pos - QPointF(textRect.width() / 2,
@ -85,12 +106,15 @@ TextPointItem::TextPointItem(const QString &text, const QPointF &pos,
: TextItem(text, font), _pos(pos), _icon(icon), _maxWidth(maxWidth), : TextItem(text, font), _pos(pos), _icon(icon), _maxWidth(maxWidth),
_anchor(anchor) _anchor(anchor)
{ {
_textRect = text.isEmpty() ? QRectF() : fuzzyBoundingRect(); _boundingRect = computeTextRect(false);
_boundingRect = moveTextRect(_textRect);
if (!_icon.isNull()) { if (!_icon.isNull()) {
#ifdef ENABLE_HIDPI
QRectF iconRect(QPointF(0, 0), QSizeF(_icon.size()) QRectF iconRect(QPointF(0, 0), QSizeF(_icon.size())
/ _icon.devicePixelRatioF()); / _icon.devicePixelRatioF());
#else // ENABLE_HIDPI
QRectF iconRect(QPointF(0, 0), QSizeF(_icon.size()));
#endif // ENABLE_HIDPI
iconRect.moveCenter(pos); iconRect.moveCenter(pos);
_boundingRect |= iconRect; _boundingRect |= iconRect;
} }
@ -98,54 +122,28 @@ TextPointItem::TextPointItem(const QString &text, const QPointF &pos,
_shape.addRect(_boundingRect); _shape.addRect(_boundingRect);
} }
void TextPointItem::setPos(const QPointF &pos)
{
QPointF d(_boundingRect.left() - _pos.x(), _boundingRect.top() - _pos.y());
_boundingRect.moveTopLeft(pos + d);
_shape = QPainterPath();
_shape.addRect(_boundingRect);
_pos = pos;
}
void TextPointItem::paint(QPainter *painter) const void TextPointItem::paint(QPainter *painter) const
{ {
//painter->setPen(Qt::red);
//painter->drawRect(_boundingRect);
QRectF textRect; QRectF textRect;
painter->setFont(font());
painter->setPen(pen());
if (!_icon.isNull()) { if (!_icon.isNull()) {
textRect = moveTextRect(painter->boundingRect(_textRect, FLAGS, text())); textRect = (_anchor != Text::Center)
? computeTextRect(true) : _boundingRect;
#ifdef ENABLE_HIDPI
painter->drawImage(_pos - QPointF(_icon.width() painter->drawImage(_pos - QPointF(_icon.width()
/ _icon.devicePixelRatioF() / 2, _icon.height() / _icon.devicePixelRatioF() / 2, _icon.height()
/ _icon.devicePixelRatioF() / 2), _icon); / _icon.devicePixelRatioF() / 2), _icon);
#else // ENABLE_HIDPI
painter->drawImage(_pos - QPointF(_icon.width() / 2,
_icon.height() / 2), _icon);
#endif // ENABLE_HIDPI
} else } else
textRect = _boundingRect; textRect = _boundingRect;
if (hasHalo()) { painter->setFont(font());
QStaticText st(text()); painter->setPen(pen());
st.setTextFormat(Qt::PlainText); painter->drawText(textRect, FLAGS, text());
st.setTextWidth(textRect.width());
st.setTextOption(QTextOption(Qt::AlignHCenter));
st.setPerformanceHint(QStaticText::AggressiveCaching);
painter->setPen(halo().color());
painter->drawStaticText(textRect.topLeft() + QPointF(-1, -1), st);
painter->drawStaticText(textRect.topLeft() + QPointF(+1, +1), st);
painter->drawStaticText(textRect.topLeft() + QPointF(-1, +1), st);
painter->drawStaticText(textRect.topLeft() + QPointF(+1, -1), st);
painter->drawStaticText(textRect.topLeft() + QPointF(0, -1), st);
painter->drawStaticText(textRect.topLeft() + QPointF(0, +1), st);
painter->drawStaticText(textRect.topLeft() + QPointF(-1, 0), st);
painter->drawStaticText(textRect.topLeft() + QPointF(+1, 0), st);
painter->setPen(pen());
painter->drawStaticText(textRect.topLeft(), st);
} else
painter->drawText(textRect, FLAGS, text());
//painter->setBrush(Qt::NoBrush);
//painter->setPen(Qt::red);
//painter->drawRect(_boundingRect);
//painter->setPen(Qt::blue);
//painter->drawRect(textRect);
} }

View File

@ -15,17 +15,14 @@ public:
QPainterPath shape() const {return _shape;} QPainterPath shape() const {return _shape;}
void paint(QPainter *painter) const; void paint(QPainter *painter) const;
void setPos(const QPointF &pos);
private: private:
QRectF exactBoundingRect() const;
QRectF fuzzyBoundingRect() const; QRectF fuzzyBoundingRect() const;
QRectF moveTextRect(const QRectF &rect) const; QRectF computeTextRect(bool exact) const;
bool hasHalo() const
{return halo().color().isValid() && halo().width() > 0;}
QPointF _pos; QPointF _pos;
QPainterPath _shape; QPainterPath _shape;
QRectF _textRect, _boundingRect; QRectF _boundingRect;
QImage _icon; QImage _icon;
int _maxWidth; int _maxWidth;
Text::Anchor _anchor; Text::Anchor _anchor;

View File

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

View File

@ -2265,6 +2265,7 @@
"source": "openmaptiles", "source": "openmaptiles",
"source-layer": "building", "source-layer": "building",
"minzoom": 13, "minzoom": 13,
"maxzoom": 14,
"layout": { "layout": {
"visibility": "visible" "visibility": "visible"
}, },