diff --git a/pbfplugin.pro b/pbfplugin.pro
index 7badc33..7321506 100644
--- a/pbfplugin.pro
+++ b/pbfplugin.pro
@@ -19,7 +19,8 @@ HEADERS += src/pbfhandler.h \
src/textpathitem.h \
src/textpointitem.h \
src/font.h \
- src/textitem.h
+ src/textitem.h \
+ src/sprites.h
SOURCES += src/pbfplugin.cpp \
src/pbfhandler.cpp \
src/gzip.cpp \
@@ -30,7 +31,8 @@ SOURCES += src/pbfplugin.cpp \
src/function.cpp \
src/textpathitem.cpp \
src/textpointitem.cpp \
- src/font.cpp
+ src/font.cpp \
+ src/sprites.cpp
RESOURCES += pbfplugin.qrc
unix:!macx{
diff --git a/pbfplugin.qrc b/pbfplugin.qrc
index ed8fc7b..11c11d4 100644
--- a/pbfplugin.qrc
+++ b/pbfplugin.qrc
@@ -1,5 +1,7 @@
style/style.json
+ style/sprite.json
+ style/sprite.png
diff --git a/src/pbf.cpp b/src/pbf.cpp
index acc340e..971a441 100644
--- a/src/pbf.cpp
+++ b/src/pbf.cpp
@@ -116,7 +116,7 @@ static inline QPoint parameters(quint32 v1, quint32 v2)
static void drawFeature(const Feature &feature, Style *style, int styleLayer,
const QSizeF &factor, Tile &tile)
{
- if (!style->match(styleLayer, feature.tags()))
+ if (!style->match(tile.zoom(), styleLayer, feature.tags()))
return;
QPoint cursor;
@@ -151,7 +151,7 @@ static void drawFeature(const Feature &feature, Style *style, int styleLayer,
}
}
- style->drawFeature(styleLayer, path, feature.tags(), tile);
+ style->drawFeature(tile, styleLayer, path, feature.tags());
}
static void drawLayer(const Layer &layer, Style *style, int styleLayer,
@@ -163,7 +163,9 @@ static void drawLayer(const Layer &layer, Style *style, int styleLayer,
QSizeF factor(tile.size().width() / scale.x() / (qreal)layer.data()->extent(),
tile.size().height() / scale.y() / (qreal)layer.data()->extent());
- style->setPainter(styleLayer, tile);
+ style->setPainter(tile, styleLayer);
+ style->setTextProperties(tile, styleLayer);
+
for (int i = 0; i < layer.features().size(); i++)
drawFeature(layer.features().at(i), style, styleLayer, factor, tile);
}
@@ -177,9 +179,8 @@ bool PBF::render(const QByteArray &data, int zoom, Style *style,
return false;
}
- Tile t(image, scale);
+ Tile t(image, zoom, scale);
- style->setZoom(zoom);
style->drawBackground(t);
// Prepare source layers
diff --git a/src/sprites.cpp b/src/sprites.cpp
new file mode 100644
index 0000000..69e8fc0
--- /dev/null
+++ b/src/sprites.cpp
@@ -0,0 +1,91 @@
+#include
+#include
+#include
+#include
+#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 QImage &atlas(const QString &fileName)
+{
+ static QImage *img = new QImage(fileName);
+ return *img;
+}
+
+Sprites::Sprite::Sprite(const QJsonObject &json)
+{
+ int x, y, width, height;
+
+ if (json.contains("x") && json["x"].isDouble())
+ x = json["x"].toInt();
+ else
+ return;
+ if (json.contains("y") && json["y"].isDouble())
+ y = json["y"].toInt();
+ else
+ return;
+ if (json.contains("width") && json["width"].isDouble())
+ width = json["width"].toInt();
+ else
+ return;
+ if (json.contains("height") && json["height"].isDouble())
+ height = json["height"].toInt();
+ else
+ return;
+
+ _rect = QRect(x, y, width, height);
+}
+
+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";
+ return false;
+ }
+ QByteArray ba(file.readAll());
+
+ QJsonParseError error;
+ QJsonDocument doc(QJsonDocument::fromJson(ba, &error));
+ if (doc.isNull()) {
+ qCritical() << jsonFile << ":" << error.errorString();
+ return false;
+ }
+
+ QJsonObject json(doc.object());
+ for (QJsonObject::const_iterator it = json.constBegin();
+ it != json.constEnd(); it++) {
+ QJsonValue val(*it);
+ if (val.isObject())
+ _sprites.insert(it.key(), Sprite(val.toObject()));
+ else
+ qWarning() << it.key() << ": invalid sprite definition";
+ }
+
+ return true;
+}
+
+QImage Sprites::icon(const QString &name) const
+{
+ if (_imageFile.isNull())
+ return QImage();
+ const QImage &img = atlas(_imageFile);
+ if (img.isNull())
+ return QImage();
+
+ QMap::const_iterator it = _sprites.find(name);
+ if (it == _sprites.constEnd())
+ return QImage();
+
+ if (!img.rect().contains(it->rect())) {
+ qWarning() << it->rect() << ": invalid sprite rect";
+ return QImage();
+ }
+
+ return img.copy(it->rect());
+}
diff --git a/src/sprites.h b/src/sprites.h
new file mode 100644
index 0000000..a029f58
--- /dev/null
+++ b/src/sprites.h
@@ -0,0 +1,29 @@
+#ifndef SPRITES_H
+#define SPRITES_H
+
+#include
+#include
+#include
+
+class Sprites
+{
+public:
+ bool load(const QString &jsonFile, const QString &imageFile);
+
+ QImage icon(const QString &name) const;
+
+private:
+ class Sprite {
+ public:
+ Sprite(const QJsonObject &json);
+ const QRect &rect() const {return _rect;}
+
+ private:
+ QRect _rect;
+ };
+
+ QMap _sprites;
+ QString _imageFile;
+};
+
+#endif // SPRITES_H
diff --git a/src/style.cpp b/src/style.cpp
index 87b7981..c591f98 100644
--- a/src/style.cpp
+++ b/src/style.cpp
@@ -3,6 +3,8 @@
#include
#include
#include
+#include
+#include
#include
#include "text.h"
#include "color.h"
@@ -168,6 +170,32 @@ bool Style::Layer::Filter::match(const QVariantHash &tags) const
}
}
+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("\\{[^\\}]*\\}");
+
Style::Layer::Paint::Paint(const QJsonObject &json)
: _fillOpacity(1.0), _lineOpacity(1.0), _lineWidth(1.0)
{
@@ -275,7 +303,7 @@ bool Style::Layer::Paint::antialias(Layer::Type type, int zoom) const
Style::Layer::Layout::Layout(const QJsonObject &json)
: _textSize(16), _textMaxWidth(10), _textMaxAngle(45), _lineCap(Qt::FlatCap),
_lineJoin(Qt::MiterJoin), _font("Open Sans"), _capitalize(false),
- _viewportAlignment(false)
+ _viewportAlignment(false), _textAnchor(Text::Center)
{
// line
if (json.contains("line-cap") && json["line-cap"].isString()) {
@@ -292,17 +320,8 @@ Style::Layer::Layout::Layout(const QJsonObject &json)
}
// text
- if (!(json.contains("text-field") && json["text-field"].isString()))
- return;
- _textField = json["text-field"].toString();
-
- QRegExp rx("\\{[^\\}]*\\}");
- int pos = 0;
- while ((pos = rx.indexIn(_textField, pos)) != -1) {
- QString match = rx.capturedTexts().first();
- _keys.append(match.mid(1, match.size() - 2));
- pos += rx.matchedLength();
- }
+ if (json.contains("text-field") && json["text-field"].isString())
+ _text = Template(json["text-field"].toString());
jsonFloat(json, "text-size", _textSize);
jsonFloat(json, "text-max-width", _textMaxWidth);
@@ -311,11 +330,27 @@ Style::Layer::Layout::Layout(const QJsonObject &json)
_font = Font::fromJsonArray(json["text-font"].toArray());
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;
+ 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());
}
QFont Style::Layer::Layout::font(int zoom) const
@@ -374,58 +409,61 @@ bool Style::Layer::match(int zoom, const QVariantHash &tags) const
return _filter.match(tags);
}
-void Style::Layer::setPathPainter(int zoom, Tile &tile) const
+void Style::Layer::setPathPainter(Tile &tile) const
{
- QPen pen(_paint.pen(_type, zoom));
- QBrush brush(_paint.brush(_type, zoom));
+ QPen pen(_paint.pen(_type, tile.zoom()));
+ QBrush brush(_paint.brush(_type, tile.zoom()));
pen.setJoinStyle(_layout.lineJoin());
pen.setCapStyle(_layout.lineCap());
QPainter &p = tile.painter();
- p.setRenderHint(QPainter::Antialiasing, _paint.antialias(_type, zoom));
+ p.setRenderHint(QPainter::Antialiasing, _paint.antialias(_type, tile.zoom()));
p.setPen(pen);
p.setBrush(brush);
- p.setOpacity(_paint.opacity(_type, zoom));
+ p.setOpacity(_paint.opacity(_type, tile.zoom()));
}
-void Style::Layer::setSymbolPainter(int zoom, Tile &tile) const
+void Style::Layer::setSymbolPainter(Tile &tile) const
{
- QPen pen(_paint.pen(_type, zoom));
- QFont font(_layout.font(zoom));
+ QPen pen(_paint.pen(_type, tile.zoom()));
+ QFont font(_layout.font(tile.zoom()));
QPainter &p = tile.painter();
p.setPen(pen);
p.setFont(font);
}
-void Style::Layer::addSymbol(int zoom, const QPainterPath &path,
- const QVariantHash &tags, Tile &tile) const
+void Style::Layer::setTextProperties(Tile &tile) const
{
- if (_layout.keys().isEmpty())
- return;
+ Text::Properties prop;
+ prop.maxWidth = _layout.maxTextWidth(tile.zoom());
+ prop.maxAngle = _layout.maxTextAngle(tile.zoom());
+ prop.anchor = _layout.textAnchor();
- QString text(_layout.field());
- for (int i = 0; i < _layout.keys().size(); i++) {
- const QString &key = _layout.keys().at(i);
- const QVariant val = tags.value(key);
- text.replace(QString("{%1}").arg(key), _layout.capitalize()
- ? val.toString().toUpper() : val.toString());
- }
+ tile.text().setProperties(prop);
+}
+void Style::Layer::addSymbol(Tile &tile, const QPainterPath &path,
+ const QVariantHash &tags, const Sprites &sprites) const
+{
+ QString text = _layout.text().value(tags);
QString tt(text.trimmed());
if (tt.isEmpty())
return;
+ if (_layout.capitalize())
+ tt = tt.toUpper();
+
+ QString icon = _layout.icon().value(tags);
if (_layout.viewportAlignment())
- tile.text().addLabel(tt, path.elementAt(0), tile.painter(),
- _layout.maxTextWidth(zoom), false);
+ tile.text().addLabel(tt, path.elementAt(0), tile.painter(), false,
+ sprites.icon(icon));
else if (path.elementCount() == 1 && path.elementAt(0).isMoveTo())
- tile.text().addLabel(tt, path.elementAt(0), tile.painter(),
- _layout.maxTextWidth(zoom), true);
+ tile.text().addLabel(tt, path.elementAt(0), tile.painter(), true,
+ sprites.icon(icon));
else
- tile.text().addLabel(tt, path, tile.painter(),
- _layout.maxTextAngle(zoom));
+ tile.text().addLabel(tt, path, tile.painter());
}
bool Style::load(const QString &fileName)
@@ -450,53 +488,69 @@ bool Style::load(const QString &fileName)
QJsonArray layers = json["layers"].toArray();
for (int i = 0; i < layers.size(); i++)
if (layers[i].isObject())
- _styles.append(Layer(layers[i].toObject()));
+ _layers.append(Layer(layers[i].toObject()));
}
- for (int i = 0; i < _styles.size(); i++)
- _sourceLayers.append(_styles.at(i).sourceLayer());
+ 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";
+ }
return true;
}
-bool Style::match(int layer, const QVariantHash &tags)
+bool Style::match(int zoom, int layer, const QVariantHash &tags) const
{
- return _styles.at(layer).match(_zoom, tags);
+ return _layers.at(layer).match(zoom, tags);
}
-void Style::setPainter(int layer, Tile &tile)
+void Style::setTextProperties(Tile &tile, int layer) const
{
- const Layer &sl = _styles.at(layer);
+ const Layer &sl = _layers.at(layer);
+ sl.setTextProperties(tile);
+}
+
+void Style::setPainter(Tile &tile, int layer) const
+{
+ const Layer &sl = _layers.at(layer);
if (sl.isPath())
- sl.setPathPainter(_zoom, tile);
+ sl.setPathPainter(tile);
else if (sl.isSymbol())
- sl.setSymbolPainter(_zoom, tile);
+ sl.setSymbolPainter(tile);
}
-void Style::drawFeature(int layer, const QPainterPath &path,
- const QVariantHash &tags, Tile &tile)
+void Style::drawFeature(Tile &tile, int layer, const QPainterPath &path,
+ const QVariantHash &tags) const
{
- const Layer &sl = _styles.at(layer);
+ const Layer &sl = _layers.at(layer);
if (sl.isPath())
tile.painter().drawPath(path);
else if (sl.isSymbol())
- sl.addSymbol(_zoom, path, tags, tile);
+ sl.addSymbol(tile, path, tags, _sprites);
}
-void Style::drawBackground(Tile &tile)
+void Style::drawBackground(Tile &tile) const
{
QRectF rect(QPointF(0, 0), tile.size());
QPainterPath path;
path.addRect(rect);
- if (_styles.isEmpty()) {
+ if (_layers.isEmpty()) {
tile.painter().setBrush(Qt::lightGray);
tile.painter().setPen(Qt::NoPen);
tile.painter().drawRect(rect);
- } else if (_styles.first().isBackground()) {
- _styles.first().setPathPainter(_zoom, tile);
+ } else if (_layers.first().isBackground()) {
+ _layers.first().setPathPainter(tile);
tile.painter().drawPath(path);
}
diff --git a/src/style.h b/src/style.h
index 1b84b25..1c292d3 100644
--- a/src/style.h
+++ b/src/style.h
@@ -9,7 +9,9 @@
#include
#include
#include
+#include "text.h"
#include "function.h"
+#include "sprites.h"
class QPainter;
@@ -23,15 +25,15 @@ public:
bool load(const QString &fileName);
- void setZoom(int zoom) {_zoom = zoom;}
-
const QStringList &sourceLayers() const {return _sourceLayers;}
- bool match(int layer, const QVariantHash &tags);
- void drawBackground(Tile &tile);
- void setPainter(int layer, Tile &tile);
- void drawFeature(int layer, const QPainterPath &path,
- const QVariantHash &tags, Tile &tile);
+ bool match(int zoom, int layer, const QVariantHash &tags) const;
+
+ void drawBackground(Tile &tile) const;
+ void setPainter(Tile &tile, int layer) const;
+ void setTextProperties(Tile &tile, int layer) const;
+ void drawFeature(Tile &tile, int layer, const QPainterPath &path,
+ const QVariantHash &tags) const;
private:
class Layer {
@@ -45,10 +47,11 @@ private:
bool isSymbol() const {return (_type == Symbol);}
bool match(int zoom, const QVariantHash &tags) const;
- void setPathPainter(int zoom, Tile &tile) const;
- void setSymbolPainter(int zoom, Tile &tile) const;
- void addSymbol(int zoom, const QPainterPath &path,
- const QVariantHash &tags, Tile &tile) const;
+ void setPathPainter(Tile &tile) const;
+ void setSymbolPainter(Tile &tile) const;
+ void setTextProperties(Tile &tile) const;
+ void addSymbol(Tile &tile, const QPainterPath &path,
+ const QVariantHash &tags, const Sprites &sprites) const;
private:
enum Type {
@@ -80,12 +83,25 @@ private:
QVector _filters;
};
+ class Template {
+ public:
+ Template() {}
+ Template(const QString &str);
+
+ QString value(const QVariantHash &tags) const;
+
+ private:
+ static QRegExp _rx;
+ QStringList _keys;
+ QString _field;
+ };
+
class Layout {
public:
Layout() : _textSize(16), _textMaxWidth(10), _textMaxAngle(45),
_lineCap(Qt::FlatCap), _lineJoin(Qt::MiterJoin),
- _font("Open Sans"), _capitalize(false), _viewportAlignment(false)
- {}
+ _font("Open Sans"), _capitalize(false), _viewportAlignment(false),
+ _textAnchor(Text::Center) {}
Layout(const QJsonObject &json);
bool capitalize() const {return _capitalize;}
@@ -93,16 +109,17 @@ private:
{return _textMaxWidth.value(zoom);}
qreal maxTextAngle(int zoom) const
{return _textMaxAngle.value(zoom);}
- const QString &field() const {return _textField;}
- const QStringList &keys() const {return _keys;}
+ const Template &text() const {return _text;}
+ const Template &icon() const {return _icon;}
QFont font(int zoom) const;
Qt::PenCapStyle lineCap() const {return _lineCap;}
Qt::PenJoinStyle lineJoin() const {return _lineJoin;}
bool viewportAlignment() const {return _viewportAlignment;}
+ Text::Anchor textAnchor() const {return _textAnchor;}
private:
- QStringList _keys;
- QString _textField;
+ Template _text;
+ Template _icon;
FunctionF _textSize;
FunctionF _textMaxWidth;
FunctionF _textMaxAngle;
@@ -111,6 +128,7 @@ private:
QFont _font;
bool _capitalize;
bool _viewportAlignment;
+ Text::Anchor _textAnchor;
};
class Paint {
@@ -144,9 +162,9 @@ private:
Paint _paint;
};
- int _zoom;
- QVector _styles;
+ QVector _layers;
QStringList _sourceLayers;
+ Sprites _sprites;
};
#endif // STYLE_H
diff --git a/src/text.cpp b/src/text.cpp
index bbb5e52..4bcc202 100644
--- a/src/text.cpp
+++ b/src/text.cpp
@@ -150,28 +150,27 @@ void Text::render(QPainter *painter) const
}
void Text::addLabel(const QString &text, const QPointF &pos,
- const QPainter &painter, qreal maxTextWidth, bool overlap)
+ const QPainter &painter, bool overlap, const QImage &icon)
{
if (text.isEmpty())
return;
- TextPointItem *ti;
-
- ti = new TextPointItem(text, pos, painter.font(), maxTextWidth);
+ TextPointItem *ti = new TextPointItem(text, pos, painter.font(),
+ _properties, icon);
if (!overlap && !_sceneRect.contains(ti->boundingRect())) {
delete ti;
return;
}
-
ti->setPen(painter.pen());
addItem(ti);
+
QList ci = collidingItems(ti);
for (int i = 0; i < ci.size(); i++)
ci[i]->setVisible(false);
}
void Text::addLabel(const QString &text, const QPainterPath &path,
- const QPainter &painter, qreal maxAngle)
+ const QPainter &painter)
{
if (path.isEmpty())
return;
@@ -182,7 +181,7 @@ void Text::addLabel(const QString &text, const QPainterPath &path,
if (textWidth > path.length())
return;
- QPainterPath tp(textPath(path, textWidth, maxAngle,
+ QPainterPath tp(textPath(path, textWidth, _properties.maxAngle,
painter.font().pixelSize() / 2, _sceneRect));
if (tp.isEmpty())
return;
diff --git a/src/text.h b/src/text.h
index 26c84a9..99c3107 100644
--- a/src/text.h
+++ b/src/text.h
@@ -1,20 +1,40 @@
#ifndef TEXT_H
#define TEXT_H
-#include "textitem.h"
+#include
+
+class TextItem;
class Text
{
public:
+ enum Anchor {
+ Center,
+ Left,
+ Right,
+ Top,
+ Bottom
+ };
+
+ struct Properties {
+ int maxWidth;
+ int maxAngle;
+ Anchor anchor;
+ };
+
+
Text(const QSize &size) : _sceneRect(QRectF(QPointF(0, 0), size)) {}
~Text();
- void render(QPainter *painter) const;
+ void setProperties(const Properties &prop)
+ {_properties = prop;}
void addLabel(const QString &text, const QPointF &pos,
- const QPainter &painter, qreal maxTextWidth, bool overlap);
+ const QPainter &painter, bool overlap, const QImage &icon);
void addLabel(const QString &text, const QPainterPath &path,
- const QPainter &painter, qreal maxAngle);
+ const QPainter &painter);
+
+ void render(QPainter *painter) const;
private:
void addItem(TextItem *item) {_items.append(item);}
@@ -22,6 +42,7 @@ private:
QRectF _sceneRect;
QList _items;
+ Properties _properties;
};
#endif // TEXT_H
diff --git a/src/textpointitem.cpp b/src/textpointitem.cpp
index ac97bed..a663e43 100644
--- a/src/textpointitem.cpp
+++ b/src/textpointitem.cpp
@@ -1,13 +1,11 @@
-#include
+#include
#include
#include "textpointitem.h"
#define FLAGS (Qt::AlignCenter | Qt::TextWordWrap | Qt::TextDontClip)
-#ifdef USE_EXACT_TEXT_RECT
-
-static QRectF textBoundingRect(const QString &str, const QFont &font,
+static QRectF exactBoundingRect(const QString &str, const QFont &font,
int maxTextWidth)
{
QFontMetrics fm(font);
@@ -26,8 +24,7 @@ static QRectF textBoundingRect(const QString &str, const QFont &font,
return br;
}
-#else // USE_EXACT_TEXT_RECT
-static QRectF textBoundingRect(const QString &str, const QFont &font,
+static QRectF fuzzyBoundingRect(const QString &str, const QFont &font,
int maxTextWidth)
{
int limit = font.pixelSize() * maxTextWidth;
@@ -67,23 +64,68 @@ static QRectF textBoundingRect(const QString &str, const QFont &font,
return QRectF(0, 0, width, lines * lh);
}
-#endif // USE_EXACT_TEXT_RECT
+
+
+QRectF TextPointItem::computeTextRect(BoundingRectFunction brf) const
+{
+ QRectF iconRect = _icon.isNull() ? QRectF() : _icon.rect();
+ QRectF textRect = brf(text(), _font, _properties.maxWidth);
+
+ switch (_properties.anchor) {
+ case Text::Center:
+ textRect.moveCenter(_pos);
+ break;
+ case Text::Left:
+ textRect.moveTopLeft(_pos - QPointF(-iconRect.width() / 2,
+ textRect.height() / 2));
+ break;
+ case Text::Right:
+ textRect.moveTopRight(_pos - QPointF(iconRect.width() / 2,
+ textRect.height() / 2));
+ break;
+ case Text::Bottom:
+ textRect.moveTopLeft(_pos - QPointF(textRect.width() / 2,
+ iconRect.height() / 2));
+ break;
+ case Text::Top:
+ textRect.moveTopLeft(_pos - QPointF(textRect.width() / 2,
+ -iconRect.height() / 2));
+ break;
+ }
+
+ return textRect;
+}
TextPointItem::TextPointItem(const QString &text, const QPointF &pos,
- const QFont &font, int maxTextWidth) : TextItem(text), _font(font)
+ const QFont &font, const Text::Properties &prop, const QImage &icon)
+ : TextItem(text), _pos(pos), _font(font), _icon(icon), _properties(prop)
{
- _boundingRect = textBoundingRect(text, font, maxTextWidth);
+ _boundingRect = computeTextRect(fuzzyBoundingRect);
+
+ if (!_icon.isNull()) {
+ QRectF iconRect(_icon.rect());
+ iconRect.moveCenter(pos);
+ _boundingRect |= iconRect;
+ }
- _boundingRect.moveCenter(pos);
_shape.addRect(_boundingRect);
}
void TextPointItem::paint(QPainter *painter) const
{
- painter->setFont(_font);
- painter->setPen(_pen);
- painter->drawText(_boundingRect, FLAGS, text());
-
//painter->setPen(Qt::red);
//painter->drawRect(_boundingRect);
+
+ QRectF textRect;
+
+ if (!_icon.isNull()) {
+ textRect = computeTextRect(exactBoundingRect);
+ painter->drawImage(_pos - QPointF(_icon.width() / 2,
+ _icon.height() / 2), _icon);
+ } else
+ textRect = computeTextRect(fuzzyBoundingRect);
+
+ painter->setFont(_font);
+ painter->setPen(_pen);
+ painter->drawText(textRect, FLAGS, text());
}
diff --git a/src/textpointitem.h b/src/textpointitem.h
index 4a51fc3..a0f33f6 100644
--- a/src/textpointitem.h
+++ b/src/textpointitem.h
@@ -5,12 +5,13 @@
#include
#include
#include "textitem.h"
+#include "text.h"
class TextPointItem : public TextItem
{
public:
TextPointItem(const QString &text, const QPointF &pos, const QFont &font,
- int maxTextWidth);
+ const Text::Properties &prop, const QImage &icon);
QRectF boundingRect() const {return _boundingRect;}
QPainterPath shape() const {return _shape;}
@@ -19,10 +20,16 @@ public:
void setPen(const QPen &pen) {_pen = pen;}
private:
+ typedef QRectF (*BoundingRectFunction)(const QString &, const QFont &, int);
+ QRectF computeTextRect(BoundingRectFunction brf) const;
+
+ QPointF _pos;
QPainterPath _shape;
QRectF _boundingRect;
QFont _font;
QPen _pen;
+ QImage _icon;
+ Text::Properties _properties;
};
#endif // TEXTPOINTITEM_H
diff --git a/src/tile.h b/src/tile.h
index 7aaa56b..1ba16e0 100644
--- a/src/tile.h
+++ b/src/tile.h
@@ -7,16 +7,18 @@
class Tile {
public:
- Tile(QImage *img, const QPointF &scale)
- : _size(img->size()), _text(QSize(img->size().width() / scale.x(),
- img->size().height() / scale.y())), _painter(img)
+ Tile(QImage *img, int zoom, const QPointF &scale)
+ : _zoom(zoom), _size(img->size()), _text(QSize(img->size().width()
+ / scale.x(), img->size().height() / scale.y())), _painter(img)
{_painter.scale(scale.x(), scale.y());}
+ int zoom() const {return _zoom;}
QSize size() const {return _size;}
Text &text() {return _text;}
QPainter &painter() {return _painter;}
private:
+ int _zoom;
QSize _size;
Text _text;
QPainter _painter;
diff --git a/style/sprite.json b/style/sprite.json
new file mode 100644
index 0000000..ffe03b5
--- /dev/null
+++ b/style/sprite.json
@@ -0,0 +1,1682 @@
+{
+ "aerialway_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 284,
+ "y": 127
+ },
+ "aerialway_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 80,
+ "y": 190
+ },
+ "airfield_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 299,
+ "y": 127
+ },
+ "airfield_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 99,
+ "y": 190
+ },
+ "airport_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 314,
+ "y": 127
+ },
+ "airport_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 118,
+ "y": 190
+ },
+ "alcohol_shop_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 381,
+ "y": 210
+ },
+ "alcohol_shop_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 64,
+ "y": 0
+ },
+ "america_football_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 398,
+ "y": 210
+ },
+ "america_football_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 85,
+ "y": 0
+ },
+ "amusement_park_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 415,
+ "y": 210
+ },
+ "amusement_park_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 106,
+ "y": 0
+ },
+ "aquarium_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 432,
+ "y": 210
+ },
+ "aquarium_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 0,
+ "y": 64
+ },
+ "art_gallery_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 449,
+ "y": 210
+ },
+ "art_gallery_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 21,
+ "y": 64
+ },
+ "attraction_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 466,
+ "y": 210
+ },
+ "attraction_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 42,
+ "y": 64
+ },
+ "bakery_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 483,
+ "y": 210
+ },
+ "bakery_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 63,
+ "y": 64
+ },
+ "bank_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 227,
+ "y": 229
+ },
+ "bank_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 84,
+ "y": 64
+ },
+ "bar_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 244,
+ "y": 229
+ },
+ "bar_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 105,
+ "y": 64
+ },
+ "baseball_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 261,
+ "y": 229
+ },
+ "baseball_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 0,
+ "y": 85
+ },
+ "basketball_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 278,
+ "y": 229
+ },
+ "basketball_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 21,
+ "y": 85
+ },
+ "beer_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 295,
+ "y": 229
+ },
+ "beer_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 42,
+ "y": 85
+ },
+ "bicycle_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 312,
+ "y": 229
+ },
+ "bicycle_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 63,
+ "y": 85
+ },
+ "bicycle_rental_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 329,
+ "y": 229
+ },
+ "bicycle_rental_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 84,
+ "y": 85
+ },
+ "building_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 329,
+ "y": 127
+ },
+ "building_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 137,
+ "y": 190
+ },
+ "bus_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 344,
+ "y": 127
+ },
+ "bus_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 156,
+ "y": 190
+ },
+ "butcher_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 346,
+ "y": 229
+ },
+ "butcher_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 105,
+ "y": 85
+ },
+ "cafe_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 363,
+ "y": 229
+ },
+ "cafe_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 0,
+ "y": 106
+ },
+ "campsite_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 380,
+ "y": 229
+ },
+ "campsite_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 21,
+ "y": 106
+ },
+ "car_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 359,
+ "y": 127
+ },
+ "car_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 175,
+ "y": 190
+ },
+ "castle_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 397,
+ "y": 229
+ },
+ "castle_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 42,
+ "y": 106
+ },
+ "cemetery_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 414,
+ "y": 229
+ },
+ "cemetery_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 63,
+ "y": 106
+ },
+ "cinema_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 431,
+ "y": 229
+ },
+ "cinema_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 84,
+ "y": 106
+ },
+ "circle-stroked_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 374,
+ "y": 127
+ },
+ "circle-stroked_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 194,
+ "y": 190
+ },
+ "circle_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 389,
+ "y": 127
+ },
+ "circle_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 213,
+ "y": 190
+ },
+ "clothing_store_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 448,
+ "y": 229
+ },
+ "clothing_store_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 105,
+ "y": 106
+ },
+ "college_11": {
+ "height": 16,
+ "pixelRatio": 1,
+ "width": 16,
+ "x": 473,
+ "y": 106
+ },
+ "college_15": {
+ "height": 20,
+ "pixelRatio": 1,
+ "width": 20,
+ "x": 0,
+ "y": 190
+ },
+ "commercial_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 404,
+ "y": 127
+ },
+ "commercial_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 232,
+ "y": 190
+ },
+ "cricket_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 465,
+ "y": 229
+ },
+ "cricket_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 126,
+ "y": 64
+ },
+ "cross_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 419,
+ "y": 127
+ },
+ "cross_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 0,
+ "y": 210
+ },
+ "dam_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 434,
+ "y": 127
+ },
+ "dam_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 19,
+ "y": 210
+ },
+ "danger_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 482,
+ "y": 229
+ },
+ "danger_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 147,
+ "y": 64
+ },
+ "default_1": {
+ "height": 18,
+ "pixelRatio": 1,
+ "width": 18,
+ "x": 152,
+ "y": 229
+ },
+ "default_2": {
+ "height": 18,
+ "pixelRatio": 1,
+ "width": 25,
+ "x": 170,
+ "y": 229
+ },
+ "default_3": {
+ "height": 18,
+ "pixelRatio": 1,
+ "width": 32,
+ "x": 195,
+ "y": 229
+ },
+ "default_4": {
+ "height": 18,
+ "pixelRatio": 1,
+ "width": 39,
+ "x": 247,
+ "y": 210
+ },
+ "default_5": {
+ "height": 18,
+ "pixelRatio": 1,
+ "width": 45,
+ "x": 286,
+ "y": 210
+ },
+ "default_6": {
+ "height": 18,
+ "pixelRatio": 1,
+ "width": 50,
+ "x": 331,
+ "y": 210
+ },
+ "dentist_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 251,
+ "y": 190
+ },
+ "dentist_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 168,
+ "y": 64
+ },
+ "doctor_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 268,
+ "y": 190
+ },
+ "doctor_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 189,
+ "y": 64
+ },
+ "dog_park_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 285,
+ "y": 190
+ },
+ "dog_park_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 210,
+ "y": 64
+ },
+ "dot_10": {
+ "height": 10,
+ "pixelRatio": 1,
+ "width": 10,
+ "x": 499,
+ "y": 229
+ },
+ "dot_11": {
+ "height": 11,
+ "pixelRatio": 1,
+ "width": 11,
+ "x": 500,
+ "y": 210
+ },
+ "dot_9": {
+ "height": 9,
+ "pixelRatio": 1,
+ "width": 9,
+ "x": 477,
+ "y": 148
+ },
+ "drinking-water_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 302,
+ "y": 190
+ },
+ "drinking_water_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 231,
+ "y": 64
+ },
+ "embassy_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 319,
+ "y": 190
+ },
+ "embassy_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 126,
+ "y": 85
+ },
+ "entrance_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 449,
+ "y": 127
+ },
+ "entrance_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 38,
+ "y": 210
+ },
+ "fast_food_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 336,
+ "y": 190
+ },
+ "fast_food_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 147,
+ "y": 85
+ },
+ "ferry_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 464,
+ "y": 127
+ },
+ "ferry_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 57,
+ "y": 210
+ },
+ "fire-station_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 353,
+ "y": 190
+ },
+ "fire-station_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 168,
+ "y": 85
+ },
+ "fuel_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 479,
+ "y": 127
+ },
+ "fuel_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 76,
+ "y": 210
+ },
+ "garden_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 370,
+ "y": 190
+ },
+ "garden_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 189,
+ "y": 85
+ },
+ "gift_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 387,
+ "y": 190
+ },
+ "gift_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 210,
+ "y": 85
+ },
+ "golf_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 404,
+ "y": 190
+ },
+ "golf_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 231,
+ "y": 85
+ },
+ "grocery_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 421,
+ "y": 190
+ },
+ "grocery_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 126,
+ "y": 106
+ },
+ "hairdresser_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 438,
+ "y": 190
+ },
+ "hairdresser_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 147,
+ "y": 106
+ },
+ "harbor_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 494,
+ "y": 127
+ },
+ "harbor_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 95,
+ "y": 210
+ },
+ "heart_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 455,
+ "y": 190
+ },
+ "heart_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 168,
+ "y": 106
+ },
+ "heliport_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 252,
+ "y": 148
+ },
+ "heliport_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 114,
+ "y": 210
+ },
+ "hospital_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 472,
+ "y": 190
+ },
+ "hospital_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 189,
+ "y": 106
+ },
+ "ice_cream_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 489,
+ "y": 190
+ },
+ "ice_cream_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 210,
+ "y": 106
+ },
+ "industry_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 267,
+ "y": 148
+ },
+ "industry_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 133,
+ "y": 210
+ },
+ "information_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 252,
+ "y": 64
+ },
+ "information_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 231,
+ "y": 106
+ },
+ "laundry_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 269,
+ "y": 64
+ },
+ "laundry_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 127,
+ "y": 0
+ },
+ "library_11": {
+ "height": 16,
+ "pixelRatio": 1,
+ "width": 16,
+ "x": 489,
+ "y": 106
+ },
+ "library_15": {
+ "height": 20,
+ "pixelRatio": 1,
+ "width": 20,
+ "x": 20,
+ "y": 190
+ },
+ "lighthouse_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 286,
+ "y": 64
+ },
+ "lighthouse_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 148,
+ "y": 0
+ },
+ "lodging_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 303,
+ "y": 64
+ },
+ "lodging_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 169,
+ "y": 0
+ },
+ "marker_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 282,
+ "y": 148
+ },
+ "marker_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 152,
+ "y": 210
+ },
+ "monument_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 320,
+ "y": 64
+ },
+ "monument_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 190,
+ "y": 0
+ },
+ "mountain_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 337,
+ "y": 64
+ },
+ "mountain_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 211,
+ "y": 0
+ },
+ "museum_11": {
+ "height": 16,
+ "pixelRatio": 1,
+ "width": 16,
+ "x": 252,
+ "y": 127
+ },
+ "museum_15": {
+ "height": 20,
+ "pixelRatio": 1,
+ "width": 20,
+ "x": 40,
+ "y": 190
+ },
+ "music_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 354,
+ "y": 64
+ },
+ "music_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 232,
+ "y": 0
+ },
+ "park_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 371,
+ "y": 64
+ },
+ "park_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 0,
+ "y": 127
+ },
+ "parking_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 297,
+ "y": 148
+ },
+ "parking_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 171,
+ "y": 210
+ },
+ "parking_garage_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 312,
+ "y": 148
+ },
+ "parking_garage_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 190,
+ "y": 210
+ },
+ "pedestrian_polygon": {
+ "height": 64,
+ "pixelRatio": 1,
+ "width": 64,
+ "x": 0,
+ "y": 0
+ },
+ "pharmacy_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 388,
+ "y": 64
+ },
+ "pharmacy_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 21,
+ "y": 127
+ },
+ "picnic_site_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 405,
+ "y": 64
+ },
+ "picnic_site_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 42,
+ "y": 127
+ },
+ "pitch_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 422,
+ "y": 64
+ },
+ "pitch_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 63,
+ "y": 127
+ },
+ "place_of_worship_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 439,
+ "y": 64
+ },
+ "place_of_worship_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 84,
+ "y": 127
+ },
+ "playground_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 456,
+ "y": 64
+ },
+ "playground_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 105,
+ "y": 127
+ },
+ "police_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 473,
+ "y": 64
+ },
+ "police_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 126,
+ "y": 127
+ },
+ "post_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 490,
+ "y": 64
+ },
+ "post_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 147,
+ "y": 127
+ },
+ "prison_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 252,
+ "y": 85
+ },
+ "prison_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 168,
+ "y": 127
+ },
+ "railway_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 327,
+ "y": 148
+ },
+ "railway_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 209,
+ "y": 210
+ },
+ "railway_light_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 342,
+ "y": 148
+ },
+ "railway_light_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 228,
+ "y": 210
+ },
+ "railway_metro_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 357,
+ "y": 148
+ },
+ "railway_metro_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 0,
+ "y": 229
+ },
+ "ranger_station_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 269,
+ "y": 85
+ },
+ "ranger_station_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 189,
+ "y": 127
+ },
+ "religious_christian_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 286,
+ "y": 85
+ },
+ "religious_christian_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 210,
+ "y": 127
+ },
+ "religious_jewish_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 303,
+ "y": 85
+ },
+ "religious_jewish_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 231,
+ "y": 127
+ },
+ "religious_muslim_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 320,
+ "y": 85
+ },
+ "religious_muslim_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 0,
+ "y": 148
+ },
+ "restaurant_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 337,
+ "y": 85
+ },
+ "restaurant_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 21,
+ "y": 148
+ },
+ "roadblock_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 354,
+ "y": 85
+ },
+ "roadblock_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 42,
+ "y": 148
+ },
+ "rocket_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 371,
+ "y": 85
+ },
+ "rocket_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 63,
+ "y": 148
+ },
+ "school_11": {
+ "height": 16,
+ "pixelRatio": 1,
+ "width": 16,
+ "x": 268,
+ "y": 127
+ },
+ "school_15": {
+ "height": 20,
+ "pixelRatio": 1,
+ "width": 20,
+ "x": 60,
+ "y": 190
+ },
+ "shelter_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 388,
+ "y": 85
+ },
+ "shelter_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 84,
+ "y": 148
+ },
+ "shop_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 405,
+ "y": 85
+ },
+ "shop_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 105,
+ "y": 148
+ },
+ "skiing_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 422,
+ "y": 85
+ },
+ "skiing_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 126,
+ "y": 148
+ },
+ "soccer_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 439,
+ "y": 85
+ },
+ "soccer_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 147,
+ "y": 148
+ },
+ "square-stroke_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 372,
+ "y": 148
+ },
+ "square-stroke_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 19,
+ "y": 229
+ },
+ "square_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 387,
+ "y": 148
+ },
+ "square_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 38,
+ "y": 229
+ },
+ "stadium_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 456,
+ "y": 85
+ },
+ "stadium_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 168,
+ "y": 148
+ },
+ "star-stroke_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 402,
+ "y": 148
+ },
+ "star-stroke_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 57,
+ "y": 229
+ },
+ "star_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 417,
+ "y": 148
+ },
+ "star_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 76,
+ "y": 229
+ },
+ "suitcase_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 473,
+ "y": 85
+ },
+ "suitcase_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 189,
+ "y": 148
+ },
+ "sushi_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 490,
+ "y": 85
+ },
+ "sushi_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 210,
+ "y": 148
+ },
+ "swimming_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 252,
+ "y": 106
+ },
+ "swimming_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 231,
+ "y": 148
+ },
+ "telephone_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 269,
+ "y": 106
+ },
+ "telephone_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 0,
+ "y": 169
+ },
+ "tennis_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 286,
+ "y": 106
+ },
+ "tennis_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 21,
+ "y": 169
+ },
+ "theatre_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 303,
+ "y": 106
+ },
+ "theatre_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 42,
+ "y": 169
+ },
+ "toilet_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 320,
+ "y": 106
+ },
+ "toilet_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 63,
+ "y": 169
+ },
+ "town-hall_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 337,
+ "y": 106
+ },
+ "town-hall_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 84,
+ "y": 169
+ },
+ "triangle_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 432,
+ "y": 148
+ },
+ "triangle_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 95,
+ "y": 229
+ },
+ "triangle_stroked_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 447,
+ "y": 148
+ },
+ "triangle_stroked_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 114,
+ "y": 229
+ },
+ "veterinary_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 354,
+ "y": 106
+ },
+ "veterinary_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 105,
+ "y": 169
+ },
+ "volcano_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 371,
+ "y": 106
+ },
+ "volcano_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 126,
+ "y": 169
+ },
+ "warehouse_11": {
+ "height": 15,
+ "pixelRatio": 1,
+ "width": 15,
+ "x": 462,
+ "y": 148
+ },
+ "warehouse_15": {
+ "height": 19,
+ "pixelRatio": 1,
+ "width": 19,
+ "x": 133,
+ "y": 229
+ },
+ "waste_basket_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 388,
+ "y": 106
+ },
+ "waste_basket_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 147,
+ "y": 169
+ },
+ "water_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 405,
+ "y": 106
+ },
+ "water_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 168,
+ "y": 169
+ },
+ "wetland_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 422,
+ "y": 106
+ },
+ "wetland_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 189,
+ "y": 169
+ },
+ "wheelchair_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 439,
+ "y": 106
+ },
+ "wheelchair_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 210,
+ "y": 169
+ },
+ "zoo_11": {
+ "height": 17,
+ "pixelRatio": 1,
+ "width": 17,
+ "x": 456,
+ "y": 106
+ },
+ "zoo_15": {
+ "height": 21,
+ "pixelRatio": 1,
+ "width": 21,
+ "x": 231,
+ "y": 169
+ }
+}
\ No newline at end of file
diff --git a/style/sprite.png b/style/sprite.png
new file mode 100644
index 0000000..762589d
Binary files /dev/null and b/style/sprite.png differ