TerrainTile.cc 10.5 KB
Newer Older
Andreas Bircher's avatar
Andreas Bircher committed
1
2
#include "TerrainTile.h"
#include "JsonHelper.h"
Andreas Bircher's avatar
Andreas Bircher committed
3
#include "QGCMapEngine.h"
Andreas Bircher's avatar
Andreas Bircher committed
4
5
6
7

#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
8
#include <QDataStream>
Andreas Bircher's avatar
Andreas Bircher committed
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

QGC_LOGGING_CATEGORY(TerrainTileLog, "TerrainTileLog")

const char*  TerrainTile::_jsonStatusKey        = "status";
const char*  TerrainTile::_jsonDataKey          = "data";
const char*  TerrainTile::_jsonBoundsKey        = "bounds";
const char*  TerrainTile::_jsonSouthWestKey     = "sw";
const char*  TerrainTile::_jsonNorthEastKey     = "ne";
const char*  TerrainTile::_jsonStatsKey         = "stats";
const char*  TerrainTile::_jsonMaxElevationKey  = "max";
const char*  TerrainTile::_jsonMinElevationKey  = "min";
const char*  TerrainTile::_jsonAvgElevationKey  = "avg";
const char*  TerrainTile::_jsonCarpetKey        = "carpet";

TerrainTile::TerrainTile()
    : _minElevation(-1.0)
    , _maxElevation(-1.0)
    , _avgElevation(-1.0)
murata's avatar
murata committed
27
    , _data(nullptr)
28
29
    , _gridSizeLat(-1)
    , _gridSizeLon(-1)
Andreas Bircher's avatar
Andreas Bircher committed
30
31
32
33
34
35
36
    , _isValid(false)
{

}

TerrainTile::~TerrainTile()
{
37
38
39
40
    if (_data) {
        for (int i = 0; i < _gridSizeLat; i++) {
            delete _data[i];
        }
Andreas Bircher's avatar
fix    
Andreas Bircher committed
41
        delete _data;
murata's avatar
murata committed
42
        _data = nullptr;
43
    }
Andreas Bircher's avatar
Andreas Bircher committed
44
45
}

46
47

TerrainTile::TerrainTile(QByteArray byteArray)
Andreas Bircher's avatar
Andreas Bircher committed
48
49
50
    : _minElevation(-1.0)
    , _maxElevation(-1.0)
    , _avgElevation(-1.0)
murata's avatar
murata committed
51
    , _data(nullptr)
52
53
    , _gridSizeLat(-1)
    , _gridSizeLon(-1)
Andreas Bircher's avatar
Andreas Bircher committed
54
55
    , _isValid(false)
{
Don Gagne's avatar
   
Don Gagne committed
56
57
    int cTileHeaderBytes = static_cast<int>(sizeof(TileInfo_t));
    int cTileBytesAvailable = byteArray.size();
58

Don Gagne's avatar
   
Don Gagne committed
59
60
    if (cTileBytesAvailable < cTileHeaderBytes) {
        qWarning() << "Terrain tile binary data too small for TileInfo_s header";
61
62
63
        return;
    }

Don Gagne's avatar
   
Don Gagne committed
64
65
66
67
68
69
70
71
72
73
    const TileInfo_t* tileInfo = reinterpret_cast<const TileInfo_t*>(byteArray.constData());
    _southWest.setLatitude(tileInfo->swLat);
    _southWest.setLongitude(tileInfo->swLon);
    _northEast.setLatitude(tileInfo->neLat);
    _northEast.setLongitude(tileInfo->neLon);
    _minElevation = tileInfo->minElevation;
    _maxElevation = tileInfo->maxElevation;
    _avgElevation = tileInfo->avgElevation;
    _gridSizeLat = tileInfo->gridSizeLat;
    _gridSizeLon = tileInfo->gridSizeLon;
Sebastian Verling's avatar
Sebastian Verling committed
74

Don Gagne's avatar
   
Don Gagne committed
75
76
77
78
79
80
    qCDebug(TerrainTileLog) << "Loading terrain tile: " << _southWest << " - " << _northEast;
    qCDebug(TerrainTileLog) << "min:max:avg:sizeLat:sizeLon" << _minElevation << _maxElevation << _avgElevation << _gridSizeLat << _gridSizeLon;

    int cTileDataBytes = static_cast<int>(sizeof(int16_t)) * _gridSizeLat * _gridSizeLon;
    if (cTileBytesAvailable < cTileHeaderBytes + cTileDataBytes) {
        qWarning() << "Terrain tile binary data too small for tile data";
81
82
        return;
    }
83

Don Gagne's avatar
   
Don Gagne committed
84
85
86
87
    _data = new int16_t*[_gridSizeLat];
    for (int k = 0; k < _gridSizeLat; k++) {
        _data[k] = new int16_t[_gridSizeLon];
    }
88

Don Gagne's avatar
   
Don Gagne committed
89
90
    int valueIndex = 0;
    const int16_t* pTileData = reinterpret_cast<const int16_t*>(&reinterpret_cast<const uint8_t*>(byteArray.constData())[cTileHeaderBytes]);
91
92
    for (int i = 0; i < _gridSizeLat; i++) {
        for (int j = 0; j < _gridSizeLon; j++) {
Don Gagne's avatar
   
Don Gagne committed
93
            _data[i][j] = pTileData[valueIndex++];
94
95
96
97
        }
    }

    _isValid = true;
Don Gagne's avatar
   
Don Gagne committed
98
99

    return;
100
101
102
103
104
105
}


bool TerrainTile::isIn(const QGeoCoordinate& coordinate) const
{
    if (!_isValid) {
Don Gagne's avatar
   
Don Gagne committed
106
        qCWarning(TerrainTileLog) << "isIn requested, but tile not valid";
107
108
109
110
111
112
113
114
        return false;
    }
    bool ret = coordinate.latitude() >= _southWest.latitude() && coordinate.longitude() >= _southWest.longitude() &&
            coordinate.latitude() <= _northEast.latitude() && coordinate.longitude() <= _northEast.longitude();
    qCDebug(TerrainTileLog) << "Checking isIn: " << coordinate << " , in sw " << _southWest << " , ne " << _northEast << ": " << ret;
    return ret;
}

115
double TerrainTile::elevation(const QGeoCoordinate& coordinate) const
116
117
118
119
120
121
{
    if (_isValid) {
        qCDebug(TerrainTileLog) << "elevation: " << coordinate << " , in sw " << _southWest << " , ne " << _northEast;
        // Get the index at resolution of 1 arc second
        int indexLat = _latToDataIndex(coordinate.latitude());
        int indexLon = _lonToDataIndex(coordinate.longitude());
122
123
        if (indexLat == -1 || indexLon == -1) {
            qCWarning(TerrainTileLog) << "Internal error indexLat:indexLon == -1" << indexLat << indexLon;
Don Gagne's avatar
   
Don Gagne committed
124
            return qQNaN();
125
        }
126
        qCDebug(TerrainTileLog) << "indexLat:indexLon" << indexLat << indexLon << "elevation" << _data[indexLat][indexLon];
127
        return static_cast<double>(_data[indexLat][indexLon]);
128
    } else {
Don Gagne's avatar
   
Don Gagne committed
129
        qCWarning(TerrainTileLog) << "Asking for elevation, but no valid data.";
Don Gagne's avatar
   
Don Gagne committed
130
        return qQNaN();
131
132
133
134
135
136
137
138
    }
}

QGeoCoordinate TerrainTile::centerCoordinate(void) const
{
    return _southWest.atDistanceAndAzimuth(_southWest.distanceTo(_northEast) / 2.0, _southWest.azimuthTo(_northEast));
}

139
QByteArray TerrainTile::serialize(QByteArray input)
140
{
141
142
143
144
145
146
147
    QJsonParseError parseError;
    QJsonDocument document = QJsonDocument::fromJson(input, &parseError);
    if (parseError.error != QJsonParseError::NoError) {
        QByteArray emptyArray;
        return emptyArray;
    }

Andreas Bircher's avatar
Andreas Bircher committed
148
    if (!document.isObject()) {
Andreas Bircher's avatar
Andreas Bircher committed
149
        qCDebug(TerrainTileLog) << "Terrain tile json doc is no object";
150
151
        QByteArray emptyArray;
        return emptyArray;
Andreas Bircher's avatar
Andreas Bircher committed
152
    }
Andreas Bircher's avatar
Andreas Bircher committed
153
    QJsonObject rootObject = document.object();
Andreas Bircher's avatar
Andreas Bircher committed
154
155
156
157

    QString errorString;
    QList<JsonHelper::KeyValidateInfo> rootVersionKeyInfoList = {
        { _jsonStatusKey, QJsonValue::String, true },
158
        { _jsonDataKey,   QJsonValue::Object, true },
Andreas Bircher's avatar
Andreas Bircher committed
159
160
161
    };
    if (!JsonHelper::validateKeys(rootObject, rootVersionKeyInfoList, errorString)) {
        qCDebug(TerrainTileLog) << "Error in reading json: " << errorString;
162
163
        QByteArray emptyArray;
        return emptyArray;
Andreas Bircher's avatar
Andreas Bircher committed
164
165
166
167
    }

    if (rootObject[_jsonStatusKey].toString() != "success") {
        qCDebug(TerrainTileLog) << "Invalid terrain tile.";
168
169
        QByteArray emptyArray;
        return emptyArray;
Andreas Bircher's avatar
Andreas Bircher committed
170
171
172
173
    }
    const QJsonObject& dataObject = rootObject[_jsonDataKey].toObject();
    QList<JsonHelper::KeyValidateInfo> dataVersionKeyInfoList = {
        { _jsonBoundsKey, QJsonValue::Object, true },
174
        { _jsonStatsKey,  QJsonValue::Object, true },
Andreas Bircher's avatar
Andreas Bircher committed
175
176
177
178
        { _jsonCarpetKey, QJsonValue::Array, true },
    };
    if (!JsonHelper::validateKeys(dataObject, dataVersionKeyInfoList, errorString)) {
        qCDebug(TerrainTileLog) << "Error in reading json: " << errorString;
179
180
        QByteArray emptyArray;
        return emptyArray;
Andreas Bircher's avatar
Andreas Bircher committed
181
182
183
184
185
186
187
188
189
190
    }

    // Bounds
    const QJsonObject& boundsObject = dataObject[_jsonBoundsKey].toObject();
    QList<JsonHelper::KeyValidateInfo> boundsVersionKeyInfoList = {
        { _jsonSouthWestKey, QJsonValue::Array, true },
        { _jsonNorthEastKey, QJsonValue::Array, true },
    };
    if (!JsonHelper::validateKeys(boundsObject, boundsVersionKeyInfoList, errorString)) {
        qCDebug(TerrainTileLog) << "Error in reading json: " << errorString;
191
192
        QByteArray emptyArray;
        return emptyArray;
Andreas Bircher's avatar
Andreas Bircher committed
193
194
195
196
197
    }
    const QJsonArray& swArray = boundsObject[_jsonSouthWestKey].toArray();
    const QJsonArray& neArray = boundsObject[_jsonNorthEastKey].toArray();
    if (swArray.count() < 2 || neArray.count() < 2 ) {
        qCDebug(TerrainTileLog) << "Incomplete bounding location";
198
199
        QByteArray emptyArray;
        return emptyArray;
Andreas Bircher's avatar
Andreas Bircher committed
200
201
202
    }

    // Stats
203
    const QJsonObject& statsObject = dataObject[_jsonStatsKey].toObject();
Andreas Bircher's avatar
Andreas Bircher committed
204
205
    QList<JsonHelper::KeyValidateInfo> statsVersionKeyInfoList = {
        { _jsonMinElevationKey, QJsonValue::Double, true },
206
        { _jsonMaxElevationKey, QJsonValue::Double, true },
Andreas Bircher's avatar
Andreas Bircher committed
207
208
209
210
        { _jsonAvgElevationKey, QJsonValue::Double, true },
    };
    if (!JsonHelper::validateKeys(statsObject, statsVersionKeyInfoList, errorString)) {
        qCDebug(TerrainTileLog) << "Error in reading json: " << errorString;
211
212
        QByteArray emptyArray;
        return emptyArray;
Andreas Bircher's avatar
Andreas Bircher committed
213
214
215
216
    }

    // Carpet
    const QJsonArray& carpetArray = dataObject[_jsonCarpetKey].toArray();
217
    int gridSizeLat = carpetArray.count();
Don Gagne's avatar
   
Don Gagne committed
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
    int gridSizeLon = carpetArray[0].toArray().count();
    qCDebug(TerrainTileLog) << "Received tile has size in latitude direction: " << gridSizeLat;
    qCDebug(TerrainTileLog) << "Received tile has size in longitued direction: " << gridSizeLon;

    TileInfo_t tileInfo;

    tileInfo.swLat = swArray[0].toDouble();
    tileInfo.swLon = swArray[1].toDouble();
    tileInfo.neLat = neArray[0].toDouble();
    tileInfo.neLon = neArray[1].toDouble();
    tileInfo.minElevation = static_cast<int16_t>(statsObject[_jsonMinElevationKey].toInt());
    tileInfo.maxElevation = static_cast<int16_t>(statsObject[_jsonMaxElevationKey].toInt());
    tileInfo.avgElevation = statsObject[_jsonAvgElevationKey].toDouble();
    tileInfo.gridSizeLat = static_cast<int16_t>(gridSizeLat);
    tileInfo.gridSizeLon = static_cast<int16_t>(gridSizeLon);

    int cTileHeaderBytes = static_cast<int>(sizeof(TileInfo_t));
    int cTileDataBytes = static_cast<int>(sizeof(int16_t)) * gridSizeLat * gridSizeLon;

    QByteArray byteArray(cTileHeaderBytes + cTileDataBytes, 0);

    TileInfo_t* pTileInfo = reinterpret_cast<TileInfo_t*>(byteArray.data());
    int16_t*    pTileData = reinterpret_cast<int16_t*>(&reinterpret_cast<uint8_t*>(byteArray.data())[cTileHeaderBytes]);

    *pTileInfo = tileInfo;

    int valueIndex = 0;
245
    for (int i = 0; i < gridSizeLat; i++) {
Andreas Bircher's avatar
Andreas Bircher committed
246
        const QJsonArray& row = carpetArray[i].toArray();
247
248
249
250
        if (row.count() < gridSizeLon) {
            qCDebug(TerrainTileLog) << "Expected row array of " << gridSizeLon << ", instead got " << row.count();
            QByteArray emptyArray;
            return emptyArray;
Andreas Bircher's avatar
Andreas Bircher committed
251
        }
252
        for (int j = 0; j < gridSizeLon; j++) {
Don Gagne's avatar
   
Don Gagne committed
253
            pTileData[valueIndex++] = static_cast<int16_t>(row[j].toDouble());
Andreas Bircher's avatar
Andreas Bircher committed
254
255
256
        }
    }

257
    return byteArray;
Andreas Bircher's avatar
Andreas Bircher committed
258
259
}

260
261
262
263
264
265

int TerrainTile::_latToDataIndex(double latitude) const
{
    if (isValid() && _southWest.isValid() && _northEast.isValid()) {
        return qRound((latitude - _southWest.latitude()) / (_northEast.latitude() - _southWest.latitude()) * (_gridSizeLat - 1));
    } else {
Don Gagne's avatar
   
Don Gagne committed
266
        qCWarning(TerrainTileLog) << "TerrainTile::_latToDataIndex internal error" << isValid() << _southWest.isValid() << _northEast.isValid();
267
268
269
270
271
272
273
274
275
        return -1;
    }
}

int TerrainTile::_lonToDataIndex(double longitude) const
{
    if (isValid() && _southWest.isValid() && _northEast.isValid()) {
        return qRound((longitude - _southWest.longitude()) / (_northEast.longitude() - _southWest.longitude()) * (_gridSizeLon - 1));
    } else {
Don Gagne's avatar
   
Don Gagne committed
276
        qCWarning(TerrainTileLog) << "TerrainTile::_lonToDataIndex internal error" << isValid() << _southWest.isValid() << _northEast.isValid();
277
278
279
        return -1;
    }
}