/* -*-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 */ #pragma once #include #include #include // 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 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("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(*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() : detail::qualified_double(0, Units::METERS) { } Distance(double value, const UnitsType& units) : detail::qualified_double(value, units) { } Distance(const Config& conf) : detail::qualified_double(conf, Units::METERS) { } Distance(const std::string& str) : detail::qualified_double(str, Units::METERS) { } }; typedef Distance Linear; // backwards compat struct Angle : public detail::qualified_double { Angle() : detail::qualified_double(0, Units::DEGREES) { } Angle(double value, const UnitsType& units) : detail::qualified_double(value, units) { } Angle(const Config& conf) : detail::qualified_double(conf, Units::DEGREES) { } Angle(const std::string& str) : detail::qualified_double(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() : detail::qualified_double(0, Units::SECONDS) { } Duration(double value, const UnitsType& units) : detail::qualified_double(value, units) { } Duration(const Config& conf) : detail::qualified_double(conf, Units::SECONDS) { } Duration(const std::string& str) : detail::qualified_double(str, Units::SECONDS) { } }; typedef Duration Temporal; // backwards compat struct Speed : public detail::qualified_double { Speed() : detail::qualified_double(0, Units::METERS_PER_SECOND) { } Speed(double value, const UnitsType& units) : detail::qualified_double(value, units) { } Speed(const Config& conf) : detail::qualified_double(conf, Units::METERS_PER_SECOND) { } Speed(const std::string& str) : detail::qualified_double(str, Units::METERS_PER_SECOND) { } }; struct ScreenSize : public detail::qualified_double { ScreenSize() : detail::qualified_double(0, Units::PIXELS) { } ScreenSize(double value, const UnitsType& units) : detail::qualified_double(value, units) { } ScreenSize(const Config& conf) : detail::qualified_double(conf, Units::PIXELS) { } ScreenSize(const std::string& str) : detail::qualified_double(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( const std::string& key, const Distance& obj ) { set(key, obj.asParseableString()); } template<> inline void Config::set( const std::string& key, const optional& opt ) { if ( opt.isSet() ) { set(key, opt.get()); } } template<> inline bool Config::get( const std::string& key, optional& output ) const { if ( hasValue(key) ) { output = Distance(value(key)); return true; } else return false; } template<> inline void Config::set( const std::string& key, const Angle& opt ) { set(key, opt.asParseableString()); } template<> inline void Config::set( const std::string& key, const optional& opt ) { if ( opt.isSet() ) { set(key, opt.get()); } } template<> inline bool Config::get( const std::string& key, optional& output ) const { if ( hasValue(key) ) { output = Angle(value(key)); return true; } else return false; } template<> inline void Config::set( const std::string& key, const Duration& opt ) { set(key, opt.asParseableString()); } template<> inline void Config::set( const std::string& key, const optional& opt ) { if ( opt.isSet() ) { set(key, opt.get()); } } template<> inline bool Config::get( const std::string& key, optional& output ) const { if ( hasValue(key) ) { output = Duration(value(key)); return true; } else return false; } template<> inline void Config::set( const std::string& key, const Speed& opt ) { set(key, opt.asParseableString()); } template<> inline void Config::set( const std::string& key, const optional& opt ) { if ( opt.isSet() ) { set(key, opt.get()); } } template<> inline bool Config::get( const std::string& key, optional& output ) const { if ( hasValue(key) ) { output = Speed(value(key)); return true; } else return false; } template<> inline void Config::set( const std::string& key, const ScreenSize& opt ) { set(key, opt.asParseableString()); } template<> inline void Config::set( const std::string& key, const optional& opt ) { if ( opt.isSet() ) { set(key, opt.get()); } } template<> inline bool Config::get( const std::string& key, optional& 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; } } }