FactMetaData.cc 42.1 KB
Newer Older
1 2 3 4 5 6 7 8 9
/****************************************************************************
 *
 *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/

Don Gagne's avatar
Don Gagne committed
10
#include "FactMetaData.h"
11
#include "SettingsManager.h"
12
#include "JsonHelper.h"
13
#include "QGCApplication.h"
Don Gagne's avatar
Don Gagne committed
14

15
#include <QDebug>
16
#include <QtMath>
17 18
#include <QJsonParseError>
#include <QJsonArray>
19

Don Gagne's avatar
Don Gagne committed
20
#include <limits>
21 22
#include <cmath>

23 24 25 26 27 28 29 30
// Conversion Constants
// Time
const qreal FactMetaData::UnitConsts_s::secondsPerHour = 3600.0;

// Velocity
const qreal FactMetaData::UnitConsts_s::knotsToKPH = 1.852; // exact, hence weird base for knotsToMetersPerSecond

// Length
31 32 33
const qreal FactMetaData::UnitConsts_s::milesToMeters =         1609.344;
const qreal FactMetaData::UnitConsts_s::feetToMeters =          0.3048;
const qreal FactMetaData::UnitConsts_s::inchesToCentimeters =   2.54;
34

35 36 37
const QString FactMetaData::defaultCategory =   tr("Other");
const QString FactMetaData::defaultGroup =      tr("Misc");

38 39
// Built in translations for all Facts
const FactMetaData::BuiltInTranslation_s FactMetaData::_rgBuiltInTranslations[] = {
40 41 42 43
    { "centi-degrees",  "deg",  FactMetaData::_centiDegreesToDegrees,                   FactMetaData::_degreesToCentiDegrees },
    { "radians",        "deg",  FactMetaData::_radiansToDegrees,                        FactMetaData::_degreesToRadians },
    { "gimbal-degrees", "deg",  FactMetaData::_mavlinkGimbalDegreesToUserGimbalDegrees, FactMetaData::_userGimbalDegreesToMavlinkGimbalDegrees },
    { "norm",           "%",    FactMetaData::_normToPercent,                           FactMetaData::_percentToNorm },
44
};
Don Gagne's avatar
Don Gagne committed
45

46 47
// Translations driven by app settings
const FactMetaData::AppSettingsTranslation_s FactMetaData::_rgAppSettingsTranslations[] = {
48 49
    { "m",      "m",        false,  UnitsSettings::DistanceUnitsMeters,         FactMetaData::_defaultTranslator,                   FactMetaData::_defaultTranslator },
    { "meters", "meters",   false,  UnitsSettings::DistanceUnitsMeters,         FactMetaData::_defaultTranslator,                   FactMetaData::_defaultTranslator },
50
    { "cm/px",  "cm/px",    false,  UnitsSettings::DistanceUnitsMeters,         FactMetaData::_defaultTranslator,                   FactMetaData::_defaultTranslator },
51
    { "m/s",    "m/s",      true,   UnitsSettings::SpeedUnitsMetersPerSecond,   FactMetaData::_defaultTranslator,                   FactMetaData::_defaultTranslator },
52
    { "C",      "C",        false,  UnitsSettings::TemperatureUnitsCelsius,     FactMetaData::_defaultTranslator,                   FactMetaData::_defaultTranslator },
53 54 55
    { "m^2",    "m^2",      false,  UnitsSettings::AreaUnitsSquareMeters,       FactMetaData::_defaultTranslator,                   FactMetaData::_defaultTranslator },
    { "m",      "ft",       false,  UnitsSettings::DistanceUnitsFeet,           FactMetaData::_metersToFeet,                        FactMetaData::_feetToMeters },
    { "meters", "ft",       false,  UnitsSettings::DistanceUnitsFeet,           FactMetaData::_metersToFeet,                        FactMetaData::_feetToMeters },
56
    { "cm/px",  "in/px",    false,  UnitsSettings::DistanceUnitsFeet,           FactMetaData::_centimetersToInches,                 FactMetaData::_inchesToCentimeters },
57 58 59 60 61 62 63 64 65
    { "m^2",    "km^2",     false,  UnitsSettings::AreaUnitsSquareKilometers,   FactMetaData::_squareMetersToSquareKilometers,      FactMetaData::_squareKilometersToSquareMeters },
    { "m^2",    "ha",       false,  UnitsSettings::AreaUnitsHectares,           FactMetaData::_squareMetersToHectares,              FactMetaData::_hectaresToSquareMeters },
    { "m^2",    "ft^2",     false,  UnitsSettings::AreaUnitsSquareFeet,         FactMetaData::_squareMetersToSquareFeet,            FactMetaData::_squareFeetToSquareMeters },
    { "m^2",    "ac",       false,  UnitsSettings::AreaUnitsAcres,              FactMetaData::_squareMetersToAcres,                 FactMetaData::_acresToSquareMeters },
    { "m^2",    "mi^2",     false,  UnitsSettings::AreaUnitsSquareMiles,        FactMetaData::_squareMetersToSquareMiles,           FactMetaData::_squareMilesToSquareMeters },
    { "m/s",    "ft/s",     true,   UnitsSettings::SpeedUnitsFeetPerSecond,     FactMetaData::_metersToFeet,                        FactMetaData::_feetToMeters },
    { "m/s",    "mph",      true,   UnitsSettings::SpeedUnitsMilesPerHour,      FactMetaData::_metersPerSecondToMilesPerHour,       FactMetaData::_milesPerHourToMetersPerSecond },
    { "m/s",    "km/h",     true,   UnitsSettings::SpeedUnitsKilometersPerHour, FactMetaData::_metersPerSecondToKilometersPerHour,  FactMetaData::_kilometersPerHourToMetersPerSecond },
    { "m/s",    "kn",       true,   UnitsSettings::SpeedUnitsKnots,             FactMetaData::_metersPerSecondToKnots,              FactMetaData::_knotsToMetersPerSecond },
66
    { "C",      "F",        false,  UnitsSettings::TemperatureUnitsFarenheit,   FactMetaData::_celsiusToFarenheit,                  FactMetaData::_farenheitToCelsius },
67 68
};

69 70 71 72 73 74 75
const char* FactMetaData::_decimalPlacesJsonKey =       "decimalPlaces";
const char* FactMetaData::_nameJsonKey =                "name";
const char* FactMetaData::_typeJsonKey =                "type";
const char* FactMetaData::_shortDescriptionJsonKey =    "shortDescription";
const char* FactMetaData::_longDescriptionJsonKey =     "longDescription";
const char* FactMetaData::_unitsJsonKey =               "units";
const char* FactMetaData::_defaultValueJsonKey =        "defaultValue";
76
const char* FactMetaData::_mobileDefaultValueJsonKey =  "mobileDefaultValue";
77 78
const char* FactMetaData::_minJsonKey =                 "min";
const char* FactMetaData::_maxJsonKey =                 "max";
79
const char* FactMetaData::_hasControlJsonKey =          "control";
80

81
FactMetaData::FactMetaData(QObject* parent)
82 83 84 85
    : QObject               (parent)
    , _type                 (valueTypeInt32)
    , _decimalPlaces        (unknownDecimalPlaces)
    , _rawDefaultValue      (0)
86
    , _defaultValueAvailable(false)
87 88 89 90 91 92 93 94 95 96 97 98
    , _category             (defaultCategory)
    , _group                (defaultGroup)
    , _rawMax               (_maxForType())
    , _maxIsDefaultForType  (true)
    , _rawMin               (_minForType())
    , _minIsDefaultForType  (true)
    , _rawTranslator        (_defaultTranslator)
    , _cookedTranslator     (_defaultTranslator)
    , _rebootRequired       (false)
    , _increment            (std::numeric_limits<double>::quiet_NaN())
    , _hasControl           (true)
    , _readOnly             (false)
Don Gagne's avatar
Don Gagne committed
99 100 101 102
{

}

103
FactMetaData::FactMetaData(ValueType_t type, QObject* parent)
104 105 106 107
    : QObject               (parent)
    , _type                 (type)
    , _decimalPlaces        (unknownDecimalPlaces)
    , _rawDefaultValue      (0)
108
    , _defaultValueAvailable(false)
109 110 111 112 113 114 115 116 117 118 119 120
    , _category             (defaultCategory)
    , _group                (defaultGroup)
    , _rawMax               (_maxForType())
    , _maxIsDefaultForType  (true)
    , _rawMin               (_minForType())
    , _minIsDefaultForType  (true)
    , _rawTranslator        (_defaultTranslator)
    , _cookedTranslator     (_defaultTranslator)
    , _rebootRequired       (false)
    , _increment            (std::numeric_limits<double>::quiet_NaN())
    , _hasControl           (true)
    , _readOnly             (false)
121 122 123 124
{

}

Don Gagne's avatar
Don Gagne committed
125 126 127 128 129 130
FactMetaData::FactMetaData(const FactMetaData& other, QObject* parent)
    : QObject(parent)
{
    *this = other;
}

131
FactMetaData::FactMetaData(ValueType_t type, const QString name, QObject* parent)
132 133 134 135
    : QObject               (parent)
    , _type                 (type)
    , _decimalPlaces        (unknownDecimalPlaces)
    , _rawDefaultValue      (0)
136
    , _defaultValueAvailable(false)
137 138 139 140 141 142 143 144 145 146 147 148 149
    , _category             (defaultCategory)
    , _group                (defaultGroup)
    , _rawMax               (_maxForType())
    , _maxIsDefaultForType  (true)
    , _rawMin               (_minForType())
    , _minIsDefaultForType  (true)
    , _name                 (name)
    , _rawTranslator        (_defaultTranslator)
    , _cookedTranslator     (_defaultTranslator)
    , _rebootRequired       (false)
    , _increment            (std::numeric_limits<double>::quiet_NaN())
    , _hasControl           (true)
    , _readOnly             (false)
150 151 152 153
{

}

Don Gagne's avatar
Don Gagne committed
154 155
const FactMetaData& FactMetaData::operator=(const FactMetaData& other)
{
156
    _decimalPlaces          = other._decimalPlaces;
157
    _rawDefaultValue        = other._rawDefaultValue;
Don Gagne's avatar
Don Gagne committed
158
    _defaultValueAvailable  = other._defaultValueAvailable;
159 160
    _bitmaskStrings         = other._bitmaskStrings;
    _bitmaskValues          = other._bitmaskValues;
161 162
    _enumStrings            = other._enumStrings;
    _enumValues             = other._enumValues;
163
    _category               = other._category;
164 165
    _group                  = other._group;
    _longDescription        = other._longDescription;
166
    _rawMax                 = other._rawMax;
Don Gagne's avatar
Don Gagne committed
167
    _maxIsDefaultForType    = other._maxIsDefaultForType;
168
    _rawMin                 = other._rawMin;
169 170 171 172
    _minIsDefaultForType    = other._minIsDefaultForType;
    _name                   = other._name;
    _shortDescription       = other._shortDescription;
    _type                   = other._type;
173 174
    _rawUnits               = other._rawUnits;
    _cookedUnits            = other._cookedUnits;
175 176
    _rawTranslator          = other._rawTranslator;
    _cookedTranslator       = other._cookedTranslator;
177
    _rebootRequired         = other._rebootRequired;
Don Gagne's avatar
Don Gagne committed
178
    _increment              = other._increment;
179
    _hasControl             = other._hasControl;
180
    _readOnly               = other._readOnly;
Don Gagne's avatar
Don Gagne committed
181 182 183
    return *this;
}

184
QVariant FactMetaData::rawDefaultValue(void) const
185 186
{
    if (_defaultValueAvailable) {
187
        return _rawDefaultValue;
188 189 190 191 192 193
    } else {
        qWarning() << "Attempt to access unavailable default value";
        return QVariant(0);
    }
}

194
void FactMetaData::setRawDefaultValue(const QVariant& rawDefaultValue)
195
{
Don Gagne's avatar
Don Gagne committed
196
    if (_type == valueTypeString || (_rawMin <= rawDefaultValue && rawDefaultValue <= _rawMax)) {
197
        _rawDefaultValue = rawDefaultValue;
198 199 200 201 202 203
        _defaultValueAvailable = true;
    } else {
        qWarning() << "Attempt to set default value which is outside min/max range";
    }
}

204
void FactMetaData::setRawMin(const QVariant& rawMin)
205
{
Don Gagne's avatar
Don Gagne committed
206
    if (rawMin >= _minForType()) {
207
        _rawMin = rawMin;
Don Gagne's avatar
Don Gagne committed
208
        _minIsDefaultForType = false;
209
    } else {
210
        qWarning() << "Attempt to set min below allowable value for fact: " << name()
211
                   << ", value attempted: " << rawMin
212
                   << ", type: " << type() << ", min for type: " << _minForType();
213
        _rawMin = _minForType();
214 215 216
    }
}

217
void FactMetaData::setRawMax(const QVariant& rawMax)
218
{
219
    if (rawMax > _maxForType()) {
220
        qWarning() << "Attempt to set max above allowable value";
221
        _rawMax = _maxForType();
222
    } else {
223
        _rawMax = rawMax;
Don Gagne's avatar
Don Gagne committed
224
        _maxIsDefaultForType = false;
225 226 227
    }
}

228
QVariant FactMetaData::_minForType(void) const
Don Gagne's avatar
Don Gagne committed
229
{
230
    switch (_type) {
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
    case valueTypeUint8:
        return QVariant(std::numeric_limits<unsigned char>::min());
    case valueTypeInt8:
        return QVariant(std::numeric_limits<signed char>::min());
    case valueTypeUint16:
        return QVariant(std::numeric_limits<unsigned short int>::min());
    case valueTypeInt16:
        return QVariant(std::numeric_limits<short int>::min());
    case valueTypeUint32:
        return QVariant(std::numeric_limits<unsigned int>::min());
    case valueTypeInt32:
        return QVariant(std::numeric_limits<int>::min());
    case valueTypeFloat:
        return QVariant(-std::numeric_limits<float>::max());
    case valueTypeDouble:
        return QVariant(-std::numeric_limits<double>::max());
Don Gagne's avatar
Don Gagne committed
247 248
    case valueTypeString:
        return QVariant();
249 250
    case valueTypeBool:
        return QVariant(0);
251 252
    case valueTypeElapsedTimeInSeconds:
        return QVariant(0.0);
253 254
    case valueTypeCustom:
        return QVariant();
255
    }
Don Gagne's avatar
Don Gagne committed
256 257 258
    
    // Make windows compiler happy, even switch is full cased
    return QVariant();
Don Gagne's avatar
Don Gagne committed
259
}
Don Gagne's avatar
Don Gagne committed
260

261
QVariant FactMetaData::_maxForType(void) const
Don Gagne's avatar
Don Gagne committed
262
{
263
    switch (_type) {
264 265 266 267 268 269 270 271 272 273 274 275 276 277
    case valueTypeUint8:
        return QVariant(std::numeric_limits<unsigned char>::max());
    case valueTypeInt8:
        return QVariant(std::numeric_limits<signed char>::max());
    case valueTypeUint16:
        return QVariant(std::numeric_limits<unsigned short int>::max());
    case valueTypeInt16:
        return QVariant(std::numeric_limits<short int>::max());
    case valueTypeUint32:
        return QVariant(std::numeric_limits<unsigned int>::max());
    case valueTypeInt32:
        return QVariant(std::numeric_limits<int>::max());
    case valueTypeFloat:
        return QVariant(std::numeric_limits<float>::max());
278
    case valueTypeElapsedTimeInSeconds:
279 280
    case valueTypeDouble:
        return QVariant(std::numeric_limits<double>::max());
Don Gagne's avatar
Don Gagne committed
281 282
    case valueTypeString:
        return QVariant();
283 284
    case valueTypeBool:
        return QVariant(1);
285 286
    case valueTypeCustom:
        return QVariant();
287
    }
Don Gagne's avatar
Don Gagne committed
288 289 290
    
    // Make windows compiler happy, even switch is full cased
    return QVariant();
Don Gagne's avatar
Don Gagne committed
291
}
Don Gagne's avatar
Don Gagne committed
292

293
bool FactMetaData::convertAndValidateRaw(const QVariant& rawValue, bool convertOnly, QVariant& typedValue, QString& errorString)
Don Gagne's avatar
Don Gagne committed
294
{
295
    bool convertOk = false;
Don Gagne's avatar
Don Gagne committed
296 297 298 299
    
    errorString.clear();
    
    switch (type()) {
300 301 302 303 304
    case FactMetaData::valueTypeInt8:
    case FactMetaData::valueTypeInt16:
    case FactMetaData::valueTypeInt32:
        typedValue = QVariant(rawValue.toInt(&convertOk));
        if (!convertOnly && convertOk) {
305
            if (typedValue < rawMin() || typedValue > rawMax()) {
306
                errorString = tr("Value must be within %1 and %2").arg(cookedMin().toInt()).arg(cookedMax().toInt());
Don Gagne's avatar
Don Gagne committed
307
            }
308 309 310 311 312 313 314
        }
        break;
    case FactMetaData::valueTypeUint8:
    case FactMetaData::valueTypeUint16:
    case FactMetaData::valueTypeUint32:
        typedValue = QVariant(rawValue.toUInt(&convertOk));
        if (!convertOnly && convertOk) {
315
            if (typedValue < rawMin() || typedValue > rawMax()) {
316
                errorString = tr("Value must be within %1 and %2").arg(cookedMin().toUInt()).arg(cookedMax().toUInt());
Don Gagne's avatar
Don Gagne committed
317
            }
318 319 320 321 322
        }
        break;
    case FactMetaData::valueTypeFloat:
        typedValue = QVariant(rawValue.toFloat(&convertOk));
        if (!convertOnly && convertOk) {
323
            if (typedValue < rawMin() || typedValue > rawMax()) {
324
                errorString = tr("Value must be within %1 and %2").arg(cookedMin().toFloat()).arg(cookedMax().toFloat());
Don Gagne's avatar
Don Gagne committed
325
            }
326 327
        }
        break;
328
    case FactMetaData::valueTypeElapsedTimeInSeconds:
329 330 331
    case FactMetaData::valueTypeDouble:
        typedValue = QVariant(rawValue.toDouble(&convertOk));
        if (!convertOnly && convertOk) {
332
            if (typedValue < rawMin() || typedValue > rawMax()) {
333
                errorString = tr("Value must be within %1 and %2").arg(cookedMin().toDouble()).arg(cookedMax().toDouble());
Don Gagne's avatar
Don Gagne committed
334
            }
335 336
        }
        break;
Don Gagne's avatar
Don Gagne committed
337 338 339 340
    case FactMetaData::valueTypeString:
        convertOk = true;
        typedValue = QVariant(rawValue.toString());
        break;
341 342 343 344
    case FactMetaData::valueTypeBool:
        convertOk = true;
        typedValue = QVariant(rawValue.toBool());
        break;
345 346 347 348
    case FactMetaData::valueTypeCustom:
        convertOk = true;
        typedValue = QVariant(rawValue.toByteArray());
        break;
Don Gagne's avatar
Don Gagne committed
349 350 351
    }
    
    if (!convertOk) {
352
        errorString += tr("Invalid number");
Don Gagne's avatar
Don Gagne committed
353 354 355 356
    }
    
    return convertOk && errorString.isEmpty();
}
357

358 359 360 361 362 363 364
bool FactMetaData::convertAndValidateCooked(const QVariant& cookedValue, bool convertOnly, QVariant& typedValue, QString& errorString)
{
    bool convertOk = false;

    errorString.clear();

    switch (type()) {
365 366 367 368 369 370
    case FactMetaData::valueTypeInt8:
    case FactMetaData::valueTypeInt16:
    case FactMetaData::valueTypeInt32:
        typedValue = QVariant(cookedValue.toInt(&convertOk));
        if (!convertOnly && convertOk) {
            if (cookedMin() > typedValue || typedValue > cookedMax()) {
371
                errorString = tr("Value must be within %1 and %2").arg(cookedMin().toInt()).arg(cookedMax().toInt());
372
            }
373 374 375 376 377 378 379 380
        }
        break;
    case FactMetaData::valueTypeUint8:
    case FactMetaData::valueTypeUint16:
    case FactMetaData::valueTypeUint32:
        typedValue = QVariant(cookedValue.toUInt(&convertOk));
        if (!convertOnly && convertOk) {
            if (cookedMin() > typedValue || typedValue > cookedMax()) {
381
                errorString = tr("Value must be within %1 and %2").arg(cookedMin().toUInt()).arg(cookedMax().toUInt());
382
            }
383 384 385 386 387 388
        }
        break;
    case FactMetaData::valueTypeFloat:
        typedValue = QVariant(cookedValue.toFloat(&convertOk));
        if (!convertOnly && convertOk) {
            if (cookedMin() > typedValue || typedValue > cookedMax()) {
389
                errorString = tr("Value must be within %1 and %2").arg(cookedMin().toFloat()).arg(cookedMax().toFloat());
390
            }
391 392
        }
        break;
393
    case FactMetaData::valueTypeElapsedTimeInSeconds:
394 395 396 397
    case FactMetaData::valueTypeDouble:
        typedValue = QVariant(cookedValue.toDouble(&convertOk));
        if (!convertOnly && convertOk) {
            if (cookedMin() > typedValue || typedValue > cookedMax()) {
398
                errorString = tr("Value must be within %1 and %2").arg(cookedMin().toDouble()).arg(cookedMax().toDouble());
399
            }
400 401
        }
        break;
Don Gagne's avatar
Don Gagne committed
402 403 404 405
    case FactMetaData::valueTypeString:
        convertOk = true;
        typedValue = QVariant(cookedValue.toString());
        break;
406 407 408 409
    case FactMetaData::valueTypeBool:
        convertOk = true;
        typedValue = QVariant(cookedValue.toBool());
        break;
410 411 412 413
    case FactMetaData::valueTypeCustom:
        convertOk = true;
        typedValue = QVariant(cookedValue.toByteArray());
        break;
414 415 416
    }

    if (!convertOk) {
417
        errorString += tr("Invalid number");
418 419 420 421 422
    }

    return convertOk && errorString.isEmpty();
}

423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
bool FactMetaData::clampValue(const QVariant& cookedValue, QVariant& typedValue)
{
    bool convertOk = false;
    switch (type()) {
    case FactMetaData::valueTypeInt8:
    case FactMetaData::valueTypeInt16:
    case FactMetaData::valueTypeInt32:
        typedValue = QVariant(cookedValue.toInt(&convertOk));
        if (convertOk) {
            if (cookedMin() > typedValue) {
                typedValue = cookedMin();
            } else if(typedValue > cookedMax()) {
                typedValue = cookedMax();
            }
        }
        break;
    case FactMetaData::valueTypeUint8:
    case FactMetaData::valueTypeUint16:
    case FactMetaData::valueTypeUint32:
        typedValue = QVariant(cookedValue.toUInt(&convertOk));
        if (convertOk) {
            if (cookedMin() > typedValue) {
                typedValue = cookedMin();
            } else if(typedValue > cookedMax()) {
                typedValue = cookedMax();
            }
        }
        break;
    case FactMetaData::valueTypeFloat:
        typedValue = QVariant(cookedValue.toFloat(&convertOk));
        if (convertOk) {
            if (cookedMin() > typedValue) {
                typedValue = cookedMin();
            } else if(typedValue > cookedMax()) {
                typedValue = cookedMax();
            }
        }
        break;
    case FactMetaData::valueTypeElapsedTimeInSeconds:
    case FactMetaData::valueTypeDouble:
        typedValue = QVariant(cookedValue.toDouble(&convertOk));
        if (convertOk) {
            if (cookedMin() > typedValue) {
                typedValue = cookedMin();
            } else if(typedValue > cookedMax()) {
                typedValue = cookedMax();
            }
        }
        break;
    case FactMetaData::valueTypeString:
        convertOk = true;
        typedValue = QVariant(cookedValue.toString());
        break;
    case FactMetaData::valueTypeBool:
        convertOk = true;
        typedValue = QVariant(cookedValue.toBool());
        break;
480 481 482 483
    case FactMetaData::valueTypeCustom:
        convertOk = true;
        typedValue = QVariant(cookedValue.toByteArray());
        break;
484 485 486 487
    }
    return convertOk;
}

488 489 490 491 492 493 494 495 496
void FactMetaData::setBitmaskInfo(const QStringList& strings, const QVariantList& values)
{
    if (strings.count() != values.count()) {
        qWarning() << "Count mismatch strings:values" << strings.count() << values.count();
        return;
    }

    _bitmaskStrings = strings;
    _bitmaskValues = values;
497
    setBuiltInTranslator();
498 499 500 501 502 503 504 505
}

void FactMetaData::addBitmaskInfo(const QString& name, const QVariant& value)
{
    _bitmaskStrings << name;
    _bitmaskValues << value;
}

506 507 508 509 510 511 512 513 514
void FactMetaData::setEnumInfo(const QStringList& strings, const QVariantList& values)
{
    if (strings.count() != values.count()) {
        qWarning() << "Count mismatch strings:values" << strings.count() << values.count();
        return;
    }

    _enumStrings = strings;
    _enumValues = values;
515
    setBuiltInTranslator();
516 517
}

518 519 520 521 522 523
void FactMetaData::addEnumInfo(const QString& name, const QVariant& value)
{
    _enumStrings << name;
    _enumValues << value;
}

524 525 526 527 528
void FactMetaData::setTranslators(Translator rawTranslator, Translator cookedTranslator)
{
    _rawTranslator = rawTranslator;
    _cookedTranslator = cookedTranslator;
}
529

530
void FactMetaData::setBuiltInTranslator(void)
531 532 533 534 535
{
    if (_enumStrings.count()) {
        // No translation if enum
        setTranslators(_defaultTranslator, _defaultTranslator);
        _cookedUnits = _rawUnits;
536
        return;
537
    } else {
538 539
        for (size_t i=0; i<sizeof(_rgBuiltInTranslations)/sizeof(_rgBuiltInTranslations[0]); i++) {
            const BuiltInTranslation_s* pBuiltInTranslation = &_rgBuiltInTranslations[i];
540 541 542 543

            if (pBuiltInTranslation->rawUnits == _rawUnits.toLower()) {
                _cookedUnits = pBuiltInTranslation->cookedUnits;
                setTranslators(pBuiltInTranslation->rawTranslator, pBuiltInTranslation->cookedTranslator);
544
                return;
545 546 547
            }
        }
    }
548 549 550

    // Translator not yet set, try app settings translators
    _setAppSettingsTranslators();
551 552 553 554
}

QVariant FactMetaData::_degreesToRadians(const QVariant& degrees)
{
555
    return QVariant(qDegreesToRadians(degrees.toDouble()));
556 557 558 559
}

QVariant FactMetaData::_radiansToDegrees(const QVariant& radians)
{
560
    return QVariant(qRadiansToDegrees(radians.toDouble()));
561 562 563 564
}

QVariant FactMetaData::_centiDegreesToDegrees(const QVariant& centiDegrees)
{
565
    return QVariant(centiDegrees.toReal() / 100.0);
566 567 568 569
}

QVariant FactMetaData::_degreesToCentiDegrees(const QVariant& degrees)
{
570
    return QVariant(qRound(degrees.toReal() * 100.0));
571 572
}

573 574 575 576 577 578 579 580 581 582 583 584 585 586
QVariant FactMetaData::_userGimbalDegreesToMavlinkGimbalDegrees(const QVariant& userGimbalDegrees)
{
    // User facing gimbal degree values are from 0 (level) to 90 (straight down)
    // Mavlink gimbal degree values are from 0 (level) to -90 (straight down)
    return userGimbalDegrees.toDouble() * -1.0;
}

QVariant FactMetaData::_mavlinkGimbalDegreesToUserGimbalDegrees(const QVariant& mavlinkGimbalDegrees)
{
    // User facing gimbal degree values are from 0 (level) to 90 (straight down)
    // Mavlink gimbal degree values are from 0 (level) to -90 (straight down)
    return mavlinkGimbalDegrees.toDouble() * -1.0;
}

587 588
QVariant FactMetaData::_metersToFeet(const QVariant& meters)
{
589
    return QVariant(meters.toDouble() * 1.0/constants.feetToMeters);
590 591 592 593
}

QVariant FactMetaData::_feetToMeters(const QVariant& feet)
{
594
    return QVariant(feet.toDouble() * constants.feetToMeters);
595 596
}

597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646
QVariant FactMetaData::_squareMetersToSquareKilometers(const QVariant& squareMeters)
{
    return QVariant(squareMeters.toDouble() * 0.000001);
}

QVariant FactMetaData::_squareKilometersToSquareMeters(const QVariant& squareKilometers)
{
    return QVariant(squareKilometers.toDouble() * 1000000.0);
}

QVariant FactMetaData::_squareMetersToHectares(const QVariant& squareMeters)
{
    return QVariant(squareMeters.toDouble() * 0.0001);
}

QVariant FactMetaData::_hectaresToSquareMeters(const QVariant& hectares)
{
    return QVariant(hectares.toDouble() * 1000.0);
}

QVariant FactMetaData::_squareMetersToSquareFeet(const QVariant& squareMeters)
{
    return QVariant(squareMeters.toDouble() * 10.7639);
}

QVariant FactMetaData::_squareFeetToSquareMeters(const QVariant& squareFeet)
{
    return QVariant(squareFeet.toDouble() * 0.0929);
}

QVariant FactMetaData::_squareMetersToAcres(const QVariant& squareMeters)
{
    return QVariant(squareMeters.toDouble() * 0.000247105);
}

QVariant FactMetaData::_acresToSquareMeters(const QVariant& acres)
{
    return QVariant(acres.toDouble() * 4046.86);
}

QVariant FactMetaData::_squareMetersToSquareMiles(const QVariant& squareMeters)
{
    return QVariant(squareMeters.toDouble() * 3.86102e-7);
}

QVariant FactMetaData::_squareMilesToSquareMeters(const QVariant& squareMiles)
{
    return QVariant(squareMiles.toDouble() * 258999039.98855);
}

647 648
QVariant FactMetaData::_metersPerSecondToMilesPerHour(const QVariant& metersPerSecond)
{
649
    return QVariant((metersPerSecond.toDouble() * 1.0/constants.milesToMeters) * constants.secondsPerHour);
650 651 652 653
}

QVariant FactMetaData::_milesPerHourToMetersPerSecond(const QVariant& milesPerHour)
{
654
    return QVariant((milesPerHour.toDouble() * constants.milesToMeters) / constants.secondsPerHour);
655 656 657 658
}

QVariant FactMetaData::_metersPerSecondToKilometersPerHour(const QVariant& metersPerSecond)
{
659
    return QVariant((metersPerSecond.toDouble() / 1000.0) * constants.secondsPerHour);
660 661 662 663
}

QVariant FactMetaData::_kilometersPerHourToMetersPerSecond(const QVariant& kilometersPerHour)
{
664
    return QVariant((kilometersPerHour.toDouble() * 1000.0) / constants.secondsPerHour);
665 666 667 668
}

QVariant FactMetaData::_metersPerSecondToKnots(const QVariant& metersPerSecond)
{
669
    return QVariant(metersPerSecond.toDouble() * constants.secondsPerHour / (1000.0 * constants.knotsToKPH));
670 671 672 673
}

QVariant FactMetaData::_knotsToMetersPerSecond(const QVariant& knots)
{
674
    return QVariant(knots.toDouble() * (1000.0 * constants.knotsToKPH / constants.secondsPerHour));
675 676
}

677 678 679 680 681 682 683 684 685 686
QVariant FactMetaData::_percentToNorm(const QVariant& percent)
{
    return QVariant(percent.toDouble() / 100.0);
}

QVariant FactMetaData::_normToPercent(const QVariant& normalized)
{
    return QVariant(normalized.toDouble() * 100.0);
}

687 688 689 690 691 692 693 694 695 696
QVariant FactMetaData::_centimetersToInches(const QVariant& centimeters)
{
    return QVariant(centimeters.toDouble() * 1.0/constants.inchesToCentimeters);
}

QVariant FactMetaData::_inchesToCentimeters(const QVariant& inches)
{
    return QVariant(inches.toDouble() * constants.inchesToCentimeters);
}

697 698 699 700 701 702 703 704 705 706
QVariant FactMetaData::_celsiusToFarenheit(const QVariant& celsius)
{
    return QVariant(celsius.toDouble() * (9.0 / 5.0) + 32);
}

QVariant FactMetaData::_farenheitToCelsius(const QVariant& farenheit)
{
    return QVariant((farenheit.toDouble() - 32) * (5.0 / 9.0));
}

707 708 709 710 711
void FactMetaData::setRawUnits(const QString& rawUnits)
{
    _rawUnits = rawUnits;
    _cookedUnits = rawUnits;

712
    setBuiltInTranslator();
713
}
Don Gagne's avatar
Don Gagne committed
714 715 716 717 718 719 720 721 722

FactMetaData::ValueType_t FactMetaData::stringToType(const QString& typeString, bool& unknownType)
{
    QStringList         knownTypeStrings;
    QList<ValueType_t>  knownTypes;

    unknownType = false;

    knownTypeStrings << QStringLiteral("Uint8")
723 724 725 726 727 728
                     << QStringLiteral("Int8")
                     << QStringLiteral("Uint16")
                     << QStringLiteral("Int16")
                     << QStringLiteral("Uint32")
                     << QStringLiteral("Int32")
                     << QStringLiteral("Float")
Don Gagne's avatar
Don Gagne committed
729
                     << QStringLiteral("Double")
730
                     << QStringLiteral("String")
731
                     << QStringLiteral("Bool")
732 733
                     << QStringLiteral("ElapsedSeconds")
                     << QStringLiteral("Custom");
Don Gagne's avatar
Don Gagne committed
734 735

    knownTypes << valueTypeUint8
736 737 738 739 740 741
               << valueTypeInt8
               << valueTypeUint16
               << valueTypeInt16
               << valueTypeUint32
               << valueTypeInt32
               << valueTypeFloat
Don Gagne's avatar
Don Gagne committed
742
               << valueTypeDouble
743
               << valueTypeString
744
               << valueTypeBool
745 746
               << valueTypeElapsedTimeInSeconds
               << valueTypeCustom;
Don Gagne's avatar
Don Gagne committed
747 748 749 750 751 752 753 754 755 756 757

    for (int i=0; i<knownTypeStrings.count(); i++) {
        if (knownTypeStrings[i].compare(typeString, Qt::CaseInsensitive) == 0) {
            return knownTypes[i];
        }
    }

    unknownType = true;

    return valueTypeDouble;
}
758 759 760 761

size_t FactMetaData::typeToSize(ValueType_t type)
{
    switch (type) {
762 763 764
    case valueTypeUint8:
    case valueTypeInt8:
        return 1;
765

766 767 768
    case valueTypeUint16:
    case valueTypeInt16:
        return 2;
769

770 771 772 773
    case valueTypeUint32:
    case valueTypeInt32:
    case valueTypeFloat:
        return 4;
774

775 776
    case valueTypeDouble:
        return 8;
777

778 779 780
    case valueTypeCustom:
        return MAVLINK_MSG_PARAM_EXT_SET_FIELD_PARAM_VALUE_LEN;

781 782
    default:
        qWarning() << "Unsupported fact value type" << type;
Don Gagne's avatar
Don Gagne committed
783
        return 0;
784 785
    }
}
786

787 788 789

/// Set translators according to app settings
void FactMetaData::_setAppSettingsTranslators(void)
790
{
791 792
    // We can only translate between real numbers
    if (!_enumStrings.count() && (type() == valueTypeDouble || type() == valueTypeFloat)) {
793 794 795
        for (size_t i=0; i<sizeof(_rgAppSettingsTranslations)/sizeof(_rgAppSettingsTranslations[0]); i++) {
            const AppSettingsTranslation_s* pAppSettingsTranslation = &_rgAppSettingsTranslations[i];

796 797 798
            if ((pAppSettingsTranslation->rawUnits == _rawUnits && // Temperature
                    (!pAppSettingsTranslation->speed && pAppSettingsTranslation->speedOrDistanceUnits == qgcApp()->toolbox()->settingsManager()->unitsSettings()->temperatureUnits()->rawValue().toUInt())) ||
            (pAppSettingsTranslation->rawUnits == _rawUnits.toLower() && // Speed and Distance
799
                    ((pAppSettingsTranslation->speed && pAppSettingsTranslation->speedOrDistanceUnits == qgcApp()->toolbox()->settingsManager()->unitsSettings()->speedUnits()->rawValue().toUInt()) ||
800
                    (!pAppSettingsTranslation->speed && pAppSettingsTranslation->speedOrDistanceUnits == qgcApp()->toolbox()->settingsManager()->unitsSettings()->distanceUnits()->rawValue().toUInt())))) {
801 802 803 804 805 806 807
                _cookedUnits = pAppSettingsTranslation->cookedUnits;
                setTranslators(pAppSettingsTranslation->rawTranslator, pAppSettingsTranslation->cookedTranslator);
                return;
            }
        }
    }
}
808 809 810 811 812 813 814

const FactMetaData::AppSettingsTranslation_s* FactMetaData::_findAppSettingsDistanceUnitsTranslation(const QString& rawUnits)
{
    for (size_t i=0; i<sizeof(_rgAppSettingsTranslations)/sizeof(_rgAppSettingsTranslations[0]); i++) {
        const AppSettingsTranslation_s* pAppSettingsTranslation = &_rgAppSettingsTranslations[i];

        if (pAppSettingsTranslation->rawUnits == rawUnits &&
815
                (!pAppSettingsTranslation->speed && pAppSettingsTranslation->speedOrDistanceUnits == qgcApp()->toolbox()->settingsManager()->unitsSettings()->distanceUnits()->rawValue().toUInt())) {
816 817 818 819 820 821 822
            return pAppSettingsTranslation;
        }
    }

    return NULL;
}

823 824 825 826 827 828
const FactMetaData::AppSettingsTranslation_s* FactMetaData::_findAppSettingsAreaUnitsTranslation(const QString& rawUnits)
{
    for (size_t i=0; i<sizeof(_rgAppSettingsTranslations)/sizeof(_rgAppSettingsTranslations[0]); i++) {
        const AppSettingsTranslation_s* pAppSettingsTranslation = &_rgAppSettingsTranslations[i];

        if (pAppSettingsTranslation->rawUnits == rawUnits &&
829
                (!pAppSettingsTranslation->speed && pAppSettingsTranslation->speedOrDistanceUnits == qgcApp()->toolbox()->settingsManager()->unitsSettings()->areaUnits()->rawValue().toUInt())
830 831 832 833 834 835 836 837
                ) {
            return pAppSettingsTranslation;
        }
    }

    return NULL;
}

838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866
QVariant FactMetaData::metersToAppSettingsDistanceUnits(const QVariant& meters)
{
    const AppSettingsTranslation_s* pAppSettingsTranslation = _findAppSettingsDistanceUnitsTranslation("m");
    if (pAppSettingsTranslation) {
        return pAppSettingsTranslation->rawTranslator(meters);
    } else {
        return meters;
    }
}

QVariant FactMetaData::appSettingsDistanceUnitsToMeters(const QVariant& distance)
{
    const AppSettingsTranslation_s* pAppSettingsTranslation = _findAppSettingsDistanceUnitsTranslation("m");
    if (pAppSettingsTranslation) {
        return pAppSettingsTranslation->cookedTranslator(distance);
    } else {
        return distance;
    }
}

QString FactMetaData::appSettingsDistanceUnitsString(void)
{
    const AppSettingsTranslation_s* pAppSettingsTranslation = _findAppSettingsDistanceUnitsTranslation("m");
    if (pAppSettingsTranslation) {
        return pAppSettingsTranslation->cookedUnits;
    } else {
        return QStringLiteral("m");
    }
}
867

868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897
QVariant FactMetaData::squareMetersToAppSettingsAreaUnits(const QVariant& squareMeters)
{
    const AppSettingsTranslation_s* pAppSettingsTranslation = _findAppSettingsAreaUnitsTranslation("m^2");
    if (pAppSettingsTranslation) {
        return pAppSettingsTranslation->rawTranslator(squareMeters);
    } else {
        return squareMeters;
    }
}

QVariant FactMetaData::appSettingsAreaUnitsToSquareMeters(const QVariant& area)
{
    const AppSettingsTranslation_s* pAppSettingsTranslation = _findAppSettingsAreaUnitsTranslation("m^2");
    if (pAppSettingsTranslation) {
        return pAppSettingsTranslation->cookedTranslator(area);
    } else {
        return area;
    }
}

QString FactMetaData::appSettingsAreaUnitsString(void)
{
    const AppSettingsTranslation_s* pAppSettingsTranslation = _findAppSettingsAreaUnitsTranslation("m^2");
    if (pAppSettingsTranslation) {
        return pAppSettingsTranslation->cookedUnits;
    } else {
        return QStringLiteral("m^2");
    }
}

898 899 900 901 902 903
int FactMetaData::decimalPlaces(void) const
{
    int actualDecimalPlaces = defaultDecimalPlaces;
    int incrementDecimalPlaces = unknownDecimalPlaces;

    // First determine decimal places from increment
904
    double increment = _rawTranslator(this->increment()).toDouble();
905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921
    if (!qIsNaN(increment)) {
        double integralPart;

        // Get the fractional part only
        increment = fabs(modf(increment, &integralPart));
        if (increment == 0.0) {
            // No fractional part, so no decimal places
            incrementDecimalPlaces = 0;
        } else {
            incrementDecimalPlaces = -ceil(log10(increment));
        }
    }

    // Correct decimal places is the larger of the two, increment or meta data value
    if (incrementDecimalPlaces != unknownDecimalPlaces && _decimalPlaces == unknownDecimalPlaces) {
        actualDecimalPlaces = incrementDecimalPlaces;
    } else {
922 923
        // Adjust decimal places for cooked translation
        int settingsDecimalPlaces = _decimalPlaces == unknownDecimalPlaces ? defaultDecimalPlaces : _decimalPlaces;
924 925 926 927 928 929 930 931
        double ctest = _rawTranslator(1.0).toDouble();

        settingsDecimalPlaces += -log10(ctest);

        settingsDecimalPlaces = qMin(25, settingsDecimalPlaces);
        settingsDecimalPlaces = qMax(0, settingsDecimalPlaces);

        actualDecimalPlaces = qMax(settingsDecimalPlaces, incrementDecimalPlaces);
932 933 934 935
    }

    return actualDecimalPlaces;
}
936

937
FactMetaData* FactMetaData::createFromJsonObject(const QJsonObject& json, QObject* metaDataParent)
938 939 940 941 942 943 944 945 946 947 948 949 950 951
{
    QString         errorString;

    // Make sure we have the required keys
    QStringList requiredKeys;
    requiredKeys << _nameJsonKey << _typeJsonKey;
    if (!JsonHelper::validateRequiredKeys(json, requiredKeys, errorString)) {
        qWarning() << errorString;
        return new FactMetaData(valueTypeUint32, metaDataParent);
    }

    // Validate key types
    QStringList             keys;
    QList<QJsonValue::Type> types;
952 953
    keys << _nameJsonKey << _decimalPlacesJsonKey << _typeJsonKey << _shortDescriptionJsonKey << _longDescriptionJsonKey << _unitsJsonKey << _minJsonKey << _maxJsonKey << _hasControlJsonKey;
    types << QJsonValue::String << QJsonValue::Double << QJsonValue::String << QJsonValue::String << QJsonValue::String << QJsonValue::String << QJsonValue::Double << QJsonValue::Double << QJsonValue::Bool;
954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970
    if (!JsonHelper::validateKeyTypes(json, keys, types, errorString)) {
        qWarning() << errorString;
        return new FactMetaData(valueTypeUint32, metaDataParent);
    }

    bool unknownType;
    FactMetaData::ValueType_t type = FactMetaData::stringToType(json[_typeJsonKey].toString(), unknownType);
    if (unknownType) {
        qWarning() << "Unknown type" << json[_typeJsonKey].toString();
        return new FactMetaData(valueTypeUint32, metaDataParent);
    }

    FactMetaData*   metaData = new FactMetaData(type, metaDataParent);

    metaData->_name = json[_nameJsonKey].toString();

    QStringList enumValues, enumStrings;
971
    if (JsonHelper::parseEnum(json, enumStrings, enumValues, errorString, metaData->name())) {
972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995
        for (int i=0; i<enumValues.count(); i++) {
            QVariant    enumVariant;
            QString     errorString;

            if (metaData->convertAndValidateRaw(enumValues[i], false /* validate */, enumVariant, errorString)) {
                metaData->addEnumInfo(enumStrings[i], enumVariant);
            } else {
                qWarning() << "Invalid enum value, name:" << metaData->name()
                           << " type:" << metaData->type()
                           << " value:" << enumValues[i]
                           << " error:" << errorString;
            }
        }
    } else {
        qWarning() << errorString;
    }

    metaData->setDecimalPlaces(json[_decimalPlacesJsonKey].toInt(0));
    metaData->setShortDescription(json[_shortDescriptionJsonKey].toString());
    metaData->setLongDescription(json[_longDescriptionJsonKey].toString());

    if (json.contains(_unitsJsonKey)) {
        metaData->setRawUnits(json[_unitsJsonKey].toString());
    }
996 997

    QString defaultValueJsonKey;
998 999
#ifdef __mobile__
    if (json.contains(_mobileDefaultValueJsonKey)) {
Don Gagne's avatar
Don Gagne committed
1000
        defaultValueJsonKey = _mobileDefaultValueJsonKey;
1001
    }
1002
#endif
1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019
    if (defaultValueJsonKey.isEmpty() && json.contains(_defaultValueJsonKey)) {
        defaultValueJsonKey = _defaultValueJsonKey;
    }
    if (!defaultValueJsonKey.isEmpty()) {
        QVariant typedValue;
        QString errorString;
        QVariant initialValue = json[defaultValueJsonKey].toVariant();
        if (metaData->convertAndValidateRaw(initialValue, true /* convertOnly */, typedValue, errorString)) {
            metaData->setRawDefaultValue(typedValue);
        } else {
            qWarning() << "Invalid default value, name:" << metaData->name()
                       << " type:" << metaData->type()
                       << " value:" << initialValue
                       << " error:" << errorString;
        }
    }

1020
    if (json.contains(_minJsonKey)) {
1021 1022
        QVariant typedValue;
        QString errorString;
1023 1024 1025 1026 1027 1028 1029 1030 1031
        QVariant initialValue = json[_minJsonKey].toVariant();
        if (metaData->convertAndValidateRaw(initialValue, true /* convertOnly */, typedValue, errorString)) {
            metaData->setRawMin(typedValue);
        } else {
            qWarning() << "Invalid min value, name:" << metaData->name()
                       << " type:" << metaData->type()
                       << " value:" << initialValue
                       << " error:" << errorString;
        }
1032
    }
1033

1034
    if (json.contains(_maxJsonKey)) {
1035 1036
        QVariant typedValue;
        QString errorString;
1037 1038 1039 1040 1041 1042 1043 1044 1045
        QVariant initialValue = json[_maxJsonKey].toVariant();
        if (metaData->convertAndValidateRaw(initialValue, true /* convertOnly */, typedValue, errorString)) {
            metaData->setRawMax(typedValue);
        } else {
            qWarning() << "Invalid max value, name:" << metaData->name()
                       << " type:" << metaData->type()
                       << " value:" << initialValue
                       << " error:" << errorString;
        }
1046
    }
1047

1048 1049 1050 1051 1052
    if (json.contains(_hasControlJsonKey)) {
        metaData->setHasControl(json[_hasControlJsonKey].toBool());
    } else {
        metaData->setHasControl(true);
    }
1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081

    return metaData;
}

QMap<QString, FactMetaData*> FactMetaData::createMapFromJsonFile(const QString& jsonFilename, QObject* metaDataParent)
{
    QMap<QString, FactMetaData*> metaDataMap;

    QFile jsonFile(jsonFilename);
    if (!jsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qWarning() << "Unable to open file" << jsonFilename << jsonFile.errorString();
        return metaDataMap;
    }

    QByteArray bytes = jsonFile.readAll();
    jsonFile.close();
    QJsonParseError jsonParseError;
    QJsonDocument doc = QJsonDocument::fromJson(bytes, &jsonParseError);
    if (jsonParseError.error != QJsonParseError::NoError) {
        qWarning() <<  "Unable to parse json document" << jsonFilename << jsonParseError.errorString();
        return metaDataMap;
    }

    if (!doc.isArray()) {
        qWarning() << "json document is not array";
        return metaDataMap;
    }

    QJsonArray jsonArray = doc.array();
1082 1083
    return createMapFromJsonArray(jsonArray, metaDataParent);
}
1084

1085 1086 1087 1088 1089
QMap<QString, FactMetaData*> FactMetaData::createMapFromJsonArray(const QJsonArray jsonArray, QObject* metaDataParent)
{
    QMap<QString, FactMetaData*> metaDataMap;
    for (int i=0; i<jsonArray.count(); i++) {
        QJsonValue jsonValue = jsonArray.at(i);
1090 1091 1092 1093 1094
        if (!jsonValue.isObject()) {
            qWarning() << QStringLiteral("JsonValue at index %1 not an object").arg(i);
            continue;
        }
        QJsonObject jsonObject = jsonValue.toObject();
1095
        FactMetaData* metaData = createFromJsonObject(jsonObject, metaDataParent);
1096 1097
        if (metaDataMap.contains(metaData->name())) {
            qWarning() << QStringLiteral("Duplicate fact name:") << metaData->name();
1098
            delete metaData;
1099 1100 1101 1102 1103 1104
        } else {
            metaDataMap[metaData->name()] = metaData;
        }
    }
    return metaDataMap;
}
1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130

QVariant FactMetaData::cookedMax(void) const
{
    // We have to be careful with cooked min/max. Running the raw values through the translator could flip min and max.
    QVariant cookedMax = _rawTranslator(_rawMax);
    QVariant cookedMin = _rawTranslator(_rawMin);
    if (cookedMax < cookedMin) {
        // We need to flip
        return cookedMin;
    } else {
        return cookedMax;
    }
}

QVariant FactMetaData::cookedMin(void) const
{
    // We have to be careful with cooked min/max. Running the raw values through the translator could flip min and max.
    QVariant cookedMax = _rawTranslator(_rawMax);
    QVariant cookedMin = _rawTranslator(_rawMin);
    if (cookedMax < cookedMin) {
        // We need to flip
        return cookedMax;
    } else {
        return cookedMin;
    }
}