From cf86fb755723f237d956ae54efc480f1ff8254c5 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Martin=20T=C5=AFma?= Date: Wed, 21 Feb 2024 08:49:09 +0100 Subject: [PATCH] Added hillshading to vector maps --- data/mapsforge/default.xml | 2 +- gpxsee.pro | 2 + src/GUI/gui.cpp | 20 ++++++++ src/GUI/gui.h | 1 + src/GUI/mapview.cpp | 12 +++++ src/GUI/mapview.h | 3 ++ src/GUI/settings.cpp | 3 ++ src/GUI/settings.h | 4 ++ src/GUI/waypointitem.cpp | 10 ++-- src/data/dem.cpp | 2 + src/data/dem.h | 7 ++- src/data/route.cpp | 2 + src/data/track.cpp | 2 + src/data/waypoint.cpp | 12 +++-- src/map/IMG/rastertile.cpp | 49 ++++++++++++++----- src/map/IMG/rastertile.h | 14 ++++-- src/map/IMG/style.cpp | 13 +++-- src/map/IMG/style.h | 59 ++++++++++++++--------- src/map/hillshading.cpp | 101 +++++++++++++++++++++++++++++++++++++++ src/map/hillshading.h | 14 ++++++ src/map/imgmap.cpp | 6 +-- src/map/map.h | 3 +- src/map/mapsforge/rastertile.cpp | 49 ++++++++++++++++--- src/map/mapsforge/rastertile.h | 52 +++++++++++++++----- src/map/mapsforge/style.cpp | 53 ++++++++++++++++++-- src/map/mapsforge/style.h | 19 ++++++++ src/map/mapsforgemap.cpp | 5 +- 27 files changed, 438 insertions(+), 81 deletions(-) create mode 100644 src/map/hillshading.cpp create mode 100644 src/map/hillshading.h diff --git a/data/mapsforge/default.xml b/data/mapsforge/default.xml index 1ec82281..e38d62dc 100644 --- a/data/mapsforge/default.xml +++ b/data/mapsforge/default.xml @@ -376,7 +376,7 @@ - + diff --git a/gpxsee.pro b/gpxsee.pro index 73e4e681..59b53c94 100644 --- a/gpxsee.pro +++ b/gpxsee.pro @@ -230,6 +230,7 @@ HEADERS += src/common/config.h \ src/map/mapsforgemap.h \ src/map/worldfilemap.h \ src/map/imgmap.h \ + src/map/hillshading.h \ src/data/itnparser.h \ src/data/link.h \ src/data/onmoveparsers.h \ @@ -435,6 +436,7 @@ SOURCES += src/main.cpp \ src/map/aqmmap.cpp \ src/map/mapsforgemap.cpp \ src/map/worldfilemap.cpp \ + src/map/hillshading.cpp \ src/data/address.cpp \ src/data/itnparser.cpp \ src/data/onmoveparsers.cpp \ diff --git a/src/GUI/gui.cpp b/src/GUI/gui.cpp index 0423b21f..dc917b8e 100644 --- a/src/GUI/gui.cpp +++ b/src/GUI/gui.cpp @@ -461,6 +461,11 @@ void GUI::createActions() _showDEMTilesAction = new QAction(tr("Show local DEM tiles"), this); _showDEMTilesAction->setMenuRole(QAction::NoRole); connect(_showDEMTilesAction, &QAction::triggered, this, &GUI::showDEMTiles); + _drawHillShadingAction = new QAction(tr("Show hillshading"), this); + _drawHillShadingAction->setMenuRole(QAction::NoRole); + _drawHillShadingAction->setCheckable(true); + connect(_drawHillShadingAction, &QAction::triggered, _mapView, + &MapView::drawHillShading); // Graph actions _showGraphsAction = new QAction(QIcon::fromTheme(SHOW_GRAPHS_NAME, @@ -711,6 +716,8 @@ void GUI::createMenus() QMenu *demMenu = menuBar()->addMenu(tr("DEM")); demMenu->addAction(_showDEMTilesAction); demMenu->addAction(_downloadDEMAction); + demMenu->addSeparator(); + demMenu->addAction(_drawHillShadingAction); QMenu *positionMenu = menuBar()->addMenu(tr("Position")); positionMenu->addAction(_showPositionCoordinatesAction); @@ -2499,6 +2506,11 @@ void GUI::writeSettings() WRITE(useStyles, _useStylesAction->isChecked()); settings.endGroup(); + /* DEM */ + settings.beginGroup(SETTINGS_DEM); + WRITE(drawHillShading, _drawHillShadingAction->isChecked()); + settings.endGroup(); + /* Position */ settings.beginGroup(SETTINGS_POSITION); WRITE(showPosition, _showPositionAction->isChecked()); @@ -2781,6 +2793,14 @@ void GUI::readSettings(QString &activeMap, QStringList &disabledPOIs, _hideMarkersAction->setChecked(true); settings.endGroup(); + /* DEM */ + settings.beginGroup(SETTINGS_DEM); + if (READ(drawHillShading).toBool()) { + _drawHillShadingAction->setChecked(true); + _mapView->drawHillShading(true); + } + settings.endGroup(); + /* Position */ settings.beginGroup(SETTINGS_POSITION); if (READ(showPosition).toBool()) { diff --git a/src/GUI/gui.h b/src/GUI/gui.h index ba182ace..5e781e4f 100644 --- a/src/GUI/gui.h +++ b/src/GUI/gui.h @@ -291,6 +291,7 @@ private: QAction *_openOptionsAction; QAction *_downloadDEMAction; QAction *_showDEMTilesAction; + QAction *_drawHillShadingAction; QAction *_mapsEnd; QAction *_poisEnd; #ifndef Q_OS_ANDROID diff --git a/src/GUI/mapview.cpp b/src/GUI/mapview.cpp index 4cf22435..d029ee59 100644 --- a/src/GUI/mapview.cpp +++ b/src/GUI/mapview.cpp @@ -64,6 +64,7 @@ MapView::MapView(Map *map, POI *poi, QWidget *parent) : QGraphicsView(parent) _outputProjection = PCS::pcs(3857); _inputProjection = GCS::gcs(4326); _hidpi = true; + _hillShading = false; _map = map; _map->load(_inputProjection, _outputProjection, _deviceRatio, _hidpi); connect(_map, &Map::tilesLoaded, this, &MapView::reloadMap); @@ -1114,6 +1115,8 @@ void MapView::drawBackground(QPainter *painter, const QRectF &rect) flags = Map::Block; else if (_opengl) flags = Map::OpenGL; + if (_hillShading) + flags |= Map::HillShading; _map->draw(painter, ir, flags); } @@ -1178,7 +1181,9 @@ void MapView::mouseMoveEvent(QMouseEvent *event) { if (_cursorCoordinates->isVisible()) { Coordinates c(_map->xy2ll(mapToScene(event->pos()))); + DEM::lock(); _cursorCoordinates->setCoordinates(c, DEM::elevation(c)); + DEM::unlock(); } QGraphicsView::mouseMoveEvent(event); @@ -1244,6 +1249,13 @@ void MapView::useAntiAliasing(bool use) setRenderHint(QPainter::Antialiasing, use); } +void MapView::drawHillShading(bool draw) +{ + _hillShading = draw; + + setMap(_map); +} + void MapView::useStyles(bool use) { GraphicsItem::useStyle(use); diff --git a/src/GUI/mapview.h b/src/GUI/mapview.h index 8e0fa323..a2e33745 100644 --- a/src/GUI/mapview.h +++ b/src/GUI/mapview.h @@ -128,6 +128,7 @@ public slots: void followPosition(bool follow); void showMotionInfo(bool show); void useStyles(bool use); + void drawHillShading(bool draw); private slots: void updatePOI(); @@ -206,6 +207,8 @@ private: qreal _areaOpacity; bool _infoBackground; + bool _hillShading; + int _digitalZoom; bool _plot; QCursor _cursor; diff --git a/src/GUI/settings.cpp b/src/GUI/settings.cpp index 3c7ec4b1..1e6a41e3 100644 --- a/src/GUI/settings.cpp +++ b/src/GUI/settings.cpp @@ -173,6 +173,9 @@ SETTING(positionMarkers, "positionMarkers", true ); SETTING(markerInfo, "markerInfo", MarkerInfoItem::None ); SETTING(useStyles, "styles", true ); +/* DEM */ +SETTING(drawHillShading, "hillshading", true ); + /* Position */ SETTING(showPosition, "show", false ); SETTING(followPosition, "follow", true ); diff --git a/src/GUI/settings.h b/src/GUI/settings.h index 41a736c6..1ab70acb 100644 --- a/src/GUI/settings.h +++ b/src/GUI/settings.h @@ -12,6 +12,7 @@ #define SETTINGS_GRAPH "Graph" #define SETTINGS_POI "POI" #define SETTINGS_DATA "Data" +#define SETTINGS_DEM "DEM" #define SETTINGS_POSITION "Position" #define SETTINGS_PDF_EXPORT "Export" #define SETTINGS_PNG_EXPORT "PNGExport" @@ -124,6 +125,9 @@ public: static const Setting markerInfo; static const Setting useStyles; + /* DEM */ + static const Setting drawHillShading; + /* Position */ static const Setting showPosition; static const Setting followPosition; diff --git a/src/GUI/waypointitem.cpp b/src/GUI/waypointitem.cpp index 8839438f..b9f0b3d7 100644 --- a/src/GUI/waypointitem.cpp +++ b/src/GUI/waypointitem.cpp @@ -25,11 +25,11 @@ ToolTip WaypointItem::info() const tt.insert(qApp->translate("WaypointItem", "Name"), _waypoint.name()); tt.insert(qApp->translate("WaypointItem", "Coordinates"), Format::coordinates(_waypoint.coordinates(), _format)); - 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) + ")"; + QPair elevations(_waypoint.elevations()); + if (!std::isnan(elevations.first)) { + QString val = Format::elevation(elevations.first, _units); + if (!std::isnan(elevations.second)) + val += " (" + Format::elevation(elevations.second, _units) + ")"; tt.insert(qApp->translate("WaypointItem", "Elevation"), val); } if (_waypoint.timestamp().isValid()) diff --git a/src/data/dem.cpp b/src/data/dem.cpp index 93103f54..c17eed63 100644 --- a/src/data/dem.cpp +++ b/src/data/dem.cpp @@ -69,6 +69,8 @@ static qreal height(const Coordinates &c, const QByteArray *data) return interpolate(dx, dy, p0, p1, p2, p3); } +QMutex DEM::_lock; + QString DEM::Tile::latStr() const { const char ns = (_lat >= 0) ? 'N' : 'S'; diff --git a/src/data/dem.h b/src/data/dem.h index 727fe6ee..49928590 100644 --- a/src/data/dem.h +++ b/src/data/dem.h @@ -4,12 +4,11 @@ #include #include #include +#include #include "common/hash.h" #include "area.h" -class QString; class Coordinates; -class RectC; class DEM { @@ -37,7 +36,10 @@ public: static void setCacheSize(int size); static void setDir(const QString &path); static void clearCache(); + static qreal elevation(const Coordinates &c); + static void lock() {_lock.lock();} + static void unlock() {_lock.unlock();} static QList tiles(); @@ -48,6 +50,7 @@ private: static QString _dir; static TileCache _data; + static QMutex _lock; }; inline HASH_T qHash(const DEM::Tile &tile) diff --git a/src/data/route.cpp b/src/data/route.cpp index 15326465..856a242e 100644 --- a/src/data/route.cpp +++ b/src/data/route.cpp @@ -55,11 +55,13 @@ Graph Route::demElevation() const QDateTime date; GraphSegment gs(date); + DEM::lock(); 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)); } + DEM::unlock(); if (gs.size() >= 2) graph.append(gs); diff --git a/src/data/track.cpp b/src/data/track.cpp index 63a53c66..e6032e21 100644 --- a/src/data/track.cpp +++ b/src/data/track.cpp @@ -281,6 +281,7 @@ Graph Track::demElevation() const { Graph ret; + DEM::lock(); for (int i = 0; i < _data.size(); i++) { const SegmentData &sd = _data.at(i); if (sd.size() < 2) @@ -298,6 +299,7 @@ Graph Track::demElevation() const if (gs.size() >= 2) ret.append(filter(gs, _elevationWindow)); } + DEM::unlock(); if (_data.style().color().isValid()) ret.setColor(_data.style().color()); diff --git a/src/data/waypoint.cpp b/src/data/waypoint.cpp index fd7f5dc7..600be6d1 100644 --- a/src/data/waypoint.cpp +++ b/src/data/waypoint.cpp @@ -10,17 +10,21 @@ QHash Waypoint::_symbolIcons; QPair Waypoint::elevations() const { if (_useDEM) { + DEM::lock(); qreal dem = DEM::elevation(coordinates()); + DEM::unlock(); if (!std::isnan(dem)) return QPair(dem, _show2ndElevation ? elevation() : NAN); else return QPair(elevation(), NAN); } else { - if (hasElevation()) - return QPair(elevation(), _show2ndElevation - ? DEM::elevation(coordinates()) : NAN); - else + if (hasElevation()) { + DEM::lock(); + qreal dem = _show2ndElevation ? DEM::elevation(coordinates()) : NAN; + DEM::unlock(); + return QPair(elevation(), dem); + } else return QPair(DEM::elevation(coordinates()), NAN); } } diff --git a/src/map/IMG/rastertile.cpp b/src/map/IMG/rastertile.cpp index ee4e67b1..93ca2d8f 100644 --- a/src/map/IMG/rastertile.cpp +++ b/src/map/IMG/rastertile.cpp @@ -5,6 +5,8 @@ #include "map/textpointitem.h" #include "map/bitmapline.h" #include "map/rectd.h" +#include "map/hillshading.h" +#include "data/dem.h" #include "style.h" #include "lblfile.h" #include "rastertile.h" @@ -292,10 +294,10 @@ void RasterTile::processStreetNames(const QList &lines, if (style.img().isNull() && style.foreground() == Qt::NoPen) continue; - const QFont *fnt = _data->style()->font(style.textFontSize(), + const QFont *fnt = _data->style()->font(style.text().size(), Style::Small); - const QColor *color = style.textColor().isValid() - ? &style.textColor() : 0; + const QColor *color = style.text().color().isValid() + ? &style.text().color() : 0; const QColor *hColor = Style::isContourLine(poly.type) ? 0 : &haloColor; const QImage *img = poly.oneway ? Style::isWaterLine(poly.type) @@ -353,7 +355,7 @@ void RasterTile::processShields(const QList &lines, it != shields.constEnd(); ++it) { const QPolygonF &p = it.value(); QRectF rect(p.boundingRect() & _rect); - if (AREA(rect) < AREA(QRect(0, 0, _pixmap.width()/4, _pixmap.width()/4))) + if (AREA(rect) < AREA(QRect(0, 0, _rect.width()/4, _rect.width()/4))) continue; QMap map; @@ -403,10 +405,10 @@ void RasterTile::processPoints(QList &points, ? 0 : &(point.label.text()); const QImage *img = style.img().isNull() ? 0 : &style.img(); const QFont *fnt = poi - ? poiFont(style.textFontSize(), _zoom, point.classLabel) - : _data->style()->font(style.textFontSize()); - const QColor *color = style.textColor().isValid() - ? &style.textColor() : &textColor; + ? poiFont(style.text().size(), _zoom, point.classLabel) + : _data->style()->font(style.text().size()); + const QColor *color = style.text().color().isValid() + ? &style.text().color() : &textColor; const QColor *hcolor = Style::isDepthPoint(point.type) ? 0 : &haloColor; @@ -443,8 +445,29 @@ void RasterTile::fetchData(QList &polygons, _data->points(pointRectD.toRectC(_proj, 20), _zoom, &points); } +Matrix RasterTile::elevation() const +{ + Matrix m(_rect.height() + 2, _rect.width() + 2); + + int left = _rect.left() - 1; + int right = _rect.right() + 1; + int top = _rect.top() - 1; + int bottom = _rect.bottom() + 1; + + DEM::lock(); + for (int y = top; y <= bottom; y++) { + for (int x = left; x <= right; x++) + m.m(y - top, x - left) = DEM::elevation(xy2ll(QPointF(x, y))); + } + DEM::unlock(); + + return m; +} + void RasterTile::render() { + QImage img(_rect.width() * _ratio, _rect.height() * _ratio, + QImage::Format_ARGB32_Premultiplied); QList polygons; QList lines; QList points; @@ -463,21 +486,23 @@ void RasterTile::render() processPolygons(polygons, textItems); processLines(lines, textItems, arrows); - _pixmap.setDevicePixelRatio(_ratio); - _pixmap.fill(Qt::transparent); + img.setDevicePixelRatio(_ratio); + img.fill(Qt::transparent); - QPainter painter(&_pixmap); + QPainter painter(&img); painter.setRenderHint(QPainter::SmoothPixmapTransform); painter.setRenderHint(QPainter::Antialiasing); painter.translate(-_rect.x(), -_rect.y()); drawPolygons(&painter, polygons); + if (_hillShading && _zoom >= 18 && _zoom <= 24) + painter.drawImage(_rect.x(), _rect.y(), HillShading::render(elevation())); drawLines(&painter, lines); drawTextItems(&painter, textItems); qDeleteAll(textItems); - _valid = true; + _pixmap = QPixmap::fromImage(img); //painter.setPen(Qt::red); //painter.setRenderHint(QPainter::Antialiasing, false); diff --git a/src/map/IMG/rastertile.h b/src/map/IMG/rastertile.h index 1c977497..d6afe09d 100644 --- a/src/map/IMG/rastertile.h +++ b/src/map/IMG/rastertile.h @@ -5,6 +5,7 @@ #include "mapdata.h" #include "map/projection.h" #include "map/transform.h" +#include "map/matrix.h" #include "style.h" class QPainter; @@ -17,15 +18,14 @@ class RasterTile { public: RasterTile(const Projection &proj, const Transform &transform, MapData *data, - int zoom, const QRect &rect, qreal ratio, const QString &key) + int zoom, const QRect &rect, qreal ratio, const QString &key, + bool hillShading) : _proj(proj), _transform(transform), _data(data), _zoom(zoom), - _rect(rect), _ratio(ratio), _key(key), - _pixmap(rect.width() * ratio, rect.height() * ratio), _valid(false) {} + _rect(rect), _ratio(ratio), _key(key), _hillShading(hillShading) {} const QString &key() const {return _key;} QPoint xy() const {return _rect.topLeft();} const QPixmap &pixmap() const {return _pixmap;} - bool isValid() const {return _valid;} void render(); @@ -34,6 +34,8 @@ private: QList &points); QPointF ll2xy(const Coordinates &c) const {return _transform.proj2img(_proj.ll2xy(c));} + Coordinates xy2ll(const QPointF &p) const + {return _proj.xy2ll(_transform.img2proj(p));} void ll2xy(QList &polys); void ll2xy(QList &points); @@ -55,6 +57,8 @@ private: const QFont *poiFont(Style::FontSize size = Style::Normal, int zoom = -1, bool extended = false); + Matrix elevation() const; + Projection _proj; Transform _transform; MapData *_data; @@ -63,7 +67,7 @@ private: qreal _ratio; QString _key; QPixmap _pixmap; - bool _valid; + bool _hillShading; }; } diff --git a/src/map/IMG/style.cpp b/src/map/IMG/style.cpp index 7ef74cd4..16c6f0a3 100644 --- a/src/map/IMG/style.cpp +++ b/src/map/IMG/style.cpp @@ -1332,6 +1332,12 @@ static QString brushColor(const QBrush &brush) return (brush == Qt::NoBrush) ? "None" : brush.color().name(); } +QDebug operator<<(QDebug dbg, const Style::Font &font) +{ + dbg.nospace() << "Font(" << font.color() << ", " << font.size() << ")"; + return dbg.space(); +} + QDebug operator<<(QDebug dbg, const Style::Polygon &polygon) { dbg.nospace() << "Polygon(" << brushColor(polygon.brush()) << ", " @@ -1342,14 +1348,15 @@ QDebug operator<<(QDebug dbg, const Style::Polygon &polygon) QDebug operator<<(QDebug dbg, const Style::Line &line) { dbg.nospace() << "Line(" << penColor(line.foreground()) << ", " - << penColor(line.background()) << ", " << !line.img().isNull() << ")"; + << penColor(line.background()) << ", " << !line.img().isNull() << ", " + << line.text() << ")"; return dbg.space(); } QDebug operator<<(QDebug dbg, const Style::Point &point) { - dbg.nospace() << "Point(" << point.textFontSize() << ", " - << point.textColor() << ", " << !point.img().isNull() << ")"; + dbg.nospace() << "Point(" << point.text() << ", " << !point.img().isNull() + << ")"; return dbg.space(); } #endif // QT_NO_DEBUG diff --git a/src/map/IMG/style.h b/src/map/IMG/style.h index 11aa76e3..dad49014 100644 --- a/src/map/IMG/style.h +++ b/src/map/IMG/style.h @@ -23,6 +23,21 @@ public: ExtraSmall = 5 }; + class Font { + public: + Font() :_size(NotSet) {} + Font(const QColor &color, FontSize size) : _color(color), _size(size) {} + + const QColor &color() const {return _color;} + FontSize size() const {return _size;} + void setColor(const QColor &color) {_color = color;} + void setSize(FontSize size) {_size = size;} + + private: + QColor _color; + FontSize _size; + }; + class Polygon { public: Polygon() : _brush(Qt::NoBrush), _pen(Qt::NoPen) {} @@ -42,49 +57,46 @@ public: class Line { public: - Line() : _foreground(Qt::NoPen), _background(Qt::NoPen), - _textFontSize(NotSet) {} + Line() : _foreground(Qt::NoPen), _background(Qt::NoPen) {} Line(const QPen &foreground, const QPen &background = Qt::NoPen) - : _foreground(foreground), _background(background), - _textFontSize(NotSet) {} + : _foreground(foreground), _background(background) {} Line(const QImage &img) : _foreground(Qt::NoPen), _background(Qt::NoPen), - _textFontSize(NotSet), _img(img.convertToFormat( - QImage::Format_ARGB32_Premultiplied)) {} - - void setTextColor(const QColor &color) {_textColor = color;} - void setTextFontSize(FontSize size) {_textFontSize = size;} + _img(img.convertToFormat(QImage::Format_ARGB32_Premultiplied)) {} const QPen &foreground() const {return _foreground;} const QPen &background() const {return _background;} - const QColor &textColor() const {return _textColor;} - FontSize textFontSize() const {return _textFontSize;} + const Font &text() const {return _text;} const QImage &img() const {return _img;} private: + friend class Style; + + void setTextColor(const QColor &color) {_text.setColor(color);} + void setTextFontSize(FontSize size) {_text.setSize(size);} + QPen _foreground, _background; - QColor _textColor; - FontSize _textFontSize; + Font _text; QImage _img; }; class Point { public: - Point() : _textFontSize(NotSet) {} + Point() {} Point(FontSize fontSize, const QColor &textColor = QColor()) - : _textColor(textColor), _textFontSize(fontSize) {} - Point(const QImage &img) : _textFontSize(NotSet), _img(img) {} + : _text(textColor, fontSize) {} + Point(const QImage &img) : _img(img) {} - void setTextColor(const QColor &color) {_textColor = color;} - void setTextFontSize(FontSize size) {_textFontSize = size;} - - const QColor &textColor() const {return _textColor;} - FontSize textFontSize() const {return _textFontSize;} + const Font &text() const {return _text;} const QImage &img() const {return _img;} private: - QColor _textColor; - FontSize _textFontSize; + friend class Style; + + void setTextColor(const QColor &color) {_text.setColor(color);} + void setTextFontSize(FontSize size) {_text.setSize(size);} + + Font _text; QImage _img; }; @@ -183,6 +195,7 @@ private: } #ifndef QT_NO_DEBUG +QDebug operator<<(QDebug dbg, const IMG::Style::Font &font); QDebug operator<<(QDebug dbg, const IMG::Style::Polygon &polygon); QDebug operator<<(QDebug dbg, const IMG::Style::Line &line); QDebug operator<<(QDebug dbg, const IMG::Style::Point &point); diff --git a/src/map/hillshading.cpp b/src/map/hillshading.cpp new file mode 100644 index 00000000..3844298b --- /dev/null +++ b/src/map/hillshading.cpp @@ -0,0 +1,101 @@ +#include +#include "hillshading.h" + +struct Constants +{ + double a1; + double a2; + double a3; +}; + +struct SubMatrix +{ + double z1; + double z2; + double z3; + double z4; + double z6; + double z7; + double z8; + double z9; +}; + +struct Derivatives +{ + double dzdx; + double dzdy; +}; + +static void getConstants(double azimuth, double elevation, Constants &c) +{ + double alpha = (M_PI / 180.0) * azimuth; + double beta = (M_PI / 180.0) * elevation; + + c.a1 = sin(beta); + c.a2 = cos(beta) * sin(alpha); + c.a3 = cos(beta) * cos(alpha); +} + +static void getDerivativesHorn(const SubMatrix &sm, double z, Derivatives &d) +{ + d.dzdx = (z * (sm.z3 + 2 * sm.z6 + sm.z9 - sm.z1 - 2 * sm.z4 - sm.z7)) / 8; + d.dzdy = (z * (sm.z1 + 2 * sm.z2 + sm.z3 - sm.z7 - 2 * sm.z8 - sm.z9)) / 8; +} + +static void getSubmatrix(int x, int y, const Matrix &m, SubMatrix &sm) +{ + int left = x - 1; + int right = x + 1; + int top = y - 1; + int bottom = y + 1; + + sm.z1 = m.m(top, left); + sm.z2 = m.m(top, x); + sm.z3 = m.m(top, right); + sm.z4 = m.m(y, left); + sm.z6 = m.m(y, right); + sm.z7 = m.m(bottom, left); + sm.z8 = m.m(bottom, x); + sm.z9 = m.m(bottom, right); +} + +QImage HillShading::render(const Matrix &m, quint8 alpha, double azimuth, + double elevation) +{ + QImage img(m.w() - 2, m.h() - 2, QImage::Format_ARGB32_Premultiplied); + uchar *bits = img.bits(); + int bpl = img.bytesPerLine(); + + Constants c; + SubMatrix sm; + Derivatives d; + double z = (double)alpha / 0xFF; + + getConstants(azimuth, elevation, c); + + for (int y = 1; y < m.h() - 1; y++) { + for (int x = 1; x < m.w() - 1; x++) { + getSubmatrix(x, y, m, sm); + getDerivativesHorn(sm, z, d); + + double L = (c.a1 - c.a2 * d.dzdx - c.a3 * d.dzdy) + / sqrt(1.0 + d.dzdx * d.dzdx + d.dzdy * d.dzdy); + + quint8 a, val; + if (std::isnan(L)) { + a = 0; + val = 0; + } else { + if (L < 0) + L = 0; + val = L * alpha; + a = alpha; + } + + quint32 pixel = a<<24 | val<<16 | val<<8 | val; + *(quint32*)(bits + (y - 1) * bpl + (x - 1) * 4) = pixel; + } + } + + return img; +} diff --git a/src/map/hillshading.h b/src/map/hillshading.h new file mode 100644 index 00000000..9d84d703 --- /dev/null +++ b/src/map/hillshading.h @@ -0,0 +1,14 @@ +#ifndef HILLSHADING_H +#define HILLSHADING_H + +#include +#include "map/matrix.h" + +class HillShading +{ +public: + static QImage render(const Matrix &m, quint8 alpha = 64, + double azimuth = 315, double elevation = 25); +}; + +#endif // HILLSHADING_H diff --git a/src/map/imgmap.cpp b/src/map/imgmap.cpp index a9475900..1211debe 100644 --- a/src/map/imgmap.cpp +++ b/src/map/imgmap.cpp @@ -201,7 +201,7 @@ void IMGMap::jobFinished(IMGMapJob *job) for (int i = 0; i < tiles.size(); i++) { const IMG::RasterTile &mt = tiles.at(i); - if (mt.isValid()) + if (!mt.pixmap().isNull()) QPixmapCache::insert(mt.key(), mt.pixmap()); } @@ -241,8 +241,8 @@ void IMGMap::draw(QPainter *painter, const QRectF &rect, Flags flags) painter->drawPixmap(ttl, pm); else { tiles.append(RasterTile(_projection, _transform, _data.at(n), - _zoom, QRect(ttl, QSize(TILE_SIZE, TILE_SIZE)), _tileRatio, - key)); + _zoom, QRect(ttl, QSize(TILE_SIZE, TILE_SIZE)), _tileRatio, + key, !n && flags & Map::HillShading)); } } } diff --git a/src/map/map.h b/src/map/map.h index 68101d9e..e8ffa6d5 100644 --- a/src/map/map.h +++ b/src/map/map.h @@ -20,7 +20,8 @@ public: enum Flag { NoFlags = 0, Block = 1, - OpenGL = 2 + OpenGL = 2, + HillShading = 4 }; Q_DECLARE_FLAGS(Flags, Flag) diff --git a/src/map/mapsforge/rastertile.cpp b/src/map/mapsforge/rastertile.cpp index 2cb96733..5558d9ad 100644 --- a/src/map/mapsforge/rastertile.cpp +++ b/src/map/mapsforge/rastertile.cpp @@ -1,7 +1,9 @@ #include #include #include +#include "data/dem.h" #include "map/rectd.h" +#include "map/hillshading.h" #include "rastertile.h" using namespace Mapsforge; @@ -392,17 +394,27 @@ void RasterTile::circleInstructions(const QList &points, } } +void RasterTile::hillShadingInstructions( + QVector &instructions) const +{ + const Style::HillShadingRender *hs = _style->hillShading(_zoom); + if (hs) + instructions.append(RenderInstruction(hs)); +} + void RasterTile::drawPaths(QPainter *painter, const QList &paths, const QList &points, QVector &painterPaths) { QVector instructions; pathInstructions(paths, painterPaths, instructions); circleInstructions(points, instructions); + hillShadingInstructions(instructions); std::sort(instructions.begin(), instructions.end()); for (int i = 0; i < instructions.size(); i++) { const RenderInstruction &is = instructions.at(i); PainterPath *path = is.path(); + const MapData::Point *point = is.point(); if (path) { const Style::PathRender *ri = is.pathRender(); @@ -418,13 +430,17 @@ void RasterTile::drawPaths(QPainter *painter, const QList &paths, painter->drawPath(parallelPath(path->pp, dy)); else painter->drawPath(path->pp); - } else { + } else if (point) { const Style::CircleRender *ri = is.circleRender(); qreal radius = ri->radius(_zoom); painter->setPen(ri->pen()); painter->setBrush(ri->brush()); - painter->drawEllipse(ll2xy(is.point()->coordinates), radius, radius); + painter->drawEllipse(ll2xy(point->coordinates), radius, radius); + } else { + if (_hillShading) + painter->drawImage(_rect.x(), _rect.y(), + HillShading::render(elevation())); } } } @@ -455,8 +471,29 @@ void RasterTile::fetchData(QList &paths, _data->points(pointRectD.toRectC(_proj, 20), _zoom, &points); } +Matrix RasterTile::elevation() const +{ + Matrix m(_rect.height() + 2, _rect.width() + 2); + + int left = _rect.left() - 1; + int right = _rect.right() + 1; + int top = _rect.top() - 1; + int bottom = _rect.bottom() + 1; + + DEM::lock(); + for (int y = top; y <= bottom; y++) { + for (int x = left; x <= right; x++) + m.m(y - top, x - left) = DEM::elevation(xy2ll(QPointF(x, y))); + } + DEM::unlock(); + + return m; +} + void RasterTile::render() { + QImage img(_rect.width() * _ratio, _rect.height() * _ratio, + QImage::Format_ARGB32_Premultiplied); QList paths; QList points; @@ -465,10 +502,10 @@ void RasterTile::render() QList textItems; QVector renderPaths(paths.size()); - _pixmap.setDevicePixelRatio(_ratio); - _pixmap.fill(Qt::transparent); + img.setDevicePixelRatio(_ratio); + img.fill(Qt::transparent); - QPainter painter(&_pixmap); + QPainter painter(&img); painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::SmoothPixmapTransform); painter.translate(-_rect.x(), -_rect.y()); @@ -487,5 +524,5 @@ void RasterTile::render() qDeleteAll(textItems); - _valid = true; + _pixmap = QPixmap::fromImage(img); } diff --git a/src/map/mapsforge/rastertile.h b/src/map/mapsforge/rastertile.h index 6f8ddae1..e1feb852 100644 --- a/src/map/mapsforge/rastertile.h +++ b/src/map/mapsforge/rastertile.h @@ -6,9 +6,18 @@ #include "map/transform.h" #include "map/textpointitem.h" #include "map/textpathitem.h" +#include "map/matrix.h" #include "style.h" #include "mapdata.h" + +#define HILLSHADING_RENDER(ptr) \ + static_cast(ptr) +#define PATH_RENDER(ptr) \ + static_cast(ptr) +#define POINT_RENDER(ptr) \ + static_cast(ptr) + namespace Mapsforge { class RasterTile @@ -20,14 +29,13 @@ public: back in zoom() */ RasterTile(const Projection &proj, const Transform &transform, const Style *style, MapData *data, int zoom, const QRect &rect, - qreal ratio) : _proj(proj), _transform(transform), _style(style), - _data(data), _zoom(zoom - 1), _rect(rect), _ratio(ratio), - _pixmap(rect.width() * ratio, rect.height() * ratio), _valid(false) {} + qreal ratio, bool hillShading) + : _proj(proj), _transform(transform), _style(style), _data(data), + _zoom(zoom - 1), _rect(rect), _ratio(ratio), _hillShading(hillShading) {} int zoom() const {return _zoom + 1;} QPoint xy() const {return _rect.topLeft();} const QPixmap &pixmap() const {return _pixmap;} - bool isValid() const {return _valid;} void render(); @@ -91,6 +99,8 @@ private: RenderInstruction(const Style::CircleRender *render, const MapData::Point *point) : _render(render), _path(0), _point(point) {} + RenderInstruction(const Style::HillShadingRender *render) + : _render(render), _path(0), _point(0) {} bool operator<(const RenderInstruction &other) const { @@ -101,19 +111,33 @@ private: } const Style::PathRender *pathRender() const - {return static_cast(_render);} + {return PATH_RENDER(_render);} const Style::CircleRender *circleRender() const - {return static_cast(_render);} + {return POINT_RENDER(_render);} + const Style::HillShadingRender *hillShadingRender() const + {return HILLSHADING_RENDER(_render);} + PainterPath *path() const {return _path;} const MapData::Point *point() const {return _point;} private: - int layer() const {return _path ? _path->path->layer : _point->layer;} + int layer() const + { + if (_path) + return _path->path->layer; + else if (_point) + return _point->layer; + else + return HILLSHADING_RENDER(_render)->layer(); + } int zOrder() const { - return _path - ? static_cast(_render)->zOrder() - : static_cast(_render)->zOrder(); + if (_path) + return PATH_RENDER(_render)->zOrder(); + else if (_point) + return POINT_RENDER(_render)->zOrder(); + else + return HILLSHADING_RENDER(_render)->zOrder(); } const Style::Render *_render; @@ -178,8 +202,12 @@ private: QVector &instructions) const; void circleInstructions(const QList &points, QVector &instructions) const; + void hillShadingInstructions( + QVector &instructions) const; QPointF ll2xy(const Coordinates &c) const {return _transform.proj2img(_proj.ll2xy(c));} + Coordinates xy2ll(const QPointF &p) const + {return _proj.xy2ll(_transform.img2proj(p));} void processPointLabels(const QList &points, QList &textItems) const; void processAreaLabels(const QVector &paths, @@ -191,6 +219,8 @@ private: void drawPaths(QPainter *painter, const QList &paths, const QList &points, QVector &painterPaths); + Matrix elevation() const; + Projection _proj; Transform _transform; const Style *_style; @@ -199,7 +229,7 @@ private: QRect _rect; qreal _ratio; QPixmap _pixmap; - bool _valid; + bool _hillShading; }; inline HASH_T qHash(const RasterTile::PathKey &key) diff --git a/src/map/mapsforge/style.cpp b/src/map/mapsforge/style.cpp index 08039ea4..e229bb8e 100644 --- a/src/map/mapsforge/style.cpp +++ b/src/map/mapsforge/style.cpp @@ -179,7 +179,7 @@ bool Style::Rule::match(int zoom, const QVector &tags) const void Style::area(QXmlStreamReader &reader, const QString &dir, qreal ratio, qreal baseStrokeWidth, const Rule &rule) { - PathRender ri(rule, _paths.size() + _circles.size()); + PathRender ri(rule, _paths.size() + _circles.size() + _hillShading.isValid()); const QXmlStreamAttributes &attr = reader.attributes(); QString file; QColor fillColor; @@ -244,7 +244,7 @@ void Style::area(QXmlStreamReader &reader, const QString &dir, qreal ratio, void Style::line(QXmlStreamReader &reader, qreal baseStrokeWidth, const Rule &rule) { - PathRender ri(rule, _paths.size() + _circles.size()); + PathRender ri(rule, _paths.size() + _circles.size() + _hillShading.isValid()); const QXmlStreamAttributes &attr = reader.attributes(); bool ok; @@ -318,7 +318,7 @@ void Style::line(QXmlStreamReader &reader, qreal baseStrokeWidth, void Style::circle(QXmlStreamReader &reader, qreal baseStrokeWidth, const Rule &rule) { - CircleRender ri(rule, _paths.size() + _circles.size()); + CircleRender ri(rule, _paths.size() + _circles.size() + _hillShading.isValid()); const QXmlStreamAttributes &attr = reader.attributes(); bool ok; QColor fillColor, strokeColor; @@ -573,6 +573,45 @@ void Style::rule(QXmlStreamReader &reader, const QString &dir, } } +void Style::hillshading(QXmlStreamReader &reader, const QSet &cats) +{ + Rule r; + const QXmlStreamAttributes &attr = reader.attributes(); + bool ok; + int layer = 5; + + if (attr.hasAttribute("cat") + && !cats.contains(attr.value("cat").toString())) { + reader.skipCurrentElement(); + return; + } + if (attr.hasAttribute("zoom-min")) { + r.setMinZoom(attr.value("zoom-min").toInt(&ok)); + if (!ok || r._zooms.min() < 0) { + reader.raiseError("invalid zoom-min value"); + return; + } + } + if (attr.hasAttribute("zoom-max")) { + r.setMaxZoom(attr.value("zoom-max").toInt(&ok)); + if (!ok || r._zooms.max() < 0) { + reader.raiseError("invalid zoom-max value"); + return; + } + } + if (attr.hasAttribute("layer")) { + layer = attr.value("layer").toInt(&ok); + if (!ok || layer < 0) { + reader.raiseError("invalid layer value"); + return; + } + } + + _hillShading = HillShadingRender(r, _paths.size() + _circles.size(), layer); + + reader.skipCurrentElement(); +} + QString Style::cat(QXmlStreamReader &reader) { const QXmlStreamAttributes &attr = reader.attributes(); @@ -654,6 +693,8 @@ void Style::rendertheme(QXmlStreamReader &reader, const QString &dir, while (reader.readNextStartElement()) { if (reader.name() == QLatin1String("rule")) rule(reader, dir, data, ratio, baseStrokeWidth, cats, r); + else if (reader.name() == QLatin1String("hillshading")) + hillshading(reader, cats); else if (reader.name() == QLatin1String("stylemenu")) { Menu menu(stylemenu(reader)); cats = menu.cats(); @@ -727,6 +768,12 @@ QList Style::circles(int zoom, return ri; } +const Style::HillShadingRender *Style::hillShading(int zoom) const +{ + return (_hillShading.isValid() && _hillShading.rule()._zooms.contains(zoom)) + ? &_hillShading : 0; +} + QList Style::pathLabels(int zoom) const { QList list; diff --git a/src/map/mapsforge/style.h b/src/map/mapsforge/style.h index d61201d1..f4a5b470 100644 --- a/src/map/mapsforge/style.h +++ b/src/map/mapsforge/style.h @@ -131,6 +131,22 @@ public: Rule _rule; }; + class HillShadingRender : public Render + { + public: + HillShadingRender() : Render(Rule()), _zOrder(-1), _layer(-1) {} + HillShadingRender(const Rule &rule, int zOrder, int layer) + : Render(rule), _zOrder(zOrder), _layer(layer) {} + + bool isValid() const {return _zOrder >= 0;} + int zOrder() const {return _zOrder;} + int layer() const {return _layer;} + + private: + int _zOrder; + int _layer; + }; + class PathRender : public Render { public: @@ -244,6 +260,7 @@ public: QList pointSymbols(int zoom) const; QList areaSymbols(int zoom) const; QList lineSymbols(int zoom) const; + const HillShadingRender *hillShading(int zoom) const; private: class Menu { @@ -286,6 +303,7 @@ private: QList _layers; }; + HillShadingRender _hillShading; QList _paths; QList _circles; QList _pathLabels, _pointLabels, _areaLabels; @@ -305,6 +323,7 @@ private: void line(QXmlStreamReader &reader, qreal baseStrokeWidth, const Rule &rule); void circle(QXmlStreamReader &reader, qreal baseStrokeWidth, const Rule &rule); + void hillshading(QXmlStreamReader &reader, const QSet &cats); void text(QXmlStreamReader &reader, const MapData &data, const Rule &rule, QList *> &lists); void symbol(QXmlStreamReader &reader, const QString &dir, qreal ratio, diff --git a/src/map/mapsforgemap.cpp b/src/map/mapsforgemap.cpp index ef7c1aa3..0e6b9793 100644 --- a/src/map/mapsforgemap.cpp +++ b/src/map/mapsforgemap.cpp @@ -150,7 +150,7 @@ void MapsforgeMap::jobFinished(MapsforgeMapJob *job) for (int i = 0; i < tiles.size(); i++) { const Mapsforge::RasterTile &mt = tiles.at(i); - if (mt.isValid()) + if (!mt.pixmap().isNull()) QPixmapCache::insert(key(mt.zoom(), mt.xy()), mt.pixmap()); } @@ -188,7 +188,8 @@ void MapsforgeMap::draw(QPainter *painter, const QRectF &rect, Flags flags) painter->drawPixmap(ttl, pm); else { tiles.append(RasterTile(_projection, _transform, &_style, &_data, - _zoom, QRect(ttl, QSize(tileSize, tileSize)), _tileRatio)); + _zoom, QRect(ttl, QSize(tileSize, tileSize)), _tileRatio, + flags & Map::HillShading)); } } } -- 2.11.4.GIT