diff --git a/QGCExternalLibs.pri b/QGCExternalLibs.pri index 506884f6f095b169d8b685a827bb78a8eea397c9..7d47a982ac0bf81f2f251a88c0c1ed1f8e8814a0 100644 --- a/QGCExternalLibs.pri +++ b/QGCExternalLibs.pri @@ -124,3 +124,24 @@ contains (DEFINES, DISABLE_ZEROCONF) { message("Skipping support for Zeroconf (unsupported platform)") } + +# +# [OPTIONAL] AirMap Support +# +contains (DEFINES, DISABLE_AIRMAP) { + message("Skipping support for AirMap (manual override from command line)") +# Otherwise the user can still disable this feature in the user_config.pri file. +} else:exists(user_config.pri):infile(user_config.pri, DEFINES, DISABLE_AIRMAP) { + message("Skipping support for AirMap (manual override from user_config.pri)") +} else { + AIRMAPD_PATH = $$PWD/libs/airmapd + INCLUDEPATH += \ + $${AIRMAPD_PATH}/include + MacBuild|iOSBuild { + message("Including support for AirMap") + LIBS += -L$${AIRMAPD_PATH}/macOS/Qt.5.9 -lairmap-qt + DEFINES += QGC_AIRMAP_ENABLED + } else { + message("Skipping support for Airmap (unsupported platform)") + } +} diff --git a/libs/airmapd/include/airmap/aircraft.h b/libs/airmapd/include/airmap/aircraft.h new file mode 100644 index 0000000000000000000000000000000000000000..9a6fbdf406213a83aace6825850b04cc2228cf7d --- /dev/null +++ b/libs/airmapd/include/airmap/aircraft.h @@ -0,0 +1,30 @@ +#ifndef AIRMAP_AIRCRAFT_H_ +#define AIRMAP_AIRCRAFT_H_ + +#include + +namespace airmap { + +/// Aircraft describes an aircraft in terms of its model and its manufacturer. +struct Aircraft { + /// Model bundles up a model id and a product name. + struct Model { + std::string id; ///< The unique id of the model in the context of AirMap. + std::string name; ///< The human-readable name of the model. + }; + + /// Manufacturer bundles up an id and a human-readable name. + /// Please note that the id is only unique/relevant in the context of the + /// AirMap services. + struct Manufacturer { + std::string id; ///< The unique id of the manufacturer in the context of AirMap. + std::string name; ///< The human-readable name of the manufacturer. + }; + + Model model; ///< Details describing the model of an aircraft. + Manufacturer manufacturer; ///< Details about the manufacturer of an aircraft. +}; + +} // namespace airmap + +#endif // AIRMAP_AIRCRAFT_H_ diff --git a/libs/airmapd/include/airmap/aircrafts.h b/libs/airmapd/include/airmap/aircrafts.h new file mode 100644 index 0000000000000000000000000000000000000000..fa8b567c7488d86d23e63e8b85913eac6db8d40f --- /dev/null +++ b/libs/airmapd/include/airmap/aircrafts.h @@ -0,0 +1,86 @@ +#ifndef AIRMAP_AIRCRAFTS_H_ +#define AIRMAP_AIRCRAFTS_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace airmap { + +/// Aircrafts models access to a database of aircraft models (specifically drones) +/// and manufacturers. +class Aircrafts : DoNotCopyOrMove { + public: + /// Manufacturers groups together types to ease interaction with + /// Aircrafts::manufacturers. + struct Manufacturers { + /// Parameters bundles up input parameters. + struct Parameters { + Optional manufacturer_name; ///< Search for the specific manufacturer with this name. + }; + + /// Result models the outcome of calling Flights::manufacturers. + using Result = Outcome, Error>; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Flights::manufacturers finishes. + using Callback = std::function; + }; + + /// Models groups together types to ease interaction with + /// Aircrafts::models. + struct Models { + /// Parameters bundles up input parameters. + struct Parameters { + Optional manufacturer; ///< Only list models by this manufacturer. + Optional model_name; ///< Search for the specific model with this name. + }; + + /// Result models the outcome of calling Flights::models. + using Result = Outcome, Error>; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Flights::models finishes. + using Callback = std::function; + }; + + /// ModelForId groups together types to ease interaction with + /// Aircrafts::model_for_id. + struct ModelForId { + /// Parameters bundles up input parameters. + struct Parameters { + std::string id; ///< Search for the model with this id. + }; + + /// Result models the outcome of calling Flights::model_for_id. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Flights::model_for_id finishes. + using Callback = std::function; + }; + + /// manufacturers queries the AirMap services for known aircraft + /// manufacturers, reporting results to 'cb'. + virtual void manufacturers(const Manufacturers::Parameters& parameters, const Manufacturers::Callback& cb) = 0; + + /// models queries the AirMap services for detailed information about + /// known Aircraft models and reports back results to 'cb'. + virtual void models(const Models::Parameters& parameters, const Models::Callback& cb) = 0; + + /// models queries the AirMap services for detailed information about + /// an Aircraft model identified by 'ModelForId::Parameters::id' and reports back results to 'cb'. + virtual void model_for_id(const ModelForId::Parameters& parameters, const ModelForId::Callback& cb) = 0; + + protected: + /// @cond + Aircrafts() = default; + /// @endcond +}; + +} // namespace airmap + +#endif // AIRMAP_AIRCRAFTS_H_ diff --git a/libs/airmapd/include/airmap/airspace.h b/libs/airmapd/include/airmap/airspace.h new file mode 100644 index 0000000000000000000000000000000000000000..c9e517e9d94ca0163b1747341f27a96126025e0b --- /dev/null +++ b/libs/airmapd/include/airmap/airspace.h @@ -0,0 +1,383 @@ +#ifndef AIRMAP_AIRSPACE_H_ +#define AIRMAP_AIRSPACE_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace airmap { + +/// Airspace groups together general information about an airspace and +/// in-depth information providing more details. +class Airspace { + public: + /// Airport bundles up properties further describing an + /// airspace around an airport. + struct Airport { + /// Runway describes an individual runway of an airport. + struct Runway { + std::string name; ///< Commn name assigned to the runway in the context of a specific airport. + float length; ///< Lenght of the runway in [m]. + float bearing; ///< Bearing of the runway in [°]. + }; + + /// Use enumerates all known usage types for + /// an airport. + enum class Use { + public_ ///< The airport is available for public use. + }; + + std::string iata; ///< IATA code of the airport. + std::string icao; ///< ICAO code of the airport. + bool paved; ///< True if the airport features paved runways. + std::string phone; ///< The phone number of the airport (typically the tower). + bool tower; ///< True if the airport features a tower. + std::vector runways; ///< Collection of runways available at the airport. + float elevation; ///< The elevation of the airport in [m]. + float longest_runway; ///< The lenght of th longest runway in [m]. + bool instrument_approach_procedure; ///< True if the airport features equipment supporting an IAP. + Use use; ///< Types of use offered by the airport. + }; + + /// ControlledAirspace bundles up properties describing + /// a controlled airspace. + struct ControlledAirspace {}; + + /// SpecialUseAirspace bundles up properties describing + /// a special use airspace. + struct SpecialUseAirspace { + /// Type enumerates all known special-purpose types. + enum class Type {}; + Type type; ///< The type of the SpecialUseAirspace. + }; + + /// TemporaryFlightRestriction describes an airspace that + /// modelling a temporary restriction of the airspace. + struct TemporaryFlightRestriction { + /// Type enumerates all known types of temporary flight restrictions. + enum class Type {}; + std::string url; ///< The URL providing further information about the temporary flight restriction. + Type type; ///< The type of the temporary flight restriction. + std::string reason; ///< The reason for the temporary flight restriction. + }; + + /// Wildfire describes an airspace around a wildfire. + struct Wildfire { + std::string effective_date; + }; + + /// Park describes an airspace over a park. + struct Park {}; + /// Prison describes an airspace over a prison. + struct Prison {}; + /// School describes an airspace over a school. + struct School {}; + /// Hospital describes an airspace over a hospital. + struct Hospital {}; + /// Fire describes an airspace over a fire. + struct Fire {}; + /// Emergency describes an airspace over an emergency situation. + struct Emergency {}; + + /// Heliport describes an airspace around a heliport. + struct Heliport { + /// Usage enumerates all known usages of a heliport. + enum class Usage {}; + std::string faa_id; ///< The FAA id of the heliport. + std::string phone; ///< The phone number of the heliport. + Usage usage; ///< The usages supported by the heliport. + }; + + /// PowerPlant describes the airspace around a power plant. + struct PowerPlant { + std::string technology; ///< The technology used by the power plant. + std::uint64_t code; ///< Official number of the power plant. + }; + + /// RelatedGeometry bundles up a geometry related to an airspace. + struct RelatedGeometry { + std::string id; ///< The unique id of the geometry in the context of AirMap. + Geometry geometry; ///< The actual geometry. + }; + + /// Enumerates all known airspace types. + enum class Type { + invalid = 0, + airport = 1 << 0, + controlled_airspace = 1 << 1, + special_use_airspace = 1 << 2, + tfr = 1 << 3, + wildfire = 1 << 4, + park = 1 << 5, + power_plant = 1 << 6, + heliport = 1 << 7, + prison = 1 << 8, + school = 1 << 9, + hospital = 1 << 10, + fire = 1 << 11, + emergency = 1 << 12, + all = airport | controlled_airspace | special_use_airspace | tfr | wildfire | park | power_plant | heliport | + prison | school | hospital | fire | emergency + }; + + using Id = std::string; + + /// @cond + Airspace(); + Airspace(const Airspace &rhs); + ~Airspace(); + Airspace &operator=(const Airspace &rhs); + bool operator==(const Airspace &rhs) const; + bool operator!=(const Airspace &rhs) const; + /// @endcond + + /// id returns an immutable reference to the unique idd + /// of this airspace. + const Id &id() const; + /// set_id adjusts the unique id of this airspace to id. + void set_id(const Id &id); + + /// name returns an immutable reference to the + /// human-readable name of this airspace. + const std::string &name() const; + /// set_name adjusts the name of this airspace to name. + void set_name(const std::string &name); + + /// type returns the Type of this airspace instance. + Type type() const; + + /// country returns an immutable reference to the name of the country + /// that the airspace belongs to. + const std::string &country() const; + /// set_country adjusts the name of the country that this airspace instance belongs to. + void set_country(const std::string &country); + + /// state returns an immutable reference to the name of the state + /// that the airspace belongs to. + const std::string &state() const; + /// set_state adjusts the name of the state that this airspace instance belongs to. + void set_state(const std::string &state); + + /// city returns an immutable reference to the name of the city + /// that the airspace belongs to. + const std::string &city() const; + /// set_city adjusts the name of the city that this airspace instance belongs to. + void set_city(const std::string &city); + + /// last_updated returns an immutable reference to the timestamp of the last update + /// to this airspace instance. + const Timestamp &last_updated() const; + /// set_last_updated adjusts the timestamp of the last update to this airspace to + /// 'timestamp'. + void set_last_updated(const Timestamp ×tamp); + + /// geometry returns an immutable reference to the geometry of this airspace instance. + const Geometry &geometry() const; + /// set_geometry adjusts the geometry of this airspace instance to 'geometry'. + void set_geometry(const Geometry &geometry); + + /// related_geometries returns an immutable reference to all geometries associated with + /// this airspace instance. + const std::map &related_geometries() const; + /// set_related_geometries adjusts the geometries associated with this airspace instance + /// to 'geometry'. + void set_related_geometries(const std::map &geometries); + + /// rules returns an immutable reference to the rules applying to this airspace instance. + const std::vector &rules() const; + /// set_rules adjusts the rules applying to this airspace instance to 'rules. + void set_rules(const std::vector &rules); + + /// details_for_airport returns an immutable reference to the details + /// further describing this airspace instance. + const Airport &details_for_airport() const; + /// details_for_airport returns a mutable reference to the details + /// further describing this airspace instance. + Airport &details_for_airport(); + + /// details_for_controlled_airspace returns an immutable reference to the details + /// further describing this airspace instance. + const ControlledAirspace &details_for_controlled_airspace() const; + /// details_for_controlled_airspace returns a mutable reference to the details + /// further describing this airspace instance. + ControlledAirspace &details_for_controlled_airspace(); + + /// details_for_emergency returns an immutable reference to the details + /// further describing this airspace instance. + const Emergency &details_for_emergency() const; + /// details_for_emergency returns a mutable reference to the details + /// further describing this airspace instance. + Emergency &details_for_emergency(); + + /// details_for_fire returns an immutable reference to the details + /// further describing this airspace instance. + const Fire &details_for_fire() const; + /// details_for_fire returns a mutable reference to the details + /// further describing this airspace instance. + Fire &details_for_fire(); + + /// details_for_heliport returns an immutable reference to the details + /// further describing this airspace instance. + const Heliport &details_for_heliport() const; + /// details_for_heliport returns a mutable reference to the details + /// further describing this airspace instance. + Heliport &details_for_heliport(); + + /// details_for_hospital returns an immutable reference to the details + /// further describing this airspace instance. + const Hospital &details_for_hospital() const; + /// details_for_hospital returns a mutable reference to the details + /// further describing this airspace instance. + Hospital &details_for_hospital(); + + /// details_for_park returns an immutable reference to the details + /// further describing this airspace instance. + const Park &details_for_park() const; + /// details_for_park returns a mutable reference to the details + /// further describing this airspace instance. + Park &details_for_park(); + + /// details_for_power_plant returns an immutable reference to the details + /// further describing this airspace instance. + const PowerPlant &details_for_power_plant() const; + /// details_for_power_plant returns a mutable reference to the details + /// further describing this airspace instance. + PowerPlant &details_for_power_plant(); + + /// details_for_prison returns an immutable reference to the details + /// further describing this airspace instance. + const Prison &details_for_prison() const; + /// details_for_prison returns a mutable reference to the details + /// further describing this airspace instance. + Prison &details_for_prison(); + + /// details_for_school returns an immutable reference to the details + /// further describing this airspace instance. + const School &details_for_school() const; + /// details_for_school returns a mutable reference to the details + /// further describing this airspace instance. + School &details_for_school(); + + /// details_for_special_use_airspace returns an immutable reference to the details + /// further describing this airspace instance. + const SpecialUseAirspace &details_for_special_use_airspace() const; + /// details_for_special_use_airspace returns a mutable reference to the details + /// further describing this airspace instance. + SpecialUseAirspace &details_for_special_use_airspace(); + + /// details_for_temporary_flight_restriction returns an immutable reference to the details + /// further describing this airspace instance. + const TemporaryFlightRestriction &details_for_temporary_flight_restriction() const; + /// details_for_temporary_flight_restriction returns a mutable reference to the details + /// further describing this airspace instance. + TemporaryFlightRestriction &details_for_temporary_flight_restriction(); + + /// details_for_wildfire returns an immutable reference to the details + /// further describing this airspace instance. + const Wildfire &details_for_wildfire() const; + /// details_for_wildfire returns an immutable reference to the details + /// further describing this airspace instance. + Wildfire &details_for_wildfire(); + + /// set_details adjusts the details of this airspace instance to 'detail'. + void set_details(const Airspace &detail); + /// set_details adjusts the details of this airspace instance to 'detail'. + void set_details(const Airport &detail); + /// set_details adjusts the details of this airspace instance to 'detail'. + void set_details(const ControlledAirspace &detail); + /// set_details adjusts the details of this airspace instance to 'detail'. + void set_details(const SpecialUseAirspace &detail); + /// set_details adjusts the details of this airspace instance to 'detail'. + void set_details(const TemporaryFlightRestriction &detail); + /// set_details adjusts the details of this airspace instance to 'detail'. + void set_details(const Wildfire &detail); + /// set_details adjusts the details of this airspace instance to 'detail'. + void set_details(const Park &detail); + /// set_details adjusts the details of this airspace instance to 'detail'. + void set_details(const PowerPlant &detail); + /// set_details adjusts the details of this airspace instance to 'detail'. + void set_details(const Heliport &detail); + /// set_details adjusts the details of this airspace instance to 'detail'. + void set_details(const Prison &detail); + /// set_details adjusts the details of this airspace instance to 'detail'. + void set_details(const School &detail); + /// set_details adjusts the details of this airspace instance to 'detail'. + void set_details(const Hospital &detail); + /// set_details adjusts the details of this airspace instance to 'detail'. + void set_details(const Fire &detail); + /// set_details adjusts the details of this airspace instance to 'detail'. + void set_details(const Emergency &detail); + + private: + struct Invalid {}; + + union Details { + Details(); + ~Details(); + + Invalid invalid; + Airport airport; + ControlledAirspace controlled_airspace; + Emergency emergency; + Fire fire; + Heliport heliport; + Hospital hospital; + Park park; + PowerPlant power_plant; + Prison prison; + School school; + SpecialUseAirspace special_use_airspace; + TemporaryFlightRestriction tfr; + Wildfire wildfire; + }; + + void reset(); + + Id id_; + std::string name_; + Type type_; + std::string country_; // TODO(tvoss): Investigate constraints on country names. + std::string state_; // TODO(tvoss): Investigate constraints on state names. + std::string city_; + Timestamp last_updated_; + Geometry geometry_; + std::map related_geometries_; + std::vector rules_; + Details details_; +}; + +/// @cond +bool operator==(const Airspace::RelatedGeometry &lhs, const Airspace::RelatedGeometry &rhs); +bool operator==(const Airspace::Airport &lhs, const Airspace::Airport &rhs); +bool operator==(const Airspace::Airport::Runway &lhs, const Airspace::Airport::Runway &rhs); +bool operator==(const Airspace::ControlledAirspace &lhs, const Airspace::ControlledAirspace &rhs); +bool operator==(const Airspace::SpecialUseAirspace &lhs, const Airspace::SpecialUseAirspace &rhs); +bool operator==(const Airspace::TemporaryFlightRestriction &lhs, const Airspace::TemporaryFlightRestriction &rhs); +bool operator==(const Airspace::Wildfire &lhs, const Airspace::Wildfire &rhs); +bool operator==(const Airspace::Park &lhs, const Airspace::Park &rhs); +bool operator==(const Airspace::Prison &lhs, const Airspace::Prison &rhs); +bool operator==(const Airspace::School &lhs, const Airspace::School &rhs); +bool operator==(const Airspace::Hospital &lhs, const Airspace::Hospital &rhs); +bool operator==(const Airspace::Fire &lhs, const Airspace::Fire &rhs); +bool operator==(const Airspace::Emergency &lhs, const Airspace::Emergency &rhs); +bool operator==(const Airspace::Heliport &lhs, const Airspace::Heliport &rhs); +bool operator==(const Airspace::PowerPlant &lhs, const Airspace::PowerPlant &rhs); + +Airspace::Type operator~(Airspace::Type); +Airspace::Type operator|(Airspace::Type, Airspace::Type); +Airspace::Type operator&(Airspace::Type, Airspace::Type); + +std::ostream &operator<<(std::ostream &, const Airspace &); +std::ostream &operator<<(std::ostream &, Airspace::Type); +/// @endcond + +} // namespace airmap + +#endif // AIRMAP_AIRSPACE_H_ diff --git a/libs/airmapd/include/airmap/airspaces.h b/libs/airmapd/include/airmap/airspaces.h new file mode 100644 index 0000000000000000000000000000000000000000..1f81c588cecac1be17ec0cc9a4d7f615ab1b4217 --- /dev/null +++ b/libs/airmapd/include/airmap/airspaces.h @@ -0,0 +1,71 @@ +#ifndef AIRMAP_AIRSPACES_H_ +#define AIRMAP_AIRSPACES_H_ + +#include +#include +#include +#include +#include + +#include +#include + +namespace airmap { + +/// Airspaces provides functionality to query the airspace database. +class Airspaces : DoNotCopyOrMove { + public: + /// ForIds groups together types to ease interaction with + /// Airspaces::ForIds. + struct ForIds { + /// Parameters bundles up input parameters. + struct Parameters { + Airspace::Id id; ///< Search for the airspace with this id. + }; + + /// Result models the outcome of calling Airspaces::for_id. + using Result = Outcome, Error>; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Airspaces::for_id finishes. + using Callback = std::function; + }; + + /// Search groups together types to ease interaction with + /// Airspaces::Search. + struct Search { + /// Parameters bundles up input parameters. + struct Parameters { + Optional types; ///< Search for airspaces with either one of these types. + Optional ignored_types; ///< Ignore airspaces with either one of these types. + Optional full; ///< If true, the complete description of airspaces in the result set is requested. + Geometry geometry; ///< Search airspaces intersection this geometry. + Optional buffer; ///< Buffer around the geometry in [m]. + Optional limit; ///< Limit the number of results to 'limit'. + Optional offset; + Optional date_time; + }; + + /// Result models the outcome of calling Airspaces::search. + using Result = Outcome, Error>; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Airspaces::search finishes. + using Callback = std::function; + }; + + /// search queries the AirMap services for surrounding airspaces and + /// reports back the results to 'cb'. + virtual void search(const Search::Parameters& parameters, const Search::Callback& cb) = 0; + + /// for_ids queries the AirMap services for detailed information about + /// airspaces identified by UUIDs and reports back results to 'cb'. + virtual void for_ids(const ForIds::Parameters& parameters, const ForIds::Callback& cb) = 0; + + protected: + /// cond + Airspaces() = default; + /// @endcond +}; + +} // namespace airmap + +#endif // AIRMAP_AIRSPACES_H_ diff --git a/libs/airmapd/include/airmap/authenticator.h b/libs/airmapd/include/airmap/authenticator.h new file mode 100644 index 0000000000000000000000000000000000000000..50d5c304f9618bbfd96c8052544880daf303722b --- /dev/null +++ b/libs/airmapd/include/airmap/authenticator.h @@ -0,0 +1,103 @@ +#ifndef AIRMAP_AUTHENTICATOR_H_ +#define AIRMAP_AUTHENTICATOR_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace airmap { + +/// Authenticator provides functionality to authenticate with the AirMap services. +class Authenticator : DoNotCopyOrMove { + public: + /// Scope enumerates all known authentication scopes. + enum class Scope { access_token = 0, open_id = 1, open_id_offline_access = 2 }; + /// GrantType enumerates all known grant types. + enum class GrantType { + password = 0, ///< The grant is constituted by a password + bearer = 1 ///< The grant is constituted by a bearer + }; + /// Connection enumerates all known types of connection to users. + enum class Connection { + username_password_authentication = 0 ///< authentication requires username/password + }; + + /// AuthenticateWithPassword groups together types to ease interaction with + /// Authenticator::authenticate_with_password. + struct AuthenticateWithPassword { + /// Parameters bundles up input parameters. + struct Params { + Credentials::OAuth oauth; ///< OAuth-specific credentials for this authentication request. + GrantType grant_type{GrantType::password}; ///< The grant type of this authentication request. + Scope scope{Scope::open_id_offline_access}; ///< The scope of this authentication request. + Connection connection{ + Connection::username_password_authentication}; ///< The connection type of the authentication request. + }; + + /// Result models the outcome of calling Authenticator::authenticate_with_password. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Authenticator::authenticate_with_password finishes. + using Callback = std::function; + }; + + /// AuthenticateAnonymously groups together types to ease interaction with + /// Authenticator::authenticate_anonymously. + struct AuthenticateAnonymously { + /// The input parameters. + using Params = Credentials::Anonymous; + /// Result models the outcome of calling Authenticator::authenticate_anonymously. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Authenticator::authenticate_anonymously finishes. + using Callback = std::function; + }; + + /// RenewAuthentication groups together types to ease interaction with + /// Authenticator::renew_authentication. + struct RenewAuthentication { + /// The input parameters. + struct Params { + std::string client_id; ///< The app id for which authentication renewal is requested. + std::string refresh_token; ///< The refresh token for the authentication renewal request. + GrantType grant_type{GrantType::bearer}; ///< The grant type of the authentication renewal request. + Scope scope{Scope::open_id}; ///< The scope of the authentication renewal request. + }; + /// Result models the outcome of calling Authenticator::renew_authentication. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Authenticator::renew_authentication finishes. + using Callback = std::function; + }; + + /// authenticate_with_password authenticates the user described in 'params' with + /// the AirMap services and reports the result to 'cb'. + virtual void authenticate_with_password(const AuthenticateWithPassword::Params& params, + const AuthenticateWithPassword::Callback& cb) = 0; + + /// authenticate_anonymously authenticates an anonymous user described by Params::user_id + /// with the AirMap services and reports the result to 'cb'. + virtual void authenticate_anonymously(const AuthenticateAnonymously::Params&, + const AuthenticateAnonymously::Callback&) = 0; + + /// renew_authentication renews a pre-authenticated JWT as given in Params::user_id with + /// the AirMap services and reports the result to 'cb'. + virtual void renew_authentication(const RenewAuthentication::Params& params, + const RenewAuthentication::Callback& cb) = 0; + + protected: + /// @cond + Authenticator() = default; + /// @endcond +}; + +} // namespace airmap + +#endif // AIRMAP_AUTHENTICATOR_H_ diff --git a/libs/airmapd/include/airmap/client.h b/libs/airmapd/include/airmap/client.h new file mode 100644 index 0000000000000000000000000000000000000000..f04c898943dad4f47a822ce518728fedd65214f1 --- /dev/null +++ b/libs/airmapd/include/airmap/client.h @@ -0,0 +1,145 @@ +#ifndef AIRMAP_CLIENT_H_ +#define AIRMAP_CLIENT_H_ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace airmap { + +class Aircrafts; +class Airspaces; +class Authenticator; +class FlightPlans; +class Flights; +class Pilots; +class RuleSets; +class Status; +class Telemetry; +class Traffic; + +/// Client enables applications to use the AirMap services and APIs. +class Client : DoNotCopyOrMove { + public: + /// Version enumerates all known versions available to clients. + enum class Version { production, staging }; + + /// Configuration bundles up parameters enabling + /// customization of a Client implementation behavior. + struct Configuration { + std::string host; ///< Address of the host exposing the AirMap services. + Version version; ///< The version of the AirMap services that should be used. + struct { + std::string host; ///< Address of the host exposing the sso service. + std::uint16_t port; ///< Port on the host exposing the sso service. + } sso; ///< The SSO endpoint used for the authenticating with the AirMap services. + struct { + std::string host; ///< Address of the host exposing the AirMap telemetry endpoints. + std::uint16_t port; ///< Port of the host exposing the AirMap telemetry endpoints. + } telemetry; ///< The telemetry submission endpoint. + struct { + std::string host; ///< Address of the mqtt broker serving air traffic information. + std::uint16_t port; ///< Port of the mqtt broker serving air traffic information. + } traffic; ///< The traffic endpoint. + Credentials credentials; ///< Credentials that are required to authorize access to the AirMap services. + }; + + /// default_production_configuration returns a Configuration instance that works + /// against the AirMap production API and telemetry endpoints. + static Configuration default_production_configuration(const Credentials& credentials); + + /// default_staging_configuration returns a Configuration instance that works + /// against the AirMap staging API and telemetry endpoints. + static Configuration default_staging_configuration(const Credentials& credentials); + + /// default_configuration returns a Configuration instance that works against + /// the AirMap API and telemetry endpoints indicated by 'version'. + static Configuration default_configuration(Version version, const Credentials& credentials); + + /// load_configuration_from_json loads a configuration from 'in', assuming the following + /// JSON format: + /// + /// @code{.json} + /// { + /// "host": "api.airmap.com", + /// "version": "production", + /// "sso": { + /// "host": "sso.airmap.io", + /// "port": 443 + /// }, + /// "telemetry": { + /// "host": "api-udp-telemetry.airmap.com", + /// "port": 16060 + /// }, + /// "traffic": { + /// "host": "mqtt-prod.airmap.io", + /// "port": 8883 + /// }, + /// "credentials": { + /// "api-key": "your api key should go here", + /// "oauth": { + /// "client-id": "your client id should go here", + /// "device-id": "your device id should go here, or generate one with uuid-gen", + /// "username": "your AirMap username should go here", + /// "password": "your AirMap password should go here" + /// }, + /// "anonymous": { + /// "id": "some id" + /// } + /// } + /// } + /// @endcode + static Configuration load_configuration_from_json(std::istream& in); + + /// authenticator returns the Authenticator implementation provided by the client. + virtual Authenticator& authenticator() = 0; + + /// aircrafts returns the Aircrafts implementation provided by the client. + virtual Aircrafts& aircrafts() = 0; + + /// airspaces returns the Airspaces implementation provided by the client. + virtual Airspaces& airspaces() = 0; + + /// flight_plans returns the FlightPlans implementation provided by the client. + virtual FlightPlans& flight_plans() = 0; + + /// flights returns the Flights implementation provided by the client. + virtual Flights& flights() = 0; + + /// pilots returns the Pilots implementation provided by the client. + virtual Pilots& pilots() = 0; + + /// rulesets returns the RuleSets implementation provided by the client. + virtual RuleSets& rulesets() = 0; + + /// status returns the Status implementation provided by the client. + virtual Status& status() = 0; + + /// telemetry returns the Telemetry implementation provided by the client. + virtual Telemetry& telemetry() = 0; + + /// traffic returns the Traffic implementation provided by the client. + virtual Traffic& traffic() = 0; + + protected: + /// @cond + Client() = default; + /// @endcond +}; + +/// @cond +std::istream& operator>>(std::istream& in, Client::Version& version); +std::ostream& operator<<(std::ostream& out, Client::Version version); +/// @endcond + +} // namespace airmap + +#endif // AIRMAP_CLIENT_H_ diff --git a/libs/airmapd/include/airmap/context.h b/libs/airmapd/include/airmap/context.h new file mode 100644 index 0000000000000000000000000000000000000000..694a3bd75a49be1cd4d8d44f05904f242253b1ac --- /dev/null +++ b/libs/airmapd/include/airmap/context.h @@ -0,0 +1,85 @@ +#ifndef AIRMAP_CONTEXT_H_ +#define AIRMAP_CONTEXT_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace airmap { + +/// Context consitutes the point-of-entry for interaction with the classes and interfaces +/// in airmap::*. +class Context : DoNotCopyOrMove { + public: + /// ReturnCode enumerates all known return values for a call to run or exec. + enum class ReturnCode { + success = 0, /// Execution finished successfully + error = 1, /// Execution finished with an error + already_running = 2 /// Indicates that the context is already executing on another thread + }; + + /// @cond + using ClientCreateResult = Outcome, Error>; + using ClientCreateCallback = std::function; + using MonitorClientCreateResult = Outcome, Error>; + using MonitorClientCreateCallback = std::function; + using CreateResult = Outcome, Error>; + using SignalHandler = std::function; + using SignalSet = std::unordered_set; + /// @endcond + + /// create tries to assemble and return a new Context instance. + static CreateResult create(const std::shared_ptr& logger); + + /// create_client_with_configuration schedules creation of a new client with 'configuration' + /// and reports results to 'cb'. + virtual void create_client_with_configuration(const Client::Configuration& configuration, + const ClientCreateCallback& cb) = 0; + + /// create_monitor_client_with_configuration schedules creation of a new monitor::Client with 'configuration' + /// and reports results to 'cb'. + virtual void create_monitor_client_with_configuration(const monitor::Client::Configuration& configuration, + const MonitorClientCreateCallback& cb) = 0; + + /// exec hands a thread of execution to a Context instance, monitoring + /// the signals present in 'signal_set' and dispatching incoming signals + /// to the registered handlers. + /// + /// Implementations are expected to block the current thread until + /// either an error occured or the user explicitly requests a Context + /// instance to stop. + virtual ReturnCode exec(const SignalSet& signal_set, const SignalHandler& handler) = 0; + + /// run hands a thread of execution to the context. + /// + /// Implementations are expected to block the current thread until + /// either an error occured or the user explicitly requests a Context + /// instance to stop. + virtual ReturnCode run() = 0; + + /// stop requests an instance to shut down its operation and return from + /// run. + virtual void stop(ReturnCode rc = ReturnCode::success) = 0; + + /// dispatch executes 'task' on the thread running this Context instance. + virtual void dispatch(const std::function& task) = 0; + + /// schedule_in schedules execution of 'functor' in 'wait_for' [us]. + virtual void schedule_in(const Microseconds& wait_for, const std::function& functor) = 0; + + protected: + /// @cond + Context() = default; + /// @endcond +}; + +} // namespace airmap + +#endif // AIRMAP_CONTEXT_H_ diff --git a/libs/airmapd/include/airmap/credentials.h b/libs/airmapd/include/airmap/credentials.h new file mode 100644 index 0000000000000000000000000000000000000000..513a641573c52adb23bb94e15692764b0a9f889d --- /dev/null +++ b/libs/airmapd/include/airmap/credentials.h @@ -0,0 +1,43 @@ +#ifndef AIRMAP_CREDENTIALS_H_ +#define AIRMAP_CREDENTIALS_H_ + +#include + +#include +#include + +namespace airmap { + +/// Credentials bundles up all credentials required +/// to use the AirMap SDK and APIs. +struct Credentials { + enum class Type { anonymous, oauth }; + + /// Anonymous bundles up all attributes needed to + /// authenticate anonymously with the AirMap services. + struct Anonymous { + std::string id; + }; + + /// OAuth bundles up all attributes needed to authenticate + /// with username/password with the AirMap services. + struct OAuth { + std::string username; + std::string password; + std::string client_id; + std::string device_id; + }; + + std::string api_key; ///< Use this api key when accessing the AirMap services + Optional oauth; /// Optional attributes for authenticating with username/password with the AirMap services + Optional anonymous; /// Optional attributes for authenticating anonymously with the AirMap services +}; + +/// operator>> extracts type from in. +std::istream& operator>>(std::istream& in, Credentials::Type& type); +/// operator<< inserts type into out. +std::ostream& operator<<(std::ostream& out, Credentials::Type type); + +} // namespace airmap + +#endif // AIRMAP_CREDENTIALS_H_ diff --git a/libs/airmapd/include/airmap/date_time.h b/libs/airmapd/include/airmap/date_time.h new file mode 100644 index 0000000000000000000000000000000000000000..2d02c78ff76cde20b1958e7843bf61b5500d6ad7 --- /dev/null +++ b/libs/airmapd/include/airmap/date_time.h @@ -0,0 +1,48 @@ +#ifndef AIRMAP_DATE_TIME_H_ +#define AIRMAP_DATE_TIME_H_ + +#include +#include + +#include +#include +#include + +namespace airmap { + +/// Clock marks the reference for time measurements. +using Clock = boost::posix_time::microsec_clock; +/// DateTime marks a specific point in time, in reference to Clock. +using DateTime = boost::posix_time::ptime; +using Hours = boost::posix_time::hours; +using Minutes = boost::posix_time::minutes; +using Seconds = boost::posix_time::seconds; +using Milliseconds = boost::posix_time::milliseconds; +using Microseconds = boost::posix_time::microseconds; + +/// milliseconds_since_epoch returns the milliseconds that elapsed since the UNIX epoch. +std::uint64_t milliseconds_since_epoch(const DateTime& dt); +/// microseconds_since_epoch returns the microseconds that elapsed since the UNIX epoch. +std::uint64_t microseconds_since_epoch(const DateTime& dt); +/// from_seconds_since_epoch returns a DateTime. +DateTime from_seconds_since_epoch(const Seconds& s); +/// from_milliseconds_since_epoch returns a DateTime. +DateTime from_milliseconds_since_epoch(const Milliseconds& ms); +/// from_microseconds_since_epoch returns a DateTime. +DateTime from_microseconds_since_epoch(const Microseconds& us); + +// moves the datetime forward to the specified hour +DateTime move_to_hour(const DateTime& dt, int hour); + +namespace iso8601 { + +/// parse parses a DateTime instance from the string s in iso8601 format. +DateTime parse(const std::string& s); +/// generate returns a string in iso8601 corresponding to 'dt'. +std::string generate(const DateTime& dt); + +} // namespace iso8601 + +} // namespace airmap + +#endif // AIRMAP_DATE_TIME_H_ diff --git a/libs/airmapd/include/airmap/do_not_copy_or_move.h b/libs/airmapd/include/airmap/do_not_copy_or_move.h new file mode 100644 index 0000000000000000000000000000000000000000..807d41887f77fc227e9c7a0f43d82fce78f64028 --- /dev/null +++ b/libs/airmapd/include/airmap/do_not_copy_or_move.h @@ -0,0 +1,19 @@ +#ifndef AIRMAP_DO_NOT_COPY_OR_MOVE_H_ +#define AIRMAP_DO_NOT_COPY_OR_MOVE_H_ + +namespace airmap { + +/// @cond +struct DoNotCopyOrMove { + DoNotCopyOrMove() = default; + DoNotCopyOrMove(const DoNotCopyOrMove&) = delete; + DoNotCopyOrMove(DoNotCopyOrMove&&) = delete; + virtual ~DoNotCopyOrMove() = default; + DoNotCopyOrMove& operator=(const DoNotCopyOrMove&) = delete; + DoNotCopyOrMove& operator=(DoNotCopyOrMove&&) = delete; +}; +/// @endcond + +} // namespace airmap + +#endif // AIRMAP_DO_NOT_COPY_OR_MOVE_H_ diff --git a/libs/airmapd/include/airmap/error.h b/libs/airmapd/include/airmap/error.h new file mode 100644 index 0000000000000000000000000000000000000000..931a0f2ad60f98e3d655ccbd3429f5cbe9684cc3 --- /dev/null +++ b/libs/airmapd/include/airmap/error.h @@ -0,0 +1,173 @@ +#ifndef AIRMAP_ERROR_H_ +#define AIRMAP_ERROR_H_ + +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace airmap { + +/// Error models an error raised by an AirMap component. +struct Error { + /// Value is a discriminated union type wrapping up multiple atomic types and + /// their composition into a vector or a dictionary. + class Value { + public: + /// Type enumerates all datatypes that can be wrapped in a Value. + enum class Type { + undefined, ///< Marks the undefined type. + boolean, ///< Marks a boolean type. + integer, ///< Marks an integer type with 64 bits. + floating_point, ///< Marks a double-precision floating point number. + string, ///< Marks a string. + blob, ///< Marks a binary blob. + dictionary, ///< Marks a dictionary of values. + vector ///< Marks a vector of values. + }; + + /// Value initializes a new Value instance of type undefined. + explicit Value(); + /// Value initializes a new Value instance of type boolean with 'value'. + explicit Value(bool value); + /// Value initializes a new Value instance of type integer with 'value'. + explicit Value(std::int64_t value); + /// Value initializes a new Value instance of type floating_point with 'value'. + explicit Value(double value); + /// Value initializes a new Value instance of type string with 'value'. + explicit Value(const std::string& value); + /// Value initializes a new Value instance of type blob with 'value'. + explicit Value(const std::vector& value); + /// Value initializes a new Value instance of type dictionary with 'value'. + explicit Value(const std::map& value); + /// Value initializes a new Value instance of type vector with 'value'. + explicit Value(const std::vector& value); + /// Value copy-constructs a value from 'other'. + Value(const Value& other); + /// Value move-constructs a value from 'other'. + Value(Value&&); + /// ~Value cleans up all resources by a Value instance. + ~Value(); + + /// operator= assigns type and value from rhs. + Value& operator=(const Value& rhs); + /// operator= moves type and value from rhs. + Value& operator=(Value&& rhs); + + /// type returns the Type of this Value instance. + Type type() const; + + /// boolean returns the boolean value of this Value instance. + /// The behavior in case of type() != Type::boolean is undefined. + bool boolean() const; + /// integer returns the boolean value of this Value instance. + /// The behavior in case of type() != Type::integer is undefined. + std::int64_t integer() const; + /// floating_point returns the floating point value of this Value instance. + /// The behavior in case of type() != Type::floating_point is undefined. + double floating_point() const; + /// string returns the string value of this Value instance. + /// The behavior in case of type() != Type::string is undefined. + const std::string& string() const; + /// blob returns the blob value of this Value instance. + /// The behavior in case of type() != Type::blob is undefined. + const std::vector blob() const; + /// dictionary returns the dictionary value of this Value instance. + /// The behavior in case of type() != Type::dictionary is undefined. + const std::map& dictionary() const; + /// vector returns the vector value of this Value instance. + /// The behavior in case of type() != Type::vector is undefined. + const std::vector& vector() const; + + private: + union Details { + Details(); + ~Details(); + + bool boolean; + std::int64_t integer; + double floating_point; + std::string string; + std::vector blob; + std::map dictionary; + std::vector vector; + }; + + Value& construct(bool value); + Value& construct(std::int64_t value); + Value& construct(double value); + Value& construct(const std::string& value); + Value& construct(std::string&& value); + Value& construct(const std::vector& value); + Value& construct(std::vector&& value); + Value& construct(const std::map& value); + Value& construct(std::map&& value); + Value& construct(const std::vector& value); + Value& construct(std::vector&& value); + Value& construct(const Value& value); + Value& construct(Value&& value); + Value& destruct(); + + Type type_; + Details details_; + }; + + /// Error initializes a new error instance with 'message'. + explicit Error(); + + /// Error initializes a new error instance with 'message'. + explicit Error(const std::string& message); + + /// message returns the message describing an error condition. + const std::string& message() const; + /// message sets the message of the Error instance to 'message'. + Error message(const std::string& message) const; + /// message sets the message of the Error instance to 'message'. + Error& message(const std::string& message); + + /// description returns the optional description of an error condition. + const Optional& description() const; + /// clear_description resets the description of the Error instance. + Error clear_description() const; + /// clear_description resets the description of the Error instance. + Error& clear_description(); + /// description sets the description of the Error instance to 'description'. + Error description(const std::string& description) const; + /// description sets the description of the Error instance to 'description'. + Error& description(const std::string& description); + + /// values returns the additional values describing an error condition. + const std::map& values() const; + /// clear_values resets the values of the Error instance. + Error clear_values() const; + /// clear_values resets the values of the Error instance. + Error& clear_values(); + /// value adds the pair (key, value) to the additional values describing an error condition. + Error value(const Value& key, const Value& value) const; + /// value adds the pair (key, value) to the additional values describing an error condition. + Error& value(const Value& key, const Value& value); + + private: + std::string message_; ///< Short, human-readable message. + Optional description_; ///< Detailed description of the error, meant to be used for defect analysis. + std::map values_; ///< Dictionary of additional data attached to the error. +}; + +/// operator== returns true if both type and value of lhs and rhs compare equal. +bool operator==(const Error::Value& lhs, const Error::Value& rhs); +/// operator< returns true if type and value of lhs compare < than type and value of rhs. +bool operator<(const Error::Value& lhs, const Error::Value& rhs); +/// operator<< inserts 'value' into 'out'. +std::ostream& operator<<(std::ostream& out, const Error::Value& value); +/// operator<< inserts 'error' into 'out'. +std::ostream& operator<<(std::ostream& out, const Error& error); + +} // namespace airmap + +#endif // AIRMAP_ERROR_H_ \ No newline at end of file diff --git a/libs/airmapd/include/airmap/evaluation.h b/libs/airmapd/include/airmap/evaluation.h new file mode 100644 index 0000000000000000000000000000000000000000..a2f65624dbea74a2792ff285f1fd8b6492cfb870 --- /dev/null +++ b/libs/airmapd/include/airmap/evaluation.h @@ -0,0 +1,89 @@ +#ifndef AIRMAP_EVALUATION_H_ +#define AIRMAP_EVALUATION_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace airmap { + +/// Evaluation bundles together information regarding an airspace ruleset evaluation. +struct Evaluation { + /// Authority models an authority capable of authorizing flight plans. + struct Authority { + std::string id; ///< The id of the authority. + std::string name; ///< The name of the authority. + }; + + /// Authorization bundles up the authorization status of a flight plan. + struct Authorization { + /// Status enumerates all known states of an Authorization. + enum class Status { + accepted, ///< The flight plan is accepted. + rejected, ///< The flight plan is rejected. + pending, ///< The request for authorization is pending a response. + accepted_upon_submission, ///< The request will be accepted on submission. + rejected_upon_submission ///< The request will be rejected on submission. + }; + + Status status; ///< The overall status of the request. + Authority authority; ///< The authority that handles the request. + std::string message; ///< The human-readable message provided by the authority. + }; + + /// Validation bundles up the validation status of a flight plan. + struct Validation { + /// Status enumerates all known states of a Validation. + enum class Status { + valid, ///< The validation succeeded. + invalid, ///< The validation was rejected. + unknown ///< The status is unknown. + }; + + /// Feature describes a specific feature that requires validation. + struct Feature { + std::string code; ///< The code of the feature. + std::string description; ///< The description of the feature. + }; + + Status status; ///< The overall status of the validation. + std::string data; ///< The data provided for validation. + std::string message; ///< The human-readable message provided by the authority. + Feature feature; ///< The specific feature requiring validation. + Authority authority; ///< The authority carrying out the validation. + }; + + /// Failure enumrates all known failures during evaluation. + enum class Failure { + validation, ///< The validation failed. + authorization, ///< The authorization failed. + rulesets ///< The ruleset engine failed. + }; + + std::vector rulesets; ///< All RuleSet instances relevant to a specific briefing/flight plan. + std::vector validations; ///< All Validation instances relevant to a specific briefing/flight plan. + std::vector + authorizations; ///< All Authorization instances relevant to a specific briefing/flight plan. + std::vector failures; ///< All Failure instances relevant to a specific briefing/flight plan. +}; + +/// @cond +std::ostream& operator<<(std::ostream& out, Evaluation::Authorization::Status status); +std::istream& operator>>(std::istream& in, Evaluation::Authorization::Status& status); + +std::ostream& operator<<(std::ostream& out, Evaluation::Validation::Status status); +std::istream& operator>>(std::istream& in, Evaluation::Validation::Status& status); + +std::ostream& operator<<(std::ostream& out, Evaluation::Failure failure); +std::istream& operator>>(std::istream& in, Evaluation::Failure& failure); +/// @endcond + +} // namespace airmap + +#endif // AIRMAP_EVALUATION_H_ diff --git a/libs/airmapd/include/airmap/flight.h b/libs/airmapd/include/airmap/flight.h new file mode 100644 index 0000000000000000000000000000000000000000..ba5879c0997ca3b4ba6e994ddb851cd39a96bebd --- /dev/null +++ b/libs/airmapd/include/airmap/flight.h @@ -0,0 +1,32 @@ +#ifndef AIRMAP_FLIGHT_H_ +#define AIRMAP_FLIGHT_H_ + +#include +#include +#include +#include + +#include + +namespace airmap { + +/// Flight bundles together properties describing an individual flight. +struct Flight { + using Id = std::string; + + Id id; ///< The unique identifier of a flight in the context of AirMap. + Optional flight_plan_id; ///< The flight plan corresponding to this flight. + Pilot pilot; ///< The pilot responsible for the flight. + Pilot::Aircraft aircraft; ///< The aircraft conducting the flight. + float latitude; ///< The latitude component of the takeoff point in [°]. + float longitude; ///< The longitude component of the takeoff point in [°]. + float max_altitude; ///< The maximum altitude over the entire flight in [m]. + Geometry geometry; ///< The geometry describing the flight. + DateTime created_at; ///< Point in time when the flight was created. + DateTime start_time; ///< Point in time when the flight will start/was started. + DateTime end_time; ///< Point in time when the fligth will end. +}; + +} // namespace airmap + +#endif // AIRMAP_FLIGHT_H_ diff --git a/libs/airmapd/include/airmap/flight_plan.h b/libs/airmapd/include/airmap/flight_plan.h new file mode 100644 index 0000000000000000000000000000000000000000..dcb635aea473cdf6174e84bcf76daff0d28f352b --- /dev/null +++ b/libs/airmapd/include/airmap/flight_plan.h @@ -0,0 +1,60 @@ +#ifndef AIRMAP_FLIGHT_PLAN_H_ +#define AIRMAP_FLIGHT_PLAN_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace airmap { + +/// FlightPlan bundles together properties describing a plan for a flight. +struct FlightPlan { + /// Id models a unique identifier for a flight plan in the context of AirMap. + using Id = std::string; + + /// Briefing bundles together information and data for a flight plan. + /// The target audience is a hypothetical pilot or operator conducting + /// the flight described in the flight plan. + struct Briefing { + /// AdvisoryStatus summarizes the status of all advisories applying to a specific flight plan. + struct AdvisoryStatus { + Status::Color color; ///< The overall color of the status. + std::vector advisories; ///< The collection of relevant advisories. + }; + + DateTime created_at; ///< The timestamp when the briefing was requested and created by the AirMap services. + AdvisoryStatus airspace; ///< The summary over all advisories relevant to a specific briefing/flight plan. + Evaluation evaluation; ///< The airspace ruleset evaluation returned for the briefing. + }; + + Id id; ///< The unique identifier of a flight in the context of AirMap. + Optional flight_id; ///< The unique identifier of the flight that is created on successful submission. + Pilot pilot; ///< The pilot responsible for the flight. + Pilot::Aircraft aircraft; ///< The aircraft conducting the flight. + struct { + float latitude; ///< The latitude component of the takeoff coordinates in [°]. + float longitude; ///< The longitude component of the takeoff coordinates in [°]. + } takeoff; ///< The takeoff coordinate. + struct { + float max; ///< The maximum altitude over the entire flight in [m]. + float min; ///< The minimum altitude over the entire flight in [m]. + } altitude_agl; ///< The altitude range of the flight in [m] above ground level. + float buffer; ///< The buffer in [m] around the geometry. + Geometry geometry; ///< The geometry describing the flight. + DateTime start_time; ///< Point in time when the flight will start/was started. + DateTime end_time; ///< Point in time when the fligth will end. +}; + +} // namespace airmap + +#endif // AIRMAP_FLIGHT_PLAN_H_ diff --git a/libs/airmapd/include/airmap/flight_plans.h b/libs/airmapd/include/airmap/flight_plans.h new file mode 100644 index 0000000000000000000000000000000000000000..03454d836197042bd76b6f3c2f47404480847160 --- /dev/null +++ b/libs/airmapd/include/airmap/flight_plans.h @@ -0,0 +1,160 @@ +#ifndef AIRMAP_FLIGHT_PLANS_H_ +#define AIRMAP_FLIGHT_PLANS_H_ + +#include +#include +#include +#include + +#include +#include +#include + +namespace airmap { + +/// FlightPlans provides functionality for managing flight plans. +class FlightPlans : DoNotCopyOrMove { + public: + /// ForId bundles up types to ease interaction with + /// FlightPlans::for_id. + struct ForId { + /// Parameters bundles up input parameters. + struct Parameters { + Optional authorization; ///< Authorization token obtained by logging in to the AirMap services. + FlightPlan::Id id; ///< Search for the flight with this id. + }; + + /// Result models the outcome of calling FlightPlans::for_id. + using Result = Outcome; + /// Callback describes the function signature of the callback that is invoked + /// when a call to FlightPlans::for_id finishes. + using Callback = std::function; + }; + + /// Create bundles up types to ease interaction with + /// FlightPlans::create_by_point and FlightPlans::create_by_polygon. + struct Create { + /// Parameters bundles up input parameters. + struct Parameters { + std::string authorization; ///< Authorization token obtained by logging in to the AirMap services. + Pilot pilot; ///< The pilot responsible for the flight. + Optional aircraft; ///< The aircraft conducting the flight. + float latitude; ///< The latitude component of the takeoff point in [°]. + float longitude; ///< The longitude component of the takeoff point in [°]. + float max_altitude; ///< The maximum altitude over the entire flight in [m]. + float min_altitude; ///< The minimum altitude over the entire flight in [m]. + float buffer; ///< The buffer in [m] around the geometry. + Geometry geometry; ///< The geometry describing the flight. + DateTime start_time; ///< Point in time when the flight will start/was started. + DateTime end_time; ///< Point in time when the fligth will end. + std::vector rulesets; ///< RuleSets that apply to this flight plan. + std::unordered_map + features; ///< Additional properties of the planned flight. + }; + + /// Result models the outcome of calling FlightPlans::create_by_polygon. + using Result = Outcome; + /// Callback describes the function signature of the callback that is invoked + /// when a call to FlightPlans::create_by_point or FlightPlans::create_by_polygon finishes. + using Callback = std::function; + }; + + /// Update bundles up types to ease interaction with + /// FlightPlans::update. + struct Update { + /// Parameters bundles up input parameters. + struct Parameters { + Optional authorization; ///< Authorization token obtained by logging in to the AirMap services. + FlightPlan flight_plan; ///< The details of the plan that should be created with the AirMap services. + }; + /// Result models the outcome of calling FlightPlans::update. + using Result = Outcome; + /// Callback describes the function signature of the callback that is invoked + /// when a call to FlightPlans::update finishes. + using Callback = std::function; + }; + + /// Delete bundles up types to ease interaction with + /// FlightPlans::delete_. + struct Delete { + /// Parameters bundles up input parameters. + struct Parameters { + Optional authorization; ///< Authorization token obtained by logging in to the AirMap services. + FlightPlan::Id id; ///< Id of the flight plan that should be deleted. + }; + + /// Response models the response from the AirMap services. + struct Response { + FlightPlan::Id id; ///< Id of the flight plan that was deleted. + }; + + /// Result models the outcome of calling FlightPlans::delete_flight. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to FlightPlans::delete_flight finishes. + using Callback = std::function; + }; + + /// RenderBriefing bundles up types to ease interaction with + /// FlightPlans::render_briefing. + struct RenderBriefing { + /// Parameters bundles up input parameters. + struct Parameters { + Optional authorization; ///< Authorization token obtained by logging in to the AirMap services. + FlightPlan::Id id; ///< Id of the flight plan that should be rendered as a briefing. + }; + /// Result models the outcome of calling FlightPlans::submit. + using Result = Outcome; + /// Callback describes the function signature of the callback that is invoked + /// when a call to FlightPlans::submit finishes. + using Callback = std::function; + }; + + /// Submit bundles up types to ease interaction with + /// FlightPlans::submit. + struct Submit { + /// Parameters bundles up input parameters. + struct Parameters { + Optional authorization; ///< Authorization token obtained by logging in to the AirMap services. + FlightPlan::Id id; ///< Id of the flight plan that should be submitted. + }; + /// Result models the outcome of calling FlightPlans::submit. + using Result = Outcome; + /// Callback describes the function signature of the callback that is invoked + /// when a call to FlightPlans::submit finishes. + using Callback = std::function; + }; + + /// for_id queries the AirMap services for detailed information about + /// a flight plan identified by a UUID and reports back results to 'cb'. + virtual void for_id(const ForId::Parameters& parameters, const ForId::Callback& cb) = 0; + + /// create_by_polygon creates a flight plan for 'parameters' and reports + /// results back to 'cb'. + virtual void create_by_polygon(const Create::Parameters& parameters, const Create::Callback& cb) = 0; + + /// update updates a flight plan identified by 'parameters' and reports + /// results back to 'cb'. + virtual void update(const Update::Parameters& parameters, const Update::Callback& cb) = 0; + + /// delete deletes a flight plan identified by 'parameters' and reports + /// results back to 'cb'. + virtual void delete_(const Delete::Parameters& parameters, const Delete::Callback& cb) = 0; + + /// render_briefing requests rendering a briefing for a flight plan identified by 'parameters' and reports + /// results back to 'cb'. + virtual void render_briefing(const RenderBriefing::Parameters& parameters, const RenderBriefing::Callback& cb) = 0; + + /// submit submits a flight plan identified by 'parameters' and reports + /// results back to 'cb'. + virtual void submit(const Submit::Parameters& parameters, const Submit::Callback& cb) = 0; + + protected: + /// @cond + FlightPlans() = default; + /// @endcond +}; + +} // namespace airmap + +#endif // AIRMAP_FLIGHT_PLANS_H_ diff --git a/libs/airmapd/include/airmap/flights.h b/libs/airmapd/include/airmap/flights.h new file mode 100644 index 0000000000000000000000000000000000000000..ca896ddd3188ecc5a47bd35d9baf3a08177898db --- /dev/null +++ b/libs/airmapd/include/airmap/flights.h @@ -0,0 +1,227 @@ +#ifndef AIRMAP_FLIGHTS_H_ +#define AIRMAP_FLIGHTS_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace airmap { + +/// Flights provides functionality for managing flights. +class Flights : DoNotCopyOrMove { + public: + /// ForId bundles up types to ease interaction with + /// Flights::for_id. + struct ForId { + /// Parameters bundles up input parameters. + struct Parameters { + Optional authorization; ///< Authorization token obtained by logging in to the AirMap services. + Flight::Id id; ///< Search for the flight with this id. + Optional enhance; ///< If true, provides extended information per flight in the result set. + }; + + /// Result models the outcome of calling Flights::for_id. + using Result = Outcome; + /// Callback describes the function signature of the callback that is invoked + /// when a call to Flights::for_id finishes. + using Callback = std::function; + }; + + /// Search bundles up types to ease interaction with + /// Flights::search. + struct Search { + /// Parameters bundles up input parameters. + struct Parameters { + Optional authorization; ///< Authorization token obtained by logging in to the AirMap services. + Optional limit; ///< Limit the number of results to 'limit'. + Optional geometry; ///< Search for flights intersecting this geometry. + Optional country; ///< Search for flights in this country. + Optional state; ///< Search for flights in this state. + Optional city; ///< Search for flights in this city. + Optional pilot_id; ///< Search for flights operated by this pilot. + Optional start_after; ///< Search for flights that started after this timestamp. + Optional start_before; ///< Search for flights that started before this timestamp. + Optional end_after; ///< Search for flights that ended after this timestamp. + Optional end_before; ///< Search for flights that ended before this timestamp. + Optional enhance; ///< If true, provides extended information per flight in the result set. + }; + + /// Response bundles up pagination and actual results for a call to Flights::search. + struct Response { + struct Paging { + std::uint32_t limit; ///< The maximum number of results per page. + std::uint32_t total; ///< The total number of results. + } paging; ///< Bundles up pagination information. + std::vector flights; ///< One page of flight results. + }; + + /// Result models the outcome of calling Flights::search. + using Result = Outcome; + /// Callback describes the function signature of the callback that is invoked + /// when a call to Flights::search finishes. + using Callback = std::function; + }; + + /// CreateFlight bundles up types to ease interaction with + /// Flights::create_flight_by_point, Flights::create_flight_by_path and + /// Flights::create_flight_by_polygon. + struct CreateFlight { + /// Parameters bundles up input parameters. + struct Parameters { + std::string authorization; ///< Authorization token obtained by logging in to the AirMap services. + Required latitude; ///< Latitude of take-off point in [°]. + Required longitude; ///< Longitude of take-off point in [°]. + float max_altitude = 121.; ///< Maximum altitude of the entire flight in [m]. + std::string aircraft_id; ///< Id of the aircraft carrying out the flight. + DateTime start_time; ///< Point in time when the flight started. + DateTime end_time; ///< Point in time when the flight will end. + bool is_public = true; ///< If true, the flight is considered public and displayed to AirMap users. + bool give_digital_notice = true; ///< If true, the flight is announced to airspace operators. + float buffer = 100; ///< Buffer around the take-off point in [m]. + Optional geometry; ///< The geometry that describes the flight. + }; + /// Result models the outcome of calling Flights::create_flight. + using Result = Outcome; + /// Callback describes the function signature of the callback that is invoked + /// when a call to Flights::create_flight finishes. + using Callback = std::function; + }; + + /// DeleteFlight bundles up types to ease interaction with + /// Flights::delete_flight. + struct DeleteFlight { + /// Parameters bundles up input parameters. + struct Parameters { + std::string authorization; ///< Authorization token obtained by logging in to the AirMap services. + Flight::Id id; ///< Id of the flight that should be deleted. + }; + + /// Response models the response from the AirMap services. + struct Response { + Flight::Id id; ///< Id of the flight that was deleted. + }; + + /// Result models the outcome of calling Flights::delete_flight. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Flights::delete_flight finishes. + using Callback = std::function; + }; + + /// EndFlight bundles up types to ease interaction with + /// Flights::end_flight. + struct EndFlight { + /// Parameters bundles up input parameters. + struct Parameters { + std::string authorization; ///< Authorization token obtained by logging in to the AirMap services. + Flight::Id id; ///< Id of the flight that should be ended. + }; + + /// Response models the response from the AirMap services. + struct Response { + DateTime end_time; ///< Point in time when the flight was ended. + }; + + /// Result models the outcome of calling Flights::delete_flight. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Flights::end_flight finishes. + using Callback = std::function; + }; + + /// StartFlightCommunications bundles up types to ease interaction with + /// Flights::start_flight_communications. + struct StartFlightCommunications { + /// Parameters bundles up input parameters. + struct Parameters { + std::string authorization; ///< Authorization token obtained by logging in to the AirMap services. + Flight::Id id; ///< Id of the flight for which flight comms should be started. + }; + + /// Response models the response from the AirMap services. + struct Response { + std::string key; ///< The encryption key that should be used to encrypt individual telemetry updates. + }; + + /// Result models the outcome of calling Flights::start_flight_communications. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Flights::start_flight_communications. + using Callback = std::function; + }; + + /// EndFlightCommunications bundles up types to ease interaction with + /// Flights::end_flight_communications. + struct EndFlightCommunications { + /// Parameters bundles up input parameters. + struct Parameters { + std::string authorization; ///< Authorization token obtained by logging in to the AirMap services. + Flight::Id id; ///< Id of the flight for which flight comms should be ended. + }; + + /// Response models the response from the AirMap services. + struct Response {}; + + /// Result models the outcome of calling Flights::end_flight_communications. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Flights::end_flight_communications finishes. + using Callback = std::function; + }; + + /// search queries the AirMap services for known flights + /// and reports results to 'cb'. + virtual void search(const Search::Parameters& parameters, const Search::Callback& cb) = 0; + + /// for_ids queries the AirMap services for detailed information about + /// flights identified by UUIDs and reports back results to 'cb'. + virtual void for_id(const ForId::Parameters& parameters, const ForId::Callback& cb) = 0; + + /// create_flight creates a flight for 'parameters' and reports + /// results back to 'cb'. + virtual void create_flight_by_point(const CreateFlight::Parameters& parameters, const CreateFlight::Callback& cb) = 0; + + /// create_flight creates a flight for 'parameters' and reports + /// results back to 'cb'. + virtual void create_flight_by_path(const CreateFlight::Parameters& parameters, const CreateFlight::Callback& cb) = 0; + + /// create_flight creates a flight for 'parameters' and reports + /// results back to 'cb'. + virtual void create_flight_by_polygon(const CreateFlight::Parameters& parameters, + const CreateFlight::Callback& cb) = 0; + + /// end_flight finalizes a flight identified by 'parameters' and reports + /// results back to 'cb'. + virtual void end_flight(const EndFlight::Parameters& parameters, const EndFlight::Callback& cb) = 0; + + /// delete_flight deletes a flight identified by 'parameters' and reports + /// results back to 'cb'. + virtual void delete_flight(const DeleteFlight::Parameters& parameters, const DeleteFlight::Callback& cb) = 0; + + /// start_flight_communications enables communications for a specific flight + /// instance and reports results back to 'cb'. + virtual void start_flight_communications(const StartFlightCommunications::Parameters& parameters, + const StartFlightCommunications::Callback& cb) = 0; + + /// end_flight_communications enables communications for a specific flight + /// instance and reports results back to cb. + virtual void end_flight_communications(const EndFlightCommunications::Parameters& parameters, + const EndFlightCommunications::Callback& cb) = 0; + + protected: + /// @cond + Flights() = default; + /// @endcond +}; + +} // namespace airmap + +#endif // AIRMAP_AIRSPACES_H_ diff --git a/libs/airmapd/include/airmap/geometry.h b/libs/airmapd/include/airmap/geometry.h new file mode 100644 index 0000000000000000000000000000000000000000..51e075cd51afda9904059d0479f3d370560fddac --- /dev/null +++ b/libs/airmapd/include/airmap/geometry.h @@ -0,0 +1,146 @@ +#ifndef AIRMAP_GEOMETRY_H_ +#define AIRMAP_GEOMETRY_H_ + +#include + +#include + +namespace airmap { + +/// Geometry bundles up different types of geometries. +class Geometry { + public: + /// Type enumerates all known geometry types. + enum class Type { + invalid, ///< Marks an invalid geometry. + point, ///< Geometry contains a Point. + multi_point, ///< Geometry contains a MultiPoint. + line_string, ///< Geometry contains a LineString. + multi_line_string, ///< Geometry contains a MultiLineString. + polygon, ///< Geometry contains a Polygon. + multi_polygon, ///< Geometry contains a MultiPolygon. + geometry_collection ///< Geometry is a GemetryCollection. + }; + + /// Coordinate marks a point in 3-dimensional space. + struct Coordinate { + double latitude; /// The latitude component of this coordinate in [°]. + double longitude; /// The longitude component of this coordinate in [°]. + Optional altitude; /// The altitude component of this coordinate in [m]. + Optional elevation; + }; + + /// CoordinateVector is a collection of points in 3-dimensional space. + template + struct CoordinateVector { + std::vector coordinates; ///< The individual coordinates. + }; + + using Point = Coordinate; + using MultiPoint = CoordinateVector; + using LineString = CoordinateVector; + using MultiLineString = std::vector; + /// Polygon follows the GeoJSON standard, citing from https://tools.ietf.org/html/rfc7946: + /// * For type "Polygon", the "coordinates" member MUST be an array of + /// linear ring coordinate arrays. + /// * For Polygons with more than one of these rings, the first MUST be + /// the exterior ring, and any others MUST be interior rings. The + /// exterior ring bounds the surface, and the interior rings (if + /// present) bound holes within the surface. + struct Polygon { + CoordinateVector outer_ring; + std::vector> inner_rings; + }; + using MultiPolygon = std::vector; + using GeometryCollection = std::vector; + + /// point returns a Geometry instance with Type::point at the given coordinate (lat, lon). + static Geometry point(double lat, double lon); + /// polygon returns a Geometry instance with Type::polygon with the given 'coordinates'. + static Geometry polygon(const std::vector& coordinates); + + /// Initializes a new instance with Type::invalid. + Geometry(); + /// Geometry initializes a new instance with the given Point. + explicit Geometry(const Point& other); + /// Geometry initializes a new instance with the given MultiPoint. + explicit Geometry(const MultiPoint& other); + /// Geometry initializes a new instance with the given LineString. + explicit Geometry(const LineString& other); + /// Geometry initializes a new instance with the given MultiLineString. + explicit Geometry(const MultiLineString& other); + /// Geometry initializes a new instance with the given Polyon. + explicit Geometry(const Polygon& other); + /// Geometry initializes a new instance with the given MultiPolygon. + explicit Geometry(const MultiPolygon& other); + /// Geometry initializes a new instance with the given GeometryCollection. + explicit Geometry(const GeometryCollection& other); + /// @cond + Geometry(const Geometry& other); + ~Geometry(); + Geometry& operator=(const Geometry& rhs); + bool operator==(const Geometry& rhs) const; + /// @endcond + + /// type returns the Type of the geometry. + Type type() const; + /// details_for_point returns an immutable instance to the contained Point instance. + const Point& details_for_point() const; + /// details_for_multi_point returns an immutable instance to the contained MultiPoint instance. + const MultiPoint& details_for_multi_point() const; + /// details_for_line_string returns an immutable instance to the contained LineString instance. + const LineString& details_for_line_string() const; + /// details_for_multi_line_string returns an immutable instance to the contained MultiLineString instance. + const MultiLineString& details_for_multi_line_string() const; + /// details_for_polygon returns an immutable instance to the contained Polygon instance. + const Polygon& details_for_polygon() const; + /// details_for_multi_polygon returns an immutable instance to the contained MultiPolygon instance. + const MultiPolygon& details_for_multi_polygon() const; + /// details_for_geometry_collection returns an immutable instance to the contained GeometryCollection instance. + const GeometryCollection details_for_geometry_collection() const; + + private: + struct Invalid {}; + + union Data { + Data(); + ~Data(); + + Invalid invalid; + Point point; + MultiPoint multi_point; + LineString line_string; + MultiLineString multi_line_string; + Polygon polygon; + MultiPolygon multi_polygon; + GeometryCollection geometry_collection; + }; + + Geometry& reset(); + void set_point(const Point& point); + void set_multi_point(const MultiPoint& multi_point); + void set_line_string(const LineString& line_string); + void set_multi_line_string(const MultiLineString& multi_line_string); + void set_polygon(const Polygon& polygon); + void set_multi_polygon(const MultiPolygon& multi_polygon); + void set_geometry_collection(const GeometryCollection& geometry_collection); + Geometry& set_geometry(const Geometry& other); + + Type type_; + Data data_; +}; + +/// @cond +bool operator==(const Geometry::Coordinate& lhs, const Geometry::Coordinate& rhs); + +bool operator==(const Geometry::Polygon& lhs, const Geometry::Polygon& rhs); + +template +bool operator==(const Geometry::CoordinateVector& lhs, const Geometry::CoordinateVector& rhs) { + return lhs.coordinates == rhs.coordinates; +} +/// @endcond + +} // namespace airmap + +#endif // AIRMAP_GEOMETRY_H_ diff --git a/libs/airmapd/include/airmap/logger.h b/libs/airmapd/include/airmap/logger.h new file mode 100644 index 0000000000000000000000000000000000000000..1d88bd3ad35d336d916edd0a02c7979bf8b99e95 --- /dev/null +++ b/libs/airmapd/include/airmap/logger.h @@ -0,0 +1,63 @@ +#ifndef AIRMAP_LOGGER_H_ +#define AIRMAP_LOGGER_H_ + +#include + +#include +#include + +namespace airmap { + +/// Logger abstracts logging of human-readable message +/// providing details on the operation of the system. +class Logger : DoNotCopyOrMove { + public: + /// Severity enumerates all known levels of severity + enum class Severity { debug = 0, info = 1, error = 2 }; + + /// debug logs a message from component with Severity::debug. + void debug(const char* message, const char* component); + + /// info logs a message from component with Severity::info. + void info(const char* message, const char* component); + + /// error logs a message from component with Severity::error. + void error(const char* message, const char* component); + + /// log handles the incoming log message originating from component. + /// Implementation should handle the case of component being a nullptr + /// gracefully. + virtual void log(Severity severity, const char* message, const char* component) = 0; + + /// should_log should return true if 'message' with 'severity' originating from + /// 'component' should be logged. + /// + /// Implementations should handle the case of either message or component being nullptr + /// gracefully. + virtual bool should_log(Severity severity, const char* message, const char* component) = 0; + + protected: + Logger() = default; +}; + +/// operator< returns true iff the numeric value of lhs < rhs. +bool operator<(Logger::Severity lhs, Logger::Severity rhs); + +/// operator>> parses severity from in. +std::istream& operator>>(std::istream& in, Logger::Severity& severity); + +/// create_default_logger returns a Logger implementation writing +/// log messages to 'out'. +std::shared_ptr create_default_logger(std::ostream& out = std::cerr); + +/// create_filtering_logger returns a logger that filters out log entries +/// with a severity smaller than the configurated severity. +std::shared_ptr create_filtering_logger(Logger::Severity severity, const std::shared_ptr& logger); + +/// create_null_logger returns a logger that does the equivalent of +/// > /dev/null. +std::shared_ptr create_null_logger(); + +} // namespace airmap + +#endif // AIRMAP_LOGGER_H_ \ No newline at end of file diff --git a/libs/airmapd/include/airmap/monitor/README.md b/libs/airmapd/include/airmap/monitor/README.md new file mode 100644 index 0000000000000000000000000000000000000000..567f071d3428d30a7f657e750d9810ddb55cba13 --- /dev/null +++ b/libs/airmapd/include/airmap/monitor/README.md @@ -0,0 +1,41 @@ +# AirMap Monitor Service {#monitord} + +The AirMap monitor service monitors the state of vehicles and takes +action based on changes to this state: + + - when the state of a vehicle changes to active: + - create a flight with the AirMap services + - start flight communications + - transmit telemetry updates to AirMap + - receive updates for manned and unmanned aerial traffic relevant to a flight + - when the state of a vehicle changes to inactive: + - stop flight communications + - end flight + +The daemon exposes its functionality via a gRPC interface (see +`${AIRMAPD_ROOT}/interfaces/grpc/airmap/monitor/monitor.proto`). Client +applications can either rely on the C++-API avaiable in +`${AIRMAPD_ROOT}/include/airmap/monitor/client.h` or rely on the gRPC +ecosystem to easily connect to the daemon in their choice of language +and runtime. Please see `${AIRMAPD_ROOT/examples/monitor/client.cpp` for +an example of using the C++-API. The following diagram summarizes the overall setup: + +![monitord](doc/images/monitord.png) + +# Service Configuration + +The service is executed with the following command: + +``` +$ airmap daemon +``` + +Please note that the service needs exactly one MavLink endpoint to be configured on the command line. +The following endpoint types are supported: + - TCP: Provide `--tcp-endpoint-ip=IP` and `--tcp-endpoint-port=PORT` to `airmap daemon`. + - UDP: Provide `--udp-endpoint-port=PORT` to `airmap daemon`. + - Serial: Provide `--serial-device=PATH/TO/DEVICE` to `airmap daemon`. + +The gRPC endpoint exported by the service can be specified with `--grpc-endpoint=ENDPOINT`, defaulting to `0.0.0.0:9090`. +The overall daemon configuration for accessing the AirMap services can be specified with `--config-file=PATH/TO/CONFIG/FILE`. +By default, the config file is expected in `~/.config/airmap/production/config.json`. \ No newline at end of file diff --git a/libs/airmapd/include/airmap/monitor/client.h b/libs/airmapd/include/airmap/monitor/client.h new file mode 100644 index 0000000000000000000000000000000000000000..3d7bc7ca355c6bc2944d995fa09f07bd198a0f74 --- /dev/null +++ b/libs/airmapd/include/airmap/monitor/client.h @@ -0,0 +1,72 @@ +#ifndef AIRMAP_MONITOR_CLIENT_H_ +#define AIRMAP_MONITOR_CLIENT_H_ + +#include +#include +#include + +#include +#include + +namespace airmap { +/// namespace monitor bundles up types and functions to +/// interact with the AirMap monitor daemon. +namespace monitor { + +/// Client provides access to the AirMap monitor service. +class Client : DoNotCopyOrMove { + public: + /// Configuration bundles up creation-time parameters of a Client. + struct Configuration { + std::string endpoint; ///< The remote endpoint hosting the service. + std::shared_ptr logger; ///< The logger instance. + }; + + /// Updates models updates delivered to clients. + struct Update { + std::vector traffic; ///< Traffic updates. + }; + + /// UpdateStream abstracts a source of incoming updates. + class UpdateStream : DoNotCopyOrMove { + public: + /// Reveiver models an entity interested in receiving updates. + class Receiver : DoNotCopyOrMove { + public: + /// handle_update is invoked for every update sent out by the service. + virtual void handle_update(const Update& update) = 0; + }; + + /// subscribe connects 'receiver' to the stream of updates. + virtual void subscribe(const std::shared_ptr& receiver) = 0; + + /// unsubscribe disconnects 'receiver' from the stream of updates. + virtual void unsubscribe(const std::shared_ptr& receiver) = 0; + + protected: + UpdateStream() = default; + }; + + /// ConnectToUpdates bundles up types for calls to Client::connect_to_updates. + struct ConnectToUpdates { + /// Result models the outcome of calling Client::connect_to_updates. + using Result = Outcome, Error>; + /// Callback models the async receiver for a call to Client::connect_to_updates. + using Callback = std::function; + }; + + /// connect_to_updates connects to incoming updates. + virtual void connect_to_updates(const ConnectToUpdates::Callback& cb) = 0; + + protected: + Client() = default; +}; + +} // namespace monitor +} // namespace airmap + +/// @example monitor/client.cpp +/// Illustrates how to use airmap::monitor::Client to connect +/// to an AirMap monitor instance. + +#endif // AIRMAP_MONITOR_CLIENT_H_ \ No newline at end of file diff --git a/libs/airmapd/include/airmap/optional.h b/libs/airmapd/include/airmap/optional.h new file mode 100644 index 0000000000000000000000000000000000000000..f66f6f6fc00cc5faacb5ccf0313b23d13185260d --- /dev/null +++ b/libs/airmapd/include/airmap/optional.h @@ -0,0 +1,138 @@ +#ifndef AIRMAP_OPTIONAL_H_ +#define AIRMAP_OPTIONAL_H_ + +#include +#include + +namespace airmap { + +/// Optional manages an optional contained value of type T. +template +class Optional { + public: + /// Optional initializes a new instance with no contained value. + Optional() : has_value{false} { + } + + /// Optional initializes a new instance with 'other'. + Optional(const Optional& other) : has_value{other.has_value} { + if (has_value) + new (&storage.value) T(other.storage.value); + } + + /// Optional initializes a new instance with 'other'. + Optional(Optional&& other) : has_value{other.has_value} { + if (has_value) + new (&storage.value) T(other.storage.value); + } + + /// Optional initializes a new instance with 'value'. + Optional(const T& value) : has_value{true} { + new (&storage.value) T(value); + } + + /// Optional initializes a new instance with 'value'. + Optional(T&& value) : has_value{true} { + new (&storage.value) T(value); + } + + /// ~Optional cleans up the instance and calls the destructor + /// of the contained value if one is set. + ~Optional() { + reset(); + } + + /// @cond + Optional& operator=(const Optional& rhs) { + if (rhs.has_value) + set(rhs.storage.value); + else + reset(); + + return *this; + } + + Optional& operator=(const T& rhs) { + set(rhs); + return *this; + } + + Optional& operator=(Optional&& rhs) { + if (rhs.has_value) + set(rhs.storage.value); + else + reset(); + + return *this; + } + + bool operator==(const Optional& rhs) const { + if (has_value != rhs.has_value) + return false; + + return has_value && (storage.value == rhs.storage.value); + } + /// @endcond + + /// operator bool returns true if this instance contains a value. + explicit operator bool() const { + return has_value; + } + + /// get returns an immutable reference to the contained value. + /// If no value is contained in this instance, the result of the call is undefined. + const T& get() const { + return storage.value; + } + + /// get returns an immutable reference to the contained value. + /// If no value is contained in this instance, the result of the call is undefined. + T& get() { + return storage.value; + } + + /// set adjusts the contained value to 'value'. + void set(const T& value) { + reset(); + + has_value = true; + new (&storage.value) T(value); + } + + /// reset frees up any contained value if one is set. + /// After this call has completed, no value is contained in this Optional instance. + void reset() { + if (has_value) + (&storage.value)->~T(); + has_value = false; + } + + private: + bool has_value; + union Storage { + Storage() { + } + ~Storage() { + } + T value; + } storage; +}; + +/// operator<< inserts value into out. +template +inline std::ostream& operator<<(std::ostream& out, const Optional& value) { + if (value) + out << value.get(); + else + out << "not set"; + return out; +} + +/// @cond +template +using Required = Optional; +/// @endcond + +} // namespace airmap + +#endif // AIRMAP_OPTIONAL_H_ diff --git a/libs/airmapd/include/airmap/outcome.h b/libs/airmapd/include/airmap/outcome.h new file mode 100644 index 0000000000000000000000000000000000000000..1b62d27e1a615df603a1dc8fb94cca414ad97002 --- /dev/null +++ b/libs/airmapd/include/airmap/outcome.h @@ -0,0 +1,170 @@ +#ifndef AIRMAP_OUTCOME_H_ +#define AIRMAP_OUTCOME_H_ + +#include + +namespace airmap { + +/// Outcome models a return value from a function XOR an error object +/// describing the error condition if no value can be returned. +template +class Outcome { + public: + /// @cond + static_assert(not std::is_same::value, "Value and Error must not be the same type"); + static_assert(std::is_copy_constructible::value && std::is_move_constructible::value, + "Value must be copy- and move-constructible"); + static_assert(std::is_copy_constructible::value && std::is_move_constructible::value, + "Error must be copy- and move-constructible"); + /// @endcond + + /// Outcome initializes a new instance with value. + explicit Outcome(const Value& value) : type{Type::value} { + new (&data.value) Value{value}; + } + + /// Outcome initializes a new instance with error. + explicit Outcome(const Error& error) : type{Type::error} { + new (&data.error) Error{error}; + } + + /// Outcome initializes a new instance with the value or error of 'other'. + Outcome(const Outcome& other) : type{other.type} { + switch (type) { + case Type::error: + new (&data.error) Error{other.data.error}; + break; + case Type::value: + new (&data.value) Value{other.data.value}; + break; + } + } + + /// Outcome initializes a new instance with the value or error of 'other'. + Outcome(Outcome&& other) : type{other.type} { + switch (type) { + case Type::error: + new (&data.error) Error{other.data.error}; + break; + case Type::value: + new (&data.value) Value{other.data.value}; + break; + } + } + + /// Outcome assigns the value or error contained in 'other' to this instance. + Outcome& operator=(const Outcome& other) { + switch (type) { + case Type::error: { + (&data.error)->~Error(); + break; + } + case Type::value: { + (&data.value)->~Value(); + break; + } + } + + type = other.type; + + switch (type) { + case Type::error: { + new (&data.error) Error{other.data.error}; + break; + } + case Type::value: { + new (&data.value) Value{other.data.value}; + break; + } + } + + return *this; + } + + /// Outcome assigns the value or error contained in 'other' to this instance. + Outcome& operator=(Outcome&& other) { + switch (type) { + case Type::error: { + (&data.error)->~Error(); + break; + } + case Type::value: { + (&data.value)->~Value(); + break; + } + } + + type = other.type; + + switch (type) { + case Type::error: { + new (&data.error) Error{other.data.error}; + break; + } + case Type::value: { + new (&data.value) Value{other.data.value}; + break; + } + } + + return *this; + } + + /// ~Outcome frees up the contained error or value contained in this instance. + ~Outcome() { + switch (type) { + case Type::error: + data.error.~Error(); + break; + case Type::value: + data.value.~Value(); + break; + } + } + + /// operator bool returns true if a value is contained in this instance. + explicit operator bool() const { + return !has_error(); + } + + /// has_error returns true if this instance carries an error. + inline bool has_error() const { + return type == Type::error; + } + + /// has_value returns true if this instance carries a value. + inline bool has_value() const { + return type == Type::value; + } + + /// error returns an immutable reference to the Error contained in this instance. + /// The result of this call is undefined if has_error() returns false. + inline const Error& error() const { + return data.error; + } + + /// value returns an immutable reference to the Value contained in this instance. + /// The result of this call is undefined if has_value() returns false. + inline const Value& value() const { + return data.value; + } + + private: + enum class Type { value, error }; + + Type type; + union Data { + Data() : value{} { + } + + ~Data() { + } + + Value value; + Error error; + } data; +}; + +} // namespace airmap + +#endif // AIRMAP_OUTCOME_H_ diff --git a/libs/airmapd/include/airmap/pilot.h b/libs/airmapd/include/airmap/pilot.h new file mode 100644 index 0000000000000000000000000000000000000000..9948f59c60aae0c49fd04f44dca1fdbfc88a6700 --- /dev/null +++ b/libs/airmapd/include/airmap/pilot.h @@ -0,0 +1,61 @@ +#ifndef AIRMAP_PILOT_H_ +#define AIRMAP_PILOT_H_ + +#include +#include +#include + +#include + +#include +#include + +namespace airmap { + +/// Pilot bundles up all properties describing a pilot on the AirMap services. +struct Pilot { + /// Aircraft describes a vehicle owned by a Pilot. + struct Aircraft { + std::string id; ///< The unique id of the vehicle in the context of AirMap. + std::string nick_name; ///< The human-readable nickname of the vehicle. + airmap::Aircraft model; ///< The model of the aircraft. + DateTime created_at; ///< Timestamp marking the creation of the device in the AirMap system. + }; + + std::string id; ///< The unique id of the pilot in the context of AirMap. + std::string first_name; ///< The first name of the pilot. + std::string last_name; ///< The last name of the pilot. + std::string user_name; ///< The AirMap username of this pilot. + Optional picture_url; ///< The URL of a picture showing the pilot. + + /// VerificationStatus summarizes the + /// status of contact detail verification. + struct VerificationStatus { + bool email; ///< true iff the email address of the pilot has been verified + bool phone; ///< true iff the phone number of the pilot has been verified + } verification_status; + + /// Statistics about the pilot and her + /// flight experience as recorded by the + /// AirMap services. + struct Statistics { + struct Flight { + std::uint64_t total; ///< The total number of flights + DateTime last_flight_time; ///< Date and time of the last flight + } flight; ///< Statistical details about flights conducted by a pilot. + struct Aircraft { + std::uint64_t total; ///< The total number of aircrafts + } aircraft; ///< Statistical details about aircrafts owned by a pilot + } statistics; + + /// App- and user-specific metadata. + struct Metadata { + std::map app; ///< App-specific meta-data. + std::map user; ///< User-specific meta-data. + } metadata; ///< Metadata associated with a pilot. + DateTime created_at; ///< Timestamp of the creation of this pilot in the AirMap system. +}; + +} // namespace airmap + +#endif // AIRMAP_PILOT_H_ diff --git a/libs/airmapd/include/airmap/pilots.h b/libs/airmapd/include/airmap/pilots.h new file mode 100644 index 0000000000000000000000000000000000000000..a9ae9b8b927a5e7ca710677c25ed5b0bdc1e1bcc --- /dev/null +++ b/libs/airmapd/include/airmap/pilots.h @@ -0,0 +1,241 @@ +#ifndef AIRMAP_PILOTS_H_ +#define AIRMAP_PILOTS_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace airmap { + +/// Pilots provides functionality to manage (the authorized) pilot. +class Pilots : DoNotCopyOrMove { + public: + /// Exclude enumerates fields that can be excluded when querying pilot and aircraft properties. + enum class Exclude { + aircraft = 1 << 0, ///< Exclude aircraft data from results. + user_metadata = 1 << 1, ///< Exclude user-specific metadata from results. + app_metadata = 1 << 2, ///< Exclude app-specific metadata from results. + authorized_apps = 1 << 3 ///< Exclude list of authorized apps from results. + }; + + /// Authenticated bundles up types to ease interaction + /// with Pilots::authenticated. + struct Authenticated { + /// Parameters bundles up input parameters. + struct Parameters { + std::string authorization; ///< Authorization token obtained by logging in to the AirMap services. + Optional exclude{}; ///< Exclude these fields from results. + bool retrieve_statistics{false}; ///< If true, statistics about flights and aircrafts are requested. + }; + + /// Result models the outcome of calling Pilots::authenticated. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Pilots::authenticated finishes. + using Callback = std::function; + }; + + /// ForId bundles up types to ease interaction + /// with Pilots::for_id. + struct ForId { + /// Parameters bundles up input parameters. + struct Parameters { + std::string authorization; ///< Authorization token obtained by logging in to the AirMap services. + std::string id; ///< Searches for the specific pilot with this id. + Optional exclude{}; ///< Exclude these fields from results. + bool retrieve_statistics{false}; ///< If true, statistics about flights and aircrafts are requested. + }; + + /// Result models the outcome of calling Pilots::for_id. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Pilots::for_id finishes. + using Callback = std::function; + }; + + /// UpdateForId bundles up types to ease interaction + /// with Pilots::update_for_id. + struct UpdateForId { + /// Parameters bundles up input parameters. + struct Parameters { + std::string authorization; ///< Authorization token obtained by logging in to the AirMap services. + std::string id; ///< Updates the specific pilot with this id. + std::string first_name; ///< The first name of the pilot. + std::string last_name; ///< The last name of the pilot. + std::string user_name; ///< The AirMap username of this pilot. + std::string phone; ///< The phone number of the pilot. + std::map app_metadata; ///< App-specific metadata associated to the pilot. + std::map user_metadata; ///< User-specific metadata associated to the pilot. + }; + + /// Result models the outcome of calling Pilots::update_for_id. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Pilots::update_for_id finishes. + using Callback = std::function; + }; + + /// StartVerifyPilotPhoneForId bundles up types to ease interaction + /// with Pilots::start_verify_pilot_phone_for_id. + struct StartVerifyPilotPhoneForId { + /// Parameters bundles up input parameters. + struct Parameters { + std::string authorization; ///< Authorization token obtained by logging in to the AirMap services. + std::string id; ///< Verifies the phone number for the pilot with this id. + }; + + struct Empty {}; + + /// Result models the outcome of calling Pilots::start_verify_pilot_phone_for_id. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Pilots::start_verify_pilot_phone_for_id finishes. + using Callback = std::function; + }; + + /// FinishVerifyPilotPhoneForId bundles up types to ease interaction + /// with Pilots::finish_verify_pilot_phone_for_id. + struct FinishVerifyPilotPhoneForId { + /// Parameters bundles up input parameters. + struct Parameters { + std::string authorization; ///< Authorization token obtained by logging in to the AirMap services. + std::string id; ///< Verifies the phone number for the pilot with this id. + std::uint32_t token; ///< The token that was received on the pilot's phone. + }; + + struct Empty {}; + + /// Result models the outcome of calling Pilots::finish_verify_pilot_phone_for_id. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Pilots::finish_verify_pilot_phone_for_id finishes. + using Callback = std::function; + }; + + /// Aircrafts bundles up types to ease interaction + /// with Pilots::aircrafts. + struct Aircrafts { + /// Parameters bundles up input parameters. + struct Parameters { + std::string authorization; ///< Authorization token obtained by logging in to the AirMap services. + std::string id; ///< Lists all aircrafts owned by the pilot with this id. + }; + + /// Result models the outcome of calling Pilots::aircrafts. + using Result = Outcome, Error>; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Pilots::aircrafts finishes. + using Callback = std::function; + }; + + /// AddAircraft bundles up types to ease interaction + /// with Pilots::add_aircraft. + struct AddAircraft { + /// Parameters bundles up input parameters. + struct Parameters { + std::string authorization; ///< Authorization token obtained by logging in to the AirMap services. + std::string id; ///< Adds an aircraft for the pilot with this id. + std::string model_id; ///< The id of the model of the aircraft. + std::string nick_name; ///< The nickname of the aircraft. + }; + + /// Result models the outcome of calling Pilots::add_aircraft. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Pilots::add_aircraft finishes. + using Callback = std::function; + }; + + /// DeleteAircraft bundles up types to ease interaction + /// with Pilots::delete_aircraft. + struct DeleteAircraft { + /// Parameters bundles up input parameters. + struct Parameters { + std::string authorization; ///< Authorization token obtained by logging in to the AirMap services. + std::string id; ///< Deletes an aircraft for the pilot with this id. + std::string aircraft_id; ///< Deletes the specific aircraft with this id. + }; + + struct Empty {}; + + /// Result models the outcome of calling Pilots::delete_aircraft. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Pilots::delete_aircraft finishes. + using Callback = std::function; + }; + + /// UpdateAircraft bundles up types to ease interaction + /// with Pilots::update_aircraft. + struct UpdateAircraft { + /// Parameters bundles up input parameters. + struct Parameters { + std::string authorization; ///< Authorization token obtained by logging in to the AirMap services. + std::string id; ///< Updates an aircraft for the pilot with this id. + std::string aircraft_id; ///< Update the specific aircraft with this id. + std::string nick_name; ///< The new nick name for the aircraft. + }; + + struct Empty {}; + /// Result models the outcome of calling Pilots::update_aircraft. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Pilots::update_aircraft finishes. + using Callback = std::function; + }; + + /// current_user queries the AirMap services for the pilot profile + /// connected to the authenticated user, reporting results to 'cb'. + virtual void authenticated(const Authenticated::Parameters& parameters, const Authenticated::Callback& cb) = 0; + + /// for_id queries the AirMap services for the pilot profile + /// with a given id, reporting results to 'cb'. + virtual void for_id(const ForId::Parameters& parameters, const ForId::Callback& cb) = 0; + + /// update_for_id updates the pilot profile specified + // by Parameters::id, reporting results to 'cb'. + virtual void update_for_id(const UpdateForId::Parameters& parameters, const UpdateForId::Callback& cb) = 0; + + /// start_verify_pilot_phone_for_id sends a verification token to the phone + /// number stored in the pilot profile, reporting results to 'cb'. + virtual void start_verify_pilot_phone_for_id(const StartVerifyPilotPhoneForId::Parameters& parameters, + const StartVerifyPilotPhoneForId::Callback& cb) = 0; + + /// finish_verify_pilot_phone_for_id responds to a verification request by + /// sending back the token sent to the pilot's phone, reporting results to 'cb'. + virtual void finish_verify_pilot_phone_for_id(const FinishVerifyPilotPhoneForId::Parameters& parameters, + const FinishVerifyPilotPhoneForId::Callback& cb) = 0; + + /// aircrafts queries the list of aircrafts owned by a pilot, reporting results to 'cb'. + virtual void aircrafts(const Aircrafts::Parameters& parameters, const Aircrafts::Callback& cb) = 0; + + /// add_aircraft associates a new aircraft with a pilot, reporting results to 'cb'. + virtual void add_aircraft(const AddAircraft::Parameters& parameters, const AddAircraft::Callback& cb) = 0; + + /// delete_aircraft removes an aircraft from a pilot profile, reporting results to 'cb'. + virtual void delete_aircraft(const DeleteAircraft::Parameters& parameters, const DeleteAircraft::Callback& cb) = 0; + + /// update_aircraft updates the properties of an aircraft associated with a pilot, reporting results to 'cb'. + virtual void update_aircraft(const UpdateAircraft::Parameters& parameters, const UpdateAircraft::Callback& cb) = 0; + + protected: + Pilots() = default; +}; + +/// @cond +Pilots::Exclude operator|(Pilots::Exclude, Pilots::Exclude); +Pilots::Exclude operator&(Pilots::Exclude, Pilots::Exclude); +std::ostream& operator<<(std::ostream& out, Pilots::Exclude exclude); +/// @endcond + +} // namespace airmap + +#endif // AIRMAP_PILOTS_H_ diff --git a/libs/airmapd/include/airmap/qt/client.h b/libs/airmapd/include/airmap/qt/client.h new file mode 100644 index 0000000000000000000000000000000000000000..913c8552cdbc7ccd76c8348587de90939297ee1e --- /dev/null +++ b/libs/airmapd/include/airmap/qt/client.h @@ -0,0 +1,63 @@ +#ifndef AIRMAP_QT_CLIENT_H_ +#define AIRMAP_QT_CLIENT_H_ + +#include +#include +#include +#include +#include + +#include + +namespace airmap { +/// @namespace namespace qt bundles up types and functions that help with integrating AirMap functionality +/// into Qt-based applications and libraries. +namespace qt { + +/// Client implements the airmap::Client interface, bridging over between +/// the Qt event loop and the native event loop of the native airmap::Client. +/// +/// All callback invocations that might happen in the context of a Client instance +/// are dispatched to the Qt applications' main thread. +class Client : public QObject, public airmap::Client { + public: + using CreateResult = Outcome; + using CreateCallback = std::function; + + /// create creates a new Client instance with parent 'parent', logging to 'logger', using the config + /// 'configuration'. The result of the request is reported to 'cb', on the thread that issued the create request. + /// + /// Please note that this function must be called on Qt's main thread as event dispatching between different + /// event loops to the Qt world is set up here. + static void create(const Client::Configuration& configuration, const std::shared_ptr& logger, QObject* parent, + const CreateCallback& cb); + + ~Client(); + + // From airmap::Client + Authenticator& authenticator() override; + Aircrafts& aircrafts() override; + Airspaces& airspaces() override; + FlightPlans& flight_plans() override; + Flights& flights() override; + Pilots& pilots() override; + RuleSets& rulesets() override; + Status& status() override; + Telemetry& telemetry() override; + Traffic& traffic() override; + + private: + /// @cond + struct Private; + Client(std::unique_ptr&& d, QObject* parent); + std::unique_ptr d_; + /// @endcond +}; + +} // namespace qt +} // namespace airmap + +/// @example qt/client.cpp +/// Illustrates how to use airmap::qt::Client, airmap::qt::DispatchingLogger and airmap::qt::Logger. + +#endif // AIRMAP_QT_CLIENT_H_ diff --git a/libs/airmapd/include/airmap/qt/logger.h b/libs/airmapd/include/airmap/qt/logger.h new file mode 100644 index 0000000000000000000000000000000000000000..c06e084faa4ebd182965caf8b112cf91c71dfdf9 --- /dev/null +++ b/libs/airmapd/include/airmap/qt/logger.h @@ -0,0 +1,56 @@ +#ifndef AIRMAP_QT_LOGGER_H_ +#define AIRMAP_QT_LOGGER_H_ + +#include + +#include + +#include + +namespace airmap { +namespace qt { + +/// Logger is an airmap::Logger implementation that uses to +/// Qt's logging facilities. +class Logger : public airmap::Logger { + public: + /// logging_category returns a QLoggingCategory instance + /// that enables calling code to fine-tune logging behavior of a Logger instance. + QLoggingCategory& logging_category(); + + /// Logger initializes a new instance. + Logger(); + /// ~Logger cleans up all resources held by a Logger instance. + ~Logger(); + + // From airmap::Logger + void log(Severity severity, const char* message, const char* component) override; + bool should_log(Severity severity, const char* message, const char* component) override; + + private: + struct Private; + std::unique_ptr d_; +}; + +/// DispatchingLogger is an airmap::Logger implementation that dispatches to Qt's main +/// event loop for logger invocation +class DispatchingLogger : public airmap::Logger { + public: + /// DispatchingLogger initializes a new instance with 'next'. + DispatchingLogger(const std::shared_ptr& next); + /// ~DispatchingLogging cleans up all resources held a DispatchingLogger instance. + ~DispatchingLogger(); + + // From airmap::Logger + void log(Severity severity, const char* message, const char* component) override; + bool should_log(Severity severity, const char* message, const char* component) override; + + private: + struct Private; + std::unique_ptr d_; +}; + +} // namespace qt +} // namespace airmap + +#endif // AIRMAP_QT_LOGGER_H_ diff --git a/libs/airmapd/include/airmap/qt/types.h b/libs/airmapd/include/airmap/qt/types.h new file mode 100644 index 0000000000000000000000000000000000000000..db789b65c61b6847cd6cdad4ef0a619e948b0f19 --- /dev/null +++ b/libs/airmapd/include/airmap/qt/types.h @@ -0,0 +1,66 @@ +#ifndef AIRMAP_QT_TYPES_H_ +#define AIRMAP_QT_TYPES_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +Q_DECLARE_METATYPE(airmap::Aircraft) +Q_DECLARE_METATYPE(airmap::Airspace) +Q_DECLARE_METATYPE(airmap::Credentials) +Q_DECLARE_METATYPE(airmap::DateTime) +Q_DECLARE_METATYPE(airmap::Error) +Q_DECLARE_METATYPE(airmap::FlightPlan) +Q_DECLARE_METATYPE(airmap::Flight) +Q_DECLARE_METATYPE(airmap::Geometry) +Q_DECLARE_METATYPE(airmap::Pilot) +Q_DECLARE_METATYPE(airmap::Rule) +Q_DECLARE_METATYPE(airmap::RuleSet) +Q_DECLARE_METATYPE(airmap::RuleSet::Rule) +Q_DECLARE_METATYPE(airmap::Status::Advisory) +Q_DECLARE_METATYPE(airmap::Status::Wind) +Q_DECLARE_METATYPE(airmap::Status::Weather) +Q_DECLARE_METATYPE(airmap::Status::Report) +Q_DECLARE_METATYPE(airmap::Telemetry::Position) +Q_DECLARE_METATYPE(airmap::Telemetry::Speed) +Q_DECLARE_METATYPE(airmap::Telemetry::Attitude) +Q_DECLARE_METATYPE(airmap::Telemetry::Barometer) +Q_DECLARE_METATYPE(airmap::Optional) +Q_DECLARE_METATYPE(airmap::Token::Type) +Q_DECLARE_METATYPE(airmap::Token::Anonymous) +Q_DECLARE_METATYPE(airmap::Token::OAuth) +Q_DECLARE_METATYPE(airmap::Token::Refreshed) +Q_DECLARE_METATYPE(airmap::Token) +Q_DECLARE_METATYPE(airmap::Traffic::Update::Type) +Q_DECLARE_METATYPE(airmap::Traffic::Update) +Q_DECLARE_METATYPE(airmap::Version) + +namespace airmap { +namespace qt { + +/// register_types makes airmap::* types known to the Qt type system. +/// +/// This function has to be called at least once to be able to use airmap::* +/// types in queued signal-slot connections. +void register_types(); + +} // namespace qt +} // namespace airmap + +#endif // AIRMAP_QT_TYPES_H_ diff --git a/libs/airmapd/include/airmap/rule.h b/libs/airmapd/include/airmap/rule.h new file mode 100644 index 0000000000000000000000000000000000000000..05b95766167dd9a5ea0c5daa74e23c9fcc6c250e --- /dev/null +++ b/libs/airmapd/include/airmap/rule.h @@ -0,0 +1,23 @@ +#ifndef AIRMAP_RULE_H_ +#define AIRMAP_RULE_H_ + +#include + +namespace airmap { + +struct Rule { + // TODO(tvoss): Fill in values once schema is known. + enum class Type {}; + Type type; + std::string id; + std::string name; + std::string description; + std::string jurisdiction; + // TODO(tvoss): Add requirements here. +}; + +bool operator==(const Rule& lhs, const Rule& rhs); + +} // namespace airmap + +#endif // AIRMAP_RULE_H_ diff --git a/libs/airmapd/include/airmap/ruleset.h b/libs/airmapd/include/airmap/ruleset.h new file mode 100644 index 0000000000000000000000000000000000000000..8f6af2dec5d003af4fe228c39bb63c897bdb5107 --- /dev/null +++ b/libs/airmapd/include/airmap/ruleset.h @@ -0,0 +1,155 @@ +#ifndef AIRMAP_RULESET_H_ +#define AIRMAP_RULESET_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace airmap { + +/// RuleSet bundles together properties describing a ruleset. +struct RuleSet { + // Feature describes a flight feature modelled by a particular rule. + struct Feature; + /// Rule models the individual result of a Rule evaluation. + struct Rule { + /// Status enumerates all known status codes of a rule. + enum class Status { + unknown, ///< The status of the rule is unknown. + conflicting, ///< The rule is conflicting. + not_conflicting, ///< The rule is not conflicting, all good to go. + missing_info, ///< The evaluation requires further information. + informational ///< The rule is of informational nature. + }; + + Status status; ///< The status of the rule. + std::string short_text; ///< The human-readable short summary of the rule. + std::string description; ///< The human-readable description of the rule. + std::int32_t display_order; ///< An indicator for ordering the ruleset. + std::vector features; ///< The features modelled by the rule. + }; + + /// SelectionType enumerates all known types for a RuleSet. + enum class SelectionType { + pickone, ///< One rule from the overall set needs to be picked. + required, ///< Satisfying the RuleSet is required. + optional ///< Satisfying the RuleSet is not required. + }; + + /// Jurisdiction describes a jurisdiction in a geographical scope. + struct Jurisdiction { + /// Region enumerates all known regional scopes of a jurisdiction. + enum class Region { + national, ///< The jurisdiction applies nation-wide. + state, ///< The jurisdiction applies to a specific state. + county, ///< The jurisdiction applies to a specific county. + city, ///< The jurisdiction applies to a specific city. + local ///< The jurisdiction only applies locally. + }; + /// Id models a unique identifier for a jurisdiction in the context of AirMap. + using Id = std::uint64_t; + + Id id; ///< The unique id. + std::string name; ///< The human-readable name. + Region region; ///< The regional scope. + }; + + /// Id models a unique identifier for a briefing in the context of AirMap. + using Id = std::string; + + Id id; ///< The unique id. + SelectionType selection_type; ///< The selection type. + std::string name; ///< The human-readable name. + std::string short_name; ///< The human-readable short name. + std::string description; ///< The human readable description. + bool is_default; + Jurisdiction jurisdiction; ///< The jurisdiction. + std::vector airspace_types; ///< The layers that a RuleSet instance applies to. + std::vector rules; ///< The individual rules in the set. + + struct Feature { + enum class Type { unknown, boolean, floating_point, string }; + enum class Measurement { unknown, speed, weight, distance }; + enum class Unit { unknown, kilograms, meters, meters_per_sec }; + + class Value { + public: + Value(); + explicit Value(bool value); + explicit Value(double value); + explicit Value(const std::string& value); + Value(const Value& other); + Value(Value&& other); + ~Value(); + Value& operator=(const Value& other); + Value& operator=(Value&& other); + + Type type() const; + bool boolean() const; + double floating_point() const; + const std::string& string() const; + + private: + Value& construct(const Value& other); + Value& construct(Value&& other); + Value& construct(bool value); + Value& construct(double value); + Value& construct(const std::string& value); + Value& destruct(); + + Type type_; + union Detail { + Detail(); + ~Detail(); + + bool b; + double d; + std::string s; + } detail_; + }; + + Optional value(bool b) const; + Optional value(double d) const; + Optional value(const std::string& s) const; + + std::int32_t id{-1}; + std::string name; + Optional code; + std::string description; + RuleSet::Rule::Status status; + Type type{Type::unknown}; + Measurement measurement{Measurement::unknown}; + Unit unit{Unit::unknown}; + }; +}; + +std::ostream& operator<<(std::ostream& out, RuleSet::Feature::Type type); +std::istream& operator>>(std::istream& in, RuleSet::Feature::Type& type); + +std::ostream& operator<<(std::ostream& out, RuleSet::Feature::Measurement measurement); +std::istream& operator>>(std::istream& in, RuleSet::Feature::Measurement& measurement); + +std::ostream& operator<<(std::ostream& out, RuleSet::Feature::Unit unit); +std::istream& operator>>(std::istream& in, RuleSet::Feature::Unit& unit); + +std::ostream& operator<<(std::ostream& out, RuleSet::Jurisdiction::Region region); +std::istream& operator>>(std::istream& in, RuleSet::Jurisdiction::Region& region); + +std::ostream& operator<<(std::ostream& out, RuleSet::SelectionType type); +std::istream& operator>>(std::istream& in, RuleSet::SelectionType& type); + +std::ostream& operator<<(std::ostream& out, RuleSet::Rule::Status status); +std::istream& operator>>(std::istream& in, RuleSet::Rule::Status& status); + +} // namespace airmap + +#endif // AIRMAP_RULESET_H_ diff --git a/libs/airmapd/include/airmap/rulesets.h b/libs/airmapd/include/airmap/rulesets.h new file mode 100644 index 0000000000000000000000000000000000000000..706bb752e428cbe98240af77aa2501bb3df5f12d --- /dev/null +++ b/libs/airmapd/include/airmap/rulesets.h @@ -0,0 +1,103 @@ +#ifndef AIRMAP_RULESETS_H_ +#define AIRMAP_RULESETS_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace airmap { + +/// RuleSets provides functionality for managing contextual airspace. +class RuleSets : DoNotCopyOrMove { + public: + /// Search bundles up types to ease interaction with + /// RuleSets::search. + struct Search { + struct Parameters { + Required geometry; ///< Search for rulesets intersecting this geometry. + }; + + /// Result models the outcome of calling RuleSets::search. + using Result = Outcome, Error>; + /// Callback describes the function signature of the callback that is invoked + /// when a call to RuleSets::search finishes. + using Callback = std::function; + }; + + /// ForId bundles up types to ease interaction with + /// RuleSets::for_id. + struct ForId { + struct Parameters { + RuleSet::Id id; ///< Search for the ruleset with this id. + }; + + /// Result models the outcome of calling RuleSets::for_id. + using Result = Outcome; + /// Callback describes the function signature of the callback that is invoked + /// when a call to RuleSets::for_id finishes. + using Callback = std::function; + }; + + /// FetchRules bundles up types to ease interaction with + /// RuleSets::fetch_rules. + struct FetchRules { + struct Parameters { + Optional rulesets; ///< Fetch rules which apply to these rulesets. + }; + + /// Result models the outcome of calling RuleSets::fetch_rules. + using Result = Outcome, Error>; + /// Callback describes the function signature of the callback that is invoked + /// when a call to RuleSets::fetch_rules finishes. + using Callback = std::function; + }; + + /// EvaluateRules bundles up types to ease interaction with + /// RuleSets::evaluate_rulesets. + struct EvaluateRules { + struct Parameters { + Required geometry; ///< Evaluate rulesets intersecting this geometry. + std::unordered_map + features; ///< Additional properties of the planned flight. + Required rulesets; ///< Evaluate these rulesets. + }; + + /// Result models the outcome of calling RuleSets::evaluate_rulesets. + using Result = Outcome; + /// Callback describes the function signature of the callback that is invoked + /// when a call to RuleSets::evaluate_rulesets finishes. + using Callback = std::function; + }; + + /// search queries the AirMap services for detailed information about + /// rulesets identified by geometry and reports back results to 'cb'. + virtual void search(const Search::Parameters& parameters, const Search::Callback& cb) = 0; + + /// for_id queries the AirMap services for detailed information about + /// a ruleset identified by a UUID and reports back results to 'cb'. + virtual void for_id(const ForId::Parameters& parameters, const ForId::Callback& cb) = 0; + + /// fetch_rules fetches rules from the rulesets identified by 'parameters' and + /// reports back results to 'cb'. + virtual void fetch_rules(const FetchRules::Parameters& parameters, const FetchRules::Callback& cb) = 0; + + /// evaluate_rulesets evaluates rulesets and geometry identified by 'parameters' and + /// reports back results to 'cb'. + virtual void evaluate_rulesets(const EvaluateRules::Parameters& parameters, const EvaluateRules::Callback& cb) = 0; + + protected: + /// @cond + RuleSets() = default; + /// @endcond +}; + +} // namespace airmap + +#endif // AIRMAP_RULESETS_H_ diff --git a/libs/airmapd/include/airmap/status.h b/libs/airmapd/include/airmap/status.h new file mode 100644 index 0000000000000000000000000000000000000000..87cc4b5848ae975724ba4709fd2fdc98707ad2ea --- /dev/null +++ b/libs/airmapd/include/airmap/status.h @@ -0,0 +1,106 @@ +#ifndef AIRMAP_STATUS_H_ +#define AIRMAP_STATUS_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace airmap { + +/// Status provides functionality to query airspace and weather information about +/// a geographic area. +class Status : DoNotCopyOrMove { + public: + /// Color enumerates known colors assigned to advisories. + enum class Color { green = 0, yellow = 1, orange = 2, red = 3 }; + + /// Advisory bundles together airspace information and its evaluation in terms + /// good to fly/needs information or feedback/conflict. + struct Advisory { + Airspace airspace; /// The airspace that the advisory refers to. + Color color; /// The evaluation of the airspace. + }; + + /// Wind bundles up attributes describing a wind conditions. + struct Wind { + std::uint32_t heading = 0; ///< The heading in [°]. + std::uint32_t speed = 0; ///< The speed in [°]. + std::uint32_t gusting = 0; + }; + + /// Weather bundles up attributes describing a weather condition. + struct Weather { + std::string condition; ///< The overall weather condition. + std::string icon; ///< The icon or class of icon that should be used for display purposes. + Wind wind; ///< The details about the current wind conditions. + std::int32_t temperature = 0; ///< The temperature in [°C]. + float humidity = 0.0; + std::uint32_t visibility = 0; ///< Visibility in [m]. + std::uint32_t precipitation = 0; ///< The probability of precipitation in [%]. + }; + + /// Report summarizes information about a geographic area. + struct Report { + std::uint32_t max_safe_distance = 0; ///< The distance to the area that is considered safe in [m]. + Color advisory_color; ///< The overall evaluation of all advisories. + std::vector advisories; ///< All relevant advisories. + Weather weather; ///< The weather conditions. + }; + + /// GetStatus bundles up types to ease interaction + /// with Status::get_status*. + struct GetStatus { + /// Parameters bundles up input parameters. + struct Parameters { + Required latitude; ///< The latitude of the center point of the query. + Required longitude; ///< The longitude of the center point of the query. + Optional types; ///< Query status information for these types of airspaces. + Optional ignored_types; ///< Ignore these types of airspaces when querying status information. + Optional weather; ///< If true, weather conditions are included with the status report. + Optional flight_date_time; ///< Time when a flight is going to happen. + Optional geometry; ///< The geometry for the query. + Optional buffer; ///< Buffer around the center point of the query. + }; + /// Result models the outcome of calling Status::get_status*. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Status::get_status* finishes. + using Callback = std::function; + }; + + /// get_status searches flight advisories for 'parameters' and reports + /// results back to 'cb'. + virtual void get_status_by_point(const GetStatus::Parameters& parameters, const GetStatus::Callback& cb) = 0; + + /// get_status searches flight advisories for 'parameters' and reports + /// results back to 'cb'. + virtual void get_status_by_path(const GetStatus::Parameters& parameters, const GetStatus::Callback& cb) = 0; + + /// get_status searches flight advisories for 'parameters' and reports + /// results back to 'cb'. + virtual void get_status_by_polygon(const GetStatus::Parameters& parameters, const GetStatus::Callback& cb) = 0; + + protected: + /// @cond + Status() = default; + /// @endcond +}; + +/// @cond +std::ostream& operator<<(std::ostream& out, Status::Color color); +std::istream& operator>>(std::istream& in, Status::Color& color); +/// @endcond + +} // namespace airmap + +#endif // AIRMAP_STATUS_H_ diff --git a/libs/airmapd/include/airmap/telemetry.h b/libs/airmapd/include/airmap/telemetry.h new file mode 100644 index 0000000000000000000000000000000000000000..792dc525afe52dc2922ff2b4a68161e53fa9b9c3 --- /dev/null +++ b/libs/airmapd/include/airmap/telemetry.h @@ -0,0 +1,117 @@ +#ifndef AIRMAP_TELEMETRY_H_ +#define AIRMAP_TELEMETRY_H_ + +#include +#include + +#include +#include +#include + +namespace airmap { + +struct Flight; + +/// Telemetry provides functionality to submit telemetry updates to the +/// AirMap services during flight. +class Telemetry : DoNotCopyOrMove { + public: + /// Position describes a timestamped geographical position. + struct Position { + std::uint64_t timestamp; ///< Ingestion timestamp of the update. + double latitude; ///< The latitude of the position [°]. + double longitude; ///< The longitude of the position in [°]. + double altitude_msl; ///< The altitude above mean sea level of the position in [m]. + double altitude_gl; ///< The altitude above ground level of the position in [m]. + double horizontal_accuracy; ///< The horizontal accuracy of the position in [m]. + }; + + /// Speed describes the timestamped 3-dim velocity of a vehicle. + struct Speed { + std::uint64_t timestamp; ///< Ingestion timestamp of the update. + float velocity_x; ///< The velocity of the vehicle in direction of the x axis in [m/s]. + float velocity_y; ///< The velocity of the vehicle in direction of the y axis in [m/s]. + float velocity_z; ///< The velocity of the vehicle in direction of the z axis in [m/s]. + }; + + /// Attitude describes the timestamped 3-dim orientation of a vehicle. + struct Attitude { + std::uint64_t timestamp; ///< Ingestion timestamp of the update. + float yaw; ///< The yaw of the vehicle in [°/s]. + float pitch; ///< The pitch of the vehicle in [°/s]. + float roll; ///< The roll of the vehicle in [°/s]. + }; + + /// Barometer describes the timestamped atmospheric pressurce conditions. + struct Barometer { + std::uint64_t timestamp; ///< Ingestion timestamp of the update. + float pressure; ///< The atmospheric pressure measurement in [Pa]. + }; + + /// @cond + static_assert(std::is_pod::value, "Position must be a POD"); + static_assert(std::is_pod::value, "Speed must be a POD"); + static_assert(std::is_pod::value, "Attitude must be a POD"); + static_assert(std::is_pod::value, "Barometer must be a POD"); + /// @endcond + + /// Update models an update of the current position, speed, attitude or atmospheric pressure + /// measurement/estimate. + class Update { + public: + /// Type enumerates all known update types. + enum class Type : std::uint8_t { + position = 1, ///< The update contains a Position instance. + speed = 2, ///< The update contains a Speed instance. + attitude = 3, ///< The update contains an Attitude instance. + barometer = 4 ///< The update contains a Barometer instance. + }; + + /// Update initializes a new instance with 'position'. + explicit Update(const Position& position); + /// Update initializes a new instance with 'speed'. + explicit Update(const Speed& speed); + /// Update initializes a new instance with 'attitude'. + explicit Update(const Attitude& attitude); + /// Update initializes a new instance with 'barometer'. + explicit Update(const Barometer& barometer); + + /// type returns the Type of the update contained within this instance. + Type type() const; + /// position returns the position update contained within this instance. + /// The behavior is undefined if type() != Type::position. + const Position& position() const; + /// speed returns the speed update contained within this instance. + /// The behavior is undefined if type() != Type::speed. + const Speed& speed() const; + /// attitude returns the attitde update contained within this instance. + /// The behavior is undefined if type() != Type::attitude. + const Attitude& attitude() const; + /// barometer returns the barometer update contained within this instance. + /// The behavior is undefined if type() != Type::barometer. + const Barometer& barometer() const; + + private: + Type type_; + union { + Position position; + Speed speed; + Attitude attitude; + Barometer barometer; + } data_; + }; + + /// submit_updates sends the telemetry data in 'updates' associated to 'flight' to the AirMap + /// services. + virtual void submit_updates(const Flight& flight, const std::string& key, + const std::initializer_list& updates) = 0; + + protected: + /// @cond + Telemetry() = default; + /// @endcond +}; + +} // namespace airmap + +#endif // AIRMAP_TELEMETRY_H_ diff --git a/libs/airmapd/include/airmap/timestamp.h b/libs/airmapd/include/airmap/timestamp.h new file mode 100644 index 0000000000000000000000000000000000000000..938215b42e0f5bb822fbff6fadde795418dd9f52 --- /dev/null +++ b/libs/airmapd/include/airmap/timestamp.h @@ -0,0 +1,13 @@ +#ifndef AIRMAP_TIMESTAMP_H_ +#define AIRMAP_TIMESTAMP_H_ + +#include + +namespace airmap { + +/// Timestamp models a specific point in time. +using Timestamp = DateTime; + +} // namespace airmap + +#endif // AIRMAP_TIMESTAMP_H_ diff --git a/libs/airmapd/include/airmap/token.h b/libs/airmapd/include/airmap/token.h new file mode 100644 index 0000000000000000000000000000000000000000..031d35e5dfe50b179adb6065fb97fe42c89b10cb --- /dev/null +++ b/libs/airmapd/include/airmap/token.h @@ -0,0 +1,115 @@ +#ifndef AIRMAP_TOKEN_H_ +#define AIRMAP_TOKEN_H_ + +#include + +#include +#include +#include + +namespace airmap { + +/// Token models an authentication token required to access the AirMap services. +class Token { + public: + /// Type enumerates all known token types. + enum class Type { + unknown, ///< Marks the unknown token type. + anonymous, ///< The token contains an Anonymous instance. + oauth, ///< The token contains an OAuth instance. + refreshed ///< The token contains a Refreshed instance. + }; + + /// Anonymous models a token for an anonymous authentication with the AirMap services. + struct Anonymous { + std::string id; ///< The authentication id. + }; + + /// OAuth models a token for an authentication with OAuth credentials with the AirMap services. + struct OAuth { + enum class Type { bearer }; + Type type; ///< The type of the OAuth token. + std::string refresh; ///< The refresh token for subsequent renewal requests. + std::string id; ///< The id token. + std::string access; ///< The access token. + }; + + /// Refreshed models a token for a refreshed authentication with OAuth credentials with the AirMap services. + struct Refreshed { + OAuth::Type type; ///< The type of the Refreshed token. + std::chrono::seconds expires_in; ///< The token expires in 'expires_in' seconds. + std::string id; ///< The id token. + Optional original_token; ///< The original token used for renewal. + }; + + /// load_from_json reads a Token instance from the input stream 'in'. + static Token load_from_json(std::istream& in); + + /// Token constructs a new invalid instance. + explicit Token(); + /// Token constructs a new instance with Type::anonymous. + explicit Token(const Anonymous& anonymous); + /// Token constructs a new instance with Type::oauth. + explicit Token(const OAuth& oauth); + /// Token constructs a new instance with Type::refreshed. + explicit Token(const Refreshed& refreshed); + /// @cond + Token(const Token& token); + Token(Token&& token); + ~Token(); + Token& operator=(const Token& token); + Token& operator=(Token&& token); + /// @endcond + + /// type returns the Type of this Token instance. + Type type() const; + /// id returns the common id of this Token instance. + const std::string& id() const; + /// anonymous returns the details for a Type::anonymous Token instance. + const Anonymous& anonymous() const; + /// anonymous returns the details for a Type::anonymous Token instance. + Anonymous& anonymous(); + /// oauth returns the details for a Type::oauth Token instance. + const OAuth& oauth() const; + /// oauth returns the details for a Type::oauth Token instance. + OAuth& oauth(); + /// refreshed returns the details for a Type::refreshed Token instance. + const Refreshed& refreshed() const; + /// refreshed returns the details for a Type::refreshed Token instance. + Refreshed& refreshed(); + + private: + union Data { + Data(); + ~Data(); + + Anonymous anonymous; + OAuth oauth; + Refreshed refreshed; + }; + + Token& construct(const Token& token); + Token& construct(const Anonymous& anonymous); + Token& construct(const OAuth& oauth); + Token& construct(const Refreshed& refreshed); + + Token& destruct(); + + Type type_; + Data data_; +}; + +/// operator<< inserts type into out. +std::ostream& operator<<(std::ostream& out, Token::Type type); +/// operator>> extracts type from in. +std::istream& operator>>(std::istream& in, Token::Type& type); +/// operator== returns true iff lhs equals rhs. +bool operator==(const Token::OAuth& lhs, const Token::OAuth& rhs); +/// operator== returns true iff lhs equals rhs. +bool operator==(Token::OAuth::Type lhs, Token::OAuth::Type rhs); +/// operator== returns true iff lhs equals rhs. +bool operator==(const Token::Refreshed& lhs, const Token::Refreshed& rhs); + +} // namespace airmap + +#endif // AIRMAP_TOKEN_H_ diff --git a/libs/airmapd/include/airmap/traffic.h b/libs/airmapd/include/airmap/traffic.h new file mode 100644 index 0000000000000000000000000000000000000000..0a043aee4de3bd4055672af1085dcbb795dd8e97 --- /dev/null +++ b/libs/airmapd/include/airmap/traffic.h @@ -0,0 +1,128 @@ +#ifndef AIRMAP_TRAFFIC_H_ +#define AIRMAP_TRAFFIC_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace airmap { + +/// Traffic provides access to the AirMap situational awareness +/// and traffic alerts. +class Traffic : DoNotCopyOrMove { + public: + /// Update bundles together information about aerial traffic + /// relevant to a UAV flight. + struct Update { + /// Type enumerates all known types of Traffic::Update. + enum class Type { + unknown, ///< Marks the unknown type. + situational_awareness, ///< Marks updates that provide planning information to operators and vehicles. + alert ///< Marks updates about aircrafts that are likely to collide with the current aircraft. + }; + + std::string id; ///< The unique id of the underlying track in the context of AirMap. + std::string aircraft_id; ///< The 'other' aircraft's id. + double latitude; ///< The latitude of the other aircraft in [°]. + double longitude; ///< The longitude of the other aircraft in [°]. + double altitude; ///< The altitude of the other aircraft in [m]. + double ground_speed; ///< The speed over ground of the other aircraft in [m/s]. + double heading; ///< The heading of the other aircraft in [°]. + double direction; ///< The direction of the other aircraft in relation to the current aircraft in [°]. + DateTime recorded; ///< The time when the datum triggering the udpate was recorded. + DateTime timestamp; ///< The time when the update was generated. + }; + + /// Monitor models handling of individual subscribers + /// to per-flight alerts and awareness notices. + class Monitor : DoNotCopyOrMove { + public: + /// Parameters bundles up input parameters. + struct Params { + std::string flight_id; ///< The id of the flight for which traffic udpates should be started. + std::string authorization; ///< The authorization token. + }; + + /// Result models the outcome of calling Traffic::monitor. + using Result = Outcome, Error>; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Traffic::monitor finishes. + using Callback = std::function; + + /// Subscriber abstracts handling of batches of Update instances. + class Subscriber { + public: + /// handle_update is invoked when a new batch of Update instances + /// is available. + virtual void handle_update(Update::Type type, const std::vector& update) = 0; + + protected: + Subscriber() = default; + }; + + /// FunctionalSubscriber is a convenience class that dispatches + /// to a function 'f' for handling batches of Update instances. + class FunctionalSubscriber : public Subscriber { + public: + /// FunctionalSubscriber initializes a new instance with 'f'. + explicit FunctionalSubscriber(const std::function&)>& f); + // From subscriber + void handle_update(Update::Type type, const std::vector& update) override; + + private: + std::function&)> f_; + }; + + /// LoggingSubscriber is a convenience class that logs incoming batches + /// of Update instances. + class LoggingSubscriber : public Subscriber { + public: + /// LoggingSubscriber initializes an instance with 'component', feeding + /// log entries to 'logger'. Please note that no change of ownership takes + /// place for 'component' and the lifetime of component has to exceed the + /// lifetime of a LoggingSubscriber instance. + explicit LoggingSubscriber(const char* component, const std::shared_ptr& logger); + + // From Subscriber + void handle_update(Update::Type type, const std::vector& update) override; + + private: + const char* component_; + std::shared_ptr logger_; + }; + + /// subscribe registers 'subscriber' such that subsequent batches of + /// Update instances are delivered to 'subscriber'. + virtual void subscribe(const std::shared_ptr& subscriber) = 0; + + /// unsubscribe unregisters 'subscriber'. + virtual void unsubscribe(const std::shared_ptr& subscriber) = 0; + + protected: + Monitor() = default; + }; + + /// monitor subscribes the user and flight described in 'params' to + /// the AirMap traffic services and reports the result to 'cb'. + virtual void monitor(const Monitor::Params& params, const Monitor::Callback& cb) = 0; + + protected: + /// @cond + Traffic() = default; + /// @endcond +}; + +/// operator<< inserts a textual representation of type into out. +std::ostream& operator<<(std::ostream& out, Traffic::Update::Type type); + +} // namespace airmap + +#endif // AIRMAP_TRAFFIC_H_ diff --git a/libs/airmapd/include/airmap/version.h b/libs/airmapd/include/airmap/version.h new file mode 100644 index 0000000000000000000000000000000000000000..18ea8baf3a5792181065742bedbb31ae958b642a --- /dev/null +++ b/libs/airmapd/include/airmap/version.h @@ -0,0 +1,26 @@ +#ifndef AIRMAP_VERSION_H_ +#define AIRMAP_VERSION_H_ + +#include +#include + +#include + +namespace airmap { + +/// Version bundles up information describing a specific version of the AirMap +/// client library. We follow semantic versioning guidelines (see https://semver.org). +struct Version { + /// current returns an immutable reference to the version of the client library. + static const Version& current(); + + std::uint32_t major; ///< The major version number. + std::uint32_t minor; ///< The minor version number. + std::uint32_t patch; ///< The patch version number. + Optional git_revision; ///< The git revision from which the release was build. + Optional build_timestamp; ///< Marks the time when the library was built. +}; + +} // namespace airmap + +#endif // AIRMAP_VERSION_H_ diff --git a/libs/airmapd/macOS/Qt.5.9/libairmap-cpp.0.0.1.dylib b/libs/airmapd/macOS/Qt.5.9/libairmap-cpp.0.0.1.dylib new file mode 120000 index 0000000000000000000000000000000000000000..db811e07bbae74fd1b0f3f7b30eb6ab2fcc0781f --- /dev/null +++ b/libs/airmapd/macOS/Qt.5.9/libairmap-cpp.0.0.1.dylib @@ -0,0 +1 @@ +libairmap-cpp.0.dylib \ No newline at end of file diff --git a/libs/airmapd/macOS/Qt.5.9/libairmap-cpp.0.dylib b/libs/airmapd/macOS/Qt.5.9/libairmap-cpp.0.dylib new file mode 100755 index 0000000000000000000000000000000000000000..aa69744166bf49bbcfd3476a7709c3bb0cfaeda8 Binary files /dev/null and b/libs/airmapd/macOS/Qt.5.9/libairmap-cpp.0.dylib differ diff --git a/libs/airmapd/macOS/Qt.5.9/libairmap-cpp.dylib b/libs/airmapd/macOS/Qt.5.9/libairmap-cpp.dylib new file mode 120000 index 0000000000000000000000000000000000000000..250337bd5ff48e80fde947ee9685a1a76f242540 --- /dev/null +++ b/libs/airmapd/macOS/Qt.5.9/libairmap-cpp.dylib @@ -0,0 +1 @@ +libairmap-cpp.0.0.1.dylib \ No newline at end of file diff --git a/libs/airmapd/macOS/Qt.5.9/libairmap-qt.0.0.1.dylib b/libs/airmapd/macOS/Qt.5.9/libairmap-qt.0.0.1.dylib new file mode 120000 index 0000000000000000000000000000000000000000..f88997b2e9c6e43fddc23ae0d8f7cae5b475cbe7 --- /dev/null +++ b/libs/airmapd/macOS/Qt.5.9/libairmap-qt.0.0.1.dylib @@ -0,0 +1 @@ +libairmap-qt.0.dylib \ No newline at end of file diff --git a/libs/airmapd/macOS/Qt.5.9/libairmap-qt.0.dylib b/libs/airmapd/macOS/Qt.5.9/libairmap-qt.0.dylib new file mode 100755 index 0000000000000000000000000000000000000000..f6bc90134d549be4db545f26e63b59c8b4dab7f5 Binary files /dev/null and b/libs/airmapd/macOS/Qt.5.9/libairmap-qt.0.dylib differ diff --git a/libs/airmapd/macOS/Qt.5.9/libairmap-qt.dylib b/libs/airmapd/macOS/Qt.5.9/libairmap-qt.dylib new file mode 120000 index 0000000000000000000000000000000000000000..c62931ac622bc94698aa6f7822ad593565784a26 --- /dev/null +++ b/libs/airmapd/macOS/Qt.5.9/libairmap-qt.dylib @@ -0,0 +1 @@ +libairmap-qt.0.0.1.dylib \ No newline at end of file diff --git a/qgcresources.qrc b/qgcresources.qrc index 5b6fa54b53d55297f9774605cfbb30482e0a7eb5..019dfa2f5e31804feaca780d04b4028b89fd2707 100644 --- a/qgcresources.qrc +++ b/qgcresources.qrc @@ -123,6 +123,7 @@ src/FlightMap/Images/ZoomPlus.svg src/FlightMap/Images/ZoomMinus.svg src/FlightMap/Images/Help.svg + src/FlightMap/Images/Home.svg src/FlightMap/Images/HelpBlack.svg src/FlightMap/Images/MapAddMission.svg src/FlightMap/Images/MapAddMissionBlack.svg @@ -171,6 +172,13 @@ src/FirmwarePlugin/APM/APMBrandImageSub.png src/FirmwarePlugin/PX4/PX4BrandImage.png src/FlightMap/Images/sub.png + src/Airmap/images/advisory-icon.svg + + + src/Airmap/images/advisory-icon.svg + src/Airmap/images/colapse.svg + src/Airmap/images/expand.svg + src/Airmap/images/pencil.svg resources/action.svg @@ -207,6 +215,7 @@ resources/wind-rose-arrow.svg resources/XDelete.svg resources/XDeleteBlack.svg + resources/waypoint.svg resources/icons/qgroundcontrol.ico diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index 1609e178c8b4a21dcb710a0d5633787d8ebe40d2..7c9f0fe8f6a4ce2e577f8d4ed1076b99342bab64 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -505,6 +505,9 @@ HEADERS += \ src/LogCompressor.h \ src/MG.h \ src/MissionManager/CameraCalc.h \ + src/MissionManager/AirspaceController.h \ + src/MissionManager/AirMapManager.h \ + src/MissionManager/AirspaceManagement.h \ src/MissionManager/CameraSection.h \ src/MissionManager/CameraSpec.h \ src/MissionManager/ComplexMissionItem.h \ @@ -564,6 +567,7 @@ HEADERS += \ src/QmlControls/RCChannelMonitorController.h \ src/QmlControls/ScreenToolsController.h \ src/QtLocationPlugin/QMLControl/QGCMapEngineManager.h \ + src/Settings/AirMapSettings.h \ src/Settings/AppSettings.h \ src/Settings/AutoConnectSettings.h \ src/Settings/BrandImageSettings.h \ @@ -697,6 +701,9 @@ SOURCES += \ src/JsonHelper.cc \ src/LogCompressor.cc \ src/MissionManager/CameraCalc.cc \ + src/MissionManager/AirspaceController.cc \ + src/MissionManager/AirMapManager.cc \ + src/MissionManager/AirspaceManagement.cc \ src/MissionManager/CameraSection.cc \ src/MissionManager/CameraSpec.cc \ src/MissionManager/ComplexMissionItem.cc \ @@ -753,6 +760,7 @@ SOURCES += \ src/QmlControls/RCChannelMonitorController.cc \ src/QmlControls/ScreenToolsController.cc \ src/QtLocationPlugin/QMLControl/QGCMapEngineManager.cc \ + src/Settings/AirMapSettings.cc \ src/Settings/AppSettings.cc \ src/Settings/AutoConnectSettings.cc \ src/Settings/BrandImageSettings.cc \ diff --git a/qgroundcontrol.qrc b/qgroundcontrol.qrc index 366817258be621af4112dfb5b2c4aab8ac277cb6..9d399d646731ae4dc731a81912a0de94f18a3873 100644 --- a/qgroundcontrol.qrc +++ b/qgroundcontrol.qrc @@ -15,6 +15,10 @@ src/ui/toolbar/JoystickIndicator.qml + src/Airmap/QGroundControl.Airmap.qmldir + src/Airmap/AirspaceControl.qml + src/Airmap/AirspaceRegulation.qml + src/Airmap/AirmapSettings.qml src/AnalyzeView/AnalyzeView.qml src/ui/AppSettings.qml src/ui/preferences/BluetoothSettings.qml @@ -195,6 +199,7 @@ src/MissionManager/MavCmdInfoRover.json src/MissionManager/MavCmdInfoSub.json src/MissionManager/MavCmdInfoVTOL.json + src/Settings/AirMap.SettingsGroup.json src/Settings/App.SettingsGroup.json src/Settings/AutoConnect.SettingsGroup.json src/Settings/FlightMap.SettingsGroup.json diff --git a/resources/waypoint.svg b/resources/waypoint.svg new file mode 100644 index 0000000000000000000000000000000000000000..257fb71144929a821b984c3cb1660a4819d8b2ef --- /dev/null +++ b/resources/waypoint.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/Airmap/AirmapSettings.qml b/src/Airmap/AirmapSettings.qml new file mode 100644 index 0000000000000000000000000000000000000000..89f23400c3a4187dd8cce4b0c9d483fbe8eb69bb --- /dev/null +++ b/src/Airmap/AirmapSettings.qml @@ -0,0 +1,195 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + + +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Dialogs 1.2 +import QtMultimedia 5.5 +import QtQuick.Layouts 1.2 + +import QGroundControl 1.0 +import QGroundControl.FactSystem 1.0 +import QGroundControl.FactControls 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.MultiVehicleManager 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.Controllers 1.0 +import QGroundControl.SettingsManager 1.0 + +QGCView { + id: _qgcView + viewPanel: panel + color: qgcPal.window + anchors.fill: parent + anchors.margins: ScreenTools.defaultFontPixelWidth + + property real _labelWidth: ScreenTools.defaultFontPixelWidth * 20 + property real _editFieldWidth: ScreenTools.defaultFontPixelWidth * 30 + property real _panelWidth: _qgcView.width * _internalWidthRatio + property Fact _enableAirMapFact: QGroundControl.settingsManager.appSettings.enableAirMap + property bool _airMapEnabled: _enableAirMapFact.rawValue + + readonly property real _internalWidthRatio: 0.8 + + QGCPalette { id: qgcPal } + + QGCViewPanel { + id: panel + anchors.fill: parent + QGCFlickable { + clip: true + anchors.fill: parent + contentHeight: settingsColumn.height + contentWidth: settingsColumn.width + Column { + id: settingsColumn + width: _qgcView.width + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + anchors.margins: ScreenTools.defaultFontPixelWidth + //----------------------------------------------------------------- + //-- General + Item { + width: _panelWidth + height: generalLabel.height + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + visible: QGroundControl.settingsManager.unitsSettings.visible + QGCLabel { + id: generalLabel + text: qsTr("General") + font.family: ScreenTools.demiboldFontFamily + } + } + Rectangle { + height: generalCol.height + (ScreenTools.defaultFontPixelHeight * 2) + width: _panelWidth + color: qgcPal.windowShade + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + Column { + id: generalCol + spacing: ScreenTools.defaultFontPixelWidth + anchors.centerIn: parent + FactCheckBox { + text: qsTr("Enable AirMap Services") + fact: _enableAirMapFact + visible: _enableAirMapFact.visible + } + QGCCheckBox { + text: qsTr("Disable Telemetry") + checked: false + enabled: _airMapEnabled + onClicked: + { + + } + } + } + } + //----------------------------------------------------------------- + //-- Login / Registration + Item { + width: _panelWidth + height: loginLabel.height + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + visible: QGroundControl.settingsManager.appSettings.visible + QGCLabel { + id: loginLabel + text: qsTr("Login / Registration") + font.family: ScreenTools.demiboldFontFamily + } + } + Rectangle { + height: loginGrid.height + (ScreenTools.defaultFontPixelHeight * 2) + width: _panelWidth + color: qgcPal.windowShade + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + GridLayout { + id: loginGrid + columns: 2 + rowSpacing: ScreenTools.defaultFontPixelHeight * 0.25 + anchors.centerIn: parent + QGCLabel { text: qsTr("Email:") } + QGCTextField { + width: _editFieldWidth + enabled: _airMapEnabled + } + QGCLabel { text: qsTr("Password:") } + QGCTextField { + width: _editFieldWidth + enabled: _airMapEnabled + } + Item { + width: 1 + height: 1 + Layout.columnSpan: 2 + } + QGCLabel { + text: qsTr("Forgot Your AirMap Password?") + Layout.alignment: Qt.AlignHCenter + Layout.columnSpan: 2 + } + Item { + width: 1 + height: 1 + Layout.columnSpan: 2 + } + QGCButton { + text: qsTr("Register for an AirMap Account") + Layout.alignment: Qt.AlignHCenter + Layout.columnSpan: 2 + enabled: _airMapEnabled + } + } + } + //----------------------------------------------------------------- + //-- Pilot Profile + Item { + width: _panelWidth + height: profileLabel.height + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + visible: QGroundControl.settingsManager.appSettings.visible + QGCLabel { + id: profileLabel + text: qsTr("Pilot Profile") + font.family: ScreenTools.demiboldFontFamily + } + } + Rectangle { + height: profileGrid.height + (ScreenTools.defaultFontPixelHeight * 2) + width: _panelWidth + color: qgcPal.windowShade + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + GridLayout { + id: profileGrid + columns: 2 + columnSpacing: ScreenTools.defaultFontPixelHeight * 2 + rowSpacing: ScreenTools.defaultFontPixelWidth * 0.25 + anchors.centerIn: parent + QGCLabel { text: qsTr("Name:") } + QGCLabel { text: qsTr("John Doe") } + QGCLabel { text: qsTr("User Name:") } + QGCLabel { text: qsTr("joe36") } + QGCLabel { text: qsTr("Email:") } + QGCLabel { text: qsTr("jonh@doe.com") } + QGCLabel { text: qsTr("Phone:") } + QGCLabel { text: qsTr("+1 212 555 1212") } + } + } + } + } + } +} diff --git a/src/Airmap/AirspaceControl.qml b/src/Airmap/AirspaceControl.qml new file mode 100644 index 0000000000000000000000000000000000000000..dea3cddf67f8c00dcd40d5acc0fe2cc9141b4694 --- /dev/null +++ b/src/Airmap/AirspaceControl.qml @@ -0,0 +1,259 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.2 +import QtQml 2.2 + +import QGroundControl 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.Vehicle 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.FactControls 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.Airmap 1.0 + +Item { + id: _root + width: parent.width + height: colapsed ? colapsedRect.height : expandedRect.height + + property bool colapsed: true + property bool showColapse: true + + readonly property real _radius: ScreenTools.defaultFontPixelWidth * 0.5 + readonly property color _colorOrange: "#d75e0d" + readonly property color _colorBrown: "#3c2b24" + readonly property color _colorLightBrown: "#5a4e49" + readonly property color _colorGray: "#615c61" + readonly property color _colorMidBrown: "#3a322f" + readonly property color _colorYellow: "#d7c61d" + readonly property color _colorWhite: "#ffffff" + + QGCPalette { + id: qgcPal + colorGroupEnabled: enabled + } + + //--------------------------------------------------------------- + //-- Colapsed State + Rectangle { + id: colapsedRect + width: parent.width + height: colapsed ? colapsedRow.height + ScreenTools.defaultFontPixelHeight : 0 + color: _colorOrange + radius: _radius + visible: colapsed + Row { + id: colapsedRow + spacing: ScreenTools.defaultFontPixelWidth + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth + anchors.verticalCenter: parent.verticalCenter + QGCColoredImage { + width: height + height: ScreenTools.defaultFontPixelWidth * 2.5 + sourceSize.height: height + source: "qrc:/airmap/advisory-icon.svg" + color: _colorWhite + anchors.verticalCenter: parent.verticalCenter + } + QGCLabel { + text: qsTr("Airspace") + color: _colorWhite + anchors.verticalCenter: parent.verticalCenter + } + } + QGCColoredImage { + width: height + height: ScreenTools.defaultFontPixelWidth * 2.5 + sourceSize.height: height + source: "qrc:/airmap/expand.svg" + color: _colorWhite + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth + anchors.verticalCenter: parent.verticalCenter + } + MouseArea { + anchors.fill: parent + onClicked: colapsed = false + } + } + //--------------------------------------------------------------- + //-- Expanded State + Rectangle { + id: expandedRect + width: parent.width + height: !colapsed ? expandedCol.height + ScreenTools.defaultFontPixelHeight : 0 + color: _colorOrange + radius: _radius + visible: !colapsed + Column { + id: expandedCol + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + //-- Header + Item { + height: expandedRow.height + anchors.left: parent.left + anchors.right: parent.right + Row { + id: expandedRow + spacing: ScreenTools.defaultFontPixelWidth + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth + QGCColoredImage { + width: height + height: ScreenTools.defaultFontPixelWidth * 2.5 + sourceSize.height: height + source: "qrc:/airmap/advisory-icon.svg" + color: _colorWhite + anchors.verticalCenter: parent.verticalCenter + } + Column { + spacing: 0 + anchors.verticalCenter: parent.verticalCenter + QGCLabel { + text: qsTr("Airspace") + color: _colorWhite + } + QGCLabel { + text: qsTr("3 Advisories") + color: _colorWhite + font.pointSize: ScreenTools.smallFontPointSize + } + } + } + QGCColoredImage { + width: height + height: ScreenTools.defaultFontPixelWidth * 2.5 + sourceSize.height: height + source: "qrc:/airmap/colapse.svg" + color: _colorWhite + visible: showColapse + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth + anchors.verticalCenter: parent.verticalCenter + MouseArea { + anchors.fill: parent + enabled: showColapse + onClicked: colapsed = true + } + } + } + //-- Contents (Brown Box) + Rectangle { + color: _colorBrown + height: airspaceCol.height + ScreenTools.defaultFontPixelHeight + radius: _radius + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth * 0.5 + Column { + id: airspaceCol + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.verticalCenter: parent.verticalCenter + //-- Regulations + Rectangle { + color: _colorLightBrown + height: regCol.height + ScreenTools.defaultFontPixelHeight + radius: _radius + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth * 0.5 + Column { + id: regCol + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.verticalCenter: parent.verticalCenter + QGCLabel { + text: qsTr("Airspace Regulations") + color: _colorWhite + anchors.horizontalCenter: parent.horizontalCenter + } + QGCLabel { + text: qsTr("Airspace advisories based on the selected rules.") + color: _colorWhite + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + font.pointSize: ScreenTools.smallFontPointSize + } + GridLayout { + columns: 2 + anchors.left: parent.left + anchors.right: parent.right + Rectangle { + width: regButton.height + height: width + radius: 2 + color: _colorGray + anchors.verticalCenter: parent.verticalCenter + QGCColoredImage { + width: height + height: parent.height * 0.5 + sourceSize.height: height + source: "qrc:/airmap/pencil.svg" + color: _colorWhite + anchors.centerIn: parent + } + } + Rectangle { + id: regButton + height: regLabel.height + ScreenTools.defaultFontPixelHeight * 0.5 + radius: 2 + color: _colorMidBrown + Layout.fillWidth: true + QGCLabel { + id: regLabel + text: qsTr("FAA-107, Airmap") + color: _colorWhite + anchors.centerIn: parent + } + } + } + } + } + AirspaceRegulation { + regTitle: qsTr("Controlled Aispace (1)") + regText: qsTr("Santa Monica Class D requires FAA Authorization, permissible below 100ft.") + regColor: _colorOrange + textColor: _colorWhite + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth * 0.5 + } + AirspaceRegulation { + regTitle: qsTr("Schools (2)") + regText: qsTr("Santa Monica School of Something.") + regColor: _colorYellow + textColor: _colorWhite + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth * 0.5 + } + } + } + //-- Footer + QGCLabel { + text: qsTr("Powered by AIRMAP") + color: _colorWhite + font.pointSize: ScreenTools.smallFontPointSize + anchors.horizontalCenter: parent.horizontalCenter + } + } + } +} diff --git a/src/Airmap/AirspaceRegulation.qml b/src/Airmap/AirspaceRegulation.qml new file mode 100644 index 0000000000000000000000000000000000000000..91a0af129ac9a65a60f45e94f056a0929dd69111 --- /dev/null +++ b/src/Airmap/AirspaceRegulation.qml @@ -0,0 +1,58 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Dialogs 1.2 +import QtQml 2.2 + +import QGroundControl 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.Airmap 1.0 + +Item { + id: _root + height: regCol.height + + property var textColor: "white" + property var regColor: "white" + property var regTitle: "" + property var regText: "" + + Column { + id: regCol + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth * 0.5 + Row { + spacing: ScreenTools.defaultFontPixelWidth + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth * 0.5 + Rectangle { + width: height + height: ScreenTools.defaultFontPixelWidth * 1.5 + radius: height * 0.5 + color: regColor + anchors.verticalCenter: parent.verticalCenter + } + QGCLabel { + text: regTitle + color: textColor + } + } + QGCLabel { + text: regText + color: textColor + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth * 0.5 + wrapMode: Text.WordWrap + font.pointSize: ScreenTools.smallFontPointSize + } + } +} diff --git a/src/Airmap/QGroundControl.Airmap.qmldir b/src/Airmap/QGroundControl.Airmap.qmldir new file mode 100644 index 0000000000000000000000000000000000000000..116917dfddfea92440f489592f6f50be96565a64 --- /dev/null +++ b/src/Airmap/QGroundControl.Airmap.qmldir @@ -0,0 +1,4 @@ +Module QGroundControl.Airmap + +AirspaceControl 1.0 AirspaceControl.qml +AirspaceRegulation 1.0 AirspaceRegulation.qml diff --git a/src/Airmap/images/advisory-icon.svg b/src/Airmap/images/advisory-icon.svg new file mode 100644 index 0000000000000000000000000000000000000000..9141d18dbb9c7da4c017d98e47c02e0942a047fa --- /dev/null +++ b/src/Airmap/images/advisory-icon.svg @@ -0,0 +1,21 @@ + + + + +Page 1 +Created with Sketch. + + + + + + diff --git a/src/Airmap/images/colapse.svg b/src/Airmap/images/colapse.svg new file mode 100644 index 0000000000000000000000000000000000000000..bf33cf0dcc87af69239a20d68956974ba3cf7914 --- /dev/null +++ b/src/Airmap/images/colapse.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/src/Airmap/images/expand.svg b/src/Airmap/images/expand.svg new file mode 100644 index 0000000000000000000000000000000000000000..196447d67b16eca07ff4ed9f6d11ff3f1832d2af --- /dev/null +++ b/src/Airmap/images/expand.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/src/Airmap/images/pencil.svg b/src/Airmap/images/pencil.svg new file mode 100644 index 0000000000000000000000000000000000000000..2b98ce6ff5a9fc5353233a3510db9e063207be38 --- /dev/null +++ b/src/Airmap/images/pencil.svg @@ -0,0 +1,17 @@ + + + + + + diff --git a/src/FlightDisplay/FlightDisplayView.qml b/src/FlightDisplay/FlightDisplayView.qml index 2404e2967e3ba9ddc338622af827b17382ffbb74..3542909f1d4d7e9315d00c669ec263db001b91fa 100644 --- a/src/FlightDisplay/FlightDisplayView.qml +++ b/src/FlightDisplay/FlightDisplayView.qml @@ -626,4 +626,51 @@ QGCView { visible: false } } + + //-- Airspace Indicator + Rectangle { + id: airspaceIndicator + width: airspaceRow.width + (ScreenTools.defaultFontPixelWidth * 3) + height: airspaceRow.height * 1.25 + color: qgcPal.globalTheme === QGCPalette.Light ? Qt.rgba(1,1,1,0.95) : Qt.rgba(0,0,0,0.75) + visible: _mainIsMap && flightPermit && flightPermit !== AirspaceAuthorization.PermitUnknown && !messageArea.visible && !criticalMmessageArea.visible + radius: 3 + border.width: 1 + border.color: qgcPal.globalTheme === QGCPalette.Light ? Qt.rgba(0,0,0,0.35) : Qt.rgba(1,1,1,0.35) + anchors.top: parent.top + anchors.topMargin: ScreenTools.toolbarHeight + (ScreenTools.defaultFontPixelHeight * 0.25) + anchors.horizontalCenter: parent.horizontalCenter + Row { + id: airspaceRow + spacing: ScreenTools.defaultFontPixelWidth + anchors.centerIn: parent + QGCLabel { text: airspaceIndicator.providerName+":"; anchors.verticalCenter: parent.verticalCenter; } + QGCLabel { + text: { + if(airspaceIndicator.flightPermit) { + if(airspaceIndicator.flightPermit === AirspaceAuthorization.PermitPending) + return qsTr("Approval Pending") + if(airspaceIndicator.flightPermit === AirspaceAuthorization.PermitAccepted) + return qsTr("Flight Approved") + if(airspaceIndicator.flightPermit === AirspaceAuthorization.PermitRejected) + return qsTr("Flight Rejected") + } + return "" + } + color: { + if(airspaceIndicator.flightPermit) { + if(airspaceIndicator.flightPermit === AirspaceAuthorization.PermitPending) + return qgcPal.colorOrange + if(airspaceIndicator.flightPermit === AirspaceAuthorization.PermitAccepted) + return qgcPal.colorGreen + } + return qgcPal.colorRed + } + anchors.verticalCenter: parent.verticalCenter; + } + } + property var flightPermit: _activeVehicle ? _activeVehicle.flightPermitStatus : null + property var providerName: _activeVehicle ? _activeVehicle.airspaceController.providerName : "" + } + } diff --git a/src/FlightDisplay/FlightDisplayViewMap.qml b/src/FlightDisplay/FlightDisplayViewMap.qml index df01d8b74fda9ff6ae0c5fafd7a0188c615a333b..281d78967fb20769f02004cd185a0cc6237e7b6b 100644 --- a/src/FlightDisplay/FlightDisplayViewMap.qml +++ b/src/FlightDisplay/FlightDisplayViewMap.qml @@ -57,7 +57,12 @@ FlightMap { // Track last known map position and zoom from Fly view in settings onZoomLevelChanged: QGroundControl.flightMapZoom = zoomLevel - onCenterChanged: QGroundControl.flightMapPosition = center + onCenterChanged: { + if(_activeVehicle) { + _activeVehicle.airspaceController.setROI(center, 5000) + } + QGroundControl.flightMapPosition = center + } // When the user pans the map we stop responding to vehicle coordinate updates until the panRecenterTimer fires onUserPannedChanged: { @@ -323,4 +328,26 @@ FlightMap { } ] } + + // Airspace overlap support + MapItemView { + model: _activeVehicle ? _activeVehicle.airspaceController.circles : [] + delegate: MapCircle { + center: object.center + radius: object.radius + border.color: "white" + color: "yellow" + opacity: 0.25 + } + } + + MapItemView { + model: _activeVehicle ? _activeVehicle.airspaceController.polygons : [] + delegate: MapPolygon { + border.color: "white" + color: "yellow" + opacity: 0.25 + path: object.polygon + } + } } diff --git a/src/FlightDisplay/FlightDisplayViewWidgets.qml b/src/FlightDisplay/FlightDisplayViewWidgets.qml index 4d61d876f7504f36582a22b14db106f9f3b3abf9..e51bed6927806339ef222dab525ecbcfbd0151dc 100644 --- a/src/FlightDisplay/FlightDisplayViewWidgets.qml +++ b/src/FlightDisplay/FlightDisplayViewWidgets.qml @@ -15,23 +15,26 @@ import QtLocation 5.3 import QtPositioning 5.3 import QtQuick.Layouts 1.2 -import QGroundControl 1.0 -import QGroundControl.ScreenTools 1.0 -import QGroundControl.Controls 1.0 -import QGroundControl.Palette 1.0 -import QGroundControl.Vehicle 1.0 -import QGroundControl.FlightMap 1.0 +import QGroundControl 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.Vehicle 1.0 +import QGroundControl.FlightMap 1.0 +import QGroundControl.Airmap 1.0 Item { - id: _root + id: widgetRoot property var qgcView property bool useLightColors property var missionController + property bool showValues: true property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle property bool _isSatellite: _mainIsMap ? (_flightMap ? _flightMap.isSatelliteMap : true) : true property bool _lightWidgetBorders: _isSatellite + property bool _enableAirMap: QGroundControl.settingsManager.appSettings.enableAirMap.rawValue readonly property real _margins: ScreenTools.defaultFontPixelHeight * 0.5 @@ -51,28 +54,14 @@ Item { if(QGroundControl.corePlugin.options.instrumentWidget) { if(QGroundControl.corePlugin.options.instrumentWidget.source.toString().length) { instrumentsLoader.source = QGroundControl.corePlugin.options.instrumentWidget.source - switch(QGroundControl.corePlugin.options.instrumentWidget.widgetPosition) { - case CustomInstrumentWidget.POS_TOP_RIGHT: - instrumentsLoader.state = "topMode" - break; - case CustomInstrumentWidget.POS_BOTTOM_RIGHT: - instrumentsLoader.state = "bottomMode" - break; - case CustomInstrumentWidget.POS_CENTER_RIGHT: - default: - instrumentsLoader.state = "centerMode" - break; - } } else { // Note: We currently show alternate instruments all the time. This is a trial change for daily builds. // Leaving non-alternate code in for now in case the trial fails. var useAlternateInstruments = true//QGroundControl.settingsManager.appSettings.virtualJoystick.value || ScreenTools.isTinyScreen if(useAlternateInstruments) { instrumentsLoader.source = "qrc:/qml/QGCInstrumentWidgetAlternate.qml" - instrumentsLoader.state = "topMode" } else { instrumentsLoader.source = "qrc:/qml/QGCInstrumentWidget.qml" - instrumentsLoader.state = QGroundControl.settingsManager.appSettings.showLargeCompass.value == 1 ? "centerMode" : "topMode" } } } else { @@ -130,43 +119,31 @@ Item { text: "The vehicle has failed a pre-arm check. In order to arm the vehicle, resolve the failure or disable the arming check via the Safety tab on the Vehicle Setup page." } } - - //-- Instrument Panel - Loader { - id: instrumentsLoader - anchors.margins: ScreenTools.defaultFontPixelHeight / 2 + Column { + id: instrumentsColumn + spacing: ScreenTools.defaultFontPixelHeight * 0.25 + anchors.top: parent.top + anchors.topMargin: QGroundControl.corePlugin.options.instrumentWidget.widgetTopMargin + (ScreenTools.defaultFontPixelHeight * 0.5) + anchors.margins: ScreenTools.defaultFontPixelHeight * 0.5 anchors.right: parent.right - z: QGroundControl.zOrderWidgets - property var qgcView: _root.qgcView - property real maxHeight:parent.height - (anchors.margins * 2) - states: [ - State { - name: "topMode" - AnchorChanges { - target: instrumentsLoader - anchors.verticalCenter: undefined - anchors.bottom: undefined - anchors.top: _root ? _root.top : undefined - } - }, - State { - name: "centerMode" - AnchorChanges { - target: instrumentsLoader - anchors.top: undefined - anchors.bottom: undefined - anchors.verticalCenter: _root ? _root.verticalCenter : undefined - } - }, - State { - name: "bottomMode" - AnchorChanges { - target: instrumentsLoader - anchors.top: undefined - anchors.verticalCenter: undefined - anchors.bottom: _root ? _root.bottom : undefined - } + //------------------------------------------------------- + // Airmap Airspace Control + AirspaceControl { + id: airspaceControl + width: getPreferredInstrumentWidth() + visible: _enableAirMap + anchors.margins: ScreenTools.defaultFontPixelHeight * 0.5 + onColapsedChanged: { + widgetRoot.showValues = colapsed } - ] + } + //------------------------------------------------------- + //-- Instrument Panel + Loader { + id: instrumentsLoader + anchors.margins: ScreenTools.defaultFontPixelHeight * 0.5 + property var qgcView: widgetRoot.qgcView + property real maxHeight: widgetRoot ? widgetRoot.height - instrumentsColumn.y - airspaceControl.height - (ScreenTools.defaultFontPixelHeight * 4) : 0 + } } } diff --git a/src/FlightMap/Images/HomeBlack.svg b/src/FlightMap/Images/HomeBlack.svg deleted file mode 100644 index ae78ea2b4c2445f74e23b4027c5be4fd3bd9fd2e..0000000000000000000000000000000000000000 --- a/src/FlightMap/Images/HomeBlack.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/src/FlightMap/Widgets/QGCInstrumentWidgetAlternate.qml b/src/FlightMap/Widgets/QGCInstrumentWidgetAlternate.qml index 290fec81fec381820564febe485d2dfd4644c683..93ebc493b09e759bddd84958963010457e5ab4c3 100644 --- a/src/FlightMap/Widgets/QGCInstrumentWidgetAlternate.qml +++ b/src/FlightMap/Widgets/QGCInstrumentWidgetAlternate.qml @@ -66,6 +66,7 @@ Rectangle { anchors.top: parent.bottom width: parent.width height: _valuesWidget.height + visible: widgetRoot.showValues Rectangle { anchors.fill: _valuesWidget diff --git a/src/JsonHelper.cc b/src/JsonHelper.cc index e7d47a6187a94fc4ab8288b64d3e7dcd8e8d449a..502f5a5cf837acda8c05d3a6a7ff70c970dba3ff 100644 --- a/src/JsonHelper.cc +++ b/src/JsonHelper.cc @@ -45,10 +45,11 @@ bool JsonHelper::validateRequiredKeys(const QJsonObject& jsonObject, const QStri return true; } -bool JsonHelper::loadGeoCoordinate(const QJsonValue& jsonValue, - bool altitudeRequired, - QGeoCoordinate& coordinate, - QString& errorString) +bool JsonHelper::_loadGeoCoordinate(const QJsonValue& jsonValue, + bool altitudeRequired, + QGeoCoordinate& coordinate, + QString& errorString, + bool geoJsonFormat) { if (!jsonValue.isArray()) { errorString = QObject::tr("value for coordinate is not array"); @@ -69,7 +70,11 @@ bool JsonHelper::loadGeoCoordinate(const QJsonValue& jsonValue, } } - coordinate = QGeoCoordinate(possibleNaNJsonValue(coordinateArray[0]), possibleNaNJsonValue(coordinateArray[1])); + if (geoJsonFormat) { + coordinate = QGeoCoordinate(coordinateArray[1].toDouble(), coordinateArray[0].toDouble()); + } else { + coordinate = QGeoCoordinate(possibleNaNJsonValue(coordinateArray[0]), possibleNaNJsonValue(coordinateArray[1])); + } if (altitudeRequired) { coordinate.setAltitude(possibleNaNJsonValue(coordinateArray[2])); } @@ -77,13 +82,18 @@ bool JsonHelper::loadGeoCoordinate(const QJsonValue& jsonValue, return true; } -void JsonHelper::saveGeoCoordinate(const QGeoCoordinate& coordinate, - bool writeAltitude, - QJsonValue& jsonValue) +void JsonHelper::_saveGeoCoordinate(const QGeoCoordinate& coordinate, + bool writeAltitude, + QJsonValue& jsonValue, + bool geoJsonFormat) { QJsonArray coordinateArray; - coordinateArray << coordinate.latitude() << coordinate.longitude(); + if (geoJsonFormat) { + coordinateArray << coordinate.longitude() << coordinate.latitude(); + } else { + coordinateArray << coordinate.latitude() << coordinate.longitude(); + } if (writeAltitude) { coordinateArray << coordinate.altitude(); } @@ -91,6 +101,37 @@ void JsonHelper::saveGeoCoordinate(const QGeoCoordinate& coordinate, jsonValue = QJsonValue(coordinateArray); } +bool JsonHelper::loadGeoCoordinate(const QJsonValue& jsonValue, + bool altitudeRequired, + QGeoCoordinate& coordinate, + QString& errorString, + bool geoJsonFormat) +{ + return _loadGeoCoordinate(jsonValue, altitudeRequired, coordinate, errorString, geoJsonFormat); +} + +void JsonHelper::saveGeoCoordinate(const QGeoCoordinate& coordinate, + bool writeAltitude, + QJsonValue& jsonValue) +{ + _saveGeoCoordinate(coordinate, writeAltitude, jsonValue, false /* geoJsonFormat */); +} + +bool JsonHelper::loadGeoJsonCoordinate(const QJsonValue& jsonValue, + bool altitudeRequired, + QGeoCoordinate& coordinate, + QString& errorString) +{ + return _loadGeoCoordinate(jsonValue, altitudeRequired, coordinate, errorString, true /* geoJsonFormat */); +} + +void JsonHelper::saveGeoJsonCoordinate(const QGeoCoordinate& coordinate, + bool writeAltitude, + QJsonValue& jsonValue) +{ + _saveGeoCoordinate(coordinate, writeAltitude, jsonValue, true /* geoJsonFormat */); +} + bool JsonHelper::validateKeyTypes(const QJsonObject& jsonObject, const QStringList& keys, const QList& types, QString& errorString) { for (int i=0; i& keyInfo, QString& errorString); /// Loads a QGeoCoordinate + /// Stored as array [ lat, lon, alt ] /// @return false: validation failed - static bool loadGeoCoordinate(const QJsonValue& jsonValue, ///< json value to load from - bool altitudeRequired, ///< true: altitude must be specified - QGeoCoordinate& coordinate, ///< returned QGeoCordinate - QString& errorString); ///< returned error string if load failure + static bool loadGeoCoordinate(const QJsonValue& jsonValue, ///< json value to load from + bool altitudeRequired, ///< true: altitude must be specified + QGeoCoordinate& coordinate, ///< returned QGeoCordinate + QString& errorString, ///< returned error string if load failure + bool geoJsonFormat = false); ///< if true, use [lon, lat], [lat, lon] otherwise + + /// Saves a QGeoCoordinate + /// Stored as array [ lat, lon, alt ] + static void saveGeoCoordinate(const QGeoCoordinate& coordinate, ///< QGeoCoordinate to save + bool writeAltitude, ///< true: write altitude to json + QJsonValue& jsonValue); ///< json value to save to + + /// Loads a QGeoCoordinate + /// Stored as array [ lon, lat, alt ] + /// @return false: validation failed + static bool loadGeoJsonCoordinate(const QJsonValue& jsonValue, ///< json value to load from + bool altitudeRequired, ///< true: altitude must be specified + QGeoCoordinate& coordinate, ///< returned QGeoCordinate + QString& errorString); ///< returned error string if load failure + + /// Saves a QGeoCoordinate + /// Stored as array [ lon, lat, alt ] + static void saveGeoJsonCoordinate(const QGeoCoordinate& coordinate, ///< QGeoCoordinate to save + bool writeAltitude, ///< true: write altitude to json + QJsonValue& jsonValue); ///< json value to save to /// Loads a polygon from an array static bool loadPolygon(const QJsonArray& polygonArray, ///< Array of coordinates @@ -76,11 +98,6 @@ public: QObject* parent, ///< parent for newly allocated QGCQGeoCoordinates QString& errorString); ///< returned error string if load failure - /// Saves a QGeoCoordinate - static void saveGeoCoordinate(const QGeoCoordinate& coordinate, ///< QGeoCoordinate to save - bool writeAltitude, ///< true: write altitude to json - QJsonValue& jsonValue); ///< json value to save to - /// Loads a list of QGeoCoordinates from a json array /// @return false: validation failed static bool loadGeoCoordinateArray(const QJsonValue& jsonValue, ///< json value which contains points @@ -116,6 +133,15 @@ public: private: static QString _jsonValueTypeToString(QJsonValue::Type type); + static bool _loadGeoCoordinate(const QJsonValue& jsonValue, + bool altitudeRequired, + QGeoCoordinate& coordinate, + QString& errorString, + bool geoJsonFormat); + static void _saveGeoCoordinate(const QGeoCoordinate& coordinate, + bool writeAltitude, + QJsonValue& jsonValue, + bool geoJsonFormat); static const char* _enumStringsJsonKey; static const char* _enumValuesJsonKey; diff --git a/src/MissionManager/AirMapManager.cc b/src/MissionManager/AirMapManager.cc new file mode 100644 index 0000000000000000000000000000000000000000..d75772ab375ca72b4c1038e96b34547c32662439 --- /dev/null +++ b/src/MissionManager/AirMapManager.cc @@ -0,0 +1,981 @@ +/**************************************************************************** + * + * (c) 2017 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "AirMapManager.h" +#include "Vehicle.h" +#include "QmlObjectListModel.h" +#include "JsonHelper.h" +#include "SettingsManager.h" +#include "AppSettings.h" +#include "AirMapSettings.h" +#include "QGCQGeoCoordinate.h" +#include "QGCApplication.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace airmap; + +QGC_LOGGING_CATEGORY(AirMapManagerLog, "AirMapManagerLog") + + +void AirMapSharedState::setSettings(const Settings& settings) +{ + logout(); + _settings = settings; +} + +void AirMapSharedState::doRequestWithLogin(const Callback& callback) +{ + if (isLoggedIn()) { + callback(_loginToken); + } else { + login(); + _pendingRequests.enqueue(callback); + } +} + +void AirMapSharedState::login() +{ + if (isLoggedIn() || _isLoginInProgress) { + return; + } + _isLoginInProgress = true; + + if (_settings.userName == "") { //use anonymous login + + Authenticator::AuthenticateAnonymously::Params params; + params.id = ""; + _client->authenticator().authenticate_anonymously(params, + [this](const Authenticator::AuthenticateAnonymously::Result& result) { + if (!_isLoginInProgress) { // was logout() called in the meanwhile? + return; + } + if (result) { + qCDebug(AirMapManagerLog)<<"Successfully authenticated with AirMap: id="<< result.value().id.c_str(); + _loginToken = QString::fromStdString(result.value().id); + _processPendingRequests(); + } else { + _pendingRequests.clear(); + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Failed to authenticate with AirMap", + QString::fromStdString(result.error().message()), description); + } + }); + + } else { + + Authenticator::AuthenticateWithPassword::Params params; + params.oauth.username = _settings.userName.toStdString(); + params.oauth.password = _settings.password.toStdString(); + params.oauth.client_id = _settings.clientID.toStdString(); + params.oauth.device_id = "QGroundControl"; + _client->authenticator().authenticate_with_password(params, + [this](const Authenticator::AuthenticateWithPassword::Result& result) { + if (!_isLoginInProgress) { // was logout() called in the meanwhile? + return; + } + if (result) { + qCDebug(AirMapManagerLog)<<"Successfully authenticated with AirMap: id="<< result.value().id.c_str()<<", access=" + < isAlive(_instance); + _shared.client()->airspaces().search(params, + [this, isAlive](const Airspaces::Search::Result& result) { + + if (!isAlive.lock()) return; + if (_state != State::RetrieveItems) return; + + if (result) { + const std::vector& airspaces = result.value(); + qCDebug(AirMapManagerLog)<<"Successful search. Items:" << airspaces.size(); + for (const auto& airspace : airspaces) { + + const Geometry& geometry = airspace.geometry(); + switch(geometry.type()) { + case Geometry::Type::polygon: { + const Geometry::Polygon& polygon = geometry.details_for_polygon(); + _addPolygonToList(polygon, _polygonList); + } + break; + case Geometry::Type::multi_polygon: { + const Geometry::MultiPolygon& multiPolygon = geometry.details_for_multi_polygon(); + for (const auto& polygon : multiPolygon) { + _addPolygonToList(polygon, _polygonList); + } + } + break; + case Geometry::Type::point: { + const Geometry::Point& point = geometry.details_for_point(); + _circleList.append(new CircularAirspaceRestriction(QGeoCoordinate(point.latitude, point.longitude), 0.)); + // TODO: radius??? + } + break; + default: + qWarning() << "unsupported geometry type: "<<(int)geometry.type(); + break; + } + + } + + } else { + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Failed to retrieve Geofences", + QString::fromStdString(result.error().message()), description); + } + emit requestDone(true); + _state = State::Idle; + }); +} + +void AirMapRestrictionManager::_addPolygonToList(const airmap::Geometry::Polygon& polygon, QList& list) +{ + QVariantList polygonArray; + for (const auto& vertex : polygon.outer_ring.coordinates) { + QGeoCoordinate coord; + if (vertex.altitude) { + coord = QGeoCoordinate(vertex.latitude, vertex.longitude, vertex.altitude.get()); + } else { + coord = QGeoCoordinate(vertex.latitude, vertex.longitude); + } + polygonArray.append(QVariant::fromValue(coord)); + } + list.append(new PolygonAirspaceRestriction(polygonArray)); + + if (polygon.inner_rings.size() > 0) { + // no need to support those (they are rare, and in most cases, there's a more restrictive polygon filling the hole) + qCDebug(AirMapManagerLog) << "Polygon with holes. Size: "<& missionItems) +{ + if (!_shared.client()) { + qCDebug(AirMapManagerLog) << "No AirMap client instance. Will not create a flight"; + return; + } + + if (_state != State::Idle) { + qCWarning(AirMapManagerLog) << "AirMapFlightManager::createFlight: State not idle"; + return; + } + + _flight.reset(); + + // get the flight trajectory + for(const auto &item : missionItems) { + switch(item->command()) { + case MAV_CMD_NAV_WAYPOINT: + case MAV_CMD_NAV_LAND: + case MAV_CMD_NAV_TAKEOFF: + // TODO: others too? + { + // TODO: handle different coordinate frames? + double lat = item->param5(); + double lon = item->param6(); + double alt = item->param7(); + _flight.coords.append(QGeoCoordinate(lat, lon, alt)); + if (alt > _flight.maxAltitude) { + _flight.maxAltitude = alt; + } + if (item->command() == MAV_CMD_NAV_TAKEOFF) { + _flight.takeoffCoord = _flight.coords.last(); + } + } + break; + default: + break; + } + } + if (_flight.coords.empty()) { + return; + } + + _flight.maxAltitude += 5; // add a safety buffer + + + if (_pilotID == "") { + // need to get the pilot id before uploading the flight + qCDebug(AirMapManagerLog) << "Getting pilot ID"; + _state = State::GetPilotID; + std::weak_ptr isAlive(_instance); + _shared.doRequestWithLogin([this, isAlive](const QString& login_token) { + if (!isAlive.lock()) return; + + Pilots::Authenticated::Parameters params; + params.authorization = login_token.toStdString(); + _shared.client()->pilots().authenticated(params, [this, isAlive](const Pilots::Authenticated::Result& result) { + + if (!isAlive.lock()) return; + if (_state != State::GetPilotID) return; + + if (result) { + _pilotID = QString::fromStdString(result.value().id); + qCDebug(AirMapManagerLog) << "Got Pilot ID:"<<_pilotID; + _uploadFlight(); + } else { + _flightPermitStatus = AirspaceAuthorization::PermitUnknown; + emit flightPermitStatusChanged(); + _state = State::Idle; + + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Failed to create Flight Plan", + QString::fromStdString(result.error().message()), description); + } + }); + }); + } else { + _uploadFlight(); + } + + _flightPermitStatus = AirspaceAuthorization::PermitPending; + emit flightPermitStatusChanged(); +} + +void AirMapFlightManager::_endFirstFlight() +{ + // it could be that AirMap still has an open pending flight, but we don't know the flight ID. + // As there can only be one, we query the flights that end in the future, and close it if there's one. + + _state = State::EndFirstFlight; + + Flights::Search::Parameters params; + params.pilot_id = _pilotID.toStdString(); + params.end_after = Clock::universal_time() - Hours{1}; + + std::weak_ptr isAlive(_instance); + _shared.client()->flights().search(params, [this, isAlive](const Flights::Search::Result& result) { + if (!isAlive.lock()) return; + if (_state != State::EndFirstFlight) return; + + if (result && result.value().flights.size() > 0) { + + Q_ASSERT(_shared.loginToken() != ""); // at this point we know the user is logged in (we queried the pilot id) + + Flights::EndFlight::Parameters params; + params.authorization = _shared.loginToken().toStdString(); + params.id = result.value().flights[0].id; // pick the first flight (TODO: match the vehicle id) + _shared.client()->flights().end_flight(params, [this, isAlive](const Flights::EndFlight::Result& result) { + if (!isAlive.lock()) return; + if (_state != State::EndFirstFlight) return; + + if (!result) { + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Failed to end first Flight", + QString::fromStdString(result.error().message()), description); + } + _state = State::Idle; + _uploadFlight(); + }); + + } else { + _state = State::Idle; + _uploadFlight(); + } + }); +} + +void AirMapFlightManager::_uploadFlight() +{ + if (_pendingFlightId != "") { + // we need to end an existing flight first + _endFlight(_pendingFlightId); + return; + } + + if (_noFlightCreatedYet) { + _endFirstFlight(); + _noFlightCreatedYet = false; + return; + } + + qCDebug(AirMapManagerLog) << "uploading flight"; + _state = State::FlightUpload; + + std::weak_ptr isAlive(_instance); + _shared.doRequestWithLogin([this, isAlive](const QString& login_token) { + + if (!isAlive.lock()) return; + if (_state != State::FlightUpload) return; + + FlightPlans::Create::Parameters params; + params.max_altitude = _flight.maxAltitude; + params.buffer = 2.f; + params.latitude = _flight.takeoffCoord.latitude(); + params.longitude = _flight.takeoffCoord.longitude(); + params.pilot.id = _pilotID.toStdString(); + params.start_time = Clock::universal_time() + Minutes{5}; + params.end_time = Clock::universal_time() + Hours{2}; // TODO: user-configurable? + params.rulesets = { // TODO: which ones to use? + "che_drone_rules", + "che_notam", + "che_airmap_rules", + "che_nature_preserve" + }; + + // geometry: LineString + Geometry::LineString lineString; + for (const auto& qcoord : _flight.coords) { + Geometry::Coordinate coord; + coord.latitude = qcoord.latitude(); + coord.longitude = qcoord.longitude(); + lineString.coordinates.push_back(coord); + } + + params.geometry = Geometry(lineString); + params.authorization = login_token.toStdString(); + _flight.coords.clear(); + + _shared.client()->flight_plans().create_by_polygon(params, [this, isAlive](const FlightPlans::Create::Result& result) { + if (!isAlive.lock()) return; + if (_state != State::FlightUpload) return; + + if (result) { + _pendingFlightPlan = QString::fromStdString(result.value().id); + qCDebug(AirMapManagerLog) << "Flight Plan created:"<<_pendingFlightPlan; + + _checkForValidBriefing(); + + } else { + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Flight Plan creation failed", + QString::fromStdString(result.error().message()), description); + } + + }); + + }); +} + +void AirMapFlightManager::_checkForValidBriefing() +{ + _state = State::FlightBrief; + FlightPlans::RenderBriefing::Parameters params; + params.authorization = _shared.loginToken().toStdString(); + params.id = _pendingFlightPlan.toStdString(); + std::weak_ptr isAlive(_instance); + _shared.client()->flight_plans().render_briefing(params, [this, isAlive](const FlightPlans::RenderBriefing::Result& result) { + if (!isAlive.lock()) return; + if (_state != State::FlightBrief) return; + + if (result) { + bool allValid = true; + for (const auto& validation : result.value().evaluation.validations) { + if (validation.status != Evaluation::Validation::Status::valid) { + emit error(QString("%1 registration identifier is invalid: %2").arg( + QString::fromStdString(validation.authority.name)).arg(QString::fromStdString(validation.message)), "", ""); + allValid = false; + } + } + if (allValid) { + _submitPendingFlightPlan(); + } else { + _flightPermitStatus = AirspaceAuthorization::PermitRejected; + emit flightPermitStatusChanged(); + _state = State::Idle; + } + + } else { + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Brief Request failed", + QString::fromStdString(result.error().message()), description); + _state = State::Idle; + } + }); +} + +void AirMapFlightManager::_submitPendingFlightPlan() +{ + _state = State::FlightSubmit; + FlightPlans::Submit::Parameters params; + params.authorization = _shared.loginToken().toStdString(); + params.id = _pendingFlightPlan.toStdString(); + std::weak_ptr isAlive(_instance); + _shared.client()->flight_plans().submit(params, [this, isAlive](const FlightPlans::Submit::Result& result) { + if (!isAlive.lock()) return; + if (_state != State::FlightSubmit) return; + + if (result) { + _pendingFlightId = QString::fromStdString(result.value().flight_id.get()); + _state = State::FlightPolling; + _pollBriefing(); + + } else { + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Failed to submit Flight Plan", + QString::fromStdString(result.error().message()), description); + _state = State::Idle; + } + }); +} + +void AirMapFlightManager::_pollBriefing() +{ + if (_state != State::FlightPolling) { + qCWarning(AirMapManagerLog) << "AirMapFlightManager::_pollBriefing: not in polling state"; + return; + } + + FlightPlans::RenderBriefing::Parameters params; + params.authorization = _shared.loginToken().toStdString(); + params.id = _pendingFlightPlan.toStdString(); + std::weak_ptr isAlive(_instance); + _shared.client()->flight_plans().render_briefing(params, [this, isAlive](const FlightPlans::RenderBriefing::Result& result) { + if (!isAlive.lock()) return; + if (_state != State::FlightPolling) return; + + if (result) { + const FlightPlan::Briefing& briefing = result.value(); + qCDebug(AirMapManagerLog) << "flight polling/briefing response"; + bool rejected = false; + bool accepted = false; + bool pending = false; + for (const auto& authorization : briefing.evaluation.authorizations) { + switch (authorization.status) { + case Evaluation::Authorization::Status::accepted: + case Evaluation::Authorization::Status::accepted_upon_submission: + accepted = true; + break; + case Evaluation::Authorization::Status::rejected: + case Evaluation::Authorization::Status::rejected_upon_submission: + rejected = true; + break; + case Evaluation::Authorization::Status::pending: + pending = true; + break; + } + } + + if (briefing.evaluation.authorizations.size() == 0) { + // if we don't get any authorizations, we assume it's accepted + accepted = true; + } + + qCDebug(AirMapManagerLog) << "flight approval: accepted=" << accepted << "rejected" << rejected << "pending" << pending; + + if ((rejected || accepted) && !pending) { + if (rejected) { // rejected has priority + _flightPermitStatus = AirspaceAuthorization::PermitRejected; + } else { + _flightPermitStatus = AirspaceAuthorization::PermitAccepted; + } + _currentFlightId = _pendingFlightId; + _pendingFlightPlan = ""; + emit flightPermitStatusChanged(); + _state = State::Idle; + } else { + // wait until we send the next polling request + _pollTimer.setSingleShot(true); + _pollTimer.start(2000); + } + } else { + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Brief Request failed", + QString::fromStdString(result.error().message()), description); + _state = State::Idle; + } + }); +} + +void AirMapFlightManager::endFlight() +{ + if (_currentFlightId.length() == 0) { + return; + } + if (_state != State::Idle) { + qCWarning(AirMapManagerLog) << "AirMapFlightManager::endFlight: State not idle"; + return; + } + _endFlight(_currentFlightId); + + _flightPermitStatus = AirspaceAuthorization::PermitUnknown; + emit flightPermitStatusChanged(); +} + +void AirMapFlightManager::_endFlight(const QString& flightID) +{ + qCDebug(AirMapManagerLog) << "ending flight" << flightID; + + _state = State::FlightEnd; + + Q_ASSERT(_shared.loginToken() != ""); // Since we have a flight ID, we need to be logged in + + Flights::EndFlight::Parameters params; + params.authorization = _shared.loginToken().toStdString(); + params.id = flightID.toStdString(); + std::weak_ptr isAlive(_instance); + _shared.client()->flights().end_flight(params, [this, isAlive](const Flights::EndFlight::Result& result) { + if (!isAlive.lock()) return; + if (_state != State::FlightEnd) return; + + _state = State::Idle; + _pendingFlightId = ""; + _pendingFlightPlan = ""; + _currentFlightId = ""; + if (result) { + if (!_flight.coords.empty()) { + _uploadFlight(); + } + } else { + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Failed to end Flight", + QString::fromStdString(result.error().message()), description); + } + }); +} + + +AirMapTelemetry::AirMapTelemetry(AirMapSharedState& shared) +: _shared(shared) +{ +} + +void AirMapTelemetry::vehicleMavlinkMessageReceived(const mavlink_message_t& message) +{ + switch (message.msgid) { + case MAVLINK_MSG_ID_GLOBAL_POSITION_INT: + _handleGlobalPositionInt(message); + break; + case MAVLINK_MSG_ID_GPS_RAW_INT: + _handleGPSRawInt(message); + break; + } + +} + +bool AirMapTelemetry::isTelemetryStreaming() const +{ + return _state == State::Streaming; +} + +void AirMapTelemetry::_handleGPSRawInt(const mavlink_message_t& message) +{ + if (!isTelemetryStreaming()) { + return; + } + + mavlink_gps_raw_int_t gps_raw; + mavlink_msg_gps_raw_int_decode(&message, &gps_raw); + + if (gps_raw.eph == UINT16_MAX) { + _lastHdop = 1.f; + } else { + _lastHdop = gps_raw.eph / 100.f; + } +} + +void AirMapTelemetry::_handleGlobalPositionInt(const mavlink_message_t& message) +{ + if (!isTelemetryStreaming()) { + return; + } + + mavlink_global_position_int_t globalPosition; + mavlink_msg_global_position_int_decode(&message, &globalPosition); + + Telemetry::Position position{ + milliseconds_since_epoch(Clock::universal_time()), + (double) globalPosition.lat / 1e7, + (double) globalPosition.lon / 1e7, + (float) globalPosition.alt / 1000.f, + (float) globalPosition.relative_alt / 1000.f, + _lastHdop + }; + Telemetry::Speed speed{ + milliseconds_since_epoch(Clock::universal_time()), + globalPosition.vx / 100.f, + globalPosition.vy / 100.f, + globalPosition.vz / 100.f + }; + + Flight flight; + flight.id = _flightID.toStdString(); + _shared.client()->telemetry().submit_updates(flight, _key, + {Telemetry::Update{position}, Telemetry::Update{speed}}); + +} + +void AirMapTelemetry::startTelemetryStream(const QString& flightID) +{ + if (_state != State::Idle) { + qCWarning(AirMapManagerLog) << "Not starting telemetry: not in idle state:" << (int)_state; + return; + } + + qCInfo(AirMapManagerLog) << "Starting Telemetry stream with flightID" << flightID; + + _state = State::StartCommunication; + _flightID = flightID; + + Flights::StartFlightCommunications::Parameters params; + params.authorization = _shared.loginToken().toStdString(); + params.id = _flightID.toStdString(); + std::weak_ptr isAlive(_instance); + _shared.client()->flights().start_flight_communications(params, [this, isAlive](const Flights::StartFlightCommunications::Result& result) { + if (!isAlive.lock()) return; + if (_state != State::StartCommunication) return; + + if (result) { + _key = result.value().key; + _state = State::Streaming; + } else { + _state = State::Idle; + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Failed to start telemetry streaming", + QString::fromStdString(result.error().message()), description); + } + }); +} + +void AirMapTelemetry::stopTelemetryStream() +{ + if (_state == State::Idle) { + return; + } + qCInfo(AirMapManagerLog) << "Stopping Telemetry stream with flightID" << _flightID; + + _state = State::EndCommunication; + Flights::EndFlightCommunications::Parameters params; + params.authorization = _shared.loginToken().toStdString(); + params.id = _flightID.toStdString(); + std::weak_ptr isAlive(_instance); + _shared.client()->flights().end_flight_communications(params, [this, isAlive](const Flights::EndFlightCommunications::Result& result) { + Q_UNUSED(result); + if (!isAlive.lock()) return; + if (_state != State::EndCommunication) return; + + _key = ""; + _state = State::Idle; + }); +} + +AirMapTrafficMonitor::~AirMapTrafficMonitor() +{ + stop(); +} + +void AirMapTrafficMonitor::startConnection(const QString& flightID) +{ + _flightID = flightID; + std::weak_ptr isAlive(_instance); + auto handler = [this, isAlive](const Traffic::Monitor::Result& result) { + if (!isAlive.lock()) return; + if (result) { + _monitor = result.value(); + _subscriber = std::make_shared( + std::bind(&AirMapTrafficMonitor::_update, this, std::placeholders::_1, std::placeholders::_2)); + _monitor->subscribe(_subscriber); + } else { + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Failed to start Traffic Monitoring", + QString::fromStdString(result.error().message()), description); + } + }; + + Traffic::Monitor::Params params{flightID.toStdString(), _shared.loginToken().toStdString()}; + _shared.client()->traffic().monitor(params, handler); +} + +void AirMapTrafficMonitor::_update(Traffic::Update::Type type, const std::vector& update) +{ + qCDebug(AirMapManagerLog) << "Traffic update with" << update.size() << "elements"; + + if (type != Traffic::Update::Type::situational_awareness) + return; // currently we're only interested in situational awareness + + for (const auto& traffic : update) { + QString traffic_id = QString::fromStdString(traffic.id); + QString vehicle_id = QString::fromStdString(traffic.aircraft_id); + emit trafficUpdate(traffic_id, vehicle_id, QGeoCoordinate(traffic.latitude, traffic.longitude, traffic.altitude), + traffic.heading); + } +} + +void AirMapTrafficMonitor::stop() +{ + if (_monitor) { + _monitor->unsubscribe(_subscriber); + _subscriber.reset(); + _monitor.reset(); + } +} + +AirMapManagerPerVehicle::AirMapManagerPerVehicle(AirMapSharedState& shared, const Vehicle& vehicle, + QGCToolbox& toolbox) + : AirspaceManagerPerVehicle(vehicle), _shared(shared), + _flightManager(shared), _telemetry(shared), _trafficMonitor(shared), + _toolbox(toolbox) +{ + connect(&_flightManager, &AirMapFlightManager::flightPermitStatusChanged, + this, &AirMapManagerPerVehicle::flightPermitStatusChanged); + connect(&_flightManager, &AirMapFlightManager::flightPermitStatusChanged, + this, &AirMapManagerPerVehicle::_flightPermitStatusChanged); + connect(&_flightManager, &AirMapFlightManager::error, this, &AirMapManagerPerVehicle::error); + connect(&_telemetry, &AirMapTelemetry::error, this, &AirMapManagerPerVehicle::error); + connect(&_trafficMonitor, &AirMapTrafficMonitor::error, this, &AirMapManagerPerVehicle::error); + connect(&_trafficMonitor, &AirMapTrafficMonitor::trafficUpdate, this, &AirspaceManagerPerVehicle::trafficUpdate); +} + +void AirMapManagerPerVehicle::createFlight(const QList& missionItems) +{ + if (!_shared.client()) { + qCDebug(AirMapManagerLog) << "No AirMap client instance. Will not create a flight"; + return; + } + _flightManager.createFlight(missionItems); +} + +AirspaceAuthorization::PermitStatus AirMapManagerPerVehicle::flightPermitStatus() const +{ + return _flightManager.flightPermitStatus(); +} + +void AirMapManagerPerVehicle::startTelemetryStream() +{ + if (_flightManager.flightID() != "") { + _telemetry.startTelemetryStream(_flightManager.flightID()); + } +} + +void AirMapManagerPerVehicle::stopTelemetryStream() +{ + _telemetry.stopTelemetryStream(); +} + +bool AirMapManagerPerVehicle::isTelemetryStreaming() const +{ + return _telemetry.isTelemetryStreaming(); +} + +void AirMapManagerPerVehicle::endFlight() +{ + _flightManager.endFlight(); + _trafficMonitor.stop(); +} + +void AirMapManagerPerVehicle::vehicleMavlinkMessageReceived(const mavlink_message_t& message) +{ + if (isTelemetryStreaming()) { + _telemetry.vehicleMavlinkMessageReceived(message); + } +} + +void AirMapManagerPerVehicle::_flightPermitStatusChanged() +{ + // activate traffic alerts + if (_flightManager.flightPermitStatus() == AirspaceAuthorization::PermitAccepted) { + qCDebug(AirMapManagerLog) << "Subscribing to Traffic Alerts"; + // since we already created the flight, we know that we have a valid login token + _trafficMonitor.startConnection(_flightManager.flightID()); + } +} + +AirMapManager::AirMapManager(QGCApplication* app, QGCToolbox* toolbox) + : AirspaceManager(app, toolbox) +{ + _logger = std::make_shared(); + qt::register_types(); // TODO: still needed? + _logger->logging_category().setEnabled(QtDebugMsg, true); + _logger->logging_category().setEnabled(QtInfoMsg, true); + _logger->logging_category().setEnabled(QtWarningMsg, true); + _dispatchingLogger = std::make_shared(_logger); + + connect(&_shared, &AirMapSharedState::error, this, &AirMapManager::_error); +} + +AirMapManager::~AirMapManager() +{ + if (_shared.client()) { + delete _shared.client(); + } +} + +void AirMapManager::setToolbox(QGCToolbox* toolbox) +{ + AirspaceManager::setToolbox(toolbox); + AirMapSettings* ap = toolbox->settingsManager()->airMapSettings(); + + connect(ap->apiKey(), &Fact::rawValueChanged, this, &AirMapManager::_settingsChanged); + connect(ap->clientID(), &Fact::rawValueChanged, this, &AirMapManager::_settingsChanged); + connect(ap->userName(), &Fact::rawValueChanged, this, &AirMapManager::_settingsChanged); + connect(ap->password(), &Fact::rawValueChanged, this, &AirMapManager::_settingsChanged); + + _settingsChanged(); + +} + +void AirMapManager::_error(const QString& what, const QString& airmapdMessage, const QString& airmapdDetails) +{ + qCDebug(AirMapManagerLog) << "Error: "<showMessage(QString("AirMap Error: %1. %2").arg(what).arg(airmapdMessage)); +} + +void AirMapManager::requestWeatherUpdate(const QGeoCoordinate& coordinate) +{ + if (!_shared.client()) { + qCDebug(AirMapManagerLog) << "No AirMap client instance. Not updating Weather information"; + emit weatherUpdate(false, QGeoCoordinate{}, WeatherInformation{}); + return; + } + + Status::GetStatus::Parameters params; + params.longitude = coordinate.longitude(); + params.latitude = coordinate.latitude(); + params.weather = true; + + _shared.client()->status().get_status_by_point(params, [this, coordinate](const Status::GetStatus::Result& result) { + + if (result) { + + const Status::Weather& weather = result.value().weather; + WeatherInformation weatherUpdateInfo; + + weatherUpdateInfo.condition = QString::fromStdString(weather.condition); + weatherUpdateInfo.icon = QString::fromStdString(weather.icon); + weatherUpdateInfo.windHeading = weather.wind.heading; + weatherUpdateInfo.windSpeed = weather.wind.speed; + weatherUpdateInfo.windGusting = weather.wind.gusting; + weatherUpdateInfo.temperature = weather.temperature; + weatherUpdateInfo.humidity = weather.humidity; + weatherUpdateInfo.visibility = weather.visibility; + weatherUpdateInfo.precipitation = weather.precipitation; + emit weatherUpdate(true, coordinate, weatherUpdateInfo); + + } else { + emit weatherUpdate(false, coordinate, WeatherInformation{}); + } + }); + +} + +void AirMapManager::_settingsChanged() +{ + qCDebug(AirMapManagerLog) << "AirMap settings changed"; + + AirMapSettings* ap = _toolbox->settingsManager()->airMapSettings(); + + AirMapSharedState::Settings settings; + settings.apiKey = ap->apiKey()->rawValueString(); + bool apiKeyChanged = settings.apiKey != _shared.settings().apiKey; + settings.clientID = ap->clientID()->rawValueString(); + settings.userName = ap->userName()->rawValueString(); + settings.password = ap->password()->rawValueString(); + _shared.setSettings(settings); + + // need to re-create the client if the API key changed + if (_shared.client() && apiKeyChanged) { + delete _shared.client(); + _shared.setClient(nullptr); + } + + + if (!_shared.client() && settings.apiKey != "") { + qCDebug(AirMapManagerLog) << "Creating AirMap client"; + + auto credentials = Credentials{}; + credentials.api_key = _shared.settings().apiKey.toStdString(); + auto configuration = Client::default_staging_configuration(credentials); + qt::Client::create(configuration, _dispatchingLogger, this, [this, ap](const qt::Client::CreateResult& result) { + if (result) { + qCDebug(AirMapManagerLog) << "Successfully created airmap::qt::Client instance"; + _shared.setClient(result.value()); + + } else { + qWarning("Failed to create airmap::qt::Client instance"); + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + _error("Failed to create airmap::qt::Client instance", + QString::fromStdString(result.error().message()), description); + } + }); + } +} + +AirspaceManagerPerVehicle* AirMapManager::instantiateVehicle(const Vehicle& vehicle) +{ + AirMapManagerPerVehicle* manager = new AirMapManagerPerVehicle(_shared, vehicle, *_toolbox); + connect(manager, &AirMapManagerPerVehicle::error, this, &AirMapManager::_error); + return manager; +} + +AirspaceRestrictionProvider* AirMapManager::instantiateRestrictionProvider() +{ + AirMapRestrictionManager* restrictionManager = new AirMapRestrictionManager(_shared); + connect(restrictionManager, &AirMapRestrictionManager::error, this, &AirMapManager::_error); + return restrictionManager; +} + diff --git a/src/MissionManager/AirMapManager.h b/src/MissionManager/AirMapManager.h new file mode 100644 index 0000000000000000000000000000000000000000..99f85ee53a2a1a9b7b5db5d4e4e18a37d67bab05 --- /dev/null +++ b/src/MissionManager/AirMapManager.h @@ -0,0 +1,367 @@ +/**************************************************************************** + * + * (c) 2017 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include "QGCToolbox.h" +#include "QGCLoggingCategory.h" +#include "QmlObjectListModel.h" +#include "MissionItem.h" +#include "MultiVehicleManager.h" +#include "AirspaceManagement.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(AirMapManagerLog) + + +/** + * @class LifetimeChecker + * Base class which helps to check if an object instance still exists. + * A subclass can take a weak pointer from _instance and then check if the object was deleted. + * This is used in callbacks that access 'this', but the instance might already be deleted (e.g. vehicle disconnect). + */ +class LifetimeChecker +{ +public: + LifetimeChecker() : _instance(this, [](void*){}) { } + virtual ~LifetimeChecker() = default; + +protected: + std::shared_ptr _instance; +}; + +/** + * @class AirMapSharedState + * contains state & settings that need to be shared (such as login) + */ +class AirMapSharedState : public QObject +{ + Q_OBJECT +public: + struct Settings { + QString apiKey; + + // login credentials + QString clientID; + QString userName; ///< use anonymous login if empty + QString password; + }; + + void setSettings(const Settings& settings); + const Settings& settings() const { return _settings; } + + void setClient(airmap::qt::Client* client) { _client = client; } + + /** + * Get the current client instance. It can be NULL. If not NULL, it implies + * there's an API key set. + */ + airmap::qt::Client* client() const { return _client; } + + bool hasAPIKey() const { return _settings.apiKey != ""; } + + bool isLoggedIn() const { return _loginToken != ""; } + + using Callback = std::function; + + /** + * Do a request that requires user login: if not yet logged in, the request is queued and + * processed after successful login, otherwise it's executed directly. + */ + void doRequestWithLogin(const Callback& callback); + + void login(); + + void logout(); + + const QString& loginToken() const { return _loginToken; } + +signals: + void error(const QString& what, const QString& airmapdMessage, const QString& airmapdDetails); + +private: + void _processPendingRequests(); + + bool _isLoginInProgress = false; + QString _loginToken; ///< login token: empty when not logged in + + airmap::qt::Client* _client = nullptr; + + Settings _settings; + + QQueue _pendingRequests; ///< pending requests that are processed after a successful login +}; + + +/// class to download polygons from AirMap +class AirMapRestrictionManager : public AirspaceRestrictionProvider, public LifetimeChecker +{ + Q_OBJECT +public: + AirMapRestrictionManager(AirMapSharedState& shared); + + void setROI(const QGeoCoordinate& center, double radiusMeters) override; + +signals: + void error(const QString& what, const QString& airmapdMessage, const QString& airmapdDetails); + +private: + + static void _addPolygonToList(const airmap::Geometry::Polygon& polygon, QList& list); + + enum class State { + Idle, + RetrieveItems, + }; + + State _state = State::Idle; + AirMapSharedState& _shared; +}; + + +/// class to upload a flight +class AirMapFlightManager : public QObject, public LifetimeChecker +{ + Q_OBJECT +public: + AirMapFlightManager(AirMapSharedState& shared); + + /// Send flight path to AirMap + void createFlight(const QList& missionItems); + + AirspaceAuthorization::PermitStatus flightPermitStatus() const { return _flightPermitStatus; } + + const QString& flightID() const { return _currentFlightId; } + +public slots: + void endFlight(); + +signals: + void error(const QString& what, const QString& airmapdMessage, const QString& airmapdDetails); + void flightPermitStatusChanged(); + +private slots: + void _pollBriefing(); + +private: + + /** + * upload flight stored in _flight + */ + void _uploadFlight(); + + /** + * query the active flights and end the first one (because only a single flight can be active at a time). + */ + void _endFirstFlight(); + + /** + * implementation of endFlight() + */ + void _endFlight(const QString& flightID); + + /** + * check if the briefing response is valid and call _submitPendingFlightPlan() if it is. + */ + void _checkForValidBriefing(); + + void _submitPendingFlightPlan(); + + enum class State { + Idle, + GetPilotID, + FlightUpload, + FlightBrief, + FlightSubmit, + FlightPolling, // poll & check for approval + FlightEnd, + EndFirstFlight, // get a list of open flights & end the first one (because there can only be 1 active at a time) + }; + struct Flight { + QList coords; + QGeoCoordinate takeoffCoord; + float maxAltitude = 0; + + void reset() { + coords.clear(); + maxAltitude = 0; + } + }; + Flight _flight; ///< flight pending to be uploaded + + State _state = State::Idle; + AirMapSharedState& _shared; + QString _currentFlightId; ///< Flight ID, empty if there is none + QString _pendingFlightId; ///< current flight ID, not necessarily accepted yet (once accepted, it's equal to _currentFlightId) + QString _pendingFlightPlan; ///< current flight plan, waiting to be submitted + AirspaceAuthorization::PermitStatus _flightPermitStatus = AirspaceAuthorization::PermitUnknown; + QString _pilotID; ///< Pilot ID in the form "auth0|abc123" + bool _noFlightCreatedYet = true; + QTimer _pollTimer; ///< timer to poll for approval check +}; + +/// class to send telemetry data to AirMap +class AirMapTelemetry : public QObject, public LifetimeChecker +{ + Q_OBJECT +public: + AirMapTelemetry(AirMapSharedState& shared); + virtual ~AirMapTelemetry() = default; + + /** + * Setup the connection to start sending telemetry + */ + void startTelemetryStream(const QString& flightID); + + void stopTelemetryStream(); + + bool isTelemetryStreaming() const; + +signals: + void error(const QString& what, const QString& airmapdMessage, const QString& airmapdDetails); + +public slots: + void vehicleMavlinkMessageReceived(const mavlink_message_t& message); + +private: + + void _handleGlobalPositionInt(const mavlink_message_t& message); + void _handleGPSRawInt(const mavlink_message_t& message); + + enum class State { + Idle, + StartCommunication, + EndCommunication, + Streaming, + }; + + State _state = State::Idle; + + AirMapSharedState& _shared; + std::string _key; ///< key for AES encryption (16 bytes) + QString _flightID; + + float _lastHdop = 1.f; +}; + + +class AirMapTrafficMonitor : public QObject, public LifetimeChecker +{ + Q_OBJECT +public: + AirMapTrafficMonitor(AirMapSharedState& shared) + : _shared(shared) + { + } + virtual ~AirMapTrafficMonitor(); + + void startConnection(const QString& flightID); + + void stop(); + +signals: + void error(const QString& what, const QString& airmapdMessage, const QString& airmapdDetails); + void trafficUpdate(QString traffic_id, QString vehicle_id, QGeoCoordinate location, float heading); + +private: + void _update(airmap::Traffic::Update::Type type, const std::vector& update); + +private: + QString _flightID; + AirMapSharedState& _shared; + std::shared_ptr _monitor; + std::shared_ptr _subscriber; +}; + + + +/// AirMap per vehicle management class. +class AirMapManagerPerVehicle : public AirspaceManagerPerVehicle +{ + Q_OBJECT +public: + AirMapManagerPerVehicle(AirMapSharedState& shared, const Vehicle& vehicle, QGCToolbox& toolbox); + virtual ~AirMapManagerPerVehicle() = default; + + + void createFlight(const QList& missionItems) override; + + AirspaceAuthorization::PermitStatus flightPermitStatus() const override; + + void startTelemetryStream() override; + + void stopTelemetryStream() override; + + bool isTelemetryStreaming() const override; + +signals: + void error(const QString& what, const QString& airmapdMessage, const QString& airmapdDetails); + +public slots: + void endFlight() override; + +protected slots: + virtual void vehicleMavlinkMessageReceived(const mavlink_message_t& message) override; +private slots: + void _flightPermitStatusChanged(); +private: + AirMapSharedState& _shared; + + AirMapFlightManager _flightManager; + AirMapTelemetry _telemetry; + AirMapTrafficMonitor _trafficMonitor; + + QGCToolbox& _toolbox; +}; + + +class AirMapManager : public AirspaceManager +{ + Q_OBJECT + +public: + AirMapManager(QGCApplication* app, QGCToolbox* toolbox); + virtual ~AirMapManager(); + + void setToolbox(QGCToolbox* toolbox) override; + + AirspaceManagerPerVehicle* instantiateVehicle(const Vehicle& vehicle) override; + + AirspaceRestrictionProvider* instantiateRestrictionProvider() override; + + QString name() const override { return "AirMap"; } + + void requestWeatherUpdate(const QGeoCoordinate& coordinate) override; + +private slots: + void _error(const QString& what, const QString& airmapdMessage, const QString& airmapdDetails); + + void _settingsChanged(); +private: + + AirMapSharedState _shared; + + std::shared_ptr _logger; + std::shared_ptr _dispatchingLogger; +}; + + diff --git a/src/MissionManager/AirspaceController.cc b/src/MissionManager/AirspaceController.cc new file mode 100644 index 0000000000000000000000000000000000000000..d81d76301408a86bb3bc470ed428471a78fe7898 --- /dev/null +++ b/src/MissionManager/AirspaceController.cc @@ -0,0 +1,20 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "AirspaceController.h" +#include "AirspaceManagement.h" +#include "QGCApplication.h" +#include "QGCQGeoCoordinate.h" + +AirspaceController::AirspaceController(QObject* parent) + : QObject(parent) + , _manager(qgcApp()->toolbox()->airspaceManager()) +{ +} + diff --git a/src/MissionManager/AirspaceController.h b/src/MissionManager/AirspaceController.h new file mode 100644 index 0000000000000000000000000000000000000000..b97c6a86a530dc3123efe493c220eb14d76a93c4 --- /dev/null +++ b/src/MissionManager/AirspaceController.h @@ -0,0 +1,36 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include "AirspaceManagement.h" +#include "QGCMapPolygon.h" + +class AirspaceController : public QObject +{ + Q_OBJECT + +public: + AirspaceController(QObject* parent = NULL); + ~AirspaceController() = default; + + Q_PROPERTY(QmlObjectListModel* polygons READ polygons CONSTANT) ///< List of PolygonAirspaceRestriction objects + Q_PROPERTY(QmlObjectListModel* circles READ circles CONSTANT) ///< List of CircularAirspaceRestriction objects + + Q_INVOKABLE void setROI(QGeoCoordinate center, double radius) { _manager->setROI(center, radius); } + + QmlObjectListModel* polygons() { return _manager->polygonRestrictions(); } + QmlObjectListModel* circles() { return _manager->circularRestrictions(); } + + Q_PROPERTY(QString providerName READ providerName CONSTANT) + + QString providerName() { return _manager->name(); } +private: + AirspaceManager* _manager; +}; diff --git a/src/MissionManager/AirspaceManagement.cc b/src/MissionManager/AirspaceManagement.cc new file mode 100644 index 0000000000000000000000000000000000000000..6b0aa1d9d4e3631eac53367de1383ed4b09c6d00 --- /dev/null +++ b/src/MissionManager/AirspaceManagement.cc @@ -0,0 +1,121 @@ +/**************************************************************************** + * + * (c) 2017 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + + +#include "AirspaceManagement.h" +#include + +QGC_LOGGING_CATEGORY(AirspaceManagementLog, "AirspaceManagementLog") + + +AirspaceRestriction::AirspaceRestriction(QObject* parent) + : QObject(parent) +{ +} + +PolygonAirspaceRestriction::PolygonAirspaceRestriction(const QVariantList& polygon, QObject* parent) + : AirspaceRestriction(parent) + , _polygon(polygon) +{ + +} + +CircularAirspaceRestriction::CircularAirspaceRestriction(const QGeoCoordinate& center, double radius, QObject* parent) + : AirspaceRestriction(parent) + , _center(center) + , _radius(radius) +{ + +} + + +AirspaceManager::AirspaceManager(QGCApplication* app, QGCToolbox* toolbox) + : QGCTool(app, toolbox) +{ + _roiUpdateTimer.setInterval(2000); + _roiUpdateTimer.setSingleShot(true); + connect(&_roiUpdateTimer, &QTimer::timeout, this, &AirspaceManager::_updateToROI); + qmlRegisterUncreatableType("QGroundControl", 1, 0, "AirspaceAuthorization", "Reference only"); + qRegisterMetaType(); +} + +AirspaceManager::~AirspaceManager() +{ + if (_restrictionsProvider) { + delete _restrictionsProvider; + } + _polygonRestrictions.clearAndDeleteContents(); + _circleRestrictions.clearAndDeleteContents(); +} + +void AirspaceManager::setToolbox(QGCToolbox* toolbox) +{ + QGCTool::setToolbox(toolbox); + + // we should not call virtual methods in the constructor, so we instantiate the restriction provider here + _restrictionsProvider = instantiateRestrictionProvider(); + if (_restrictionsProvider) { + connect(_restrictionsProvider, &AirspaceRestrictionProvider::requestDone, this, + &AirspaceManager::_restrictionsUpdated); + } +} + +void AirspaceManager::setROI(const QGeoCoordinate& center, double radiusMeters) +{ + _roiCenter = center; + _roiRadius = radiusMeters; + _roiUpdateTimer.start(); +} + +void AirspaceManager::_updateToROI() +{ + if (_restrictionsProvider) { + _restrictionsProvider->setROI(_roiCenter, _roiRadius); + } +} + +void AirspaceManager::_restrictionsUpdated(bool success) +{ + _polygonRestrictions.clearAndDeleteContents(); + _circleRestrictions.clearAndDeleteContents(); + if (success) { + for (const auto& circle : _restrictionsProvider->circles()) { + _circleRestrictions.append(circle); + } + for (const auto& polygon : _restrictionsProvider->polygons()) { + _polygonRestrictions.append(polygon); + } + } else { + // TODO: show error? + } +} + + +AirspaceManagerPerVehicle::AirspaceManagerPerVehicle(const Vehicle& vehicle) + : _vehicle(vehicle) +{ + connect(&_vehicle, &Vehicle::armedChanged, this, &AirspaceManagerPerVehicle::_vehicleArmedChanged); + connect(&_vehicle, &Vehicle::mavlinkMessageReceived, this, &AirspaceManagerPerVehicle::vehicleMavlinkMessageReceived); +} + +void AirspaceManagerPerVehicle::_vehicleArmedChanged(bool armed) +{ + if (armed) { + startTelemetryStream(); + _vehicleWasInMissionMode = _vehicle.flightMode() == _vehicle.missionFlightMode(); + } else { + stopTelemetryStream(); + // end the flight if we were in mission mode during arming or disarming + // TODO: needs to be improved. for instance if we do RTL and then want to continue the mission... + if (_vehicleWasInMissionMode || _vehicle.flightMode() == _vehicle.missionFlightMode()) { + endFlight(); + } + } +} + diff --git a/src/MissionManager/AirspaceManagement.h b/src/MissionManager/AirspaceManagement.h new file mode 100644 index 0000000000000000000000000000000000000000..1a17b6ec496c428f3cfcad775c4bd61a5da549c0 --- /dev/null +++ b/src/MissionManager/AirspaceManagement.h @@ -0,0 +1,255 @@ +/**************************************************************************** + * + * (c) 2017 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +/** + * @file AirspaceManagement.h + * This file contains the interface definitions used by an airspace management implementation (AirMap). + * There are 3 base classes that must be subclassed: + * - AirspaceManager + * main manager that contains the restrictions for display. It acts as a factory to create instances of the other + * classes. + * - AirspaceManagerPerVehicle + * this provides the multi-vehicle support - each vehicle has an instance + * - AirspaceRestrictionProvider + * provides airspace restrictions. Currently only used by AirspaceManager, but + * each vehicle could have its own restrictions. + */ + +#include "QGCToolbox.h" +#include "MissionItem.h" + +#include +#include +#include + +#include + +Q_DECLARE_LOGGING_CATEGORY(AirspaceManagementLog) + +/** + * Contains the status of the Airspace authorization + */ +class AirspaceAuthorization : public QObject { + Q_OBJECT +public: + enum PermitStatus { + PermitUnknown = 0, + PermitPending, + PermitAccepted, + PermitRejected, + }; + Q_ENUMS(PermitStatus); +}; + +/** + * Base class for an airspace restriction + */ +class AirspaceRestriction : public QObject +{ + Q_OBJECT + +public: + AirspaceRestriction(QObject* parent = NULL); +}; + +class PolygonAirspaceRestriction : public AirspaceRestriction +{ + Q_OBJECT + +public: + PolygonAirspaceRestriction(const QVariantList& polygon, QObject* parent = NULL); + + Q_PROPERTY(QVariantList polygon MEMBER _polygon CONSTANT) + + const QVariantList& getPolygon() const { return _polygon; } + +private: + QVariantList _polygon; +}; + +class CircularAirspaceRestriction : public AirspaceRestriction +{ + Q_OBJECT + +public: + CircularAirspaceRestriction(const QGeoCoordinate& center, double radius, QObject* parent = NULL); + + Q_PROPERTY(QGeoCoordinate center MEMBER _center CONSTANT) + Q_PROPERTY(double radius MEMBER _radius CONSTANT) + +private: + QGeoCoordinate _center; + double _radius; +}; + + +/** + * @class AirspaceRestrictionProvider + * Base class that queries for airspace restrictions + */ +class AirspaceRestrictionProvider : public QObject { + Q_OBJECT +public: + AirspaceRestrictionProvider() = default; + ~AirspaceRestrictionProvider() = default; + + + /** + * Set region of interest that should be queried. When finished, the requestDone() signal will be emmited. + * @param center Center coordinate for ROI + * @param radiusMeters Radius in meters around center which is of interest + */ + virtual void setROI(const QGeoCoordinate& center, double radiusMeters) = 0; + + const QList& polygons() const { return _polygonList; } + const QList& circles() const { return _circleList; } + +signals: + void requestDone(bool success); + +protected: + QList _polygonList; + QList _circleList; +}; + +class AirspaceManagerPerVehicle; +class Vehicle; + +struct WeatherInformation +{ + QString condition; ///< The overall weather condition. + QString icon; ///< The icon or class of icon that should be used for display purposes. + uint32_t windHeading = 0; ///< The heading in [°]. + uint32_t windSpeed = 0; ///< The speed in [°]. + uint32_t windGusting = 0; + int32_t temperature = 0; ///< The temperature in [°C]. + float humidity = 0.0; + uint32_t visibility = 0; ///< Visibility in [m]. + uint32_t precipitation = 0; ///< The probability of precipitation in [%]. +}; +Q_DECLARE_METATYPE(WeatherInformation); + +/** + * @class AirspaceManager + * Base class for airspace management. There is one (global) instantiation of this + */ +class AirspaceManager : public QGCTool { + Q_OBJECT +public: + AirspaceManager(QGCApplication* app, QGCToolbox* toolbox); + virtual ~AirspaceManager(); + + /** + * Factory method to create an AirspaceManagerPerVehicle object + */ + virtual AirspaceManagerPerVehicle* instantiateVehicle(const Vehicle& vehicle) = 0; + + /** + * + * Factory method to create an AirspaceRestrictionProvider object + */ + virtual AirspaceRestrictionProvider* instantiateRestrictionProvider() = 0; + + /** + * Set the ROI for airspace information (restrictions shown in UI) + * @param center Center coordinate for ROI + * @param radiusMeters Radius in meters around center which is of interest + */ + void setROI(const QGeoCoordinate& center, double radiusMeters); + + QmlObjectListModel* polygonRestrictions(void) { return &_polygonRestrictions; } + QmlObjectListModel* circularRestrictions(void) { return &_circleRestrictions; } + + void setToolbox(QGCToolbox* toolbox) override; + + /** + * Name of the airspace management provider (used in the UI) + */ + virtual QString name() const = 0; + + /** + * Request weather information update. When done, it will emit the weatherUpdate() signal. + * @param coordinate request update for this coordinate + */ + virtual void requestWeatherUpdate(const QGeoCoordinate& coordinate) = 0; + +signals: + void weatherUpdate(bool success, QGeoCoordinate coordinate, WeatherInformation weather); + +private slots: + void _restrictionsUpdated(bool success); + +private: + void _updateToROI(); + + AirspaceRestrictionProvider* _restrictionsProvider = nullptr; ///< restrictions that are shown in the UI + + QmlObjectListModel _polygonRestrictions; ///< current polygon restrictions + QmlObjectListModel _circleRestrictions; ///< current circle restrictions + + QTimer _roiUpdateTimer; + QGeoCoordinate _roiCenter; + double _roiRadius; +}; + + +/** + * @class AirspaceManagerPerVehicle + * Base class for per-vehicle management (each vehicle has one (or zero) of these) + */ +class AirspaceManagerPerVehicle : public QObject { + Q_OBJECT +public: + AirspaceManagerPerVehicle(const Vehicle& vehicle); + virtual ~AirspaceManagerPerVehicle() = default; + + + /** + * create/upload a flight from a mission. This should update the flight permit status. + * There can only be one active flight for each vehicle. + */ + virtual void createFlight(const QList& missionItems) = 0; + + /** + * get the current flight permit status + */ + virtual AirspaceAuthorization::PermitStatus flightPermitStatus() const = 0; + + + /** + * Setup the connection and start sending telemetry + */ + virtual void startTelemetryStream() = 0; + + virtual void stopTelemetryStream() = 0; + + virtual bool isTelemetryStreaming() const = 0; + +public slots: + virtual void endFlight() = 0; + +signals: + void trafficUpdate(QString traffic_id, QString vehicle_id, QGeoCoordinate location, float heading); + + void flightPermitStatusChanged(); + +protected slots: + virtual void vehicleMavlinkMessageReceived(const mavlink_message_t& message) = 0; + +protected: + const Vehicle& _vehicle; + +private slots: + void _vehicleArmedChanged(bool armed); + +private: + bool _vehicleWasInMissionMode = false; ///< true if the vehicle was in mission mode when arming +}; diff --git a/src/MissionManager/GeoFenceManager.cc b/src/MissionManager/GeoFenceManager.cc index ce6ce1933efecfbc1ef788047a598f12afcf245e..ddda1d1bd6567777a964e79564d9be14d860f729 100644 --- a/src/MissionManager/GeoFenceManager.cc +++ b/src/MissionManager/GeoFenceManager.cc @@ -11,6 +11,7 @@ #include "Vehicle.h" #include "QmlObjectListModel.h" #include "ParameterManager.h" +#include "QGCApplication.h" #include "QGCMapPolygon.h" #include "QGCMapCircle.h" @@ -20,6 +21,7 @@ GeoFenceManager::GeoFenceManager(Vehicle* vehicle) : _vehicle (vehicle) , _planManager (vehicle, MAV_MISSION_TYPE_FENCE) , _firstParamLoadComplete (false) + , _airspaceManager (qgcApp()->toolbox()->airspaceManager()) { connect(&_planManager, &PlanManager::inProgressChanged, this, &GeoFenceManager::inProgressChanged); connect(&_planManager, &PlanManager::error, this, &GeoFenceManager::error); diff --git a/src/MissionManager/GeoFenceManager.h b/src/MissionManager/GeoFenceManager.h index 9c7c4e41fd31cdf3d415e6932da8041a7a9e6054..26ec69d968f7c8ef525fb4249ab1766a27694c6e 100644 --- a/src/MissionManager/GeoFenceManager.h +++ b/src/MissionManager/GeoFenceManager.h @@ -13,6 +13,7 @@ #include #include +#include "AirspaceManagement.h" #include "QGCLoggingCategory.h" #include "FactSystem.h" #include "PlanManager.h" @@ -96,6 +97,7 @@ private: bool _firstParamLoadComplete; QList _sendPolygons; QList _sendCircles; + AirspaceManager* _airspaceManager; }; #endif diff --git a/src/MissionManager/PlanManager.cc b/src/MissionManager/PlanManager.cc index 33a609f5dc444389b89f5f5198bb9cc3207c87db..c73286c8765543241c17fd2a2276e55a25e2fc55 100644 --- a/src/MissionManager/PlanManager.cc +++ b/src/MissionManager/PlanManager.cc @@ -78,6 +78,14 @@ void PlanManager::writeMissionItems(const QList& missionItems) return; } + if (_planType == MAV_MISSION_TYPE_MISSION) { + // upload the flight to the airspace management backend + AirspaceManagerPerVehicle* airspaceManager = _vehicle->airspaceManager(); + if (airspaceManager) { + airspaceManager->createFlight(missionItems); + } + } + _clearAndDeleteWriteMissionItems(); bool skipFirstItem = _planType == MAV_MISSION_TYPE_MISSION && !_vehicle->firmwarePlugin()->sendHomePositionToVehicle(); diff --git a/src/PlanView/GeoFenceEditor.qml b/src/PlanView/GeoFenceEditor.qml index dd592b5fe6ef150d52a78da19cbf77468424d466..094e4183eade8ffd151d1fb3daec3d3ded5687e4 100644 --- a/src/PlanView/GeoFenceEditor.qml +++ b/src/PlanView/GeoFenceEditor.qml @@ -42,7 +42,7 @@ QGCFlickable { anchors.left: parent.left anchors.top: parent.top text: qsTr("GeoFence") - color: "black" + anchors.leftMargin: ScreenTools.defaultFontPixelWidth } Rectangle { diff --git a/src/PlanView/MissionItemEditor.qml b/src/PlanView/MissionItemEditor.qml index dd643588f178fbdf9122e36d73082e24af2d6b52..2eb46f0facc0f2b827577f3fbf7779b4b59e73de 100644 --- a/src/PlanView/MissionItemEditor.qml +++ b/src/PlanView/MissionItemEditor.qml @@ -15,7 +15,7 @@ import QGroundControl.Palette 1.0 /// Mission item edit control Rectangle { id: _root - height: editorLoader.y + (editorLoader.visible ? editorLoader.height : 0) + (_margin * 2) + height: header.height + (editorLoader.visible ? (editorLoader.height + (_margin * 3)) : 0) color: _currentItem ? qgcPal.missionItemEditor : qgcPal.windowShade radius: _radius @@ -33,15 +33,14 @@ Rectangle { property var _masterController: masterController property var _missionController: _masterController.missionController property bool _currentItem: missionItem.isCurrentItem - property color _outerTextColor: _currentItem ? qgcPal.primaryButtonText : qgcPal.text property bool _noMissionItemsAdded: ListView.view.model.count === 1 property real _sectionSpacer: ScreenTools.defaultFontPixelWidth / 2 // spacing between section headings property bool _singleComplexItem: _missionController.complexMissionItemNames.length === 1 readonly property real _editFieldWidth: Math.min(width - _margin * 2, ScreenTools.defaultFontPixelWidth * 12) - readonly property real _margin: ScreenTools.defaultFontPixelWidth / 2 - readonly property real _radius: ScreenTools.defaultFontPixelWidth / 2 - readonly property real _hamburgerSize: commandPicker.height * 0.75 + readonly property real _margin: ScreenTools.defaultFontPixelWidth * 0.5 + readonly property real _radius: ScreenTools.defaultFontPixelWidth * 0.5 + readonly property real _hamburgerSize: header.height * 0.75 readonly property bool _waypointsOnlyMode: QGroundControl.corePlugin.options.missionWaypointsOnly QGCPalette { @@ -52,7 +51,6 @@ Rectangle { FocusScope { id: currentItemScope anchors.fill: parent - MouseArea { anchors.fill: parent onClicked: { @@ -62,38 +60,76 @@ Rectangle { } } + //-- Dialog Component { id: editPositionDialog - EditPositionDialog { coordinate: missionItem.coordinate onCoordinateChanged: missionItem.coordinate = coordinate } } - - QGCLabel { - id: label - anchors.verticalCenter: commandPicker.verticalCenter - anchors.leftMargin: _margin + //-- Header + Row { + id: header + spacing: ScreenTools.defaultFontPixelWidth + height: ScreenTools.defaultFontPixelHeight * 3 + anchors.verticalCenter: editorLoader.visible ? undefined : parent.verticalCenter anchors.left: parent.left - text: missionItem.homePosition ? "H" : missionItem.sequenceNumber - color: _outerTextColor + anchors.leftMargin: ScreenTools.defaultFontPixelWidth + Item { + width: ScreenTools.defaultFontPixelWidth * 3 + height: parent.height + QGCColoredImage { + width: ScreenTools.defaultFontPixelHeight + height: width + sourceSize.height: width + source: "qrc:/qmlimages/Home.svg" + visible: missionItem.homePosition + color: qgcPal.text + anchors.centerIn: parent + } + QGCLabel { + text: missionItem.sequenceNumber + color: qgcPal.text + visible: !missionItem.homePosition + anchors.centerIn: parent + } + } + QGCLabel { + id: label + visible: !missionItem.isCurrentItem || !missionItem.isSimpleItem || _waypointsOnlyMode + text: missionItem.commandName + color: qgcPal.text + anchors.verticalCenter: parent.verticalCenter + } + QGCButton { + id: commandPicker + visible: !label.visible + text: missionItem.commandName + anchors.verticalCenter: parent.verticalCenter + Component { + id: commandDialog + MissionCommandDialog { + missionItem: _root.missionItem + } + } + onClicked: qgcView.showDialog(commandDialog, qsTr("Select Mission Command"), qgcView.showDialogDefaultWidth, StandardButton.Cancel) + } } - + //-- Hamburger button at the right of header QGCColoredImage { id: hamburger anchors.rightMargin: ScreenTools.defaultFontPixelWidth anchors.right: parent.right - anchors.verticalCenter: commandPicker.verticalCenter + anchors.verticalCenter: header.verticalCenter width: _hamburgerSize height: _hamburgerSize sourceSize.height: _hamburgerSize source: "qrc:/qmlimages/Hamburger.svg" - visible: missionItem.isCurrentItem && missionItem.sequenceNumber != 0 - color: qgcPal.windowShade - + visible: missionItem.isCurrentItem && missionItem.sequenceNumber !== 0 + color: qgcPal.text } - + //-- Hamburger Menu QGCMouseArea { fillItem: hamburger visible: hamburger.visible @@ -101,67 +137,54 @@ Rectangle { currentItemScope.focus = true hamburgerMenu.popup() } - Menu { id: hamburgerMenu - MenuItem { text: qsTr("Insert waypoint") onTriggered: insertWaypoint() } - Menu { id: patternMenu title: qsTr("Insert pattern") visible: !_singleComplexItem - Instantiator { model: _missionController.complexMissionItemNames - onObjectAdded: patternMenu.insertItem(index, object) onObjectRemoved: patternMenu.removeItem(object) - MenuItem { text: modelData onTriggered: insertComplexItem(modelData) } } } - MenuItem { text: qsTr("Insert ") + _missionController.complexMissionItemNames[0] visible: _singleComplexItem onTriggered: insertComplexItem(_missionController.complexMissionItemNames[0]) } - MenuItem { text: qsTr("Delete") onTriggered: remove() } - MenuItem { text: qsTr("Change command...") onTriggered: commandPicker.clicked() visible: !_waypointsOnlyMode } - MenuItem { text: qsTr("Edit position...") visible: missionItem.specifiesCoordinate onTriggered: qgcView.showDialog(editPositionDialog, qsTr("Edit Position"), qgcView.showDialogDefaultWidth, StandardButton.Cancel) } - MenuSeparator { visible: missionItem.isSimpleItem && !_waypointsOnlyMode } - MenuItem { - text: qsTr("Show all values") - checkable: true - checked: missionItem.isSimpleItem ? missionItem.rawEdit : false - visible: missionItem.isSimpleItem && !_waypointsOnlyMode - - onTriggered: { + text: qsTr("Show all values") + checkable: true + checked: missionItem.isSimpleItem ? missionItem.rawEdit : false + visible: missionItem.isSimpleItem && !_waypointsOnlyMode + onTriggered: { if (missionItem.rawEdit) { if (missionItem.friendlyEditAllowed) { missionItem.rawEdit = false @@ -176,48 +199,16 @@ Rectangle { } } } - - QGCButton { - id: commandPicker - anchors.topMargin: _margin / 2 - anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 2 - anchors.rightMargin: ScreenTools.defaultFontPixelWidth - anchors.left: label.right - anchors.top: parent.top - visible: !commandLabel.visible - text: missionItem.commandName - - Component { - id: commandDialog - - MissionCommandDialog { - missionItem: _root.missionItem - } - } - - onClicked: qgcView.showDialog(commandDialog, qsTr("Select Mission Command"), qgcView.showDialogDefaultWidth, StandardButton.Cancel) - } - - QGCLabel { - id: commandLabel - anchors.fill: commandPicker - visible: !missionItem.isCurrentItem || !missionItem.isSimpleItem || _waypointsOnlyMode - verticalAlignment: Text.AlignVCenter - text: missionItem.commandName - color: _outerTextColor - } - + //-- Editor Content Loader { id: editorLoader anchors.leftMargin: _margin - anchors.topMargin: _margin anchors.left: parent.left - anchors.top: commandPicker.bottom + anchors.top: header.bottom source: missionItem.editorQml visible: _currentItem - property var masterController: _masterController property real availableWidth: _root.width - (_margin * 2) ///< How wide the editor should be property var editorRoot: _root } -} // Rectangle +} diff --git a/src/PlanView/PlanView.qml b/src/PlanView/PlanView.qml index 55ac65606f39a1099a79270dbffd3e149c4b4887..e19564ea8c7f2e9b19dfea768bed30f9efc2530f 100644 --- a/src/PlanView/PlanView.qml +++ b/src/PlanView/PlanView.qml @@ -25,6 +25,7 @@ import QGroundControl.FactControls 1.0 import QGroundControl.Palette 1.0 import QGroundControl.Mavlink 1.0 import QGroundControl.Controllers 1.0 +import QGroundControl.Airmap 1.0 /// Mission Editor @@ -33,27 +34,31 @@ QGCView { viewPanel: panel z: QGroundControl.zOrderTopMost + property bool planControlColapsed: false + readonly property int _decimalPlaces: 8 - readonly property real _horizontalMargin: ScreenTools.defaultFontPixelWidth / 2 + readonly property real _horizontalMargin: ScreenTools.defaultFontPixelWidth * 0.5 readonly property real _margin: ScreenTools.defaultFontPixelHeight * 0.5 + readonly property real _radius: ScreenTools.defaultFontPixelWidth * 0.5 readonly property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle readonly property real _rightPanelWidth: Math.min(parent.width / 3, ScreenTools.defaultFontPixelWidth * 30) readonly property real _toolButtonTopMargin: parent.height - ScreenTools.availableHeight + (ScreenTools.defaultFontPixelHeight / 2) readonly property var _defaultVehicleCoordinate: QtPositioning.coordinate(37.803784, -122.462276) readonly property bool _waypointsOnlyMode: QGroundControl.corePlugin.options.missionWaypointsOnly - property var _planMasterController: masterController - property var _missionController: _planMasterController.missionController - property var _geoFenceController: _planMasterController.geoFenceController - property var _rallyPointController: _planMasterController.rallyPointController - property var _visualItems: _missionController.visualItems - property bool _lightWidgetBorders: editorMap.isSatelliteMap - property bool _addWaypointOnClick: false - property bool _addROIOnClick: false - property bool _singleComplexItem: _missionController.complexMissionItemNames.length === 1 - property real _toolbarHeight: _qgcView.height - ScreenTools.availableHeight - property int _editingLayer: _layerMission - property int _toolStripBottom: toolStrip.height + toolStrip.y + property bool _enableAirMap: QGroundControl.settingsManager.appSettings.enableAirMap.rawValue + property var _planMasterController: masterController + property var _missionController: _planMasterController.missionController + property var _geoFenceController: _planMasterController.geoFenceController + property var _rallyPointController: _planMasterController.rallyPointController + property var _visualItems: _missionController.visualItems + property bool _lightWidgetBorders: editorMap.isSatelliteMap + property bool _addWaypointOnClick: false + property bool _addROIOnClick: false + property bool _singleComplexItem: _missionController.complexMissionItemNames.length === 1 + property real _toolbarHeight: _qgcView.height - ScreenTools.availableHeight + property int _editingLayer: _layerMission + property int _toolStripBottom: toolStrip.height + toolStrip.y readonly property int _layerMission: 1 readonly property int _layerGeoFence: 2 @@ -90,6 +95,15 @@ QGCView { planMasterController: _planMasterController } + on_EnableAirMapChanged: { + if(_enableAirMap) { + planControlColapsed = true + airspaceControl.colapsed = false + } else { + planControlColapsed = false + } + } + Connections { target: QGroundControl.settingsManager.appSettings.defaultMissionItemAltitude @@ -263,7 +277,7 @@ QGCView { function accept() { var toIndex = toCombo.currentIndex - if (toIndex == 0) { + if (toIndex === 0) { toIndex = 1 } _missionController.moveMissionItem(_moveDialogMissionItemIndex, toIndex) @@ -304,7 +318,7 @@ QGCView { planView: true // This is the center rectangle of the map which is not obscured by tools - property rect centerViewport: Qt.rect(_leftToolWidth, _toolbarHeight, editorMap.width - _leftToolWidth - _rightPanelWidth, editorMap.height - _statusHeight - _toolbarHeight) + property rect centerViewport: Qt.rect(_leftToolWidth, _toolbarHeight, editorMap.width - _leftToolWidth - _rightPanelWidth, editorMap.height - _statusHeight - _toolbarHeight) property real _leftToolWidth: toolStrip.x + toolStrip.width property real _statusHeight: waypointValuesDisplay.visible ? editorMap.height - waypointValuesDisplay.y : 0 @@ -481,105 +495,196 @@ QGCView { } } } - } // FlightMap - + } + //----------------------------------------------------------- // Right pane for mission editing controls Rectangle { id: rightPanel - anchors.bottom: parent.bottom - anchors.right: parent.right height: ScreenTools.availableHeight width: _rightPanelWidth color: qgcPal.window opacity: 0.2 + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth } - + //------------------------------------------------------- + // Right Panel Controls Item { - anchors.fill: rightPanel - - // Plan Element selector (Mission/Fence/Rally) - Row { - id: planElementSelectorRow - anchors.topMargin: Math.round(ScreenTools.defaultFontPixelHeight / 3) - anchors.top: parent.top + anchors.fill: rightPanel + Column { + id: rightControls + spacing: ScreenTools.defaultFontPixelHeight * 0.5 anchors.left: parent.left anchors.right: parent.right - spacing: _horizontalMargin - visible: QGroundControl.corePlugin.options.enablePlanViewSelector - - readonly property real _buttonRadius: ScreenTools.defaultFontPixelHeight * 0.75 - - ExclusiveGroup { - id: planElementSelectorGroup - onCurrentChanged: { - switch (current) { - case planElementMission: - _editingLayer = _layerMission - break - case planElementGeoFence: - _editingLayer = _layerGeoFence - break - case planElementRallyPoints: - _editingLayer = _layerRallyPoints - break + anchors.top: parent.top + anchors.topMargin: ScreenTools.defaultFontPixelHeight * 0.25 + //------------------------------------------------------- + // Airmap Airspace Control + AirspaceControl { + id: airspaceControl + width: parent.width + visible: _enableAirMap + showColapse: false + onColapsedChanged: { + if(!airspaceControl.colasped) { + planControlColapsed = true } } } - - QGCRadioButton { - id: planElementMission - exclusiveGroup: planElementSelectorGroup - text: qsTr("Mission") - checked: true - color: mapPal.text - textStyle: Text.Outline - textStyleColor: mapPal.textOutline - } - - Item { height: 1; width: 1 } - - QGCRadioButton { - id: planElementGeoFence - exclusiveGroup: planElementSelectorGroup - text: qsTr("Fence") - color: mapPal.text - textStyle: Text.Outline - textStyleColor: mapPal.textOutline + //------------------------------------------------------- + // Mission Controls (Colapsed) + Rectangle { + id: planColapsed + width: parent.width + height: planControlColapsed ? colapsedRow.height + ScreenTools.defaultFontPixelHeight : 0 + color: qgcPal.missionItemEditor + radius: _radius + visible: planControlColapsed && _enableAirMap + Row { + id: colapsedRow + spacing: ScreenTools.defaultFontPixelWidth + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth + anchors.verticalCenter: parent.verticalCenter + QGCColoredImage { + width: height + height: ScreenTools.defaultFontPixelWidth * 2.5 + sourceSize.height: height + source: "qrc:/res/waypoint.svg" + color: qgcPal.text + anchors.verticalCenter: parent.verticalCenter + } + QGCLabel { + text: qsTr("Plan") + color: qgcPal.text + anchors.verticalCenter: parent.verticalCenter + } + } + QGCColoredImage { + width: height + height: ScreenTools.defaultFontPixelWidth * 2.5 + sourceSize.height: height + source: "qrc:/airmap/expand.svg" + color: "white" + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth + anchors.verticalCenter: parent.verticalCenter + } + MouseArea { + anchors.fill: parent + onClicked: { + airspaceControl.colapsed = true + planControlColapsed = false + } + } } - - Item { height: 1; width: 1 } - - QGCRadioButton { - id: planElementRallyPoints - exclusiveGroup: planElementSelectorGroup - text: qsTr("Rally") - color: mapPal.text - textStyle: Text.Outline - textStyleColor: mapPal.textOutline + //------------------------------------------------------- + // Mission Controls (Expanded) + Rectangle { + id: planExpanded + width: parent.width + height: !planControlColapsed ? expandedCol.height + ScreenTools.defaultFontPixelHeight : 0 + color: qgcPal.missionItemEditor + radius: _radius + visible: !planControlColapsed || !_enableAirMap + Item { + height: expandedCol.height + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + Column { + id: expandedCol + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + anchors.left: parent.left + anchors.right: parent.right + //-- Header + Row { + id: expandedRow + spacing: ScreenTools.defaultFontPixelWidth + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth + readonly property real _buttonRadius: ScreenTools.defaultFontPixelHeight * 0.75 + QGCColoredImage { + width: height + height: ScreenTools.defaultFontPixelWidth * 2.5 + sourceSize.height: height + source: "qrc:/res/waypoint.svg" + color: qgcPal.text + anchors.verticalCenter: parent.verticalCenter + } + QGCLabel { + text: qsTr("Plan") + color: qgcPal.text + visible: !QGroundControl.corePlugin.options.enablePlanViewSelector + anchors.verticalCenter: parent.verticalCenter + } + ExclusiveGroup { + id: planElementSelectorGroup + onCurrentChanged: { + switch (current) { + case planElementMission: + _editingLayer = _layerMission + break + case planElementGeoFence: + _editingLayer = _layerGeoFence + break + case planElementRallyPoints: + _editingLayer = _layerRallyPoints + break + } + } + } + QGCRadioButton { + id: planElementMission + exclusiveGroup: planElementSelectorGroup + text: qsTr("Mission") + checked: true + visible: QGroundControl.corePlugin.options.enablePlanViewSelector + anchors.verticalCenter: parent.verticalCenter + } + QGCRadioButton { + id: planElementGeoFence + exclusiveGroup: planElementSelectorGroup + text: qsTr("Fence") + visible: QGroundControl.corePlugin.options.enablePlanViewSelector + anchors.verticalCenter: parent.verticalCenter + } + QGCRadioButton { + id: planElementRallyPoints + exclusiveGroup: planElementSelectorGroup + text: qsTr("Rally") + visible: QGroundControl.corePlugin.options.enablePlanViewSelector + anchors.verticalCenter: parent.verticalCenter + } + } + } + } } - } // Row - Plan Element Selector - + } + //------------------------------------------------------- // Mission Item Editor Item { - id: missionItemEditor - anchors.topMargin: ScreenTools.defaultFontPixelHeight / 2 - anchors.top: planElementSelectorRow.visible ? planElementSelectorRow.bottom : planElementSelectorRow.top - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - visible: _editingLayer == _layerMission - + id: missionItemEditor + anchors.left: parent.left + anchors.right: parent.right + anchors.top: rightControls.bottom + anchors.topMargin: ScreenTools.defaultFontPixelHeight * 0.5 + anchors.bottom: parent.bottom + anchors.bottomMargin: ScreenTools.defaultFontPixelHeight * 0.25 + visible: _editingLayer == _layerMission && !planControlColapsed QGCListView { id: missionItemEditorListView anchors.fill: parent - spacing: _margin / 2 + spacing: ScreenTools.defaultFontPixelHeight * 0.5 orientation: ListView.Vertical model: _missionController.visualItems cacheBuffer: Math.max(height * 2, 0) clip: true currentIndex: _missionController.currentPlanViewIndex highlightMoveDuration: 250 - + visible: _editingLayer == _layerMission && !planControlColapsed + //-- List Elements delegate: MissionItemEditor { map: editorMap masterController: _planMasterController @@ -587,9 +692,7 @@ QGCView { width: parent.width readOnly: false rootQgcView: _qgcView - onClicked: _missionController.setCurrentPlanViewIndex(object.sequenceNumber, false) - onRemove: { var removeIndex = index _missionController.removeMissionItem(removeIndex) @@ -598,17 +701,15 @@ QGCView { } _missionController.setCurrentPlanViewIndex(removeIndex, true) } - onInsertWaypoint: insertSimpleMissionItem(editorMap.center, index) onInsertComplexItem: insertComplexMissionItem(complexItemName, editorMap.center, index) } - } // QGCListView - } // Item - Mission Item editor - + } + } // GeoFence Editor GeoFenceEditor { - anchors.topMargin: ScreenTools.defaultFontPixelHeight / 2 - anchors.top: planElementSelectorRow.bottom + anchors.top: rightControls.bottom + anchors.topMargin: ScreenTools.defaultFontPixelHeight * 0.5 anchors.left: parent.left anchors.right: parent.right availableHeight: ScreenTools.availableHeight @@ -616,30 +717,27 @@ QGCView { flightMap: editorMap visible: _editingLayer == _layerGeoFence } - // Rally Point Editor - RallyPointEditorHeader { - id: rallyPointHeader - anchors.topMargin: ScreenTools.defaultFontPixelHeight / 2 - anchors.top: planElementSelectorRow.bottom - anchors.left: parent.left - anchors.right: parent.right - visible: _editingLayer == _layerRallyPoints - controller: _rallyPointController + id: rallyPointHeader + anchors.top: rightControls.bottom + anchors.topMargin: ScreenTools.defaultFontPixelHeight * 0.5 + anchors.left: parent.left + anchors.right: parent.right + visible: _editingLayer == _layerRallyPoints + controller: _rallyPointController } - RallyPointItemEditor { - id: rallyPointEditor - anchors.topMargin: ScreenTools.defaultFontPixelHeight / 2 - anchors.top: rallyPointHeader.bottom - anchors.left: parent.left - anchors.right: parent.right - visible: _editingLayer == _layerRallyPoints && _rallyPointController.points.count - rallyPoint: _rallyPointController.currentRallyPoint - controller: _rallyPointController + id: rallyPointEditor + anchors.top: rallyPointHeader.bottom + anchors.topMargin: ScreenTools.defaultFontPixelHeight * 0.5 + anchors.left: parent.left + anchors.right: parent.right + visible: _editingLayer == _layerRallyPoints && _rallyPointController.points.count + rallyPoint: _rallyPointController.currentRallyPoint + controller: _rallyPointController } - } // Right panel + } MapScale { id: mapScale @@ -660,7 +758,7 @@ QGCView { missionItems: _missionController.visualItems visible: _editingLayer === _layerMission && (_toolStripBottom + mapScale.height) < y && QGroundControl.corePlugin.options.showMissionStatus } - } // QGCViewPanel + } Component { id: syncLoadFromVehicleOverwrite diff --git a/src/PlanView/RallyPointEditorHeader.qml b/src/PlanView/RallyPointEditorHeader.qml index ff0a86277f00775ef7c4f7b44bcc446648543bf2..0c5d677c82581e94852b25b1a348e0cf1291ca69 100644 --- a/src/PlanView/RallyPointEditorHeader.qml +++ b/src/PlanView/RallyPointEditorHeader.qml @@ -28,7 +28,6 @@ QGCFlickable { anchors.left: parent.left anchors.top: parent.top text: qsTr("Rally Points") - color: "black" } Rectangle { diff --git a/src/PlanView/RallyPointItemEditor.qml b/src/PlanView/RallyPointItemEditor.qml index 775f322a65e46c8cefbabb1cc142ddc7caa8c488..91513017bf5c076812d4ad4394a17a1d2bbf28da 100644 --- a/src/PlanView/RallyPointItemEditor.qml +++ b/src/PlanView/RallyPointItemEditor.qml @@ -17,8 +17,7 @@ Rectangle { property var rallyPoint ///< RallyPoint object associated with editor property var controller ///< RallyPointController - property bool _currentItem: rallyPoint ? rallyPoint == controller.currentRallyPoint : false - property color _outerTextColor: _currentItem ? "black" : qgcPal.text + property bool _currentItem: rallyPoint ? rallyPoint === controller.currentRallyPoint : false readonly property real _margin: ScreenTools.defaultFontPixelWidth / 2 readonly property real _radius: ScreenTools.defaultFontPixelWidth / 2 @@ -47,10 +46,9 @@ Rectangle { anchors.left: indicator.right anchors.verticalCenter: parent.verticalCenter text: qsTr("Rally Point") - color: _outerTextColor } - Image { + QGCColoredImage { id: hamburger anchors.rightMargin: _margin anchors.right: parent.right @@ -59,14 +57,12 @@ Rectangle { height: width sourceSize.height: height source: "qrc:/qmlimages/Hamburger.svg" - + color: qgcPal.text MouseArea { anchors.fill: parent onClicked: hamburgerMenu.popup() - Menu { id: hamburgerMenu - MenuItem { text: qsTr("Delete") onTriggered: controller.removePoint(rallyPoint) @@ -74,7 +70,7 @@ Rectangle { } } } - } // Item - titleBar + } Rectangle { id: valuesRect diff --git a/src/QGCApplication.cc b/src/QGCApplication.cc index ec3d865182f5c6a34c42ab8291559ce7dfa4816f..832fadb429dd23c997c75332dee2fb5c1960c66c 100644 --- a/src/QGCApplication.cc +++ b/src/QGCApplication.cc @@ -84,6 +84,7 @@ #include "CameraCalc.h" #include "VisualMissionItem.h" #include "EditPositionDialogController.h" +#include "AirspaceController.h" #ifndef NO_SERIAL_LINK #include "SerialLink.h" @@ -373,6 +374,7 @@ void QGCApplication::_initCommon(void) qmlRegisterUncreatableType ("QGroundControl.Vehicle", 1, 0, "ParameterManager", "Reference only"); qmlRegisterUncreatableType ("QGroundControl.Vehicle", 1, 0, "QGCCameraManager", "Reference only"); qmlRegisterUncreatableType ("QGroundControl.Vehicle", 1, 0, "QGCCameraControl", "Reference only"); + qmlRegisterUncreatableType ("QGroundControl.Vehicle", 1, 0, "AirspaceController", "Reference only"); qmlRegisterUncreatableType ("QGroundControl.JoystickManager", 1, 0, "JoystickManager", "Reference only"); qmlRegisterUncreatableType ("QGroundControl.JoystickManager", 1, 0, "Joystick", "Reference only"); qmlRegisterUncreatableType ("QGroundControl.QGCPositionManager", 1, 0, "QGCPositionManager", "Reference only"); diff --git a/src/QGCPalette.cc b/src/QGCPalette.cc index ec5ac8fa79e1a44e2e2f4aace3f5d8e01a3840a3..70a6098eac12908cfd6f79791a33e41710943042 100644 --- a/src/QGCPalette.cc +++ b/src/QGCPalette.cc @@ -73,13 +73,13 @@ void QGCPalette::_buildMap(void) DECLARE_QGC_COLOR(alertBackground, "#eecc44", "#eecc44", "#eecc44", "#eecc44") DECLARE_QGC_COLOR(alertBorder, "#808080", "#808080", "#808080", "#808080") DECLARE_QGC_COLOR(alertText, "#000000", "#000000", "#000000", "#000000") - DECLARE_QGC_COLOR(missionItemEditor, "#585858", "#8cb3be", "#585858", "#8cb3be") + DECLARE_QGC_COLOR(missionItemEditor, "#585858", "#dbfef8", "#585858", "#585d83") // Colors are not affecting by theming DECLARE_QGC_COLOR(mapWidgetBorderLight, "#ffffff", "#ffffff", "#ffffff", "#ffffff") DECLARE_QGC_COLOR(mapWidgetBorderDark, "#000000", "#000000", "#000000", "#000000") DECLARE_QGC_COLOR(brandingPurple, "#4A2C6D", "#4A2C6D", "#4A2C6D", "#4A2C6D") - DECLARE_QGC_COLOR(brandingBlue, "#48D6FF", "#48D6FF", "#48D6FF", "#48D6FF") + DECLARE_QGC_COLOR(brandingBlue, "#48D6FF", "#6045c5", "#48D6FF", "#6045c5") } void QGCPalette::setColorGroupEnabled(bool enabled) diff --git a/src/QGCToolbox.cc b/src/QGCToolbox.cc index c2eadcfd9dcbefcbcc74824d9439a263b10d81ed..94d5795a31f304ceed27249573972a8536a722b0 100644 --- a/src/QGCToolbox.cc +++ b/src/QGCToolbox.cc @@ -30,32 +30,34 @@ #include "QGCOptions.h" #include "SettingsManager.h" #include "QGCApplication.h" +#include "AirMapManager.h" #if defined(QGC_CUSTOM_BUILD) #include CUSTOMHEADER #endif QGCToolbox::QGCToolbox(QGCApplication* app) - : _audioOutput(NULL) - , _factSystem(NULL) + : _audioOutput (NULL) + , _factSystem (NULL) , _firmwarePluginManager(NULL) #ifndef __mobile__ - , _gpsManager(NULL) + , _gpsManager (NULL) #endif - , _imageProvider(NULL) - , _joystickManager(NULL) - , _linkManager(NULL) - , _mavlinkProtocol(NULL) - , _missionCommandTree(NULL) - , _multiVehicleManager(NULL) - , _mapEngineManager(NULL) - , _uasMessageHandler(NULL) - , _followMe(NULL) - , _qgcPositionManager(NULL) - , _videoManager(NULL) - , _mavlinkLogManager(NULL) - , _corePlugin(NULL) - , _settingsManager(NULL) + , _imageProvider (NULL) + , _joystickManager (NULL) + , _linkManager (NULL) + , _mavlinkProtocol (NULL) + , _missionCommandTree (NULL) + , _multiVehicleManager (NULL) + , _mapEngineManager (NULL) + , _uasMessageHandler (NULL) + , _followMe (NULL) + , _qgcPositionManager (NULL) + , _videoManager (NULL) + , _mavlinkLogManager (NULL) + , _corePlugin (NULL) + , _settingsManager (NULL) + , _airspaceManager (NULL) { // SettingsManager must be first so settings are available to any subsequent tools _settingsManager = new SettingsManager(app, this); @@ -80,6 +82,7 @@ QGCToolbox::QGCToolbox(QGCApplication* app) _followMe = new FollowMe (app, this); _videoManager = new VideoManager (app, this); _mavlinkLogManager = new MAVLinkLogManager (app, this); + _airspaceManager = new AirMapManager (app, this); } void QGCToolbox::setChildToolboxes(void) @@ -106,6 +109,7 @@ void QGCToolbox::setChildToolboxes(void) _qgcPositionManager->setToolbox(this); _videoManager->setToolbox(this); _mavlinkLogManager->setToolbox(this); + _airspaceManager->setToolbox(this); } void QGCToolbox::_scanAndLoadPlugins(QGCApplication* app) diff --git a/src/QGCToolbox.h b/src/QGCToolbox.h index a45e37eb473f1ab8b6aaac9a3fb10508c2da8b6d..5ece9befe85c08d488cf656586c6e4e668ab3462 100644 --- a/src/QGCToolbox.h +++ b/src/QGCToolbox.h @@ -32,6 +32,7 @@ class VideoManager; class MAVLinkLogManager; class QGCCorePlugin; class SettingsManager; +class AirspaceManager; /// This is used to manage all of our top level services/tools class QGCToolbox : public QObject { @@ -56,6 +57,7 @@ public: MAVLinkLogManager* mavlinkLogManager(void) { return _mavlinkLogManager; } QGCCorePlugin* corePlugin(void) { return _corePlugin; } SettingsManager* settingsManager(void) { return _settingsManager; } + AirspaceManager* airspaceManager(void) { return _airspaceManager; } #ifndef __mobile__ GPSManager* gpsManager(void) { return _gpsManager; } @@ -86,6 +88,7 @@ private: MAVLinkLogManager* _mavlinkLogManager; QGCCorePlugin* _corePlugin; SettingsManager* _settingsManager; + AirspaceManager* _airspaceManager; friend class QGCApplication; }; diff --git a/src/QmlControls/PageView.qml b/src/QmlControls/PageView.qml index 6dfdc6cb4c2944bc81a19130e471c8b5a50ea278..6dda2997c98c9eb4c26c2571fe7bd325e0446d96 100644 --- a/src/QmlControls/PageView.qml +++ b/src/QmlControls/PageView.qml @@ -10,6 +10,7 @@ Rectangle { id: _root height: pageFlickable.y + pageFlickable.height + _margins color: qgcPal.window + radius: ScreenTools.defaultFontPixelWidth * 0.5 property var qgcView ///< QGCView to use for showing dialogs property real maxHeight ///< Maximum height that should be taken, smaller than this is ok diff --git a/src/QmlControls/QmlTest.qml b/src/QmlControls/QmlTest.qml index 92193f776be0b0f52361f5bcc2252a0b721dbbc6..7ff8acb5fc339aaf5ef0870a4da84c62f31ae90b 100644 --- a/src/QmlControls/QmlTest.qml +++ b/src/QmlControls/QmlTest.qml @@ -841,6 +841,38 @@ Rectangle { text: palette.alertText } + // missionItemEditor + Loader { + sourceComponent: rowHeader + property var text: "missionItemEditor" + } + ClickableColor { + property var palette: QGCPalette { colorGroupEnabled: false } + color: palette.missionItemEditor + onColorSelected: palette.missionItemEditor = color + } + ClickableColor { + property var palette: QGCPalette { colorGroupEnabled: true } + color: palette.missionItemEditor + onColorSelected: palette.missionItemEditor = color + } + Text { + width: 80 + height: 20 + color: "black" + horizontalAlignment: Text.AlignHCenter + property var palette: QGCPalette { colorGroupEnabled: false } + text: palette.missionItemEditor + } + Text { + width: 80 + height: 20 + color: "black" + horizontalAlignment: Text.AlignHCenter + property var palette: QGCPalette { colorGroupEnabled: true } + text: palette.missionItemEditor + } + } Column { diff --git a/src/Settings/AirMap.SettingsGroup.json b/src/Settings/AirMap.SettingsGroup.json new file mode 100644 index 0000000000000000000000000000000000000000..5806903ae0445c06c8f65496769a1b56b23db052 --- /dev/null +++ b/src/Settings/AirMap.SettingsGroup.json @@ -0,0 +1,26 @@ +[ +{ + "name": "apiKey", + "shortDescription": "AirMap API Key", + "type": "string", + "defaultValue": "" +}, +{ + "name": "clientID", + "shortDescription": "AirMap Client ID", + "type": "string", + "defaultValue": "" +}, +{ + "name": "userName", + "shortDescription": "AirMap User Name", + "type": "string", + "defaultValue": "" +}, +{ + "name": "password", + "shortDescription": "AirMap Password", + "type": "string", + "defaultValue": "" +} +] diff --git a/src/Settings/AirMapSettings.cc b/src/Settings/AirMapSettings.cc new file mode 100644 index 0000000000000000000000000000000000000000..a2cc7ce0ea48f0a7f500b1b13311d8c414dd093a --- /dev/null +++ b/src/Settings/AirMapSettings.cc @@ -0,0 +1,30 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "AirMapSettings.h" +#include "QGCPalette.h" +#include "QGCApplication.h" + +#include +#include + +DECLARE_SETTINGGROUP(AirMap) +{ + INIT_SETTINGFACT(apiKey); + INIT_SETTINGFACT(clientID); + INIT_SETTINGFACT(userName); + INIT_SETTINGFACT(password); + QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); + qmlRegisterUncreatableType("QGroundControl.SettingsManager", 1, 0, "AirMapSettings", "Reference only"); +} + +DECLARE_SETTINGSFACT(AirMapSettings, apiKey) +DECLARE_SETTINGSFACT(AirMapSettings, clientID) +DECLARE_SETTINGSFACT(AirMapSettings, userName) +DECLARE_SETTINGSFACT(AirMapSettings, password) diff --git a/src/Settings/AirMapSettings.h b/src/Settings/AirMapSettings.h new file mode 100644 index 0000000000000000000000000000000000000000..04542d52d76a85a54b93decc510ad775ecb9595a --- /dev/null +++ b/src/Settings/AirMapSettings.h @@ -0,0 +1,28 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include "SettingsGroup.h" +#include "QGCMAVLink.h" + +class AirMapSettings : public SettingsGroup +{ + Q_OBJECT +public: + AirMapSettings(QObject* parent = NULL); + + DEFINE_SETTINGGROUP(AirMap) + + DEFINE_SETTINGFACT(apiKey) + DEFINE_SETTINGFACT(clientID) + DEFINE_SETTINGFACT(userName) + DEFINE_SETTINGFACT(password) + +}; diff --git a/src/Settings/App.SettingsGroup.json b/src/Settings/App.SettingsGroup.json index 81520a00d83d844c8af5b5436cfe0616471935de..ca59f385e89c5b4eaab98abbb8e31200a84c350f 100644 --- a/src/Settings/App.SettingsGroup.json +++ b/src/Settings/App.SettingsGroup.json @@ -183,5 +183,12 @@ "shortDescription": "Default firmware type for flashing", "type": "uint32", "defaultValue": 12 +}, +{ + "name": "EnableAirMap", + "shortDescription": "Enable AirMap", + "longDescription": "Enable AirMap Services", + "type": "bool", + "defaultValue": false } ] diff --git a/src/Settings/AppSettings.cc b/src/Settings/AppSettings.cc index 3e8b950e88401411b79425f9ede36e74db97f3eb..a4a6f88142a1db880e8e567ada67d0bbfd3e9365 100644 --- a/src/Settings/AppSettings.cc +++ b/src/Settings/AppSettings.cc @@ -37,6 +37,7 @@ const char* AppSettings::mapboxTokenName = "MapboxT const char* AppSettings::esriTokenName = "EsriToken"; const char* AppSettings::defaultFirmwareTypeName = "DefaultFirmwareType"; const char* AppSettings::gstDebugName = "GstreamerDebugLevel"; +const char* AppSettings::enableAirMapName = "EnableAirMap"; const char* AppSettings::parameterFileExtension = "params"; const char* AppSettings::planFileExtension = "plan"; @@ -77,6 +78,7 @@ AppSettings::AppSettings(QObject* parent) , _esriTokenFact(NULL) , _defaultFirmwareTypeFact(NULL) , _gstDebugFact(NULL) + , _enableAirMapFact(NULL) { QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); qmlRegisterUncreatableType("QGroundControl.SettingsManager", 1, 0, "AppSettings", "Reference only"); @@ -400,3 +402,12 @@ Fact* AppSettings::defaultFirmwareType(void) return _defaultFirmwareTypeFact; } + +Fact* AppSettings::enableAirMap(void) +{ + if (!_enableAirMapFact) { + _enableAirMapFact = _createSettingsFact(enableAirMapName); + } + + return _enableAirMapFact; +} diff --git a/src/Settings/AppSettings.h b/src/Settings/AppSettings.h index 6b421aa35ddac5ce78e53ba0a85d3920af3992c7..12334aee6b096229a3b415a099e82f9b2e10eea9 100644 --- a/src/Settings/AppSettings.h +++ b/src/Settings/AppSettings.h @@ -41,6 +41,7 @@ public: Q_PROPERTY(Fact* esriToken READ esriToken CONSTANT) Q_PROPERTY(Fact* defaultFirmwareType READ defaultFirmwareType CONSTANT) Q_PROPERTY(Fact* gstDebug READ gstDebug CONSTANT) + Q_PROPERTY(Fact* enableAirMap READ enableAirMap CONSTANT) Q_PROPERTY(QString missionSavePath READ missionSavePath NOTIFY savePathsChanged) Q_PROPERTY(QString parameterSavePath READ parameterSavePath NOTIFY savePathsChanged) @@ -77,12 +78,13 @@ public: Fact* esriToken (void); Fact* defaultFirmwareType (void); Fact* gstDebug (void); + Fact* enableAirMap (void); QString missionSavePath (void); QString parameterSavePath (void); QString telemetrySavePath (void); QString logSavePath (void); - QString videoSavePath (void); + QString videoSavePath (void); static MAV_AUTOPILOT offlineEditingFirmwareTypeFromFirmwareType(MAV_AUTOPILOT firmwareType); static MAV_TYPE offlineEditingVehicleTypeFromVehicleType(MAV_TYPE vehicleType); @@ -110,6 +112,7 @@ public: static const char* esriTokenName; static const char* defaultFirmwareTypeName; static const char* gstDebugName; + static const char* enableAirMapName; // Application wide file extensions static const char* parameterFileExtension; @@ -158,6 +161,7 @@ private: SettingsFact* _esriTokenFact; SettingsFact* _defaultFirmwareTypeFact; SettingsFact* _gstDebugFact; + SettingsFact* _enableAirMapFact; }; #endif diff --git a/src/Settings/SettingsGroup.h b/src/Settings/SettingsGroup.h index 048c0b1132fdb721cb48c5f962ad505776f2c934..7f0bfce022a0d9155b9530a520ec593f761ad56e 100644 --- a/src/Settings/SettingsGroup.h +++ b/src/Settings/SettingsGroup.h @@ -17,6 +17,34 @@ #include +#define DEFINE_SETTINGGROUP(CLASS) \ + static const char* CLASS ## Settings ## GroupName; + +#define DECLARE_SETTINGGROUP(CLASS) \ + const char* CLASS ## Settings::CLASS ## Settings ## GroupName = #CLASS; \ + CLASS ## Settings::CLASS ## Settings(QObject* parent) \ + : SettingsGroup(CLASS ## Settings ## GroupName, QString() /* root settings group */, parent) + +#define DECLARE_SETTINGSFACT(CLASS, NAME) \ + const char* CLASS::NAME ## Name = #NAME; \ + Fact* CLASS::NAME(void) \ + { \ + if (!_ ## NAME ## Fact) { \ + _ ## NAME ## Fact = _createSettingsFact(NAME ## Name); \ + } \ + return _ ## NAME ## Fact; \ + } + +#define DEFINE_SETTINGFACT(NAME) \ + public: \ + Q_PROPERTY(Fact* NAME READ NAME CONSTANT) \ + Fact* NAME(); \ + static const char* NAME ## Name; \ + private: \ + SettingsFact* _ ## NAME ## Fact; + +#define INIT_SETTINGFACT(NAME) _ ## NAME ## Fact = NULL + /// Provides access to group of settings. The group is named and has a visible property associated with which can control whether the group /// is shows in the ui. class SettingsGroup : public QObject diff --git a/src/Settings/SettingsManager.cc b/src/Settings/SettingsManager.cc index 643bf234515342e8b52af8e66fbe829e8907723d..b7d6a127bb6b4129139d6a906fb73a63c3c72421 100644 --- a/src/Settings/SettingsManager.cc +++ b/src/Settings/SettingsManager.cc @@ -14,6 +14,7 @@ SettingsManager::SettingsManager(QGCApplication* app, QGCToolbox* toolbox) : QGCTool(app, toolbox) + , _airMapSettings (NULL) , _appSettings (NULL) , _unitsSettings (NULL) , _autoConnectSettings (NULL) @@ -40,4 +41,5 @@ void SettingsManager::setToolbox(QGCToolbox *toolbox) _rtkSettings = new RTKSettings(this); _guidedSettings = new GuidedSettings(this); _brandImageSettings = new BrandImageSettings(this); + _airMapSettings = new AirMapSettings(this); } diff --git a/src/Settings/SettingsManager.h b/src/Settings/SettingsManager.h index a69b7f6bc55fcb7eec112da77aeaa0d7322a6c59..bba1104e797b27294960d98488f696be7d8875b9 100644 --- a/src/Settings/SettingsManager.h +++ b/src/Settings/SettingsManager.h @@ -22,6 +22,7 @@ #include "RTKSettings.h" #include "GuidedSettings.h" #include "BrandImageSettings.h" +#include "AirMapSettings.h" #include @@ -33,6 +34,7 @@ class SettingsManager : public QGCTool public: SettingsManager(QGCApplication* app, QGCToolbox* toolbox); + Q_PROPERTY(QObject* airMapSettings READ airMapSettings CONSTANT) Q_PROPERTY(QObject* appSettings READ appSettings CONSTANT) Q_PROPERTY(QObject* unitsSettings READ unitsSettings CONSTANT) Q_PROPERTY(QObject* autoConnectSettings READ autoConnectSettings CONSTANT) @@ -45,6 +47,7 @@ public: // Override from QGCTool virtual void setToolbox(QGCToolbox *toolbox); + AirMapSettings* airMapSettings (void) { return _airMapSettings; } AppSettings* appSettings (void) { return _appSettings; } UnitsSettings* unitsSettings (void) { return _unitsSettings; } AutoConnectSettings* autoConnectSettings (void) { return _autoConnectSettings; } @@ -55,6 +58,7 @@ public: BrandImageSettings* brandImageSettings (void) { return _brandImageSettings; } private: + AirMapSettings* _airMapSettings; AppSettings* _appSettings; UnitsSettings* _unitsSettings; AutoConnectSettings* _autoConnectSettings; diff --git a/src/Vehicle/ADSBVehicle.cc b/src/Vehicle/ADSBVehicle.cc index bd796cf0bc7d942abc2f1ce5978d17005cc5e449..6fcda9ef0eedb04f0223b2553232959710279ed9 100644 --- a/src/Vehicle/ADSBVehicle.cc +++ b/src/Vehicle/ADSBVehicle.cc @@ -27,6 +27,23 @@ ADSBVehicle::ADSBVehicle(mavlink_adsb_vehicle_t& adsbVehicle, QObject* parent) update(adsbVehicle); } +ADSBVehicle::ADSBVehicle(const QGeoCoordinate& location, float heading, QObject* parent) + : QObject(parent), _icaoAddress(0) +{ + update(location, heading); +} + +void ADSBVehicle::update(const QGeoCoordinate& location, float heading) +{ + _coordinate = location; + _altitude = location.altitude(); + _heading = heading; + emit coordinateChanged(_coordinate); + emit altitudeChanged(_altitude); + emit headingChanged(_heading); + _lastUpdateTimer.restart(); +} + void ADSBVehicle::update(mavlink_adsb_vehicle_t& adsbVehicle) { if (_icaoAddress != adsbVehicle.ICAO_address) { @@ -68,4 +85,10 @@ void ADSBVehicle::update(mavlink_adsb_vehicle_t& adsbVehicle) _heading = newHeading; emit headingChanged(_heading); } + _lastUpdateTimer.restart(); +} + +bool ADSBVehicle::expired() +{ + return _lastUpdateTimer.hasExpired(expirationTimeoutMs); } diff --git a/src/Vehicle/ADSBVehicle.h b/src/Vehicle/ADSBVehicle.h index fe6046fc1515118e101f181ad3d33cd571d0b997..f0124f089bd69cefe68cbfd662fe894099a3e9eb 100644 --- a/src/Vehicle/ADSBVehicle.h +++ b/src/Vehicle/ADSBVehicle.h @@ -11,6 +11,7 @@ #include #include +#include #include "QGCMAVLink.h" @@ -21,6 +22,8 @@ class ADSBVehicle : public QObject public: ADSBVehicle(mavlink_adsb_vehicle_t& adsbVehicle, QObject* parent = NULL); + ADSBVehicle(const QGeoCoordinate& location, float heading, QObject* parent = NULL); + Q_PROPERTY(int icaoAddress READ icaoAddress CONSTANT) Q_PROPERTY(QString callsign READ callsign NOTIFY callsignChanged) Q_PROPERTY(QGeoCoordinate coordinate READ coordinate NOTIFY coordinateChanged) @@ -36,6 +39,11 @@ public: /// Update the vehicle with new information void update(mavlink_adsb_vehicle_t& adsbVehicle); + void update(const QGeoCoordinate& location, float heading); + + /// check if the vehicle is expired and should be removed + bool expired(); + signals: void coordinateChanged(QGeoCoordinate coordinate); void callsignChanged(QString callsign); @@ -43,9 +51,14 @@ signals: void headingChanged(double heading); private: + static constexpr qint64 expirationTimeoutMs = 5000; ///< timeout with no update in ms after which the vehicle is removed. + ///< AirMap sends updates for each vehicle every second. + uint32_t _icaoAddress; QString _callsign; QGeoCoordinate _coordinate; double _altitude; double _heading; + + QElapsedTimer _lastUpdateTimer; }; diff --git a/src/Vehicle/Vehicle.cc b/src/Vehicle/Vehicle.cc index 64e27fa4774ce233995443e142a46f2415fa3102..de687093d7491d7f24d229aea27e5f187c5e4f5c 100644 --- a/src/Vehicle/Vehicle.cc +++ b/src/Vehicle/Vehicle.cc @@ -38,6 +38,7 @@ #include "QGCCameraManager.h" #include "VideoReceiver.h" #include "VideoManager.h" +#include "AirspaceController.h" QGC_LOGGING_CATEGORY(VehicleLog, "VehicleLog") @@ -139,6 +140,8 @@ Vehicle::Vehicle(LinkInterface* link, , _rallyPointManager(NULL) , _rallyPointManagerInitialRequestSent(false) , _parameterManager(NULL) + , _airspaceController(NULL) + , _airspaceManagerPerVehicle(NULL) , _armed(false) , _base_mode(0) , _custom_mode(0) @@ -257,6 +260,22 @@ Vehicle::Vehicle(LinkInterface* link, // Create camera manager instance _cameras = _firmwarePlugin->createCameraManager(this); emit dynamicCamerasChanged(); + + connect(&_adsbTimer, &QTimer::timeout, this, &Vehicle::_adsbTimerTimeout); + _adsbTimer.setSingleShot(false); + _adsbTimer.start(1000); + + _airspaceController = new AirspaceController(this); + + AirspaceManager* airspaceManager = _toolbox->airspaceManager(); + if (airspaceManager) { + _airspaceManagerPerVehicle = airspaceManager->instantiateVehicle(*this); + if (_airspaceManagerPerVehicle) { + connect(_airspaceManagerPerVehicle, &AirspaceManagerPerVehicle::trafficUpdate, this, &Vehicle::_trafficUpdate); + connect(_airspaceManagerPerVehicle, &AirspaceManagerPerVehicle::flightPermitStatusChanged, this, &Vehicle::flightPermitStatusChanged); + } + } + } // Disconnected Vehicle for offline editing @@ -317,6 +336,8 @@ Vehicle::Vehicle(MAV_AUTOPILOT firmwareType, , _rallyPointManager(NULL) , _rallyPointManagerInitialRequestSent(false) , _parameterManager(NULL) + , _airspaceController(NULL) + , _airspaceManagerPerVehicle(NULL) , _armed(false) , _base_mode(0) , _custom_mode(0) @@ -450,6 +471,9 @@ Vehicle::~Vehicle() delete _mav; _mav = NULL; + if (_airspaceManagerPerVehicle) { + delete _airspaceManagerPerVehicle; + } } void Vehicle::prepareDelete() @@ -2978,6 +3002,35 @@ void Vehicle::_updateHighLatencyLink(void) } } +void Vehicle::_trafficUpdate(QString traffic_id, QString vehicle_id, QGeoCoordinate location, float heading) +{ + Q_UNUSED(vehicle_id); + // qDebug() << "traffic update:" << traffic_id << vehicle_id << heading << location; + // TODO: filter based on minimum altitude? + if (_trafficVehicleMap.contains(traffic_id)) { + _trafficVehicleMap[traffic_id]->update(location, heading); + } else { + ADSBVehicle* vehicle = new ADSBVehicle(location, heading, this); + _trafficVehicleMap[traffic_id] = vehicle; + _adsbVehicles.append(vehicle); + } + +} +void Vehicle::_adsbTimerTimeout() +{ + // TODO: take into account _adsbICAOMap as well? Needs to be tested, especially the timeout + + for (auto it = _trafficVehicleMap.begin(); it != _trafficVehicleMap.end();) { + if (it.value()->expired()) { + _adsbVehicles.removeOne(it.value()); + delete it.value(); + it = _trafficVehicleMap.erase(it); + } else { + ++it; + } + } +} + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- diff --git a/src/Vehicle/Vehicle.h b/src/Vehicle/Vehicle.h index 8159e2c4ea99f0ccaeff53b494ce01c2ca3f9ab7..e1225e2cd897ce041453240662e9c17120fd873a 100644 --- a/src/Vehicle/Vehicle.h +++ b/src/Vehicle/Vehicle.h @@ -20,6 +20,7 @@ #include "MAVLinkProtocol.h" #include "UASMessageHandler.h" #include "SettingsFact.h" +#include class UAS; class UASInterface; @@ -35,6 +36,7 @@ class UASMessage; class SettingsManager; class ADSBVehicle; class QGCCameraManager; +class AirspaceController; Q_DECLARE_LOGGING_CATEGORY(VehicleLog) @@ -353,6 +355,8 @@ public: Q_PROPERTY(QString hobbsMeter READ hobbsMeter NOTIFY hobbsMeterChanged) Q_PROPERTY(bool vtolInFwdFlight READ vtolInFwdFlight WRITE setVtolInFwdFlight NOTIFY vtolInFwdFlightChanged) Q_PROPERTY(bool highLatencyLink READ highLatencyLink NOTIFY highLatencyLinkChanged) + Q_PROPERTY(AirspaceAuthorization::PermitStatus flightPermitStatus READ flightPermitStatus NOTIFY flightPermitStatusChanged) ///< state of flight permission + Q_PROPERTY(AirspaceController* airspaceController READ airspaceController CONSTANT) // Vehicle state used for guided control Q_PROPERTY(bool flying READ flying NOTIFY flyingChanged) ///< Vehicle is flying @@ -572,6 +576,8 @@ public: QmlObjectListModel* cameraTriggerPoints(void) { return &_cameraTriggerPoints; } QmlObjectListModel* adsbVehicles(void) { return &_adsbVehicles; } + AirspaceController* airspaceController() { return _airspaceController; } + int flowImageIndex() { return _flowImageIndex; } //-- Mavlink Logging @@ -755,6 +761,11 @@ public: /// Vehicle is about to be deleted void prepareDelete(); + AirspaceAuthorization::PermitStatus flightPermitStatus() const + { return _airspaceManagerPerVehicle ? _airspaceManagerPerVehicle->flightPermitStatus() : AirspaceAuthorization::PermitUnknown; } + + AirspaceManagerPerVehicle* airspaceManager() const { return _airspaceManagerPerVehicle; } + signals: void allLinksInactive(Vehicle* vehicle); void coordinateChanged(QGeoCoordinate coordinate); @@ -789,6 +800,8 @@ signals: void capabilityBitsChanged(uint64_t capabilityBits); void toolBarIndicatorsChanged(void); void highLatencyLinkChanged(bool highLatencyLink); + void flightPermitStatusChanged(); + void messagesReceivedChanged (); void messagesSentChanged (); @@ -887,6 +900,9 @@ private slots: void _updateHobbsMeter(void); void _vehicleParamLoaded(bool ready); + void _trafficUpdate(QString traffic_id, QString vehicle_id, QGeoCoordinate location, float heading); + void _adsbTimerTimeout(); + private: bool _containsLink(LinkInterface* link); void _addLink(LinkInterface* link); @@ -1037,7 +1053,10 @@ private: RallyPointManager* _rallyPointManager; bool _rallyPointManagerInitialRequestSent; - ParameterManager* _parameterManager; + ParameterManager* _parameterManager; + + AirspaceController* _airspaceController; + AirspaceManagerPerVehicle* _airspaceManagerPerVehicle; bool _armed; ///< true: vehicle is armed uint8_t _base_mode; ///< base_mode from HEARTBEAT @@ -1068,6 +1087,8 @@ private: QmlObjectListModel _adsbVehicles; QMap _adsbICAOMap; + QMap _trafficVehicleMap; + QTimer _adsbTimer; // Toolbox references FirmwarePluginManager* _firmwarePluginManager; diff --git a/src/api/QGCCorePlugin.cc b/src/api/QGCCorePlugin.cc index 58b06834e82948323797611d4de59f72d147f161..a2e2519687249738ac2c95b340d3c7ec48d49863 100644 --- a/src/api/QGCCorePlugin.cc +++ b/src/api/QGCCorePlugin.cc @@ -30,6 +30,7 @@ public: : pGeneral (NULL) , pCommLinks (NULL) , pOfflineMaps (NULL) + , pAirmap (NULL) , pMAVLink (NULL) , pConsole (NULL) #if defined(QT_DEBUG) @@ -53,6 +54,8 @@ public: delete pCommLinks; if(pOfflineMaps) delete pOfflineMaps; + if(pAirmap) + delete pAirmap; if(pMAVLink) delete pMAVLink; if(pConsole) @@ -70,6 +73,7 @@ public: QmlComponentInfo* pGeneral; QmlComponentInfo* pCommLinks; QmlComponentInfo* pOfflineMaps; + QmlComponentInfo* pAirmap; QmlComponentInfo* pMAVLink; QmlComponentInfo* pConsole; #if defined(QT_DEBUG) @@ -127,6 +131,10 @@ QVariantList &QGCCorePlugin::settingsPages() QUrl::fromUserInput("qrc:/qml/OfflineMap.qml"), QUrl::fromUserInput("qrc:/res/waves.svg")); _p->settingsList.append(QVariant::fromValue((QmlComponentInfo*)_p->pOfflineMaps)); + _p->pAirmap = new QmlComponentInfo(tr("AirMap"), + QUrl::fromUserInput("qrc:/qml/AirmapSettings.qml"), + QUrl::fromUserInput("")); + _p->settingsList.append(QVariant::fromValue((QmlComponentInfo*)_p->pAirmap)); _p->pMAVLink = new QmlComponentInfo(tr("MAVLink"), QUrl::fromUserInput("qrc:/qml/MavlinkSettings.qml"), QUrl::fromUserInput("qrc:/res/waves.svg")); diff --git a/src/api/QGCOptions.h b/src/api/QGCOptions.h index 9d06d00bdb2fa6161f5befda061623ddd8984fa6..3e2607409e64e379a3e497603175ea2c97876668 100644 --- a/src/api/QGCOptions.h +++ b/src/api/QGCOptions.h @@ -121,18 +121,11 @@ class CustomInstrumentWidget : public QObject { Q_OBJECT public: - //-- Widget Position - enum Pos { - POS_TOP_RIGHT = 0, - POS_CENTER_RIGHT = 1, - POS_BOTTOM_RIGHT = 2, - }; - Q_ENUMS(Pos) CustomInstrumentWidget(QObject* parent = NULL); Q_PROPERTY(QUrl source READ source CONSTANT) - Q_PROPERTY(Pos widgetPosition READ widgetPosition NOTIFY widgetPositionChanged) + Q_PROPERTY(double widgetTopMargin READ widgetTopMargin NOTIFY widgetTopMarginChanged) virtual QUrl source () { return QUrl(); } - virtual Pos widgetPosition () { return POS_CENTER_RIGHT; } + virtual double widgetTopMargin () { return 0.0; } signals: - void widgetPositionChanged (); + void widgetTopMarginChanged(); }; diff --git a/src/ui/preferences/GeneralSettings.qml b/src/ui/preferences/GeneralSettings.qml index e74cfe334f5cfb5fe4d05d06a2c5604a449a5b41..4a36ad9608269ea3ea66ec1c9d79de86ce80e186 100644 --- a/src/ui/preferences/GeneralSettings.qml +++ b/src/ui/preferences/GeneralSettings.qml @@ -555,6 +555,52 @@ QGCView { } } + //----------------------------------------------------------------- + //-- AirMap + Item { + width: _qgcView.width * 0.8 + height: unitLabel.height + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + visible: QGroundControl.settingsManager.rtkSettings.visible + QGCLabel { + text: qsTr("AirMap") + font.family: ScreenTools.demiboldFontFamily + } + } + Rectangle { + height: airMapCol.height + (ScreenTools.defaultFontPixelHeight * 2) + width: _qgcView.width * 0.8 + color: qgcPal.windowShade + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + visible: QGroundControl.settingsManager.airMapSettings.visible + Column { + id: airMapCol + spacing: ScreenTools.defaultFontPixelWidth + anchors.centerIn: parent + Row { + spacing: ScreenTools.defaultFontPixelWidth + QGCLabel {text: qsTr("API Key:"); width: _labelWidth; anchors.verticalCenter: parent.verticalCenter } + FactTextField {fact: QGroundControl.settingsManager.airMapSettings.apiKey; width: _editFieldWidth; anchors.verticalCenter: parent.verticalCenter } + } + Row { + spacing: ScreenTools.defaultFontPixelWidth + QGCLabel {text: qsTr("Client ID:"); width: _labelWidth; anchors.verticalCenter: parent.verticalCenter } + FactTextField {fact: QGroundControl.settingsManager.airMapSettings.clientID; width: _editFieldWidth; anchors.verticalCenter: parent.verticalCenter } + } + Row { + spacing: ScreenTools.defaultFontPixelWidth + QGCLabel {text: qsTr("User Name:"); width: _labelWidth; anchors.verticalCenter: parent.verticalCenter } + FactTextField {fact: QGroundControl.settingsManager.airMapSettings.userName; width: _editFieldWidth; anchors.verticalCenter: parent.verticalCenter } + } + Row { + spacing: ScreenTools.defaultFontPixelWidth + QGCLabel {text: qsTr("Password:"); width: _labelWidth; anchors.verticalCenter: parent.verticalCenter } + FactTextField {fact: QGroundControl.settingsManager.airMapSettings.password; width: _editFieldWidth; anchors.verticalCenter: parent.verticalCenter; echoMode: TextInput.Password } + } + } + } //----------------------------------------------------------------- //-- Video Source Item {