TerrainTile.cc 10.6 KB
Newer Older
Andreas Bircher's avatar
Andreas Bircher committed
1 2
#include "TerrainTile.h"
#include "JsonHelper.h"
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)
27 28 29
    , _data(NULL)
    , _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
Andreas Bircher committed
41 42
        delete _data;
        _data = NULL;
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)
51 52 53
    , _data(NULL)
    , _gridSizeLat(-1)
    , _gridSizeLon(-1)
Andreas Bircher's avatar
Andreas Bircher committed
54 55
    , _isValid(false)
{
56 57
    QDataStream stream(byteArray);

58
    float lat,lon;
59 60 61 62 63 64 65 66 67 68
    if (stream.atEnd()) {
        qWarning() << "Terrain tile binary data does not contain all data";
        return;
    }
    stream >> lat;
    if (stream.atEnd()) {
        qWarning() << "Terrain tile binary data does not contain all data";
        return;
    }
    stream >> lon;
69 70
    _southWest.setLatitude(lat);
    _southWest.setLongitude(lon);
71 72 73 74 75 76 77 78 79 80 81

    if (stream.atEnd()) {
        qWarning() << "Terrain tile binary data does not contain all data";
        return;
    }
    stream >> lat;
    if (stream.atEnd()) {
        qWarning() << "Terrain tile binary data does not contain all data";
        return;
    }
    stream >> lon;
82 83 84
    _northEast.setLatitude(lat);
    _northEast.setLongitude(lon);

85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
    if (stream.atEnd()) {
        qWarning() << "Terrain tile binary data does not contain all data";
        return;
    }
    stream >> _minElevation;
    if (stream.atEnd()) {
        qWarning() << "Terrain tile binary data does not contain all data";
        return;
    }
    stream >> _maxElevation;
    if (stream.atEnd()) {
        qWarning() << "Terrain tile binary data does not contain all data";
        return;
    }
    stream >> _avgElevation;
    if (stream.atEnd()) {
        qWarning() << "Terrain tile binary data does not contain all data";
        return;
    }
    stream >> _gridSizeLat;
    if (stream.atEnd()) {
        qWarning() << "Terrain tile binary data does not contain all data";
        return;
    }
    stream >> _gridSizeLon;
110 111 112

    qCDebug(TerrainTileLog) << "Loading terrain tile: " << _southWest << " - " << _northEast;
    qCDebug(TerrainTileLog) << "min:max:avg:sizeLat:sizeLon" << _minElevation << _maxElevation << _avgElevation << _gridSizeLat << _gridSizeLon;
113 114 115

    for (int i = 0; i < _gridSizeLat; i++) {
        if (i == 0) {
116
            _data = new int16_t*[_gridSizeLat];
117
            for (int k = 0; k < _gridSizeLat; k++) {
118
                _data[k] = new int16_t[_gridSizeLon];
119 120 121
            }
        }
        for (int j = 0; j < _gridSizeLon; j++) {
122 123 124 125
            if (stream.atEnd()) {
                qWarning() << "Terrain tile binary data does not contain all data";
                return;
            }
126 127 128 129 130 131 132 133 134 135 136
            stream >> _data[i][j];
        }
    }

    _isValid = true;
}


bool TerrainTile::isIn(const QGeoCoordinate& coordinate) const
{
    if (!_isValid) {
137
        qCWarning(TerrainTileLog) << "isIn requested, but tile not valid";
138 139 140 141 142 143 144 145
        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;
}

146
double TerrainTile::elevation(const QGeoCoordinate& coordinate) const
147 148 149 150 151 152
{
    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());
153 154 155 156
        if (indexLat == -1 || indexLon == -1) {
            qCWarning(TerrainTileLog) << "Internal error indexLat:indexLon == -1" << indexLat << indexLon;
            return -1.0;
        }
157
        qCDebug(TerrainTileLog) << "indexLat:indexLon" << indexLat << indexLon << "elevation" << _data[indexLat][indexLon];
158
        return static_cast<double>(_data[indexLat][indexLon]);
159
    } else {
160
        qCWarning(TerrainTileLog) << "Asking for elevation, but no valid data.";
161 162 163 164 165 166 167 168 169
        return -1.0;
    }
}

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

170
QByteArray TerrainTile::serialize(QByteArray input)
171
{
172 173 174 175 176 177 178
    QJsonParseError parseError;
    QJsonDocument document = QJsonDocument::fromJson(input, &parseError);
    if (parseError.error != QJsonParseError::NoError) {
        QByteArray emptyArray;
        return emptyArray;
    }

179
    QByteArray byteArray;
180
    QDataStream stream(&byteArray, QIODevice::WriteOnly);
Andreas Bircher's avatar
Andreas Bircher committed
181
    if (!document.isObject()) {
Andreas Bircher's avatar
Andreas Bircher committed
182
        qCDebug(TerrainTileLog) << "Terrain tile json doc is no object";
183 184
        QByteArray emptyArray;
        return emptyArray;
Andreas Bircher's avatar
Andreas Bircher committed
185
    }
Andreas Bircher's avatar
Andreas Bircher committed
186
    QJsonObject rootObject = document.object();
Andreas Bircher's avatar
Andreas Bircher committed
187 188 189 190

    QString errorString;
    QList<JsonHelper::KeyValidateInfo> rootVersionKeyInfoList = {
        { _jsonStatusKey, QJsonValue::String, true },
191
        { _jsonDataKey,   QJsonValue::Object, true },
Andreas Bircher's avatar
Andreas Bircher committed
192 193 194
    };
    if (!JsonHelper::validateKeys(rootObject, rootVersionKeyInfoList, errorString)) {
        qCDebug(TerrainTileLog) << "Error in reading json: " << errorString;
195 196
        QByteArray emptyArray;
        return emptyArray;
Andreas Bircher's avatar
Andreas Bircher committed
197 198 199 200
    }

    if (rootObject[_jsonStatusKey].toString() != "success") {
        qCDebug(TerrainTileLog) << "Invalid terrain tile.";
201 202
        QByteArray emptyArray;
        return emptyArray;
Andreas Bircher's avatar
Andreas Bircher committed
203 204 205 206
    }
    const QJsonObject& dataObject = rootObject[_jsonDataKey].toObject();
    QList<JsonHelper::KeyValidateInfo> dataVersionKeyInfoList = {
        { _jsonBoundsKey, QJsonValue::Object, true },
207
        { _jsonStatsKey,  QJsonValue::Object, true },
Andreas Bircher's avatar
Andreas Bircher committed
208 209 210 211
        { _jsonCarpetKey, QJsonValue::Array, true },
    };
    if (!JsonHelper::validateKeys(dataObject, dataVersionKeyInfoList, errorString)) {
        qCDebug(TerrainTileLog) << "Error in reading json: " << errorString;
212 213
        QByteArray emptyArray;
        return emptyArray;
Andreas Bircher's avatar
Andreas Bircher committed
214 215 216 217 218 219 220 221 222 223
    }

    // 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;
224 225
        QByteArray emptyArray;
        return emptyArray;
Andreas Bircher's avatar
Andreas Bircher committed
226 227 228 229 230
    }
    const QJsonArray& swArray = boundsObject[_jsonSouthWestKey].toArray();
    const QJsonArray& neArray = boundsObject[_jsonNorthEastKey].toArray();
    if (swArray.count() < 2 || neArray.count() < 2 ) {
        qCDebug(TerrainTileLog) << "Incomplete bounding location";
231 232
        QByteArray emptyArray;
        return emptyArray;
Andreas Bircher's avatar
Andreas Bircher committed
233
    }
234 235 236 237
    stream << static_cast<float>(swArray[0].toDouble());
    stream << static_cast<float>(swArray[1].toDouble());
    stream << static_cast<float>(neArray[0].toDouble());
    stream << static_cast<float>(neArray[1].toDouble());
Andreas Bircher's avatar
Andreas Bircher committed
238 239

    // Stats
240
    const QJsonObject& statsObject = dataObject[_jsonStatsKey].toObject();
Andreas Bircher's avatar
Andreas Bircher committed
241 242
    QList<JsonHelper::KeyValidateInfo> statsVersionKeyInfoList = {
        { _jsonMinElevationKey, QJsonValue::Double, true },
243
        { _jsonMaxElevationKey, QJsonValue::Double, true },
Andreas Bircher's avatar
Andreas Bircher committed
244 245 246 247
        { _jsonAvgElevationKey, QJsonValue::Double, true },
    };
    if (!JsonHelper::validateKeys(statsObject, statsVersionKeyInfoList, errorString)) {
        qCDebug(TerrainTileLog) << "Error in reading json: " << errorString;
248 249
        QByteArray emptyArray;
        return emptyArray;
Andreas Bircher's avatar
Andreas Bircher committed
250
    }
251 252 253
    stream << static_cast<int16_t>(statsObject[_jsonMinElevationKey].toInt());
    stream << static_cast<int16_t>(statsObject[_jsonMaxElevationKey].toInt());
    stream << static_cast<float>(statsObject[_jsonAvgElevationKey].toDouble());
Andreas Bircher's avatar
Andreas Bircher committed
254 255 256

    // Carpet
    const QJsonArray& carpetArray = dataObject[_jsonCarpetKey].toArray();
257
    int gridSizeLat = carpetArray.count();
258
    stream << static_cast<int16_t>(gridSizeLat);
259
    int gridSizeLon = 0;
260
    qCDebug(TerrainTileLog) << "Received tile has size in latitude direction: " << carpetArray.count();
261
    for (int i = 0; i < gridSizeLat; i++) {
Andreas Bircher's avatar
Andreas Bircher committed
262
        const QJsonArray& row = carpetArray[i].toArray();
263
        if (i == 0) {
264
            gridSizeLon = row.count();
265
            stream << static_cast<int16_t>(gridSizeLon);
266 267
            qCDebug(TerrainTileLog) << "Received tile has size in longitued direction: " << row.count();
        }
268 269 270 271
        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
272
        }
273
        for (int j = 0; j < gridSizeLon; j++) {
274
            stream << static_cast<int16_t>(row[j].toDouble());
Andreas Bircher's avatar
Andreas Bircher committed
275 276 277
        }
    }

278
    return byteArray;
Andreas Bircher's avatar
Andreas Bircher committed
279 280
}

281 282 283 284 285 286

int TerrainTile::_latToDataIndex(double latitude) const
{
    if (isValid() && _southWest.isValid() && _northEast.isValid()) {
        return qRound((latitude - _southWest.latitude()) / (_northEast.latitude() - _southWest.latitude()) * (_gridSizeLat - 1));
    } else {
287
        qCWarning(TerrainTileLog) << "TerrainTile::_latToDataIndex internal error" << isValid() << _southWest.isValid() << _northEast.isValid();
288 289 290 291 292 293 294 295 296
        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 {
297
        qCWarning(TerrainTileLog) << "TerrainTile::_lonToDataIndex internal error" << isValid() << _southWest.isValid() << _northEast.isValid();
298 299 300
        return -1;
    }
}