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"
|
2018-11-06 22:35:30 +01:00
|
|
|
#include "font.h"
|
2018-10-29 00:11:23 +01:00
|
|
|
#include "tile.h"
|
2019-01-01 21:27:07 +01:00
|
|
|
#include "pbf.h"
|
2018-10-29 00:11:23 +01:00
|
|
|
#include "style.h"
|
|
|
|
|
|
|
|
|
2019-01-01 21:27:07 +01:00
|
|
|
static vector_tile::Tile_GeomType geometryType(const QString &str)
|
|
|
|
{
|
|
|
|
if (str == "Point")
|
|
|
|
return vector_tile::Tile_GeomType_POINT;
|
|
|
|
else if (str == "LineString")
|
|
|
|
return vector_tile::Tile_GeomType_LINESTRING;
|
|
|
|
else if (str == "Polygon")
|
|
|
|
return vector_tile::Tile_GeomType_POLYGON;
|
|
|
|
else
|
|
|
|
return vector_tile::Tile_GeomType_UNKNOWN;
|
|
|
|
}
|
|
|
|
|
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);
|
2019-01-01 21:27:07 +01:00
|
|
|
if (json.at(1).toString() == "$type") {
|
|
|
|
_type = GeometryType;
|
|
|
|
_kv = QPair<QString, QVariant>(QString(),
|
|
|
|
QVariant(geometryType(json.at(2).toString())));
|
|
|
|
} else {
|
|
|
|
_type = EQ;
|
|
|
|
_kv = QPair<QString, QVariant>(json.at(1).toString(),
|
|
|
|
json.at(2).toVariant());
|
|
|
|
}
|
2018-10-29 00:11:23 +01:00
|
|
|
} 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
|
|
|
}
|
|
|
|
|
2019-01-01 21:27:07 +01:00
|
|
|
bool Style::Layer::Filter::match(const PBF::Feature &feature) const
|
2018-10-29 00:11:23 +01:00
|
|
|
{
|
2019-01-01 21:27:07 +01:00
|
|
|
const QVariant *v;
|
|
|
|
|
2018-10-29 00:11:23 +01:00
|
|
|
switch (_type) {
|
|
|
|
case None:
|
|
|
|
return true;
|
|
|
|
case EQ:
|
2019-01-01 21:27:07 +01:00
|
|
|
if (!(v = feature.value(_kv.first)))
|
|
|
|
return false;
|
|
|
|
else
|
|
|
|
return *v == _kv.second;
|
2018-10-29 00:11:23 +01:00
|
|
|
case NE:
|
2019-01-01 21:27:07 +01:00
|
|
|
if (!(v = feature.value(_kv.first)))
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
return *v != _kv.second;
|
2018-10-29 00:11:23 +01:00
|
|
|
case GT:
|
2019-01-01 21:27:07 +01:00
|
|
|
if (!(v = feature.value(_kv.first)))
|
|
|
|
return false;
|
|
|
|
else
|
|
|
|
return *v > _kv.second;
|
2018-10-29 00:11:23 +01:00
|
|
|
case GE:
|
2019-01-01 21:27:07 +01:00
|
|
|
if (!(v = feature.value(_kv.first)))
|
|
|
|
return false;
|
|
|
|
else
|
|
|
|
return *v >= _kv.second;
|
2018-10-29 00:11:23 +01:00
|
|
|
case LT:
|
2019-01-01 21:27:07 +01:00
|
|
|
if (!(v = feature.value(_kv.first)))
|
|
|
|
return false;
|
|
|
|
else
|
|
|
|
return *v < _kv.second;
|
2018-10-29 00:11:23 +01:00
|
|
|
case LE:
|
2019-01-01 21:27:07 +01:00
|
|
|
if (!(v = feature.value(_kv.first)))
|
|
|
|
return false;
|
|
|
|
else
|
|
|
|
return *v <= _kv.second;
|
2018-10-29 00:11:23 +01:00
|
|
|
case In:
|
2019-01-01 21:27:07 +01:00
|
|
|
if (!(v = feature.value(_kv.first)))
|
|
|
|
return _not;
|
|
|
|
else
|
|
|
|
return _set.contains((*v).toString()) ^ _not;
|
2018-10-29 00:11:23 +01:00
|
|
|
case Has:
|
2019-01-01 21:27:07 +01:00
|
|
|
return (feature.value(_kv.first) ? true : false) ^ _not;
|
2018-10-29 00:11:23 +01:00
|
|
|
case All:
|
2019-01-01 21:27:07 +01:00
|
|
|
for (int i = 0; i < _filters.size(); i++)
|
|
|
|
if (!_filters.at(i).match(feature))
|
2018-10-29 00:11:23 +01:00
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
case Any:
|
2019-01-01 21:27:07 +01:00
|
|
|
for (int i = 0; i < _filters.size(); i++)
|
|
|
|
if (_filters.at(i).match(feature))
|
2018-10-29 00:11:23 +01:00
|
|
|
return true;
|
|
|
|
return false;
|
2019-01-01 21:27:07 +01:00
|
|
|
case GeometryType:
|
|
|
|
return feature.type() == _kv.second.toUInt();
|
2018-10-29 00:11:23 +01:00
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-01 21:27:07 +01:00
|
|
|
QString Style::Layer::Template::value(int zoom, const PBF::Feature &feature) const
|
2018-11-22 21:57:21 +01:00
|
|
|
{
|
2018-11-23 23:35:28 +01:00
|
|
|
QRegExp rx = QRegExp("\\{[^\\}]*\\}");
|
|
|
|
QString text(_field.value(zoom));
|
|
|
|
QStringList keys;
|
2018-11-22 21:57:21 +01:00
|
|
|
int pos = 0;
|
|
|
|
|
2018-11-23 23:35:28 +01:00
|
|
|
while ((pos = rx.indexIn(text, pos)) != -1) {
|
|
|
|
QString match = rx.capturedTexts().first();
|
|
|
|
keys.append(match.mid(1, match.size() - 2));
|
|
|
|
pos += rx.matchedLength();
|
2018-11-22 21:57:21 +01:00
|
|
|
}
|
2018-11-23 23:35:28 +01:00
|
|
|
for (int i = 0; i < keys.size(); i++) {
|
|
|
|
const QString &key = keys.at(i);
|
2019-01-01 21:27:07 +01:00
|
|
|
const QVariant *val = feature.value(key);
|
|
|
|
text.replace(QString("{%1}").arg(key), val ? val->toString() : "");
|
2018-11-22 21:57:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return text;
|
|
|
|
}
|
|
|
|
|
2018-10-29 00:11:23 +01:00
|
|
|
Style::Layer::Paint::Paint(const QJsonObject &json)
|
|
|
|
{
|
2018-11-13 00:07:51 +01:00
|
|
|
// fill
|
2018-11-24 09:52:54 +01:00
|
|
|
_fillOpacity = FunctionF(json["fill-opacity"], 1.0);
|
|
|
|
_fillColor = FunctionC(json["fill-color"]);
|
|
|
|
if (json.contains("fill-outline-color"))
|
|
|
|
_fillOutlineColor = FunctionC(json["fill-outline-color"]);
|
|
|
|
else
|
|
|
|
_fillOutlineColor = _fillColor;
|
|
|
|
_fillAntialias = FunctionB(json["fill-antialias"]);
|
2018-11-03 19:16:08 +01:00
|
|
|
if (json.contains("fill-pattern")) {
|
2018-11-24 09:52:54 +01:00
|
|
|
_fillPattern = FunctionS(json["fill-pattern"]);
|
2018-11-03 19:16:08 +01:00
|
|
|
_fillColor = FunctionC(QColor());
|
|
|
|
_fillOutlineColor = FunctionC(QColor());
|
|
|
|
}
|
|
|
|
|
2018-11-13 00:07:51 +01:00
|
|
|
// line
|
2018-11-24 09:52:54 +01:00
|
|
|
_lineColor = FunctionC(json["line-color"]);
|
|
|
|
_lineWidth = FunctionF(json["line-width"], 1.0);
|
|
|
|
_lineOpacity = FunctionF(json["line-opacity"], 1.0);
|
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
|
2018-11-24 09:52:54 +01:00
|
|
|
_backgroundColor = FunctionC(json["background-color"]);
|
2018-11-13 00:07:51 +01:00
|
|
|
|
|
|
|
// text
|
2018-11-24 09:52:54 +01:00
|
|
|
_textColor = FunctionC(json["text-color"]);
|
2019-04-27 22:52:18 +02:00
|
|
|
_textHaloColor = FunctionC(json["text-halo-color"], QColor());
|
|
|
|
_textHaloWidth = FunctionF(json["text-halo-width"]);
|
|
|
|
_textHaloBlur = FunctionF(json["text-halo-blur"]);
|
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;
|
|
|
|
}
|
|
|
|
|
2018-12-05 00:03:41 +01:00
|
|
|
QBrush Style::Layer::Paint::brush(Type type, int zoom, const Sprites &sprites)
|
|
|
|
const
|
2018-10-29 00:11:23 +01:00
|
|
|
{
|
2018-10-31 00:47:23 +01:00
|
|
|
QColor color;
|
2018-11-23 23:35:28 +01:00
|
|
|
QBrush brush(Qt::NoBrush);
|
|
|
|
QString pattern;
|
2018-10-31 00:47:23 +01:00
|
|
|
|
2018-10-29 00:11:23 +01:00
|
|
|
switch (type) {
|
|
|
|
case Fill:
|
2018-10-31 00:47:23 +01:00
|
|
|
color = _fillColor.value(zoom);
|
2018-11-23 23:35:28 +01:00
|
|
|
if (color.isValid())
|
|
|
|
brush = QBrush(color);
|
|
|
|
pattern = _fillPattern.value(zoom);
|
|
|
|
if (!pattern.isNull())
|
2018-12-05 00:03:41 +01:00
|
|
|
brush.setTextureImage(sprites.icon(pattern));
|
2018-11-23 23:35:28 +01:00
|
|
|
break;
|
2018-10-29 00:11:23 +01:00
|
|
|
case Background:
|
2018-10-31 00:47:23 +01:00
|
|
|
color = _backgroundColor.value(zoom);
|
2018-11-23 23:35:28 +01:00
|
|
|
if (color.isValid())
|
|
|
|
brush = QBrush(color);
|
|
|
|
pattern = _fillPattern.value(zoom);
|
|
|
|
if (!pattern.isNull())
|
2018-12-05 00:03:41 +01:00
|
|
|
brush.setTextureImage(sprites.icon(pattern));
|
2018-11-23 23:35:28 +01:00
|
|
|
break;
|
2018-10-29 00:11:23 +01:00
|
|
|
default:
|
2018-11-23 23:35:28 +01:00
|
|
|
break;
|
2018-10-29 00:11:23 +01:00
|
|
|
}
|
2018-11-23 23:35:28 +01:00
|
|
|
|
|
|
|
return brush;
|
2018-10-29 00:11:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-06 22:35:30 +01:00
|
|
|
bool Style::Layer::Paint::antialias(Layer::Type type, int zoom) const
|
2018-10-29 00:11:23 +01:00
|
|
|
{
|
|
|
|
switch (type) {
|
|
|
|
case Fill:
|
2018-11-06 22:35:30 +01:00
|
|
|
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-28 23:39:33 +01:00
|
|
|
: _lineCap(Qt::FlatCap), _lineJoin(Qt::MiterJoin), _font("Open Sans")
|
2018-10-29 00:11:23 +01:00
|
|
|
{
|
2018-11-13 18:24:51 +01:00
|
|
|
// line
|
2018-11-24 09:52:54 +01:00
|
|
|
_lineCap = FunctionS(json["line-cap"], "butt");
|
|
|
|
_lineJoin = FunctionS(json["line-join"], "miter");
|
2018-11-13 00:07:51 +01:00
|
|
|
|
2018-11-13 18:24:51 +01:00
|
|
|
// text
|
2018-11-24 09:52:54 +01:00
|
|
|
_text = Template(FunctionS(json["text-field"]));
|
2018-10-29 00:11:23 +01:00
|
|
|
|
2018-11-24 09:52:54 +01:00
|
|
|
_textSize = FunctionF(json["text-size"], 16);
|
|
|
|
_textMaxWidth = FunctionF(json["text-max-width"], 10);
|
|
|
|
_textMaxAngle = FunctionF(json["text-max-angle"], 45);
|
2018-11-24 10:14:23 +01:00
|
|
|
_textTransform = FunctionS(json["text-transform"], "none");
|
2018-11-28 23:39:33 +01:00
|
|
|
_textRotationAlignment = FunctionS(json["text-rotation-alignment"]);
|
|
|
|
_textAnchor = FunctionS(json["text-anchor"]);
|
2018-11-24 10:14:23 +01:00
|
|
|
|
2018-11-06 22:35:30 +01:00
|
|
|
if (json.contains("text-font") && json["text-font"].isArray())
|
|
|
|
_font = Font::fromJsonArray(json["text-font"].toArray());
|
2018-11-22 21:57:21 +01:00
|
|
|
|
|
|
|
// icon
|
2018-11-24 09:52:54 +01:00
|
|
|
_icon = Template(FunctionS(json["icon-image"]));
|
2018-11-28 23:39:33 +01:00
|
|
|
|
|
|
|
// symbol
|
|
|
|
_symbolPlacement = FunctionS(json["symbol-placement"]);
|
2018-10-29 00:11:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
QFont Style::Layer::Layout::font(int zoom) const
|
|
|
|
{
|
2018-11-06 22:35:30 +01:00
|
|
|
QFont font(_font);
|
2018-11-06 23:51:44 +01:00
|
|
|
font.setPixelSize(_textSize.value(zoom));
|
2018-11-25 11:47:14 +01:00
|
|
|
font.setCapitalization(textTransform(zoom));
|
2018-10-29 00:11:23 +01:00
|
|
|
|
|
|
|
return font;
|
|
|
|
}
|
|
|
|
|
2018-11-24 09:52:54 +01:00
|
|
|
Text::Anchor Style::Layer::Layout::textAnchor(int zoom) const
|
|
|
|
{
|
|
|
|
QString anchor(_textAnchor.value(zoom));
|
|
|
|
|
|
|
|
if (anchor == "left")
|
|
|
|
return Text::Left;
|
|
|
|
else if (anchor == "right")
|
|
|
|
return Text::Right;
|
|
|
|
else if (anchor == "top")
|
|
|
|
return Text::Top;
|
|
|
|
else if (anchor == "bottom")
|
|
|
|
return Text::Bottom;
|
|
|
|
else
|
|
|
|
return Text::Center;
|
|
|
|
}
|
|
|
|
|
2018-11-25 11:47:14 +01:00
|
|
|
QFont::Capitalization Style::Layer::Layout::textTransform(int zoom) const
|
2018-11-24 10:14:23 +01:00
|
|
|
{
|
|
|
|
QString transform(_textTransform.value(zoom));
|
|
|
|
|
|
|
|
if (transform == "uppercase")
|
2018-11-25 11:47:14 +01:00
|
|
|
return QFont::AllUppercase;
|
2018-11-24 10:14:23 +01:00
|
|
|
else if (transform == "lowercase")
|
2018-11-25 11:47:14 +01:00
|
|
|
return QFont::AllLowercase;
|
2018-11-24 10:14:23 +01:00
|
|
|
else
|
2018-11-25 11:47:14 +01:00
|
|
|
return QFont::MixedCase;
|
2018-11-24 10:14:23 +01:00
|
|
|
}
|
|
|
|
|
2018-11-24 09:52:54 +01:00
|
|
|
Qt::PenCapStyle Style::Layer::Layout::lineCap(int zoom) const
|
|
|
|
{
|
|
|
|
QString cap(_lineCap.value(zoom));
|
|
|
|
|
|
|
|
if (cap == "round")
|
|
|
|
return Qt::RoundCap;
|
|
|
|
else if (cap == "square")
|
|
|
|
return Qt::SquareCap;
|
|
|
|
else
|
|
|
|
return Qt::FlatCap;
|
|
|
|
}
|
|
|
|
|
|
|
|
Qt::PenJoinStyle Style::Layer::Layout::lineJoin(int zoom) const
|
|
|
|
{
|
|
|
|
QString join(_lineJoin.value(zoom));
|
|
|
|
|
|
|
|
if (join == "bevel")
|
|
|
|
return Qt::BevelJoin;
|
|
|
|
else if (join == "round")
|
|
|
|
return Qt::RoundJoin;
|
|
|
|
else
|
|
|
|
return Qt::MiterJoin;
|
|
|
|
}
|
|
|
|
|
2018-11-28 23:39:33 +01:00
|
|
|
Text::SymbolPlacement Style::Layer::Layout::symbolPlacement(int zoom) const
|
|
|
|
{
|
|
|
|
QString placement(_symbolPlacement.value(zoom));
|
|
|
|
|
|
|
|
if (placement == "line")
|
|
|
|
return Text::Line;
|
|
|
|
else if (placement == "line-center")
|
|
|
|
return Text::LineCenter;
|
|
|
|
else
|
|
|
|
return Text::Point;
|
|
|
|
}
|
|
|
|
|
|
|
|
Text::RotationAlignment Style::Layer::Layout::textRotationAlignment(int zoom)
|
|
|
|
const
|
|
|
|
{
|
|
|
|
QString alignment(_textRotationAlignment.value(zoom));
|
|
|
|
|
|
|
|
if (alignment == "map")
|
|
|
|
return Text::Map;
|
|
|
|
else if (alignment == "viewport")
|
|
|
|
return Text::Viewport;
|
|
|
|
else
|
|
|
|
return Text::Auto;
|
|
|
|
}
|
|
|
|
|
2018-10-29 00:11:23 +01:00
|
|
|
Style::Layer::Layer(const QJsonObject &json)
|
2019-10-02 23:14:20 +02:00
|
|
|
: _type(Unknown), _minZoom(0), _maxZoom(22)
|
2018-10-29 00:11:23 +01:00
|
|
|
{
|
|
|
|
// type
|
2018-11-29 23:01:06 +01:00
|
|
|
QString type(json["type"].toString());
|
|
|
|
|
2018-10-29 00:11:23 +01:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2019-01-01 21:27:07 +01:00
|
|
|
bool Style::Layer::match(int zoom, const PBF::Feature &feature) const
|
2018-10-29 00:11:23 +01:00
|
|
|
{
|
2019-10-02 23:14:20 +02:00
|
|
|
if (zoom >= 0 && (zoom < _minZoom || zoom > _maxZoom))
|
|
|
|
return false;
|
2018-10-29 00:11:23 +01:00
|
|
|
|
2019-01-01 21:27:07 +01:00
|
|
|
return _filter.match(feature);
|
2018-10-29 00:11:23 +01:00
|
|
|
}
|
|
|
|
|
2018-11-23 23:35:28 +01:00
|
|
|
void Style::Layer::setPathPainter(Tile &tile, const Sprites &sprites) const
|
2018-10-29 00:11:23 +01:00
|
|
|
{
|
2018-11-29 23:01:06 +01:00
|
|
|
QPainter &p = tile.painter();
|
|
|
|
int zoom = tile.zoom();
|
2018-10-31 00:47:23 +01:00
|
|
|
|
2018-11-29 23:01:06 +01:00
|
|
|
QPen pen(_paint.pen(_type, zoom));
|
|
|
|
pen.setJoinStyle(_layout.lineJoin(zoom));
|
|
|
|
pen.setCapStyle(_layout.lineCap(zoom));
|
2018-10-29 00:11:23 +01:00
|
|
|
|
2018-12-05 00:03:41 +01:00
|
|
|
QBrush brush(_paint.brush(_type, zoom, sprites));
|
2018-11-29 23:01:06 +01:00
|
|
|
|
|
|
|
p.setRenderHint(QPainter::Antialiasing, _paint.antialias(_type, 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-29 23:01:06 +01:00
|
|
|
p.setOpacity(_paint.opacity(_type, zoom));
|
2018-10-29 00:11:23 +01:00
|
|
|
}
|
|
|
|
|
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-29 23:01:06 +01:00
|
|
|
Text &t = tile.text();
|
|
|
|
int zoom = tile.zoom();
|
|
|
|
|
|
|
|
t.setMaxWidth(_layout.maxTextWidth(zoom));
|
|
|
|
t.setMaxAngle(_layout.maxTextAngle(zoom));
|
|
|
|
t.setAnchor(_layout.textAnchor(zoom));
|
|
|
|
t.setPen(_paint.pen(_type, zoom));
|
|
|
|
t.setFont(_layout.font(zoom));
|
|
|
|
t.setSymbolPlacement(_layout.symbolPlacement(zoom));
|
|
|
|
t.setRotationAlignment(_layout.textRotationAlignment(zoom));
|
2019-04-27 22:52:18 +02:00
|
|
|
t.setHalo(_paint.halo(zoom));
|
2018-11-22 21:57:21 +01:00
|
|
|
}
|
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,
|
2019-01-01 21:27:07 +01:00
|
|
|
const PBF::Feature &feature, const Sprites &sprites) const
|
2018-11-22 21:57:21 +01:00
|
|
|
{
|
2019-01-01 21:27:07 +01:00
|
|
|
QString text = _layout.text(tile.zoom(), feature);
|
2018-11-25 12:46:38 +01:00
|
|
|
if (text.isEmpty())
|
2018-11-03 21:05:28 +01:00
|
|
|
return;
|
2018-11-22 21:57:21 +01:00
|
|
|
|
2019-01-01 21:27:07 +01:00
|
|
|
QString icon = _layout.icon(tile.zoom(), feature);
|
2018-12-05 00:03:41 +01:00
|
|
|
tile.text().addLabel(text, sprites.icon(icon), path);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool loadSprites(const QDir &styleDir, const QString &json,
|
|
|
|
const QString &img, Sprites &sprites)
|
|
|
|
{
|
|
|
|
QString spritesJSON(styleDir.filePath(json));
|
|
|
|
|
|
|
|
if (QFileInfo::exists(spritesJSON)) {
|
|
|
|
QString spritesImg(styleDir.filePath(img));
|
|
|
|
if (QFileInfo::exists(spritesImg))
|
|
|
|
return sprites.load(spritesJSON, spritesImg);
|
|
|
|
else {
|
|
|
|
qCritical() << spritesImg << ": no such file";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2018-10-29 00:11:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Style::load(const QString &fileName)
|
|
|
|
{
|
|
|
|
QFile file(fileName);
|
2018-11-01 18:15:05 +01:00
|
|
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
|
|
qCritical() << fileName << ": error opening file";
|
2018-10-29 00:11:23 +01:00
|
|
|
return false;
|
2018-11-01 18:15:05 +01:00
|
|
|
}
|
2018-10-29 00:11:23 +01:00
|
|
|
QByteArray ba(file.readAll());
|
|
|
|
file.close();
|
|
|
|
|
2018-11-01 18:15:05 +01:00
|
|
|
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
|
|
|
QDir styleDir = QFileInfo(fileName).absoluteDir();
|
2018-12-05 00:03:41 +01:00
|
|
|
loadSprites(styleDir, "sprite.json", "sprite.png", _sprites);
|
2018-12-17 18:31:26 +01:00
|
|
|
#ifdef ENABLE_HIDPI
|
2018-12-05 00:03:41 +01:00
|
|
|
loadSprites(styleDir, "sprite@2x.json", "sprite@2x.png", _sprites2x);
|
2018-12-17 18:31:26 +01:00
|
|
|
#endif // ENABLE_HIDPI
|
2018-12-04 00:09:23 +01:00
|
|
|
|
2018-10-29 00:11:23 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-12-17 18:31:26 +01:00
|
|
|
const Sprites &Style::sprites(const QPointF &scale) const
|
|
|
|
{
|
|
|
|
#ifdef ENABLE_HIDPI
|
|
|
|
return (scale.x() > 1.0 || scale.y() > 1.0)
|
|
|
|
&& !_sprites2x.isNull() ? _sprites2x : _sprites;
|
|
|
|
#else // ENABLE_HIDPI
|
|
|
|
Q_UNUSED(scale);
|
|
|
|
return _sprites;
|
|
|
|
#endif // ENABLE_HIDPI
|
|
|
|
}
|
|
|
|
|
2019-01-01 21:27:07 +01:00
|
|
|
void Style::setupLayer(Tile &tile, const Layer &layer) const
|
2018-10-29 00:11:23 +01:00
|
|
|
{
|
2019-01-01 21:27:07 +01:00
|
|
|
if (layer.isSymbol())
|
|
|
|
layer.setTextProperties(tile);
|
|
|
|
else if (layer.isPath())
|
|
|
|
layer.setPathPainter(tile, sprites(tile.scale()));
|
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
|
|
|
{
|
2018-11-12 22:05:55 +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()) {
|
2018-11-23 23:35:28 +01:00
|
|
|
_layers.first().setPathPainter(tile, _sprites);
|
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
|
|
|
}
|
2019-01-01 21:27:07 +01:00
|
|
|
|
|
|
|
void Style::drawFeature(const PBF::Feature &feature, const Layer &layer,
|
|
|
|
Tile &tile, const QSizeF &factor) const
|
|
|
|
{
|
|
|
|
if (!layer.match(tile.zoom(), feature))
|
|
|
|
return;
|
|
|
|
|
|
|
|
QPainterPath path(feature.path(factor));
|
|
|
|
if (!path.elementCount())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (layer.isPath())
|
|
|
|
tile.painter().drawPath(path);
|
|
|
|
else if (layer.isSymbol())
|
|
|
|
layer.addSymbol(tile, path, feature, sprites(tile.scale()));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Style::drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer,
|
|
|
|
Tile &tile) const
|
|
|
|
{
|
|
|
|
if (pbfLayer.data()->version() > 2)
|
|
|
|
return;
|
|
|
|
|
|
|
|
QSizeF factor(tile.size().width() / tile.scale().x() /
|
|
|
|
(qreal)pbfLayer.data()->extent(), tile.size().height() / tile.scale().y()
|
|
|
|
/ (qreal)pbfLayer.data()->extent());
|
|
|
|
|
|
|
|
tile.painter().save();
|
|
|
|
setupLayer(tile, styleLayer);
|
|
|
|
for (int i = 0; i < pbfLayer.features().size(); i++)
|
|
|
|
drawFeature(pbfLayer.features().at(i), styleLayer, tile, factor);
|
|
|
|
tile.painter().restore();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Style::render(const PBF &data, Tile &tile) const
|
|
|
|
{
|
|
|
|
drawBackground(tile);
|
|
|
|
|
|
|
|
for (int i = 0; i < _layers.size(); i++) {
|
|
|
|
QHash<QString, PBF::Layer*>::const_iterator it = data.layers().find(
|
|
|
|
_layers.at(i).sourceLayer());
|
|
|
|
if (it == data.layers().constEnd())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
drawLayer(**it, _layers.at(i), tile);
|
|
|
|
}
|
|
|
|
|
|
|
|
tile.text().render(&tile.painter());
|
|
|
|
}
|