516 lines
21 KiB
C++
516 lines
21 KiB
C++
/* -*-c++-*- */
|
|
/* osgEarth - Geospatial SDK for OpenSceneGraph
|
|
* Copyright 2020 Pelican Mapping
|
|
* http://osgearth.org
|
|
*
|
|
* osgEarth is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
*/
|
|
#pragma once
|
|
#include <osgEarth/Common>
|
|
#include <osgEarth/Config>
|
|
#include <ostream>
|
|
|
|
// make sure we don't collide with common macros
|
|
#undef DEGREES
|
|
#undef RADIANS
|
|
|
|
namespace osgEarth
|
|
{
|
|
class Registry;
|
|
|
|
namespace Units
|
|
{
|
|
enum class Domain
|
|
{
|
|
DISTANCE,
|
|
ANGLE,
|
|
TIME,
|
|
SPEED,
|
|
SCREEN,
|
|
INVALID
|
|
};
|
|
}
|
|
|
|
class OSGEARTH_EXPORT UnitsType
|
|
{
|
|
public:
|
|
inline bool valid() const {
|
|
return
|
|
_type == Units::Domain::SPEED ? (_distance != nullptr && _time != nullptr) :
|
|
_type != Units::Domain::INVALID;
|
|
}
|
|
|
|
inline bool canConvert(const UnitsType& to) const;
|
|
|
|
inline bool convertTo(const UnitsType& to, double input, double& output) const;
|
|
|
|
inline double convertTo(const UnitsType& to, double input) const;
|
|
|
|
const std::string& getName() const { return _name; }
|
|
|
|
const std::string& getAbbr() const { return _abbr; }
|
|
|
|
const Units::Domain& getDomain() const { return _type; }
|
|
|
|
bool operator == (const UnitsType& rhs) const {
|
|
return
|
|
valid() &&
|
|
rhs.valid() &&
|
|
_type == rhs._type &&
|
|
_toBase == rhs._toBase &&
|
|
(_type != Units::Domain::SPEED || *_distance == *rhs._distance) &&
|
|
(_type != Units::Domain::SPEED || *_time == *rhs._time);
|
|
}
|
|
|
|
bool operator != (const UnitsType& rhs) const {
|
|
return !operator==(rhs);
|
|
}
|
|
|
|
bool isLinear() const { return _type == Units::Domain::DISTANCE; }
|
|
bool isDistance() const { return _type == Units::Domain::DISTANCE; }
|
|
bool isAngular() const { return _type == Units::Domain::ANGLE; }
|
|
bool isAngle() const { return _type == Units::Domain::ANGLE; }
|
|
bool isTemporal() const { return _type == Units::Domain::TIME; }
|
|
bool isTime() const { return _type == Units::Domain::TIME; }
|
|
bool isSpeed() const { return _type == Units::Domain::SPEED; }
|
|
bool isScreenSize() const { return _type == Units::Domain::SCREEN; }
|
|
|
|
// Make a new unit definition (LINEAR, ANGULAR, TEMPORAL, SCREEN)
|
|
UnitsType(const char* name, const char* abbr, const Units::Domain& type, double toBase) :
|
|
_name(name),
|
|
_abbr(abbr),
|
|
_type(type),
|
|
_toBase(toBase) { }
|
|
|
|
// Maks a new unit definition (SPEED)
|
|
UnitsType(const char* name, const char* abbr, const UnitsType& distance, const UnitsType& time) :
|
|
_name(name),
|
|
_abbr(abbr),
|
|
_type(Units::Domain::SPEED),
|
|
_toBase(1.0),
|
|
_distance(&distance),
|
|
_time(&time) { }
|
|
|
|
UnitsType() { }
|
|
|
|
std::string _name;
|
|
std::string _abbr;
|
|
Units::Domain _type = Units::Domain::INVALID;
|
|
double _toBase = 0.0;
|
|
const UnitsType* _distance = nullptr;
|
|
const UnitsType* _time = nullptr;
|
|
};
|
|
|
|
|
|
namespace Units
|
|
{
|
|
// Distances; factor converts to METERS:
|
|
const UnitsType CENTIMETERS("centimeters", "cm", Units::Domain::DISTANCE, 0.01);
|
|
const UnitsType FEET("feet", "ft", Units::Domain::DISTANCE, 0.3048);
|
|
const UnitsType FEET_US_SURVEY("feet(us)", "ft", Units::Domain::DISTANCE, 12.0 / 39.37);
|
|
const UnitsType KILOMETERS("kilometers", "km", Units::Domain::DISTANCE, 1000.0);
|
|
const UnitsType METERS("meters", "m", Units::Domain::DISTANCE, 1.0);
|
|
const UnitsType MILES("miles", "mi", Units::Domain::DISTANCE, 1609.334);
|
|
const UnitsType MILLIMETERS("millimeters", "mm", Units::Domain::DISTANCE, 0.001);
|
|
|
|
const UnitsType YARDS("yards", "yd", Units::Domain::DISTANCE, 0.9144);
|
|
const UnitsType NAUTICAL_MILES("nautical miles", "nm", Units::Domain::DISTANCE, 1852.0);
|
|
const UnitsType DATA_MILES("data miles", "dm", Units::Domain::DISTANCE, 1828.8);
|
|
const UnitsType INCHES("inches", "in", Units::Domain::DISTANCE, 0.0254);
|
|
const UnitsType FATHOMS("fathoms", "fm", Units::Domain::DISTANCE, 1.8288);
|
|
const UnitsType KILOFEET("kilofeet", "kf", Units::Domain::DISTANCE, 304.8);
|
|
const UnitsType KILOYARDS("kiloyards", "kyd", Units::Domain::DISTANCE, 914.4);
|
|
|
|
// Factor converts unit into RADIANS:
|
|
const UnitsType DEGREES("degrees", "\xb0", Units::Domain::ANGLE, 0.017453292519943295);
|
|
const UnitsType RADIANS("radians", "rad", Units::Domain::ANGLE, 1.0);
|
|
const UnitsType BAM("BAM", "bam", Units::Domain::ANGLE, 6.283185307179586476925286766559);
|
|
const UnitsType NATO_MILS("mils", "mil", Units::Domain::ANGLE, 9.8174770424681038701957605727484e-4);
|
|
const UnitsType DECIMAL_HOURS("hours", "h", Units::Domain::ANGLE, 15.0 * 0.017453292519943295);
|
|
|
|
// Factor convert unit into SECONDS:
|
|
const UnitsType DAYS("days", "d", Units::Domain::TIME, 86400.0);
|
|
const UnitsType HOURS("hours", "hr", Units::Domain::TIME, 3600.0);
|
|
const UnitsType MICROSECONDS("microseconds", "us", Units::Domain::TIME, 0.000001);
|
|
const UnitsType MILLISECONDS("milliseconds", "ms", Units::Domain::TIME, 0.001);
|
|
const UnitsType MINUTES("minutes", "min", Units::Domain::TIME, 60.0);
|
|
const UnitsType SECONDS("seconds", "s", Units::Domain::TIME, 1.0);
|
|
const UnitsType WEEKS("weeks", "wk", Units::Domain::TIME, 604800.0);
|
|
|
|
const UnitsType FEET_PER_SECOND("feet per second", "ft/s", Units::FEET, Units::SECONDS);
|
|
const UnitsType YARDS_PER_SECOND("yards per second", "yd/s", Units::YARDS, Units::SECONDS);
|
|
const UnitsType METERS_PER_SECOND("meters per second", "m/s", Units::METERS, Units::SECONDS);
|
|
const UnitsType KILOMETERS_PER_SECOND("kilometers per second", "km/s", Units::KILOMETERS, Units::SECONDS);
|
|
const UnitsType KILOMETERS_PER_HOUR("kilometers per hour", "kmh", Units::KILOMETERS, Units::HOURS);
|
|
const UnitsType MILES_PER_HOUR("miles per hour", "mph", Units::MILES, Units::HOURS);
|
|
const UnitsType DATA_MILES_PER_HOUR("data miles per hour", "dm/h", Units::DATA_MILES, Units::HOURS);
|
|
const UnitsType KNOTS("nautical miles per hour", "kts", Units::NAUTICAL_MILES, Units::HOURS);
|
|
|
|
const UnitsType PIXELS("pixels", "px", Units::Domain::SCREEN, 1.0);
|
|
|
|
extern OSGEARTH_EXPORT bool parse(const std::string& input, UnitsType& output);
|
|
extern OSGEARTH_EXPORT bool parse(const std::string& input, double& out_value, UnitsType& out_units, const UnitsType& defaultUnits);
|
|
extern OSGEARTH_EXPORT bool parse(const std::string& input, float& out_value, UnitsType& out_units, const UnitsType& defaultUnits);
|
|
extern OSGEARTH_EXPORT bool parse(const std::string& input, int& out_value, UnitsType& out_units, const UnitsType& defaultUnits);
|
|
|
|
|
|
inline bool canConvert(const UnitsType& from, const UnitsType& to) {
|
|
return from.getDomain() == to.getDomain();
|
|
}
|
|
|
|
inline void convertSimple(const UnitsType& from, const UnitsType& to, double input, double& output) {
|
|
output = input * from._toBase / to._toBase;
|
|
}
|
|
|
|
inline void convertSpeed(const UnitsType& from, const UnitsType& to, double input, double& output) {
|
|
double t = from._distance->convertTo(*to._distance, input);
|
|
output = to._time->convertTo(*from._time, t);
|
|
}
|
|
|
|
inline bool convert(const UnitsType& from, const UnitsType& to, double input, double& output) {
|
|
if (canConvert(from, to)) {
|
|
if (from.isDistance() || from.isAngle() || from.isTime())
|
|
convertSimple(from, to, input, output);
|
|
else if (from.isSpeed())
|
|
convertSpeed(from, to, input, output);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline double convert(const UnitsType& from, const UnitsType& to, double input) {
|
|
double output = input;
|
|
convert(from, to, input, output);
|
|
return output;
|
|
}
|
|
|
|
extern OSGEARTH_EXPORT void registerAll(Registry* registry);
|
|
|
|
extern OSGEARTH_EXPORT int unitTest();
|
|
}
|
|
|
|
namespace detail
|
|
{
|
|
template<typename T>
|
|
class qualified_double
|
|
{
|
|
public:
|
|
qualified_double(double value, const UnitsType& units) : _value(value), _units(units) { }
|
|
|
|
qualified_double(const T& rhs) : _value(rhs._value), _units(rhs._units) { }
|
|
|
|
// parses the qualified number from a parseable string (e.g., "123km")
|
|
qualified_double(const std::string& parseable, const UnitsType& defaultUnits) : _value(0.0), _units(defaultUnits) {
|
|
Units::parse(parseable, _value, _units, defaultUnits);
|
|
}
|
|
|
|
// loads the qualified number from an old-school config (e.g., { value="123" units="km" } )
|
|
qualified_double(const Config& conf, const UnitsType& defaultUnits) : _value(0.0) {
|
|
if (conf.hasValue("value")) {
|
|
_value = conf.value<double>("value", 0.0);
|
|
if (!Units::parse(conf.value("units"), _units))
|
|
_units = defaultUnits;
|
|
}
|
|
}
|
|
|
|
void set(double value, const UnitsType& units) {
|
|
_value = value;
|
|
_units = units;
|
|
}
|
|
|
|
T& operator = (const T& rhs) {
|
|
set(rhs._value, rhs._units);
|
|
return static_cast<T&>(*this);
|
|
}
|
|
|
|
T operator + (const T& rhs) const {
|
|
return _units.canConvert(rhs._units) ?
|
|
T(_value + rhs.as(_units), _units) :
|
|
T(0, {});
|
|
}
|
|
|
|
T operator - (const T& rhs) const {
|
|
return _units.canConvert(rhs._units) ?
|
|
T(_value - rhs.as(_units), _units) :
|
|
T(0, {});
|
|
}
|
|
|
|
T operator * (double rhs) const {
|
|
return T(_value * rhs, _units);
|
|
}
|
|
|
|
T operator / (double rhs) const {
|
|
return T(_value / rhs, _units);
|
|
}
|
|
|
|
bool operator == (const T& rhs) const {
|
|
return _units.canConvert(rhs._units) && rhs.as(_units) == _value;
|
|
}
|
|
|
|
bool operator != (const T& rhs) const {
|
|
return !_units.canConvert(rhs._units) || rhs.as(_units) != _value;
|
|
}
|
|
|
|
bool operator < (const T& rhs) const {
|
|
return _units.canConvert(rhs._units) && _value < rhs.as(_units);
|
|
}
|
|
|
|
bool operator <= (const T& rhs) const {
|
|
return _units.canConvert(rhs._units) && _value <= rhs.as(_units);
|
|
}
|
|
|
|
bool operator > (const T& rhs) const {
|
|
return _units.canConvert(rhs._units) && _value > rhs.as(_units);
|
|
}
|
|
|
|
bool operator >= (const T& rhs) const {
|
|
return _units.canConvert(rhs._units) && _value >= rhs.as(_units);
|
|
}
|
|
|
|
double as(const UnitsType& convertTo) const {
|
|
return _units.convertTo(convertTo, _value);
|
|
}
|
|
|
|
T to(const UnitsType& convertTo) const {
|
|
return T(as(convertTo), convertTo);
|
|
}
|
|
|
|
double asDistance(const UnitsType& convertTo, double refLatDegrees) const {
|
|
if (_units.isAngle() && convertTo.isLinear()) {
|
|
double angleDeg = Units::convert(_units, Units::DEGREES, _value);
|
|
double meters = angleDeg * 111000.0 * cos(osg::DegreesToRadians(refLatDegrees));
|
|
return Units::convert(Units::METERS, convertTo, meters);
|
|
}
|
|
else if (_units.isLinear() && convertTo.isAngle()) {
|
|
double valueMeters = Units::convert(_units, Units::METERS, _value);
|
|
double angleDeg = valueMeters / (111000.0 * cos(osg::DegreesToRadians(refLatDegrees)));
|
|
return Units::convert(Units::DEGREES, convertTo, angleDeg);
|
|
}
|
|
else return as(convertTo);
|
|
}
|
|
|
|
double getValue() const { return _value; }
|
|
const UnitsType& getUnits() const { return _units; }
|
|
|
|
// same as getValue; use this class directly as a double or a float
|
|
operator double() const { return _value; }
|
|
|
|
Config getConfig() const {
|
|
Config conf;
|
|
conf.set("value", _value);
|
|
conf.set("units", _units.getAbbr());
|
|
return conf;
|
|
}
|
|
|
|
std::string asString() const {
|
|
return Stringify() << _value << _units.getAbbr();
|
|
}
|
|
|
|
virtual std::string asParseableString() const {
|
|
return asString();
|
|
}
|
|
|
|
protected:
|
|
double _value;
|
|
UnitsType _units;
|
|
};
|
|
}
|
|
|
|
struct Distance : public detail::qualified_double<Distance> {
|
|
Distance() : detail::qualified_double<Distance>(0, Units::METERS) { }
|
|
Distance(double value, const UnitsType& units) : detail::qualified_double<Distance>(value, units) { }
|
|
Distance(const Config& conf) : detail::qualified_double<Distance>(conf, Units::METERS) { }
|
|
Distance(const std::string& str) : detail::qualified_double<Distance>(str, Units::METERS) { }
|
|
};
|
|
typedef Distance Linear; // backwards compat
|
|
|
|
struct Angle : public detail::qualified_double<Angle> {
|
|
Angle() : detail::qualified_double<Angle>(0, Units::DEGREES) { }
|
|
Angle(double value, const UnitsType& units) : detail::qualified_double<Angle>(value, units) { }
|
|
Angle(const Config& conf) : detail::qualified_double<Angle>(conf, Units::DEGREES) { }
|
|
Angle(const std::string& str) : detail::qualified_double<Angle>(str, Units::DEGREES) { }
|
|
std::string asParseableString() const {
|
|
if ( _units == Units::DEGREES ) return Stringify() << _value;
|
|
else return asString();
|
|
}
|
|
};
|
|
typedef Angle Angular; // backwards compat
|
|
|
|
struct Duration : public detail::qualified_double<Duration> {
|
|
Duration() : detail::qualified_double<Duration>(0, Units::SECONDS) { }
|
|
Duration(double value, const UnitsType& units) : detail::qualified_double<Duration>(value, units) { }
|
|
Duration(const Config& conf) : detail::qualified_double<Duration>(conf, Units::SECONDS) { }
|
|
Duration(const std::string& str) : detail::qualified_double<Duration>(str, Units::SECONDS) { }
|
|
};
|
|
typedef Duration Temporal; // backwards compat
|
|
|
|
struct Speed : public detail::qualified_double<Speed> {
|
|
Speed() : detail::qualified_double<Speed>(0, Units::METERS_PER_SECOND) { }
|
|
Speed(double value, const UnitsType& units) : detail::qualified_double<Speed>(value, units) { }
|
|
Speed(const Config& conf) : detail::qualified_double<Speed>(conf, Units::METERS_PER_SECOND) { }
|
|
Speed(const std::string& str) : detail::qualified_double<Speed>(str, Units::METERS_PER_SECOND) { }
|
|
};
|
|
|
|
struct ScreenSize : public detail::qualified_double<ScreenSize> {
|
|
ScreenSize() : detail::qualified_double<ScreenSize>(0, Units::PIXELS) { }
|
|
ScreenSize(double value, const UnitsType& units) : detail::qualified_double<ScreenSize>(value, units) { }
|
|
ScreenSize(const Config& conf) : detail::qualified_double<ScreenSize>(conf, Units::PIXELS) { }
|
|
ScreenSize(const std::string& str) : detail::qualified_double<ScreenSize>(str, Units::PIXELS) { }
|
|
};
|
|
|
|
|
|
|
|
// UnitsType inlines
|
|
inline bool UnitsType::canConvert(const UnitsType& to) const {
|
|
return _type == to._type;
|
|
}
|
|
|
|
inline bool UnitsType::convertTo(const UnitsType& to, double input, double& output) const {
|
|
return Units::convert(*this, to, input, output);
|
|
}
|
|
|
|
inline double UnitsType::convertTo(const UnitsType& to, double input) const {
|
|
return Units::convert(*this, to, input);
|
|
}
|
|
|
|
|
|
// Config specializations:
|
|
|
|
template<> inline
|
|
void Config::set<Distance>( const std::string& key, const Distance& obj ) {
|
|
set(key, obj.asParseableString());
|
|
}
|
|
template<> inline
|
|
void Config::set<Distance>( const std::string& key, const optional<Distance>& opt ) {
|
|
if ( opt.isSet() ) { set(key, opt.get()); }
|
|
}
|
|
template<> inline
|
|
bool Config::get<Distance>( const std::string& key, optional<Distance>& output ) const {
|
|
if ( hasValue(key) ) { output = Distance(value(key)); return true; }
|
|
else return false;
|
|
}
|
|
|
|
template<> inline
|
|
void Config::set<Angle>( const std::string& key, const Angle& opt ) {
|
|
set(key, opt.asParseableString());
|
|
}
|
|
template<> inline
|
|
void Config::set<Angle>( const std::string& key, const optional<Angle>& opt ) {
|
|
if ( opt.isSet() ) { set(key, opt.get()); }
|
|
}
|
|
template<> inline
|
|
bool Config::get<Angle>( const std::string& key, optional<Angle>& output ) const {
|
|
if ( hasValue(key) ) { output = Angle(value(key)); return true; }
|
|
else return false;
|
|
}
|
|
|
|
|
|
template<> inline
|
|
void Config::set<Duration>( const std::string& key, const Duration& opt ) {
|
|
set(key, opt.asParseableString());
|
|
}
|
|
template<> inline
|
|
void Config::set<Duration>( const std::string& key, const optional<Duration>& opt ) {
|
|
if ( opt.isSet() ) { set(key, opt.get()); }
|
|
}
|
|
|
|
template<> inline
|
|
bool Config::get<Duration>( const std::string& key, optional<Duration>& output ) const {
|
|
if ( hasValue(key) ) { output = Duration(value(key)); return true; }
|
|
else return false;
|
|
}
|
|
|
|
template<> inline
|
|
void Config::set<Speed>( const std::string& key, const Speed& opt ) {
|
|
set(key, opt.asParseableString());
|
|
}
|
|
template<> inline
|
|
void Config::set<Speed>( const std::string& key, const optional<Speed>& opt ) {
|
|
if ( opt.isSet() ) { set(key, opt.get()); }
|
|
}
|
|
template<> inline
|
|
bool Config::get<Speed>( const std::string& key, optional<Speed>& output ) const {
|
|
if ( hasValue(key) ) { output = Speed(value(key)); return true; }
|
|
else return false;
|
|
}
|
|
|
|
template<> inline
|
|
void Config::set<ScreenSize>( const std::string& key, const ScreenSize& opt ) {
|
|
set(key, opt.asParseableString());
|
|
}
|
|
template<> inline
|
|
void Config::set<ScreenSize>( const std::string& key, const optional<ScreenSize>& opt ) {
|
|
if ( opt.isSet() ) { set(key, opt.get()); }
|
|
}
|
|
template<> inline
|
|
bool Config::get<ScreenSize>( const std::string& key, optional<ScreenSize>& output ) const {
|
|
if ( hasValue(key) ) { output = ScreenSize(value(key)); return true; }
|
|
else return false;
|
|
}
|
|
|
|
// osgEarth::Strings specializations
|
|
namespace Util
|
|
{
|
|
template<> inline
|
|
Distance as(const std::string& str, const Distance& default_value) {
|
|
double val;
|
|
UnitsType units;
|
|
if (Units::parse(str, val, units, Units::METERS))
|
|
return Distance(val, units);
|
|
else
|
|
return default_value;
|
|
}
|
|
|
|
template<> inline
|
|
Angle as(const std::string& str, const Angle& default_value) {
|
|
double val;
|
|
UnitsType units;
|
|
if (Units::parse(str, val, units, Units::DEGREES))
|
|
return Angle(val, units);
|
|
else
|
|
return default_value;
|
|
}
|
|
|
|
template<> inline
|
|
Duration as(const std::string& str, const Duration& default_value) {
|
|
double val;
|
|
UnitsType units;
|
|
if (Units::parse(str, val, units, Units::SECONDS))
|
|
return Duration(val, units);
|
|
else
|
|
return default_value;
|
|
}
|
|
|
|
template<> inline
|
|
Speed as(const std::string& str, const Speed& default_value) {
|
|
double val;
|
|
UnitsType units;
|
|
if (Units::parse(str, val, units, Units::METERS_PER_SECOND))
|
|
return Speed(val, units);
|
|
else
|
|
return default_value;
|
|
}
|
|
|
|
template<> inline
|
|
ScreenSize as(const std::string& str, const ScreenSize& default_value) {
|
|
double val;
|
|
UnitsType units;
|
|
if (Units::parse(str, val, units, Units::PIXELS))
|
|
return ScreenSize(val, units);
|
|
else
|
|
return default_value;
|
|
}
|
|
}
|
|
}
|