77 Commits
2.3 ... 3.1

Author SHA1 Message Date
1d4640c85c Code cleanup 2024-06-15 06:01:04 +02:00
aeed3e3848 Fixed homebrew paths 2024-06-14 19:59:43 +02:00
7488fc8f98 protobuf debug 2024-06-14 19:45:44 +02:00
549eb18ec6 Trying to fix broken homebrew... 2024-06-14 19:37:07 +02:00
5ed06f90b7 Use the propper image format 2024-06-14 18:56:14 +02:00
3a0694323f Fixed build on Qt < 5.13 2024-06-14 18:45:40 +02:00
c8b7051eba Added support for SDF sprites + fixed symbol layout algorithms 2024-06-14 18:33:10 +02:00
5954562200 Use Qt 6.7 in Windows build 2024-04-22 08:15:45 +02:00
ee1b91329e Optimizations 2024-02-05 10:08:58 +01:00
97d5748d11 Fixed OpenMapTiles map source URL 2024-02-04 19:11:06 +01:00
559ade2bab Use the latest actions versions 2024-02-03 18:35:23 +01:00
1fe198f26e Use the latest actions versions 2024-02-03 18:18:10 +01:00
a7b83f5d78 Build on Ubuntu 22.04 2024-02-03 17:40:57 +01:00
071a68a0dd Copyright year update 2024-02-03 17:02:09 +01:00
aab4a5dc55 Version++ 2024-02-03 16:58:18 +01:00
ff3d11e8da Improved debug grid display 2024-02-01 09:48:36 +01:00
bca266cb86 Android build fix 2024-01-18 22:59:44 +01:00
bdffe48676 Remove unnecessary path element 2023-12-18 20:21:06 +01:00
cb86e30fab Fixed artifact path 2023-12-15 21:37:53 +01:00
407f543039 Use libpbf instead of pbf in Android target name 2023-12-15 21:35:49 +01:00
f24a8a47a9 Fix plugins path 2023-12-15 21:32:33 +01:00
ce7b3d9158 Use propper plugin target path 2023-12-15 21:25:38 +01:00
f064148a0f Use propper target name on Android 2023-12-15 21:21:10 +01:00
733375fb74 Fixed artifact file name 2023-12-15 17:11:06 +01:00
440f7f91a4 Added artifacts upload 2023-12-15 17:03:59 +01:00
dbcfd9b9d3 Added Google Protocol Buffers to Android build 2023-12-15 15:04:55 +01:00
672de3919b Fixed Android qmake step 2023-12-15 09:27:02 +01:00
0a696a6ab8 Added Android build workflow 2023-12-15 09:23:48 +01:00
d8a521623a Unify the code samples to use QImage 2023-12-11 11:40:18 +01:00
f5f006dc7b Fixed artifact name/path 2023-12-11 08:16:07 +01:00
c86eb7bac2 Increase major version due to the API change
The API should be backward compatible but the overzoom extension should rather
be promoted with a major version number change.
2023-12-10 18:14:54 +01:00
3ef6c55d20 Code samples cleanup 2023-12-10 17:46:39 +01:00
98933deb0e Added HiDPI and Overzoom sections 2023-12-10 15:53:19 +01:00
6e1bc09d62 Cosmetics 2023-12-10 15:19:35 +01:00
ea98da4a74 Added overzoom description 2023-12-10 15:16:23 +01:00
8261ee2e79 Keep the overzoom and scaled size separated 2023-12-10 15:12:13 +01:00
d5c315efbe Version++ 2023-12-10 14:44:36 +01:00
39ffdaf616 Added support for overzoom 2023-12-10 14:43:07 +01:00
38b6e2320b Example codes cleanup 2023-12-10 08:18:16 +01:00
b36fb5fa92 Fixed maximal lines angle check 2023-10-22 23:53:39 +02:00
a7b7d16f4f Version++ 2023-10-22 23:47:28 +02:00
8cf73b5bb5 Silenced compiler warning 2023-10-10 23:04:39 +02:00
038f4e7d64 Fixed line splitting algorithm 2023-10-10 08:16:20 +02:00
aaff1c0bdb Fixed/improved path label layout algorithm 2023-10-09 21:59:05 +02:00
37c6c36204 Improved Windows dll info 2023-10-08 11:02:57 +02:00
f901243baa Remove non-ASCII characters from project file
UTF-8 is not supported by qmake
2023-10-07 01:38:39 +02:00
19c6b9854f Fixed artifact path
With the introduction of the VERSION qmake variable, the dll name has changed
to pbf2.dll.
2023-10-07 00:25:47 +02:00
52d15c63bd Extended Windows executable info 2023-10-07 00:25:12 +02:00
71331b06f1 Version++ 2023-10-06 22:55:40 +02:00
d6a24a4498 Switch back to Homebrew-only based builds
Build all using Homebrew packages and do not provide artifacts as those are
unusable outside the particular build machine.
2023-10-06 22:27:44 +02:00
a26c86dbd8 Checkout action version++ 2023-10-06 22:26:58 +02:00
0db93fd5ee Use GPXSee compatible Qt versions 2023-09-26 20:14:58 +02:00
f8044efda6 Fixed triplet path 2023-09-26 20:14:18 +02:00
450a1458a3 Use the propper static/dynamic crt triplet 2023-09-26 19:43:01 +02:00
bff8c7229c Fixed artifacts path 2023-09-22 10:49:16 +02:00
ff569644b6 Back to non-static vcpkg 2023-09-22 09:57:43 +02:00
6a32f0a447 Rename zlib lib to fit the pro file 2023-09-22 09:19:37 +02:00
1051a1c5b2 Use the static vcpkg builds 2023-09-22 02:02:45 +02:00
11e360d9a4 Fixed protoc path + added cache 2023-09-22 01:18:25 +02:00
5577590f6b Added AppVeyor build config 2023-09-22 00:50:12 +02:00
9dba35082b Use protobuf@21 in OS X build 2023-09-21 01:00:14 +02:00
76f1a23f8d Switch to Qt6 in OS X build 2023-09-21 00:45:41 +02:00
f931b653c4 Yet anothe OS X build fix 2023-09-21 00:23:35 +02:00
b0885a4b05 Trying to fix the OS X build 2023-09-21 00:12:48 +02:00
7c34a2c4e1 Use protobuf@21 in OS X builds 2023-09-20 23:48:06 +02:00
0cb3edda05 Fixed compiler warning 2023-09-20 23:36:30 +02:00
871aad37da Fix MacOS CI build 2023-05-05 23:26:03 +02:00
12c878d8e2 Proprly initialize the tile with transparent background 2023-05-05 19:15:36 +02:00
44862114ab Added Ordnance Survey and Esri styles info 2022-06-08 01:02:04 +02:00
1065a27bfb Fixed cut&paste error 2022-01-04 08:36:18 +01:00
3c22cd7823 Added linux CI build 2022-01-04 08:34:24 +01:00
1afd242302 Get rid of TravisCI 2021-10-27 17:48:33 +02:00
ae8b2f00db Added GitHub actions OS X build 2021-10-27 17:42:47 +02:00
e22ea9aa12 Updated Maputnik editor URL 2021-07-16 22:23:58 +02:00
62a78fdaae protobufs are already installed 2021-05-28 08:00:28 +02:00
63f86a900e Trying to fix the OS X TravisCI build... 2021-05-28 07:29:40 +02:00
ea8819a381 Updated minimal Qt version
Qt 5.11 works as well
2020-12-29 10:50:49 +01:00
20 changed files with 473 additions and 203 deletions

32
.appveyor.yml Normal file
View File

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

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

@ -0,0 +1,54 @@
name: Android
on:
push:
branches:
- master
jobs:
build:
name: QtPBFImagePlugin
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install protobuf compiler
run: |
sudo apt-get update
sudo apt-get install protobuf-compiler
- name: set up JDK 11
uses: actions/setup-java@v4
with:
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: Install Android Google Protocol Buffers
run: git clone https://github.com/tumic0/android_protobuf.git
- name: Configure build
run: qmake pbfplugin.pro PROTOBUF=android_protobuf
- name: Build project
run: make -j2
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: libplugins_imageformats_libpbf_arm64-v8a.so
path: plugins/libplugins_imageformats_libpbf_arm64-v8a.so

22
.github/workflows/linux.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Linux
on:
push:
branches:
- master
jobs:
build:
name: QtPBFImagePlugin
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install qtbase5-dev qtbase5-dev-tools qt5-qmake libprotobuf-dev protobuf-compiler zlib1g-dev
- name: Configure build
run: qmake pbfplugin.pro
- name: Build project
run: make -j2

41
.github/workflows/osx.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: OS X
on:
push:
branches:
- master
jobs:
qt5:
name: QtPBFImagePlugin Qt5 build
runs-on: macos-latest
steps:
- name: Set environment variables
run: echo "PATH=/opt/homebrew/opt/qt@5/bin:/opt/homebrew/opt/protobuf@21/bin:$PATH" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: |
brew update
brew install qt@5 protobuf@21
- name: Configure build
run: qmake PROTOBUF=/opt/homebrew/opt/protobuf@21 pbfplugin.pro
- name: Build project
run: make -j3
qt6:
name: QtPBFImagePlugin Qt6 build
runs-on: macos-latest
steps:
- name: Set environment variables
run: echo "PATH=/opt/homebrew/opt/qt@6/bin:/opt/homebrew/opt/protobuf@21/bin:$PATH" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: |
brew update
brew install qt@6 protobuf@21
- name: Configure build
run: qmake PROTOBUF=/opt/homebrew/opt/protobuf@21 pbfplugin.pro
- name: Build project
run: make -j3

View File

@ -1,23 +0,0 @@
language: c++
dist: focal
os:
- linux
- osx
env:
- QT_SELECT=qt5
before_install:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi
install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install qt protobuf; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install qtbase5-dev qtbase5-dev-tools qt5-qmake libprotobuf-dev protobuf-compiler; fi
script:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then PATH=/usr/local/opt/qt/bin/:${PATH}; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then qmake PROTOBUF=/usr/local pbfplugin.pro; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then qmake pbfplugin.pro; fi
- make

View File

@ -7,7 +7,7 @@ displaying raster MBTiles maps or raster XYZ online maps to also display
PBF(MVT) vector tiles without (almost, see usage) any application modifications.
Standard Mapbox GL Styles are used for styling the maps. Most relevant style
features used by [Maputnik](http://editor.openmaptiles.org) are supported.
features used by [Maputnik](https://maputnik.github.io/editor) are supported.
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
@ -18,24 +18,39 @@ to fit the styles and available data (OpenMapTiles, Mapbox tiles).
Due to a major design flaw in the Mapbox vector tiles specification - the zoom
is not part of the PBF data - the plugin can not be used "as is", but passing
the zoom level is necessary. This is done by exploiting the optional *format*
parameter of the QImage constructor or the QImage::fromData() or
QPixmap::loadFromData() functions. The zoom number is passed as ASCII string
parameter of the QImage constructor or the *QImage::loadFromData()* or
*QPixmap::loadFromData()* functions. The zoom number is passed as ASCII string
to the functions:
```cpp
QPixmap pm;
pm.loadFromData(tileData, QString::number(zoom).toLatin1());
QImage img;
img.loadFromData(data, QByteArray::number(zoom));
```
The plugin supports vector scaling using QImageReader's setScaledSize() method,
For a complete code sample see the [pbf2png](https://github.com/tumic0/pbf2png)
conversion utility.
### HiDPI
The plugin supports vector scaling using QImageReader's *setScaledSize()* method,
so when used like in the following example:
```cpp
QImageReader reader(file, QString::number(zoom).toLatin1());
QImage img;
QImageReader reader(file, QByteArray::number(zoom));
reader.setScaledSize(QSize(1024, 1024));
reader.read(&image);
reader.read(&img);
```
you will get 1024x1024px tiles with a pixel ratio of 2 (= HiDPI tiles).
For a sample code see the [pbf2png](https://github.com/tumic0/pbf2png)
conversion utility.
### Overzoom
Since version 3 of the plugin tile overzoom is supported. If you set *format*
to `$zoom;$overzoom`:
```cpp
QImage img;
QByteArray fmt(QByteArray::number(zoom) + ';' + QByteArray::number(overzoom));
img.loadFromData(data, fmt);
```
you will get (512<<overzoom)x(512<<overzoom)px tiles with a pixel ratio of 1.
When overzoom is combined with setScaledSize(), the base size is the overzoomed
tile size.
## Styles
The map style is loaded from the
@ -43,7 +58,8 @@ The map style is loaded from the
file on plugin load. If the style uses a sprite, the sprite JSON file must
be named `sprite.json` and the sprite image `sprite.png` and both files must be
placed in the same directory as the style itself. *A style compatible with the
tiles data schema (Mapbox, OpenMapTiles, Tilezen) must be used.*
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)
@ -51,7 +67,7 @@ repository.
## Build
### Requirements
* Qt5 >= 5.12 or Qt6
* Qt5 >= 5.11 or Qt6
* Google Protocol Buffers (protobuf-lite)
* Zlib
@ -92,7 +108,7 @@ algorithm to work properly.
A picture is worth a thousand words.
#### OpenMapTiles
* Data: [MapTiler](https://github.com/tumic0/GPXSee-maps/blob/master/World/MapTiler.tpl)
* Data: [MapTiler](https://github.com/tumic0/GPXSee-maps/blob/master/World/MapTiler-OpenMapTiles.tpl)
* Style: [OSM-liberty](https://github.com/tumic0/QtPBFImagePlugin-styles/blob/master/OpenMapTiles/osm-liberty/style.json)
![osm-liberty 5](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-5.png)

View File

@ -2,6 +2,7 @@ TARGET = pbf
TEMPLATE = lib
CONFIG += plugin
QT += gui
VERSION = 3.1
PROTOS = protobuf/vector_tile.proto
include(protobuf/vector_tile.pri)
@ -38,21 +39,34 @@ RESOURCES += pbfplugin.qrc
DEFINES += QT_NO_DEPRECATED_WARNINGS
unix:!macx{
unix:!macx:!android {
LIBS += -lprotobuf-lite \
-lz
target.path += $$[QT_INSTALL_PLUGINS]/imageformats
INSTALLS += target
}
win32 {
INCLUDEPATH += $$PROTOBUF/include \
$$ZLIB/include
LIBS += $$PROTOBUF/lib/libprotobuf-lite.lib \
$$ZLIB/lib/zlibstatic.lib
QMAKE_TARGET_PRODUCT = QtPBFImagePlugin
QMAKE_TARGET_DESCRIPTION = Qt $$QT_VERSION MVT/PBF image plugin
QMAKE_TARGET_COPYRIGHT = Copyright (c) 2018-2024 Martin Tuma
}
macx {
INCLUDEPATH += $$PROTOBUF/include
LIBS += $$PROTOBUF/lib/libprotobuf-lite.a \
-lz
}
android {
INCLUDEPATH += $$PROTOBUF/include
LIBS += $$PROTOBUF/$$ANDROID_TARGET_ARCH/libprotobuf-lite.a \
-lz
target.path += $$[QT_INSTALL_PLUGINS]/imageformats
INSTALLS += target
top_builddir=$$shadowed($$PWD)
DESTDIR = $$top_builddir/plugins
TARGET = $$qt5LibraryTarget(libpbf, "plugins/imageformats/")
}

View File

@ -6,7 +6,7 @@
QByteArray Gzip::uncompress(QIODevice *device, int limit)
{
int ret;
int ret = Z_STREAM_END;
z_stream strm;
unsigned char in[CHUNK];
unsigned char out[CHUNK];

View File

@ -79,16 +79,19 @@ bool PBFHandler::read(QImage *image)
return false;
}
bool ok;
int zoom = format().toInt(&ok);
QList<QByteArray> list(format().split(';'));
int zoom = list.size() ? list.first().toInt() : 0;
int overzoom = (list.size() > 1) ? list.at(1).toInt() : 0;
QSize scaledSize(_scaledSize.isValid()
? _scaledSize : QSize(TILE_SIZE, TILE_SIZE));
QSize size(scaledSize.width()<<overzoom,
scaledSize.height()<<overzoom);
QPointF scale((qreal)scaledSize.width() / TILE_SIZE,
(qreal)scaledSize.height() / TILE_SIZE);
QSize size = _scaledSize.isValid()
? _scaledSize : QSize(TILE_SIZE, TILE_SIZE);
QPointF scale = _scaledSize.isValid()
? QPointF((qreal)_scaledSize.width() / TILE_SIZE,
(qreal)_scaledSize.height() / TILE_SIZE) : QPointF(1.0, 1.0);
*image = QImage(size, QImage::Format_ARGB32_Premultiplied);
Tile tile(image, ok ? zoom : -1, scale);
Tile tile(image, zoom, scale);
_style->render(data, tile);

View File

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

View File

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

View File

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

View File

@ -8,7 +8,6 @@
#include <QRegularExpression>
#include <QDebug>
#include "text.h"
#include "color.h"
#include "font.h"
#include "tile.h"
#include "pbf.h"
@ -205,7 +204,7 @@ bool Style::Layer::Filter::match(const PBF::Feature &feature) const
QString Style::Layer::Template::value(int zoom, const PBF::Feature &feature) const
{
QRegularExpression rx("\\{[^\\}]*\\}");
static QRegularExpression rx("\\{[^\\}]*\\}");
QString text(_field.value(zoom));
QRegularExpressionMatchIterator it = rx.globalMatch(text);
QStringList keys;
@ -258,6 +257,9 @@ Style::Layer::Paint::Paint(const QJsonObject &json)
_textHaloColor = FunctionC(json["text-halo-color"], QColor());
_textHaloWidth = FunctionF(json["text-halo-width"]);
_textHaloBlur = FunctionF(json["text-halo-blur"]);
// icon
_iconColor = FunctionC(json["icon-color"]);
}
QPen Style::Layer::Paint::pen(Type type, int zoom) const
@ -293,7 +295,7 @@ QPen Style::Layer::Paint::pen(Type type, int zoom) const
return pen;
}
QBrush Style::Layer::Paint::brush(Type type, int zoom, const Sprites &sprites)
QBrush Style::Layer::Paint::brush(Type type, int zoom, Sprites &sprites)
const
{
QColor color;
@ -370,6 +372,7 @@ Style::Layer::Layout::Layout(const QJsonObject &json)
// icon
_icon = Template(FunctionS(json["icon-image"]));
_iconSize = FunctionF(json["icon-size"]);
// symbol
_symbolPlacement = FunctionS(json["symbol-placement"]);
@ -512,7 +515,7 @@ bool Style::Layer::match(int zoom, const PBF::Feature &feature) const
return _filter.match(feature);
}
void Style::Layer::setPathPainter(Tile &tile, const Sprites &sprites) const
void Style::Layer::setPathPainter(Tile &tile, Sprites &sprites) const
{
QPainter &p = tile.painter();
int zoom = tile.zoom();
@ -545,14 +548,18 @@ void Style::Layer::setTextProperties(Tile &tile) const
}
void Style::Layer::addSymbol(Tile &tile, const QPainterPath &path,
const PBF::Feature &feature, const Sprites &sprites) const
const PBF::Feature &feature, Sprites &sprites) const
{
QString text = _layout.text(tile.zoom(), feature);
if (text.isEmpty())
QString text(_layout.text(tile.zoom(), feature));
QString icon(_layout.icon(tile.zoom(), feature));
QColor color(_paint.iconColor(tile.zoom()));
qreal size(_layout.iconSize(tile.zoom()));
QImage img(sprites.icon(icon, color, size));
if (text.isEmpty() && img.isNull())
return;
QString icon = _layout.icon(tile.zoom(), feature);
tile.text().addLabel(text, sprites.icon(icon), path);
tile.text().addLabel(text, img, path);
}
static bool loadSprites(const QDir &styleDir, const QString &json,
@ -598,20 +605,20 @@ bool Style::load(const QString &fileName)
_layers.append(Layer(layers[i].toObject()));
}
QDir styleDir = QFileInfo(fileName).absoluteDir();
QDir styleDir(QFileInfo(fileName).absoluteDir());
loadSprites(styleDir, "sprite.json", "sprite.png", _sprites);
loadSprites(styleDir, "sprite@2x.json", "sprite@2x.png", _sprites2x);
return true;
}
const Sprites &Style::sprites(const QPointF &scale) const
Sprites &Style::sprites(const QPointF &scale)
{
return (scale.x() > 1.0 || scale.y() > 1.0)
&& !_sprites2x.isNull() ? _sprites2x : _sprites;
}
void Style::setupLayer(Tile &tile, const Layer &layer) const
void Style::setupLayer(Tile &tile, const Layer &layer)
{
if (layer.isSymbol())
layer.setTextProperties(tile);
@ -619,7 +626,7 @@ void Style::setupLayer(Tile &tile, const Layer &layer) const
layer.setPathPainter(tile, sprites(tile.scale()));
}
void Style::drawBackground(Tile &tile) const
void Style::drawBackground(Tile &tile)
{
QRectF rect(QPointF(0, 0), QSizeF(tile.size().width() / tile.scale().x(),
tile.size().height() / tile.scale().y()));
@ -634,13 +641,10 @@ void Style::drawBackground(Tile &tile) const
_layers.first().setPathPainter(tile, _sprites);
tile.painter().drawPath(path);
}
//tile.painter().setPen(Qt::red);
//tile.painter().drawRect(rect);
}
void Style::drawFeature(const PBF::Feature &feature, const Layer &layer,
Tile &tile, const QSizeF &factor) const
Tile &tile, const QSizeF &factor)
{
if (!layer.match(tile.zoom(), feature))
return;
@ -656,7 +660,7 @@ void Style::drawFeature(const PBF::Feature &feature, const Layer &layer,
}
void Style::drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer,
Tile &tile) const
Tile &tile)
{
if (pbfLayer.data()->version() > 2)
return;
@ -675,7 +679,7 @@ void Style::drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer,
tile.painter().restore();
}
void Style::render(const PBF &data, Tile &tile) const
void Style::render(const PBF &data, Tile &tile)
{
drawBackground(tile);
@ -689,4 +693,11 @@ void Style::render(const PBF &data, Tile &tile) const
}
tile.text().render(&tile.painter());
//QRectF rect(QPointF(0, 0), QSizeF(tile.size().width() / tile.scale().x(),
// tile.size().height() / tile.scale().y()));
//tile.painter().setPen(Qt::red);
//tile.painter().setBrush(Qt::NoBrush);
//tile.painter().setRenderHint(QPainter::Antialiasing, false);
//tile.painter().drawRect(rect);
}

View File

@ -29,7 +29,7 @@ public:
Style(QObject *parent = 0) : QObject(parent) {}
bool load(const QString &fileName);
void render(const PBF &data, Tile &tile) const;
void render(const PBF &data, Tile &tile);
private:
class Layer {
@ -44,10 +44,10 @@ private:
bool isVisible() const {return (_layout.visible());}
bool match(int zoom, const PBF::Feature &feature) const;
void setPathPainter(Tile &tile, const Sprites &sprites) const;
void setPathPainter(Tile &tile, Sprites &sprites) const;
void setTextProperties(Tile &tile) const;
void addSymbol(Tile &tile, const QPainterPath &path,
const PBF::Feature &feature, const Sprites &sprites) const;
const PBF::Feature &feature, Sprites &sprites) const;
private:
enum Type {
@ -103,6 +103,8 @@ private:
{return _text.value(zoom, feature).trimmed();}
QString icon(int zoom, const PBF::Feature &feature) const
{return _icon.value(zoom, feature);}
qreal iconSize(int zoom) const
{return _iconSize.value(zoom);}
QFont font(int zoom) const;
Qt::PenCapStyle lineCap(int zoom) const;
Qt::PenJoinStyle lineJoin(int zoom) const;
@ -116,6 +118,7 @@ private:
Template _text;
Template _icon;
FunctionF _iconSize;
FunctionF _textSize;
FunctionF _textMaxWidth;
FunctionF _textMaxAngle;
@ -135,13 +138,15 @@ private:
Paint(const QJsonObject &json);
QPen pen(Layer::Type type, int zoom) const;
QBrush brush(Layer::Type type, int zoom, const Sprites &sprites)
QBrush brush(Layer::Type type, int zoom, Sprites &sprites)
const;
qreal opacity(Layer::Type type, int zoom) const;
bool antialias(Layer::Type type, int zoom) const;
Text::Halo halo(int zoom) const
{return Text::Halo(_textHaloColor.value(zoom),
_textHaloWidth.value(zoom), _textHaloBlur.value(zoom));}
QColor iconColor(int zoom) const
{return _iconColor.value(zoom);}
private:
FunctionC _textColor;
@ -150,6 +155,7 @@ private:
FunctionC _fillColor;
FunctionC _fillOutlineColor;
FunctionC _backgroundColor;
FunctionC _iconColor;
FunctionF _fillOpacity;
FunctionF _lineOpacity;
FunctionF _lineWidth;
@ -168,14 +174,14 @@ private:
Paint _paint;
};
const Sprites &sprites(const QPointF &scale) const;
Sprites &sprites(const QPointF &scale);
void drawBackground(Tile &tile) const;
void setupLayer(Tile &tile, const Layer &layer) const;
void drawBackground(Tile &tile);
void setupLayer(Tile &tile, const Layer &layer);
void drawFeature(const PBF::Feature &feature, const Layer &layer,
Tile &tile, const QSizeF &factor) const;
Tile &tile, const QSizeF &factor);
void drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer,
Tile &tile) const;
Tile &tile);
QVector<Layer> _layers;
Sprites _sprites, _sprites2x;

View File

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

View File

@ -21,14 +21,14 @@ qreal TextItem::avgCharWidth() const
ratio = 1.0;
// Greek & Cyrilic
else if (cp >= 0x03FF && cp <= 0x04FF) {
ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.80 : 0.73;
ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.80 : 0.70;
if (_font.bold())
ratio *= 1.1;
if (_font.italic())
ratio *= 0.9;
// The rest (Latin scripts, Arabic, ...)
} else {
ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.75 : 0.63;
ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.75 : 0.60;
if (_font.bold())
ratio *= 1.1;
if (_font.italic())

View File

@ -9,23 +9,20 @@
#define INTERSECTS intersects
#endif // QT 5.15
static bool intersection(const QLineF &line, const QRectF &rect,
QPointF *p)
static void swap(const QLineF &line, QPointF *p1, QPointF *p2)
{
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;
QPointF lp1(line.p1());
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,
@ -38,20 +35,26 @@ static bool intersection(const QLineF &line, const QRectF &rect, QPointF *p1,
p = p2;
if (line.INTERSECTS(QLineF(rect.topLeft(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection) {
if (p == p2)
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)
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)
if (p == p2) {
swap(line, p1, p2);
return true;
}
}
Q_ASSERT(p != p2);
@ -59,22 +62,42 @@ static bool intersection(const QLineF &line, const QRectF &rect, QPointF *p1,
return false;
}
static QPainterPath subpath(const QList<QLineF> &lines, int start, int end,
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 ss = 0, es = 0;
int si = start, ei = end;
for (int i = start; i <= end; i++) {
qreal len = lines.at(i).length();
for (int i = start; i < end; i++) {
QLineF l(path.at(i), path.at(i+1));
qreal len = l.length();
if (ss + len < cut / 2) {
ss += len;
si++;
} else
break;
}
for (int i = end; i >= start; i--) {
qreal len = lines.at(i).length();
for (int i = end; i > start; i--) {
QLineF l(path.at(i), path.at(i-1));
qreal len = l.length();
if (es + len < cut / 2) {
es += len;
ei--;
@ -82,98 +105,117 @@ static QPainterPath subpath(const QList<QLineF> &lines, int start, int end,
break;
}
QLineF sl(lines.at(si).p2(), lines.at(si).p1());
QLineF sl(path.at(si+1), path.at(si));
sl.setLength(sl.length() - (cut / 2 - ss));
QLineF el(lines.at(ei));
QLineF el(path.at(ei-1), path.at(ei));
el.setLength(el.length() - (cut / 2 - es));
QPainterPath p(sl.p2());
for (int i = si; i <= ei; i++)
p.lineTo(lines.at(i).p2());
p.setElementPositionAt(p.elementCount() - 1, el.p2().x(), el.p2().y());
for (int i = si + 1; i < ei; i++)
p.lineTo(path.at(i));
p.lineTo(el.p2());
return p;
}
static QList<QLineF> lineString(const QPainterPath &path,
const QRectF &boundingRect)
static QList<QPolygonF> polyLines(const QPainterPath &path, const QRectF &rect)
{
QList<QLineF> lines;
int start = -1, end = -1;
QList<QPolygonF> lines;
QPolygonF line;
bool lastIn = rect.contains(path.elementAt(0));
for (int i = 1; i < path.elementCount(); i++) {
if (rect.contains(path.elementAt(i))) {
if (lastIn) {
if (line.isEmpty())
line.append(path.elementAt(i-1));
line.append(path.elementAt(i));
} else {
QPointF p;
QLineF l(path.elementAt(i-1), path.elementAt(i));
for (int i = 0; i < path.elementCount(); i++) {
if (boundingRect.contains(path.elementAt(i))) {
start = i;
break;
}
}
for (int i = path.elementCount() - 1; i >= 0; i--) {
if (boundingRect.contains(path.elementAt(i))) {
end = i;
break;
}
}
if (start < 0) {
QPointF p1, p2;
for (int i = 1; i < path.elementCount(); i++) {
QLineF l(path.elementAt(i-1), path.elementAt(i));
if (intersection(l, boundingRect, &p1, &p2)) {
lines.append(QLineF(p1, p2));
break;
if (intersection(l, rect, &p))
line.append(p);
line.append(path.elementAt(i));
}
}
} else {
QPointF p;
if (start > 0) {
QLineF l(path.elementAt(start-1), path.elementAt(start));
if (intersection(l, boundingRect, &p))
lines.append(QLineF(p, path.elementAt(start)));
}
for (int i = start + 1; i <= end; i++)
lines.append(QLineF(path.elementAt(i-1), path.elementAt(i)));
if (end < path.elementCount() - 1) {
QLineF l(path.elementAt(end), path.elementAt(end+1));
if (intersection(l, boundingRect, &p))
lines.append(QLineF(path.elementAt(end), p));
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())
lines.append(line);
return lines;
}
static qreal diff(qreal a1, qreal a2)
{
qreal d = qAbs(a1 - a2);
return (d > 180) ? 360 - d : d;
}
static QPainterPath textPath(const QPainterPath &path, qreal textWidth,
qreal maxAngle, qreal charWidth, const QRectF &tileRect)
{
QList<QLineF> lines(lineString(path, tileRect));
if (lines.isEmpty())
if (path.isEmpty())
return QPainterPath();
qreal length = 0;
qreal angle = lines.first().angle();
int last = 0;
QList<QPolygonF> lines(polyLines(path, tileRect));
for (int i = 0; i < lines.size(); i++) {
qreal sl = lines.at(i).length();
qreal a = lines.at(i).angle();
const QPolygonF &pl = lines.at(i);
qreal angle = 0, length = 0;
int last = 0;
if (!tileRect.contains(lines.at(i).p2()) || sl < charWidth
|| qAbs(angle - a) > maxAngle) {
if (length > textWidth)
return subpath(lines, last, i - 1, length - textWidth);
last = i;
length = 0;
} else
length += sl;
for (int j = 1; j < pl.size(); j ++) {
QLineF l(pl.at(j-1), pl.at(j));
qreal sl = l.length();
qreal a = l.angle();
angle = a;
if (sl < charWidth) {
if (length > textWidth)
return subpath(pl, last, j - 1, length - textWidth);
last = j;
length = 0;
} else if (j > 1 && diff(angle, a) > maxAngle) {
if (length > textWidth)
return subpath(pl, last, j - 1, length - textWidth);
last = j - 1;
length = sl;
} else
length += sl;
angle = a;
}
if (length > textWidth)
return subpath(pl, last, pl.size() - 1, length - textWidth);
}
return (length > textWidth)
? subpath(lines, last, lines.size() - 1, length - textWidth)
: QPainterPath();
return QPainterPath();
}
static bool reverse(const QPainterPath &path)
@ -208,9 +250,6 @@ TextPathItem::TextPathItem(const QString &text, const QPainterPath &path,
void TextPathItem::paint(QPainter *painter) const
{
//painter->setPen(Qt::red);
//painter->drawPath(_shape);
QFontMetrics fm(font());
int textWidth = fm.boundingRect(text()).width();
@ -259,4 +298,9 @@ void TextPathItem::paint(QPainter *painter) const
int width = fm.horizontalAdvance(text().at(i));
percent += ((qreal)width / (qreal)textWidth) * factor;
}
//painter->setBrush(Qt::NoBrush);
//painter->setPen(Qt::red);
//painter->setRenderHint(QPainter::Antialiasing, false);
//painter->drawPath(_shape);
}

View File

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

View File

@ -11,7 +11,10 @@ public:
: _zoom(zoom), _size(img->size()), _scale(scale),
_text(QSize(img->size().width() / scale.x(),
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;}
const QSize &size() const {return _size;}

View File

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