1
0
mirror of https://github.com/tumic0/GPXSee.git synced 2025-07-09 00:34:27 +02:00

Compare commits

..

76 Commits
7.24 ... 7.29

Author SHA1 Message Date
cbe312d9c8 Version++ 2020-04-19 14:52:40 +02:00
08334d7fde Move the world maps bounds limit hack to the propper place 2020-04-19 11:36:17 +02:00
33bbd6a592 Yet another special case 2020-04-18 00:00:48 +02:00
7811527239 Rather show less road shields than more 2020-04-15 22:48:28 +02:00
31da4e1906 Some more default IMG style tweaking 2020-04-15 22:48:02 +02:00
652cbd7c11 Fixed Qt4 build 2020-04-09 10:17:30 +02:00
eb0ff84379 Code cleanup 2020-04-08 22:28:35 +02:00
6ee3a8ea8d Added support for FIT course points 2020-04-08 00:54:35 +02:00
ee3d43e249 A slightly darker white 2020-04-08 00:00:43 +02:00
242babb741 Improved default IMG style
("less green")
2020-04-07 00:54:31 +02:00
e26d1abd5a Version++ 2020-04-05 10:50:24 +02:00
e80d16bec5 Merge branch 'origin/master' into Weblate. 2020-04-05 10:39:16 +02:00
412ae74bfa Fixed broken map enable condition
(falsly enabled map can crash)
2020-04-05 10:38:16 +02:00
1c472e47b9 Translated using Weblate (Hungarian)
Currently translated at 100.0% (356 of 356 strings)

Translation: GPXSee/Translations
Translate-URL: https://hosted.weblate.org/projects/gpxsee/translations/hu/
2020-04-04 20:09:37 +02:00
4a725375e2 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (356 of 356 strings)

Translation: GPXSee/Translations
Translate-URL: https://hosted.weblate.org/projects/gpxsee/translations/uk/
2020-04-02 12:09:36 +02:00
383a196414 Translated using Weblate (Russian)
Currently translated at 100.0% (356 of 356 strings)

Translation: GPXSee/Translations
Translate-URL: https://hosted.weblate.org/projects/gpxsee/translations/ru/
2020-04-02 12:09:36 +02:00
f05b51efa6 Translated using Weblate (Finnish)
Currently translated at 100.0% (356 of 356 strings)

Translation: GPXSee/Translations
Translate-URL: https://hosted.weblate.org/projects/gpxsee/translations/fi/
2020-04-02 12:09:36 +02:00
a56c02953f Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.4% (354 of 356 strings)

Translation: GPXSee/Translations
Translate-URL: https://hosted.weblate.org/projects/gpxsee/translations/nb_NO/
2020-03-29 20:05:00 +02:00
00d3849e4f Translated using Weblate (French)
Currently translated at 100.0% (356 of 356 strings)

Translation: GPXSee/Translations
Translate-URL: https://hosted.weblate.org/projects/gpxsee/translations/fr/
2020-03-29 20:05:00 +02:00
c9244c0684 Fixed broken graph color change when secondary graphs present 2020-03-28 23:28:39 +01:00
d94938261a Version++ 2020-03-28 19:50:39 +01:00
d5fc06d9d1 Fixed remaining qWarning() format warning 2020-03-28 19:15:03 +01:00
9e7ebe930e Do not rescale the map on tile cache reload
(we do not reload the map parameters any more)
2020-03-28 16:12:15 +01:00
19bc509043 Merge remote-tracking branch 'weblate/master' 2020-03-27 23:30:44 +01:00
335794ee21 Translated using Weblate (German)
Currently translated at 100.0% (356 of 356 strings)

Translation: GPXSee/Translations
Translate-URL: https://hosted.weblate.org/projects/gpxsee/translations/de/
2020-03-27 23:10:10 +01:00
2404107d87 Translated using Weblate (German)
Currently translated at 100.0% (356 of 356 strings)

Translation: GPXSee/Translations
Translate-URL: https://hosted.weblate.org/projects/gpxsee/translations/de/
2020-03-27 23:10:09 +01:00
9447addd19 German translation 2020-03-27 23:09:43 +01:00
b1647d944c Silenced clang warnings 2020-03-27 23:09:13 +01:00
77ac919b83 Fixed broken error path reporting 2020-03-27 23:03:11 +01:00
4d652aeaff Merge branch 'origin/master' into Weblate. 2020-03-27 20:48:18 +01:00
3ef2361523 Removed obsolete stuff 2020-03-27 20:47:45 +01:00
e2b1c2c778 Merge branch 'origin/master' into Weblate. 2020-03-27 00:12:15 +01:00
1f5ecdfc38 Use a unicode character constant that works on all OSs (Windows) 2020-03-27 00:11:12 +01:00
c55b4f1217 Translated using Weblate (Swedish)
Currently translated at 100.0% (356 of 356 strings)

Translation: GPXSee/Translations
Translate-URL: https://hosted.weblate.org/projects/gpxsee/translations/sv/
2020-03-26 18:47:50 +01:00
fd71a4c7ce Translated using Weblate (Czech)
Currently translated at 100.0% (356 of 356 strings)

Translation: GPXSee/Translations
Translate-URL: https://hosted.weblate.org/projects/gpxsee/translations/cs/
2020-03-26 18:47:49 +01:00
0b6b09f226 Merge branch 'origin/master' into Weblate. 2020-03-25 23:33:35 +01:00
517ca89814 Localization update 2020-03-25 23:33:12 +01:00
455ec3a54b Merge branch 'origin/master' into Weblate. 2020-03-25 23:20:03 +01:00
8cb8d97ee2 Use the standard value of 3.5 in the outlier test 2020-03-25 23:19:30 +01:00
40e520d3bf Merge branch 'origin/master' into Weblate. 2020-03-25 23:12:37 +01:00
6b75442312 Removed obsolete code 2020-03-25 23:12:21 +01:00
cbc5b2466e Merge branch 'origin/master' into Weblate. 2020-03-25 23:10:04 +01:00
19a847c7d4 Enable simultaneous display of GPS and DEM data 2020-03-25 23:08:26 +01:00
f9e5cb206f Merge branch 'origin/master' into Weblate. 2020-03-24 22:06:34 +01:00
441c738d0f Allow IGC files with an A header of size 6 2020-03-24 22:06:03 +01:00
0bf6d41de6 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (354 of 354 strings)

Translation: GPXSee/Translations
Translate-URL: https://hosted.weblate.org/projects/gpxsee/translations/pt_BR/
2020-03-23 04:48:54 +01:00
b7869e985d Translated using Weblate (Russian)
Currently translated at 100.0% (354 of 354 strings)

Translation: GPXSee/Translations
Translate-URL: https://hosted.weblate.org/projects/gpxsee/translations/ru/
2020-03-18 16:45:24 +01:00
5152d5eb0b Translated using Weblate (Ukrainian)
Currently translated at 100.0% (354 of 354 strings)

Translation: GPXSee/Translations
Translate-URL: https://hosted.weblate.org/projects/gpxsee/translations/uk/
2020-03-18 16:45:23 +01:00
c0653ab0a8 Translated using Weblate (Finnish)
Currently translated at 100.0% (354 of 354 strings)

Translation: GPXSee/Translations
Translate-URL: https://hosted.weblate.org/projects/gpxsee/translations/fi/
2020-03-18 16:45:22 +01:00
41d27cabe2 Translated using Weblate (Swedish)
Currently translated at 100.0% (354 of 354 strings)

Translation: GPXSee/Translations
Translate-URL: https://hosted.weblate.org/projects/gpxsee/translations/sv/
2020-03-18 16:45:12 +01:00
e2f2e9700f Localization update 2020-03-17 21:13:21 +01:00
654bfcd058 Version++ 2020-03-17 21:10:45 +01:00
82c0c1f8a7 Asynchronous WMS/WMTS map loading
(also fixes crash on OS X)
2020-03-17 21:06:51 +01:00
9ce6e16b60 Fixed graph axis ticks when range < min range and units != m
Fixes #278
2020-03-14 23:55:57 +01:00
98cd3c3922 Merge branch 'master' of https://github.com/tumic0/GPXSee 2020-03-12 09:37:55 +01:00
a776f1d30e Added missing button group on OS X
fixes #276
2020-03-12 09:36:23 +01:00
aea17c9fed Added support for GPX comments (cmt tag)
Closes #272
2020-03-09 20:04:13 +01:00
23c18d4acd Optimization 2020-03-07 19:24:39 +01:00
eb8fc7b540 Merge branch 'origin/master' into Weblate. 2020-03-04 19:49:27 +01:00
bf0dd1b24a Version++ 2020-03-04 19:49:11 +01:00
9b687bb830 Merge branch 'origin/master' into Weblate. 2020-03-04 19:48:15 +01:00
9859608115 Added missing support for URLs defined in OnlineResources 2020-03-04 19:47:23 +01:00
3d66b2fbb6 Merge branch 'origin/master' into Weblate. 2020-03-03 09:39:27 +01:00
9f62b7114e The service parameter is expected in the GetMap request by some servers
(The WMS specification is not 100% clear here)
2020-03-03 09:38:18 +01:00
c8f7e6f691 Merge branch 'origin/master' into Weblate. 2020-03-03 09:29:47 +01:00
c85f404d28 Enable specifiing of format parameters 2020-03-03 09:29:16 +01:00
273a0f0f27 Merge branch 'origin/master' into Weblate. 2020-03-01 14:39:44 +01:00
bd3a3b90bc Merge branch 'origin/master' into Weblate. 2020-03-01 13:59:42 +01:00
440a5736f6 Merge branch 'origin/master' into Weblate. 2020-03-01 13:26:52 +01:00
f73c27c39c Merge branch 'origin/master' into Weblate. 2020-03-01 11:46:52 +01:00
3ec5c37fd5 Merge branch 'origin/master' into Weblate. 2020-03-01 11:43:41 +01:00
ef017edbf6 Merge branch 'origin/master' into Weblate. 2020-02-29 21:40:28 +01:00
dc03ab91d6 Merge branch 'origin/master' into Weblate. 2020-02-29 20:12:26 +01:00
497017091f Merge branch 'origin/master' into Weblate. 2020-02-29 13:48:19 +01:00
86535021aa Merge branch 'origin/master' into Weblate. 2020-02-23 12:45:59 +01:00
86a943d143 Translated using Weblate (Polish)
Currently translated at 98.5% (346 of 351 strings)

Translation: GPXSee/Translations
Translate-URL: https://hosted.weblate.org/projects/gpxsee/translations/pl/
2020-02-20 12:42:26 +01:00
83 changed files with 5389 additions and 4630 deletions

View File

@ -1,4 +1,4 @@
version: 7.24.{build}
version: 7.29.{build}
configuration:
- Release

View File

@ -3,7 +3,7 @@ unix:!macx {
} else {
TARGET = GPXSee
}
VERSION = 7.24
VERSION = 7.29
QT += core \
gui \
@ -20,6 +20,7 @@ equals(QT_MAJOR_VERSION, 5) : lessThan(QT_MINOR_VERSION, 4) {QT += opengl}
INCLUDEPATH += ./src
HEADERS += src/common/config.h \
src/GUI/graphicsscene.h \
src/GUI/mapaction.h \
src/GUI/popup.h \
src/common/garmin.h \
src/common/staticassert.h \
@ -248,6 +249,7 @@ SOURCES += src/main.cpp \
src/GUI/gearratiographitem.cpp \
src/GUI/mapview.cpp \
src/GUI/areaitem.cpp \
src/data/waypoint.cpp \
src/map/IMG/bitmapline.cpp \
src/map/IMG/bitstream.cpp \
src/map/IMG/deltastream.cpp \

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
; The name of the installer
Name "GPXSee"
; Program version
!define VERSION "7.24"
!define VERSION "7.29"
; The file to write
OutFile "GPXSee-${VERSION}.exe"

View File

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

View File

@ -5,7 +5,7 @@
CadenceGraphItem::CadenceGraphItem(const Graph &graph, GraphType type,
int width, const QColor &color, QGraphicsItem *parent)
: GraphItem(graph, type, width, color, parent)
: GraphItem(graph, type, width, color, Qt::SolidLine, parent)
{
}

View File

@ -65,35 +65,38 @@ void ElevationGraph::setInfo()
}
}
GraphItem *ElevationGraph::loadGraph(const Graph &graph, Type type)
GraphItem *ElevationGraph::loadGraph(const Graph &graph, PathType type,
const QColor &color, bool primary)
{
if (!graph.isValid()) {
_palette.nextColor();
if (!graph.isValid())
return 0;
}
ElevationGraphItem *gi = new ElevationGraphItem(graph, _graphType, _width,
_palette.nextColor());
color, primary ? Qt::SolidLine : Qt::DashLine);
gi->setUnits(_units);
if (type == Track) {
if (type == TrackPath) {
_tracks.append(gi);
if (_showTracks)
addGraph(gi);
_trackAscent += gi->ascent();
_trackDescent += gi->descent();
_trackMax = nMax(_trackMax, gi->max());
_trackMin = nMin(_trackMin, gi->min());
if (primary) {
_trackAscent += gi->ascent();
_trackDescent += gi->descent();
_trackMax = nMax(_trackMax, gi->max());
_trackMin = nMin(_trackMin, gi->min());
}
} else {
_routes.append(gi);
if (_showRoutes)
addGraph(gi);
_routeAscent += gi->ascent();
_routeDescent += gi->descent();
_routeMax = nMax(_routeMax, gi->max());
_routeMin = nMin(_routeMin, gi->min());
if (primary) {
_routeAscent += gi->ascent();
_routeDescent += gi->descent();
_routeMax = nMax(_routeMax, gi->max());
_routeMin = nMin(_routeMin, gi->min());
}
}
return gi;
@ -102,11 +105,32 @@ GraphItem *ElevationGraph::loadGraph(const Graph &graph, Type type)
QList<GraphItem*> ElevationGraph::loadData(const Data &data)
{
QList<GraphItem*> graphs;
GraphItem *primary, *secondary;
for (int i = 0; i < data.tracks().count(); i++)
graphs.append(loadGraph(data.tracks().at(i).elevation(), Track));
for (int i = 0; i < data.routes().count(); i++)
graphs.append(loadGraph(data.routes().at(i).elevation(), Route));
for (int i = 0; i < data.tracks().count(); i++) {
QColor color(_palette.nextColor());
const GraphPair &gp = data.tracks().at(i).elevation();
primary = loadGraph(gp.primary(), TrackPath, color, true);
secondary = primary
? loadGraph(gp.secondary(), TrackPath, color, false) : 0;
if (primary && secondary)
primary->setSecondaryGraph(secondary);
graphs.append(primary);
}
for (int i = 0; i < data.routes().count(); i++) {
QColor color(_palette.nextColor());
const GraphPair &gp = data.routes().at(i).elevation();
primary = loadGraph(gp.primary(), RoutePath, color, true);
secondary = primary
? loadGraph(gp.secondary(), RoutePath, color, false) : 0;
if (primary && secondary)
primary->setSecondaryGraph(secondary);
graphs.append(primary);
}
for (int i = 0; i < data.areas().count(); i++)
_palette.nextColor();

View File

@ -21,7 +21,7 @@ public:
void showRoutes(bool show);
private:
enum Type {Track, Route};
enum PathType {TrackPath, RoutePath};
qreal max() const;
qreal min() const;
@ -31,7 +31,8 @@ private:
void setYUnits(Units units);
void setInfo();
GraphItem *loadGraph(const Graph &graph, Type type);
GraphItem *loadGraph(const Graph &graph, PathType type, const QColor &color,
bool primary);
void showItems(const QList<ElevationGraphItem *> &list, bool show);
qreal _trackAscent, _trackDescent;

View File

@ -4,8 +4,8 @@
ElevationGraphItem::ElevationGraphItem(const Graph &graph, GraphType type,
int width, const QColor &color, QGraphicsItem *parent)
: GraphItem(graph, type, width, color, parent)
int width, const QColor &color, Qt::PenStyle style, QGraphicsItem *parent)
: GraphItem(graph, type, width, color, style, parent)
{
_min = GraphItem::min();
_max = GraphItem::max();
@ -42,5 +42,6 @@ QString ElevationGraphItem::info() const
tt.insert(tr("Minimum"), l.toString(min() * scale, 'f', 0)
+ UNIT_SPACE + su);
return tt.toString();
}

View File

@ -8,8 +8,10 @@ class ElevationGraphItem : public GraphItem
Q_OBJECT
public:
enum DataType {GPS, DEM};
ElevationGraphItem(const Graph &graph, GraphType type, int width,
const QColor &color, QGraphicsItem *parent = 0);
const QColor &color, Qt::PenStyle style, QGraphicsItem *parent = 0);
qreal ascent() const {return _ascent;}
qreal descent() const {return _descent;}

View File

@ -6,7 +6,7 @@
GearRatioGraphItem::GearRatioGraphItem(const Graph &graph, GraphType type,
int width, const QColor &color, QGraphicsItem *parent)
: GraphItem(graph, type, width, color, parent)
: GraphItem(graph, type, width, color, Qt::SolidLine, parent)
{
for (int i = 0; i < graph.size(); i++) {
const GraphSegment &segment = graph.at(i);

View File

@ -5,13 +5,13 @@
GraphItem::GraphItem(const Graph &graph, GraphType type, int width,
const QColor &color, QGraphicsItem *parent)
: GraphicsItem(parent), _graph(graph), _type(type)
const QColor &color, Qt::PenStyle style, QGraphicsItem *parent)
: GraphicsItem(parent), _graph(graph), _type(type), _secondaryGraph(0)
{
Q_ASSERT(_graph.isValid());
_units = Metric;
_pen = QPen(color, width);
_pen = QPen(color, width, style);
_sx = 0; _sy = 0;
_time = _graph.hasTime();
setZValue(2.0);

View File

@ -12,8 +12,8 @@ class GraphItem : public QObject, public GraphicsItem
Q_OBJECT
public:
GraphItem(const Graph &graph, GraphType type, int width, const QColor &color,
QGraphicsItem *parent = 0);
GraphItem(const Graph &graph, GraphType type, int width,
const QColor &color, Qt::PenStyle style, QGraphicsItem *parent = 0);
virtual ~GraphItem() {}
virtual QString info() const = 0;
@ -35,6 +35,9 @@ public:
void setWidth(int width);
void setUnits(Units units) {_units = units;}
GraphItem *secondaryGraph() const {return _secondaryGraph;}
void setSecondaryGraph(GraphItem *graph) {_secondaryGraph = graph;}
qreal yAtX(qreal x);
qreal distanceAtTime(qreal time);
@ -69,6 +72,8 @@ private:
qreal _sx, _sy;
QPen _pen;
bool _time;
GraphItem *_secondaryGraph;
};
#endif // GRAPHITEM_H

View File

@ -1,3 +1,4 @@
#include <QSet>
#include <QGraphicsScene>
#include <QEvent>
#include <QMouseEvent>
@ -274,8 +275,8 @@ void GraphView::redraw(const QSizeF &size)
rx = RangeF(bounds().left() * _xScale, bounds().right() * _xScale);
ry = RangeF(bounds().top() * _yScale + _yOffset, bounds().bottom() * _yScale
+ _yOffset);
if (ry.size() < _minYRange)
ry.resize(_minYRange);
if (ry.size() < _minYRange * _yScale)
ry.resize(_minYRange * _yScale);
_xAxis->setRange(rx);
_yAxis->setRange(ry);
@ -419,14 +420,16 @@ void GraphView::updateSliderInfo()
{
QLocale l(QLocale::system());
qreal r = 0, y = 0;
GraphItem *cardinal = (_graphs.count() == 1 || (_graphs.count() == 2
&& _graphs.first()->secondaryGraph())) ? _graphs.first() : 0;
if (_graphs.count() == 1) {
QRectF br(_graphs.first()->bounds());
if (cardinal) {
QRectF br(_bounds);
if (br.height() < _minYRange)
br.adjust(0, -(_minYRange/2 - br.height()/2), 0,
_minYRange/2 - br.height()/2);
y = _graphs.first()->yAtX(_sliderPos);
y = cardinal->yAtX(_sliderPos);
r = (y - br.bottom()) / br.height();
}
@ -436,11 +439,17 @@ void GraphView::updateSliderInfo()
_sliderInfo->setSide(s);
_sliderInfo->setPos(QPointF(0, _slider->boundingRect().height() * r));
_sliderInfo->setText(_graphType == Time ? Format::timeSpan(_sliderPos,
QString xText(_graphType == Time ? Format::timeSpan(_sliderPos,
bounds().width() > 3600) : l.toString(_sliderPos * _xScale, 'f', 1)
+ UNIT_SPACE + _xUnits, (_graphs.count() > 1) ? QString()
: l.toString(-y * _yScale + _yOffset, 'f', _precision) + UNIT_SPACE
+ _yUnits);
+ UNIT_SPACE + _xUnits);
QString yText((!cardinal) ? QString() : l.toString(-y * _yScale + _yOffset,
'f', _precision) + UNIT_SPACE + _yUnits);
if (cardinal && cardinal->secondaryGraph()) {
qreal delta = y - cardinal->secondaryGraph()->yAtX(_sliderPos);
yText += " " + QChar(0x0394) + l.toString(-delta * _yScale + _yOffset,
'f', _precision) + UNIT_SPACE + _yUnits;
}
_sliderInfo->setText(xText, yText);
}
void GraphView::emitSliderPositionChanged(const QPointF &pos)
@ -486,8 +495,23 @@ void GraphView::setPalette(const Palette &palette)
_palette = palette;
_palette.reset();
for (int i = 0; i < _graphs.count(); i++)
_graphs.at(i)->setColor(_palette.nextColor());
QSet<GraphItem*> secondary;
for (int i = 0; i < _graphs.count(); i++) {
GraphItem *g = _graphs[i];
if (g->secondaryGraph())
secondary.insert(g->secondaryGraph());
}
for (int i = 0; i < _graphs.count(); i++) {
GraphItem *g = _graphs[i];
if (secondary.contains(g))
continue;
QColor color(_palette.nextColor());
g->setColor(color);
if (g->secondaryGraph())
g->secondaryGraph()->setColor(color);
}
}
void GraphView::setGraphWidth(int width)

View File

@ -3,7 +3,6 @@
#include <QGraphicsView>
#include <QList>
#include <QSet>
#include "data/graph.h"
#include "palette.h"
#include "units.h"

View File

@ -51,6 +51,7 @@
#include "graphtab.h"
#include "graphitem.h"
#include "pathitem.h"
#include "mapaction.h"
#include "gui.h"
@ -58,7 +59,6 @@
GUI::GUI()
{
loadMaps();
loadPOIs();
createMapView();
@ -106,24 +106,13 @@ GUI::GUI()
updateStatusBarInfo();
}
void GUI::loadMaps()
{
_ml = new MapList(this);
QString mapDir(ProgramPaths::mapDir());
if (!mapDir.isNull() && !_ml->loadDir(mapDir))
qWarning("%s", qPrintable(_ml->errorPath() + ": " + _ml->errorString()));
_map = new EmptyMap(this);
}
void GUI::loadPOIs()
{
_poi = new POI(this);
QString poiDir(ProgramPaths::poiDir());
if (!poiDir.isNull() && !_poi->loadDir(poiDir))
qWarning("%s", qPrintable(_poi->errorString()));
QString poiDir(ProgramPaths::poiDir());
if (!poiDir.isNull())
_poi->loadDir(poiDir);
}
void GUI::createBrowser()
@ -134,40 +123,56 @@ void GUI::createBrowser()
void GUI::createMapActions()
{
_mapsSignalMapper = new QSignalMapper(this);
_mapsActionGroup = new QActionGroup(this);
_mapsActionGroup->setExclusive(true);
for (int i = 0; i < _ml->maps().count(); i++)
createMapAction(_ml->maps().at(i));
QString mapDir(ProgramPaths::mapDir());
if (mapDir.isNull())
return;
connect(_mapsSignalMapper, SIGNAL(mapped(int)), this,
SLOT(mapChanged(int)));
QString unused;
QList<Map*> maps(MapList::loadMaps(mapDir, unused));
for (int i = 0; i < maps.count(); i++) {
MapAction *a = createMapAction(maps.at(i));
connect(a, SIGNAL(loaded()), this, SLOT(mapInitialized()));
}
}
QAction *GUI::createMapAction(const Map *map)
MapAction *GUI::createMapAction(Map *map)
{
QAction *a = new QAction(map->name(), this);
MapAction *a = new MapAction(map);
a->setMenuRole(QAction::NoRole);
a->setCheckable(true);
a->setActionGroup(_mapsActionGroup);
_mapActions.append(a);
_mapsSignalMapper->setMapping(a, _mapActions.size() - 1);
connect(a, SIGNAL(triggered()), _mapsSignalMapper, SLOT(map()));
connect(a, SIGNAL(triggered()), this, SLOT(mapChanged()));
return a;
}
void GUI::mapInitialized()
{
MapAction *action = static_cast<MapAction*>(QObject::sender());
Map *map = action->data().value<Map*>();
if (map->isValid()) {
if (!_mapsActionGroup->checkedAction())
action->trigger();
_showMapAction->setEnabled(true);
_clearMapCacheAction->setEnabled(true);
} else {
qWarning("%s: %s", qPrintable(map->name()), qPrintable(map->errorString()));
action->deleteLater();
}
}
void GUI::createPOIFilesActions()
{
_poiFilesSignalMapper = new QSignalMapper(this);
connect(_poiFilesSignalMapper, SIGNAL(mapped(int)), this,
SLOT(poiFileChecked(int)));
for (int i = 0; i < _poi->files().count(); i++)
createPOIFileAction(_poi->files().at(i));
connect(_poiFilesSignalMapper, SIGNAL(mapped(int)), this,
SLOT(poiFileChecked(int)));
}
QAction *GUI::createPOIFileAction(const QString &fileName)
@ -281,8 +286,10 @@ void GUI::createActions()
createPOIFilesActions();
// Map actions
createMapActions();
_showMapAction = new QAction(QIcon(SHOW_MAP_ICON), tr("Show map"),
this);
_showMapAction->setEnabled(false);
_showMapAction->setMenuRole(QAction::NoRole);
_showMapAction->setCheckable(true);
_showMapAction->setShortcut(SHOW_MAP_SHORTCUT);
@ -294,10 +301,10 @@ void GUI::createActions()
_loadMapAction->setMenuRole(QAction::NoRole);
connect(_loadMapAction, SIGNAL(triggered()), this, SLOT(loadMap()));
_clearMapCacheAction = new QAction(tr("Clear tile cache"), this);
_clearMapCacheAction->setEnabled(false);
_clearMapCacheAction->setMenuRole(QAction::NoRole);
connect(_clearMapCacheAction, SIGNAL(triggered()), _mapView,
SLOT(clearMapCache()));
createMapActions();
_nextMapAction = new QAction(tr("Next map"), this);
_nextMapAction->setMenuRole(QAction::NoRole);
_nextMapAction->setShortcut(NEXT_MAP_SHORTCUT);
@ -308,10 +315,6 @@ void GUI::createActions()
_prevMapAction->setShortcut(PREV_MAP_SHORTCUT);
connect(_prevMapAction, SIGNAL(triggered()), this, SLOT(prevMap()));
addAction(_prevMapAction);
if (_ml->maps().isEmpty()) {
_showMapAction->setEnabled(false);
_clearMapCacheAction->setEnabled(false);
}
_showCoordinatesAction = new QAction(tr("Show cursor coordinates"), this);
_showCoordinatesAction->setMenuRole(QAction::NoRole);
_showCoordinatesAction->setCheckable(true);
@ -506,7 +509,7 @@ void GUI::createMenus()
#endif // Q_OS_MAC
_mapMenu = menuBar()->addMenu(tr("&Map"));
_mapMenu->addActions(_mapActions);
_mapMenu->addActions(_mapsActionGroup->actions());
_mapsEnd = _mapMenu->addSeparator();
_mapMenu->addAction(_loadMapAction);
_mapMenu->addAction(_clearMapCacheAction);
@ -608,6 +611,7 @@ void GUI::createToolBars()
void GUI::createMapView()
{
_map = new EmptyMap(this);
_mapView = new MapView(_map, _poi, this);
_mapView->setSizePolicy(QSizePolicy(QSizePolicy::Ignored,
QSizePolicy::Expanding));
@ -905,9 +909,14 @@ void GUI::openOptions()
Track::action(options.option); \
reload = true; \
}
#define SET_DATA_OPTION(option, action) \
#define SET_ROUTE_OPTION(option, action) \
if (options.option != _options.option) { \
Data::action(options.option); \
Route::action(options.option); \
reload = true; \
}
#define SET_WAYPOINT_OPTION(option, action) \
if (options.option != _options.option) { \
Waypoint::action(options.option); \
reload = true; \
}
@ -953,13 +962,18 @@ void GUI::openOptions()
SET_TRACK_OPTION(pauseSpeed, setPauseSpeed);
SET_TRACK_OPTION(pauseInterval, setPauseInterval);
SET_TRACK_OPTION(useReportedSpeed, useReportedSpeed);
SET_TRACK_OPTION(dataUseDEM, useDEM);
SET_TRACK_OPTION(showSecondaryElevation, showSecondaryElevation);
SET_TRACK_OPTION(showSecondarySpeed, showSecondarySpeed);
SET_DATA_OPTION(dataUseDEM, useDEM);
SET_ROUTE_OPTION(dataUseDEM, useDEM);
SET_ROUTE_OPTION(showSecondaryElevation, showSecondaryElevation);
SET_WAYPOINT_OPTION(dataUseDEM, useDEM);
SET_WAYPOINT_OPTION(showSecondaryElevation, showSecondaryElevation);
if (options.poiRadius != _options.poiRadius)
_poi->setRadius(options.poiRadius);
if (options.poiUseDEM != _options.poiUseDEM)
_poi->useDEM(options.poiUseDEM);
if (options.pixmapCache != _options.pixmapCache)
QPixmapCache::setCacheLimit(options.pixmapCache * 1024);
@ -1322,25 +1336,49 @@ void GUI::loadMap()
bool GUI::loadMap(const QString &fileName)
{
// On OS X fileName may be a directory!
if (fileName.isEmpty())
return false;
QFileInfo fi(fileName);
bool res = fi.isDir() ? _ml->loadDir(fileName) : _ml->loadFile(fileName);
QString error;
QList<Map*> maps = MapList::loadMaps(fileName, error);
if (maps.isEmpty()) {
error = tr("Error loading map:") + "\n\n"
+ fileName + "\n\n" + error;
QMessageBox::critical(this, APP_NAME, error);
return false;
}
if (res) {
QAction *a = createMapAction(_ml->maps().last());
for (int i = 0; i < maps.size(); i++) {
Map *map = maps.at(i);
MapAction *a = createMapAction(map);
_mapMenu->insertAction(_mapsEnd, a);
if (map->isReady()) {
a->trigger();
_showMapAction->setEnabled(true);
_clearMapCacheAction->setEnabled(true);
} else
connect(a, SIGNAL(loaded()), this, SLOT(mapLoaded()));
}
return true;
}
void GUI::mapLoaded()
{
MapAction *action = static_cast<MapAction*>(QObject::sender());
Map *map = action->data().value<Map*>();
if (map->isValid()) {
action->trigger();
_showMapAction->setEnabled(true);
_clearMapCacheAction->setEnabled(true);
a->trigger();
return true;
} else {
QString error = tr("Error loading map:") + "\n\n"
+ fileName + "\n\n" + _ml->errorString();
+ map->name() + "\n\n" + map->errorString();
QMessageBox::critical(this, APP_NAME, error);
return false;
action->deleteLater();
}
}
@ -1382,31 +1420,42 @@ void GUI::updateWindowTitle()
setWindowTitle(APP_NAME);
}
void GUI::mapChanged(int index)
void GUI::mapChanged()
{
_map = _ml->maps().at(index);
_map = _mapsActionGroup->checkedAction()->data().value<Map*>();
_mapView->setMap(_map);
}
void GUI::nextMap()
{
if (_ml->maps().count() < 2)
QAction *checked = _mapsActionGroup->checkedAction();
if (!checked)
return;
int next = (_ml->maps().indexOf(_map) + 1) % _ml->maps().count();
_mapActions.at(next)->setChecked(true);
mapChanged(next);
QList<QAction*> maps = _mapsActionGroup->actions();
for (int i = 1; i < maps.size(); i++) {
int next = (maps.indexOf(checked) + i) % maps.count();
if (maps.at(next)->isEnabled()) {
maps.at(next)->trigger();
break;
}
}
}
void GUI::prevMap()
{
if (_ml->maps().count() < 2)
QAction *checked = _mapsActionGroup->checkedAction();
if (!checked)
return;
int prev = (_ml->maps().indexOf(_map) + _ml->maps().count() - 1)
% _ml->maps().count();
_mapActions.at(prev)->setChecked(true);
mapChanged(prev);
QList<QAction*> maps = _mapsActionGroup->actions();
for (int i = 1; i < maps.size(); i++) {
int prev = (maps.indexOf(checked) + maps.count() - i) % maps.count();
if (maps.at(prev)->isEnabled()) {
maps.at(prev)->trigger();
break;
}
}
}
void GUI::poiFileChecked(int index)
@ -1812,10 +1861,14 @@ void GUI::writeSettings()
settings.setValue(USE_REPORTED_SPEED_SETTING, _options.useReportedSpeed);
if (_options.dataUseDEM != DATA_USE_DEM_DEFAULT)
settings.setValue(DATA_USE_DEM_SETTING, _options.dataUseDEM);
if (_options.showSecondaryElevation != SHOW_SECONDARY_ELEVATION_DEFAULT)
settings.setValue(SHOW_SECONDARY_ELEVATION_SETTING,
_options.showSecondaryElevation);
if (_options.showSecondarySpeed != SHOW_SECONDARY_SPEED_DEFAULT)
settings.setValue(SHOW_SECONDARY_SPEED_SETTING,
_options.showSecondarySpeed);
if (_options.poiRadius != POI_RADIUS_DEFAULT)
settings.setValue(POI_RADIUS_SETTING, _options.poiRadius);
if (_options.poiUseDEM != POI_USE_DEM_DEFAULT)
settings.setValue(POI_USE_DEM_SETTING, _options.poiUseDEM);
if (_options.useOpenGL != USE_OPENGL_DEFAULT)
settings.setValue(USE_OPENGL_SETTING, _options.useOpenGL);
#ifdef ENABLE_HTTP2
@ -1899,9 +1952,11 @@ void GUI::readSettings()
_showMapAction->setChecked(true);
else
_mapView->showMap(false);
if (_ml->maps().count()) {
int index = mapIndex(settings.value(CURRENT_MAP_SETTING).toString());
_mapActions.at(index)->trigger();
QAction *ma = mapAction(settings.value(CURRENT_MAP_SETTING).toString());
if (ma) {
ma->trigger();
_showMapAction->setEnabled(true);
_clearMapCacheAction->setEnabled(true);
}
if (settings.value(SHOW_COORDINATES_SETTING, SHOW_COORDINATES_DEFAULT)
.toBool()) {
@ -2078,14 +2133,18 @@ void GUI::readSettings()
USE_REPORTED_SPEED_DEFAULT).toBool();
_options.dataUseDEM = settings.value(DATA_USE_DEM_SETTING,
DATA_USE_DEM_DEFAULT).toBool();
_options.showSecondaryElevation = settings.value(
SHOW_SECONDARY_ELEVATION_SETTING,
SHOW_SECONDARY_ELEVATION_DEFAULT).toBool();
_options.showSecondarySpeed = settings.value(
SHOW_SECONDARY_SPEED_SETTING,
SHOW_SECONDARY_SPEED_DEFAULT).toBool();
_options.automaticPause = settings.value(AUTOMATIC_PAUSE_SETTING,
AUTOMATIC_PAUSE_DEFAULT).toBool();
_options.pauseInterval = settings.value(PAUSE_INTERVAL_SETTING,
PAUSE_INTERVAL_DEFAULT).toInt();
_options.poiRadius = settings.value(POI_RADIUS_SETTING, POI_RADIUS_DEFAULT)
.toInt();
_options.poiUseDEM = settings.value(POI_USE_DEM_SETTING,
POI_USE_DEM_DEFAULT).toBool();
_options.useOpenGL = settings.value(USE_OPENGL_SETTING, USE_OPENGL_DEFAULT)
.toBool();
#ifdef ENABLE_HTTP2
@ -2165,21 +2224,38 @@ void GUI::readSettings()
Track::setPauseSpeed(_options.pauseSpeed);
Track::setPauseInterval(_options.pauseInterval);
Track::useReportedSpeed(_options.useReportedSpeed);
Data::useDEM(_options.dataUseDEM);
Track::useDEM(_options.dataUseDEM);
Track::showSecondaryElevation(_options.showSecondaryElevation);
Track::showSecondarySpeed(_options.showSecondarySpeed);
Route::useDEM(_options.dataUseDEM);
Route::showSecondaryElevation(_options.showSecondaryElevation);
Waypoint::useDEM(_options.dataUseDEM);
Waypoint::showSecondaryElevation(_options.showSecondaryElevation);
_poi->setRadius(_options.poiRadius);
_poi->useDEM(_options.poiUseDEM);
QPixmapCache::setCacheLimit(_options.pixmapCache * 1024);
settings.endGroup();
}
int GUI::mapIndex(const QString &name)
QAction *GUI::mapAction(const QString &name)
{
for (int i = 0; i < _ml->maps().count(); i++)
if (_ml->maps().at(i)->name() == name)
return i;
QList<QAction *> maps = _mapsActionGroup->actions();
// Last map
for (int i = 0; i < maps.count(); i++) {
Map *map = maps.at(i)->data().value<Map*>();
if (map->name() == name && map->isReady())
return maps.at(i);
}
// Any usable map
for (int i = 0; i < maps.count(); i++) {
Map *map = maps.at(i)->data().value<Map*>();
if (map->isReady())
return maps.at(i);
}
return 0;
}

View File

@ -26,9 +26,9 @@ class FileBrowser;
class GraphTab;
class MapView;
class Map;
class MapList;
class POI;
class QScreen;
class MapAction;
class GUI : public QMainWindow
{
@ -64,7 +64,7 @@ private slots:
void prevMap();
void openOptions();
void mapChanged(int);
void mapChanged();
void graphChanged(int);
void poiFileChecked(int);
@ -88,16 +88,18 @@ private slots:
void screenChanged(QScreen *screen);
void logicalDotsPerInchChanged(qreal dpi);
void mapLoaded();
void mapInitialized();
private:
typedef QPair<QDate, QDate> DateRange;
void loadMaps();
void loadPOIs();
void closeFiles();
void plot(QPrinter *printer);
QAction *createPOIFileAction(const QString &fileName);
QAction *createMapAction(const Map *map);
MapAction *createMapAction(Map *map);
void createPOIFilesActions();
void createMapActions();
void createActions();
@ -127,7 +129,7 @@ private:
qreal distance() const;
qreal time() const;
qreal movingTime() const;
int mapIndex(const QString &name);
QAction *mapAction(const QString &name);
void readSettings();
void writeSettings();
@ -196,11 +198,9 @@ private:
QAction *_showCoordinatesAction;
QAction *_openOptionsAction;
QAction *_mapsEnd;
QList<QAction*> _mapActions;
QList<QAction*> _poiFilesActions;
QList<QAction*> _poiFilesActions;
QSignalMapper *_poiFilesSignalMapper;
QSignalMapper *_mapsSignalMapper;
QLabel *_fileNameLabel;
QLabel *_distanceLabel;
@ -212,7 +212,6 @@ private:
QList<GraphTab*> _tabs;
POI *_poi;
MapList *_ml;
Map *_map;
FileBrowser *_browser;

View File

@ -5,7 +5,7 @@
HeartRateGraphItem::HeartRateGraphItem(const Graph &graph, GraphType type,
int width, const QColor &color, QGraphicsItem *parent)
: GraphItem(graph, type, width, color, parent)
: GraphItem(graph, type, width, color, Qt::SolidLine, parent)
{
}

32
src/GUI/mapaction.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef MAPACTION_H
#define MAPACTION_H
#include <QAction>
#include "map/map.h"
class MapAction : public QAction
{
Q_OBJECT
public:
MapAction(Map *map, QObject *parent = 0) : QAction(map->name(), parent)
{
map->setParent(this);
setData(QVariant::fromValue(map));
setEnabled(map->isReady());
connect(map, SIGNAL(mapLoaded()), this, SLOT(mapLoaded()));
}
signals:
void loaded();
private slots:
void mapLoaded()
{
Map *map = data().value<Map*>();
setEnabled(map->isValid());
emit loaded();
}
};
#endif // MAPACTION_H

View File

@ -54,7 +54,7 @@ MapView::MapView(Map *map, POI *poi, QWidget *parent)
_map = map;
_map->load();
_map->setProjection(_projection);
connect(_map, SIGNAL(loaded()), this, SLOT(reloadMap()));
connect(_map, SIGNAL(tilesLoaded()), this, SLOT(reloadMap()));
_poi = poi;
connect(_poi, SIGNAL(pointsChanged()), this, SLOT(updatePOI()));
@ -316,7 +316,7 @@ void MapView::setMap(Map *map)
RectC cr(_map->xy2ll(vr.topLeft()), _map->xy2ll(vr.bottomRight()));
_map->unload();
disconnect(_map, SIGNAL(loaded()), this, SLOT(reloadMap()));
disconnect(_map, SIGNAL(tilesLoaded()), this, SLOT(reloadMap()));
_map = map;
_map->load();
@ -324,7 +324,7 @@ void MapView::setMap(Map *map)
#ifdef ENABLE_HIDPI
_map->setDevicePixelRatio(_deviceRatio, _mapRatio);
#endif // ENABLE_HIDPI
connect(_map, SIGNAL(loaded()), this, SLOT(reloadMap()));
connect(_map, SIGNAL(tilesLoaded()), this, SLOT(reloadMap()));
digitalZoom(0);
@ -451,10 +451,7 @@ void MapView::setCoordinatesFormat(CoordinatesFormat format)
void MapView::clearMapCache()
{
_map->clearCache();
fitMapZoom();
rescale();
centerOn(contentCenter());
reloadMap();
}
void MapView::digitalZoom(int zoom)

View File

@ -12,6 +12,7 @@
#include <QRadioButton>
#include <QLabel>
#include <QSysInfo>
#include <QButtonGroup>
#include "map/pcs.h"
#include "icons.h"
#include "colorbox.h"
@ -406,6 +407,8 @@ QWidget *OptionsDialog::createDataPage()
_reportedSpeed->setChecked(true);
else
_computedSpeed->setChecked(true);
_showSecondarySpeed = new QCheckBox(tr("Show secondary speed"));
_showSecondarySpeed->setChecked(_options->showSecondarySpeed);
_dataGPSElevation = new QRadioButton(tr("GPS data"));
_dataDEMElevation = new QRadioButton(tr("DEM data"));
@ -413,19 +416,28 @@ QWidget *OptionsDialog::createDataPage()
_dataDEMElevation->setChecked(true);
else
_dataGPSElevation->setChecked(true);
_showSecondaryElevation = new QCheckBox(tr("Show secondary elevation"));
_showSecondaryElevation->setChecked(_options->showSecondaryElevation);
QWidget *sourceTab = new QWidget();
QVBoxLayout *sourceTabLayout = new QVBoxLayout();
#ifdef Q_OS_MAC
QButtonGroup *speedGroup = new QButtonGroup(this);
speedGroup->addButton(_computedSpeed);
speedGroup->addButton(_reportedSpeed);
QVBoxLayout *speedOptions = new QVBoxLayout();
speedOptions->addWidget(_computedSpeed);
speedOptions->addWidget(_reportedSpeed);
speedOptions->addWidget(_showSecondarySpeed);
QButtonGroup *elevationGroup = new QButtonGroup(this);
elevationGroup->addButton(_dataGPSElevation);
elevationGroup->addButton(_dataDEMElevation);
QVBoxLayout *elevationOptions = new QVBoxLayout();
elevationOptions->addWidget(_dataGPSElevation);
elevationOptions->addWidget(_dataDEMElevation);
elevationOptions->addWidget(_showSecondaryElevation);
QFormLayout *formLayout = new QFormLayout();
formLayout->addRow(tr("Speed:"), speedOptions);
@ -438,12 +450,14 @@ QWidget *OptionsDialog::createDataPage()
speedLayout->addWidget(_computedSpeed);
speedLayout->addWidget(_reportedSpeed);
speedLayout->addWidget(_showSecondarySpeed);
QGroupBox *speedBox = new QGroupBox(tr("Speed"));
speedBox->setLayout(speedLayout);
elevationLayout->addWidget(_dataGPSElevation);
elevationLayout->addWidget(_dataDEMElevation);
elevationLayout->addWidget(_showSecondaryElevation);
QGroupBox *elevationBox = new QGroupBox(tr("Elevation"));
elevationBox->setLayout(elevationLayout);
@ -465,13 +479,6 @@ QWidget *OptionsDialog::createDataPage()
QWidget *OptionsDialog::createPOIPage()
{
_poiGPSElevation = new QRadioButton(tr("GPS data"));
_poiDEMElevation = new QRadioButton(tr("DEM data"));
if (_options->poiUseDEM)
_poiDEMElevation->setChecked(true);
else
_poiGPSElevation->setChecked(true);
_poiRadius = new QDoubleSpinBox();
_poiRadius->setSingleStep(1);
_poiRadius->setDecimals(1);
@ -486,13 +493,8 @@ QWidget *OptionsDialog::createPOIPage()
_poiRadius->setSuffix(UNIT_SPACE + tr("km"));
}
QVBoxLayout *elevationLayout = new QVBoxLayout();
elevationLayout->addWidget(_poiGPSElevation);
elevationLayout->addWidget(_poiDEMElevation);
QFormLayout *poiLayout = new QFormLayout();
poiLayout->addRow(tr("Radius:"), _poiRadius);
poiLayout->addRow(tr("Elevation:"), elevationLayout);
QWidget *poiTab = new QWidget();
poiTab->setLayout(poiLayout);
@ -718,13 +720,14 @@ void OptionsDialog::accept()
_options->pauseInterval = _pauseInterval->value();
_options->useReportedSpeed = _reportedSpeed->isChecked();
_options->dataUseDEM = _dataDEMElevation->isChecked();
_options->showSecondaryElevation = _showSecondaryElevation->isChecked();
_options->showSecondarySpeed = _showSecondarySpeed->isChecked();
qreal poiRadius = (_options->units == Imperial)
? _poiRadius->value() * MIINM : (_options->units == Nautical)
? _poiRadius->value() * NMIINM : _poiRadius->value() * KMINM;
if (qAbs(poiRadius - _options->poiRadius) > 0.01)
_options->poiRadius = poiRadius;
_options->poiUseDEM = _poiDEMElevation->isChecked();
_options->useOpenGL = _useOpenGL->isChecked();
#ifdef ENABLE_HTTP2

View File

@ -54,9 +54,10 @@ struct Options {
int pauseInterval;
bool useReportedSpeed;
bool dataUseDEM;
bool showSecondaryElevation;
bool showSecondarySpeed;
// POI
int poiRadius;
bool poiUseDEM;
// System
bool useOpenGL;
#ifdef ENABLE_HTTP2
@ -142,10 +143,10 @@ private:
QRadioButton *_reportedSpeed;
QRadioButton *_dataGPSElevation;
QRadioButton *_dataDEMElevation;
QCheckBox *_showSecondaryElevation;
QCheckBox *_showSecondarySpeed;
// POI
QDoubleSpinBox *_poiRadius;
QRadioButton *_poiGPSElevation;
QRadioButton *_poiDEMElevation;
// System
QSpinBox *_pixmapCache;
QSpinBox *_connectionTimeout;

View File

@ -5,7 +5,7 @@
PowerGraphItem::PowerGraphItem(const Graph &graph, GraphType type, int width,
const QColor &color, QGraphicsItem *parent)
: GraphItem(graph, type, width, color, parent)
: GraphItem(graph, type, width, color, Qt::SolidLine, parent)
{
}

View File

@ -15,6 +15,8 @@ QString RouteItem::info() const
tt.insert(tr("Name"), _name);
if (!_desc.isEmpty())
tt.insert(tr("Description"), _desc);
if (!_comment.isEmpty() && _comment != _desc)
tt.insert(tr("Comment"), _comment);
tt.insert(tr("Distance"), Format::distance(path().last().last().distance(),
_units));
if (!_links.isEmpty()) {
@ -43,6 +45,7 @@ RouteItem::RouteItem(const Route &route, Map *map, QGraphicsItem *parent)
_name = route.name();
_desc = route.description();
_comment = route.comment();
_links = route.links();
_coordinatesFormat = DecimalDegrees;
}

View File

@ -29,6 +29,7 @@ public:
private:
QString _name;
QString _desc;
QString _comment;
QVector<Link> _links;
CoordinatesFormat _coordinatesFormat;

View File

@ -145,10 +145,12 @@
#define USE_REPORTED_SPEED_DEFAULT false
#define DATA_USE_DEM_SETTING "dataUseDEM"
#define DATA_USE_DEM_DEFAULT false
#define SHOW_SECONDARY_ELEVATION_SETTING "showSecondaryElevation"
#define SHOW_SECONDARY_ELEVATION_DEFAULT false
#define SHOW_SECONDARY_SPEED_SETTING "showSecondarySpeed"
#define SHOW_SECONDARY_SPEED_DEFAULT false
#define POI_RADIUS_SETTING "poiRadius"
#define POI_RADIUS_DEFAULT (int)(IMPERIAL_UNITS() ? MIINM : KMINM)
#define POI_USE_DEM_SETTING "poiUseDEM"
#define POI_USE_DEM_DEFAULT false
#define USE_OPENGL_SETTING "useOpenGL"
#define USE_OPENGL_DEFAULT false
#define ENABLE_HTTP2_SETTING "enableHTTP2"

View File

@ -40,31 +40,46 @@ void SpeedGraph::setInfo()
clearInfo();
}
GraphItem *SpeedGraph::loadGraph(const Graph &graph, const Track &track,
const QColor &color, bool primary)
{
if (!graph.isValid())
return 0;
SpeedGraphItem *gi = new SpeedGraphItem(graph, _graphType, _width,
color, primary ? Qt::SolidLine : Qt::DashLine, track.movingTime());
gi->setTimeType(_timeType);
gi->setUnits(_units);
_tracks.append(gi);
if (_showTracks)
addGraph(gi);
if (primary) {
_avg.append(QPointF(track.distance(), gi->avg()));
_mavg.append(QPointF(track.distance(), gi->mavg()));
}
return gi;
}
QList<GraphItem*> SpeedGraph::loadData(const Data &data)
{
QList<GraphItem*> graphs;
for (int i = 0; i < data.tracks().count(); i++) {
GraphItem *primary, *secondary;
QColor color(_palette.nextColor());
const Track &track = data.tracks().at(i);
const Graph &graph = track.speed();
const GraphPair &gp = track.speed();
if (!graph.isValid()) {
_palette.nextColor();
graphs.append(0);
} else {
SpeedGraphItem *gi = new SpeedGraphItem(graph, _graphType, _width,
_palette.nextColor(), track.movingTime());
gi->setTimeType(_timeType);
gi->setUnits(_units);
primary = loadGraph(gp.primary(), track, color, true);
secondary = primary
? loadGraph(gp.secondary(), track, color, false) : 0;
if (primary && secondary)
primary->setSecondaryGraph(secondary);
_tracks.append(gi);
if (_showTracks)
addGraph(gi);
_avg.append(QPointF(track.distance(), gi->avg()));
_mavg.append(QPointF(track.distance(), gi->mavg()));
graphs.append(gi);
}
graphs.append(primary);
}
for (int i = 0; i < data.routes().count(); i++) {

View File

@ -5,6 +5,7 @@
#include "graphtab.h"
class SpeedGraphItem;
class Track;
class SpeedGraph : public GraphTab
{
@ -22,6 +23,8 @@ public:
void showTracks(bool show);
private:
GraphItem *loadGraph(const Graph &graph, const Track &track,
const QColor &color, bool primary);
qreal avg() const;
qreal max() const {return bounds().bottom();}
void setYUnits();

View File

@ -5,8 +5,8 @@
SpeedGraphItem::SpeedGraphItem(const Graph &graph, GraphType type, int width,
const QColor &color, qreal movingTime, QGraphicsItem *parent)
: GraphItem(graph, type, width, color, parent)
const QColor &color, Qt::PenStyle style, qreal movingTime,
QGraphicsItem *parent) : GraphItem(graph, type, width, color, style, parent)
{
_timeType = Total;

View File

@ -10,7 +10,8 @@ class SpeedGraphItem : public GraphItem
public:
SpeedGraphItem(const Graph &graph, GraphType type, int width,
const QColor &color, qreal movingTime, QGraphicsItem *parent = 0);
const QColor &color, Qt::PenStyle style, qreal movingTime,
QGraphicsItem *parent = 0);
qreal avg() const {return _avg;}
qreal mavg() const {return _mavg;}

View File

@ -5,7 +5,7 @@
TemperatureGraphItem::TemperatureGraphItem(const Graph &graph, GraphType type,
int width, const QColor &color, QGraphicsItem *parent)
: GraphItem(graph, type, width, color, parent)
: GraphItem(graph, type, width, color, Qt::SolidLine, parent)
{
_min = GraphItem::min();
_max = GraphItem::max();

View File

@ -13,6 +13,8 @@ QString TrackItem::info() const
tt.insert(tr("Name"), _name);
if (!_desc.isEmpty())
tt.insert(tr("Description"), _desc);
if (!_comment.isEmpty() && _comment != _desc)
tt.insert(tr("Comment"), _comment);
tt.insert(tr("Distance"), Format::distance(path().last().last().distance(),
_units));
if (_time > 0)
@ -41,6 +43,7 @@ TrackItem::TrackItem(const Track &track, Map *map, QGraphicsItem *parent)
{
_name = track.name();
_desc = track.description();
_comment = track.comment();
_links = track.links();
_date = track.date();
_time = track.time();

View File

@ -22,6 +22,7 @@ public:
private:
QString _name;
QString _desc;
QString _comment;
QVector<Link> _links;
QDateTime _date;
qreal _time;

View File

@ -21,15 +21,23 @@ QString WaypointItem::info() const
tt.insert(qApp->translate("WaypointItem", "Name"), _waypoint.name());
tt.insert(qApp->translate("WaypointItem", "Coordinates"),
Format::coordinates(_waypoint.coordinates(), _format));
if (_waypoint.hasElevation())
tt.insert(qApp->translate("WaypointItem", "Elevation"),
Format::elevation(_waypoint.elevation(), _units));
if (!std::isnan(_waypoint.elevations().first)) {
QString val = Format::elevation(_waypoint.elevations().first, _units);
if (!std::isnan(_waypoint.elevations().second))
val += " (" + Format::elevation(_waypoint.elevations().second,
_units) + ")";
tt.insert(qApp->translate("WaypointItem", "Elevation"), val);
}
if (_waypoint.timestamp().isValid())
tt.insert(qApp->translate("WaypointItem", "Date"),
_waypoint.timestamp().toString(Qt::SystemLocaleShortDate));
if (!_waypoint.description().isEmpty())
tt.insert(qApp->translate("WaypointItem", "Description"),
_waypoint.description());
if (!_waypoint.comment().isEmpty()
&& _waypoint.comment() != _waypoint.description())
tt.insert(qApp->translate("WaypointItem", "Comment"),
_waypoint.comment());
if (_waypoint.address().isValid()) {
QString addr("<address>");
addr += _waypoint.address().street();

View File

@ -20,7 +20,6 @@
#include "cupparser.h"
#include "gpiparser.h"
#include "smlparser.h"
#include "dem.h"
#include "data.h"
@ -73,49 +72,17 @@ static QMap<QString, Parser*> parsers()
return map;
}
QMap<QString, Parser*> Data::_parsers = parsers();
bool Data::_useDEM = false;
void Data::processData(QList<TrackData> &trackData, QList<RouteData> &routeData)
{
for (int i = 0; i < trackData.count(); i++) {
TrackData &track = trackData[i];
for (int j = 0; j < track.size(); j++) {
SegmentData &segment = track[j];
for (int k = 0; k < segment.size(); k++) {
Trackpoint &t = segment[k];
if (!t.hasElevation() || _useDEM) {
qreal elevation = DEM::elevation(t.coordinates());
if (!std::isnan(elevation))
t.setElevation(elevation);
}
}
}
for (int i = 0; i < trackData.count(); i++)
_tracks.append(Track(trackData.at(i)));
}
for (int i = 0; i < routeData.count(); i++) {
RouteData &route = routeData[i];
for (int j = 0; j < route.size(); j++) {
Waypoint &w = route[j];
if (!w.hasElevation() || _useDEM) {
qreal elevation = DEM::elevation(w.coordinates());
if (!std::isnan(elevation))
w.setElevation(elevation);
}
}
for (int i = 0; i < routeData.count(); i++)
_routes.append(Route(routeData.at(i)));
}
for (int i = 0; i < _waypoints.size(); i++) {
if (!_waypoints.at(i).hasElevation() || _useDEM) {
qreal elevation = DEM::elevation(_waypoints.at(i).coordinates());
if (!std::isnan(elevation))
_waypoints[i].setElevation(elevation);
}
}
}
Data::Data(const QString &fileName, bool poi)
Data::Data(const QString &fileName)
{
QFile file(fileName);
QFileInfo fi(fileName);
@ -134,8 +101,7 @@ Data::Data(const QString &fileName, bool poi)
if ((it = _parsers.find(fi.suffix().toLower())) != _parsers.end()) {
if (it.value()->parse(&file, trackData, routeData, _polygons,
_waypoints)) {
if (!poi)
processData(trackData, routeData);
processData(trackData, routeData);
_valid = true;
return;
} else {
@ -146,8 +112,7 @@ Data::Data(const QString &fileName, bool poi)
for (it = _parsers.begin(); it != _parsers.end(); it++) {
if (it.value()->parse(&file, trackData, routeData, _polygons,
_waypoints)) {
if (!poi)
processData(trackData, routeData);
processData(trackData, routeData);
_valid = true;
return;
}
@ -198,8 +163,3 @@ QStringList Data::filter()
return filter;
}
void Data::useDEM(bool use)
{
_useDEM = use;
}

View File

@ -14,7 +14,7 @@
class Data
{
public:
Data(const QString &fileName, bool poi = false);
Data(const QString &fileName);
bool isValid() const {return _valid;}
const QString &errorString() const {return _errorString;}
@ -28,8 +28,6 @@ public:
static QString formats();
static QStringList filter();
static void useDEM(bool use);
private:
void processData(QList<TrackData> &trackData, QList<RouteData> &routeData);
@ -43,7 +41,6 @@ private:
QVector<Waypoint> _waypoints;
static QMap<QString, Parser*> _parsers;
static bool _useDEM;
};
#endif // DATA_H

View File

@ -7,6 +7,7 @@
#define RECORD_MESSAGE 20
#define EVENT_MESSAGE 21
#define COURSE_POINT 32
#define TIMESTAMP_FIELD 253
class Event {
@ -48,10 +49,12 @@ public:
class FITParser::CTX {
public:
CTX(QFile *file) : file(file), len(0), endian(0), timestamp(0),
lastWrite(0), ratio(NAN) {}
CTX(QFile *file, QVector<Waypoint> &waypoints)
: file(file), waypoints(waypoints), len(0), endian(0), timestamp(0),
lastWrite(0), ratio(NAN) {}
QFile *file;
QVector<Waypoint> &waypoints;
quint32 len;
quint8 endian;
quint32 timestamp, lastWrite;
@ -61,6 +64,41 @@ public:
SegmentData segment;
};
static QMap<int, QString> coursePointDescInit()
{
QMap<int, QString> map;
map.insert(1, "Summit");
map.insert(2, "Valley");
map.insert(3, "Water");
map.insert(4, "Food");
map.insert(5, "Danger");
map.insert(6, "Left");
map.insert(7, "Right");
map.insert(8, "Straight");
map.insert(9, "First aid");
map.insert(10, "Fourth category");
map.insert(11, "Third category");
map.insert(12, "Second category");
map.insert(13, "First category");
map.insert(14, "Hors category");
map.insert(15, "Sprint");
map.insert(16, "Left fork");
map.insert(17, "Right fork");
map.insert(18, "Middle fork");
map.insert(19, "Slight left");
map.insert(20, "Sharp left");
map.insert(21, "Slight right");
map.insert(22, "Sharp right");
map.insert(23, "U-Turn");
map.insert(24, "Segment start");
map.insert(25, "Segment end");
return map;
}
static QMap<int, QString> coursePointDesc = coursePointDescInit();
bool FITParser::readData(QFile *file, char *data, size_t size)
{
@ -80,17 +118,12 @@ bool FITParser::readData(QFile *file, char *data, size_t size)
template<class T> bool FITParser::readValue(CTX &ctx, T &val)
{
T data;
if (!readData(ctx.file, (char*)&data, sizeof(T)))
if (!readData(ctx.file, (char*)&val, sizeof(T)))
return false;
ctx.len -= sizeof(T);
if (ctx.endian)
val = qFromBigEndian(data);
else
val = qFromLittleEndian(data);
if (sizeof(T) > 1)
val = (ctx.endian) ? qFromBigEndian(val) : qFromLittleEndian(val);
return true;
}
@ -167,41 +200,51 @@ bool FITParser::parseDefinitionMessage(CTX &ctx, quint8 header)
return true;
}
bool FITParser::readField(CTX &ctx, Field *field, quint32 &val)
bool FITParser::readField(CTX &ctx, Field *field, QVariant &val, bool &valid)
{
quint8 v8 = (quint8)-1;
quint16 v16 = (quint16)-1;
bool ret;
val = (quint32)-1;
#define VAL(type, inval) \
{type var; \
if (field->size == sizeof(var)) { \
ret = readValue(ctx, var); \
val = var; \
valid = (var != (inval)); \
} else { \
ret = skipValue(ctx, field->size); \
valid = false; \
}}
switch (field->type) {
case 0: // enum
case 1: // sint8
VAL(qint8, 0x7fU);
break;
case 2: // uint8
if (field->size == 1) {
ret = readValue(ctx, v8);
val = v8;
} else
ret = skipValue(ctx, field->size);
case 0: // enum
VAL(quint8, 0xffU);
break;
case 7: // UTF8 nul terminated string
{QByteArray ba(ctx.file->read(field->size));
ctx.len -= field->size;
ret = (ba.size() == field->size);
val = ret ? ba : QString();
valid = !ba.isEmpty();}
break;
case 0x83: // sint16
VAL(qint16, 0x7fffU);
break;
case 0x84: // uint16
if (field->size == 2) {
ret = readValue(ctx, v16);
val = v16;
} else
ret = skipValue(ctx, field->size);
VAL(quint16, 0xffffU);
break;
case 0x85: // sint32
VAL(qint32, 0x7fffffffU);
break;
case 0x86: // uint32
if (field->size == 4)
ret = readValue(ctx, val);
else
ret = skipValue(ctx, field->size);
VAL(quint32, 0xffffffffU);
break;
default:
ret = skipValue(ctx, field->size);
valid = false;
break;
}
@ -211,8 +254,10 @@ bool FITParser::readField(CTX &ctx, Field *field, quint32 &val)
bool FITParser::parseData(CTX &ctx, const MessageDefinition *def)
{
Field *field;
QVariant val;
bool valid;
Event event;
quint32 val;
Waypoint waypoint;
if (!def->fields && !def->devFields) {
@ -224,69 +269,79 @@ bool FITParser::parseData(CTX &ctx, const MessageDefinition *def)
for (int i = 0; i < def->numFields; i++) {
field = &def->fields[i];
if (!readField(ctx, field, val))
if (!readField(ctx, field, val, valid))
return false;
if (!valid)
continue;
if (field->id == TIMESTAMP_FIELD)
ctx.timestamp = val;
ctx.timestamp = val.toUInt();
else if (def->globalId == RECORD_MESSAGE) {
switch (field->id) {
case 0:
if (val != 0x7fffffff)
ctx.trackpoint.rcoordinates().setLat(
((qint32)val / (double)0x7fffffff) * 180);
ctx.trackpoint.rcoordinates().setLat(
(val.toInt() / (double)0x7fffffff) * 180);
break;
case 1:
if (val != 0x7fffffff)
ctx.trackpoint.rcoordinates().setLon(
((qint32)val / (double)0x7fffffff) * 180);
ctx.trackpoint.rcoordinates().setLon(
(val.toInt() / (double)0x7fffffff) * 180);
break;
case 2:
if (val != 0xffff)
ctx.trackpoint.setElevation((val / 5.0) - 500);
ctx.trackpoint.setElevation((val.toUInt() / 5.0) - 500);
break;
case 3:
if (val != 0xff)
ctx.trackpoint.setHeartRate(val);
ctx.trackpoint.setHeartRate(val.toUInt());
break;
case 4:
if (val != 0xff)
ctx.trackpoint.setCadence(val);
ctx.trackpoint.setCadence(val.toUInt());
break;
case 6:
if (val != 0xffff)
ctx.trackpoint.setSpeed(val / 1000.0f);
ctx.trackpoint.setSpeed(val.toUInt() / 1000.0f);
break;
case 7:
if (val != 0xffff)
ctx.trackpoint.setPower(val);
ctx.trackpoint.setPower(val.toUInt());
break;
case 13:
if (val != 0x7f)
ctx.trackpoint.setTemperature((qint8)val);
ctx.trackpoint.setTemperature(val.toInt());
break;
case 73:
if (val != 0xffffffff)
ctx.trackpoint.setSpeed(val / 1000.0f);
ctx.trackpoint.setSpeed(val.toUInt() / 1000.0f);
break;
case 78:
if (val != 0xffffffff)
ctx.trackpoint.setElevation((val / 5.0) - 500);
ctx.trackpoint.setElevation((val.toUInt() / 5.0) - 500);
break;
default:
break;
}
} else if (def->globalId == EVENT_MESSAGE) {
switch (field->id) {
case 0:
event.id = val;
event.id = val.toUInt();
break;
case 1:
event.type = val;
event.type = val.toUInt();
break;
case 3:
event.data = val;
event.data = val.toUInt();
break;
}
} else if (def->globalId == COURSE_POINT) {
switch (field->id) {
case 1:
waypoint.setTimestamp(QDateTime::fromTime_t(val.toUInt()
+ 631065600));
break;
case 2:
waypoint.rcoordinates().setLat(
(val.toInt() / (double)0x7fffffff) * 180);
break;
case 3:
waypoint.rcoordinates().setLon(
(val.toInt() / (double)0x7fffffff) * 180);
break;
case 5:
waypoint.setDescription(coursePointDesc.value(val.toUInt()));
break;
case 6:
waypoint.setName(val.toString());
break;
}
}
@ -294,7 +349,7 @@ bool FITParser::parseData(CTX &ctx, const MessageDefinition *def)
for (int i = 0; i < def->numDevFields; i++) {
field = &def->devFields[i];
if (!readField(ctx, field, val))
if (!readField(ctx, field, val, valid))
return false;
}
@ -315,7 +370,9 @@ bool FITParser::parseData(CTX &ctx, const MessageDefinition *def)
ctx.trackpoint = Trackpoint();
ctx.lastWrite = ctx.timestamp;
}
}
} else if (def->globalId == COURSE_POINT)
if (waypoint.coordinates().isValid())
ctx.waypoints.append(waypoint);
return true;
}
@ -381,9 +438,8 @@ bool FITParser::parse(QFile *file, QList<TrackData> &tracks,
QList<Area> &polygons, QVector<Waypoint> &waypoints)
{
Q_UNUSED(routes);
Q_UNUSED(waypoints);
Q_UNUSED(polygons);
CTX ctx(file);
CTX ctx(file, waypoints);
if (!parseHeader(ctx))

View File

@ -21,7 +21,7 @@ private:
bool readData(QFile *file, char *data, size_t size);
template<class T> bool readValue(CTX &ctx, T &val);
bool skipValue(CTX &ctx, quint8 size);
bool readField(CTX &ctx, Field *field, quint32 &val);
bool readField(CTX &ctx, Field *field, QVariant &val, bool &valid);
bool parseHeader(CTX &ctx);
bool parseRecord(CTX &ctx);

View File

@ -282,14 +282,14 @@ static quint32 readNotes(QDataStream &stream, QTextCodec *codec,
if (s1 & 0x1) {
QList<TranslatedString> obj;
ds += readTranslatedObjects(stream, codec, obj);
if (!obj.isEmpty() && waypoint.description().isNull())
waypoint.setDescription(obj.first().str());
if (!obj.isEmpty())
waypoint.setComment(obj.first().str());
}
if (s1 & 0x2) {
QString str;
ds += readString(stream, codec, str);
if (!str.isEmpty() && waypoint.description().isNull())
waypoint.setDescription(str);
if (!str.isEmpty())
waypoint.setComment(str);
}
if (ds != rh.size)

View File

@ -192,6 +192,8 @@ void GPXParser::waypointData(Waypoint &waypoint, SegmentData *autoRoute)
waypoint.setName(_reader.readElementText());
else if (_reader.name() == QLatin1String("desc"))
waypoint.setDescription(_reader.readElementText());
else if (_reader.name() == QLatin1String("cmt"))
waypoint.setComment(_reader.readElementText());
else if (_reader.name() == QLatin1String("ele"))
waypoint.setElevation(number());
else if (_reader.name() == QLatin1String("geoidheight"))
@ -244,6 +246,8 @@ void GPXParser::routepoints(RouteData &route, QList<TrackData> &tracks)
route.setName(_reader.readElementText());
else if (_reader.name() == QLatin1String("desc"))
route.setDescription(_reader.readElementText());
else if (_reader.name() == QLatin1String("cmt"))
route.setComment(_reader.readElementText());
else if (_reader.name() == QLatin1String("link")) {
Link l(link());
if (!l.URL().isEmpty())
@ -278,6 +282,8 @@ void GPXParser::track(TrackData &track)
track.setName(_reader.readElementText());
else if (_reader.name() == QLatin1String("desc"))
track.setDescription(_reader.readElementText());
else if (_reader.name() == QLatin1String("cmt"))
track.setComment(_reader.readElementText());
else if (_reader.name() == QLatin1String("link")) {
Link l(link());
if (!l.URL().isEmpty())

View File

@ -67,4 +67,17 @@ public:
}
};
class GraphPair
{
public:
GraphPair(const Graph &primary, const Graph &secondary)
: _primary(primary), _secondary(secondary) {}
const Graph &primary() const {return _primary;}
const Graph &secondary() const {return _secondary;}
private:
Graph _primary, _secondary;
};
#endif // GRAPH_H

View File

@ -87,10 +87,12 @@ static bool readTimestamp(const char *data, QTime &time)
static bool readARecord(const char *line, qint64 len)
{
if (len < 7 || line[0] != 'A')
/* The minimal A record length should be 7 according to the specification,
but records with length of 6 exist in the wild */
if (len < 6 || line[0] != 'A')
return false;
for (int i = 1; i < 7; i++)
for (int i = 1; i < 6; i++)
if (!::isprint(line[i]))
return false;
return true;

View File

@ -14,28 +14,19 @@ POI::POI(QObject *parent) : QObject(parent)
{
_errorLine = 0;
_radius = 1000;
_useDEM = false;
}
bool POI::loadFile(const QString &path, bool dir)
bool POI::loadFile(const QString &path)
{
Data data(path, true);
Data data(path);
FileIndex index;
index.enabled = true;
index.start = _data.size();
if (!data.isValid()) {
if (dir) {
if (data.errorLine())
_errorString += QString("%1:%2: %3\n").arg(path)
.arg(data.errorLine()).arg(data.errorString());
else
_errorString += path + ": " + data.errorString() + "\n";
} else {
_errorString = data.errorString();
_errorLine = data.errorLine();
}
_errorString = data.errorString();
_errorLine = data.errorLine();
return false;
}
@ -59,37 +50,22 @@ bool POI::loadFile(const QString &path, bool dir)
return true;
}
bool POI::loadFile(const QString &path)
{
_errorString.clear();
_errorLine = 0;
return loadFile(path, false);
}
bool POI::loadDir(const QString &path)
void POI::loadDir(const QString &path)
{
QDir md(path);
md.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
QFileInfoList fl = md.entryInfoList();
bool ret = true;
_errorString.clear();
_errorLine = 0;
for (int i = 0; i < fl.size(); i++) {
const QFileInfo &fi = fl.at(i);
if (fi.isDir()) {
if (!loadDir(fi.absoluteFilePath()))
ret = false;
} else {
if (!loadFile(fi.absoluteFilePath(), true))
ret = false;
}
if (fi.isDir())
loadDir(fi.absoluteFilePath());
else
if (!loadFile(fi.absoluteFilePath()))
qWarning("%s: %s", qPrintable(fi.absoluteFilePath()),
qPrintable(_errorString));
}
return ret;
}
static bool cb(size_t data, void* context)
@ -112,17 +88,6 @@ void POI::search(const RectC &rect, QSet<int> &set) const
_tree.Search(min, max, cb, &set);
}
void POI::appendElevation(QList<Waypoint> &points) const
{
for (int i = 0; i < points.size(); i++) {
if (!points.at(i).hasElevation() || _useDEM) {
qreal elevation = DEM::elevation(points.at(i).coordinates());
if (!std::isnan(elevation))
points[i].setElevation(elevation);
}
}
}
QList<Waypoint> POI::points(const Path &path) const
{
QList<Waypoint> ret;
@ -158,8 +123,6 @@ QList<Waypoint> POI::points(const Path &path) const
for (it = set.constBegin(); it != set.constEnd(); ++it)
ret.append(_data.at(*it));
appendElevation(ret);
return ret;
}
@ -181,8 +144,6 @@ QList<Waypoint> POI::points(const Waypoint &point) const
for (it = set.constBegin(); it != set.constEnd(); ++it)
ret.append(_data.at(*it));
appendElevation(ret);
return ret;
}
@ -206,8 +167,6 @@ QList<Waypoint> POI::points(const Area &area) const
for (it = set.constBegin(); it != set.constEnd(); ++it)
ret.append(_data.at(*it));
appendElevation(ret);
return ret;
}
@ -253,10 +212,3 @@ void POI::setRadius(unsigned radius)
emit pointsChanged();
}
void POI::useDEM(bool use)
{
_useDEM = use;
emit pointsChanged();
}

View File

@ -20,13 +20,12 @@ public:
POI(QObject *parent = 0);
bool loadFile(const QString &path);
bool loadDir(const QString &path);
void loadDir(const QString &path);
const QString &errorString() const {return _errorString;}
int errorLine() const {return _errorLine;}
unsigned radius() const {return _radius;}
void setRadius(unsigned radius);
void useDEM(bool use);
QList<Waypoint> points(const Path &path) const;
QList<Waypoint> points(const Waypoint &point) const;
@ -49,7 +48,6 @@ private:
bool loadFile(const QString &path, bool dir);
void search(const RectC &rect, QSet<int> &set) const;
void appendElevation(QList<Waypoint> &points) const;
POITree _tree;
QVector<Waypoint> _data;
@ -57,7 +55,6 @@ private:
QList<FileIndex> _indexes;
unsigned _radius;
bool _useDEM;
QString _errorString;
int _errorLine;

View File

@ -1,5 +1,8 @@
#include "dem.h"
#include "route.h"
bool Route::_useDEM = false;
bool Route::_show2ndElevation = false;
Route::Route(const RouteData &data) : _data(data)
{
@ -25,7 +28,7 @@ Path Route::path() const
return ret;
}
Graph Route::elevation() const
Graph Route::gpsElevation() const
{
Graph graph;
graph.append(GraphSegment());
@ -38,6 +41,38 @@ Graph Route::elevation() const
return graph;
}
Graph Route::demElevation() const
{
Graph graph;
graph.append(GraphSegment());
GraphSegment &gs = graph.last();
for (int i = 0; i < _data.size(); i++) {
qreal dem = DEM::elevation(_data.at(i).coordinates());
if (!std::isnan(dem))
gs.append(GraphPoint(_distance.at(i), NAN, dem));
}
return graph;
}
GraphPair Route::elevation() const
{
if (_useDEM) {
Graph dem(demElevation());
if (dem.isValid())
return GraphPair(dem, _show2ndElevation ? gpsElevation() : Graph());
else
return GraphPair(gpsElevation(), Graph());
} else {
Graph gps(gpsElevation());
if (gps.isValid())
return GraphPair(gps, _show2ndElevation ? demElevation() : Graph());
else
return GraphPair(demElevation(), Graph());
}
}
qreal Route::distance() const
{
return (_distance.isEmpty()) ? 0 : _distance.last();

View File

@ -11,23 +11,31 @@ class Route
public:
Route(const RouteData &data);
Path path() const;
const RouteData &data() const {return _data;}
Graph elevation() const;
Path path() const;
GraphPair elevation() const;
qreal distance() const;
const QString &name() const {return _data.name();}
const QString &description() const {return _data.description();}
const QString &comment() const {return _data.comment();}
const QVector<Link> &links() const {return _data.links();}
bool isValid() const {return _data.size() >= 2;}
static void useDEM(bool use) {_useDEM = use;}
static void showSecondaryElevation(bool show)
{_show2ndElevation = show;}
private:
Graph gpsElevation() const;
Graph demElevation() const;
RouteData _data;
QVector<qreal> _distance;
static bool _useDEM;
static bool _show2ndElevation;
};
#endif // ROUTE_H

View File

@ -11,15 +11,18 @@ class RouteData : public QVector<Waypoint>
public:
const QString &name() const {return _name;}
const QString &description() const {return _desc;}
const QString &comment() const {return _comment;}
const QVector<Link> &links() const {return _links;}
void setName(const QString &name) {_name = name;}
void setDescription(const QString &desc) {_desc = desc;}
void setComment(const QString &comment) {_comment = comment;}
void addLink(const Link &link) {_links.append(link);}
private:
QString _name;
QString _desc;
QString _comment;
QVector<Link> _links;
};

View File

@ -1,3 +1,4 @@
#include "dem.h"
#include "track.h"
@ -13,6 +14,9 @@ int Track::_pauseInterval = 10;
bool Track::_outlierEliminate = true;
bool Track::_useReportedSpeed = false;
bool Track::_useDEM = false;
bool Track::_show2ndElevation = false;
bool Track::_show2ndSpeed = false;
static qreal avg(const QVector<qreal> &v)
@ -47,7 +51,7 @@ static QSet<int> eliminate(const QVector<qreal> &v)
qreal M = MAD(w, m);
for (int i = 0; i < v.size(); i++)
if (qAbs((0.6745 * (v.at(i) - m)) / M) > 5)
if (qAbs((0.6745 * (v.at(i) - m)) / M) > 3.5)
rm.insert(i);
return rm;
@ -213,7 +217,7 @@ Track::Track(const TrackData &data) : _data(data), _pause(0)
}
}
Graph Track::elevation() const
Graph Track::gpsElevation() const
{
Graph ret;
@ -237,7 +241,48 @@ Graph Track::elevation() const
return ret;
}
Graph Track::speed() const
Graph Track::demElevation() const
{
Graph ret;
for (int i = 0; i < _data.size(); i++) {
const SegmentData &sd = _data.at(i);
if (sd.size() < 2)
continue;
const Segment &seg = _segments.at(i);
GraphSegment gs;
for (int j = 0; j < sd.size(); j++) {
qreal dem = DEM::elevation(sd.at(j).coordinates());
if (std::isnan(dem) || seg.outliers.contains(j))
continue;
gs.append(GraphPoint(seg.distance.at(j), seg.time.at(j), dem));
}
ret.append(filter(gs, _elevationWindow));
}
return ret;
}
GraphPair Track::elevation() const
{
if (_useDEM) {
Graph dem(demElevation());
if (dem.isValid())
return GraphPair(dem, _show2ndElevation ? gpsElevation() : Graph());
else
return GraphPair(gpsElevation(), Graph());
} else {
Graph gps(gpsElevation());
if (gps.isValid())
return GraphPair(gps, _show2ndElevation ? demElevation() : Graph());
else
return GraphPair(demElevation(), Graph());
}
}
Graph Track::computedSpeed() const
{
Graph ret;
@ -251,14 +296,10 @@ Graph Track::speed() const
qreal v;
for (int j = 0; j < sd.size(); j++) {
if (seg.stop.contains(j) && (!std::isnan(seg.speed.at(j))
|| sd.at(j).hasSpeed())) {
if (seg.stop.contains(j) && !std::isnan(seg.speed.at(j))) {
v = 0;
stop.append(gs.size());
} else if (_useReportedSpeed && sd.at(j).hasSpeed()
&& !seg.outliers.contains(j))
v = sd.at(j).speed();
else if (!std::isnan(seg.speed.at(j)) && !seg.outliers.contains(j))
} else if (!std::isnan(seg.speed.at(j)) && !seg.outliers.contains(j))
v = seg.speed.at(j);
else
continue;
@ -276,6 +317,60 @@ Graph Track::speed() const
return ret;
}
Graph Track::reportedSpeed() const
{
Graph ret;
for (int i = 0; i < _data.size(); i++) {
const SegmentData &sd = _data.at(i);
if (sd.size() < 2)
continue;
const Segment &seg = _segments.at(i);
GraphSegment gs;
QList<int> stop;
qreal v;
for (int j = 0; j < sd.size(); j++) {
if (seg.stop.contains(j) && sd.at(j).hasSpeed()) {
v = 0;
stop.append(gs.size());
} else if (sd.at(j).hasSpeed() && !seg.outliers.contains(j))
v = sd.at(j).speed();
else
continue;
gs.append(GraphPoint(seg.distance.at(j), seg.time.at(j), v));
}
ret.append(filter(gs, _speedWindow));
GraphSegment &filtered = ret.last();
for (int j = 0; j < stop.size(); j++)
filtered[stop.at(j)].setY(0);
}
return ret;
}
GraphPair Track::speed() const
{
if (_useReportedSpeed) {
Graph reported(reportedSpeed());
if (reported.isValid())
return GraphPair(reported, _show2ndSpeed ? computedSpeed()
: Graph());
else
return GraphPair(computedSpeed(), Graph());
} else {
Graph computed(computedSpeed());
if (computed.isValid())
return GraphPair(computed, _show2ndSpeed ? reportedSpeed()
: Graph());
else
return GraphPair(reportedSpeed(), Graph());
}
}
Graph Track::heartRate() const
{
Graph ret;

View File

@ -17,8 +17,8 @@ public:
Path path() const;
Graph elevation() const;
Graph speed() const;
GraphPair elevation() const;
GraphPair speed() const;
Graph heartRate() const;
Graph temperature() const;
Graph cadence() const;
@ -32,6 +32,7 @@ public:
const QString &name() const {return _data.name();}
const QString &description() const {return _data.description();}
const QString &comment() const {return _data.comment();}
const QVector<Link> &links() const {return _data.links();}
bool isValid() const;
@ -47,6 +48,11 @@ public:
static void setOutlierElimination(bool eliminate)
{_outlierEliminate = eliminate;}
static void useReportedSpeed(bool use) {_useReportedSpeed = use;}
static void useDEM(bool use) {_useDEM = use;}
static void showSecondaryElevation(bool show)
{_show2ndElevation = show;}
static void showSecondarySpeed(bool show)
{_show2ndSpeed = show;}
private:
struct Segment {
@ -59,6 +65,11 @@ private:
bool discardStopPoint(const Segment &seg, int i) const;
Graph demElevation() const;
Graph gpsElevation() const;
Graph reportedSpeed() const;
Graph computedSpeed() const;
TrackData _data;
QList<Segment> _segments;
qreal _pause;
@ -73,6 +84,9 @@ private:
static qreal _pauseSpeed;
static int _pauseInterval;
static bool _useReportedSpeed;
static bool _useDEM;
static bool _show2ndElevation;
static bool _show2ndSpeed;
};
#endif // TRACK_H

View File

@ -14,15 +14,18 @@ class TrackData : public QList<SegmentData>
public:
const QString &name() const {return _name;}
const QString &description() const {return _desc;}
const QString &comment() const {return _comment;}
const QVector<Link> &links() const {return _links;}
void setName(const QString &name) {_name = name;}
void setDescription(const QString &desc) {_desc = desc;}
void setComment(const QString &comment) {_comment = comment;}
void addLink(const Link &link) {_links.append(link);}
private:
QString _name;
QString _desc;
QString _comment;
QVector<Link> _links;
};

23
src/data/waypoint.cpp Normal file
View File

@ -0,0 +1,23 @@
#include "dem.h"
#include "waypoint.h"
bool Waypoint::_useDEM = false;
bool Waypoint::_show2ndElevation = false;
QPair<qreal, qreal> Waypoint::elevations() const
{
if (_useDEM) {
qreal dem = DEM::elevation(coordinates());
if (!std::isnan(dem))
return QPair<qreal, qreal>(dem, _show2ndElevation ? elevation()
: NAN);
else
return QPair<qreal, qreal>(elevation(), NAN);
} else {
if (hasElevation())
return QPair<qreal, qreal>(elevation(), _show2ndElevation
? DEM::elevation(coordinates()) : NAN);
else
return QPair<qreal, qreal>(DEM::elevation(coordinates()), NAN);
}
}

View File

@ -14,24 +14,29 @@
class Waypoint
{
public:
Waypoint() {_elevation = NAN;}
Waypoint(const Coordinates &coordinates) : _coordinates(coordinates)
{_elevation = NAN;}
Waypoint() : _elevation(NAN) {}
Waypoint(const Coordinates &coordinates)
: _coordinates(coordinates), _elevation(NAN) {}
const Coordinates &coordinates() const {return _coordinates;}
Coordinates &rcoordinates() {return _coordinates;}
const QString &name() const {return _name;}
const QString &description() const {return _description;}
const QString &comment() const {return _comment;}
const Address &address() const {return _address;}
const QVector<ImageInfo> &images() const {return _images;}
const QVector<Link> &links() const {return _links;}
const QDateTime &timestamp() const {return _timestamp;}
qreal elevation() const {return _elevation;}
QPair<qreal, qreal> elevations() const;
void setCoordinates(const Coordinates &coordinates)
{_coordinates = coordinates;}
void setName(const QString &name) {_name = name;}
void setDescription(const QString &description)
{_description = description;}
void setComment(const QString &comment) {_comment = comment;}
void setAddress(const Address &address) {_address = address;}
void setTimestamp(const QDateTime &timestamp) {_timestamp = timestamp;}
void setElevation(qreal elevation) {_elevation = elevation;}
@ -44,15 +49,23 @@ public:
{return this->_name == other._name
&& this->_coordinates == other._coordinates;}
static void useDEM(bool use) {_useDEM = use;}
static void showSecondaryElevation(bool show)
{_show2ndElevation = show;}
private:
Coordinates _coordinates;
QString _name;
QString _description;
QString _comment;
Address _address;
QVector<ImageInfo> _images;
QVector<Link> _links;
QDateTime _timestamp;
qreal _elevation;
static bool _useDEM;
static bool _show2ndElevation;
};
inline uint qHash(const Waypoint &key)
@ -64,11 +77,9 @@ inline uint qHash(const Waypoint &key)
inline QDebug operator<<(QDebug dbg, const Waypoint &waypoint)
{
dbg.nospace() << "Waypoint(" << waypoint.coordinates() << ", "
<< waypoint.name() << ", " << waypoint.description() << ")";
<< waypoint.name() << ")";
return dbg.space();
}
#endif // QT_NO_DEBUG
Q_DECLARE_TYPEINFO(Waypoint, Q_MOVABLE_TYPE);
#endif // WAYPOINT_H

View File

@ -1,6 +1,5 @@
#include <QXmlStreamReader>
#include <QDir>
#include "map/osm.h"
#include "vectortile.h"
#include "gmap.h"
@ -104,11 +103,6 @@ bool GMAP::loadTile(const QDir &dir, bool baseMap)
if (tile->zooms().min() < _zooms.min())
_zooms.setMin(tile->zooms().min());
// Limit world maps bounds so that the maps can be projected using
// the default Web Mercator projection
if (_bounds.height() > 120)
_bounds &= OSM::BOUNDS;
return true;
}

View File

@ -1,6 +1,5 @@
#include <QMap>
#include <QtEndian>
#include "map/osm.h"
#include "vectortile.h"
#include "img.h"
@ -169,11 +168,6 @@ IMG::IMG(const QString &fileName) : _file(fileName)
tile->markAsBasemap();
}
// Limit world maps bounds so that the maps can be projected using
// the default Web Mercator projection
if (_bounds.height() > 120)
_bounds &= OSM::BOUNDS;
if (!_tileTree.Count())
_errorString = "No usable map tile found";
else

View File

@ -151,7 +151,9 @@ Label LBLFile::label8b(Handle &hdl, quint32 offset, bool capitalize) const
if (!c || c == 0x1d)
break;
if ((c >= 0x1e && c <= 0x1f)) {
if (c == 0x1c)
capitalize = false;
else if ((c >= 0x1e && c <= 0x1f)) {
if (bap == &shieldLabel)
bap = &label;
else

View File

@ -25,12 +25,12 @@ void Style::defaultPolygonStyle()
_polygons[TYPE(0x12)] = Polygon(QBrush("#e6e2d9"));
_polygons[TYPE(0x13)] = Polygon(QBrush("#dbd0b6"),
QPen(QColor("#cdccc4"), 1));
_polygons[TYPE(0x14)] = Polygon(QBrush("#d4ebb8"));
_polygons[TYPE(0x15)] = Polygon(QBrush("#d4ebb8"));
_polygons[TYPE(0x14)] = Polygon(QBrush("#cadfaf"));
_polygons[TYPE(0x15)] = Polygon(QBrush("#cadfaf"));
_polygons[TYPE(0x16)] = Polygon(QBrush(QColor("#9ac269"),
Qt::BDiagPattern));
_polygons[TYPE(0x17)] = Polygon(QBrush("#d4ebb8"));
_polygons[TYPE(0x18)] = Polygon(QBrush("#d4ebb8"));
_polygons[TYPE(0x17)] = Polygon(QBrush("#e4efcf"));
_polygons[TYPE(0x18)] = Polygon(QBrush("#e3edc6"));
_polygons[TYPE(0x19)] = Polygon(QBrush("#e3edc6"), QPen("#c9d3a5"));
_polygons[TYPE(0x1a)] = Polygon(QBrush("#000000", Qt::Dense6Pattern),
QPen(QColor("#cdccc4"), 1));
@ -60,23 +60,23 @@ void Style::defaultPolygonStyle()
_polygons[TYPE(0x4a)] = Polygon(QBrush("#f1f0e5"), QPen("#f1f0e5"));
_polygons[TYPE(0x4c)] = Polygon(QBrush("#9fc4e1", Qt::Dense6Pattern));
_polygons[TYPE(0x4d)] = Polygon(QBrush("#ddf1fd"));
_polygons[TYPE(0x4e)] = Polygon(QBrush("#e3edc1"));
_polygons[TYPE(0x4f)] = Polygon(QBrush("#d4ebb8"));
_polygons[TYPE(0x50)] = Polygon(QBrush("#d4ebb8"));
_polygons[TYPE(0x4e)] = Polygon(QBrush("#f8f8f8"));
_polygons[TYPE(0x4f)] = Polygon(QBrush("#e4efcf"));
_polygons[TYPE(0x50)] = Polygon(QBrush("#cadfaf"));
_polygons[TYPE(0x51)] = Polygon(QBrush("#9fc4e1", Qt::Dense4Pattern));
_polygons[TYPE(0x52)] = Polygon(QBrush("#d4ebb8"));
_polygons[TYPE(0x52)] = Polygon(QBrush("#cadfaf"));
_drawOrder << TYPE(0x4b) << TYPE(0x4a) << TYPE(0x01) << TYPE(0x02)
<< TYPE(0x03) << TYPE(0x17) << TYPE(0x18) << TYPE(0x19) << TYPE(0x1a)
<< TYPE(0x28) << TYPE(0x29) << TYPE(0x32) << TYPE(0x3b) << TYPE(0x3c)
<< TYPE(0x3d) << TYPE(0x3e) << TYPE(0x3f) << TYPE(0x40) << TYPE(0x41)
<< TYPE(0x42) << TYPE(0x43) << TYPE(0x44) << TYPE(0x45) << TYPE(0x46)
<< TYPE(0x47) << TYPE(0x48) << TYPE(0x49) << TYPE(0x4c) << TYPE(0x4d)
<< TYPE(0x4e) << TYPE(0x4f) << TYPE(0x50) << TYPE(0x51) << TYPE(0x52)
<< TYPE(0x14) << TYPE(0x15) << TYPE(0x16) << TYPE(0x1e) << TYPE(0x1f)
<< TYPE(0x04) << TYPE(0x05) << TYPE(0x06) << TYPE(0x07) << TYPE(0x08)
<< TYPE(0x09) << TYPE(0x0a) << TYPE(0x0b) << TYPE(0x0c) << TYPE(0x0d)
<< TYPE(0x0e) << TYPE(0x0f) << TYPE(0x10) << TYPE(0x11) << TYPE(0x12)
<< TYPE(0x03) << TYPE(0x17) << TYPE(0x18) << TYPE(0x1a) << TYPE(0x28)
<< TYPE(0x29) << TYPE(0x32) << TYPE(0x3b) << TYPE(0x3c) << TYPE(0x3d)
<< TYPE(0x3e) << TYPE(0x3f) << TYPE(0x40) << TYPE(0x41) << TYPE(0x42)
<< TYPE(0x43) << TYPE(0x44) << TYPE(0x45) << TYPE(0x46) << TYPE(0x47)
<< TYPE(0x48) << TYPE(0x49) << TYPE(0x4c) << TYPE(0x4d) << TYPE(0x4e)
<< TYPE(0x4f) << TYPE(0x50) << TYPE(0x51) << TYPE(0x52) << TYPE(0x14)
<< TYPE(0x15) << TYPE(0x16) << TYPE(0x1e) << TYPE(0x1f) << TYPE(0x04)
<< TYPE(0x05) << TYPE(0x06) << TYPE(0x07) << TYPE(0x08) << TYPE(0x09)
<< TYPE(0x0a) << TYPE(0x0b) << TYPE(0x0c) << TYPE(0x0d) << TYPE(0x0e)
<< TYPE(0x0f) << TYPE(0x10) << TYPE(0x11) << TYPE(0x12) << TYPE(0x19)
<< TYPE(0x13);
}

View File

@ -16,6 +16,7 @@
#include "IMG/style.h"
#include "IMG/img.h"
#include "IMG/gmap.h"
#include "osm.h"
#include "pcs.h"
#include "rectd.h"
#include "imgmap.h"
@ -244,6 +245,11 @@ IMGMap::IMGMap(const QString &fileName, QObject *parent)
return;
}
// Limit world maps bounds so that the maps can be projected using
// the default Web Mercator projection
_dataBounds = (_data->bounds().height() > 120)
? _data->bounds() & OSM::BOUNDS : _data->bounds();
_zoom = _data->zooms().min();
updateTransform();
@ -260,13 +266,6 @@ void IMGMap::unload()
_data->clear();
}
QRectF IMGMap::bounds()
{
RectD prect(_data->bounds(), _projection);
return QRectF(_transform.proj2img(prect.topLeft()),
_transform.proj2img(prect.bottomRight()));
}
int IMGMap::zoomFit(const QSize &size, const RectC &rect)
{
if (rect.isValid()) {
@ -312,7 +311,7 @@ Transform IMGMap::transform(int zoom) const
{
double scale = _projection.isGeographic()
? 360.0 / (1<<zoom) : (2.0 * M_PI * WGS84_RADIUS) / (1<<zoom);
PointD topLeft(_projection.ll2xy(_data->bounds().topLeft()));
PointD topLeft(_projection.ll2xy(_dataBounds.topLeft()));
return Transform(ReferencePoint(PointD(0, 0), topLeft),
PointD(scale, scale));
}
@ -320,6 +319,10 @@ Transform IMGMap::transform(int zoom) const
void IMGMap::updateTransform()
{
_transform = transform(_zoom);
RectD prect(_dataBounds, _projection);
_bounds = QRectF(_transform.proj2img(prect.topLeft()),
_transform.proj2img(prect.bottomRight()));
}
QPointF IMGMap::ll2xy(const Coordinates &c)
@ -486,7 +489,7 @@ void IMGMap::processShields(QList<MapData::Poly> &lines, const QRect &tileRect,
= shields.constBegin(); it != shields.constEnd(); ++it) {
const QPolygonF &p = it.value();
QRectF rect(p.boundingRect() & tileRect);
if (qSqrt(AREA(rect)) < TILE_SIZE/8)
if (qSqrt(AREA(rect)) < TILE_SIZE/4)
continue;
QMap<qreal, int> map;

View File

@ -18,7 +18,7 @@ public:
QString name() const {return _data->name();}
QRectF bounds();
QRectF bounds() {return _bounds;}
virtual int zoom() const {return _zoom;}
virtual void setZoom(int zoom);
@ -62,6 +62,8 @@ private:
int _zoom;
Projection _projection;
Transform _transform;
QRectF _bounds;
RectC _dataBounds;
bool _valid;
QString _errorString;

View File

@ -50,12 +50,15 @@ public:
virtual void setProjection(const Projection &) {}
virtual bool isValid() const {return true;}
virtual bool isReady() const {return true;}
virtual QString errorString() const {return QString();}
signals:
void loaded();
void tilesLoaded();
void mapLoaded();
};
Q_DECLARE_METATYPE(Map*)
Q_DECLARE_OPERATORS_FOR_FLAGS(Map::Flags)
#endif // MAP_H

View File

@ -1,5 +1,6 @@
#include <QFileInfo>
#include <QDir>
#include <QApplication>
#include "atlas.h"
#include "ozimap.h"
#include "jnxmap.h"
@ -12,31 +13,8 @@
#include "maplist.h"
bool MapList::loadMap(Map *map, const QString &path)
{
if (map && map->isValid()) {
_maps.append(map);
return true;
} else {
_errorPath = path;
_errorString = (map) ? map->errorString() : "Unknown file format";
return false;
}
}
Map *MapList::loadSource(const QString &path)
{
Map *map = MapSource::loadMap(path, _errorString);
if (!map)
_errorPath = path;
else
map->setParent(this);
return map;
}
bool MapList::loadFile(const QString &path, bool *terminate)
Map *MapList::loadFile(const QString &path, QString &errorString,
bool *terminate)
{
QFileInfo fi(path);
QString suffix = fi.suffix().toLower();
@ -45,75 +23,95 @@ bool MapList::loadFile(const QString &path, bool *terminate)
if (Atlas::isAtlas(path)) {
if (terminate)
*terminate = true;
map = new Atlas(path, this);
map = new Atlas(path);
} else if (suffix == "xml") {
if (MapSource::isMap(path) && !(map = loadSource(path)))
return false;
else if (GMAP::isGMAP(path)) {
if (MapSource::isMap(path)) {
if (!(map = MapSource::loadMap(path, errorString)))
return 0;
} else if (GMAP::isGMAP(path)) {
if (terminate)
*terminate = true;
map = new IMGMap(path, this);
map = new IMGMap(path);
}
} else if (suffix == "jnx")
map = new JNXMap(path, this);
map = new JNXMap(path);
else if (suffix == "tif" || suffix == "tiff")
map = new GeoTIFFMap(path, this);
map = new GeoTIFFMap(path);
else if (suffix == "mbtiles")
map = new MBTilesMap(path, this);
map = new MBTilesMap(path);
else if (suffix == "rmap" || suffix == "rtmap")
map = new RMap(path, this);
map = new RMap(path);
else if (suffix == "img")
map = new IMGMap(path, this);
map = new IMGMap(path);
else if (suffix == "map" || suffix == "tar")
map = new OziMap(path, this);
map = new OziMap(path);
if (!loadMap(map, path)) {
if (map && map->isValid())
return map;
else {
errorString = (map) ? map->errorString() : "Unknown file format";
delete map;
return false;
return 0;
}
return true;
}
bool MapList::loadDir(const QString &path)
QList<Map*> MapList::loadDir(const QString &path, QString &errorString)
{
QDir md(path);
md.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
md.setSorting(QDir::DirsLast);
QFileInfoList ml = md.entryInfoList();
bool ret = true;
QList<Map*> list;
for (int i = 0; i < ml.size(); i++) {
const QFileInfo &fi = ml.at(i);
QString suffix = fi.suffix().toLower();
bool terminate = false;
if (fi.isDir() && fi.fileName() != "set") {
if (!loadDir(fi.absoluteFilePath()))
ret = false;
} else if (filter().contains("*." + suffix)) {
if (!loadFile(fi.absoluteFilePath(), &terminate))
ret = false;
if (fi.isDir() && fi.fileName() != "set")
list.append(loadDir(fi.absoluteFilePath(), errorString));
else if (filter().contains("*." + suffix)) {
Map *map = loadFile(fi.absoluteFilePath(), errorString, &terminate);
if (map)
list.append(map);
else
qWarning("%s: %s", qPrintable(fi.absoluteFilePath()),
qPrintable(errorString));
if (terminate)
break;
}
}
return ret;
return list;
}
QList<Map*> MapList::loadMaps(const QString &path, QString &errorString)
{
if (QFileInfo(path).isDir())
return loadDir(path, errorString);
else {
QList<Map*> list;
Map *map = loadFile(path, errorString, 0);
if (map)
list.append(map);
return list;
}
}
QString MapList::formats()
{
return
tr("Supported files") + " (" + filter().join(" ") + ");;"
+ tr("Garmin IMG maps") + " (*.gmap *.gmapi *.img *.xml);;"
+ tr("Garmin JNX maps") + " (*.jnx);;"
+ tr("OziExplorer maps") + " (*.map);;"
+ tr("MBTiles maps") + " (*.mbtiles);;"
+ tr("TrekBuddy maps/atlases") + " (*.tar *.tba);;"
+ tr("GeoTIFF images") + " (*.tif *.tiff);;"
+ tr("TwoNav maps") + " (*.rmap *.rtmap);;"
+ tr("Online map sources") + " (*.xml)";
qApp->translate("MapList", "Supported files")
+ " (" + filter().join(" ") + ");;"
+ qApp->translate("MapList", "Garmin IMG maps")
+ " (*.gmap *.gmapi *.img *.xml);;"
+ qApp->translate("MapList", "Garmin JNX maps") + " (*.jnx);;"
+ qApp->translate("MapList", "OziExplorer maps") + " (*.map);;"
+ qApp->translate("MapList", "MBTiles maps") + " (*.mbtiles);;"
+ qApp->translate("MapList", "TrekBuddy maps/atlases") + " (*.tar *.tba);;"
+ qApp->translate("MapList", "GeoTIFF images") + " (*.tif *.tiff);;"
+ qApp->translate("MapList", "TwoNav maps") + " (*.rmap *.rtmap);;"
+ qApp->translate("MapList", "Online map sources") + " (*.xml)";
}
QStringList MapList::filter()

View File

@ -1,36 +1,21 @@
#ifndef MAPLIST_H
#define MAPLIST_H
#include <QObject>
#include <QString>
class Map;
class MapList : public QObject
class MapList
{
Q_OBJECT
public:
MapList(QObject *parent = 0) : QObject(parent) {}
bool loadFile(const QString &path, bool *terminate = 0);
bool loadDir(const QString &path);
const QList<Map*> &maps() const {return _maps;}
const QString &errorString() const {return _errorString;}
const QString &errorPath() const {return _errorPath;}
static QList<Map*> loadMaps(const QString &path, QString &errorString);
static QString formats();
static QStringList filter();
private:
Map *loadSource(const QString &path);
bool loadMap(Map *map, const QString &path);
QList<Map*> _maps;
QString _errorString;
QString _errorPath;
static Map *loadFile(const QString &path, QString &errorString,
bool *terminate);
static QList<Map*> loadDir(const QString &path, QString &errorString);
};
#endif // MAPLIST_H

View File

@ -21,7 +21,7 @@ OnlineMap::OnlineMap(const QString &name, const QString &url,
_tileLoader->setUrl(url);
_tileLoader->setAuthorization(authorization);
_tileLoader->setQuadTiles(quadTiles);
connect(_tileLoader, SIGNAL(finished()), this, SIGNAL(loaded()));
connect(_tileLoader, SIGNAL(finished()), this, SIGNAL(tilesLoaded()));
}
QRectF OnlineMap::bounds()

View File

@ -8,6 +8,11 @@
#include "wms.h"
static QString bareFormat(const QString &format)
{
return format.left(format.indexOf(';')).trimmed();
}
static inline double hint2denominator(double h)
{
/* Some WMS 1.1.1 servers use a 72dpi resolution by default. Using the usual
@ -33,14 +38,48 @@ WMS::CTX::CTX(const Setup &setup) : setup(setup), formatSupported(false)
}
}
void WMS::get(QXmlStreamReader &reader, CTX &ctx)
{
while (reader.readNextStartElement()) {
if (reader.name() == "OnlineResource") {
QXmlStreamAttributes attr = reader.attributes();
ctx.url = attr.value("xlink:href").toString();
reader.skipCurrentElement();
} else
reader.skipCurrentElement();
}
}
void WMS::http(QXmlStreamReader &reader, CTX &ctx)
{
while (reader.readNextStartElement()) {
if (reader.name() == "Get")
get(reader, ctx);
else
reader.skipCurrentElement();
}
}
void WMS::dcpType(QXmlStreamReader &reader, CTX &ctx)
{
while (reader.readNextStartElement()) {
if (reader.name() == "HTTP")
http(reader, ctx);
else
reader.skipCurrentElement();
}
}
void WMS::getMap(QXmlStreamReader &reader, CTX &ctx)
{
while (reader.readNextStartElement()) {
if (reader.name() == "Format") {
QString format(reader.readElementText());
if (format.left(format.indexOf(';')) == ctx.setup.format())
if (bareFormat(format) == bareFormat(ctx.setup.format()))
ctx.formatSupported = true;
} else
} else if (reader.name() == "DCPType")
dcpType(reader, ctx);
else
reader.skipCurrentElement();
}
}
@ -179,10 +218,10 @@ void WMS::capabilities(QXmlStreamReader &reader, CTX &ctx)
}
}
bool WMS::parseCapabilities(const QString &path, const Setup &setup)
bool WMS::parseCapabilities()
{
QFile file(path);
CTX ctx(setup);
QFile file(_path);
CTX ctx(_setup);
QXmlStreamReader reader;
@ -205,7 +244,7 @@ bool WMS::parseCapabilities(const QString &path, const Setup &setup)
reader.raiseError("Not a WMS Capabilities XML file");
}
if (reader.error()) {
_errorString = QString("%1:%2: %3").arg(path).arg(reader.lineNumber())
_errorString = QString("%1:%2: %3").arg(_path).arg(reader.lineNumber())
.arg(reader.errorString());
return false;
}
@ -251,10 +290,10 @@ bool WMS::parseCapabilities(const QString &path, const Setup &setup)
return false;
}
_boundingBox = ctx.layers.first().boundingBox;
_bbox = ctx.layers.first().boundingBox;
for (int i = 1; i < ctx.layers.size(); i++)
_boundingBox &= ctx.layers.at(i).boundingBox;
if (_boundingBox.isNull()) {
_bbox &= ctx.layers.at(i).boundingBox;
if (_bbox.isNull()) {
_errorString = "Empty layers bounding box join";
return false;
}
@ -267,40 +306,57 @@ bool WMS::parseCapabilities(const QString &path, const Setup &setup)
return false;
}
if (_version >= "1.3.0") {
if (_setup.coordinateSystem().axisOrder() == CoordinateSystem::Unknown)
_cs = _projection.coordinateSystem();
else
_cs = _setup.coordinateSystem();
} else
_cs = CoordinateSystem::XY;
_getMapUrl = ctx.url.isEmpty() ? _setup.url() : ctx.url;
return true;
}
bool WMS::getCapabilities(const QString &url, const QString &file,
const Authorization &authorization)
bool WMS::downloadCapabilities(const QString &url)
{
Downloader d;
QList<Download> dl;
dl.append(Download(url, file));
QEventLoop wait;
QObject::connect(&d, SIGNAL(finished()), &wait, SLOT(quit()));
if (d.get(dl, authorization))
wait.exec();
if (!QFileInfo(file).exists()) {
_errorString = "Error downloading capabilities XML file";
return false;
if (!_downloader) {
_downloader = new Downloader(this);
connect(_downloader, SIGNAL(finished()), this,
SLOT(capabilitiesReady()));
}
return true;
QList<Download> dl;
dl.append(Download(url, _path));
return _downloader->get(dl, _setup.authorization());
}
WMS::WMS(const QString &file, const WMS::Setup &setup) : _valid(false)
void WMS::capabilitiesReady()
{
QString capaUrl = QString("%1%2service=WMS&request=GetCapabilities")
if (!QFileInfo(_path).exists()) {
_errorString = "Error downloading capabilities XML file";
_valid = false;
} else {
_ready = true;
_valid = parseCapabilities();
}
emit downloadFinished();
}
WMS::WMS(const QString &file, const WMS::Setup &setup, QObject *parent)
: QObject(parent), _setup(setup), _path(file), _downloader(0), _valid(false),
_ready(false)
{
QString url = QString("%1%2service=WMS&request=GetCapabilities")
.arg(setup.url(), setup.url().contains('?') ? "&" : "?");
if (!QFileInfo(file).exists())
if (!getCapabilities(capaUrl, file, setup.authorization()))
return;
if (!parseCapabilities(file, setup))
return;
_valid = true;
_valid = downloadCapabilities(url);
else {
_ready = true;
_valid = parseCapabilities();
}
}

View File

@ -12,8 +12,10 @@
class QXmlStreamReader;
class WMS
class WMS : public QObject
{
Q_OBJECT
public:
class Setup
{
@ -48,16 +50,26 @@ public:
};
WMS(const QString &path, const Setup &setup);
WMS(const QString &path, const Setup &setup, QObject *parent = 0);
const RectC &bbox() const {return _bbox;}
const Projection &projection() const {return _projection;}
CoordinateSystem cs() const {return _cs;}
const RangeF &scaleDenominator() const {return _scaleDenominator;}
const RectC &boundingBox() const {return _boundingBox;}
const QString &version() const {return _version;}
const QString &getMapUrl() const {return _getMapUrl;}
const WMS::Setup &setup() const {return _setup;}
bool isReady() const {return _valid && _ready;}
bool isValid() const {return _valid;}
const QString &errorString() const {return _errorString;}
signals:
void downloadFinished();
private slots:
void capabilitiesReady();
private:
struct Layer {
QString name;
@ -79,12 +91,16 @@ private:
const Setup &setup;
QList<Layer> layers;
bool formatSupported;
QString url;
CTX(const Setup &setup);
};
RectC geographicBoundingBox(QXmlStreamReader &reader);
QString style(QXmlStreamReader &reader);
void get(QXmlStreamReader &reader, CTX &ctx);
void http(QXmlStreamReader &reader, CTX &ctx);
void dcpType(QXmlStreamReader &reader, CTX &ctx);
void getMap(QXmlStreamReader &reader, CTX &ctx);
void request(QXmlStreamReader &reader, CTX &ctx);
void layer(QXmlStreamReader &reader, CTX &ctx, const QList<QString> &pCRSs,
@ -92,19 +108,21 @@ private:
RectC &pBoundingBox);
void capability(QXmlStreamReader &reader, CTX &ctx);
void capabilities(QXmlStreamReader &reader, CTX &ctx);
bool parseCapabilities(const QString &path, const Setup &setup);
bool getCapabilities(const QString &url, const QString &file,
const Authorization &authorization);
bool parseCapabilities();
bool downloadCapabilities(const QString &url);
WMS::Setup _setup;
QString _path;
Downloader *_downloader;
Projection _projection;
RangeF _scaleDenominator;
RectC _boundingBox;
RectC _bbox;
QString _version;
QString _getMapUrl;
CoordinateSystem _cs;
bool _valid;
bool _valid, _ready;
QString _errorString;
static Downloader *_downloader;
};
#endif // WMS_H

View File

@ -14,109 +14,94 @@
double WMSMap::sd2res(double scaleDenominator) const
{
return scaleDenominator * 0.28e-3 * _projection.units().fromMeters(1.0);
return scaleDenominator * _wms->projection().units().fromMeters(1.0)
* 0.28e-3;
}
QString WMSMap::tileUrl(const QString &version) const
QString WMSMap::tileUrl() const
{
QString url;
const WMS::Setup &setup = _wms->setup();
url = QString("%1%2version=%3&request=GetMap&bbox=$bbox"
QString url = QString("%1%2service=WMS&version=%3&request=GetMap&bbox=$bbox"
"&width=%4&height=%5&layers=%6&styles=%7&format=%8&transparent=true")
.arg(_setup.url(), _setup.url().contains('?') ? "&" : "?", version,
QString::number(_tileSize), QString::number(_tileSize), _setup.layer(),
_setup.style(), _setup.format());
.arg(_wms->getMapUrl(), _wms->getMapUrl().contains('?') ? "&" : "?",
_wms->version(), QString::number(_tileSize), QString::number(_tileSize),
setup.layer(), setup.style(), setup.format());
if (version >= "1.3.0")
url.append(QString("&CRS=%1").arg(_setup.crs()));
if (_wms->version() >= "1.3.0")
url.append(QString("&CRS=%1").arg(setup.crs()));
else
url.append(QString("&SRS=%1").arg(_setup.crs()));
url.append(QString("&SRS=%1").arg(setup.crs()));
for (int i = 0; i < _setup.dimensions().size(); i++) {
const KV<QString, QString> &dim = _setup.dimensions().at(i);
for (int i = 0; i < setup.dimensions().size(); i++) {
const KV<QString, QString> &dim = setup.dimensions().at(i);
url.append(QString("&%1=%2").arg(dim.key(), dim.value()));
}
return url;
}
QString WMSMap::tilesDir() const
{
return QString(QDir(ProgramPaths::tilesDir()).filePath(_name));
}
void WMSMap::computeZooms(const RangeF &scaleDenominator)
void WMSMap::computeZooms()
{
_zooms.clear();
if (scaleDenominator.size() > 0) {
double ld = log2(scaleDenominator.max() - EPSILON)
- log2(scaleDenominator.min() + EPSILON);
const RangeF &sd = _wms->scaleDenominator();
if (sd.size() > 0) {
double ld = log2(sd.max() - EPSILON) - log2(sd.min() + EPSILON);
int cld = (int)ceil(ld);
double step = ld / (double)cld;
double lmax = log2(scaleDenominator.max() - EPSILON);
double lmax = log2(sd.max() - EPSILON);
for (int i = 0; i <= cld; i++)
_zooms.append(pow(2.0, lmax - i * step));
} else
_zooms.append(scaleDenominator.min() + EPSILON);
_zooms.append(sd.min() + EPSILON);
}
void WMSMap::updateTransform()
{
double pixelSpan = sd2res(_zooms.at(_zoom));
if (_projection.isGeographic())
if (_wms->projection().isGeographic())
pixelSpan /= deg2rad(WGS84_RADIUS);
_transform = Transform(ReferencePoint(PointD(0, 0),
_projection.ll2xy(_bbox.topLeft())), PointD(pixelSpan, pixelSpan));
}
bool WMSMap::loadWMS()
{
QString file = tilesDir() + "/" + CAPABILITIES_FILE;
WMS wms(file, _setup);
if (!wms.isValid()) {
_errorString = wms.errorString();
return false;
}
_projection = wms.projection();
_bbox = wms.boundingBox();
_bounds = RectD(_bbox, _projection);
_tileLoader->setUrl(tileUrl(wms.version()));
if (wms.version() >= "1.3.0") {
if (_setup.coordinateSystem().axisOrder() == CoordinateSystem::Unknown)
_cs = _projection.coordinateSystem();
else
_cs = _setup.coordinateSystem();
} else
_cs = CoordinateSystem::XY;
computeZooms(wms.scaleDenominator());
updateTransform();
return true;
_wms->projection().ll2xy(_wms->bbox().topLeft())),
PointD(pixelSpan, pixelSpan));
}
WMSMap::WMSMap(const QString &name, const WMS::Setup &setup, int tileSize,
QObject *parent) : Map(parent), _name(name), _setup(setup), _tileLoader(0),
_zoom(0), _tileSize(tileSize), _mapRatio(1.0), _valid(false)
QObject *parent) : Map(parent), _name(name), _tileLoader(0), _zoom(0),
_tileSize(tileSize), _mapRatio(1.0)
{
_tileLoader = new TileLoader(tilesDir(), this);
_tileLoader->setAuthorization(_setup.authorization());
connect(_tileLoader, SIGNAL(finished()), this, SIGNAL(loaded()));
QString tilesDir(QDir(ProgramPaths::tilesDir()).filePath(_name));
_valid = loadWMS();
_tileLoader = new TileLoader(tilesDir, this);
_tileLoader->setAuthorization(setup.authorization());
connect(_tileLoader, SIGNAL(finished()), this, SIGNAL(tilesLoaded()));
_wms = new WMS(QDir(tilesDir).filePath(CAPABILITIES_FILE), setup, this);
connect(_wms, SIGNAL(downloadFinished()), this, SLOT(wmsReady()));
if (_wms->isReady())
init();
}
void WMSMap::init()
{
_tileLoader->setUrl(tileUrl());
_bounds = RectD(_wms->bbox(), _wms->projection());
computeZooms();
updateTransform();
}
void WMSMap::wmsReady()
{
if (_wms->isValid())
init();
emit mapLoaded();
}
void WMSMap::clearCache()
{
_tileLoader->clearCache();
_zoom = 0;
if (!loadWMS())
qWarning("%s: %s", qPrintable(_name), qPrintable(_errorString));
}
QRectF WMSMap::bounds()
@ -128,10 +113,10 @@ QRectF WMSMap::bounds()
int WMSMap::zoomFit(const QSize &size, const RectC &rect)
{
if (rect.isValid()) {
RectD prect(rect, _projection);
RectD prect(rect, _wms->projection());
PointD sc(prect.width() / size.width(), prect.height() / size.height());
double resolution = qMax(qAbs(sc.x()), qAbs(sc.y()));
if (_projection.isGeographic())
if (_wms->projection().isGeographic())
resolution *= deg2rad(WGS84_RADIUS);
_zoom = 0;
@ -169,12 +154,12 @@ int WMSMap::zoomOut()
QPointF WMSMap::ll2xy(const Coordinates &c)
{
return _transform.proj2img(_projection.ll2xy(c)) / _mapRatio;
return _transform.proj2img(_wms->projection().ll2xy(c)) / _mapRatio;
}
Coordinates WMSMap::xy2ll(const QPointF &p)
{
return _projection.xy2ll(_transform.img2proj(p * _mapRatio));
return _wms->projection().xy2ll(_transform.img2proj(p * _mapRatio));
}
qreal WMSMap::tileSize() const
@ -197,7 +182,7 @@ void WMSMap::draw(QPainter *painter, const QRectF &rect, Flags flags)
j * _tileSize)));
PointD tbr(_transform.img2proj(QPointF(i * _tileSize + _tileSize,
j * _tileSize + _tileSize)));
RectD bbox = (_cs.axisOrder() == CoordinateSystem::YX)
RectD bbox = (_wms->cs().axisOrder() == CoordinateSystem::YX)
? RectD(PointD(tbr.y(), tbr.x()), PointD(ttl.y(), ttl.x()))
: RectD(ttl, tbr);

View File

@ -36,34 +36,30 @@ public:
{_mapRatio = mapRatio;}
void clearCache();
bool isValid() const {return _valid;}
QString errorString() const {return _errorString;}
bool isReady() const {return _wms->isReady();}
bool isValid() const {return _wms->isValid();}
QString errorString() const {return _wms->errorString();}
private slots:
void wmsReady();
private:
QString tileUrl(const QString &version) const;
QString tileUrl() const;
double sd2res(double scaleDenominator) const;
QString tilesDir() const;
void computeZooms(const RangeF &scaleDenominator);
void computeZooms();
void updateTransform();
bool loadWMS();
qreal tileSize() const;
void init();
QString _name;
WMS::Setup _setup;
WMS *_wms;
TileLoader *_tileLoader;
Projection _projection;
Transform _transform;
CoordinateSystem _cs;
QVector<double> _zooms;
RectC _bbox;
RectD _bounds;
Transform _transform;
QVector<double> _zooms;
int _zoom;
int _tileSize;
qreal _mapRatio;
bool _valid;
QString _errorString;
};
#endif // WMSMAP_H

View File

@ -12,6 +12,11 @@
#include "wmts.h"
static QString bareFormat(const QString &format)
{
return format.left(format.indexOf(';')).trimmed();
}
static void skipParentElement(QXmlStreamReader &reader)
{
while (reader.readNextStartElement())
@ -53,7 +58,7 @@ void WMTS::tileMatrixSet(QXmlStreamReader &reader, CTX &ctx)
{
while (reader.readNextStartElement()) {
if (reader.name() == "Identifier") {
if (reader.readElementText() != ctx.setup.set()) {
if (reader.readElementText() != _setup.set()) {
skipParentElement(reader);
return;
}
@ -109,7 +114,7 @@ void WMTS::tileMatrixSetLink(QXmlStreamReader &reader, CTX &ctx)
{
while (reader.readNextStartElement()) {
if (reader.name() == "TileMatrixSet") {
if (reader.readElementText() == ctx.setup.set())
if (reader.readElementText() == _setup.set())
ctx.hasSet = true;
else {
skipParentElement(reader);
@ -158,7 +163,7 @@ void WMTS::layer(QXmlStreamReader &reader, CTX &ctx)
{
while (reader.readNextStartElement()) {
if (reader.name() == "Identifier") {
if (reader.readElementText() == ctx.setup.layer())
if (reader.readElementText() == _setup.layer())
ctx.hasLayer = true;
else {
skipParentElement(reader);
@ -167,10 +172,10 @@ void WMTS::layer(QXmlStreamReader &reader, CTX &ctx)
} else if (reader.name() == "TileMatrixSetLink")
tileMatrixSetLink(reader, ctx);
else if (reader.name() == "WGS84BoundingBox")
_bounds = wgs84BoundingBox(reader);
ctx.bbox = wgs84BoundingBox(reader);
else if (reader.name() == "ResourceURL") {
const QXmlStreamAttributes &attr = reader.attributes();
if (attr.value("resourceType") == "tile" && ctx.setup.rest())
if (attr.value("resourceType") == "tile" && _setup.rest())
_tileUrl = attr.value("template").toString();
reader.skipCurrentElement();
} else if (reader.name() == "Style") {
@ -179,11 +184,11 @@ void WMTS::layer(QXmlStreamReader &reader, CTX &ctx)
QString s = style(reader);
if (isDefault)
ctx.defaultStyle = s;
if (s == ctx.setup.style())
if (s == _setup.style())
ctx.hasStyle = true;
} else if (reader.name() == "Format") {
QString format(reader.readElementText());
if (format.left(format.indexOf(';')) == ctx.setup.format())
if (bareFormat(format) == bareFormat(_setup.format()))
ctx.hasFormat = true;
} else
reader.skipCurrentElement();
@ -227,9 +232,9 @@ void WMTS::createZooms(const CTX &ctx)
qSort(_zooms);
}
bool WMTS::parseCapabilities(const QString &path, CTX &ctx)
bool WMTS::parseCapabilities(CTX &ctx)
{
QFile file(path);
QFile file(_path);
QXmlStreamReader reader;
if (!file.open(QFile::ReadOnly | QFile::Text)) {
@ -245,30 +250,30 @@ bool WMTS::parseCapabilities(const QString &path, CTX &ctx)
reader.raiseError("Not a Capabilities XML file");
}
if (reader.error()) {
_errorString = QString("%1:%2: %3").arg(path).arg(reader.lineNumber())
_errorString = QString("%1:%2: %3").arg(_path).arg(reader.lineNumber())
.arg(reader.errorString());
return false;
}
if (!ctx.hasLayer) {
_errorString = ctx.setup.layer() + ": layer not provided";
_errorString = _setup.layer() + ": layer not provided";
return false;
}
if (!ctx.hasStyle && !ctx.setup.style().isEmpty()) {
_errorString = ctx.setup.style() + ": style not provided";
if (!ctx.hasStyle && !_setup.style().isEmpty()) {
_errorString = _setup.style() + ": style not provided";
return false;
}
if (!ctx.hasStyle && ctx.setup.style().isEmpty()
if (!ctx.hasStyle && _setup.style().isEmpty()
&& ctx.defaultStyle.isEmpty()) {
_errorString = "Default style not provided";
return false;
}
if (!ctx.setup.rest() && !ctx.hasFormat) {
_errorString = ctx.setup.format() + ": format not provided";
if (!_setup.rest() && !ctx.hasFormat) {
_errorString = _setup.format() + ": format not provided";
return false;
}
if (!ctx.hasSet) {
_errorString = ctx.setup.set() + ": set not provided";
_errorString = _setup.set() + ": set not provided";
return false;
}
if (ctx.crs.isNull()) {
@ -285,74 +290,92 @@ bool WMTS::parseCapabilities(const QString &path, CTX &ctx)
_errorString = "No usable tile matrix found";
return false;
}
if (ctx.setup.rest() && _tileUrl.isNull()) {
if (_setup.rest() && _tileUrl.isNull()) {
_errorString = "Missing tile URL template";
return false;
}
_bbox = ctx.bbox;
_cs = (_setup.coordinateSystem().axisOrder() == CoordinateSystem::Unknown)
? _projection.coordinateSystem() : _setup.coordinateSystem();
return true;
}
bool WMTS::downloadCapabilities(const QString &url, const QString &file,
const Authorization &authorization)
bool WMTS::downloadCapabilities(const QString &url)
{
Downloader d;
QList<Download> dl;
dl.append(Download(url, file));
QEventLoop wait;
QObject::connect(&d, SIGNAL(finished()), &wait, SLOT(quit()));
if (d.get(dl, authorization))
wait.exec();
if (!QFileInfo(file).exists()) {
_errorString = "Error downloading capabilities XML file";
return false;
if (!_downloader) {
_downloader = new Downloader(this);
connect(_downloader, SIGNAL(finished()), this,
SLOT(capabilitiesReady()));
}
return true;
QList<Download> dl;
dl.append(Download(url, _path));
return _downloader->get(dl, _setup.authorization());
}
WMTS::WMTS(const QString &file, const WMTS::Setup &setup) : _valid(false)
void WMTS::capabilitiesReady()
{
QUrl url(setup.rest() ? setup.url() : QString(
"%1%2service=WMTS&Version=1.0.0&request=GetCapabilities").arg(setup.url(),
setup.url().contains('?') ? "&" : "?"));
if (!QFileInfo(_path).exists()) {
_errorString = "Error downloading capabilities XML file";
_valid = false;
} else {
_ready = true;
_valid = init();
}
if (!url.isLocalFile() && !QFileInfo(file).exists())
if (!downloadCapabilities(url.toString(), file, setup.authorization()))
return;
emit downloadFinished();
}
CTX ctx(setup);
if (!parseCapabilities(url.isLocalFile() ? url.toLocalFile() : file, ctx))
return;
bool WMTS::init()
{
CTX ctx;
if (!parseCapabilities(ctx))
return false;
QString style = setup.style().isEmpty() ? ctx.defaultStyle : setup.style();
if (!setup.rest()) {
QString style = _setup.style().isEmpty() ? ctx.defaultStyle : _setup.style();
if (!_setup.rest()) {
_tileUrl = QString("%1%2service=WMTS&Version=1.0.0&request=GetTile"
"&Format=%3&Layer=%4&Style=%5&TileMatrixSet=%6&TileMatrix=$z"
"&TileRow=$y&TileCol=$x").arg(setup.url(),
setup.url().contains('?') ? "&" : "?" , setup.format(),
setup.layer(), style, setup.set());
for (int i = 0; i < setup.dimensions().size(); i++) {
const KV<QString, QString> &dim = setup.dimensions().at(i);
"&TileRow=$y&TileCol=$x").arg(_setup.url(),
_setup.url().contains('?') ? "&" : "?" , _setup.format(),
_setup.layer(), style, _setup.set());
for (int i = 0; i < _setup.dimensions().size(); i++) {
const KV<QString, QString> &dim = _setup.dimensions().at(i);
_tileUrl.append(QString("&%1=%2").arg(dim.key(), dim.value()));
}
} else {
_tileUrl.replace("{Style}", style, Qt::CaseInsensitive);
_tileUrl.replace("{TileMatrixSet}", setup.set(), Qt::CaseInsensitive);
_tileUrl.replace("{TileMatrixSet}", _setup.set(), Qt::CaseInsensitive);
_tileUrl.replace("{TileMatrix}", "$z", Qt::CaseInsensitive);
_tileUrl.replace("{TileRow}", "$y", Qt::CaseInsensitive);
_tileUrl.replace("{TileCol}", "$x", Qt::CaseInsensitive);
for (int i = 0; i < setup.dimensions().size(); i++) {
const KV<QString, QString> &dim = setup.dimensions().at(i);
for (int i = 0; i < _setup.dimensions().size(); i++) {
const KV<QString, QString> &dim = _setup.dimensions().at(i);
_tileUrl.replace(QString("{%1}").arg(dim.key()), dim.value(),
Qt::CaseInsensitive);
}
}
_valid = true;
return true;
}
WMTS::WMTS(const QString &file, const WMTS::Setup &setup, QObject *parent)
: QObject(parent), _setup(setup), _downloader(0), _valid(false), _ready(false)
{
QUrl url(setup.rest() ? setup.url() : QString(
"%1%2service=WMTS&Version=1.0.0&request=GetCapabilities").arg(setup.url(),
setup.url().contains('?') ? "&" : "?"));
_path = url.isLocalFile() ? url.toLocalFile() : file;
if (!url.isLocalFile() && !QFileInfo(file).exists())
_valid = downloadCapabilities(url.toString());
else {
_ready = true;
_valid = init();
}
}
#ifndef QT_NO_DEBUG

View File

@ -14,8 +14,10 @@
class QXmlStreamReader;
class WMTS
class WMTS : public QObject
{
Q_OBJECT
public:
class Setup
{
@ -79,16 +81,24 @@ public:
};
WMTS(const QString &path, const Setup &setup);
WMTS(const QString &path, const Setup &setup, QObject *parent = 0);
const RectC &bounds() const {return _bounds;}
const RectC &bbox() const {return _bbox;}
const QList<Zoom> &zooms() const {return _zooms;}
const Projection &projection() const {return _projection;}
const QString &tileUrl() const {return _tileUrl;}
CoordinateSystem cs() const {return _cs;}
bool isReady() const {return _valid && _ready;}
bool isValid() const {return _valid;}
const QString &errorString() const {return _errorString;}
signals:
void downloadFinished();
private slots:
void capabilitiesReady();
private:
struct TileMatrix {
QString id;
@ -118,18 +128,18 @@ private:
};
struct CTX {
const Setup &setup;
QSet<TileMatrix> matrixes;
QSet<MatrixLimits> limits;
QString crs;
QString defaultStyle;
RectC bbox;
bool hasLayer;
bool hasStyle;
bool hasFormat;
bool hasSet;
CTX(const Setup &setup) : setup(setup), hasLayer(false), hasStyle(false),
hasFormat(false), hasSet(false) {}
CTX() : hasLayer(false), hasStyle(false), hasFormat(false), hasSet(false)
{}
};
RectC wgs84BoundingBox(QXmlStreamReader &reader);
@ -142,17 +152,21 @@ private:
void layer(QXmlStreamReader &reader, CTX &ctx);
void contents(QXmlStreamReader &reader, CTX &ctx);
void capabilities(QXmlStreamReader &reader, CTX &ctx);
bool parseCapabilities(const QString &path, CTX &ctx);
bool downloadCapabilities(const QString &url, const QString &file,
const Authorization &authorization);
bool parseCapabilities(CTX &ctx);
bool downloadCapabilities(const QString &url);
void createZooms(const CTX &ctx);
bool init();
WMTS::Setup _setup;
QString _path;
Downloader *_downloader;
RectC _bbox;
QList<Zoom> _zooms;
RectC _bounds;
Projection _projection;
QString _tileUrl;
CoordinateSystem _cs;
bool _valid;
bool _valid, _ready;
QString _errorString;
friend uint qHash(const WMTS::TileMatrix &key);

View File

@ -12,70 +12,57 @@
#define CAPABILITIES_FILE "capabilities.xml"
bool WMTSMap::loadWMTS()
WMTSMap::WMTSMap(const QString &name, const WMTS::Setup &setup, qreal tileRatio,
QObject *parent) : Map(parent), _name(name), _tileLoader(0), _zoom(0),
_mapRatio(1.0), _tileRatio(tileRatio)
{
QString file = tilesDir() + "/" + CAPABILITIES_FILE;
QString tilesDir(QDir(ProgramPaths::tilesDir()).filePath(_name));
WMTS wmts(file, _setup);
if (!wmts.isValid()) {
_errorString = wmts.errorString();
return false;
}
_tileLoader = new TileLoader(tilesDir, this);
_tileLoader->setAuthorization(setup.authorization());
connect(_tileLoader, SIGNAL(finished()), this, SIGNAL(tilesLoaded()));
_zooms = wmts.zooms();
_projection = wmts.projection();
_tileLoader->setUrl(wmts.tileUrl());
_bounds = RectD(wmts.bounds(), _projection);
if (_setup.coordinateSystem().axisOrder() == CoordinateSystem::Unknown)
_cs = _projection.coordinateSystem();
else
_cs = _setup.coordinateSystem();
updateTransform();
return true;
_wmts = new WMTS(QDir(tilesDir).filePath(CAPABILITIES_FILE), setup, this);
connect(_wmts, SIGNAL(downloadFinished()), this, SLOT(wmtsReady()));
if (_wmts->isReady())
init();
}
WMTSMap::WMTSMap(const QString &name, const WMTS::Setup &setup, qreal tileRatio,
QObject *parent) : Map(parent), _name(name), _setup(setup), _tileLoader(0),
_zoom(0), _mapRatio(1.0), _tileRatio(tileRatio), _valid(false)
void WMTSMap::init()
{
_tileLoader = new TileLoader(tilesDir(), this);
_tileLoader->setAuthorization(_setup.authorization());
connect(_tileLoader, SIGNAL(finished()), this, SIGNAL(loaded()));
_tileLoader->setUrl(_wmts->tileUrl());
_bounds = RectD(_wmts->bbox(), _wmts->projection());
updateTransform();
}
_valid = loadWMTS();
void WMTSMap::wmtsReady()
{
if (_wmts->isValid())
init();
emit mapLoaded();
}
void WMTSMap::clearCache()
{
_tileLoader->clearCache();
_zoom = 0;
if (!loadWMTS())
qWarning("%s: %s", qPrintable(_name), qPrintable(_errorString));
}
QString WMTSMap::tilesDir() const
{
return QString(QDir(ProgramPaths::tilesDir()).filePath(_name));
}
double WMTSMap::sd2res(double scaleDenominator) const
{
return scaleDenominator * 0.28e-3 * _projection.units().fromMeters(1.0);
return scaleDenominator * 0.28e-3
* _wmts->projection().units().fromMeters(1.0);
}
void WMTSMap::updateTransform()
{
const WMTS::Zoom &z = _zooms.at(_zoom);
const WMTS::Zoom &z = _wmts->zooms().at(_zoom);
PointD topLeft = (_cs.axisOrder() == CoordinateSystem::YX)
PointD topLeft = (_wmts->cs().axisOrder() == CoordinateSystem::YX)
? PointD(z.topLeft().y(), z.topLeft().x()) : z.topLeft();
double pixelSpan = sd2res(z.scaleDenominator());
if (_projection.isGeographic())
if (_wmts->projection().isGeographic())
pixelSpan /= deg2rad(WGS84_RADIUS);
_transform = Transform(ReferencePoint(PointD(0, 0), topLeft),
PointD(pixelSpan, pixelSpan));
@ -83,7 +70,7 @@ void WMTSMap::updateTransform()
QRectF WMTSMap::bounds()
{
const WMTS::Zoom &z = _zooms.at(_zoom);
const WMTS::Zoom &z = _wmts->zooms().at(_zoom);
QRectF tileBounds, bounds;
tileBounds = (z.limits().isNull()) ?
@ -95,29 +82,29 @@ QRectF WMTSMap::bounds()
if (_bounds.isValid())
bounds = QRectF(_transform.proj2img(_bounds.topLeft())
/ coordinatesRatio(), _transform.proj2img(_bounds.bottomRight())
/ coordinatesRatio());
/ coordinatesRatio(), _transform.proj2img(
_bounds.bottomRight()) / coordinatesRatio());
return bounds.isValid() ? tileBounds.intersected(bounds) : tileBounds;
}
int WMTSMap::zoomFit(const QSize &size, const RectC &rect)
{
if (rect.isValid()) {
RectD prect(rect, _projection);
RectD prect(rect, _wmts->projection());
PointD sc(prect.width() / size.width(), prect.height() / size.height());
double resolution = qMax(qAbs(sc.x()), qAbs(sc.y()));
if (_projection.isGeographic())
if (_wmts->projection().isGeographic())
resolution *= deg2rad(WGS84_RADIUS);
_zoom = 0;
for (int i = 0; i < _zooms.size(); i++) {
if (sd2res(_zooms.at(i).scaleDenominator()) < resolution
for (int i = 0; i < _wmts->zooms().size(); i++) {
if (sd2res(_wmts->zooms().at(i).scaleDenominator()) < resolution
/ coordinatesRatio())
break;
_zoom = i;
}
} else
_zoom = _zooms.size() - 1;
_zoom = _wmts->zooms().size() - 1;
updateTransform();
return _zoom;
@ -131,7 +118,7 @@ void WMTSMap::setZoom(int zoom)
int WMTSMap::zoomIn()
{
_zoom = qMin(_zoom + 1, _zooms.size() - 1);
_zoom = qMin(_zoom + 1, _wmts->zooms().size() - 1);
updateTransform();
return _zoom;
}
@ -161,7 +148,7 @@ QSizeF WMTSMap::tileSize(const WMTS::Zoom &zoom) const
void WMTSMap::draw(QPainter *painter, const QRectF &rect, Flags flags)
{
const WMTS::Zoom &z = _zooms.at(_zoom);
const WMTS::Zoom &z = _wmts->zooms().at(_zoom);
QSizeF ts(tileSize(z));
QPoint tl = QPoint(qFloor(rect.left() / ts.width()),
@ -194,10 +181,12 @@ void WMTSMap::draw(QPainter *painter, const QRectF &rect, Flags flags)
QPointF WMTSMap::ll2xy(const Coordinates &c)
{
return _transform.proj2img(_projection.ll2xy(c)) / coordinatesRatio();
return _transform.proj2img(_wmts->projection().ll2xy(c))
/ coordinatesRatio();
}
Coordinates WMTSMap::xy2ll(const QPointF &p)
{
return _projection.xy2ll(_transform.img2proj(p * coordinatesRatio()));
return _wmts->projection().xy2ll(_transform.img2proj(p
* coordinatesRatio()));
}

View File

@ -36,31 +36,28 @@ public:
{_mapRatio = mapRatio;}
void clearCache();
bool isValid() const {return _valid;}
QString errorString() const {return _errorString;}
bool isReady() const {return _wmts->isReady();}
bool isValid() const {return _wmts->isValid();}
QString errorString() const {return _wmts->errorString();}
private slots:
void wmtsReady();
private:
bool loadWMTS();
double sd2res(double scaleDenominator) const;
QString tilesDir() const;
void updateTransform();
QSizeF tileSize(const WMTS::Zoom &zoom) const;
qreal coordinatesRatio() const;
qreal imageRatio() const;
void init();
QString _name;
WMTS::Setup _setup;
WMTS *_wmts;
TileLoader *_tileLoader;
RectD _bounds;
QList<WMTS::Zoom> _zooms;
Projection _projection;
Transform _transform;
CoordinateSystem _cs;
RectD _bounds;
int _zoom;
qreal _mapRatio, _tileRatio;
bool _valid;
QString _errorString;
};
#endif // WMTSMAP_H