QtPBFImagePlugin/src/style.cpp

560 lines
14 KiB
C++
Raw Normal View History

2018-10-29 00:11:23 +01:00
#include <QPainter>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
2018-11-22 21:57:21 +01:00
#include <QFileInfo>
#include <QDir>
2018-10-29 00:11:23 +01:00
#include <QDebug>
#include "text.h"
#include "color.h"
#include "font.h"
2018-10-29 00:11:23 +01:00
#include "tile.h"
#include "style.h"
2018-11-13 00:07:51 +01:00
static void jsonColor(const QJsonObject &json, const char *name, FunctionC &var)
{
if (json.contains(name)) {
const QJsonValue value = json[name];
if (value.isString())
var = FunctionC(Color::fromJsonString(value.toString()));
else if (value.isObject())
var = FunctionC(value.toObject());
}
}
static void jsonFloat(const QJsonObject &json, const char *name, FunctionF &var)
{
if (json.contains(name)) {
const QJsonValue value = json[name];
if (value.isDouble())
var = FunctionF(value.toDouble());
else if (value.isObject())
var = FunctionF(value.toObject());
}
}
static void jsonBool(const QJsonObject &json, const char *name, FunctionB &var)
{
if (json.contains(name)) {
QJsonValue value = json[name];
if (value.isBool())
var = FunctionB(value.toBool());
else if (value.isObject())
var = FunctionB(value.toObject());
}
}
2018-10-29 00:11:23 +01:00
Style::Layer::Filter::Filter(const QJsonArray &json)
: _type(Unknown), _not(false)
{
2018-10-30 22:08:33 +01:00
#define INVALID_FILTER(json) \
{qWarning() << json << ": invalid filter"; return;}
2018-10-29 00:11:23 +01:00
if (json.isEmpty())
2018-10-30 22:08:33 +01:00
INVALID_FILTER(json);
2018-10-29 00:11:23 +01:00
QString type = json.at(0).toString();
if (type == "==") {
2018-10-30 22:08:33 +01:00
if (json.size() != 3)
INVALID_FILTER(json);
2018-10-29 00:11:23 +01:00
_type = EQ;
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
} else if (type == "!=") {
2018-10-30 22:08:33 +01:00
if (json.size() != 3)
INVALID_FILTER(json);
2018-10-29 00:11:23 +01:00
_type = NE;
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
} else if (type == "<") {
2018-10-30 22:08:33 +01:00
if (json.size() != 3)
INVALID_FILTER(json);
2018-10-29 00:11:23 +01:00
_type = LT;
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
} else if (type == "<=") {
2018-10-30 22:08:33 +01:00
if (json.size() != 3)
INVALID_FILTER(json);
2018-10-29 00:11:23 +01:00
_type = LE;
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
} else if (type == ">") {
2018-10-30 22:08:33 +01:00
if (json.size() != 3)
INVALID_FILTER(json);
2018-10-29 00:11:23 +01:00
_type = GT;
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
} else if (type == ">=") {
2018-10-30 22:08:33 +01:00
if (json.size() != 3)
INVALID_FILTER(json);
2018-10-29 00:11:23 +01:00
_type = GE;
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
} else if (type == "all") {
_type = All;
for (int i = 1; i < json.size(); i++)
_filters.append(Filter(json.at(i).toArray()));
} else if (type == "any") {
_type = Any;
for (int i = 1; i < json.size(); i++)
_filters.append(Filter(json.at(i).toArray()));
} else if (type == "in") {
2018-10-30 22:08:33 +01:00
if (json.size() < 3)
INVALID_FILTER(json);
2018-10-29 00:11:23 +01:00
_type = In;
_kv = QPair<QString, QVariant>(json.at(1).toString(), QVariant());
for (int i = 2; i < json.size(); i++)
_set.insert(json.at(i).toString());
} else if (type == "!in") {
2018-10-30 22:08:33 +01:00
if (json.size() < 3)
INVALID_FILTER(json);
2018-10-29 00:11:23 +01:00
_type = In;
_not = true;
_kv = QPair<QString, QVariant>(json.at(1).toString(), QVariant());
for (int i = 2; i < json.size(); i++)
_set.insert(json.at(i).toString());
} else if (type == "has") {
2018-10-30 22:08:33 +01:00
if (json.size() < 2)
INVALID_FILTER(json);
2018-10-29 00:11:23 +01:00
_type = Has;
_kv = QPair<QString, QVariant>(json.at(1).toString(), QVariant());
} else if (type == "!has") {
2018-10-30 22:08:33 +01:00
if (json.size() < 2)
INVALID_FILTER(json);
2018-10-29 00:11:23 +01:00
_type = Has;
_not = true;
_kv = QPair<QString, QVariant>(json.at(1).toString(), QVariant());
} else
2018-10-30 22:08:33 +01:00
INVALID_FILTER(json);
2018-10-29 00:11:23 +01:00
}
2018-11-10 19:34:45 +01:00
bool Style::Layer::Filter::match(const QVariantHash &tags) const
2018-10-29 00:11:23 +01:00
{
switch (_type) {
case None:
return true;
case EQ:
return tags.value(_kv.first) == _kv.second;
case NE:
return tags.value(_kv.first) != _kv.second;
case GT:
return tags.value(_kv.first) > _kv.second;
case GE:
return tags.value(_kv.first) >= _kv.second;
case LT:
return tags.value(_kv.first) < _kv.second;
case LE:
return tags.value(_kv.first) <= _kv.second;
case In:
return _set.contains(tags.value(_kv.first).toString()) ^ _not;
case Has:
return tags.contains(_kv.first) ^ _not;
case All:
for (int i = 0; i < _filters.size(); i++) {
if (!_filters.at(i).match(tags))
return false;
}
return true;
case Any:
for (int i = 0; i < _filters.size(); i++) {
if (_filters.at(i).match(tags))
return true;
}
return false;
default:
return false;
}
}
2018-11-22 21:57:21 +01:00
Style::Layer::Template::Template(const QString &str) : _field(str)
{
int pos = 0;
while ((pos = _rx.indexIn(_field, pos)) != -1) {
QString match = _rx.capturedTexts().first();
_keys.append(match.mid(1, match.size() - 2));
pos += _rx.matchedLength();
}
}
QString Style::Layer::Template::value(const QVariantHash &tags) const
{
QString text(_field);
for (int i = 0; i < _keys.size(); i++) {
const QString &key = _keys.at(i);
const QVariant val = tags.value(key);
text.replace(QString("{%1}").arg(key), val.toString());
}
return text;
}
QRegExp Style::Layer::Template::_rx = QRegExp("\\{[^\\}]*\\}");
2018-10-29 00:11:23 +01:00
Style::Layer::Paint::Paint(const QJsonObject &json)
: _fillOpacity(1.0), _lineOpacity(1.0), _lineWidth(1.0)
2018-10-29 00:11:23 +01:00
{
2018-11-13 00:07:51 +01:00
// fill
jsonFloat(json, "fill-opacity", _fillOpacity);
jsonColor(json, "fill-color", _fillColor);
_fillOutlineColor = _fillColor;
jsonColor(json, "fill-outline-color", _fillOutlineColor);
jsonBool(json, "fill-antialias",_fillAntialias);
2018-11-03 19:16:08 +01:00
if (json.contains("fill-pattern")) {
_fillColor = FunctionC(QColor());
_fillOutlineColor = FunctionC(QColor());
}
2018-11-13 00:07:51 +01:00
// line
jsonColor(json, "line-color", _lineColor);
jsonFloat(json, "line-width", _lineWidth);
jsonFloat(json, "line-opacity", _lineOpacity);
2018-10-29 00:11:23 +01:00
if (json.contains("line-dasharray") && json["line-dasharray"].isArray()) {
QJsonArray array = json["line-dasharray"].toArray();
for (int i = 0; i < array.size(); i++)
_lineDasharray.append(array.at(i).toDouble());
}
2018-11-13 00:07:51 +01:00
// background
jsonColor(json, "background-color", _backgroundColor);
// text
jsonColor(json, "text-color", _textColor);
2018-10-29 00:11:23 +01:00
}
QPen Style::Layer::Paint::pen(Type type, int zoom) const
{
QPen pen(Qt::NoPen);
qreal width;
2018-10-31 00:47:23 +01:00
QColor color;
2018-10-29 00:11:23 +01:00
switch (type) {
case Line:
width = _lineWidth.value(zoom);
2018-10-31 00:47:23 +01:00
color = _lineColor.value(zoom);
if (color.isValid() && width > 0) {
pen = QPen(color, width);
2018-10-29 00:11:23 +01:00
if (!_lineDasharray.isEmpty())
pen.setDashPattern(_lineDasharray);
}
break;
case Fill:
2018-10-31 00:47:23 +01:00
color = _fillOutlineColor.value(zoom);
if (color.isValid())
pen = QPen(color);
2018-10-29 00:11:23 +01:00
break;
case Symbol:
2018-10-31 00:47:23 +01:00
color = _textColor.value(zoom);
if (color.isValid())
pen = QPen(color);
2018-10-29 00:11:23 +01:00
break;
default:
break;
}
return pen;
}
QBrush Style::Layer::Paint::brush(Type type, int zoom) const
{
2018-10-31 00:47:23 +01:00
QColor color;
2018-10-29 00:11:23 +01:00
switch (type) {
case Fill:
2018-10-31 00:47:23 +01:00
color = _fillColor.value(zoom);
return color.isValid() ? QBrush(color) : QBrush(Qt::NoBrush);
2018-10-29 00:11:23 +01:00
case Background:
2018-10-31 00:47:23 +01:00
color = _backgroundColor.value(zoom);
return color.isValid() ? QBrush(color) : QBrush(Qt::NoBrush);
2018-10-29 00:11:23 +01:00
default:
return QBrush(Qt::NoBrush);
}
}
qreal Style::Layer::Paint::opacity(Type type, int zoom) const
{
switch (type) {
case Fill:
return _fillOpacity.value(zoom);
case Line:
return _lineOpacity.value(zoom);
default:
return 1.0;
}
}
bool Style::Layer::Paint::antialias(Layer::Type type, int zoom) const
2018-10-29 00:11:23 +01:00
{
switch (type) {
case Fill:
return _fillAntialias.value(zoom);
2018-10-29 00:11:23 +01:00
case Line:
return true;
default:
return false;
}
}
Style::Layer::Layout::Layout(const QJsonObject &json)
2018-11-04 23:54:34 +01:00
: _textSize(16), _textMaxWidth(10), _textMaxAngle(45), _lineCap(Qt::FlatCap),
_lineJoin(Qt::MiterJoin), _font("Open Sans"), _capitalize(false),
2018-11-22 21:57:21 +01:00
_viewportAlignment(false), _textAnchor(Text::Center)
2018-10-29 00:11:23 +01:00
{
2018-11-13 18:24:51 +01:00
// line
2018-11-13 00:07:51 +01:00
if (json.contains("line-cap") && json["line-cap"].isString()) {
if (json["line-cap"].toString() == "round")
_lineCap = Qt::RoundCap;
else if (json["line-cap"].toString() == "square")
_lineCap = Qt::SquareCap;
}
if (json.contains("line-join") && json["line-join"].isString()) {
if (json["line-join"].toString() == "bevel")
_lineJoin = Qt::BevelJoin;
else if (json["line-join"].toString() == "round")
_lineJoin = Qt::RoundJoin;
}
2018-11-13 18:24:51 +01:00
// text
2018-11-22 21:57:21 +01:00
if (json.contains("text-field") && json["text-field"].isString())
_text = Template(json["text-field"].toString());
2018-10-29 00:11:23 +01:00
2018-11-13 00:07:51 +01:00
jsonFloat(json, "text-size", _textSize);
jsonFloat(json, "text-max-width", _textMaxWidth);
jsonFloat(json, "text-max-angle", _textMaxAngle);
if (json.contains("text-font") && json["text-font"].isArray())
_font = Font::fromJsonArray(json["text-font"].toArray());
2018-10-29 18:42:04 +01:00
if (json.contains("text-transform") && json["text-transform"].isString())
_capitalize = json["text-transform"].toString() == "uppercase";
if (json.contains("text-rotation-alignment")
&& json["text-rotation-alignment"].isString())
if (json["text-rotation-alignment"].toString() == "viewport")
_viewportAlignment = true;
2018-11-22 21:57:21 +01:00
if (json.contains("text-anchor") && json["text-anchor"].isString()) {
QString anchor(json["text-anchor"].toString());
if (anchor == "center")
_textAnchor = Text::Center;
else if (anchor == "left")
_textAnchor = Text::Left;
else if (anchor == "right")
_textAnchor = Text::Right;
else if (anchor == "top")
_textAnchor = Text::Top;
else if (anchor == "bottom")
_textAnchor = Text::Bottom;
}
// icon
if (json.contains("icon-image") && json["icon-image"].isString())
_icon = Template(json["icon-image"].toString());
2018-10-29 00:11:23 +01:00
}
QFont Style::Layer::Layout::font(int zoom) const
{
QFont font(_font);
font.setPixelSize(_textSize.value(zoom));
2018-10-29 00:11:23 +01:00
return font;
}
Style::Layer::Layer(const QJsonObject &json)
: _type(Unknown), _minZoom(-1), _maxZoom(-1)
{
// type
QString type = json["type"].toString();
if (type == "fill")
_type = Fill;
else if (type == "line")
_type = Line;
else if (type == "background")
_type = Background;
else if (type == "symbol")
_type = Symbol;
// source-layer
_sourceLayer = json["source-layer"].toString();
// zooms
if (json.contains("minzoom") && json["minzoom"].isDouble())
_minZoom = json["minzoom"].toInt();
if (json.contains("maxzoom") && json["maxzoom"].isDouble())
_maxZoom = json["maxzoom"].toInt();
// filter
if (json.contains("filter") && json["filter"].isArray())
_filter = Filter(json["filter"].toArray());
// layout
if (json.contains("layout") && json["layout"].isObject())
_layout = Layout(json["layout"].toObject());
// paint
if (json.contains("paint") && json["paint"].isObject())
_paint = Paint(json["paint"].toObject());
}
2018-11-10 19:34:45 +01:00
bool Style::Layer::match(int zoom, const QVariantHash &tags) const
2018-10-29 00:11:23 +01:00
{
if (zoom >= 0) {
if (_minZoom > 0 && zoom < _minZoom)
return false;
if (_maxZoom > 0 && zoom > _maxZoom)
return false;
}
return _filter.match(tags);
}
2018-11-22 21:57:21 +01:00
void Style::Layer::setPathPainter(Tile &tile) const
2018-10-29 00:11:23 +01:00
{
2018-11-22 21:57:21 +01:00
QPen pen(_paint.pen(_type, tile.zoom()));
QBrush brush(_paint.brush(_type, tile.zoom()));
2018-10-31 00:47:23 +01:00
2018-10-29 00:11:23 +01:00
pen.setJoinStyle(_layout.lineJoin());
pen.setCapStyle(_layout.lineCap());
2018-10-31 00:47:23 +01:00
QPainter &p = tile.painter();
2018-11-22 21:57:21 +01:00
p.setRenderHint(QPainter::Antialiasing, _paint.antialias(_type, tile.zoom()));
2018-10-29 22:35:05 +01:00
p.setPen(pen);
2018-10-31 00:47:23 +01:00
p.setBrush(brush);
2018-11-22 21:57:21 +01:00
p.setOpacity(_paint.opacity(_type, tile.zoom()));
2018-10-29 00:11:23 +01:00
}
2018-11-22 21:57:21 +01:00
void Style::Layer::setSymbolPainter(Tile &tile) const
2018-11-13 18:24:51 +01:00
{
2018-11-22 21:57:21 +01:00
QPen pen(_paint.pen(_type, tile.zoom()));
QFont font(_layout.font(tile.zoom()));
2018-11-13 18:24:51 +01:00
QPainter &p = tile.painter();
p.setPen(pen);
p.setFont(font);
}
2018-11-22 21:57:21 +01:00
void Style::Layer::setTextProperties(Tile &tile) const
2018-10-29 00:11:23 +01:00
{
2018-11-22 21:57:21 +01:00
Text::Properties prop;
prop.maxWidth = _layout.maxTextWidth(tile.zoom());
prop.maxAngle = _layout.maxTextAngle(tile.zoom());
prop.anchor = _layout.textAnchor();
2018-11-03 21:05:28 +01:00
2018-11-22 21:57:21 +01:00
tile.text().setProperties(prop);
}
2018-10-29 00:11:23 +01:00
2018-11-22 21:57:21 +01:00
void Style::Layer::addSymbol(Tile &tile, const QPainterPath &path,
const QVariantHash &tags, const Sprites &sprites) const
{
QString text = _layout.text().value(tags);
2018-11-03 21:05:28 +01:00
QString tt(text.trimmed());
if (tt.isEmpty())
return;
2018-11-22 21:57:21 +01:00
if (_layout.capitalize())
tt = tt.toUpper();
QString icon = _layout.icon().value(tags);
2018-11-03 21:05:28 +01:00
if (_layout.viewportAlignment())
2018-11-22 21:57:21 +01:00
tile.text().addLabel(tt, path.elementAt(0), tile.painter(), false,
sprites.icon(icon));
else if (path.elementCount() == 1 && path.elementAt(0).isMoveTo())
2018-11-22 21:57:21 +01:00
tile.text().addLabel(tt, path.elementAt(0), tile.painter(), true,
sprites.icon(icon));
2018-10-29 00:11:23 +01:00
else
2018-11-22 21:57:21 +01:00
tile.text().addLabel(tt, path, tile.painter());
2018-10-29 00:11:23 +01:00
}
bool Style::load(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qCritical() << fileName << ": error opening file";
2018-10-29 00:11:23 +01:00
return false;
}
2018-10-29 00:11:23 +01:00
QByteArray ba(file.readAll());
file.close();
QJsonParseError error;
QJsonDocument doc(QJsonDocument::fromJson(ba, &error));
if (doc.isNull()) {
qCritical() << fileName << ":" << error.errorString();
return false;
}
QJsonObject json(doc.object());
2018-10-29 00:11:23 +01:00
if (json.contains("layers") && json["layers"].isArray()) {
QJsonArray layers = json["layers"].toArray();
for (int i = 0; i < layers.size(); i++)
if (layers[i].isObject())
2018-11-22 21:57:21 +01:00
_layers.append(Layer(layers[i].toObject()));
2018-10-29 00:11:23 +01:00
}
2018-11-22 21:57:21 +01:00
for (int i = 0; i < _layers.size(); i++)
_sourceLayers.append(_layers.at(i).sourceLayer());
QDir styleDir = QFileInfo(fileName).absoluteDir();
QString spritesJSON(styleDir.filePath("sprite.json"));
if (QFileInfo::exists(spritesJSON)) {
QString spritesImg(styleDir.filePath("sprite.png"));
if (QFileInfo::exists(spritesImg))
_sprites.load(spritesJSON, spritesImg);
else
qCritical() << spritesImg << ": no such file";
}
2018-10-29 00:11:23 +01:00
return true;
}
2018-11-22 21:57:21 +01:00
bool Style::match(int zoom, int layer, const QVariantHash &tags) const
{
return _layers.at(layer).match(zoom, tags);
}
void Style::setTextProperties(Tile &tile, int layer) const
2018-10-29 00:11:23 +01:00
{
2018-11-22 21:57:21 +01:00
const Layer &sl = _layers.at(layer);
sl.setTextProperties(tile);
2018-10-29 00:11:23 +01:00
}
2018-11-22 21:57:21 +01:00
void Style::setPainter(Tile &tile, int layer) const
2018-11-13 01:01:36 +01:00
{
2018-11-22 21:57:21 +01:00
const Layer &sl = _layers.at(layer);
2018-11-13 01:01:36 +01:00
if (sl.isPath())
2018-11-22 21:57:21 +01:00
sl.setPathPainter(tile);
2018-11-13 18:24:51 +01:00
else if (sl.isSymbol())
2018-11-22 21:57:21 +01:00
sl.setSymbolPainter(tile);
2018-11-13 01:01:36 +01:00
}
2018-11-22 21:57:21 +01:00
void Style::drawFeature(Tile &tile, int layer, const QPainterPath &path,
const QVariantHash &tags) const
2018-10-29 00:11:23 +01:00
{
2018-11-22 21:57:21 +01:00
const Layer &sl = _layers.at(layer);
2018-10-29 00:11:23 +01:00
if (sl.isPath())
2018-11-13 01:01:36 +01:00
tile.painter().drawPath(path);
2018-10-29 00:11:23 +01:00
else if (sl.isSymbol())
2018-11-22 21:57:21 +01:00
sl.addSymbol(tile, path, tags, _sprites);
2018-10-29 00:11:23 +01:00
}
2018-11-22 21:57:21 +01:00
void Style::drawBackground(Tile &tile) const
2018-10-29 00:11:23 +01:00
{
QRectF rect(QPointF(0, 0), tile.size());
2018-10-29 18:42:04 +01:00
QPainterPath path;
2018-10-31 17:40:43 +01:00
path.addRect(rect);
2018-10-29 00:11:23 +01:00
2018-11-22 21:57:21 +01:00
if (_layers.isEmpty()) {
2018-10-29 22:35:05 +01:00
tile.painter().setBrush(Qt::lightGray);
2018-10-31 17:40:43 +01:00
tile.painter().setPen(Qt::NoPen);
tile.painter().drawRect(rect);
2018-11-22 21:57:21 +01:00
} else if (_layers.first().isBackground()) {
_layers.first().setPathPainter(tile);
2018-11-13 01:01:36 +01:00
tile.painter().drawPath(path);
}
2018-11-17 22:22:46 +01:00
//tile.painter().setPen(Qt::red);
//tile.painter().drawRect(rect);
2018-10-29 00:11:23 +01:00
}