1
0
mirror of https://github.com/tumic0/GPXSee.git synced 2025-07-01 05:19:15 +02:00

Compare commits

...

28 Commits

Author SHA1 Message Date
aec052edaf Fixed elevation source selection logic 2023-04-10 17:21:02 +02:00
1bbc57173e Add only valid graph segments to the graphs
Fixes #489
2023-04-10 13:06:19 +02:00
52ea52ff4e Fixed broken graphs when data contains empty segments
fixes #490
2023-04-10 10:00:30 +02:00
b6ca28e159 Render circles 2023-04-08 00:34:33 +02:00
648627b17f Use style-defined priorities
+ code cleanup
2023-04-06 23:29:05 +02:00
2aa759a4bc Filter all way references, not only "ref"s 2023-04-06 09:41:31 +02:00
84dc58da71 Code cleanup 2023-04-05 21:50:19 +02:00
25838ad02d Removed obsolete header include 2023-04-05 21:02:20 +02:00
d11483cd94 Merge branch 'origin/master' into Weblate. 2023-04-05 19:28:37 +02:00
6cd122f59b Fix/improve Mapsforge tiles rendering 2023-04-05 19:28:17 +02:00
981bd33680 Properly encapsulate the ENC-specific text point 2023-04-05 19:27:25 +02:00
2697507c3a Merge branch 'origin/master' into Weblate. 2023-04-04 00:31:47 +02:00
8b24aa17e2 Increase path render instructions cache 2023-04-04 00:29:50 +02:00
6fc5a2f67d Added support for the "text-transform" attribute 2023-04-04 00:22:15 +02:00
d794de5818 Added support for curved lines 2023-04-03 23:53:57 +02:00
81f695a672 Fixed crash on empty IMG/GMAP maps 2023-04-03 23:29:35 +02:00
9580ddd54a Merge branch 'origin/master' into Weblate. 2023-04-02 21:27:06 +02:00
20aaf68405 Do not bundle the unused SQL plugins (ODBC, PostgreSQL) 2023-04-02 21:24:52 +02:00
cdc0a64ead Merge branch 'origin/master' into Weblate. 2023-04-02 18:58:31 +02:00
f2218126ef Version++ 2023-04-02 18:58:24 +02:00
14670868bb Translated using Weblate (Esperanto)
Currently translated at 94.2% (440 of 467 strings)

Translation: GPXSee/Translations
Translate-URL: https://hosted.weblate.org/projects/gpxsee/translations/eo/
2023-04-02 18:39:07 +02:00
6a73b6358d Translated using Weblate (Finnish)
Currently translated at 97.8% (457 of 467 strings)

Translation: GPXSee/Translations
Translate-URL: https://hosted.weblate.org/projects/gpxsee/translations/fi/
2023-04-02 18:39:07 +02:00
7e89f339f4 Remove obsolete environment variables setup 2023-04-02 15:31:54 +02:00
9e80319577 Remove the Qt5 modules... 2023-04-02 15:22:19 +02:00
0596201fb2 Fixed Qt5 modules list 2023-04-02 15:17:02 +02:00
dce078bca6 Use Qt 5.15.2 as the Qt5 version for OS X builds 2023-04-02 15:11:37 +02:00
34f935462f Use the official Qt builds instead of homebrew in mac builds 2023-04-02 15:02:55 +02:00
84a592998d Only tile big (> Qt OpenGL cache size / 2) images 2023-04-02 14:36:03 +02:00
29 changed files with 560 additions and 351 deletions

View File

@ -1,4 +1,4 @@
version: 12.3.{build}
version: 12.4.{build}
configuration:
- Release

View File

@ -10,14 +10,12 @@ jobs:
name: GPXSee Qt5 build
runs-on: macos-latest
steps:
- name: Set environment variables
run: echo "PATH=/usr/local/opt/qt@5/bin:$PATH" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v3
- name: Install dependencies
run: |
brew update
brew install qt5
- name: Install Qt
uses: jurplel/install-qt-action@v3
with:
version: '5.15.2'
- name: Create localization
run: lrelease gpxsee.pro
- name: Configure build
@ -25,7 +23,7 @@ jobs:
- name: Build project
run: make -j3
- name: Create DMG
run: macdeployqt GPXSee.app -dmg
run: macdeployqt GPXSee.app -dmg -appstore-compliant
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
@ -36,14 +34,13 @@ jobs:
name: GPXSee Qt6 build
runs-on: macos-latest
steps:
- name: Set environment variables
run: echo "PATH=/usr/local/opt/qt@6/bin:$PATH" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v3
- name: Install dependencies
run: |
brew update
brew install qt6
- name: Install Qt
uses: jurplel/install-qt-action@v3
with:
version: '6.4.3'
modules: qtpositioning qt5compat qtserialport
- name: Create localization
run: lrelease gpxsee.pro
- name: Configure build
@ -51,7 +48,7 @@ jobs:
- name: Build project
run: make -j3
- name: Create DMG
run: macdeployqt GPXSee.app -dmg
run: macdeployqt GPXSee.app -dmg -appstore-compliant
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:

View File

@ -3,7 +3,7 @@ unix:!macx:!android {
} else {
TARGET = GPXSee
}
VERSION = 12.3
VERSION = 12.4
QT += core \
gui \

View File

@ -30,7 +30,7 @@
<message>
<location filename="../src/GUI/cadencegraph.cpp" line="28"/>
<source>Avg</source>
<translation type="unfinished"></translation>
<translation>Mez</translation>
</message>
<message>
<location filename="../src/GUI/cadencegraph.cpp" line="30"/>
@ -1156,7 +1156,7 @@
<message>
<location filename="../src/GUI/heartrategraph.cpp" line="28"/>
<source>Avg</source>
<translation type="unfinished"></translation>
<translation>Mez</translation>
</message>
<message>
<location filename="../src/GUI/heartrategraph.cpp" line="30"/>
@ -2157,7 +2157,7 @@
<message>
<location filename="../src/GUI/powergraph.cpp" line="28"/>
<source>Avg</source>
<translation type="unfinished"></translation>
<translation>Mez</translation>
</message>
<message>
<location filename="../src/GUI/powergraph.cpp" line="30"/>
@ -2277,7 +2277,7 @@
<message>
<location filename="../src/GUI/speedgraph.cpp" line="35"/>
<source>Avg</source>
<translation type="unfinished"></translation>
<translation>Mez</translation>
</message>
<message>
<location filename="../src/GUI/speedgraph.cpp" line="37"/>
@ -2374,7 +2374,7 @@
<message>
<location filename="../src/GUI/temperaturegraph.cpp" line="28"/>
<source>Avg</source>
<translation type="unfinished"></translation>
<translation>Mez</translation>
</message>
<message>
<location filename="../src/GUI/temperaturegraph.cpp" line="30"/>

View File

@ -791,7 +791,7 @@
<message>
<location filename="../src/GUI/gui.cpp" line="963"/>
<source>Open directory</source>
<translation type="unfinished"></translation>
<translation>Avaa hakemisto</translation>
</message>
<message>
<location filename="../src/GUI/gui.cpp" line="1215"/>
@ -1790,7 +1790,7 @@
<message>
<location filename="../src/GUI/optionsdialog.cpp" line="577"/>
<source>Use $lat and $lon for NYY/SYY and EXXX/WXXX in the URL.</source>
<translation type="unfinished"></translation>
<translation>Käytä $lat ja $lon kohdissa NYY/SYY ja EXXX/WXXX URL-osoitteessa.</translation>
</message>
<message>
<location filename="../src/GUI/optionsdialog.cpp" line="582"/>

View File

@ -37,7 +37,7 @@ Unicode true
; The name of the installer
Name "GPXSee"
; Program version
!define VERSION "12.3"
!define VERSION "12.4"
; The file to write
OutFile "GPXSee-${VERSION}_x64.exe"

View File

@ -47,7 +47,7 @@ QList<GraphItem*> CadenceGraph::loadData(const Data &data)
const Track &track = data.tracks().at(i);
const Graph &graph = track.cadence();
if (!graph.isValid()) {
if (graph.isEmpty()) {
_palette.nextColor();
graphs.append(0);
} else {

View File

@ -85,7 +85,7 @@ void ElevationGraph::setInfo()
GraphItem *ElevationGraph::loadGraph(const Graph &graph, PathType type,
const QColor &color, bool primary)
{
if (!graph.isValid())
if (graph.isEmpty())
return 0;
ElevationGraphItem *gi = new ElevationGraphItem(graph, _graphType, _width,

View File

@ -50,7 +50,7 @@ QList<GraphItem*> GearRatioGraph::loadData(const Data &data)
for (int i = 0; i < data.tracks().count(); i++) {
const Graph &graph = data.tracks().at(i).ratio();
if (!graph.isValid()) {
if (graph.isEmpty()) {
_palette.nextColor();
graphs.append(0);
} else {

View File

@ -8,8 +8,6 @@ GraphItem::GraphItem(const Graph &graph, GraphType type, int width,
const QColor &color, Qt::PenStyle style, QGraphicsItem *parent)
: GraphicsItem(parent), _graph(graph), _type(type), _secondaryGraph(0)
{
Q_ASSERT(_graph.isValid());
_units = Metric;
_sx = 0; _sy = 0;
_color = color;

View File

@ -47,7 +47,7 @@ QList<GraphItem*> HeartRateGraph::loadData(const Data &data)
const Track &track = data.tracks().at(i);
const Graph &graph = track.heartRate();
if (!graph.isValid()) {
if (graph.isEmpty()) {
_palette.nextColor();
graphs.append(0);
} else {

View File

@ -47,7 +47,7 @@ QList<GraphItem*> PowerGraph::loadData(const Data &data)
const Track &track = data.tracks().at(i);
const Graph &graph = track.power();
if (!graph.isValid()) {
if (graph.isEmpty()) {
_palette.nextColor();
graphs.append(0);
} else {

View File

@ -50,7 +50,7 @@ void SpeedGraph::setInfo()
GraphItem *SpeedGraph::loadGraph(const Graph &graph, const Track &track,
const QColor &color, bool primary)
{
if (!graph.isValid())
if (graph.isEmpty())
return 0;
SpeedGraphItem *gi = new SpeedGraphItem(graph, _graphType, _width,

View File

@ -51,7 +51,7 @@ QList<GraphItem*> TemperatureGraph::loadData(const Data &data)
const Track &track = data.tracks().at(i);
const Graph &graph = track.temperature();
if (!graph.isValid()) {
if (graph.isEmpty()) {
_palette.nextColor();
graphs.append(0);
} else {

View File

@ -46,16 +46,6 @@ typedef QVector<GraphPoint> GraphSegment;
class Graph : public QList<GraphSegment>
{
public:
bool isValid() const
{
if (isEmpty())
return false;
for (int i = 0; i < size(); i++)
if (at(i).size() < 2)
return false;
return true;
}
bool hasTime() const
{
for (int i = 0; i < size(); i++) {

View File

@ -68,16 +68,14 @@ GraphPair Route::elevation() const
{
if (_useDEM) {
Graph dem(demElevation());
if (dem.isValid())
return GraphPair(dem, _show2ndElevation ? gpsElevation() : Graph());
else
return GraphPair(gpsElevation(), Graph());
return (dem.isEmpty())
? GraphPair(gpsElevation(), Graph())
: GraphPair(dem, _show2ndElevation ? gpsElevation() : Graph());
} else {
Graph gps(gpsElevation());
if (gps.isValid())
return GraphPair(gps, _show2ndElevation ? demElevation() : Graph());
else
return GraphPair(demElevation(), Graph());
return (gps.isEmpty())
? GraphPair(demElevation(), Graph())
: GraphPair(gps, _show2ndElevation ? demElevation() : Graph());
}
}

View File

@ -88,6 +88,24 @@ static GraphSegment filter(const GraphSegment &g, int window)
}
qreal Track::lastDistance(int seg)
{
for (int i = seg - 1; i >= 0; i--)
if (!_segments.at(i).distance.isEmpty())
return _segments.at(i).distance.last();
return 0;
}
qreal Track::lastTime(int seg)
{
for (int i = seg - 1; i >= 0; i--)
if (!_segments.at(i).time.isEmpty())
return _segments.at(i).time.last();
return 0;
}
Track::Track(const TrackData &data) : _pause(0)
{
qreal ds, dt;
@ -113,11 +131,8 @@ Track::Track(const TrackData &data) : _pause(0)
Segment &seg = _segments.last();
seg.distance.append(i && !_segments.at(i-1).distance.isEmpty()
? _segments.at(i-1).distance.last() : 0);
seg.time.append(i && !_segments.at(i-1).time.isEmpty()
? _segments.at(i-1).time.last() :
sd.first().hasTimestamp() ? 0 : NAN);
seg.distance.append(lastDistance(i));
seg.time.append(sd.first().hasTimestamp() ? lastTime(i) : NAN);
seg.speed.append(sd.first().hasTimestamp() ? 0 : NAN);
acceleration.append(sd.first().hasTimestamp() ? 0 : NAN);
bool hasTime = !std::isnan(seg.time.first());
@ -251,7 +266,8 @@ Graph Track::gpsElevation() const
sd.at(j).elevation()));
}
ret.append(filter(gs, _elevationWindow));
if (gs.size() >= 2)
ret.append(filter(gs, _elevationWindow));
}
if (_data.style().color().isValid())
@ -278,7 +294,8 @@ Graph Track::demElevation() const
gs.append(GraphPoint(seg.distance.at(j), seg.time.at(j), dem));
}
ret.append(filter(gs, _elevationWindow));
if (gs.size() >= 2)
ret.append(filter(gs, _elevationWindow));
}
if (_data.style().color().isValid())
@ -291,16 +308,14 @@ GraphPair Track::elevation() const
{
if (_useDEM) {
Graph dem(demElevation());
if (dem.isValid())
return GraphPair(dem, _show2ndElevation ? gpsElevation() : Graph());
else
return GraphPair(gpsElevation(), Graph());
return (dem.isEmpty())
? GraphPair(gpsElevation(), Graph())
: GraphPair(dem, _show2ndElevation ? gpsElevation() : Graph());
} else {
Graph gps(gpsElevation());
if (gps.isValid())
return GraphPair(gps, _show2ndElevation ? demElevation() : Graph());
else
return GraphPair(demElevation(), Graph());
return (gps.isEmpty())
? GraphPair(demElevation(), Graph())
: GraphPair(gps, _show2ndElevation ? demElevation() : Graph());
}
}
@ -329,11 +344,13 @@ Graph Track::computedSpeed() const
gs.append(GraphPoint(seg.distance.at(j), seg.time.at(j), v));
}
ret.append(filter(gs, _speedWindow));
GraphSegment &filtered = ret.last();
if (gs.size() >= 2) {
ret.append(filter(gs, _speedWindow));
GraphSegment &filtered = ret.last();
for (int j = 0; j < stop.size(); j++)
filtered[stop.at(j)].setY(0);
for (int j = 0; j < stop.size(); j++)
filtered[stop.at(j)].setY(0);
}
}
if (_data.style().color().isValid())
@ -367,11 +384,13 @@ Graph Track::reportedSpeed() const
gs.append(GraphPoint(seg.distance.at(j), seg.time.at(j), v));
}
ret.append(filter(gs, _speedWindow));
GraphSegment &filtered = ret.last();
if (gs.size() >= 2) {
ret.append(filter(gs, _speedWindow));
GraphSegment &filtered = ret.last();
for (int j = 0; j < stop.size(); j++)
filtered[stop.at(j)].setY(0);
for (int j = 0; j < stop.size(); j++)
filtered[stop.at(j)].setY(0);
}
}
if (_data.style().color().isValid())
@ -384,18 +403,14 @@ GraphPair Track::speed() const
{
if (_useReportedSpeed) {
Graph reported(reportedSpeed());
if (reported.isValid())
return GraphPair(reported, _show2ndSpeed ? computedSpeed()
: Graph());
else
return GraphPair(computedSpeed(), Graph());
return (reported.isEmpty())
? GraphPair(computedSpeed(), Graph())
: GraphPair(reported, _show2ndSpeed ? computedSpeed() : Graph());
} else {
Graph computed(computedSpeed());
if (computed.isValid())
return GraphPair(computed, _show2ndSpeed ? reportedSpeed()
: Graph());
else
return GraphPair(reportedSpeed(), Graph());
return (computed.isEmpty())
? GraphPair(reportedSpeed(), Graph())
: GraphPair(computed, _show2ndSpeed ? reportedSpeed() : Graph());
}
}
@ -415,7 +430,8 @@ Graph Track::heartRate() const
gs.append(GraphPoint(seg.distance.at(j), seg.time.at(j),
sd.at(j).heartRate()));
ret.append(filter(gs, _heartRateWindow));
if (gs.size() >= 2)
ret.append(filter(gs, _heartRateWindow));
}
if (_data.style().color().isValid())
@ -441,7 +457,8 @@ Graph Track::temperature() const
sd.at(j).temperature()));
}
ret.append(gs);
if (gs.size() >= 2)
ret.append(gs);
}
if (_data.style().color().isValid())
@ -466,7 +483,8 @@ Graph Track::ratio() const
gs.append(GraphPoint(seg.distance.at(j), seg.time.at(j),
sd.at(j).ratio()));
ret.append(gs);
if (gs.size() >= 2)
ret.append(gs);
}
if (_data.style().color().isValid())
@ -500,11 +518,13 @@ Graph Track::cadence() const
gs.append(GraphPoint(seg.distance.at(j), seg.time.at(j), c));
}
ret.append(filter(gs, _cadenceWindow));
GraphSegment &filtered = ret.last();
if (gs.size() >= 2) {
ret.append(filter(gs, _cadenceWindow));
GraphSegment &filtered = ret.last();
for (int j = 0; j < stop.size(); j++)
filtered[stop.at(j)].setY(0);
for (int j = 0; j < stop.size(); j++)
filtered[stop.at(j)].setY(0);
}
}
if (_data.style().color().isValid())
@ -539,11 +559,13 @@ Graph Track::power() const
gs.append(GraphPoint(seg.distance.at(j), seg.time.at(j), p));
}
ret.append(filter(gs, _powerWindow));
GraphSegment &filtered = ret.last();
if (gs.size() >= 2) {
ret.append(filter(gs, _powerWindow));
GraphSegment &filtered = ret.last();
for (int j = 0; j < stop.size(); j++)
filtered[stop.at(j)].setY(0);
for (int j = 0; j < stop.size(); j++)
filtered[stop.at(j)].setY(0);
}
}
if (_data.style().color().isValid())

View File

@ -63,6 +63,8 @@ private:
QSet<int> stop;
};
qreal lastDistance(int seg);
qreal lastTime(int seg);
bool discardStopPoint(const Segment &seg, int i) const;
Graph demElevation() const;

View File

@ -2,31 +2,16 @@
#include <QPainter>
#include "common/linec.h"
#include "map/bitmapline.h"
#include "map/textpointitem.h"
#include "map/textpathitem.h"
#include "style.h"
#include "rastertile.h"
using namespace ENC;
#define ICON_PADDING 2
#define TSSLPT_SIZE 0.005 /* ll */
#define RDOCAL_SIZE 12 /* px */
#define CURENT_SIZE 12 /* px */
class PointItem : public TextPointItem
{
public:
PointItem(const QPoint &point, const QString *text, const QFont *font,
const QImage *img, const QImage *rimg, const QColor *color,
const QColor *haloColor) : TextPointItem(point, text, font, img, color,
haloColor, 0, ICON_PADDING), _rimg(rimg) {}
~PointItem() {delete _rimg;}
private:
const QImage *_rimg;
};
typedef QMap<Coordinates, const MapData::Point*> PointMap;
const float C1 = 0.866025f; /* sqrt(3)/2 */

View File

@ -4,6 +4,7 @@
#include <QPixmap>
#include "map/projection.h"
#include "map/transform.h"
#include "map/textpointitem.h"
#include "mapdata.h"
class TextItem;
@ -30,6 +31,19 @@ public:
void render();
private:
class PointItem : public TextPointItem
{
public:
PointItem(const QPoint &point, const QString *text, const QFont *font,
const QImage *img, const QImage *rimg, const QColor *color,
const QColor *haloColor) : TextPointItem(point, text, font, img, color,
haloColor, 0, 2), _rimg(rimg) {}
~PointItem() {delete _rimg;}
private:
const QImage *_rimg;
};
QPointF ll2xy(const Coordinates &c) const
{return _transform.proj2img(_proj.ll2xy(c));}
QPainterPath painterPath(const Polygon &polygon) const;

View File

@ -109,6 +109,9 @@ void MapData::computeZooms()
zooms.insert(z.at(i));
}
if (zooms.isEmpty())
return;
_zooms = zooms.values();
std::sort(_zooms.begin(), _zooms.end());

View File

@ -2,6 +2,11 @@
#include "image.h"
#define TILE_SIZE 256
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
#define OPENGL_SIZE_LIMIT 536870912 /* 512MB */
#else
#define OPENGL_SIZE_LIMIT 134217728 /* 128MB */
#endif
void Image::draw(QPainter *painter, const QRectF &rect, Map::Flags flags)
{
@ -11,7 +16,7 @@ void Image::draw(QPainter *painter, const QRectF &rect, Map::Flags flags)
/* When OpenGL is used, big images are rendered incredibly slow or not at
all using the QPainter::drawImage() function with a source rect set. So
we have to tile the image ourself before it can be drawn. */
if (flags & Map::OpenGL) {
if (flags & Map::OpenGL && _img.sizeInBytes() > OPENGL_SIZE_LIMIT) {
for (int i = sr.left()/TILE_SIZE; i <= sr.right()/TILE_SIZE; i++) {
for (int j = sr.top()/TILE_SIZE; j <= sr.bottom()/TILE_SIZE; j++) {
QPoint tl(i * TILE_SIZE, j * TILE_SIZE);

View File

@ -3,6 +3,7 @@
#include <QFile>
#include <QDataStream>
#include <QColor>
#include "common/hash.h"
#include "map/osm.h"
#include "subfile.h"
#include "mapdata.h"
@ -14,37 +15,6 @@ using namespace Mapsforge;
#define MD(val) ((val) / 1e6)
#define OFFSET_MASK 0x7FFFFFFFFFL
static uint pointType(const QVector<MapData::Tag> &tags)
{
for (int i = 0; i < tags.size(); i++) {
const MapData::Tag &tag = tags.at(i);
if (tag.key == "place") {
if (tag.value == "country")
return 4;
else if (tag.value == "city")
return 3;
else if (tag.value == "town")
return 2;
else if (tag.value == "village")
return 1;
else
return 0;
}
}
return 0;
}
static void setPointId(MapData::Point &p)
{
uint hash = (uint)qHash(QPair<double,double>((uint)qHash(
QPair<double, double>(p.coordinates.lon(), p.coordinates.lat())),
(uint)qHash(p.label)));
uint type = pointType(p.tags);
p.id = ((quint64)type)<<32 | hash;
}
static void copyPaths(const RectC &rect, const QList<MapData::Path> *src,
QList<MapData::Path> *dst)
{
@ -194,7 +164,7 @@ static bool readDoubleDelta(SubFile &subfile, const Coordinates &c,
return true;
}
static bool readPolygon(SubFile &subfile, const Coordinates &c,
static bool readPolygonPath(SubFile &subfile, const Coordinates &c,
bool doubleDelta, Polygon &polygon)
{
quint32 blocks, nodes;
@ -202,7 +172,7 @@ static bool readPolygon(SubFile &subfile, const Coordinates &c,
if (!subfile.readVUInt32(blocks))
return false;
polygon.reserve(blocks);
polygon.reserve(polygon.size() + blocks);
for (quint32 i = 0; i < blocks; i++) {
if (!subfile.readVUInt32(nodes) || !nodes)
return false;
@ -604,7 +574,7 @@ bool MapData::readPaths(const VectorTile *tile, int zoom, QList<Path> *list)
return false;
for (unsigned i = 0; i < paths[zoom - info.min]; i++) {
Path p;
Path p(subfile.offset() + subfile.pos());
qint32 lon = 0, lat = 0;
if (!(subfile.readVUInt32(unused) && subfile.readUInt16(bitmap)
@ -646,15 +616,15 @@ bool MapData::readPaths(const VectorTile *tile, int zoom, QList<Path> *list)
Q_ASSERT(blocks);
for (unsigned j = 0; j < blocks; j++) {
if (!readPolygon(subfile, tile->pos, flags & 0x04, p.poly))
if (!readPolygonPath(subfile, tile->pos, flags & 0x04, p.poly))
return false;
p.closed = isClosed(p.poly);
if (flags & 0x10)
p.labelPos = Coordinates(p.poly.first().first().lon() + MD(lon),
p.poly.first().first().lat() + MD(lat));
list->append(p);
}
p.closed = isClosed(p.poly);
if (flags & 0x10)
p.labelPos = Coordinates(p.poly.first().first().lon() + MD(lon),
p.poly.first().first().lat() + MD(lat));
list->append(p);
}
return true;
@ -719,7 +689,6 @@ bool MapData::readPoints(const VectorTile *tile, int zoom, QList<Point> *list)
p.tags.append(Tag("ele", QByteArray::number(elevation)));
}
setPointId(p);
list->append(p);
}
@ -735,15 +704,14 @@ QDebug operator<<(QDebug dbg, const Mapsforge::MapData::Tag &tag)
QDebug operator<<(QDebug dbg, const MapData::Path &path)
{
dbg.nospace() << "Path(" << path.poly.boundingRect() << ", " << path.label
<< ", " << path.tags << ")";
dbg.nospace() << "Path(" << path.poly.boundingRect() << ", "
<< path.tags << ")";
return dbg.space();
}
QDebug operator<<(QDebug dbg, const MapData::Point &point)
{
dbg.nospace() << "Point(" << point.coordinates << "," << point.label
<< ", " << point.tags << ")";
dbg.nospace() << "Point(" << point.coordinates << ", " << point.tags << ")";
return dbg.space();
}
#endif // QT_NO_DEBUG

View File

@ -3,7 +3,6 @@
#include <QFile>
#include <QCache>
#include <QPainterPath>
#include "common/hash.h"
#include "common/rectc.h"
#include "common/rtree.h"
@ -42,31 +41,31 @@ public:
};
struct Point {
Point(const Coordinates &c) : coordinates(c) {}
Point(const Coordinates &c) : coordinates(c)
{
id = (quint64)qHash(QPair<double, double>(c.lon(), c.lat()));
}
quint64 id;
Coordinates coordinates;
QVector<Tag> tags;
int layer;
quint64 id;
QString label;
bool operator<(const Point &other) const
{return id > other.id;}
};
struct Path {
Path(quint64 id) : id(id) {}
quint64 id;
Polygon poly;
QVector<Tag> tags;
Coordinates labelPos;
int layer;
bool closed;
QString label;
QPainterPath path;
bool operator<(const Path &other) const
{return layer < other.layer;}
bool operator==(const Path &other) const
{return (id == other.id);}
};
RectC bounds() const;
@ -75,7 +74,7 @@ public:
int tileSize() const {return _tileSize;}
void points(const RectC &rect, int zoom, QList<Point> *list);
void paths(const RectC &rect, int zoom, QList<Path> *list);
void paths(const RectC &rect, int zoom, QList<Path> *set);
void load();
void clear();
@ -175,6 +174,11 @@ inline HASH_T qHash(const MapData::Tag &tag)
return ::qHash(tag.key) ^ ::qHash(tag.value);
}
inline HASH_T qHash(const MapData::Path &path)
{
return ::qHash(path.id);
}
}
#ifndef QT_NO_DEBUG

View File

@ -1,9 +1,6 @@
#include <QPainter>
#include <QCache>
#include "common/programpaths.h"
#include "map/mapsforgemap.h"
#include "map/textpathitem.h"
#include "map/textpointitem.h"
#include "rastertile.h"
using namespace Mapsforge;
@ -44,36 +41,13 @@ static QPointF centroid(const QPainterPath &polygon)
return QPointF(cx * factor, cy * factor);
}
static QString *pointLabel(const Style::TextRender *ri, MapData::Point &point)
static const QByteArray *label(const QByteArray &key,
const QVector<MapData::Tag> &tags)
{
for (int i = 0; i < point.tags.size(); i++) {
if (point.tags.at(i).key == ri->key()) {
if (point.tags.at(i).value.isEmpty())
return 0;
else {
point.label = point.tags.at(i).value;
return &point.label;
}
}
}
return 0;
}
static QString *pathLabel(const Style::TextRender *ri, MapData::Path &path,
bool *limit = 0)
{
for (int i = 0; i < path.tags.size(); i++) {
if (path.tags.at(i).key == ri->key()) {
if (path.tags.at(i).value.isEmpty())
return 0;
else {
path.label = path.tags.at(i).value;
if (limit)
*limit = (path.tags.at(i).key == "ref");
return &path.label;
}
}
for (int i = 0; i < tags.size(); i++) {
const MapData::Tag &tag = tags.at(i);
if (tag.key == key)
return tag.value.isEmpty() ? 0 : &tag.value;
}
return 0;
@ -85,23 +59,23 @@ static const QColor *haloColor(const Style::TextRender *ti)
? &ti->strokeColor() : 0;
}
void RasterTile::processPointLabels(QList<TextItem*> &textItems)
{
const Style &s = style(_ratio);
QList<const Style::TextRender*> labels(s.pointLabels(_zoom));
QList<const Style::Symbol*> symbols(s.pointSymbols(_zoom));
QList<PainterPoint> points;
for (int i = 0; i < _points.size(); i++) {
MapData::Point &point = _points[i];
QString *label = 0;
const MapData::Point &point = _points.at(i);
const QByteArray *lbl = 0;
const Style::TextRender *ti = 0;
const Style::Symbol *si = 0;
for (int j = 0; j < labels.size(); j++) {
const Style::TextRender *ri = labels.at(j);
if (ri->rule().match(point.tags)) {
if ((label = pointLabel(ri, point))) {
if ((lbl = label(ri->key(), point.tags))) {
ti = ri;
break;
}
@ -116,17 +90,21 @@ void RasterTile::processPointLabels(QList<TextItem*> &textItems)
}
}
if (!ti && !si)
continue;
if (ti || si)
points.append(PainterPoint(&point, lbl, si, ti));
}
const QImage *img = si ? &si->img() : 0;
const QFont *font = ti ? &ti->font() : 0;
const QColor *color = ti ? &ti->fillColor() : 0;
const QColor *hColor = ti ? haloColor(ti) : 0;
std::sort(points.begin(), points.end());
TextPointItem *item = new TextPointItem(
ll2xy(point.coordinates).toPoint(), label, font, img, color,
hColor, 0);
for (int i = 0; i < points.size(); i++) {
const PainterPoint &p = points.at(i);
const QImage *img = p.si ? &p.si->img() : 0;
const QFont *font = p.ti ? &p.ti->font() : 0;
const QColor *color = p.ti ? &p.ti->fillColor() : 0;
const QColor *hColor = p.ti ? haloColor(p.ti) : 0;
PointItem *item = new PointItem(ll2xy(p.p->coordinates).toPoint(),
p.lbl, font, img, color, hColor);
if (item->isValid() && !item->collides(textItems))
textItems.append(item);
else
@ -134,34 +112,34 @@ void RasterTile::processPointLabels(QList<TextItem*> &textItems)
}
}
void RasterTile::processAreaLabels(QList<TextItem*> &textItems)
void RasterTile::processAreaLabels(QList<TextItem*> &textItems,
QVector<PainterPath> &paths)
{
const Style &s = style(_ratio);
QList<const Style::TextRender*> labels(s.areaLabels(_zoom));
QList<const Style::Symbol*> symbols(s.areaSymbols(_zoom));
for (int i = 0; i < _paths.size(); i++) {
MapData::Path &path = _paths[i];
QString *label = 0;
for (int i = 0; i < paths.size(); i++) {
PainterPath &path = paths[i];
const Style::TextRender *ti = 0;
const Style::Symbol *si = 0;
const QByteArray *lbl = 0;
if (!path.closed)
if (!path.path->closed)
continue;
for (int j = 0; j < labels.size(); j++) {
const Style::TextRender *ri = labels.at(j);
if (ri->rule().match(path.closed, path.tags)) {
if ((label = pathLabel(ri, path))) {
if (ri->rule().match(path.path->closed, path.path->tags)) {
if ((lbl = label(ri->key(), path.path->tags)))
ti = ri;
break;
}
break;
}
}
for (int j = 0; j < symbols.size(); j++) {
const Style::Symbol *ri = symbols.at(j);
if (ri->rule().match(path.tags)) {
if (ri->rule().match(path.path->tags)) {
si = ri;
break;
}
@ -170,18 +148,15 @@ void RasterTile::processAreaLabels(QList<TextItem*> &textItems)
if (!ti && !si)
continue;
if (!path.path.elementCount())
path.path = painterPath(path.poly);
const QImage *img = si ? &si->img() : 0;
const QFont *font = ti ? &ti->font() : 0;
const QColor *color = ti ? &ti->fillColor() : 0;
const QColor *hColor = ti ? haloColor(ti) : 0;
QPointF pos = path.labelPos.isNull()
? centroid(path.path) : ll2xy(path.labelPos);
QPointF pos = path.path->labelPos.isNull()
? centroid(path.pp) : ll2xy(path.path->labelPos);
TextPointItem *item = new TextPointItem(pos.toPoint(), label, font, img,
color, hColor, 0);
PointItem *item = new PointItem(pos.toPoint(), lbl, font, img, color,
hColor);
if (item->isValid() && _rect.contains(item->boundingRect().toRect())
&& !item->collides(textItems))
textItems.append(item);
@ -190,38 +165,39 @@ void RasterTile::processAreaLabels(QList<TextItem*> &textItems)
}
}
void RasterTile::processLineLabels(QList<TextItem*> &textItems)
void RasterTile::processLineLabels(QList<TextItem*> &textItems,
QVector<PainterPath> &paths)
{
const Style &s = style(_ratio);
QList<const Style::TextRender*> instructions(s.pathLabels(_zoom));
QSet<QString> set;
QSet<QByteArray> set;
for (int i = 0; i < instructions.size(); i++) {
const Style::TextRender *ri = instructions.at(i);
for (int j = 0; j < _paths.size(); j++) {
MapData::Path &path = _paths[j];
QString *label = 0;
bool limit = false;
for (int i = 0; i < paths.size(); i++) {
PainterPath &path = paths[i];
const QByteArray *lbl = label(ri->key(), path.path->tags);
if (!path.path.elementCount())
if (!lbl)
continue;
if (!ri->rule().match(path.closed, path.tags))
if (!ri->rule().match(path.path->closed, path.path->tags))
continue;
if (!(label = pathLabel(ri, path, &limit)))
continue;
if (limit && set.contains(path.label))
bool limit = (ri->key() == "ref" || ri->key() == "ele"
|| ri->key() == "ref_hike" || ri->key() == "ref_cycle"
|| ri->key() == "ref_mtb");
if (limit && set.contains(*lbl))
continue;
TextPathItem *item = new TextPathItem(path.path, label, _rect,
&ri->font(), &ri->fillColor(), haloColor(ri));
PathItem *item = new PathItem(path.pp, lbl, _rect, &ri->font(),
&ri->fillColor(), haloColor(ri));
if (item->isValid() && !item->collides(textItems)) {
textItems.append(item);
if (limit)
set.insert(path.label);
set.insert(*lbl);
} else
delete item;
}
}
}
}
@ -232,98 +208,121 @@ void RasterTile::drawTextItems(QPainter *painter,
textItems.at(i)->paint(painter);
}
QPainterPath RasterTile::painterPath(const Polygon &polygon) const
QPainterPath RasterTile::painterPath(const Polygon &polygon, bool curve) const
{
QPainterPath path;
for (int i = 0; i < polygon.size(); i++) {
const QVector<Coordinates> &subpath = polygon.at(i);
path.moveTo(ll2xy(subpath.first()));
for (int j = 1; j < subpath.size(); j++)
path.lineTo(ll2xy(subpath.at(j)));
if (curve) {
QPointF p1(ll2xy(subpath.first()));
QPointF p2(0, 0);
QPointF p3(0, 0);
path.moveTo(p1);
for (int j = 1; j < subpath.size(); j++) {
p3 = ll2xy(subpath.at(j));
p2 = QPointF((p1.x() + p3.x()) / 2.0, (p1.y() + p3.y()) / 2.0);
path.quadTo(p1, p2);
p1 = p3;
}
path.quadTo(p2, p3);
} else {
path.moveTo(ll2xy(subpath.first()));
for (int j = 1; j < subpath.size(); j++)
path.lineTo(ll2xy(subpath.at(j)));
}
}
return path;
}
QVector<RasterTile::PathInstruction> RasterTile::pathInstructions()
void RasterTile::pathInstructions(QVector<PainterPath> &paths,
QVector<RasterTile::RenderInstruction> &instructions)
{
QCache<Key, QVector<const Style::PathRender *> > cache(1024);
QVector<PathInstruction> instructions;
QCache<PathKey, QList<const Style::PathRender *> > cache(8192);
const Style &s = style(_ratio);
QVector<const Style::PathRender*> *ri;
QList<const Style::PathRender*> *ri;
for (int i = 0 ; i < _paths.size(); i++) {
MapData::Path &path = _paths[i];
for (int i = 0; i < _paths.size(); i++) {
const MapData::Path &path = _paths.at(i);
PainterPath &rp = paths[i];
PathKey key(_zoom, path.closed, path.tags);
Key key(_zoom, path.closed, path.tags);
QVector<const Style::PathRender*> *cached = cache.object(key);
if (!cached) {
ri = new QVector<const Style::PathRender*>(s.paths(_zoom,
path.closed, path.tags));
rp.path = &path;
if (!(ri = cache.object(key))) {
ri = new QList<const Style::PathRender*>(s.paths(_zoom, path.closed,
path.tags));
for (int j = 0; j < ri->size(); j++)
instructions.append(PathInstruction(ri->at(j), &path));
instructions.append(RenderInstruction(ri->at(j), &rp));
cache.insert(key, ri);
} else {
for (int j = 0; j < cached->size(); j++)
instructions.append(PathInstruction(cached->at(j), &path));
for (int j = 0; j < ri->size(); j++)
instructions.append(RenderInstruction(ri->at(j), &rp));
}
}
std::sort(instructions.begin(), instructions.end());
return instructions;
}
void RasterTile::drawPaths(QPainter *painter)
void RasterTile::circleInstructions(
QVector<RasterTile::RenderInstruction> &instructions)
{
QVector<PathInstruction> instructions(pathInstructions());
const Style::PathRender *lri = 0;
QCache<PointKey, QList<const Style::CircleRender *> > cache(8192);
const Style &s = style(_ratio);
QList<const Style::CircleRender*> *ri;
QPixmap layer(_pixmap.size());
layer.setDevicePixelRatio(_ratio);
layer.fill(Qt::transparent);
for (int i = 0; i < _points.size(); i++) {
const MapData::Point &point = _points.at(i);
PointKey key(_zoom, point.tags);
QPainter lp(&layer);
lp.setRenderHint(QPainter::Antialiasing);
lp.translate(-_rect.x(), -_rect.y());
lp.setCompositionMode(QPainter::CompositionMode_Source);
for (int i = 0; i < instructions.size(); i++) {
PathInstruction &is = instructions[i];
const Style::PathRender *ri = is.render();
if (lri && lri != ri) {
painter->drawPixmap(_rect.topLeft(), layer);
lp.fillRect(QRect(_rect.topLeft(), _pixmap.size()), Qt::transparent);
}
if (!is.path()->path.elementCount())
is.path()->path = painterPath(is.path()->poly);
if (ri->area()) {
lp.setPen(ri->pen(_zoom));
lp.setBrush(ri->brush());
lp.drawPath(is.path()->path);
lri = ri;
if (!(ri = cache.object(key))) {
ri = new QList<const Style::CircleRender*>(s.circles(_zoom, point.tags));
for (int j = 0; j < ri->size(); j++)
instructions.append(RenderInstruction(ri->at(j), &point));
cache.insert(key, ri);
} else {
painter->setPen(ri->pen(_zoom));
painter->setBrush(ri->brush());
painter->drawPath(is.path()->path);
lri = 0;
for (int j = 0; j < ri->size(); j++)
instructions.append(RenderInstruction(ri->at(j), &point));
}
}
}
if (lri)
painter->drawPixmap(_rect.topLeft(), layer);
void RasterTile::drawPaths(QPainter *painter, QVector<PainterPath> &paths)
{
QVector<RenderInstruction> instructions;
pathInstructions(paths, instructions);
circleInstructions(instructions);
std::sort(instructions.begin(), instructions.end());
for (int i = 0; i < instructions.size(); i++) {
const RenderInstruction &is = instructions.at(i);
PainterPath *path = is.path();
if (path) {
const Style::PathRender *ri = is.pathRender();
if (!path->pp.elementCount())
path->pp = painterPath(path->path->poly, ri->curve());
painter->setPen(ri->pen(_zoom));
painter->setBrush(ri->brush());
painter->drawPath(path->pp);
} else {
const Style::CircleRender *ri = is.circleRender();
qreal radius = ri->radius(_zoom);
painter->setPen(ri->pen());
painter->setBrush(ri->brush());
painter->drawEllipse(ll2xy(is.point()->coordinates), radius, radius);
}
}
}
void RasterTile::render()
{
std::sort(_points.begin(), _points.end());
QList<TextItem*> textItems;
QVector<PainterPath> renderPaths(_paths.size());
_pixmap.setDevicePixelRatio(_ratio);
_pixmap.fill(Qt::transparent);
@ -332,11 +331,11 @@ void RasterTile::render()
painter.setRenderHint(QPainter::Antialiasing);
painter.translate(-_rect.x(), -_rect.y());
drawPaths(&painter);
drawPaths(&painter, renderPaths);
processPointLabels(textItems);
processAreaLabels(textItems);
processLineLabels(textItems);
processAreaLabels(textItems, renderPaths);
processLineLabels(textItems, renderPaths);
drawTextItems(&painter, textItems);
//painter.setPen(Qt::red);

View File

@ -4,12 +4,11 @@
#include <QPixmap>
#include "map/projection.h"
#include "map/transform.h"
#include "map/textpointitem.h"
#include "map/textpathitem.h"
#include "style.h"
#include "mapdata.h"
class MapsforgeMap;
class TextItem;
namespace Mapsforge {
class RasterTile
@ -30,33 +29,77 @@ public:
void render();
private:
class PathInstruction
{
public:
PathInstruction() : _render(0), _path(0) {}
PathInstruction(const Style::PathRender *render, MapData::Path *path)
: _render(render), _path(path) {}
struct PainterPath {
PainterPath() : path(0) {}
bool operator<(const PathInstruction &other) const
{
if (_path->layer == other._path->layer)
return _render->zOrder() < other._render->zOrder();
else
return (_path->layer < other._path->layer);
}
const Style::PathRender *render() const {return _render;}
MapData::Path *path() {return _path;}
private:
const Style::PathRender *_render;
MapData::Path *_path;
QPainterPath pp;
const MapData::Path *path;
};
struct Key {
Key(int zoom, bool closed, const QVector<MapData::Tag> &tags)
struct PainterPoint {
PainterPoint(const MapData::Point *p, const QByteArray *lbl,
const Style::Symbol *si, const Style::TextRender *ti)
: p(p), lbl(lbl), ti(ti), si(si)
{
Q_ASSERT(si || ti);
}
bool operator<(const PainterPoint &other) const
{
if (priority() == other.priority())
return p->id < other.p->id;
else
return (priority() > other.priority());
}
int priority() const {return si ? si->priority() : ti->priority();}
const MapData::Point *p;
const QByteArray *lbl;
const Style::TextRender *ti;
const Style::Symbol *si;
};
class RenderInstruction
{
public:
RenderInstruction() : _pathRender(0), _circleRender(0), _path(0),
_point(0) {}
RenderInstruction(const Style::PathRender *render, PainterPath *path)
: _pathRender(render), _circleRender(0), _path(path), _point(0) {}
RenderInstruction(const Style::CircleRender *render,
const MapData::Point *point) : _pathRender(0), _circleRender(render),
_path(0), _point(point) {}
bool operator<(const RenderInstruction &other) const
{
if (layer() == other.layer())
return zOrder() < other.zOrder();
else
return (layer() < other.layer());
}
const Style::PathRender *pathRender() const {return _pathRender;}
const Style::CircleRender *circleRender() const {return _circleRender;}
PainterPath *path() const {return _path;}
const MapData::Point *point() const {return _point;}
private:
int layer() const {return _path ? _path->path->layer : _point->layer;}
int zOrder() const
{
return _pathRender ? _pathRender->zOrder() : _circleRender->zOrder();
}
const Style::PathRender *_pathRender;
const Style::CircleRender *_circleRender;
PainterPath *_path;
const MapData::Point *_point;
};
struct PathKey {
PathKey(int zoom, bool closed, const QVector<MapData::Tag> &tags)
: zoom(zoom), closed(closed), tags(tags) {}
bool operator==(const Key &other) const
bool operator==(const PathKey &other) const
{
return zoom == other.zoom && closed == other.closed
&& tags == other.tags;
@ -67,18 +110,54 @@ private:
const QVector<MapData::Tag> &tags;
};
friend HASH_T qHash(const RasterTile::Key &key);
friend HASH_T qHash(const RasterTile::PathInstruction &pi);
struct PointKey {
PointKey(int zoom, const QVector<MapData::Tag> &tags)
: zoom(zoom), tags(tags) {}
bool operator==(const PointKey &other) const
{
return zoom == other.zoom && tags == other.tags;
}
QVector<PathInstruction> pathInstructions();
int zoom;
const QVector<MapData::Tag> &tags;
};
class PointItem : public TextPointItem
{
public:
PointItem(const QPoint &point, const QByteArray *label,
const QFont *font, const QImage *img, const QColor *color,
const QColor *haloColor) : TextPointItem(point,
label ? new QString(*label) : 0, font, img, color, haloColor, 0) {}
~PointItem() {delete _text;}
};
class PathItem : public TextPathItem
{
public:
PathItem(const QPainterPath &line, const QByteArray *label,
const QRect &tileRect, const QFont *font, const QColor *color,
const QColor *haloColor) : TextPathItem(line,
label ? new QString(*label) : 0, tileRect, font, color, haloColor) {}
~PathItem() {delete _text;}
};
friend HASH_T qHash(const RasterTile::PathKey &key);
friend HASH_T qHash(const RasterTile::PointKey &key);
void pathInstructions(QVector<PainterPath> &paths,
QVector<RasterTile::RenderInstruction> &instructions);
void circleInstructions(QVector<RasterTile::RenderInstruction> &instructions);
QPointF ll2xy(const Coordinates &c) const
{return _transform.proj2img(_proj.ll2xy(c));}
void processPointLabels(QList<TextItem*> &textItems);
void processAreaLabels(QList<TextItem*> &textItems);
void processLineLabels(QList<TextItem*> &textItems);
QPainterPath painterPath(const Polygon &polygon) const;
void processAreaLabels(QList<TextItem*> &textItems,
QVector<PainterPath> &paths);
void processLineLabels(QList<TextItem*> &textItems,
QVector<PainterPath> &paths);
QPainterPath painterPath(const Polygon &polygon, bool curve) const;
void drawTextItems(QPainter *painter, const QList<TextItem*> &textItems);
void drawPaths(QPainter *painter);
void drawPaths(QPainter *painter, QVector<PainterPath> &paths);
Projection _proj;
Transform _transform;
@ -88,10 +167,16 @@ private:
QPixmap _pixmap;
QList<MapData::Path> _paths;
QList<MapData::Point> _points;
bool _valid;
};
inline HASH_T qHash(const RasterTile::Key &key)
inline HASH_T qHash(const RasterTile::PathKey &key)
{
return ::qHash(key.zoom) ^ ::qHash(key.tags);
}
inline HASH_T qHash(const RasterTile::PointKey &key)
{
return ::qHash(key.zoom) ^ ::qHash(key.tags);
}

View File

@ -79,10 +79,24 @@ bool Style::Rule::match(int zoom, bool closed,
return true;
}
bool Style::Rule::match(int zoom, const QVector<MapData::Tag> &tags) const
{
if (_type && NodeType != _type)
return false;
if (!_zooms.contains(zoom))
return false;
for (int i = 0; i < _filters.size(); i++)
if (!_filters.at(i).match(tags))
return false;
return true;
}
void Style::area(QXmlStreamReader &reader, const QString &dir, qreal ratio,
const Rule &rule)
{
PathRender ri(rule, _paths.size());
PathRender ri(rule, _paths.size() + _circles.size());
const QXmlStreamAttributes &attr = reader.attributes();
QString file;
int height = 0, width = 0;
@ -127,7 +141,7 @@ void Style::area(QXmlStreamReader &reader, const QString &dir, qreal ratio,
void Style::line(QXmlStreamReader &reader, const Rule &rule)
{
PathRender ri(rule, _paths.size());
PathRender ri(rule, _paths.size() + _circles.size());
const QXmlStreamAttributes &attr = reader.attributes();
bool ok;
@ -169,12 +183,60 @@ void Style::line(QXmlStreamReader &reader, const Rule &rule)
else if (join == "bevel")
ri._strokeJoin = Qt::BevelJoin;
}
if (attr.hasAttribute("curve")) {
QString curve(attr.value("curve").toString());
if (curve == "cubic")
ri._curve = true;
}
_paths.append(ri);
reader.skipCurrentElement();
}
void Style::circle(QXmlStreamReader &reader, const Rule &rule)
{
CircleRender ri(rule, _paths.size() + _circles.size());
const QXmlStreamAttributes &attr = reader.attributes();
bool ok;
QColor fillColor, strokeColor;
qreal strokeWidth = 0;
if (attr.hasAttribute("fill"))
fillColor = QColor(attr.value("fill").toString());
if (attr.hasAttribute("stroke"))
strokeColor = QColor(attr.value("stroke").toString());
if (attr.hasAttribute("stroke-width")) {
strokeWidth = attr.value("stroke-width").toFloat(&ok);
if (!ok || strokeWidth < 0) {
reader.raiseError("invalid stroke-width value");
return;
}
}
if (attr.hasAttribute("radius")) {
ri._radius = attr.value("radius").toDouble(&ok);
if (!ok || ri._radius <= 0) {
reader.raiseError("invalid radius value");
return;
}
} else {
reader.raiseError("missing radius");
return;
}
if (attr.hasAttribute("scale-radius")) {
if (attr.value("scale-radius").toString() == "true")
ri._scale = true;
}
ri._pen = (strokeColor.isValid() && strokeWidth > 0)
? QPen(QBrush(strokeColor), strokeWidth) : Qt::NoPen;
ri._brush = fillColor.isValid() ? QBrush(fillColor) : Qt::NoBrush;
_circles.append(ri);
reader.skipCurrentElement();
}
void Style::text(QXmlStreamReader &reader, const Rule &rule,
QList<QList<TextRender>*> &lists)
{
@ -183,6 +245,7 @@ void Style::text(QXmlStreamReader &reader, const Rule &rule,
int fontSize = 9;
bool bold = false, italic = false;
QString fontFamily("Helvetica");
QFont::Capitalization capitalization = QFont::MixedCase;
bool ok;
if (attr.hasAttribute("k"))
@ -223,11 +286,28 @@ void Style::text(QXmlStreamReader &reader, const Rule &rule,
else if (family == "serif")
fontFamily = "Times New Roman";
}
if (attr.hasAttribute("text-transform")) {
QString transform(attr.value("text-transform").toString());
if (transform == "uppercase")
capitalization = QFont::AllUppercase;
else if (transform == "lowercase")
capitalization = QFont::AllLowercase;
else if (transform == "capitalize")
capitalization = QFont::Capitalize;
}
if (attr.hasAttribute("priority")) {
ri._priority = attr.value("priority").toInt(&ok);
if (!ok) {
reader.raiseError("invalid priority value");
return;
}
}
ri._font.setFamily(fontFamily);
ri._font.setPixelSize(fontSize);
ri._font.setBold(bold);
ri._font.setItalic(italic);
ri._font.setCapitalization(capitalization);
if (fontSize)
for (int i = 0; i < lists.size(); i++)
@ -261,6 +341,13 @@ void Style::symbol(QXmlStreamReader &reader, const QString &dir, qreal ratio,
return;
}
}
if (attr.hasAttribute("priority")) {
ri._priority = attr.value("priority").toInt(&ok);
if (!ok) {
reader.raiseError("invalid priority value");
return;
}
}
if (!file.isNull())
ri._img = image(file, width, height, ratio);
@ -321,6 +408,8 @@ void Style::rule(QXmlStreamReader &reader, const QString &dir, qreal ratio,
area(reader, dir, ratio, r);
else if (reader.name() == QLatin1String("line"))
line(reader, r);
else if (reader.name() == QLatin1String("circle"))
circle(reader, r);
else if (reader.name() == QLatin1String("pathText")) {
QList<QList<TextRender>*> list;
list.append(&_pathLabels);
@ -416,10 +505,10 @@ Style::Style(const QString &path, qreal ratio)
loadXml(":/mapsforge/default.xml", ratio);
}
QVector<const Style::PathRender *> Style::paths(int zoom, bool closed,
QList<const Style::PathRender *> Style::paths(int zoom, bool closed,
const QVector<MapData::Tag> &tags) const
{
QVector<const PathRender*> ri;
QList<const PathRender*> ri;
for (int i = 0; i < _paths.size(); i++)
if (_paths.at(i).rule().match(zoom, closed, tags))
@ -428,6 +517,18 @@ QVector<const Style::PathRender *> Style::paths(int zoom, bool closed,
return ri;
}
QList<const Style::CircleRender *> Style::circles(int zoom,
const QVector<MapData::Tag> &tags) const
{
QList<const CircleRender*> ri;
for (int i = 0; i < _circles.size(); i++)
if (_circles.at(i).rule().match(zoom, tags))
ri.append(&_circles.at(i));
return ri;
}
QList<const Style::TextRender*> Style::pathLabels(int zoom) const
{
QList<const TextRender*> list;
@ -519,3 +620,8 @@ QBrush Style::PathRender::brush() const
else
return Qt::NoBrush;
}
qreal Style::CircleRender::radius(int zoom) const
{
return (_scale && zoom >= 12) ? pow(1.5, zoom - 12) * _radius : _radius;
}

View File

@ -33,6 +33,7 @@ public:
bool match(bool closed, const QVector<MapData::Tag> &tags) const;
bool match(int zoom, bool closed,
const QVector<MapData::Tag> &tags) const;
bool match(int zoom, const QVector<MapData::Tag> &tags) const;
private:
enum Type {
@ -163,12 +164,13 @@ public:
public:
PathRender(const Rule &rule, int zOrder) : Render(rule),
_zOrder(zOrder), _strokeWidth(0), _strokeCap(Qt::RoundCap),
_strokeJoin(Qt::RoundJoin), _area(false) {}
_strokeJoin(Qt::RoundJoin), _area(false), _curve(false) {}
int zOrder() const {return _zOrder;}
QPen pen(int zoom) const;
QBrush brush() const;
bool area() const {return _area;}
bool curve() const {return _curve;}
private:
friend class Style;
@ -180,25 +182,49 @@ public:
Qt::PenCapStyle _strokeCap;
Qt::PenJoinStyle _strokeJoin;
QImage _fillImage;
bool _area;
bool _area, _curve;
};
class CircleRender : public Render
{
public:
CircleRender(const Rule &rule, int zOrder) : Render(rule),
_zOrder(zOrder), _pen(Qt::NoPen), _brush(Qt::NoBrush),
_scale(false) {}
int zOrder() const {return _zOrder;}
const QPen &pen() const {return _pen;}
const QBrush &brush() const {return _brush;}
qreal radius(int zoom) const;
private:
friend class Style;
int _zOrder;
QPen _pen;
QBrush _brush;
qreal _radius;
bool _scale;
};
class TextRender : public Render
{
public:
TextRender(const Rule &rule)
: Render(rule), _fillColor(Qt::black), _strokeColor(Qt::black),
_strokeWidth(0) {}
: Render(rule), _priority(0), _fillColor(Qt::black),
_strokeColor(Qt::black), _strokeWidth(0) {}
const QFont &font() const {return _font;}
const QColor &fillColor() const {return _fillColor;}
const QColor &strokeColor() const {return _strokeColor;}
qreal strokeWidth() const {return _strokeWidth;}
const QByteArray &key() const {return _key;}
int priority() const {return _priority;}
private:
friend class Style;
int _priority;
QColor _fillColor, _strokeColor;
qreal _strokeWidth;
QFont _font;
@ -208,19 +234,23 @@ public:
class Symbol : public Render
{
public:
Symbol(const Rule &rule) : Render(rule) {}
Symbol(const Rule &rule) : Render(rule), _priority(0) {}
const QImage &img() const {return _img;}
int priority() const {return _priority;}
private:
friend class Style;
int _priority;
QImage _img;
};
Style(const QString &path, qreal ratio);
QVector<const PathRender *> paths(int zoom, bool closed,
QList<const PathRender *> paths(int zoom, bool closed,
const QVector<MapData::Tag> &tags) const;
QList<const CircleRender *> circles(int zoom,
const QVector<MapData::Tag> &tags) const;
QList<const TextRender*> pathLabels(int zoom) const;
QList<const TextRender*> pointLabels(int zoom) const;
@ -230,6 +260,7 @@ public:
private:
QList<PathRender> _paths;
QList<CircleRender> _circles;
QList<TextRender> _pathLabels, _pointLabels, _areaLabels;
QList<Symbol> _symbols;
@ -243,6 +274,7 @@ private:
void area(QXmlStreamReader &reader, const QString &dir, qreal ratio,
const Rule &rule);
void line(QXmlStreamReader &reader, const Rule &rule);
void circle(QXmlStreamReader &reader, const Rule &rule);
void text(QXmlStreamReader &reader, const Rule &rule,
QList<QList<TextRender> *> &lists);
void symbol(QXmlStreamReader &reader, const QString &dir, qreal ratio,

View File

@ -14,6 +14,7 @@ public:
: _file(file), _offset(offset), _size(size), _pos(-1),
_blockNum(-1), _blockPos(-1) {}
quint64 offset() const {return _offset;}
quint64 pos() const {return _pos;}
bool seek(quint64 pos);