Marble

ElevationModel.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2011 Niko Sams <niko.sams@gmail.com>
4//
5
6#include "ElevationModel.h"
7#include "GeoSceneDocument.h"
8#include "GeoSceneHead.h"
9#include "GeoSceneLayer.h"
10#include "GeoSceneMap.h"
11#include "GeoSceneTextureTileDataset.h"
12#include "HttpDownloadManager.h"
13#include "MapThemeManager.h"
14#include "MarbleDebug.h"
15#include "PluginManager.h"
16#include "Tile.h"
17#include "TileId.h"
18#include "TileLoader.h"
19#include "TileLoaderHelper.h"
20
21#include <QCache>
22#include <QImage>
23#include <qmath.h>
24
25namespace Marble
26{
27
28class ElevationModelPrivate
29{
30public:
31 ElevationModelPrivate(ElevationModel *_q, HttpDownloadManager *downloadManager, PluginManager *pluginManager)
32 : q(_q)
33 , m_tileLoader(downloadManager, pluginManager)
34 , m_textureLayer(nullptr)
35 , m_srtmTheme(nullptr)
36 {
37 m_cache.setMaxCost(10); // keep 10 tiles in memory (~17MB)
38
39 m_srtmTheme = MapThemeManager::loadMapTheme("earth/srtm2/srtm2.dgml");
40 if (!m_srtmTheme) {
41 mDebug() << "Failed to load map theme earth/srtm2/srtm2.dgml. Check your installation. No elevation will be returned.";
42 return;
43 }
44
45 const GeoSceneHead *head = m_srtmTheme->head();
46 Q_ASSERT(head);
47
48 const GeoSceneMap *map = m_srtmTheme->map();
49 Q_ASSERT(map);
50
51 const GeoSceneLayer *sceneLayer = map->layer(head->theme());
52 if (!sceneLayer) {
53 mDebug() << "Failed to instantiate elevation map. No elevation will be returned.";
54 return;
55 }
56 Q_ASSERT(sceneLayer);
57
58 m_textureLayer = dynamic_cast<GeoSceneTextureTileDataset *>(sceneLayer->datasets().first());
59 Q_ASSERT(m_textureLayer);
60 }
61
62 ~ElevationModelPrivate()
63 {
64 delete m_srtmTheme;
65 }
66
67 void tileCompleted(const TileId &tileId, const QImage &image)
68 {
69 m_cache.insert(tileId, new QImage(image));
70 Q_EMIT q->updateAvailable();
71 }
72
73public:
74 ElevationModel *q;
75
76 TileLoader m_tileLoader;
77 const GeoSceneTextureTileDataset *m_textureLayer;
79 GeoSceneDocument *m_srtmTheme;
80};
81
82ElevationModel::ElevationModel(HttpDownloadManager *downloadManager, PluginManager *pluginManager, QObject *parent)
83 : QObject(parent)
84 , d(new ElevationModelPrivate(this, downloadManager, pluginManager))
85{
86 connect(&d->m_tileLoader, SIGNAL(tileCompleted(TileId, QImage)), this, SLOT(tileCompleted(TileId, QImage)));
87}
88
89ElevationModel::~ElevationModel()
90{
91 delete d;
92}
93
94qreal ElevationModel::height(qreal lon, qreal lat) const
95{
96 if (!d->m_textureLayer) {
97 return invalidElevationData;
98 }
99
100 const int tileZoomLevel = TileLoader::maximumTileLevel(*(d->m_textureLayer));
101 Q_ASSERT(tileZoomLevel == 9);
102
103 const int width = d->m_textureLayer->tileSize().width();
104 const int height = d->m_textureLayer->tileSize().height();
105
106 const int numTilesX = TileLoaderHelper::levelToColumn(d->m_textureLayer->levelZeroColumns(), tileZoomLevel);
107 const int numTilesY = TileLoaderHelper::levelToRow(d->m_textureLayer->levelZeroRows(), tileZoomLevel);
108 Q_ASSERT(numTilesX > 0);
109 Q_ASSERT(numTilesY > 0);
110
111 qreal textureX = 180 + lon;
112 textureX *= numTilesX * width / 360;
113
114 qreal textureY = 90 - lat;
115 textureY *= numTilesY * height / 180;
116
117 qreal ret = 0;
118 bool hasHeight = false;
119 qreal noData = 0;
120
121 for (int i = 0; i < 4; ++i) {
122 const int x = static_cast<int>(textureX + (i % 2));
123 const int y = static_cast<int>(textureY + (i / 2));
124
125 // mDebug() << "x" << x << ( x / width );
126 // mDebug() << "y" << y << ( y / height );
127
128 const TileId id(0, tileZoomLevel, (x % (numTilesX * width)) / width, (y % (numTilesY * height)) / height);
129 // mDebug() << "LAT" << lat << "LON" << lon << "tile" << ( x % ( numTilesX * width ) ) / width << ( y % ( numTilesY * height ) ) / height;
130
131 const QImage *image = d->m_cache[id];
132 if (image == nullptr) {
133 image = new QImage(d->m_tileLoader.loadTileImage(d->m_textureLayer, id, DownloadBrowse));
134 d->m_cache.insert(id, image);
135 }
136 Q_ASSERT(image);
137 Q_ASSERT(!image->isNull());
138 Q_ASSERT(width == image->width());
139 Q_ASSERT(height == image->height());
140
141 const qreal dx = (textureX > (qreal)x) ? textureX - (qreal)x : (qreal)x - textureX;
142 const qreal dy = (textureY > (qreal)y) ? textureY - (qreal)y : (qreal)y - textureY;
143
144 Q_ASSERT(0 <= dx && dx <= 1);
145 Q_ASSERT(0 <= dy && dy <= 1);
146 unsigned int pixel = image->pixel(x % width, y % height) & 0xffff; // 16 valid bits
147 auto elevation = (short int)pixel; // and signed type, so just cast it
148 // mDebug() << "(1-dx)" << (1-dx) << "(1-dy)" << (1-dy);
149 if (pixel != invalidElevationData) { // no data?
150 // mDebug() << "got at x" << x % width << "y" << y % height << "a height of" << pixel << "** RGB" << qRed(pixel) << qGreen(pixel) << qBlue(pixel);
151 ret += (qreal)elevation * (1 - dx) * (1 - dy);
152 hasHeight = true;
153 } else {
154 // mDebug() << "no data at" << x % width << "y" << y % height;
155 noData += (1 - dx) * (1 - dy);
156 }
157 }
158
159 if (!hasHeight) {
160 ret = invalidElevationData; // no data
161 } else {
162 if (noData) {
163 // mDebug() << "NO DATA" << noData;
164 ret += (ret / (1 - noData)) * noData;
165 }
166 }
167
168 // mDebug() << ">>>" << lat << lon << "returning an elevation of" << ret;
169 return ret;
170}
171
172QList<GeoDataCoordinates> ElevationModel::heightProfile(qreal fromLon, qreal fromLat, qreal toLon, qreal toLat) const
173{
174 if (!d->m_textureLayer) {
175 return {};
176 }
177
178 const int tileZoomLevel = TileLoader::maximumTileLevel(*(d->m_textureLayer));
179 const int width = d->m_textureLayer->tileSize().width();
180 const int numTilesX = TileLoaderHelper::levelToColumn(d->m_textureLayer->levelZeroColumns(), tileZoomLevel);
181
182 qreal distPerPixel = (qreal)360 / (width * numTilesX);
183 // mDebug() << "heightProfile" << fromLat << fromLon << toLat << toLon << "distPerPixel" << distPerPixel;
184
185 qreal lat = fromLat;
186 qreal lon = fromLon;
187 char dirLat = fromLat < toLat ? 1 : -1;
188 char dirLon = fromLon < toLon ? 1 : -1;
189 qreal k = qAbs((fromLat - toLat) / (fromLon - toLon));
190 // mDebug() << "fromLon" << fromLon << "fromLat" << fromLat;
191 // mDebug() << "diff lon" << ( fromLon - toLon ) << "diff lat" << ( fromLat - toLat );
192 // mDebug() << "dirLon" << QString::number(dirLon) << "dirLat" << QString::number(dirLat) << "k" << k;
194 while (lat * dirLat <= toLat * dirLat && lon * dirLon <= toLon * dirLon) {
195 // mDebug() << lat << lon;
196 qreal h = height(lon, lat);
197 if (h < 32000) {
198 ret << GeoDataCoordinates(lon, lat, h, GeoDataCoordinates::Degree);
199 }
200 if (k < 0.5) {
201 // mDebug() << "lon(x) += distPerPixel";
202 lat += distPerPixel * k * dirLat;
203 lon += distPerPixel * dirLon;
204 } else {
205 // mDebug() << "lat(y) += distPerPixel";
206 lat += distPerPixel * dirLat;
207 lon += distPerPixel / k * dirLon;
208 }
209 }
210 // mDebug() << ret;
211 return ret;
212}
213
214}
215
216#include "moc_ElevationModel.cpp"
static GeoSceneDocument * loadMapTheme(const QString &mapThemeStringID)
Returns the map theme as a GeoSceneDocument object.
Binds a QML item to a specific geodetic location in screen coordinates.
bool insert(const Key &key, T *object, qsizetype cost)
void setMaxCost(qsizetype cost)
int height() const const
bool isNull() const const
QRgb pixel(const QPoint &position) const const
int width() const const
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 8 2024 12:02:43 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.