diff --git a/.gitignore b/.gitignore index 0832a6bacce75849bc2ed93c542685e8be2d9217..07ca8ac69cd7268bc5b03c064d0198210c4b9ae5 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ thirdParty/qserialport/lib/ libs/thirdParty/libxbee/lib/ GeneratedFiles/ gstreamer-1.0-android* +src/Airmap/Airmap_api_key.h *.autosave .settings/ @@ -84,3 +85,4 @@ src/latex/ .vagrant/ Qt*-linux*.tar.* +libs/airmapd/include/boost diff --git a/ChangeLog.md b/ChangeLog.md index df99792ec298717246f80bbecc194003ccdebf4b..4bb40590f408c7ce66ee154dd66359bfd8c9cd05 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -5,6 +5,7 @@ Note: This file only contains high level features or important fixes. ## 3.5 ### 3.5.0 - Not yet released +* Added Airmap integration to QGC * Add ESTIMATOR_STATUS values to new estimatorStatus Vehicle FactGroup. These are now available to display in instrument panel. ## 3.4 diff --git a/QGCExternalLibs.pri b/QGCExternalLibs.pri index fa73a0bb438eae4bc5ab4ec9644c86182f689ff6..6704d4accffca283b43d7755488588ee1ca01aac 100644 --- a/QGCExternalLibs.pri +++ b/QGCExternalLibs.pri @@ -142,3 +142,34 @@ 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 + MacBuild { + exists($${AIRMAPD_PATH}/macOS/Qt.5.11.0) { + message("Including support for AirMap for macOS") + LIBS += -L$${AIRMAPD_PATH}/macOS/Qt.5.11.0 -lairmap-qt + DEFINES += QGC_AIRMAP_ENABLED + } + } else:LinuxBuild { + exists($${AIRMAPD_PATH}/linux/Qt.5.11.0) { + message("Including support for AirMap for Linux") + LIBS += -L$${AIRMAPD_PATH}/linux/Qt.5.11.0 -lairmap-qt + DEFINES += QGC_AIRMAP_ENABLED + } + } else { + message("Skipping support for Airmap (unsupported platform)") + } + contains (DEFINES, QGC_AIRMAP_ENABLED) { + INCLUDEPATH += \ + $${AIRMAPD_PATH}/include + } +} diff --git a/QGCSetup.pri b/QGCSetup.pri index 7ba219472d5e6724dd05a61e17558fb864e6af89..508ff1f68314c465952172b3fcc32deb5bbc4b5f 100644 --- a/QGCSetup.pri +++ b/QGCSetup.pri @@ -51,6 +51,11 @@ MacBuild { QMAKE_POST_LINK += && rsync -a --delete $$BASEDIR/libs/lib/Frameworks $$DESTDIR/$${TARGET}.app/Contents/ # SDL2 Framework QMAKE_POST_LINK += && install_name_tool -change "@rpath/SDL2.framework/Versions/A/SDL2" "@executable_path/../Frameworks/SDL2.framework/Versions/A/SDL2" $$DESTDIR/$${TARGET}.app/Contents/MacOS/$${TARGET} + # AirMap + contains (DEFINES, QGC_AIRMAP_ENABLED) { + QMAKE_POST_LINK += && rsync -a $$BASEDIR/libs/airmapd/macOS/Qt.5.11.0/* $$DESTDIR/$${TARGET}.app/Contents/Frameworks/ + QMAKE_POST_LINK += && install_name_tool -change "@rpath/libairmap-qt.0.0.1.dylib" "@executable_path/../Frameworks/libairmap-qt.0.0.1.dylib" $$DESTDIR/$${TARGET}.app/Contents/MacOS/$${TARGET} + } } WindowsBuild { @@ -163,6 +168,11 @@ LinuxBuild { # QT_INSTALL_QML QMAKE_POST_LINK += && $$QMAKE_COPY --dereference --recursive $$[QT_INSTALL_QML] $$DESTDIR/Qt/ + # Airmap + contains (DEFINES, QGC_AIRMAP_ENABLED) { + QMAKE_POST_LINK += && $$QMAKE_COPY $$PWD/libs/airmapd/linux/Qt.5.11.0/libairmap-qt.so.0.0.1 $$DESTDIR/Qt/libs/ + } + # QGroundControl start script QMAKE_POST_LINK += && $$QMAKE_COPY $$BASEDIR/deploy/qgroundcontrol-start.sh $$DESTDIR QMAKE_POST_LINK += && $$QMAKE_COPY $$BASEDIR/deploy/qgroundcontrol.desktop $$DESTDIR diff --git a/libs/airmapd/include/airmap/advisory.h b/libs/airmapd/include/airmap/advisory.h new file mode 100644 index 0000000000000000000000000000000000000000..82fe8d5c8f556ac1ee8aa4c4629d56202d7b671d --- /dev/null +++ b/libs/airmapd/include/airmap/advisory.h @@ -0,0 +1,128 @@ +#ifndef AIRMAP_ADVISORY_H_ +#define AIRMAP_ADVISORY_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace airmap { + +/// Advisory provides functionality to query airspace and weather information about +/// a geographic area. +class Advisory : DoNotCopyOrMove { + public: + /// Advisory bundles together airspace information and its evaluation in terms + /// good to fly/needs information or feedback/conflict. + struct AirspaceAdvisory { + Status::Advisory advisory; /// Airspace information. + Status::Color color; /// The evaluation of the airspace. + std::uint32_t rule_id; /// The id of the ruleset. + std::string ruleset_id; /// The id of the rule. + }; + + /// Wind bundles up attributes describing a wind conditions. + struct Wind { + std::uint32_t heading = 0; ///< The heading in [°]. + float speed = 0.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. + float temperature = 0.0; ///< The temperature in [°C]. + float humidity = 0.0; + float visibility = 0.0; ///< Visibility in [m]. + float precipitation = 0.0; ///< The probability of precipitation in [%]. + std::string timezone; ///< The timezone of the weather location. + DateTime time; ///< Timestamp of the weather report. + float dew_point = 0.0; ///< The current dew point. + float mslp = 0.0; ///< The Median Sea Level Pressure in [mbar]. + }; + + /// ForId bundles up types to ease interaction + /// with Advisory::for_id. + struct ForId { + /// Parameters bundles up input parameters. + struct Parameters { + Optional start; ///< Search for advisories before this time. + Optional end; ///< Search for advisories after this time. + FlightPlan::Id id; ///< Search for advisories relating to this flight plan. + }; + /// Result models the outcome of calling Advisory::for_id. + using Result = Outcome, Error>; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Advisory::for_id finishes. + using Callback = std::function; + }; + + /// Search bundles up types to ease interaction + /// with Advisory::search. + struct Search { + /// Parameters bundles up input parameters. + struct Parameters { + Required geometry; ///< Evaluate rulesets intersecting this geometry. + Required rulesets; ///< Evaluate these rulesets. + Optional start; ///< Search for advisories after this time. + Optional end; ///< Search for advisories before this time. + }; + /// Result models the outcome of calling Advisory::search. + using Result = Outcome, Error>; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Advisory::_search finishes. + using Callback = std::function; + }; + + /// ReportWeather bundles up types to ease interaction + /// with Advisory::report_weather. + struct ReportWeather { + /// Parameters bundles up input parameters. + struct Parameters { + float latitude; ///< The latitude component of the takeoff point in [°]. + float longitude; ///< The longitude component of the takeoff point in [°]. + Optional start; ///< Search for weather data after this time. + Optional end; ///< Search for weather data before this time. + }; + /// Result models the outcome of calling Advisory::report_weather. + using Result = Outcome; + /// Callback describes the function signature of the callback that is + /// invoked when a call to Advisory::report_weather finishes. + using Callback = std::function; + }; + + /// for_id searches flight advisories for a flight plan and reports + /// results back to 'cb'. + virtual void for_id(const ForId::Parameters& parameters, const ForId::Callback& cb) = 0; + + /// search searches flight advisories for 'parameters' and reports + /// results back to 'cb'. + virtual void search(const Search::Parameters& parameters, const Search::Callback& cb) = 0; + + /// report_weather gets the current weather conditions and reports + /// results back to 'cb'. + virtual void report_weather(const ReportWeather::Parameters& parameters, const ReportWeather::Callback& cb) = 0; + + protected: + /// @cond + Advisory() = default; + /// @endcond +}; + +} // namespace airmap + +#endif // AIRMAP_ADVISORY_H_ 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..6c51a6e328cff9e37e65fd11488f89793a16cde4 --- /dev/null +++ b/libs/airmapd/include/airmap/airspace.h @@ -0,0 +1,385 @@ +#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{false}; ///< True if the airport features paved runways. + std::string phone; ///< The phone number of the airport (typically the tower). + bool tower{false}; ///< True if the airport features a tower. + std::vector runways; ///< Collection of runways available at the airport. + float elevation{0.f}; ///< The elevation of the airport in [m]. + float longest_runway{0.f}; ///< The lenght of th longest runway in [m]. + bool instrument_approach_procedure{false}; ///< True if the airport features equipment supporting an IAP. + Use use{Use::public_}; ///< Types of use offered by the airport. + }; + + /// ControlledAirspace bundles up properties describing + /// a controlled airspace. + struct ControlledAirspace { + std::string airspace_classification; ///< The classification of the 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..d4d447e9a20c5f60904528122a0c3f2c59a4e83a --- /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; + /// 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..2a7ec52fa079cfc14c83d13779f1da6d7632f42c --- /dev/null +++ b/libs/airmapd/include/airmap/client.h @@ -0,0 +1,149 @@ +#ifndef AIRMAP_CLIENT_H_ +#define AIRMAP_CLIENT_H_ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace airmap { + +class Advisory; +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; + + /// advisory returns the Advisory implementation provided by the client. + virtual Advisory& advisory() = 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..d8ded48caf8b53d9a8f1c4aa564aa610b6769647 --- /dev/null +++ b/libs/airmapd/include/airmap/date_time.h @@ -0,0 +1,144 @@ +#ifndef AIRMAP_DATE_TIME_H_ +#define AIRMAP_DATE_TIME_H_ + +#include +#include + +namespace airmap { +class DateTime; +template +class Duration; + +namespace detail { +class Duration; +} // namespace detail + +namespace tag { + +struct Hours {}; +struct Minutes {}; +struct Seconds {}; +struct Milliseconds {}; +struct Microseconds {}; + +} // namespace tag + +using Hours = Duration; +using Minutes = Duration; +using Seconds = Duration; +using Milliseconds = Duration; +using Microseconds = Duration; + +/// Clock marks the reference for time measurements. +class Clock { + public: + static DateTime universal_time(); + static DateTime local_time(); +}; + +namespace boost_iso { + +DateTime datetime(const std::string &iso_time); +std::string to_iso_string(const DateTime &); + +} // namespace boost_iso + +/// DateTime marks a specific point in time, in reference to Clock. +class DateTime { + public: + DateTime(); + ~DateTime(); + DateTime(DateTime const &); + DateTime(DateTime &&); + DateTime &operator=(const DateTime &); + DateTime &operator=(DateTime &&); + + DateTime operator+(const detail::Duration &) const; + Microseconds operator-(const DateTime &) const; + bool operator==(const DateTime &) const; + bool operator!=(const DateTime &) const; + + friend std::istream &operator>>(std::istream &, DateTime &); + friend std::ostream &operator<<(std::ostream &, const DateTime &); + + DateTime date() const; + Microseconds time_of_day() const; + + private: + struct Impl; + std::unique_ptr impl; + + explicit DateTime(std::unique_ptr &&); + friend DateTime Clock::universal_time(); + friend DateTime Clock::local_time(); + friend DateTime boost_iso::datetime(const std::string &iso_time); + friend std::string boost_iso::to_iso_string(const DateTime &datetime); +}; + +Hours hours(int64_t raw); +Minutes minutes(int64_t raw); +Seconds seconds(int64_t raw); +Milliseconds milliseconds(int64_t raw); +Microseconds microseconds(int64_t raw); + +namespace detail { + +class Duration { + public: + Duration(); + ~Duration(); + Duration(Duration const &old); + Duration &operator=(const Duration &); + + uint64_t total_seconds() const; + uint64_t total_milliseconds() const; + uint64_t total_microseconds() const; + + uint64_t hours() const; + + private: + struct Impl; + std::unique_ptr impl; + + friend DateTime DateTime::operator+(const detail::Duration &) const; + friend Microseconds DateTime::operator-(const DateTime &) const; + friend Microseconds DateTime::time_of_day() const; + + friend Hours airmap::hours(int64_t raw); + friend Minutes airmap::minutes(int64_t raw); + friend Seconds airmap::seconds(int64_t raw); + friend Milliseconds airmap::milliseconds(int64_t raw); + friend Microseconds airmap::microseconds(int64_t raw); +}; + +} // namespace detail + +template +class Duration : public detail::Duration {}; + +/// milliseconds_since_epoch returns the milliseconds that elapsed since the UNIX epoch. +uint64_t milliseconds_since_epoch(const DateTime &dt); +/// microseconds_since_epoch returns the microseconds that elapsed since the UNIX epoch. +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, uint64_t 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..bb3d79c05c9d0c38922ee45df4e39ced9ab19c53 --- /dev/null +++ b/libs/airmapd/include/airmap/flight_plan.h @@ -0,0 +1,62 @@ +#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. + std::vector rulesets; ///< RuleSets that apply to this flight plan. + std::unordered_map features; ///< Additional properties of the planned flight. +}; + +} // 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..5ad55ed085d6e3eaddca9c814a4af3e24fad1738 --- /dev/null +++ b/libs/airmapd/include/airmap/qt/client.h @@ -0,0 +1,64 @@ +#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; + Advisory& advisory() 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.11.0/libairmap-qt.0.0.1.dylib b/libs/airmapd/macOS/Qt.5.11.0/libairmap-qt.0.0.1.dylib new file mode 120000 index 0000000000000000000000000000000000000000..f88997b2e9c6e43fddc23ae0d8f7cae5b475cbe7 --- /dev/null +++ b/libs/airmapd/macOS/Qt.5.11.0/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.11.0/libairmap-qt.0.dylib b/libs/airmapd/macOS/Qt.5.11.0/libairmap-qt.0.dylib new file mode 100755 index 0000000000000000000000000000000000000000..1a4304fad386793411fec465f259279c16f1c0af Binary files /dev/null and b/libs/airmapd/macOS/Qt.5.11.0/libairmap-qt.0.dylib differ diff --git a/libs/airmapd/macOS/Qt.5.11.0/libairmap-qt.dylib b/libs/airmapd/macOS/Qt.5.11.0/libairmap-qt.dylib new file mode 120000 index 0000000000000000000000000000000000000000..c62931ac622bc94698aa6f7822ad593565784a26 --- /dev/null +++ b/libs/airmapd/macOS/Qt.5.11.0/libairmap-qt.dylib @@ -0,0 +1 @@ +libairmap-qt.0.0.1.dylib \ No newline at end of file diff --git a/libs/mavlink/include/mavlink/v2.0 b/libs/mavlink/include/mavlink/v2.0 index d8fcf0a694dc11b3f83b89a0970e3d8c4e48d418..f5c0ba684659fbc6c6c5f8777bd30e0b3c32fdef 160000 --- a/libs/mavlink/include/mavlink/v2.0 +++ b/libs/mavlink/include/mavlink/v2.0 @@ -1 +1 @@ -Subproject commit d8fcf0a694dc11b3f83b89a0970e3d8c4e48d418 +Subproject commit f5c0ba684659fbc6c6c5f8777bd30e0b3c32fdef diff --git a/qgcresources.qrc b/qgcresources.qrc index b274d33fddbd73c30716ba2130f4c9e180bf907a..79fababc83b52445b9638b2f311bc9a37d685012 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,8 @@ src/FirmwarePlugin/APM/APMBrandImageSub.png src/FirmwarePlugin/PX4/PX4BrandImage.png src/FlightMap/Images/sub.png + src/FlightMap/Images/AlertAircraft.svg + src/FlightMap/Images/AwarenessAircraft.svg resources/check.svg @@ -213,6 +216,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 8b2606331b199445ce362e906d267dd325c1a547..0f8c82c81c6ceeefdbdd68b8a83c8a2d5147f4b9 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -589,6 +589,7 @@ HEADERS += \ src/QmlControls/QGCImageProvider.h \ src/QmlControls/QGroundControlQmlGlobal.h \ src/QmlControls/QmlObjectListModel.h \ + src/QmlControls/QGCGeoBoundingCube.h \ src/QmlControls/RCChannelMonitorController.h \ src/QmlControls/ScreenToolsController.h \ src/QtLocationPlugin/QMLControl/QGCMapEngineManager.h \ @@ -619,6 +620,7 @@ HEADERS += \ src/uas/UASMessageHandler.h \ src/UTM.h \ + AndroidBuild { HEADERS += \ src/Joystick/JoystickAndroid.h \ @@ -785,6 +787,7 @@ SOURCES += \ src/QmlControls/QGCImageProvider.cc \ src/QmlControls/QGroundControlQmlGlobal.cc \ src/QmlControls/QmlObjectListModel.cc \ + src/QmlControls/QGCGeoBoundingCube.cc \ src/QmlControls/RCChannelMonitorController.cc \ src/QmlControls/ScreenToolsController.cc \ src/QtLocationPlugin/QMLControl/QGCMapEngineManager.cc \ @@ -1093,6 +1096,92 @@ SOURCES += \ src/FactSystem/ParameterManager.cc \ src/FactSystem/SettingsFact.cc \ +#------------------------------------------------------------------------------------- +# AirMap + +contains (DEFINES, QGC_AIRMAP_ENABLED) { + + #-- These should be always enabled but not yet + INCLUDEPATH += \ + src/AirspaceManagement + + HEADERS += \ + src/AirspaceManagement/AirspaceAdvisoryProvider.h \ + src/AirspaceManagement/AirspaceFlightPlanProvider.h \ + src/AirspaceManagement/AirspaceManager.h \ + src/AirspaceManagement/AirspaceRestriction.h \ + src/AirspaceManagement/AirspaceRestrictionProvider.h \ + src/AirspaceManagement/AirspaceRulesetsProvider.h \ + src/AirspaceManagement/AirspaceVehicleManager.h \ + src/AirspaceManagement/AirspaceWeatherInfoProvider.h \ + + SOURCES += \ + src/AirspaceManagement/AirspaceAdvisoryProvider.cc \ + src/AirspaceManagement/AirspaceFlightPlanProvider.cc \ + src/AirspaceManagement/AirspaceManager.cc \ + src/AirspaceManagement/AirspaceRestriction.cc \ + src/AirspaceManagement/AirspaceRestrictionProvider.cc \ + src/AirspaceManagement/AirspaceRulesetsProvider.cc \ + src/AirspaceManagement/AirspaceVehicleManager.cc \ + src/AirspaceManagement/AirspaceWeatherInfoProvider.cc \ + + #-- This is the AirMap implementation of the above + RESOURCES += \ + src/Airmap/airmap.qrc + + INCLUDEPATH += \ + src/Airmap + + HEADERS += \ + src/Airmap/AirMapAdvisoryManager.h \ + src/Airmap/AirMapFlightManager.h \ + src/Airmap/AirMapFlightPlanManager.h \ + src/Airmap/AirMapManager.h \ + src/Airmap/AirMapRestrictionManager.h \ + src/Airmap/AirMapRulesetsManager.h \ + src/Airmap/AirMapSettings.h \ + src/Airmap/AirMapSharedState.h \ + src/Airmap/AirMapTelemetry.h \ + src/Airmap/AirMapTrafficMonitor.h \ + src/Airmap/AirMapVehicleManager.h \ + src/Airmap/AirMapWeatherInfoManager.h \ + src/Airmap/LifetimeChecker.h \ + + SOURCES += \ + src/Airmap/AirMapAdvisoryManager.cc \ + src/Airmap/AirMapFlightManager.cc \ + src/Airmap/AirMapFlightPlanManager.cc \ + src/Airmap/AirMapManager.cc \ + src/Airmap/AirMapRestrictionManager.cc \ + src/Airmap/AirMapRulesetsManager.cc \ + src/Airmap/AirMapSettings.cc \ + src/Airmap/AirMapSharedState.cc \ + src/Airmap/AirMapTelemetry.cc \ + src/Airmap/AirMapTrafficMonitor.cc \ + src/Airmap/AirMapVehicleManager.cc \ + src/Airmap/AirMapWeatherInfoManager.cc \ + + #-- Do we have an API key? + exists(src/Airmap/Airmap_api_key.h) { + HEADERS += \ + src/Airmap/Airmap_api_key.h + DEFINES += QGC_AIRMAP_KEY_AVAILABLE + } + + include(src/Airmap/QJsonWebToken/src/qjsonwebtoken.pri) + +} else { + #-- Dummies + INCLUDEPATH += \ + src/Airmap/dummy + RESOURCES += \ + src/Airmap/dummy/airmap_dummy.qrc + HEADERS += \ + src/Airmap/dummy/AirspaceManager.h + SOURCES += \ + src/Airmap/dummy/AirspaceManager.cc +} + #------------------------------------------------------------------------------------- # Video Streaming 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/AirMap.SettingsGroup.json b/src/Airmap/AirMap.SettingsGroup.json new file mode 100644 index 0000000000000000000000000000000000000000..2f2181e0cfe23071ae3ea8ce6006f735b90970bd --- /dev/null +++ b/src/Airmap/AirMap.SettingsGroup.json @@ -0,0 +1,50 @@ +[ +{ + "name": "usePersonalApiKey", + "shortDescription": "Use Personal AirMap API Key", + "type": "bool", + "defaultValue": false +}, +{ + "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": "" +}, +{ + "name": "enableAirMap", + "shortDescription": "Enable AirMap", + "type": "bool", + "defaultValue": false +}, +{ + "name": "enableAirspace", + "shortDescription": "Show Airspace on Map (Experimental)", + "type": "bool", + "defaultValue": false +}, +{ + "name": "enableTelemetry", + "shortDescription": "Enable AirMap Telemetry", + "type": "bool", + "defaultValue": false +} +] diff --git a/src/Airmap/AirMapAdvisoryManager.cc b/src/Airmap/AirMapAdvisoryManager.cc new file mode 100644 index 0000000000000000000000000000000000000000..de4a4d91335e21dd260559f998444ea8936a0eb6 --- /dev/null +++ b/src/Airmap/AirMapAdvisoryManager.cc @@ -0,0 +1,143 @@ +/**************************************************************************** + * + * (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 "AirMapAdvisoryManager.h" +#include "AirspaceRestriction.h" +#include "AirMapRulesetsManager.h" +#include "AirMapManager.h" +#include "QGCApplication.h" + +#include +#include +#include + +#include "airmap/airspaces.h" +#include "airmap/advisory.h" + +#define ADVISORY_UPDATE_DISTANCE 500 //-- 500m threshold for updates + +using namespace airmap; + +//----------------------------------------------------------------------------- +AirMapAdvisory::AirMapAdvisory(QObject* parent) + : AirspaceAdvisory(parent) +{ +} + +//----------------------------------------------------------------------------- +AirMapAdvisoryManager::AirMapAdvisoryManager(AirMapSharedState& shared, QObject *parent) + : AirspaceAdvisoryProvider(parent) + , _valid(false) + , _shared(shared) +{ +} + +//----------------------------------------------------------------------------- +void +AirMapAdvisoryManager::setROI(const QGCGeoBoundingCube& roi, bool reset) +{ + //-- If first time or we've moved more than ADVISORY_UPDATE_DISTANCE, ask for updates. + if(reset || (!_lastROI.isValid() || _lastROI.pointNW.distanceTo(roi.pointNW) > ADVISORY_UPDATE_DISTANCE || _lastROI.pointSE.distanceTo(roi.pointSE) > ADVISORY_UPDATE_DISTANCE)) { + _lastROI = roi; + _requestAdvisories(); + } +} + +//----------------------------------------------------------------------------- +static bool +adv_sort(QObject* a, QObject* b) +{ + AirMapAdvisory* aa = qobject_cast(a); + AirMapAdvisory* bb = qobject_cast(b); + if(!aa || !bb) return false; + return static_cast(aa->color()) > static_cast(bb->color()); +} + +//----------------------------------------------------------------------------- +void +AirMapAdvisoryManager::_requestAdvisories() +{ + qCDebug(AirMapManagerLog) << "Advisories Request (ROI Changed)"; + if (!_shared.client()) { + qCDebug(AirMapManagerLog) << "No AirMap client instance. Not updating Advisories"; + _valid = false; + emit advisoryChanged(); + return; + } + _valid = false; + _advisories.clearAndDeleteContents(); + Advisory::Search::Parameters params; + //-- Geometry + Geometry::Polygon polygon; + //-- Get ROI bounding box, clipping to max area of interest + for (const auto& qcoord : _lastROI.polygon2D(qgcApp()->toolbox()->airspaceManager()->maxAreaOfInterest())) { + Geometry::Coordinate coord; + coord.latitude = qcoord.latitude(); + coord.longitude = qcoord.longitude(); + polygon.outer_ring.coordinates.push_back(coord); + } + params.geometry = Geometry(polygon); + //-- Rulesets + AirMapRulesetsManager* pRulesMgr = dynamic_cast(qgcApp()->toolbox()->airspaceManager()->ruleSets()); + QString ruleIDs; + if(pRulesMgr) { + for(int rs = 0; rs < pRulesMgr->ruleSets()->count(); rs++) { + AirMapRuleSet* ruleSet = qobject_cast(pRulesMgr->ruleSets()->get(rs)); + //-- If this ruleset is selected + if(ruleSet && ruleSet->selected()) { + ruleIDs = ruleIDs + ruleSet->id(); + //-- Separate rules with commas + if(rs < pRulesMgr->ruleSets()->count() - 1) { + ruleIDs = ruleIDs + ","; + } + } + } + } + if(ruleIDs.isEmpty()) { + qCDebug(AirMapManagerLog) << "No rules defined. Not updating Advisories"; + _valid = false; + emit advisoryChanged(); + return; + } + params.rulesets = ruleIDs.toStdString(); + //-- Time + quint64 start = static_cast(QDateTime::currentDateTimeUtc().toMSecsSinceEpoch()); + quint64 end = start + 60 * 30 * 1000; + params.start = airmap::from_milliseconds_since_epoch(airmap::milliseconds(static_cast(start))); + params.end = airmap::from_milliseconds_since_epoch(airmap::milliseconds(static_cast(end))); + std::weak_ptr isAlive(_instance); + _shared.client()->advisory().search(params, [this, isAlive](const Advisory::Search::Result& result) { + if (!isAlive.lock()) return; + if (result) { + qCDebug(AirMapManagerLog) << "Successful advisory search. Items:" << result.value().size(); + _airspaceColor = AirspaceAdvisoryProvider::Green; + for (const auto& advisory : result.value()) { + AirMapAdvisory* pAdvisory = new AirMapAdvisory(this); + pAdvisory->_id = QString::fromStdString(advisory.advisory.airspace.id()); + pAdvisory->_name = QString::fromStdString(advisory.advisory.airspace.name()); + pAdvisory->_type = static_cast(advisory.advisory.airspace.type()); + pAdvisory->_color = static_cast(advisory.color); + if(pAdvisory->_color > _airspaceColor) { + _airspaceColor = pAdvisory->_color; + } + _advisories.append(pAdvisory); + qCDebug(AirMapManagerLog) << "Adding advisory" << pAdvisory->name(); + } + //-- Sort in order of color (priority) + _advisories.beginReset(); + std::sort(_advisories.objectList()->begin(), _advisories.objectList()->end(), adv_sort); + _advisories.endReset(); + _valid = true; + } else { + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + qCDebug(AirMapManagerLog) << "Advisories Request Failed" << QString::fromStdString(result.error().message()) << description; + } + emit advisoryChanged(); + }); +} diff --git a/src/Airmap/AirMapAdvisoryManager.h b/src/Airmap/AirMapAdvisoryManager.h new file mode 100644 index 0000000000000000000000000000000000000000..bbbfbae5e67891e099fdbd0b1e55abbfa7f54c7d --- /dev/null +++ b/src/Airmap/AirMapAdvisoryManager.h @@ -0,0 +1,71 @@ +/**************************************************************************** + * + * (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 "QmlObjectListModel.h" + +#include "LifetimeChecker.h" + +#include "AirspaceAdvisoryProvider.h" +#include "AirMapSharedState.h" + +#include "QGCGeoBoundingCube.h" + +#include "airmap/status.h" + +/** + * @file AirMapAdvisoryManager.h + * Advisory information provided by AirMap. + */ + +//----------------------------------------------------------------------------- +class AirMapAdvisory : public AirspaceAdvisory +{ + Q_OBJECT + friend class AirMapAdvisoryManager; + friend class AirMapFlightPlanManager; +public: + AirMapAdvisory (QObject* parent = nullptr); + QString id () override { return _id; } + QString name () override { return _name; } + AdvisoryType type () override { return _type; } + QGeoCoordinate coordinates () override { return _coordinates; } + qreal radius () override { return _radius; } + AirspaceAdvisoryProvider::AdvisoryColor color () override { return _color; } +private: + QString _id; + QString _name; + AdvisoryType _type; + QGeoCoordinate _coordinates; + qreal _radius; + AirspaceAdvisoryProvider::AdvisoryColor _color; +}; + +//----------------------------------------------------------------------------- +class AirMapAdvisoryManager : public AirspaceAdvisoryProvider, public LifetimeChecker +{ + Q_OBJECT +public: + AirMapAdvisoryManager (AirMapSharedState &shared, QObject *parent = nullptr); + bool valid () override { return _valid; } + AdvisoryColor airspaceColor () override { return _airspaceColor; } + QmlObjectListModel* advisories () override { return &_advisories; } + void setROI (const QGCGeoBoundingCube& roi, bool reset = false) override; +signals: + void error (const QString& what, const QString& airmapdMessage, const QString& airmapdDetails); +private: + void _requestAdvisories (); +private: + bool _valid; + AirMapSharedState& _shared; + QGCGeoBoundingCube _lastROI; + QmlObjectListModel _advisories; + AdvisoryColor _airspaceColor; +}; diff --git a/src/Airmap/AirMapFlightManager.cc b/src/Airmap/AirMapFlightManager.cc new file mode 100644 index 0000000000000000000000000000000000000000..d85c0dab209794a198d38685c3aa4d15d9fc4be4 --- /dev/null +++ b/src/Airmap/AirMapFlightManager.cc @@ -0,0 +1,112 @@ +/**************************************************************************** + * + * (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 "AirMapFlightManager.h" +#include "AirMapManager.h" +#include "AirMapRulesetsManager.h" +#include "QGCApplication.h" + +#include "QGCMAVLink.h" + +#include "airmap/pilots.h" +#include "airmap/flights.h" +#include "airmap/date_time.h" +#include "airmap/flight_plans.h" +#include "airmap/geometry.h" + +using namespace airmap; + +//----------------------------------------------------------------------------- +AirMapFlightManager::AirMapFlightManager(AirMapSharedState& shared) + : _shared(shared) +{ + +} + +//----------------------------------------------------------------------------- +void +AirMapFlightManager::findFlight(const QGCGeoBoundingCube& bc) +{ + _state = State::FetchFlights; + _searchArea = bc; + std::weak_ptr isAlive(_instance); + _shared.doRequestWithLogin([this, isAlive](const QString& login_token) { + if (!isAlive.lock()) return; + if (_state != State::FetchFlights) return; + QList coords = _searchArea.polygon2D(); + Geometry::LineString lineString; + for (const auto& qcoord : coords) { + Geometry::Coordinate coord; + coord.latitude = qcoord.latitude(); + coord.longitude = qcoord.longitude(); + lineString.coordinates.push_back(coord); + } + _flightID.clear(); + Flights::Search::Parameters params; + params.authorization = login_token.toStdString(); + params.geometry = Geometry(lineString); + _shared.client()->flights().search(params, [this, isAlive](const Flights::Search::Result& result) { + if (!isAlive.lock()) return; + if (_state != State::FetchFlights) return; + if (result && result.value().flights.size() > 0) { + const Flights::Search::Response& response = result.value(); + qCDebug(AirMapManagerLog) << "Find flights response"; + for (const auto& flight : response.flights) { + QString fid = QString::fromStdString(flight.id); + qCDebug(AirMapManagerLog) << "Checking flight:" << fid; + if(flight.geometry.type() == Geometry::Type::line_string) { + const Geometry::LineString& lineString = flight.geometry.details_for_line_string(); + QList rcoords; + for (const auto& vertex : lineString.coordinates) { + rcoords.append(QGeoCoordinate(vertex.latitude, vertex.longitude)); + } + if(_searchArea == rcoords) { + qCDebug(AirMapManagerLog) << "Found match:" << fid; + _flightID = fid; + _state = State::Idle; + emit flightIDChanged(); + return; + } + } + } + } + qCDebug(AirMapManagerLog) << "No flights found"; + emit flightIDChanged(); + }); + _state = State::Idle; + }); +} + +//----------------------------------------------------------------------------- +void +AirMapFlightManager::endFlight(const QString& flightID) +{ + if (_state != State::Idle) { + qCWarning(AirMapManagerLog) << "AirMapFlightManager::endFlight: State not idle"; + return; + } + qCDebug(AirMapManagerLog) << "Ending flight" << flightID; + _state = State::FlightEnd; + 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; + if (!result) { + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Failed to end Flight", + QString::fromStdString(result.error().message()), description); + } + }); +} + + diff --git a/src/Airmap/AirMapFlightManager.h b/src/Airmap/AirMapFlightManager.h new file mode 100644 index 0000000000000000000000000000000000000000..fb17cdfd9c84d68191fcec5b77fcd64d2040d0ec --- /dev/null +++ b/src/Airmap/AirMapFlightManager.h @@ -0,0 +1,53 @@ +/**************************************************************************** + * + * (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 "LifetimeChecker.h" +#include "AirMapSharedState.h" +#include "AirspaceFlightPlanProvider.h" + +#include +#include +#include +#include + +//-- TODO: This is not even WIP yet. Just a skeleton of what's to come. + +//----------------------------------------------------------------------------- +/// class to upload a flight +class AirMapFlightManager : public QObject, public LifetimeChecker +{ + Q_OBJECT +public: + AirMapFlightManager (AirMapSharedState& shared); + + void findFlight (const QGCGeoBoundingCube& bc); + void endFlight (const QString& id); + QString flightID () { return _flightID; } + +signals: + void error (const QString& what, const QString& airmapdMessage, const QString& airmapdDetails); + void flightIDChanged (); + +private: + + enum class State { + Idle, + GetPilotID, + FetchFlights, + FlightEnd, + }; + + State _state = State::Idle; + AirMapSharedState& _shared; + QString _flightID; + QGCGeoBoundingCube _searchArea; +}; + diff --git a/src/Airmap/AirMapFlightPlanManager.cc b/src/Airmap/AirMapFlightPlanManager.cc new file mode 100644 index 0000000000000000000000000000000000000000..72462b2967a71d98470a95b957932c9c28cb7c67 --- /dev/null +++ b/src/Airmap/AirMapFlightPlanManager.cc @@ -0,0 +1,918 @@ +/**************************************************************************** + * + * (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 "AirMapFlightPlanManager.h" +#include "AirMapManager.h" +#include "AirMapRulesetsManager.h" +#include "AirMapAdvisoryManager.h" +#include "QGCApplication.h" +#include "SettingsManager.h" + +#include "PlanMasterController.h" +#include "QGCMAVLink.h" + +#include "airmap/date_time.h" +#include "airmap/flight_plans.h" +#include "airmap/flights.h" +#include "airmap/geometry.h" +#include "airmap/pilots.h" + +using namespace airmap; + +//----------------------------------------------------------------------------- +AirMapFlightAuthorization::AirMapFlightAuthorization(const Evaluation::Authorization auth, QObject *parent) + : AirspaceFlightAuthorization(parent) + , _auth(auth) +{ +} + +//----------------------------------------------------------------------------- +AirspaceFlightAuthorization::AuthorizationStatus +AirMapFlightAuthorization::status() +{ + switch(_auth.status) { + case Evaluation::Authorization::Status::accepted: + return AirspaceFlightAuthorization::Accepted; + case Evaluation::Authorization::Status::rejected: + return AirspaceFlightAuthorization::Rejected; + case Evaluation::Authorization::Status::pending: + return AirspaceFlightAuthorization::Pending; + case Evaluation::Authorization::Status::accepted_upon_submission: + return AirspaceFlightAuthorization::AcceptedOnSubmission; + case Evaluation::Authorization::Status::rejected_upon_submission: + return AirspaceFlightAuthorization::RejectedOnSubmission; + } + return AirspaceFlightAuthorization::Unknown; +} + +//----------------------------------------------------------------------------- +AirMapFlightInfo::AirMapFlightInfo(const airmap::Flight& flight, QObject *parent) + : AirspaceFlightInfo(parent) + , _flight(flight) +{ + //-- Load bounding box geometry + const Geometry& geometry = flight.geometry; + if(geometry.type() == Geometry::Type::polygon) { + const Geometry::Polygon& polygon = geometry.details_for_polygon(); + 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); + } + _boundingBox.append(QVariant::fromValue(coord)); + } + } +} + +//----------------------------------------------------------------------------- +QString +AirMapFlightInfo::createdTime() +{ + return QDateTime::fromMSecsSinceEpoch(static_cast(airmap::milliseconds_since_epoch(_flight.created_at))).toString("yyyy MM dd - hh:mm:ss"); +} + +//----------------------------------------------------------------------------- +QString +AirMapFlightInfo::startTime() +{ + return QDateTime::fromMSecsSinceEpoch(static_cast(airmap::milliseconds_since_epoch(_flight.start_time))).toString("yyyy MM dd - hh:mm:ss"); +} + +//----------------------------------------------------------------------------- +QDateTime +AirMapFlightInfo::qStartTime() +{ + return QDateTime::fromMSecsSinceEpoch(static_cast(airmap::milliseconds_since_epoch(_flight.start_time))); +} + +//----------------------------------------------------------------------------- +bool +AirMapFlightInfo::active() +{ + QDateTime end = QDateTime::fromMSecsSinceEpoch(static_cast(airmap::milliseconds_since_epoch(_flight.end_time))); + QDateTime now = QDateTime::currentDateTime(); + return end > now; +} + +//----------------------------------------------------------------------------- +void +AirMapFlightInfo::setEndFlight(DateTime end) +{ + _flight.end_time = end; + emit activeChanged(); +} + +//----------------------------------------------------------------------------- +QString +AirMapFlightInfo::endTime() +{ + return QDateTime::fromMSecsSinceEpoch(static_cast(airmap::milliseconds_since_epoch(_flight.end_time))).toString("yyyy MM dd - hh:mm:ss"); +} + +//----------------------------------------------------------------------------- +AirMapFlightPlanManager::AirMapFlightPlanManager(AirMapSharedState& shared, QObject *parent) + : AirspaceFlightPlanProvider(parent) + , _shared(shared) +{ + connect(&_pollTimer, &QTimer::timeout, this, &AirMapFlightPlanManager::_pollBriefing); + _flightStartTime = QDateTime::currentDateTime().addSecs(60); +} + +//----------------------------------------------------------------------------- +AirMapFlightPlanManager::~AirMapFlightPlanManager() +{ + _advisories.deleteListAndContents(); + _rulesets.deleteListAndContents(); +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::setFlightStartTime(QDateTime start) +{ + if(start < QDateTime::currentDateTime()) { + start = QDateTime::currentDateTime().addSecs(1); + } + if(_flightStartTime != start) { + _flightStartTime = start; + emit flightStartTimeChanged(); + } + qCDebug(AirMapManagerLog) << "Set time start time" << _flightStartTime; +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::setFlightStartsNow(bool now) +{ + _flightStartsNow = now; + emit flightStartsNowChanged(); +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::setFlightDuration(int seconds) +{ + _flightDuration = seconds; + if(_flightDuration < 30) { + _flightDuration = 30; + } + emit flightDurationChanged(); + qCDebug(AirMapManagerLog) << "Set time duration" << _flightDuration; +} + +//----------------------------------------------------------------------------- +QDateTime +AirMapFlightPlanManager::flightStartTime() const +{ + return _flightStartTime; +} + +//----------------------------------------------------------------------------- +int +AirMapFlightPlanManager::flightDuration() const +{ + return _flightDuration; +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::startFlightPlanning(PlanMasterController *planController) +{ + if (!_shared.client()) { + qCDebug(AirMapManagerLog) << "No AirMap client instance. Will not create a flight"; + return; + } + + if (_state != State::Idle) { + qCWarning(AirMapManagerLog) << "AirMapFlightPlanManager::startFlightPlanning: State not idle"; + return; + } + + //-- TODO: Check if there is an ongoing flight plan and do something about it (Delete it?) + + /* + * if(!flightPlanID().isEmpty()) { + * do something; + * } + */ + + if(!_planController) { + _planController = planController; + //-- Get notified of mission changes + connect(planController->missionController(), &MissionController::missionBoundingCubeChanged, this, &AirMapFlightPlanManager::_missionChanged); + } + //-- Set initial flight start time + setFlightStartTime(QDateTime::currentDateTime().addSecs(5 * 60)); +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::submitFlightPlan() +{ + if(flightPlanID().isEmpty()) { + qCWarning(AirMapManagerLog) << "Submit flight with no flight plan."; + return; + } + _flightId.clear(); + emit flightIDChanged(_flightId); + _state = State::FlightSubmit; + FlightPlans::Submit::Parameters params; + params.authorization = _shared.loginToken().toStdString(); + params.id = flightPlanID().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) { + _flightPlan = result.value(); + _flightId = QString::fromStdString(_flightPlan.flight_id.get()); + _state = State::Idle; + _pollBriefing(); + emit flightIDChanged(_flightId); + } 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; + _flightPermitStatus = AirspaceFlightPlanProvider::PermitRejected; + emit flightPermitStatusChanged(); + } + }); +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::updateFlightPlan() +{ + //-- Are we enabled? + if(!qgcApp()->toolbox()->settingsManager()->airMapSettings()->enableAirMap()->rawValue().toBool()) { + return; + } + //-- Do we have a license? + if(!_shared.hasAPIKey()) { + return; + } + _flightPermitStatus = AirspaceFlightPlanProvider::PermitPending; + emit flightPermitStatusChanged(); + _updateFlightPlan(true); +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::endFlight(QString flightID) +{ + qCDebug(AirMapManagerLog) << "End flight"; + _flightToEnd = flightID; + if (_shared.pilotID().isEmpty()) { + //-- Need to get the pilot id + 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) { + QString pilotID = QString::fromStdString(result.value().id); + _shared.setPilotID(pilotID); + qCDebug(AirMapManagerLog) << "Got Pilot ID:" << pilotID; + _state = State::Idle; + _endFlight(); + } else { + _state = State::Idle; + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Failed to get pilot ID", QString::fromStdString(result.error().message()), description); + return; + } + }); + }); + } else { + _endFlight(); + } +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::_endFlight() +{ + if(_flightToEnd.isEmpty()) { + qCDebug(AirMapManagerLog) << "End non existing flight"; + return; + } + qCDebug(AirMapManagerLog) << "End Flight. State:" << static_cast(_state); + if(_state != State::Idle) { + QTimer::singleShot(100, this, &AirMapFlightPlanManager::_endFlight); + return; + } + qCDebug(AirMapManagerLog) << "Ending flight:" << _flightToEnd; + _state = State::FlightEnd; + std::weak_ptr isAlive(_instance); + Flights::EndFlight::Parameters params; + params.authorization = _shared.loginToken().toStdString(); + params.id = _flightToEnd.toStdString(); + //-- End flight + _shared.client()->flights().end_flight(params, [this, isAlive](const Flights::EndFlight::Result& result) { + if (!isAlive.lock()) return; + if (_state != State::FlightEnd) return; + if (result) { + qCDebug(AirMapManagerLog) << "Flight Ended"; + int idx = _flightList.findFlightID(_flightToEnd); + if(idx >= 0) { + AirMapFlightInfo* pInfo = qobject_cast(_flightList.get(idx)); + if(pInfo) { + pInfo->setEndFlight(result.value().end_time); + } + } + } else { + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("End flight failed", QString::fromStdString(result.error().message()), description); + } + _flightToEnd.clear(); + _state = State::Idle; + }); +} + +//----------------------------------------------------------------------------- +bool +AirMapFlightPlanManager::_collectFlightDtata() +{ + if(!_planController || !_planController->missionController()) { + return false; + } + //-- Get flight bounding cube and prepare (box) polygon + QGCGeoBoundingCube bc = *_planController->missionController()->travelBoundingCube(); + if(!bc.isValid() || (fabs(bc.area()) < 0.0001)) { + //-- TODO: If single point, we need to set a point and a radius instead + qCDebug(AirMapManagerLog) << "Not enough points for a flight plan."; + return false; + } + _flight.maxAltitude = static_cast(fmax(bc.pointNW.altitude(), bc.pointSE.altitude())); + _flight.takeoffCoord = _planController->missionController()->takeoffCoordinate(); + _flight.coords = bc.polygon2D(); + _flight.bc = bc; + emit missionAreaChanged(); + return true; +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::_createFlightPlan() +{ + _flight.reset(); + + //-- Get flight data + if(!_collectFlightDtata()) { + return; + } + + qCDebug(AirMapManagerLog) << "About to create flight plan"; + qCDebug(AirMapManagerLog) << "Takeoff: " << _flight.takeoffCoord; + qCDebug(AirMapManagerLog) << "Bounding box:" << _flight.bc.pointNW << _flight.bc.pointSE; + qCDebug(AirMapManagerLog) << "Flight Start:" << flightStartTime().toString(); + qCDebug(AirMapManagerLog) << "Flight Duration: " << flightDuration(); + + if (_shared.pilotID().isEmpty() && !_shared.settings().userName.isEmpty() && !_shared.settings().password.isEmpty()) { + //-- Need to get the pilot id before uploading the flight plan + 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) { + QString pilotID = QString::fromStdString(result.value().id); + _shared.setPilotID(pilotID); + qCDebug(AirMapManagerLog) << "Got Pilot ID:" << pilotID; + _state = State::Idle; + _uploadFlightPlan(); + } else { + _flightPermitStatus = AirspaceFlightPlanProvider::PermitNone; + 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); + return; + } + }); + }); + } else { + _uploadFlightPlan(); + } + + _flightPermitStatus = AirspaceFlightPlanProvider::PermitPending; + emit flightPermitStatusChanged(); +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::_updateRulesAndFeatures(std::vector& rulesets, std::unordered_map& features, bool updateFeatures) +{ + AirMapRulesetsManager* pRulesMgr = dynamic_cast(qgcApp()->toolbox()->airspaceManager()->ruleSets()); + if(pRulesMgr) { + for(int rs = 0; rs < pRulesMgr->ruleSets()->count(); rs++) { + AirMapRuleSet* ruleSet = qobject_cast(pRulesMgr->ruleSets()->get(rs)); + //-- If this ruleset is selected + if(ruleSet && ruleSet->selected()) { + rulesets.push_back(ruleSet->id().toStdString()); + //-- Features within each rule (only when updating) + if(updateFeatures) { + for(int r = 0; r < ruleSet->rules()->count(); r++) { + AirMapRule* rule = qobject_cast(ruleSet->rules()->get(r)); + if(rule) { + for(int f = 0; f < rule->features()->count(); f++) { + AirMapRuleFeature* feature = qobject_cast(rule->features()->get(f)); + if(features.find(feature->name().toStdString()) != features.end()) { + qCDebug(AirMapManagerLog) << "Removing duplicate:" << feature->name(); + continue; + } + if(feature && feature->value().isValid()) { + switch(feature->type()) { + case AirspaceRuleFeature::Boolean: + if(feature->value().toInt() == 0 || feature->value().toInt() == 1) { + features[feature->name().toStdString()] = RuleSet::Feature::Value(feature->value().toBool()); + } else { + //-- If not set, default to false + features[feature->name().toStdString()] = RuleSet::Feature::Value(false); + } + break; + case AirspaceRuleFeature::Float: + //-- Sanity check for floats + if(std::isfinite(feature->value().toFloat())) { + features[feature->name().toStdString()] = RuleSet::Feature::Value(feature->value().toDouble()); + } + break; + case AirspaceRuleFeature::String: + //-- Skip empty responses + if(!feature->value().toString().isEmpty()) { + features[feature->name().toStdString()] = RuleSet::Feature::Value(feature->value().toString().toStdString()); + } + break; + default: + qCWarning(AirMapManagerLog) << "Unknown type for feature" << feature->name(); + } + } + } + } + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::_updateFlightStartEndTime(DateTime& start_time, DateTime& end_time) +{ + if(_flightStartsNow || _flightStartTime < QDateTime::currentDateTime()) { + setFlightStartTime(QDateTime::currentDateTime().addSecs(1)); + } + quint64 startt = static_cast(_flightStartTime.toUTC().toMSecsSinceEpoch()); + start_time = airmap::from_milliseconds_since_epoch(airmap::milliseconds(static_cast(startt))); + quint64 endt = startt + (static_cast(_flightDuration) * 1000); + end_time = airmap::from_milliseconds_since_epoch(airmap::milliseconds(static_cast(endt))); +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::_uploadFlightPlan() +{ + qCDebug(AirMapManagerLog) << "Uploading flight plan. State:" << static_cast(_state); + if(_state != State::Idle) { + QTimer::singleShot(100, this, &AirMapFlightPlanManager::_uploadFlightPlan); + return; + } + //-- Reset "relevant" features + _importantFeatures.clear(); + _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.min_altitude = 0.0; + params.buffer = 10.f; + params.latitude = static_cast(_flight.takeoffCoord.latitude()); + params.longitude = static_cast(_flight.takeoffCoord.longitude()); + params.pilot.id = _shared.pilotID().toStdString(); + //-- Handle flight start/end + _updateFlightStartEndTime(params.start_time, params.end_time); + //-- Rules & Features + _updateRulesAndFeatures(params.rulesets, params.features); + //-- Geometry: polygon + Geometry::Polygon polygon; + for (const auto& qcoord : _flight.coords) { + Geometry::Coordinate coord; + coord.latitude = qcoord.latitude(); + coord.longitude = qcoord.longitude(); + polygon.outer_ring.coordinates.push_back(coord); + } + params.geometry = Geometry(polygon); + params.authorization = login_token.toStdString(); + //-- Create flight plan + _shared.client()->flight_plans().create_by_polygon(params, [this, isAlive](const FlightPlans::Create::Result& result) { + if (!isAlive.lock()) return; + if (_state != State::FlightUpload) return; + _state = State::Idle; + if (result) { + _flightPlan = result.value(); + qCDebug(AirMapManagerLog) << "Flight plan created:" << flightPlanID(); + _pollBriefing(); + } 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 +AirMapFlightPlanManager::_updateFlightPlanOnTimer() +{ + _updateFlightPlan(false); +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::_updateFlightPlan(bool interactive) +{ + qCDebug(AirMapManagerLog) << "Updating flight plan. State:" << static_cast(_state); + + if(_state != State::Idle) { + QTimer::singleShot(250, this, &AirMapFlightPlanManager::_updateFlightPlanOnTimer); + return; + } + //-- Get flight data + if(!_collectFlightDtata()) { + return; + } + + //-- Update local instance of the flight plan + _flightPlan.altitude_agl.max = _flight.maxAltitude; + _flightPlan.altitude_agl.min = 0.0f; + _flightPlan.buffer = 2.f; + _flightPlan.takeoff.latitude = static_cast(_flight.takeoffCoord.latitude()); + _flightPlan.takeoff.longitude = static_cast(_flight.takeoffCoord.longitude()); + //-- Rules & Features + _flightPlan.rulesets.clear(); + _flightPlan.features.clear(); + //-- If interactive, we collect features otherwise we don't + _updateRulesAndFeatures(_flightPlan.rulesets, _flightPlan.features, interactive); + //-- Handle flight start/end + _updateFlightStartEndTime(_flightPlan.start_time, _flightPlan.end_time); + //-- Geometry: polygon + Geometry::Polygon polygon; + for (const auto& qcoord : _flight.coords) { + Geometry::Coordinate coord; + coord.latitude = qcoord.latitude(); + coord.longitude = qcoord.longitude(); + polygon.outer_ring.coordinates.push_back(coord); + } + _flightPlan.geometry = Geometry(polygon); + + qCDebug(AirMapManagerLog) << "Takeoff: " << _flight.takeoffCoord; + qCDebug(AirMapManagerLog) << "Bounding box: " << _flight.bc.pointNW << _flight.bc.pointSE; + qCDebug(AirMapManagerLog) << "Flight Start: " << flightStartTime().toString(); + qCDebug(AirMapManagerLog) << "Flight Duration:" << flightDuration(); + + _state = State::FlightUpdate; + std::weak_ptr isAlive(_instance); + _shared.doRequestWithLogin([this, isAlive](const QString& login_token) { + if (!isAlive.lock()) return; + if (_state != State::FlightUpdate) return; + FlightPlans::Update::Parameters params = {}; + params.authorization = login_token.toStdString(); + params.flight_plan = _flightPlan; + //-- Update flight plan + _shared.client()->flight_plans().update(params, [this, isAlive](const FlightPlans::Update::Result& result) { + if (!isAlive.lock()) return; + if (_state != State::FlightUpdate) return; + _state = State::Idle; + if (result) { + qCDebug(AirMapManagerLog) << "Flight plan updated:" << flightPlanID(); + _pollBriefing(); + } else { + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Flight Plan update failed", QString::fromStdString(result.error().message()), description); + } + }); + }); +} + +//----------------------------------------------------------------------------- +static bool +adv_sort(QObject* a, QObject* b) +{ + AirMapAdvisory* aa = qobject_cast(a); + AirMapAdvisory* bb = qobject_cast(b); + if(!aa || !bb) return false; + return static_cast(aa->color()) > static_cast(bb->color()); +} + +//----------------------------------------------------------------------------- +static bool +rules_sort(QObject* a, QObject* b) +{ + AirMapRule* aa = qobject_cast(a); + AirMapRule* bb = qobject_cast(b); + if(!aa || !bb) return false; + return static_cast(aa->status()) > static_cast(bb->status()); +} + +//----------------------------------------------------------------------------- +bool +AirMapFlightPlanManager::_findBriefFeature(const QString& name) +{ + for(int i = 0; i < _briefFeatures.count(); i++ ) { + AirMapRuleFeature* feature = qobject_cast(_briefFeatures.get(i)); + if (feature && feature->name() == name) { + return true; + } + } + return false; +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::_pollBriefing() +{ + qCDebug(AirMapManagerLog) << "Poll Briefing. State:" << static_cast(_state); + if(_state != State::Idle) { + QTimer::singleShot(100, this, &AirMapFlightPlanManager::_pollBriefing); + return; + } + _state = State::FlightPolling; + FlightPlans::RenderBriefing::Parameters params; + params.authorization = _shared.loginToken().toStdString(); + params.id = flightPlanID().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"; + //-- Collect advisories + _valid = false; + _advisories.clearAndDeleteContents(); + const std::vector advisories = briefing.airspace.advisories; + _airspaceColor = static_cast(briefing.airspace.color); + for (const auto& advisory : advisories) { + AirMapAdvisory* pAdvisory = new AirMapAdvisory(this); + pAdvisory->_id = QString::fromStdString(advisory.airspace.id()); + pAdvisory->_name = QString::fromStdString(advisory.airspace.name()); + pAdvisory->_type = static_cast(advisory.airspace.type()); + pAdvisory->_color = static_cast(advisory.color); + _advisories.append(pAdvisory); + qCDebug(AirMapManagerLog) << "Adding briefing advisory" << pAdvisory->name(); + } + //-- Sort in order of color (priority) + _advisories.beginReset(); + std::sort(_advisories.objectList()->begin(), _advisories.objectList()->end(), adv_sort); + _advisories.endReset(); + _valid = true; + //-- Collect Rulesets + _authorizations.clearAndDeleteContents(); + _rulesViolation.clearAndDeleteContents(); + _rulesInfo.clearAndDeleteContents(); + _rulesReview.clearAndDeleteContents(); + _rulesFollowing.clearAndDeleteContents(); + _briefFeatures.clear(); + for(const auto& ruleset : briefing.evaluation.rulesets) { + AirMapRuleSet* pRuleSet = new AirMapRuleSet(this); + pRuleSet->_id = QString::fromStdString(ruleset.id); + //-- Iterate Rules + for (const auto& rule : ruleset.rules) { + AirMapRule* pRule = new AirMapRule(rule, this); + //-- Iterate Rule Features + for (const auto& feature : rule.features) { + AirMapRuleFeature* pFeature = new AirMapRuleFeature(feature, this); + pRule->_features.append(pFeature); + if(rule.status == RuleSet::Rule::Status::missing_info) { + if(!_findBriefFeature(pFeature->name())) { + _briefFeatures.append(pFeature); + _importantFeatures.append(pFeature); + qCDebug(AirMapManagerLog) << "Adding briefing feature" << pFeature->name() << pFeature->description() << pFeature->type(); + } else { + qCDebug(AirMapManagerLog) << "Skipping briefing feature duplicate" << pFeature->name() << pFeature->description() << pFeature->type(); + } + } + } + //-- When a flight is first created, we send no features. That means that all "missing_info" are "relevant" features. + // We keep a list of them so they will be always shown to the user even when they are no longer "missing_info" + for(const auto& feature : _importantFeatures) { + if(!_findBriefFeature(feature->name())) { + _briefFeatures.append(feature); + } + } + pRuleSet->_rules.append(pRule); + //-- Rules separated by status for presentation + switch(rule.status) { + case RuleSet::Rule::Status::conflicting: + _rulesViolation.append(new AirMapRule(rule, this)); + break; + case RuleSet::Rule::Status::not_conflicting: + _rulesFollowing.append(new AirMapRule(rule, this)); + break; + case RuleSet::Rule::Status::missing_info: + _rulesInfo.append(new AirMapRule(rule, this)); + break; + case RuleSet::Rule::Status::informational: + _rulesReview.append(new AirMapRule(rule, this)); + break; + default: + break; + } + } + //-- Sort rules by relevance order + pRuleSet->_rules.beginReset(); + std::sort(pRuleSet->_rules.objectList()->begin(), pRuleSet->_rules.objectList()->end(), rules_sort); + pRuleSet->_rules.endReset(); + _rulesets.append(pRuleSet); + qCDebug(AirMapManagerLog) << "Adding briefing ruleset" << pRuleSet->id(); + } + //-- Evaluate briefing status + if (briefing.evaluation.authorizations.size() == 0) { + _flightPermitStatus = AirspaceFlightPlanProvider::PermitNotRequired; + emit flightPermitStatusChanged(); + } else { + bool rejected = false; + bool accepted = false; + bool pending = false; + for (const auto& authorization : briefing.evaluation.authorizations) { + AirMapFlightAuthorization* pAuth = new AirMapFlightAuthorization(authorization, this); + _authorizations.append(pAuth); + qCDebug(AirMapManagerLog) << "Autorization:" << pAuth->name() << " (" << pAuth->message() << ")" << static_cast(pAuth->status()); + 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; + } + } + qCDebug(AirMapManagerLog) << "Flight approval: accepted=" << accepted << "rejected" << rejected << "pending" << pending; + if ((rejected || accepted) && !pending) { + if (rejected) { // rejected has priority + _flightPermitStatus = AirspaceFlightPlanProvider::PermitRejected; + } else { + _flightPermitStatus = AirspaceFlightPlanProvider::PermitAccepted; + } + emit flightPermitStatusChanged(); + } else { + //-- Pending. Try again. + _pollTimer.setSingleShot(true); + _pollTimer.start(1000); + } + } + emit advisoryChanged(); + emit rulesChanged(); + _state = State::Idle; + } else { + _state = State::Idle; + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Brief Request failed", + QString::fromStdString(result.error().message()), description); + } + }); +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::_missionChanged() +{ + //-- Are we enabled? + if(!qgcApp()->toolbox()->settingsManager()->airMapSettings()->enableAirMap()->rawValue().toBool()) { + return; + } + //-- Do we have a license? + if(!_shared.hasAPIKey()) { + return; + } + //-- Creating a new flight plan? + if(_state == State::Idle) { + if(flightPlanID().isEmpty()) { + _createFlightPlan(); + } else { + //-- Plan is being modified + _updateFlightPlan(); + } + } +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::loadFlightList(QDateTime startTime, QDateTime endTime) +{ + //-- TODO: This is not checking if the state is Idle. Again, these need to + // queued up and handled by a worker thread. + qCDebug(AirMapManagerLog) << "Preparing load flight list"; + _loadingFlightList = true; + emit loadingFlightListChanged(); + _rangeStart = startTime; + _rangeEnd = endTime; + qCDebug(AirMapManagerLog) << "List flights from:" << _rangeStart.toString("yyyy MM dd - hh:mm:ss") << "to" << _rangeEnd.toString("yyyy MM dd - hh:mm:ss"); + if (_shared.pilotID().isEmpty()) { + //-- Need to get the pilot id + 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) { + QString pilotID = QString::fromStdString(result.value().id); + _shared.setPilotID(pilotID); + qCDebug(AirMapManagerLog) << "Got Pilot ID:" << pilotID; + _state = State::Idle; + _loadFlightList(); + } else { + _state = State::Idle; + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Failed to get pilot ID", QString::fromStdString(result.error().message()), description); + _loadingFlightList = false; + emit loadingFlightListChanged(); + return; + } + }); + }); + } else { + _loadFlightList(); + } +} + +//----------------------------------------------------------------------------- +void +AirMapFlightPlanManager::_loadFlightList() +{ + qCDebug(AirMapManagerLog) << "Load flight list. State:" << static_cast(_state); + if(_state != State::Idle) { + QTimer::singleShot(100, this, &AirMapFlightPlanManager::_loadFlightList); + return; + } + _flightList.clear(); + emit flightListChanged(); + _state = State::LoadFlightList; + std::weak_ptr isAlive(_instance); + _shared.doRequestWithLogin([this, isAlive](const QString& login_token) { + if (!isAlive.lock()) return; + if (_state != State::LoadFlightList) return; + Flights::Search::Parameters params; + params.authorization = login_token.toStdString(); + quint64 start = static_cast(_rangeStart.toUTC().toMSecsSinceEpoch()); + quint64 end = static_cast(_rangeEnd.toUTC().toMSecsSinceEpoch()); + params.start_after = airmap::from_milliseconds_since_epoch(airmap::milliseconds(static_cast(start))); + params.start_before = airmap::from_milliseconds_since_epoch(airmap::milliseconds(static_cast(end))); + params.limit = 250; + params.pilot_id = _shared.pilotID().toStdString(); + _shared.client()->flights().search(params, [this, isAlive](const Flights::Search::Result& result) { + if (!isAlive.lock()) return; + if (_state != State::LoadFlightList) return; + if (result && result.value().flights.size() > 0) { + const Flights::Search::Response& response = result.value(); + for (const auto& flight : response.flights) { + AirMapFlightInfo* pFlight = new AirMapFlightInfo(flight, this); + _flightList.append(pFlight); + qCDebug(AirMapManagerLog) << "Found:" << pFlight->flightID() << pFlight->flightPlanID() << pFlight->endTime(); + } + _flightList.sortStartFlight(); + emit flightListChanged(); + } else { + if(!result) { + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Flight search failed", QString::fromStdString(result.error().message()), description); + } + } + _state = State::Idle; + _loadingFlightList = false; + emit loadingFlightListChanged(); + }); + }); +} + diff --git a/src/Airmap/AirMapFlightPlanManager.h b/src/Airmap/AirMapFlightPlanManager.h new file mode 100644 index 0000000000000000000000000000000000000000..51a35b9b96404b80d6eb81064279b766ca7c55af --- /dev/null +++ b/src/Airmap/AirMapFlightPlanManager.h @@ -0,0 +1,181 @@ +/**************************************************************************** + * + * (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 "LifetimeChecker.h" +#include "AirMapSharedState.h" +#include "AirspaceFlightPlanProvider.h" + +#include +#include +#include +#include + +#include "airmap/flight.h" +#include "airmap/flight_plan.h" +#include "airmap/ruleset.h" + +class AirMapRuleFeature; +class PlanMasterController; + +//----------------------------------------------------------------------------- +class AirMapFlightAuthorization : public AirspaceFlightAuthorization +{ + Q_OBJECT +public: + AirMapFlightAuthorization (const airmap::Evaluation::Authorization auth, QObject *parent = nullptr); + + AirspaceFlightAuthorization::AuthorizationStatus + status () override; + QString name () override { return QString::fromStdString(_auth.authority.name); } + QString id () override { return QString::fromStdString(_auth.authority.id); } + QString message () override { return QString::fromStdString(_auth.message); } +private: + airmap::Evaluation::Authorization _auth; +}; + +//----------------------------------------------------------------------------- +class AirMapFlightInfo : public AirspaceFlightInfo +{ + Q_OBJECT +public: + AirMapFlightInfo (const airmap::Flight& flight, QObject *parent = nullptr); + QString flightID () override { return QString::fromStdString(_flight.id); } + QString flightPlanID () override { return _flight.flight_plan_id ? QString::fromStdString(_flight.flight_plan_id.get()) : QString(); } + QString createdTime () override; + QString startTime () override; + QString endTime () override; + QDateTime qStartTime () override; + QGeoCoordinate takeOff () override { return QGeoCoordinate(static_cast(_flight.latitude), static_cast(_flight.longitude));} + QVariantList boundingBox () override { return _boundingBox; } + bool active () override; + void setEndFlight (airmap::DateTime end); +private: + airmap::Flight _flight; + QVariantList _boundingBox; +}; + +//----------------------------------------------------------------------------- +/// class to upload a flight +class AirMapFlightPlanManager : public AirspaceFlightPlanProvider, public LifetimeChecker +{ + Q_OBJECT +public: + AirMapFlightPlanManager (AirMapSharedState& shared, QObject *parent = nullptr); + ~AirMapFlightPlanManager () override; + + PermitStatus flightPermitStatus () const override { return _flightPermitStatus; } + QDateTime flightStartTime () const override; + int flightDuration () const override; + bool flightStartsNow () const override { return _flightStartsNow; } + bool valid () override { return _valid; } + QmlObjectListModel* advisories () override { return &_advisories; } + QmlObjectListModel* ruleSets () override { return &_rulesets; } + QGCGeoBoundingCube* missionArea () override { return &_flight.bc; } + + AirspaceAdvisoryProvider::AdvisoryColor + airspaceColor () override { return _airspaceColor; } + + QmlObjectListModel* rulesViolation () override { return &_rulesViolation; } + QmlObjectListModel* rulesInfo () override { return &_rulesInfo; } + QmlObjectListModel* rulesReview () override { return &_rulesReview; } + QmlObjectListModel* rulesFollowing () override { return &_rulesFollowing; } + QmlObjectListModel* briefFeatures () override { return &_briefFeatures; } + QmlObjectListModel* authorizations () override { return &_authorizations; } + AirspaceFlightModel*flightList () override { return &_flightList; } + bool loadingFlightList () override { return _loadingFlightList; } + QString flightPlanID () {return QString::fromStdString(_flightPlan.id); } + QString flightID () {return _flightId; } + + void updateFlightPlan () override; + void submitFlightPlan () override; + void startFlightPlanning (PlanMasterController* planController) override; + void setFlightStartTime (QDateTime start) override; + void setFlightDuration (int seconds) override; + void loadFlightList (QDateTime startTime, QDateTime endTime) override; + void setFlightStartsNow (bool now) override; + void endFlight (QString flightID) override; + +signals: + void error (const QString& what, const QString& airmapdMessage, const QString& airmapdDetails); + void flightIDChanged (QString flightID); + +private slots: + void _pollBriefing (); + void _missionChanged (); + void _endFlight (); + void _uploadFlightPlan (); + void _updateFlightPlanOnTimer (); + void _loadFlightList (); + +private: + void _createFlightPlan (); + bool _collectFlightDtata (); + void _updateFlightPlan (bool interactive = false); + bool _findBriefFeature (const QString& name); + void _updateFlightStartEndTime (airmap::DateTime& start_time, airmap::DateTime& end_time); + void _updateRulesAndFeatures (std::vector& rulesets, std::unordered_map& features, bool updateFeatures = false); + +private: + enum class State { + Idle, + GetPilotID, + FlightUpload, + FlightUpdate, + FlightEnd, + FlightSubmit, + FlightPolling, + LoadFlightList, + }; + + struct Flight { + QGCGeoBoundingCube bc; + QList coords; + QGeoCoordinate takeoffCoord; + float maxAltitude = 0; + void reset() { + bc.reset(); + coords.clear(); + maxAltitude = 0; + } + }; + + Flight _flight; ///< flight pending to be uploaded + State _state = State::Idle; + AirMapSharedState& _shared; + QTimer _pollTimer; ///< timer to poll for approval check + QString _flightId; ///< Current flight ID, not necessarily accepted yet + QString _flightToEnd; + PlanMasterController* _planController = nullptr; + bool _valid = false; + bool _loadingFlightList = false; + bool _flightStartsNow = false; + QmlObjectListModel _advisories; + QmlObjectListModel _rulesets; + QmlObjectListModel _rulesViolation; + QmlObjectListModel _rulesInfo; + QmlObjectListModel _rulesReview; + QmlObjectListModel _rulesFollowing; + QmlObjectListModel _briefFeatures; + QmlObjectListModel _authorizations; + AirspaceFlightModel _flightList; + QDateTime _rangeStart; + QDateTime _rangeEnd; + airmap::FlightPlan _flightPlan; + QDateTime _flightStartTime; + int _flightDuration = 15 * 60; + + QList _importantFeatures; + + AirspaceAdvisoryProvider::AdvisoryColor _airspaceColor; + AirspaceFlightPlanProvider::PermitStatus _flightPermitStatus = AirspaceFlightPlanProvider::PermitNone; + +}; + diff --git a/src/Airmap/AirMapManager.cc b/src/Airmap/AirMapManager.cc new file mode 100644 index 0000000000000000000000000000000000000000..053665fb49487e941c06e984d0930e4f1a1daab2 --- /dev/null +++ b/src/Airmap/AirMapManager.cc @@ -0,0 +1,257 @@ +/**************************************************************************** + * + * (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 "AirMapAdvisoryManager.h" +#include "AirMapFlightPlanManager.h" +#include "AirMapManager.h" +#include "AirMapRestrictionManager.h" +#include "AirMapRulesetsManager.h" +#include "AirMapSettings.h" +#include "AirMapTelemetry.h" +#include "AirMapTrafficMonitor.h" +#include "AirMapVehicleManager.h" +#include "AirMapWeatherInfoManager.h" + +#include "QmlObjectListModel.h" +#include "JsonHelper.h" +#include "SettingsManager.h" +#include "AppSettings.h" +#include "QGCQGeoCoordinate.h" +#include "QGCApplication.h" + +#include + +//-- Hardwired API key +#if defined(QGC_AIRMAP_KEY_AVAILABLE) +#include "Airmap_api_key.h" +#endif + +using namespace airmap; + +QGC_LOGGING_CATEGORY(AirMapManagerLog, "AirMapManagerLog") + +//----------------------------------------------------------------------------- +AirMapManager::AirMapManager(QGCApplication* app, QGCToolbox* toolbox) + : AirspaceManager(app, toolbox) + , _authStatus(Unknown) +{ + _logger = std::make_shared(); + qt::register_types(); // TODO: still needed? + _logger->logging_category().setEnabled(QtDebugMsg, false); + _logger->logging_category().setEnabled(QtInfoMsg, false); + _logger->logging_category().setEnabled(QtWarningMsg, false); + _dispatchingLogger = std::make_shared(_logger); + connect(&_shared, &AirMapSharedState::error, this, &AirMapManager::_error); + connect(&_shared, &AirMapSharedState::authStatus, this, &AirMapManager::_authStatusChanged); +} + +//----------------------------------------------------------------------------- +AirMapManager::~AirMapManager() +{ + if (_shared.client()) { + delete _shared.client(); + } +} + +//----------------------------------------------------------------------------- +void +AirMapManager::setToolbox(QGCToolbox* toolbox) +{ + _settingsTimer.setSingleShot(true); + AirspaceManager::setToolbox(toolbox); + AirMapSettings* ap = toolbox->settingsManager()->airMapSettings(); + connect(ap->enableAirMap(), &Fact::rawValueChanged, this, &AirMapManager::_settingsChanged); + connect(ap->usePersonalApiKey(),&Fact::rawValueChanged, this, &AirMapManager::_settingsChanged); + 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); + connect(ap->enableAirspace(), &Fact::rawValueChanged, this, &AirMapManager::_airspaceEnabled); + connect(&_settingsTimer, &QTimer::timeout, this, &AirMapManager::_settingsTimeout); + _settingsTimeout(); +} + +//----------------------------------------------------------------------------- +bool +AirMapManager::connected() const +{ + return _shared.client() != nullptr; +} + +//----------------------------------------------------------------------------- +void +AirMapManager::_error(const QString& what, const QString& airmapdMessage, const QString& airmapdDetails) +{ + qCDebug(AirMapManagerLog) << "Error: "<showMessage(QString("Error: %1. %2").arg(what).arg(airmapdMessage)); +} + +//----------------------------------------------------------------------------- +void +AirMapManager::_authStatusChanged(AirspaceManager::AuthStatus status) +{ + _authStatus = status; + emit authStatusChanged(); +} + +//----------------------------------------------------------------------------- +void +AirMapManager::_settingsChanged() +{ + _settingsTimer.start(1000); +} + +//----------------------------------------------------------------------------- +void +AirMapManager::_airspaceEnabled() +{ + if(qgcApp()->toolbox()->settingsManager()->airMapSettings()->enableAirspace()->rawValue().toBool()) { + if(_airspaces) { + _airspaces->setROI(_roi, true); + } + } +} + +//----------------------------------------------------------------------------- +void +AirMapManager::_settingsTimeout() +{ + qCDebug(AirMapManagerLog) << "AirMap settings changed"; + _connectStatus.clear(); + emit connectStatusChanged(); + AirMapSettings* ap = _toolbox->settingsManager()->airMapSettings(); + //-- If we are disabled, there is nothing else to do. + if (!ap->enableAirMap()->rawValue().toBool()) { + _shared.logout(); + if(_shared.client()) { + delete _shared.client(); + _shared.setClient(nullptr); + emit connectedChanged(); + } + return; + } + AirMapSharedState::Settings settings; + if(ap->usePersonalApiKey()->rawValue().toBool()) { + settings.apiKey = ap->apiKey()->rawValueString(); + settings.clientID = ap->clientID()->rawValueString(); + } + //-- If we have a hardwired key (and no custom key is present), set it. +#if defined(QGC_AIRMAP_KEY_AVAILABLE) + if(!ap->usePersonalApiKey()->rawValue().toBool()) { + settings.apiKey = AirmapAPIKey(); + settings.clientID = AirmapClientID(); + } + bool authChanged = settings.apiKey != _shared.settings().apiKey || settings.apiKey.isEmpty(); +#else + bool authChanged = settings.apiKey != _shared.settings().apiKey; +#endif + settings.userName = ap->userName()->rawValueString(); + settings.password = ap->password()->rawValueString(); + if(settings.userName != _shared.settings().userName || settings.password != _shared.settings().password) { + authChanged = true; + } + _shared.setSettings(settings); + //-- Need to re-create the client if the API key or user name/password changed + if ((_shared.client() && authChanged) || !ap->enableAirMap()->rawValue().toBool()) { + delete _shared.client(); + _shared.setClient(nullptr); + emit connectedChanged(); + } + if (!_shared.client() && settings.apiKey != "") { + qCDebug(AirMapManagerLog) << "Creating AirMap client"; + auto credentials = Credentials{}; + credentials.api_key = _shared.settings().apiKey.toStdString(); + auto configuration = Client::default_production_configuration(credentials); + configuration.telemetry.host = "udp.telemetry.k8s.airmap.io"; + configuration.telemetry.port = 7070; + qt::Client::create(configuration, _dispatchingLogger, this, [this](const qt::Client::CreateResult& result) { + if (result) { + qCDebug(AirMapManagerLog) << "Successfully created airmap::qt::Client instance"; + _shared.setClient(result.value()); + emit connectedChanged(); + _connectStatus = tr("AirMap Enabled"); + emit connectStatusChanged(); + //-- Now test authentication + _shared.login(); + } else { + qWarning("Failed to create airmap::qt::Client instance"); + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + QString error = QString::fromStdString(result.error().message()); + _error(tr("Failed to create airmap::qt::Client instance"), + error, description); + _connectStatus = error; + if(!description.isEmpty()) { + _connectStatus += "\n"; + _connectStatus += description; + } + emit connectStatusChanged(); + } + }); + } else { + if(settings.apiKey == "") { + _connectStatus = tr("No API key for AirMap"); + emit connectStatusChanged(); + qCDebug(AirMapManagerLog) << _connectStatus; + } + } +} + +//----------------------------------------------------------------------------- +AirspaceVehicleManager* +AirMapManager::instantiateVehicle(const Vehicle& vehicle) +{ + AirMapVehicleManager* manager = new AirMapVehicleManager(_shared, vehicle); + connect(manager, &AirMapVehicleManager::error, this, &AirMapManager::_error); + return manager; +} + +//----------------------------------------------------------------------------- +AirspaceRulesetsProvider* +AirMapManager::_instantiateRulesetsProvider() +{ + AirMapRulesetsManager* rulesetsManager = new AirMapRulesetsManager(_shared); + connect(rulesetsManager, &AirMapRulesetsManager::error, this, &AirMapManager::_error); + return rulesetsManager; +} + +//----------------------------------------------------------------------------- +AirspaceWeatherInfoProvider* +AirMapManager::_instatiateAirspaceWeatherInfoProvider() +{ + AirMapWeatherInfoManager* weatherInfo = new AirMapWeatherInfoManager(_shared); + connect(weatherInfo, &AirMapWeatherInfoManager::error, this, &AirMapManager::_error); + return weatherInfo; +} + +//----------------------------------------------------------------------------- +AirspaceAdvisoryProvider* +AirMapManager::_instatiateAirspaceAdvisoryProvider() +{ + AirMapAdvisoryManager* advisories = new AirMapAdvisoryManager(_shared); + connect(advisories, &AirMapAdvisoryManager::error, this, &AirMapManager::_error); + return advisories; +} + +//----------------------------------------------------------------------------- +AirspaceRestrictionProvider* +AirMapManager::_instantiateAirspaceRestrictionProvider() +{ + AirMapRestrictionManager* airspaces = new AirMapRestrictionManager(_shared); + connect(airspaces, &AirMapRestrictionManager::error, this, &AirMapManager::_error); + return airspaces; +} + +//----------------------------------------------------------------------------- +AirspaceFlightPlanProvider* +AirMapManager::_instantiateAirspaceFlightPlanProvider() +{ + AirMapFlightPlanManager* flightPlan = new AirMapFlightPlanManager(_shared); + connect(flightPlan, &AirMapFlightPlanManager::error, this, &AirMapManager::_error); + return flightPlan; +} diff --git a/src/Airmap/AirMapManager.h b/src/Airmap/AirMapManager.h new file mode 100644 index 0000000000000000000000000000000000000000..4bf16b8c90afa929bca9605faa8adb8a9521aaaf --- /dev/null +++ b/src/Airmap/AirMapManager.h @@ -0,0 +1,71 @@ +/**************************************************************************** + * + * (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 "AirMapSharedState.h" +#include "AirspaceManager.h" +#include "QGCLoggingCategory.h" + +#include +#include + +#include + +#include + +class QGCToolbox; + +Q_DECLARE_LOGGING_CATEGORY(AirMapManagerLog) + +//----------------------------------------------------------------------------- +/** + * @class AirMapManager + * AirMap implementation of AirspaceManager + */ + +class AirMapManager : public AirspaceManager +{ + Q_OBJECT +public: + AirMapManager(QGCApplication* app, QGCToolbox* toolbox); + virtual ~AirMapManager() override; + + void setToolbox (QGCToolbox* toolbox) override; + + QString providerName () const override { return QString("AirMap"); } + AirspaceVehicleManager* instantiateVehicle (const Vehicle& vehicle) override; + bool connected () const override; + QString connectStatus () const override { return _connectStatus; } + AirspaceManager::AuthStatus authStatus () const override { return _authStatus; } + +protected: + AirspaceRulesetsProvider* _instantiateRulesetsProvider () override; + AirspaceWeatherInfoProvider* _instatiateAirspaceWeatherInfoProvider () override; + AirspaceAdvisoryProvider* _instatiateAirspaceAdvisoryProvider () override; + AirspaceRestrictionProvider* _instantiateAirspaceRestrictionProvider () override; + AirspaceFlightPlanProvider* _instantiateAirspaceFlightPlanProvider () override; + +private slots: + void _error (const QString& what, const QString& airmapdMessage, const QString& airmapdDetails); + void _settingsChanged (); + void _settingsTimeout (); + void _airspaceEnabled (); + void _authStatusChanged (AirspaceManager::AuthStatus status); + +private: + QString _connectStatus; + QTimer _settingsTimer; + AirMapSharedState _shared; + std::shared_ptr _logger; + std::shared_ptr _dispatchingLogger; + AirspaceManager::AuthStatus _authStatus; +}; + + diff --git a/src/Airmap/AirMapRestrictionManager.cc b/src/Airmap/AirMapRestrictionManager.cc new file mode 100644 index 0000000000000000000000000000000000000000..9d105934c26ecad8fe7e02df7255149f3eca24b4 --- /dev/null +++ b/src/Airmap/AirMapRestrictionManager.cc @@ -0,0 +1,234 @@ +/**************************************************************************** + * + * (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 "AirMapRestrictionManager.h" +#include "AirMapManager.h" +#include "AirspaceRestriction.h" + +#include "QGCApplication.h" +#include "SettingsManager.h" + +#define RESTRICTION_UPDATE_DISTANCE 500 //-- 500m threshold for updates + +using namespace airmap; + +//----------------------------------------------------------------------------- +AirMapRestrictionManager::AirMapRestrictionManager(AirMapSharedState& shared) + : _shared(shared) +{ +} + +//----------------------------------------------------------------------------- +void +AirMapRestrictionManager::setROI(const QGCGeoBoundingCube& roi, bool reset) +{ + if(qgcApp()->toolbox()->settingsManager()->airMapSettings()->enableAirspace()->rawValue().toBool()) { + //-- If first time or we've moved more than RESTRICTION_UPDATE_DISTANCE, ask for updates. + if(reset || + (!_lastROI.isValid() || _lastROI.pointNW.distanceTo(roi.pointNW) > RESTRICTION_UPDATE_DISTANCE || _lastROI.pointSE.distanceTo(roi.pointSE) > RESTRICTION_UPDATE_DISTANCE) || + (_polygons.count() == 0 && _circles.count() == 0)) { + //-- Limit area of interest + qCDebug(AirMapManagerLog) << "ROI Area:" << roi.area() << "km^2"; + if(roi.area() < qgcApp()->toolbox()->airspaceManager()->maxAreaOfInterest()) { + _lastROI = roi; + _requestRestrictions(roi); + } else { + _polygons.clear(); + _circles.clear(); + } + } + } +} + + +//----------------------------------------------------------------------------- +void +AirMapRestrictionManager::_getColor(const Airspace& airspace, QColor& color, QColor& lineColor, float& lineWidth) +{ + if(airspace.type() == Airspace::Type::airport) { + color = QColor(246,165,23,50); + lineColor = QColor(246,165,23,255); + lineWidth = 2.0f; + } else if(airspace.type() == Airspace::Type::controlled_airspace) { + QString classification = QString::fromStdString(airspace.details_for_controlled_airspace().airspace_classification).toUpper(); + if(classification == "B") { + color = QColor(31,160,211,25); + lineColor = QColor(31,160,211,255); + lineWidth = 1.5f; + } else if(classification == "C") { + color = QColor(155,108,157,25); + lineColor = QColor(155,108,157,255); + lineWidth = 1.5f; + } else if(classification == "D") { + color = QColor(26,116,179,25); + lineColor = QColor(26,116,179,255); + lineWidth = 1.0f; + } else if(classification == "E") { + color = QColor(155,108,157,25); + lineColor = QColor(155,108,157,255); + lineWidth = 1.0f; + } else { + //-- Don't know it + qCWarning(AirMapManagerLog) << "Unknown airspace classification:" << QString::fromStdString(airspace.details_for_controlled_airspace().airspace_classification); + color = QColor(255,230,0,25); + lineColor = QColor(255,230,0,255); + lineWidth = 0.5f; + } + } else if(airspace.type() == Airspace::Type::special_use_airspace) { + color = QColor(27,90,207,38); + lineColor = QColor(27,90,207,255); + lineWidth = 1.0f; + } else if(airspace.type() == Airspace::Type::tfr) { + color = QColor(244,67,54,38); + lineColor = QColor(244,67,54,255); + lineWidth = 2.0f; + } else if(airspace.type() == Airspace::Type::wildfire) { + color = QColor(244,67,54,50); + lineColor = QColor(244,67,54,255); + lineWidth = 1.0f; + } else if(airspace.type() == Airspace::Type::park) { + color = QColor(224,18,18,25); + lineColor = QColor(224,18,18,255); + lineWidth = 1.0f; + } else if(airspace.type() == Airspace::Type::power_plant) { + color = QColor(246,165,23,25); + lineColor = QColor(246,165,23,255); + lineWidth = 1.0f; + } else if(airspace.type() == Airspace::Type::heliport) { + color = QColor(246,165,23,20); + lineColor = QColor(246,165,23,100); + lineWidth = 1.0f; + } else if(airspace.type() == Airspace::Type::prison) { + color = QColor(246,165,23,38); + lineColor = QColor(246,165,23,255); + lineWidth = 1.0f; + } else if(airspace.type() == Airspace::Type::school) { + color = QColor(246,165,23,38); + lineColor = QColor(246,165,23,255); + lineWidth = 1.0f; + } else if(airspace.type() == Airspace::Type::hospital) { + color = QColor(246,165,23,38); + lineColor = QColor(246,165,23,255); + lineWidth = 1.0f; + } else if(airspace.type() == Airspace::Type::fire) { + color = QColor(244,67,54,50); + lineColor = QColor(244,67,54,255); + lineWidth = 1.0f; + } else if(airspace.type() == Airspace::Type::emergency) { + color = QColor(246,113,23,77); + lineColor = QColor(246,113,23,255); + lineWidth = 1.0f; + } else { + //-- Don't know it + qCWarning(AirMapManagerLog) << "Unknown airspace type:" << static_cast(airspace.type()); + color = QColor(255,0,230,25); + lineColor = QColor(255,0,230,255); + lineWidth = 0.5f; + } +} + +//----------------------------------------------------------------------------- +void +AirMapRestrictionManager::_requestRestrictions(const QGCGeoBoundingCube& roi) +{ + if (!_shared.client()) { + qCDebug(AirMapManagerLog) << "No AirMap client instance. Not updating Airspace"; + return; + } + if (_state != State::Idle) { + qCWarning(AirMapManagerLog) << "AirMapRestrictionManager::updateROI: state not idle"; + return; + } + qCDebug(AirMapManagerLog) << "Restrictions Request (ROI Changed)"; + _polygons.clear(); + _circles.clear(); + _state = State::RetrieveItems; + Airspaces::Search::Parameters params; + params.full = false; + params.date_time = Clock::universal_time(); + //-- Geometry: Polygon + Geometry::Polygon polygon; + for (const auto& qcoord : roi.polygon2D()) { + Geometry::Coordinate coord; + coord.latitude = qcoord.latitude(); + coord.longitude = qcoord.longitude(); + polygon.outer_ring.coordinates.push_back(coord); + } + params.geometry = Geometry(polygon); + std::weak_ptr 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) { + QColor color; + QColor lineColor; + float lineWidth; + _getColor(airspace, color, lineColor, lineWidth); + const Geometry& geometry = airspace.geometry(); + switch(geometry.type()) { + case Geometry::Type::polygon: { + const Geometry::Polygon& polygon = geometry.details_for_polygon(); + _addPolygonToList(polygon, QString::fromStdString(airspace.id()), color, lineColor, lineWidth); + } + break; + case Geometry::Type::multi_polygon: { + const Geometry::MultiPolygon& multiPolygon = geometry.details_for_multi_polygon(); + for (const auto& polygon : multiPolygon) { + _addPolygonToList(polygon, QString::fromStdString(airspace.id()), color, lineColor, lineWidth); + } + } + break; + case Geometry::Type::point: { + const Geometry::Point& point = geometry.details_for_point(); + _circles.append(new AirspaceCircularRestriction(QGeoCoordinate(point.latitude, point.longitude), 0., QString::fromStdString(airspace.id()), color, lineColor, lineWidth)); + // TODO: radius??? + } + break; + case Geometry::Type::invalid: { + qWarning() << "Invalid geometry type"; + } + break; + default: + qWarning() << "Unsupported geometry type: " << static_cast(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); + } + _state = State::Idle; + }); +} + +//----------------------------------------------------------------------------- +void +AirMapRestrictionManager::_addPolygonToList(const airmap::Geometry::Polygon& polygon, const QString advisoryID, const QColor color, const QColor lineColor, float lineWidth) +{ + 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)); + } + _polygons.append(new AirspacePolygonRestriction(polygonArray, advisoryID, color, lineColor, lineWidth)); + 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: "< + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include "LifetimeChecker.h" +#include "AirspaceRestrictionProvider.h" +#include "AirMapSharedState.h" +#include "QGCGeoBoundingCube.h" + +#include +#include + +#include "airmap/geometry.h" +#include "airmap/airspaces.h" + +/** + * @file AirMapRestrictionManager.h + * Class to download polygons from AirMap + */ + +class AirMapRestrictionManager : public AirspaceRestrictionProvider, public LifetimeChecker +{ + Q_OBJECT +public: + AirMapRestrictionManager (AirMapSharedState& shared); + QmlObjectListModel* polygons () override { return &_polygons; } + QmlObjectListModel* circles () override { return &_circles; } + void setROI (const QGCGeoBoundingCube &roi, bool reset = false) override; + +signals: + void error (const QString& what, const QString& airmapdMessage, const QString& airmapdDetails); + +private: + void _requestRestrictions(const QGCGeoBoundingCube& roi); + void _addPolygonToList (const airmap::Geometry::Polygon& polygon, const QString advisoryID, const QColor color, const QColor lineColor, float lineWidth); + void _getColor (const airmap::Airspace& airspace, QColor &color, QColor &lineColor, float &lineWidth); + + enum class State { + Idle, + RetrieveItems, + }; + + AirMapSharedState& _shared; + QGCGeoBoundingCube _lastROI; + State _state = State::Idle; + QmlObjectListModel _polygons; + QmlObjectListModel _circles; +}; + diff --git a/src/Airmap/AirMapRulesetsManager.cc b/src/Airmap/AirMapRulesetsManager.cc new file mode 100644 index 0000000000000000000000000000000000000000..99d75c0b23d293eba8ddf59ef99d91135440b808 --- /dev/null +++ b/src/Airmap/AirMapRulesetsManager.cc @@ -0,0 +1,385 @@ +/**************************************************************************** + * + * (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 "AirMapRulesetsManager.h" +#include "AirMapManager.h" +#include "QGCApplication.h" +#include + +using namespace airmap; + +static const char* kAirMapFeatureGroup = "AirMapFeatureGroup"; + +//----------------------------------------------------------------------------- +AirMapRuleFeature::AirMapRuleFeature(QObject* parent) + : AirspaceRuleFeature(parent) +{ +} + +//----------------------------------------------------------------------------- +AirMapRuleFeature::AirMapRuleFeature(airmap::RuleSet::Feature feature, QObject* parent) + : AirspaceRuleFeature(parent) + , _feature(feature) +{ + //-- Restore persisted value (if it exists) + QSettings settings; + settings.beginGroup(kAirMapFeatureGroup); + switch(_feature.type) { + case RuleSet::Feature::Type::boolean: + //-- For boolean, we have 3 states: 0 - false, 1 - true and 2 - not set + _value = settings.value(name(), 2); + break;; + case RuleSet::Feature::Type::floating_point: + _value = settings.value(name(), NAN); + break;; + case RuleSet::Feature::Type::string: + _value = settings.value(name(), QString()); + break;; + default: + break; + } + settings.endGroup(); +} + +//----------------------------------------------------------------------------- +QVariant +AirMapRuleFeature::value() +{ + //qCDebug(AirMapManagerLog) << "Value of" << name() << "==>" << _value << type(); + return _value; +} + +//----------------------------------------------------------------------------- +AirspaceRuleFeature::Type +AirMapRuleFeature::type() +{ + switch(_feature.type) { + case RuleSet::Feature::Type::boolean: + return AirspaceRuleFeature::Boolean; + case RuleSet::Feature::Type::floating_point: + return AirspaceRuleFeature::Float; + case RuleSet::Feature::Type::string: + return AirspaceRuleFeature::String; + default: + break; + } + return AirspaceRuleFeature::Unknown; +} + +//----------------------------------------------------------------------------- +AirspaceRuleFeature::Unit +AirMapRuleFeature::unit() +{ + switch(_feature.unit) { + case RuleSet::Feature::Unit::kilograms: + return AirspaceRuleFeature::Kilogram; + case RuleSet::Feature::Unit::meters: + return AirspaceRuleFeature::Meters; + case RuleSet::Feature::Unit::meters_per_sec: + return AirspaceRuleFeature::MetersPerSecond; + default: + break; + } + return AirspaceRuleFeature::UnknownUnit; +} + +//----------------------------------------------------------------------------- +AirspaceRuleFeature::Measurement +AirMapRuleFeature::measurement() +{ + switch(_feature.measurement) { + case RuleSet::Feature::Measurement::speed: + return AirspaceRuleFeature::Speed; + case RuleSet::Feature::Measurement::weight: + return AirspaceRuleFeature::Weight; + case RuleSet::Feature::Measurement::distance: + return AirspaceRuleFeature::Distance; + default: + break; + } + return AirspaceRuleFeature::UnknownMeasurement; +} + +//----------------------------------------------------------------------------- +void +AirMapRuleFeature::setValue(const QVariant val) +{ + switch(_feature.type) { + case RuleSet::Feature::Type::boolean: + if(val.toInt() != 0 && val.toInt() != 1) { + return; + } + break; + case RuleSet::Feature::Type::floating_point: + if(!std::isfinite(val.toDouble())) { + return; + } + break;; + case RuleSet::Feature::Type::string: + if(val.toString().isEmpty()) { + return; + } + break;; + default: + return; + } + _value = val; + //-- Make value persistent + QSettings settings; + settings.beginGroup(kAirMapFeatureGroup); + settings.setValue(name(), _value); + settings.endGroup(); + emit valueChanged(); +} + +//----------------------------------------------------------------------------- +AirMapRule::AirMapRule(QObject* parent) + : AirspaceRule(parent) +{ +} + +//----------------------------------------------------------------------------- +AirMapRule::AirMapRule(const airmap::RuleSet::Rule& rule, QObject* parent) + : AirspaceRule(parent) + , _rule(rule) +{ +} + +//----------------------------------------------------------------------------- +AirMapRule::~AirMapRule() +{ + _features.deleteListAndContents(); +} + +//----------------------------------------------------------------------------- +AirspaceRule::Status +AirMapRule::status() +{ + switch(_rule.status) { + case RuleSet::Rule::Status::conflicting: + return AirspaceRule::Conflicting; + case RuleSet::Rule::Status::not_conflicting: + return AirspaceRule::NotConflicting; + case RuleSet::Rule::Status::missing_info: + return AirspaceRule::MissingInfo; + case RuleSet::Rule::Status::unknown: + default: + return AirspaceRule::Unknown; + } +} + +//----------------------------------------------------------------------------- +AirMapRuleSet::AirMapRuleSet(QObject* parent) + : AirspaceRuleSet(parent) + , _isDefault(false) + , _selected(false) + , _selectionType(AirspaceRuleSet::Optional) +{ +} + +//----------------------------------------------------------------------------- +AirMapRuleSet::~AirMapRuleSet() +{ + _rules.deleteListAndContents(); +} + +//----------------------------------------------------------------------------- +void +AirMapRuleSet::setSelected(bool sel) +{ + if(_selectionType != AirspaceRuleSet::Required) { + if(_selected != sel) { + _selected = sel; + emit selectedChanged(); + } + } else { + if(!_selected) { + _selected = true; + emit selectedChanged(); + } + } +} + +//----------------------------------------------------------------------------- +AirMapRulesetsManager::AirMapRulesetsManager(AirMapSharedState& shared) + : _shared(shared) +{ +} + +//----------------------------------------------------------------------------- +static bool +rules_sort(QObject* a, QObject* b) +{ + AirMapRule* aa = qobject_cast(a); + AirMapRule* bb = qobject_cast(b); + if(!aa || !bb) return false; + return static_cast(aa->order()) > static_cast(bb->order()); +} + +//----------------------------------------------------------------------------- +void AirMapRulesetsManager::setROI(const QGCGeoBoundingCube& roi, bool reset) +{ + Q_UNUSED(reset); + if (!_shared.client()) { + qCDebug(AirMapManagerLog) << "No AirMap client instance. Not updating Airspace"; + return; + } + if (_state != State::Idle) { + qCWarning(AirMapManagerLog) << "AirMapRulesetsManager::updateROI: state not idle"; + return; + } + qCDebug(AirMapManagerLog) << "Rulesets Request (ROI Changed)"; + _valid = false; + //-- Save current selection state + QMap selectionSet; + for(int rs = 0; rs < ruleSets()->count(); rs++) { + AirMapRuleSet* ruleSet = qobject_cast(ruleSets()->get(rs)); + selectionSet[ruleSet->id()] = ruleSet->selected(); + } + _ruleSets.clearAndDeleteContents(); + _state = State::RetrieveItems; + RuleSets::Search::Parameters params; + //-- Geometry: Polygon + Geometry::Polygon polygon; + //-- Get ROI bounding box, clipping to max area of interest + for (const auto& qcoord : roi.polygon2D(qgcApp()->toolbox()->airspaceManager()->maxAreaOfInterest())) { + Geometry::Coordinate coord; + coord.latitude = qcoord.latitude(); + coord.longitude = qcoord.longitude(); + polygon.outer_ring.coordinates.push_back(coord); + } + params.geometry = Geometry(polygon); + std::weak_ptr isAlive(_instance); + _shared.client()->rulesets().search(params, + [this, isAlive, selectionSet](const RuleSets::Search::Result& result) { + if (!isAlive.lock()) return; + if (_state != State::RetrieveItems) return; + if (result) { + const std::vector rulesets = result.value(); + qCDebug(AirMapManagerLog) << "Successful rulesets search. Items:" << rulesets.size(); + for (const auto& ruleset : rulesets) { + AirMapRuleSet* pRuleSet = new AirMapRuleSet(this); + connect(pRuleSet, &AirspaceRuleSet::selectedChanged, this, &AirMapRulesetsManager::_selectedChanged); + pRuleSet->_id = QString::fromStdString(ruleset.id); + pRuleSet->_name = QString::fromStdString(ruleset.name); + pRuleSet->_shortName = QString::fromStdString(ruleset.short_name); + pRuleSet->_description = QString::fromStdString(ruleset.description); + pRuleSet->_isDefault = ruleset.is_default; + //-- Restore selection set (if any) + if(selectionSet.contains(pRuleSet->id())) { + pRuleSet->_selected = selectionSet[pRuleSet->id()]; + } else { + if(pRuleSet->_isDefault) { + pRuleSet->_selected = true; + } + } + switch(ruleset.selection_type) { + case RuleSet::SelectionType::pickone: + pRuleSet->_selectionType = AirspaceRuleSet::Pickone; + break; + case RuleSet::SelectionType::required: + pRuleSet->_selectionType = AirspaceRuleSet::Required; + pRuleSet->_selected = true; + break; + case RuleSet::SelectionType::optional: + pRuleSet->_selectionType = AirspaceRuleSet::Optional; + break; + } + //-- Iterate Rules + for (const auto& rule : ruleset.rules) { + AirMapRule* pRule = new AirMapRule(rule, this); + //-- Iterate Rule Features + for (const auto& feature : rule.features) { + AirMapRuleFeature* pFeature = new AirMapRuleFeature(feature, this); + pRule->_features.append(pFeature); + } + pRuleSet->_rules.append(pRule); + } + //-- Sort rules by display order + std::sort(pRuleSet->_rules.objectList()->begin(), pRuleSet->_rules.objectList()->end(), rules_sort); + _ruleSets.append(pRuleSet); + qCDebug(AirMapManagerLog) << "Adding ruleset" << pRuleSet->name(); + /* + qDebug() << "------------------------------------------"; + qDebug() << "Jurisdiction:" << ruleset.jurisdiction.name.data() << (int)ruleset.jurisdiction.region; + qDebug() << "Name: " << ruleset.name.data(); + qDebug() << "Short Name: " << ruleset.short_name.data(); + qDebug() << "Description: " << ruleset.description.data(); + qDebug() << "Is default: " << ruleset.is_default; + qDebug() << "Applicable to these airspace types:"; + for (const auto& airspaceType : ruleset.airspace_types) { + qDebug() << " " << airspaceType.data(); + } + qDebug() << "Rules:"; + for (const auto& rule : ruleset.rules) { + qDebug() << " --------------------------------------"; + qDebug() << " short_text: " << rule.short_text.data(); + qDebug() << " description: " << rule.description.data(); + qDebug() << " display_order:" << rule.display_order; + qDebug() << " status: " << (int)rule.status; + qDebug() << " ------------------------------"; + qDebug() << " Features:"; + for (const auto& feature : rule.features) { + qDebug() << " name: " << feature.name.data(); + qDebug() << " description:" << feature.description.data(); + qDebug() << " status: " << (int)feature.status; + qDebug() << " type: " << (int)feature.type; + qDebug() << " measurement:" << (int)feature.measurement; + qDebug() << " unit: " << (int)feature.unit; + } + } + */ + } + _valid = true; + } else { + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + emit error("Failed to retrieve RuleSets", QString::fromStdString(result.error().message()), description); + } + _state = State::Idle; + emit ruleSetsChanged(); + emit selectedRuleSetsChanged(); + }); +} + +//----------------------------------------------------------------------------- +QString +AirMapRulesetsManager::selectedRuleSets() +{ + QString selection; + for(int i = 0; i < _ruleSets.count(); i++) { + AirMapRuleSet* rule = qobject_cast(_ruleSets.get(i)); + if(rule && rule->selected()) { + selection += rule->shortName() + ", "; + } + } + int idx = selection.lastIndexOf(", "); + if(idx >= 0) { + selection = selection.left(idx); + } + return selection; +} + +//----------------------------------------------------------------------------- +void +AirMapRulesetsManager::_selectedChanged() +{ + emit selectedRuleSetsChanged(); + //-- TODO: Do whatever it is you do to select a rule +} + +//----------------------------------------------------------------------------- +void +AirMapRulesetsManager::clearAllFeatures() +{ + QSettings settings; + settings.beginGroup(kAirMapFeatureGroup); + settings.remove(""); + settings.endGroup(); +} + diff --git a/src/Airmap/AirMapRulesetsManager.h b/src/Airmap/AirMapRulesetsManager.h new file mode 100644 index 0000000000000000000000000000000000000000..ccbc6e2481ca117ab88434c0cc8d93cb57d556cc --- /dev/null +++ b/src/Airmap/AirMapRulesetsManager.h @@ -0,0 +1,132 @@ +/**************************************************************************** + * + * (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 "LifetimeChecker.h" +#include "AirspaceRulesetsProvider.h" +#include "AirMapSharedState.h" +#include "QGCGeoBoundingCube.h" + +#include +#include + +#include + +/** + * @file AirMapRulesetsManager.h + * Class to download rulesets from AirMap + */ + +//----------------------------------------------------------------------------- +class AirMapRuleFeature : public AirspaceRuleFeature +{ + Q_OBJECT +public: + + AirMapRuleFeature(QObject* parent = nullptr); + AirMapRuleFeature(airmap::RuleSet::Feature feature, QObject* parent = nullptr); + + quint32 id () override { return static_cast(_feature.id); } + Type type () override; + Unit unit () override; + Measurement measurement () override; + QString name () override { return QString::fromStdString(_feature.name); } + QString description () override { return QString::fromStdString(_feature.description); } + QVariant value () override; + void setValue (const QVariant val) override; +private: + airmap::RuleSet::Feature _feature; + QVariant _value; +}; + +//----------------------------------------------------------------------------- +class AirMapRule : public AirspaceRule +{ + Q_OBJECT + friend class AirMapRulesetsManager; + friend class AirMapFlightPlanManager; +public: + + AirMapRule (QObject* parent = nullptr); + AirMapRule (const airmap::RuleSet::Rule& rule, QObject* parent = nullptr); + ~AirMapRule () override; + + int order () { return static_cast(_rule.display_order); } + Status status () override; + QString shortText () override { return QString::fromStdString(_rule.short_text); } + QString description () override { return QString::fromStdString(_rule.description); } + QmlObjectListModel* features () override { return &_features; } + +private: + airmap::RuleSet::Rule _rule; + QmlObjectListModel _features; +}; + +//----------------------------------------------------------------------------- +class AirMapRuleSet : public AirspaceRuleSet +{ + Q_OBJECT + friend class AirMapRulesetsManager; + friend class AirMapFlightPlanManager; +public: + AirMapRuleSet (QObject* parent = nullptr); + ~AirMapRuleSet () override; + QString id () override { return _id; } + QString description () override { return _description; } + bool isDefault () override { return _isDefault; } + QString name () override { return _name; } + QString shortName () override { return _shortName; } + SelectionType selectionType () override { return _selectionType; } + bool selected () override { return _selected; } + void setSelected (bool sel) override; + QmlObjectListModel* rules () override { return &_rules; } +private: + QString _id; + QString _description; + bool _isDefault; + bool _selected; + QString _name; + QString _shortName; + SelectionType _selectionType; + QmlObjectListModel _rules; +}; + +//----------------------------------------------------------------------------- +class AirMapRulesetsManager : public AirspaceRulesetsProvider, public LifetimeChecker +{ + Q_OBJECT +public: + AirMapRulesetsManager (AirMapSharedState& shared); + + bool valid () override { return _valid; } + QmlObjectListModel* ruleSets () override { return &_ruleSets; } + QString selectedRuleSets() override; + + void setROI (const QGCGeoBoundingCube& roi, bool reset = false) override; + void clearAllFeatures() override; + +signals: + void error (const QString& what, const QString& airmapdMessage, const QString& airmapdDetails); + +private slots: + void _selectedChanged (); + +private: + enum class State { + Idle, + RetrieveItems, + }; + bool _valid; + State _state = State::Idle; + AirMapSharedState& _shared; + QmlObjectListModel _ruleSets; //-- List of AirMapRuleSet elements +}; + + diff --git a/src/Airmap/AirMapSettings.cc b/src/Airmap/AirMapSettings.cc new file mode 100644 index 0000000000000000000000000000000000000000..1c62be31eaff9ae54905ddc87f6dfe0853be9115 --- /dev/null +++ b/src/Airmap/AirMapSettings.cc @@ -0,0 +1,37 @@ +/**************************************************************************** + * + * (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 "QGCApplication.h" + +#include +#include + +DECLARE_SETTINGGROUP(AirMap) +{ + INIT_SETTINGFACT(usePersonalApiKey); + INIT_SETTINGFACT(apiKey); + INIT_SETTINGFACT(clientID); + INIT_SETTINGFACT(userName); + INIT_SETTINGFACT(password); + INIT_SETTINGFACT(enableAirMap); + INIT_SETTINGFACT(enableAirspace); + INIT_SETTINGFACT(enableTelemetry); + QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); + qmlRegisterUncreatableType("QGroundControl.SettingsManager", 1, 0, "AirMapSettings", "Reference only"); +} + +DECLARE_SETTINGSFACT(AirMapSettings, usePersonalApiKey) +DECLARE_SETTINGSFACT(AirMapSettings, apiKey) +DECLARE_SETTINGSFACT(AirMapSettings, clientID) +DECLARE_SETTINGSFACT(AirMapSettings, userName) +DECLARE_SETTINGSFACT(AirMapSettings, password) +DECLARE_SETTINGSFACT(AirMapSettings, enableAirMap) +DECLARE_SETTINGSFACT(AirMapSettings, enableAirspace) +DECLARE_SETTINGSFACT(AirMapSettings, enableTelemetry) diff --git a/src/Airmap/AirMapSettings.h b/src/Airmap/AirMapSettings.h new file mode 100644 index 0000000000000000000000000000000000000000..0c10ae4024f13b5eacfd03681b91001c08465911 --- /dev/null +++ b/src/Airmap/AirMapSettings.h @@ -0,0 +1,31 @@ +/**************************************************************************** + * + * (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" + +class AirMapSettings : public SettingsGroup +{ + Q_OBJECT +public: + AirMapSettings(QObject* parent = nullptr); + + DEFINE_SETTINGGROUP(AirMap) + + DEFINE_SETTINGFACT(usePersonalApiKey) + DEFINE_SETTINGFACT(apiKey) + DEFINE_SETTINGFACT(clientID) + DEFINE_SETTINGFACT(userName) + DEFINE_SETTINGFACT(password) + DEFINE_SETTINGFACT(enableAirMap) + DEFINE_SETTINGFACT(enableAirspace) + DEFINE_SETTINGFACT(enableTelemetry) + +}; diff --git a/src/Airmap/AirMapSharedState.cc b/src/Airmap/AirMapSharedState.cc new file mode 100644 index 0000000000000000000000000000000000000000..d13d4e0e3cd2266fe6014c13e3dee4a39b3205bf --- /dev/null +++ b/src/Airmap/AirMapSharedState.cc @@ -0,0 +1,124 @@ +/**************************************************************************** + * + * (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 "AirMapSharedState.h" +#include "AirMapManager.h" + +#include "airmap/authenticator.h" +#include "qjsonwebtoken.h" + +using namespace airmap; + +void +AirMapSharedState::setSettings(const Settings& settings) +{ + logout(); + _settings = settings; +} + +void +AirMapSharedState::doRequestWithLogin(const Callback& callback) +{ + if (isLoggedIn()) { + callback(_loginToken); + } else { + login(); + _pendingRequests.enqueue(callback); + } +} + +//-- TODO: +// For now, only anonymous login collects the (anonymous) pilot ID within login() +// For autheticated logins, we need to collect it here as opposed to spread all over +// the place as it is the case now. + +void +AirMapSharedState::login() +{ + if (isLoggedIn() || _isLoginInProgress) { + return; + } + _isLoginInProgress = true; + if (_settings.userName == "") { //use anonymous login + qCDebug(AirMapManagerLog) << "Anonymous authentication"; + Authenticator::AuthenticateAnonymously::Params params; + params.id = "Anonymous"; + _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(); + emit authStatus(AirspaceManager::AuthStatus::Anonymous); + _loginToken = QString::fromStdString(result.value().id); + QJsonWebToken token = QJsonWebToken::fromTokenAndSecret(_loginToken, QString()); + QJsonDocument doc = token.getPayloadJDoc(); + QJsonObject json = doc.object(); + _pilotID = json.value("sub").toString(); + qCDebug(AirMapManagerLog) << "Anonymous pilot id:" << _pilotID; + _processPendingRequests(); + } else { + _pendingRequests.clear(); + emit authStatus(AirspaceManager::AuthStatus::Error); + 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"; + qCDebug(AirMapManagerLog) << "User authentication" << _settings.userName; + _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=" + < + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include +#include + +#include "AirspaceManager.h" + +#include + +/** + * @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; } + + QString pilotID () { return _pilotID; } + void setPilotID (const QString& pilotID) { _pilotID = pilotID; } + + /** + * 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); + void authStatus (AirspaceManager::AuthStatus status); + +private: + void _processPendingRequests (); + +private: + bool _isLoginInProgress = false; + QString _loginToken; ///< login token: empty when not logged in + QString _pilotID; + airmap::qt::Client* _client = nullptr; + Settings _settings; + QQueue _pendingRequests; ///< pending requests that are processed after a successful login +}; + diff --git a/src/Airmap/AirMapTelemetry.cc b/src/Airmap/AirMapTelemetry.cc new file mode 100644 index 0000000000000000000000000000000000000000..5357cf70cbf154a59f921590758fbf8049d29452 --- /dev/null +++ b/src/Airmap/AirMapTelemetry.cc @@ -0,0 +1,156 @@ +/**************************************************************************** + * + * (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 "AirMapTelemetry.h" +#include "AirMapManager.h" + +#include "QGCMAVLink.h" + +#include "airmap/telemetry.h" +#include "airmap/flights.h" + +using namespace airmap; + +//----------------------------------------------------------------------------- +AirMapTelemetry::AirMapTelemetry(AirMapSharedState& shared) + : _shared(shared) +{ +} + +//----------------------------------------------------------------------------- +void +AirMapTelemetry::vehicleMessageReceived(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() +{ + 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; + } + // rate-limit updates to 5 Hz + if (!_timerLastSent.hasExpired(200)) { + return; + } + _timerLastSent.restart(); + + mavlink_global_position_int_t globalPosition; + mavlink_msg_global_position_int_decode(&message, &globalPosition); + Telemetry::Position position{ + milliseconds_since_epoch(Clock::universal_time()), + static_cast(globalPosition.lat / 1e7), + static_cast(globalPosition.lon / 1e7), + static_cast(globalPosition.alt) / 1000.0, + static_cast(globalPosition.relative_alt) / 1000.0, + static_cast(_lastHdop) + }; + Telemetry::Speed speed{ + milliseconds_since_epoch(Clock::universal_time()), + globalPosition.vx / 100.f, + globalPosition.vy / 100.f, + globalPosition.vz / 100.f + }; + + //qCDebug(AirMapManagerLog) << "Telemetry:" << globalPosition.lat / 1e7 << globalPosition.lon / 1e7; + 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:" << static_cast(_state); + return; + } + if(flightID.isEmpty()) { + qCWarning(AirMapManagerLog) << "Not starting telemetry: No flight ID."; + 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); + } + }); + _timerLastSent.start(); +} + +//----------------------------------------------------------------------------- +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; + }); +} + diff --git a/src/Airmap/AirMapTelemetry.h b/src/Airmap/AirMapTelemetry.h new file mode 100644 index 0000000000000000000000000000000000000000..707a1dc4ac5465d9d33101eac0ebbd5321b52cd1 --- /dev/null +++ b/src/Airmap/AirMapTelemetry.h @@ -0,0 +1,57 @@ +/**************************************************************************** + * + * (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 "LifetimeChecker.h" +#include "AirMapSharedState.h" + +#include + +#include +#include + +/// Class to send telemetry data to AirMap +class AirMapTelemetry : public QObject, public LifetimeChecker +{ + Q_OBJECT +public: + AirMapTelemetry (AirMapSharedState& shared); + virtual ~AirMapTelemetry () = default; + + void startTelemetryStream (const QString& flightID); + void stopTelemetryStream (); + bool isTelemetryStreaming (); + +signals: + void error (const QString& what, const QString& airmapdMessage, const QString& airmapdDetails); + +public slots: + void vehicleMessageReceived (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; + QElapsedTimer _timerLastSent; +}; + diff --git a/src/Airmap/AirMapTrafficMonitor.cc b/src/Airmap/AirMapTrafficMonitor.cc new file mode 100644 index 0000000000000000000000000000000000000000..c09cd5c0906d39d8b21161bf5f93d4c4cdfbba75 --- /dev/null +++ b/src/Airmap/AirMapTrafficMonitor.cc @@ -0,0 +1,79 @@ +/**************************************************************************** + * + * (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 "AirMapTrafficMonitor.h" +#include "AirMapManager.h" + +using namespace airmap; + +//----------------------------------------------------------------------------- +AirMapTrafficMonitor::AirMapTrafficMonitor(AirMapSharedState& shared) + : _shared(shared) +{ +} + +//----------------------------------------------------------------------------- +AirMapTrafficMonitor::~AirMapTrafficMonitor() +{ + stop(); +} + +//----------------------------------------------------------------------------- +void +AirMapTrafficMonitor::startConnection(const QString& flightID) +{ + if(flightID.isEmpty() || _flightID == flightID) { + return; + } + _flightID = flightID; + qCDebug(AirMapManagerLog) << "Traffic update started for" << 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(type == Traffic::Update::Type::alert, traffic_id, vehicle_id, + QGeoCoordinate(traffic.latitude, traffic.longitude, traffic.altitude), static_cast(traffic.heading)); + } +} + +//----------------------------------------------------------------------------- +void +AirMapTrafficMonitor::stop() +{ + if (_monitor) { + _monitor->unsubscribe(_subscriber); + _subscriber.reset(); + _monitor.reset(); + } +} + diff --git a/src/Airmap/AirMapTrafficMonitor.h b/src/Airmap/AirMapTrafficMonitor.h new file mode 100644 index 0000000000000000000000000000000000000000..c6c5d71eae7c1aca310dd5074826f5ce752cfbb6 --- /dev/null +++ b/src/Airmap/AirMapTrafficMonitor.h @@ -0,0 +1,52 @@ +/**************************************************************************** + * + * (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 "LifetimeChecker.h" +#include "AirMapSharedState.h" + +#include +#include + +#include "airmap/traffic.h" + +#include + +/** + * @class AirMapTrafficMonitor + * + */ + +class AirMapTrafficMonitor : public QObject, public LifetimeChecker +{ + Q_OBJECT +public: + AirMapTrafficMonitor (AirMapSharedState& shared); + virtual ~AirMapTrafficMonitor (); + + void startConnection (const QString& flightID); + + void stop(); + +signals: + void error (const QString& what, const QString& airmapdMessage, const QString& airmapdDetails); + void trafficUpdate (bool alert, 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; +}; + + diff --git a/src/Airmap/AirMapVehicleManager.cc b/src/Airmap/AirMapVehicleManager.cc new file mode 100644 index 0000000000000000000000000000000000000000..935ed7321cded2e1e5ec2229134c7a5244a3fc39 --- /dev/null +++ b/src/Airmap/AirMapVehicleManager.cc @@ -0,0 +1,100 @@ +/**************************************************************************** + * + * (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 "AirspaceFlightPlanProvider.h" +#include "AirMapFlightPlanManager.h" +#include "AirMapVehicleManager.h" +#include "AirMapManager.h" + +#include "QGCApplication.h" +#include "Vehicle.h" +#include "QGCApplication.h" +#include "SettingsManager.h" + +//----------------------------------------------------------------------------- +AirMapVehicleManager::AirMapVehicleManager(AirMapSharedState& shared, const Vehicle& vehicle) + : AirspaceVehicleManager(vehicle) + , _shared(shared) + , _flightManager(shared) + , _telemetry(shared) + , _trafficMonitor(shared) +{ + connect(&_flightManager, &AirMapFlightManager::error, this, &AirMapVehicleManager::error); + connect(&_telemetry, &AirMapTelemetry::error, this, &AirMapVehicleManager::error); + connect(&_trafficMonitor, &AirMapTrafficMonitor::error, this, &AirMapVehicleManager::error); + connect(&_trafficMonitor, &AirMapTrafficMonitor::trafficUpdate, this, &AirspaceVehicleManager::trafficUpdate); + AirMapFlightPlanManager* planMgr = qobject_cast(qgcApp()->toolbox()->airspaceManager()->flightPlan()); + if(planMgr) { + connect(planMgr, &AirMapFlightPlanManager::flightIDChanged, this, &AirMapVehicleManager::_flightIDChanged); + } +} + +//----------------------------------------------------------------------------- +void +AirMapVehicleManager::startTelemetryStream() +{ + AirMapFlightPlanManager* planMgr = qobject_cast(qgcApp()->toolbox()->airspaceManager()->flightPlan()); + if (!planMgr->flightID().isEmpty()) { + //-- Is telemetry enabled? + if(qgcApp()->toolbox()->settingsManager()->airMapSettings()->enableTelemetry()->rawValue().toBool()) { + //-- TODO: This will start telemetry using the current flight ID in memory (current flight in AirMapFlightPlanManager) + qCDebug(AirMapManagerLog) << "AirMap telemetry stream enabled"; + _telemetry.startTelemetryStream(planMgr->flightID()); + } + } else { + qCDebug(AirMapManagerLog) << "AirMap telemetry stream not possible (No Flight ID)"; + } +} + +//----------------------------------------------------------------------------- +void +AirMapVehicleManager::stopTelemetryStream() +{ + _telemetry.stopTelemetryStream(); +} + +//----------------------------------------------------------------------------- +bool +AirMapVehicleManager::isTelemetryStreaming() +{ + return _telemetry.isTelemetryStreaming(); +} + +//----------------------------------------------------------------------------- +void +AirMapVehicleManager::endFlight() +{ + AirMapFlightPlanManager* planMgr = qobject_cast(qgcApp()->toolbox()->airspaceManager()->flightPlan()); + if (!planMgr->flightID().isEmpty()) { + _flightManager.endFlight(planMgr->flightID()); + } + _trafficMonitor.stop(); +} + +//----------------------------------------------------------------------------- +void +AirMapVehicleManager::vehicleMavlinkMessageReceived(const mavlink_message_t& message) +{ + if (isTelemetryStreaming()) { + _telemetry.vehicleMessageReceived(message); + } +} + +//----------------------------------------------------------------------------- +void +AirMapVehicleManager::_flightIDChanged(QString flightID) +{ + qCDebug(AirMapManagerLog) << "Flight ID Changed:" << flightID; + //-- Handle traffic monitor + if(flightID.isEmpty()) { + _trafficMonitor.stop(); + } else { + _trafficMonitor.startConnection(flightID); + } +} diff --git a/src/Airmap/AirMapVehicleManager.h b/src/Airmap/AirMapVehicleManager.h new file mode 100644 index 0000000000000000000000000000000000000000..84813f2732696e30e199627ef9698c7bd39be3df --- /dev/null +++ b/src/Airmap/AirMapVehicleManager.h @@ -0,0 +1,49 @@ +/**************************************************************************** + * + * (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 "AirspaceManager.h" +#include "AirspaceVehicleManager.h" +#include "AirMapSharedState.h" +#include "AirMapFlightManager.h" +#include "AirMapTelemetry.h" +#include "AirMapTrafficMonitor.h" + +/// AirMap per vehicle management class. + +class AirMapVehicleManager : public AirspaceVehicleManager +{ + Q_OBJECT +public: + AirMapVehicleManager (AirMapSharedState& shared, const Vehicle& vehicle); + ~AirMapVehicleManager () override = default; + + void startTelemetryStream () override; + void stopTelemetryStream () override; + bool isTelemetryStreaming () override; + +signals: + void error (const QString& what, const QString& airmapdMessage, const QString& airmapdDetails); + +public slots: + void endFlight () override; + +protected slots: + void vehicleMavlinkMessageReceived(const mavlink_message_t& message) override; + +private slots: + void _flightIDChanged (QString flightID); + +private: + AirMapSharedState& _shared; + AirMapFlightManager _flightManager; + AirMapTelemetry _telemetry; + AirMapTrafficMonitor _trafficMonitor; +}; diff --git a/src/Airmap/AirMapWeatherInfoManager.cc b/src/Airmap/AirMapWeatherInfoManager.cc new file mode 100644 index 0000000000000000000000000000000000000000..cb49f27da4b88ac25bc2e583ddbef2dfdfee6929 --- /dev/null +++ b/src/Airmap/AirMapWeatherInfoManager.cc @@ -0,0 +1,73 @@ +/**************************************************************************** + * + * (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 "AirMapWeatherInfoManager.h" +#include "AirMapManager.h" + + +#define WEATHER_UPDATE_DISTANCE 50000 //-- 50km threshold for weather updates +#define WEATHER_UPDATE_TIME 30 * 60 * 60 * 1000 //-- 30 minutes threshold for weather updates + +using namespace airmap; + +AirMapWeatherInfoManager::AirMapWeatherInfoManager(AirMapSharedState& shared, QObject *parent) + : AirspaceWeatherInfoProvider(parent) + , _valid(false) + , _shared(shared) +{ +} + +void +AirMapWeatherInfoManager::setROI(const QGCGeoBoundingCube& roi, bool reset) +{ + //-- If first time or we've moved more than WEATHER_UPDATE_DISTANCE, ask for weather updates. + if(reset || (!_lastRoiCenter.isValid() || _lastRoiCenter.distanceTo(roi.center()) > WEATHER_UPDATE_DISTANCE)) { + _lastRoiCenter = roi.center(); + _requestWeatherUpdate(_lastRoiCenter); + _weatherTime.start(); + } else { + //-- Check weather once every WEATHER_UPDATE_TIME + if(_weatherTime.elapsed() > WEATHER_UPDATE_TIME) { + _requestWeatherUpdate(roi.center()); + _weatherTime.start(); + } + } +} + +void +AirMapWeatherInfoManager::_requestWeatherUpdate(const QGeoCoordinate& coordinate) +{ + qCDebug(AirMapManagerLog) << "Weather Request (ROI Changed)"; + if (!_shared.client()) { + qCDebug(AirMapManagerLog) << "No AirMap client instance. Not updating Weather information"; + _valid = false; + emit weatherChanged(); + return; + } + Advisory::ReportWeather::Parameters params; + params.longitude= static_cast(coordinate.longitude()); + params.latitude = static_cast(coordinate.latitude()); + _shared.client()->advisory().report_weather(params, [this, coordinate](const Advisory::ReportWeather::Result& result) { + if (result) { + _weather = result.value(); + _valid = true; + if(_weather.icon.empty()) { + _icon = QStringLiteral("qrc:/airmapweather/unknown.svg"); + } else { + _icon = QStringLiteral("qrc:/airmapweather/") + QString::fromStdString(_weather.icon).replace("-", "_") + QStringLiteral(".svg"); + } + qCDebug(AirMapManagerLog) << "Weather Info: " << _valid << "Icon:" << QString::fromStdString(_weather.icon) << "Condition:" << QString::fromStdString(_weather.condition) << "Temp:" << _weather.temperature; + } else { + _valid = false; + QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : ""); + qCDebug(AirMapManagerLog) << "Request Weather Failed" << QString::fromStdString(result.error().message()) << description; + } + emit weatherChanged(); + }); +} diff --git a/src/Airmap/AirMapWeatherInfoManager.h b/src/Airmap/AirMapWeatherInfoManager.h new file mode 100644 index 0000000000000000000000000000000000000000..9349e71a5dfee62520ed6143bcbbf91875b2e44f --- /dev/null +++ b/src/Airmap/AirMapWeatherInfoManager.h @@ -0,0 +1,61 @@ +/**************************************************************************** + * + * (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 "LifetimeChecker.h" + +#include "AirspaceWeatherInfoProvider.h" +#include "AirMapSharedState.h" +#include "QGCGeoBoundingCube.h" + +#include +#include + +#include "airmap/advisory.h" + +/** + * @file AirMapWeatherInfoManager.h + * Weather information provided by AirMap. + */ + +class AirMapWeatherInfoManager : public AirspaceWeatherInfoProvider, public LifetimeChecker +{ + Q_OBJECT +public: + AirMapWeatherInfoManager(AirMapSharedState &shared, QObject *parent = nullptr); + + bool valid () override { return _valid; } + QString condition () override { return QString::fromStdString(_weather.condition); } + QString icon () override { return _icon; } + quint32 windHeading () override { return _weather.wind.heading; } + float windSpeed () override { return _weather.wind.speed; } + quint32 windGusting () override { return _weather.wind.gusting; } + float temperature () override { return _weather.temperature; } + float humidity () override { return _weather.humidity; } + float visibility () override { return _weather.visibility; } + float precipitation () override { return _weather.precipitation; } + + void setROI (const QGCGeoBoundingCube& roi, bool reset = false) override; + +signals: + void error (const QString& what, const QString& airmapdMessage, const QString& airmapdDetails); + +private: + void _requestWeatherUpdate (const QGeoCoordinate& coordinate); + +private: + bool _valid; + QString _icon; + airmap::Advisory::Weather _weather; + //-- Don't check the weather every time the user moves the map + AirMapSharedState& _shared; + QGeoCoordinate _lastRoiCenter; + QTime _weatherTime; +}; diff --git a/src/Airmap/AirmapSettings.qml b/src/Airmap/AirmapSettings.qml new file mode 100644 index 0000000000000000000000000000000000000000..30ac1eb9fa884f154831e3bd5a625e36cc9de086 --- /dev/null +++ b/src/Airmap/AirmapSettings.qml @@ -0,0 +1,741 @@ +/**************************************************************************** + * + * (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 QtGraphicalEffects 1.0 +import QtMultimedia 5.5 +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 QtLocation 5.3 +import QtPositioning 5.3 + +import QGroundControl 1.0 +import QGroundControl.Airspace 1.0 +import QGroundControl.Controllers 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.FactControls 1.0 +import QGroundControl.FactSystem 1.0 +import QGroundControl.FlightMap 1.0 +import QGroundControl.MultiVehicleManager 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.ScreenTools 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 * 20 + property real _buttonWidth: ScreenTools.defaultFontPixelWidth * 18 + property real _panelWidth: _qgcView.width * _internalWidthRatio + property Fact _enableAirMapFact: QGroundControl.settingsManager.airMapSettings.enableAirMap + property bool _airMapEnabled: _enableAirMapFact.rawValue + property var _authStatus: QGroundControl.airspaceManager.authStatus + + 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: generalRow.height + (ScreenTools.defaultFontPixelHeight * 2) + width: _panelWidth + color: qgcPal.windowShade + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + Row { + id: generalRow + spacing: ScreenTools.defaultFontPixelWidth * 4 + anchors.centerIn: parent + Column { + spacing: ScreenTools.defaultFontPixelWidth + FactCheckBox { + text: qsTr("Enable AirMap Services") + fact: _enableAirMapFact + visible: _enableAirMapFact.visible + } + FactCheckBox { + text: qsTr("Enable Telemetry") + fact: _enableTelemetryFact + visible: _enableTelemetryFact.visible + enabled: _airMapEnabled + property Fact _enableTelemetryFact: QGroundControl.settingsManager.airMapSettings.enableTelemetry + } + FactCheckBox { + text: qsTr("Show Airspace on Map (Experimental)") + fact: _enableAirspaceFact + visible: _enableAirspaceFact.visible + enabled: _airMapEnabled + property Fact _enableAirspaceFact: QGroundControl.settingsManager.airMapSettings.enableAirspace + } + } + QGCButton { + text: qsTr("Clear Saved Answers") + enabled: _enableAirMapFact.rawValue + onClicked: clearDialog.open() + anchors.verticalCenter: parent.verticalCenter + MessageDialog { + id: clearDialog + visible: false + icon: StandardIcon.Warning + standardButtons: StandardButton.Yes | StandardButton.No + title: qsTr("Clear Saved Answers") + text: qsTr("All saved ruleset answers will be cleared. Is this really what you want?") + onYes: { + QGroundControl.airspaceManager.ruleSets.clearAllFeatures() + clearDialog.close() + } + onNo: { + clearDialog.close() + } + } + } + } + } + //----------------------------------------------------------------- + //-- Connection Status + Item { + width: _panelWidth + height: statusLabel.height + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + visible: QGroundControl.settingsManager.appSettings.visible && _airMapEnabled + QGCLabel { + id: statusLabel + text: qsTr("Connection Status") + font.family: ScreenTools.demiboldFontFamily + } + } + Rectangle { + height: statusCol.height + (ScreenTools.defaultFontPixelHeight * 2) + width: _panelWidth + color: qgcPal.windowShade + visible: QGroundControl.settingsManager.appSettings.visible && _airMapEnabled + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + Column { + id: statusCol + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + width: parent.width + anchors.centerIn: parent + QGCLabel { + text: QGroundControl.airspaceManager.connected ? qsTr("Connected") : qsTr("Not Connected") + color: QGroundControl.airspaceManager.connected ? qgcPal.colorGreen : qgcPal.colorRed + anchors.horizontalCenter: parent.horizontalCenter + } + QGCLabel { + text: QGroundControl.airspaceManager.connectStatus + visible: QGroundControl.airspaceManager.connectStatus != "" + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + width: parent.width * 0.8 + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + //----------------------------------------------------------------- + //-- Login / Registration + Item { + width: _panelWidth + height: loginLabel.height + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + 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: 3 + columnSpacing: ScreenTools.defaultFontPixelWidth + rowSpacing: ScreenTools.defaultFontPixelHeight * 0.25 + anchors.centerIn: parent + QGCLabel { text: qsTr("User Name:") } + FactTextField { + fact: _usernameFact + width: _editFieldWidth + enabled: _airMapEnabled + visible: _usernameFact.visible + Layout.fillWidth: true + Layout.minimumWidth: _editFieldWidth + property Fact _usernameFact: QGroundControl.settingsManager.airMapSettings.userName + } + QGCLabel { + text: { + if(!QGroundControl.airspaceManager.connected) + return qsTr("Not Connected") + switch(_authStatus) { + case AirspaceManager.Unknown: + return qsTr("") + case AirspaceManager.Anonymous: + return qsTr("Anonymous") + case AirspaceManager.Authenticated: + return qsTr("Authenticated") + default: + return qsTr("Authentication Error") + } + } + Layout.rowSpan: 2 + Layout.alignment: Qt.AlignVCenter + } + QGCLabel { text: qsTr("Password:") } + FactTextField { + fact: _passwordFact + width: _editFieldWidth + enabled: _airMapEnabled + visible: _passwordFact.visible + echoMode: TextInput.Password + Layout.fillWidth: true + Layout.minimumWidth: _editFieldWidth + property Fact _passwordFact: QGroundControl.settingsManager.airMapSettings.password + } + Item { + width: 1 + height: 1 + } + Item { + width: 1 + height: 1 + Layout.columnSpan: 3 + } + QGCLabel { + text: qsTr("Forgot Your AirMap Password?") + Layout.alignment: Qt.AlignHCenter + Layout.columnSpan: 3 + } + Item { + width: 1 + height: 1 + Layout.columnSpan: 3 + } + QGCButton { + text: qsTr("Register for an AirMap Account") + Layout.alignment: Qt.AlignHCenter + Layout.columnSpan: 3 + enabled: _airMapEnabled + onClicked: { + Qt.openUrlExternally("https://www.airmap.com"); + } + } + } + } + //----------------------------------------------------------------- + //-- Pilot Profile + Item { + //-- Disabled for now + width: _panelWidth + height: profileLabel.height + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + visible: false // QGroundControl.settingsManager.appSettings.visible + QGCLabel { + id: profileLabel + text: qsTr("Pilot Profile (WIP)") + font.family: ScreenTools.demiboldFontFamily + } + } + Rectangle { + //-- Disabled for now + height: profileGrid.height + (ScreenTools.defaultFontPixelHeight * 2) + width: _panelWidth + color: qgcPal.windowShade + visible: false // QGroundControl.settingsManager.appSettings.visible + 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") } + } + } + //----------------------------------------------------------------- + //-- License (Will this stay here?) + Item { + width: _panelWidth + height: licenseLabel.height + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + visible: QGroundControl.settingsManager.airMapSettings.usePersonalApiKey.visible + QGCLabel { + id: licenseLabel + text: qsTr("License") + font.family: ScreenTools.demiboldFontFamily + } + } + Rectangle { + height: licenseGrid.height + (ScreenTools.defaultFontPixelHeight * 2) + width: _panelWidth + color: qgcPal.windowShade + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + visible: QGroundControl.settingsManager.airMapSettings.usePersonalApiKey.visible + GridLayout { + id: licenseGrid + columns: 2 + columnSpacing: ScreenTools.defaultFontPixelHeight * 2 + rowSpacing: ScreenTools.defaultFontPixelWidth * 0.25 + anchors.centerIn: parent + FactCheckBox { + id: hasPrivateKey + text: qsTr("Personal API Key") + fact: QGroundControl.settingsManager.airMapSettings.usePersonalApiKey + Layout.columnSpan: 2 + } + Item { + width: 1 + height: 1 + visible: hasPrivateKey.checked + Layout.columnSpan: 2 + } + QGCLabel { text: qsTr("API Key:"); visible: hasPrivateKey.checked; } + FactTextField { fact: QGroundControl.settingsManager.airMapSettings.apiKey; width: _editFieldWidth * 2; visible: hasPrivateKey.checked; Layout.fillWidth: true; Layout.minimumWidth: _editFieldWidth * 2; } + QGCLabel { text: qsTr("Client ID:"); visible: hasPrivateKey.checked; } + FactTextField { fact: QGroundControl.settingsManager.airMapSettings.clientID; width: _editFieldWidth * 2; visible: hasPrivateKey.checked; Layout.fillWidth: true; Layout.minimumWidth: _editFieldWidth * 2; } + } + } + //----------------------------------------------------------------- + //-- Flight List + Item { + width: _panelWidth + height: flightListLabel.height + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + visible: QGroundControl.settingsManager.appSettings.visible + QGCLabel { + id: flightListLabel + text: qsTr("Flight List Management") + font.family: ScreenTools.demiboldFontFamily + } + } + Rectangle { + height: flightListButton.height + (ScreenTools.defaultFontPixelHeight * 2) + width: _panelWidth + color: qgcPal.windowShade + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + QGCButton { + id: flightListButton + text: qsTr("Show Flight List") + backRadius: 4 + heightFactor: 0.3333 + showBorder: true + width: ScreenTools.defaultFontPixelWidth * 16 + anchors.centerIn: parent + onClicked: { + panelLoader.sourceComponent = flightList + } + } + } + } + } + Loader { + id: panelLoader + anchors.centerIn: parent + } + } + //--------------------------------------------------------------- + //-- Flight List + Component { + id: flightList + Rectangle { + id: flightListRoot + width: _qgcView.width + height: _qgcView.height + color: qgcPal.window + property var _flightList: QGroundControl.airspaceManager.flightPlan.flightList + property real _mapWidth: ScreenTools.defaultFontPixelWidth * 40 + MouseArea { + anchors.fill: parent + hoverEnabled: true + onWheel: { wheel.accepted = true; } + onPressed: { mouse.accepted = true; } + onReleased: { mouse.accepted = true; } + } + //--------------------------------------------------------- + //-- Flight List + RowLayout { + anchors.fill: parent + TableView { + id: tableView + model: _flightList + selectionMode: SelectionMode.SingleSelection + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + Layout.fillHeight: true + onCurrentRowChanged: { + var o = _flightList.get(tableView.currentRow) + if(o) { + flightArea.path = o.boundingBox + map.fitViewportToMapItems() + } + } + TableViewColumn { + title: qsTr("No") + width: ScreenTools.defaultFontPixelWidth * 3 + horizontalAlignment: Text.AlignHCenter + delegate : Text { + horizontalAlignment: Text.AlignHCenter + text: (styleData.row + 1) + color: tableView.currentRow === styleData.row ? qgcPal.colorBlue : "black" + font.family: ScreenTools.fixedFontFamily + font.pixelSize: ScreenTools.smallFontPointSize + } + } + TableViewColumn { + title: qsTr("Created") + width: ScreenTools.defaultFontPixelWidth * 18 + horizontalAlignment: Text.AlignHCenter + delegate : Text { + horizontalAlignment: Text.AlignHCenter + text: { + var o = _flightList.get(styleData.row) + return o ? o.createdTime : "" + } + color: tableView.currentRow === styleData.row ? qgcPal.colorBlue : "black" + font.family: ScreenTools.fixedFontFamily + font.pixelSize: ScreenTools.smallFontPointSize + } + } + TableViewColumn { + title: qsTr("Flight Start") + width: ScreenTools.defaultFontPixelWidth * 18 + horizontalAlignment: Text.AlignHCenter + delegate : Text { + horizontalAlignment: Text.AlignHCenter + text: { + var o = _flightList.get(styleData.row) + return o ? o.startTime : "" + } + color: tableView.currentRow === styleData.row ? qgcPal.colorBlue : "black" + font.family: ScreenTools.fixedFontFamily + font.pixelSize: ScreenTools.smallFontPointSize + } + } + TableViewColumn { + title: qsTr("Flight End") + width: ScreenTools.defaultFontPixelWidth * 18 + horizontalAlignment: Text.AlignHCenter + delegate : Text { + horizontalAlignment: Text.AlignHCenter + text: { + var o = _flightList.get(styleData.row) + return o ? o.endTime : "" + } + color: tableView.currentRow === styleData.row ? qgcPal.colorBlue : "black" + font.family: ScreenTools.fixedFontFamily + font.pixelSize: ScreenTools.smallFontPointSize + } + } + TableViewColumn { + title: qsTr("State") + width: ScreenTools.defaultFontPixelWidth * 8 + horizontalAlignment: Text.AlignHCenter + delegate : Text { + horizontalAlignment: Text.AlignHCenter + text: { + var o = _flightList.get(styleData.row) + return o ? (o.active ? qsTr("Active") : qsTr("Completed")) : qsTr("Unknown") + } + color: tableView.currentRow === styleData.row ? qgcPal.colorBlue : "black" + font.family: ScreenTools.fixedFontFamily + font.pixelSize: ScreenTools.smallFontPointSize + } + } + } + Item { + width: flightListRoot._mapWidth + height: parent.height + Layout.alignment: Qt.AlignTop | Qt.AlignLeft + QGCLabel { + id: loadingLabel + text: qsTr("Loading Flight List") + visible: QGroundControl.airspaceManager.flightPlan.loadingFlightList + anchors.centerIn: parent + } + QGCColoredImage { + id: busyIndicator + height: ScreenTools.defaultFontPixelHeight * 2.5 + width: height + source: "/qmlimages/MapSync.svg" + sourceSize.height: height + fillMode: Image.PreserveAspectFit + mipmap: true + smooth: true + color: qgcPal.colorGreen + visible: loadingLabel.visible + anchors.top: loadingLabel.bottom + anchors.topMargin: ScreenTools.defaultFontPixelHeight + anchors.horizontalCenter: parent.horizontalCenter + RotationAnimation on rotation { + loops: Animation.Infinite + from: 360 + to: 0 + duration: 740 + running: busyIndicator.visible + } + } + Column { + spacing: ScreenTools.defaultFontPixelHeight * 0.75 + visible: !QGroundControl.airspaceManager.flightPlan.loadingFlightList + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + QGCLabel { + text: qsTr("Flight List") + anchors.horizontalCenter: parent.horizontalCenter + } + Rectangle { + color: qgcPal.window + border.color: qgcPal.globalTheme === QGCPalette.Dark ? Qt.rgba(1,1,1,0.25) : Qt.rgba(0,0,0,0.25) + border.width: 1 + radius: 4 + width: _mapWidth - (ScreenTools.defaultFontPixelWidth * 2) + height: rangeCol.height + (ScreenTools.defaultFontPixelHeight * 2) + Column { + id: rangeCol + anchors.centerIn: parent + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + QGCLabel { + text: qsTr("Range") + anchors.horizontalCenter: parent.horizontalCenter + } + Row { + spacing: ScreenTools.defaultFontPixelWidth * 2 + anchors.horizontalCenter: parent.horizontalCenter + Column { + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + QGCButton { + text: qsTr("From") + backRadius: 4 + heightFactor: 0.3333 + showBorder: true + width: _buttonWidth * 0.5 + onClicked: fromPicker.visible = true + anchors.horizontalCenter: parent.horizontalCenter + } + QGCLabel { + anchors.horizontalCenter: parent.horizontalCenter + text: fromPicker.selectedDate.toLocaleDateString(Qt.locale()) + } + } + Rectangle { + width: 1 + height: parent.height + color: qgcPal.globalTheme === QGCPalette.Dark ? Qt.rgba(1,1,1,0.25) : Qt.rgba(0,0,0,0.25) + } + Column { + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + QGCButton { + text: qsTr("To") + backRadius: 4 + heightFactor: 0.3333 + showBorder: true + width: _buttonWidth * 0.5 + onClicked: toPicker.visible = true + anchors.horizontalCenter: parent.horizontalCenter + } + QGCLabel { + anchors.horizontalCenter: parent.horizontalCenter + text: toPicker.selectedDate.toLocaleDateString(Qt.locale()) + } + } + } + } + } + QGCButton { + text: qsTr("Refresh") + backRadius: 4 + heightFactor: 0.3333 + showBorder: true + width: _buttonWidth + enabled: true + anchors.horizontalCenter: parent.horizontalCenter + onClicked: { + var start = fromPicker.selectedDate + var end = toPicker.selectedDate + start.setHours(0,0,0,0) + end.setHours(23,59,59,0) + QGroundControl.airspaceManager.flightPlan.loadFlightList(start, end) + } + } + QGCButton { + text: qsTr("End Selected") + backRadius: 4 + heightFactor: 0.3333 + showBorder: true + width: _buttonWidth + enabled: { + var o = _flightList.get(tableView.currentRow) + return o && o.active + } + anchors.horizontalCenter: parent.horizontalCenter + onClicked: { + endFlightDialog.visible = true + } + MessageDialog { + id: endFlightDialog + visible: false + icon: StandardIcon.Warning + standardButtons: StandardButton.Yes | StandardButton.No + title: qsTr("End Flight") + text: qsTr("Confirm ending active flight?") + onYes: { + var o = _flightList.get(tableView.currentRow) + if(o) { + QGroundControl.airspaceManager.flightPlan.endFlight(o.flightID) + } + endFlightDialog.visible = false + } + onNo: { + endFlightDialog.visible = false + } + } + } + QGCButton { + text: qsTr("Close") + backRadius: 4 + heightFactor: 0.3333 + showBorder: true + width: _buttonWidth + anchors.horizontalCenter: parent.horizontalCenter + onClicked: { + panelLoader.sourceComponent = null + } + } + QGCLabel { + text: _flightList.count > 0 ? _flightList.count + qsTr(" Flights Loaded") : qsTr("No Flights Loaded") + anchors.horizontalCenter: parent.horizontalCenter + } + QGCLabel { + text: qsTr("A maximum of 250 flights were loaded") + color: qgcPal.colorOrange + font.pixelSize: ScreenTools.smallFontPointSize + visible: _flightList.count >= 250 + anchors.horizontalCenter: parent.horizontalCenter + } + } + QGCLabel { + text: qsTr("Flight Area ") + (tableView.currentRow + 1) + visible: !QGroundControl.airspaceManager.flightPlan.loadingFlightList && _flightList.count > 0 && tableView.currentRow >= 0 + anchors.bottom: map.top + anchors.bottomMargin: ScreenTools.defaultFontPixelHeight * 0.25 + anchors.horizontalCenter: parent.horizontalCenter + } + Map { + id: map + width: ScreenTools.defaultFontPixelWidth * 40 + height: width * 0.6666 + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + zoomLevel: QGroundControl.flightMapZoom + center: QGroundControl.flightMapPosition + gesture.acceptedGestures: MapGestureArea.PinchGesture + plugin: Plugin { name: "QGroundControl" } + visible: !QGroundControl.airspaceManager.flightPlan.loadingFlightList && _flightList.count > 0 && tableView.currentRow >= 0 + function updateActiveMapType() { + var settings = QGroundControl.settingsManager.flightMapSettings + var fullMapName = settings.mapProvider.enumStringValue + " " + settings.mapType.enumStringValue + for (var i = 0; i < map.supportedMapTypes.length; i++) { + if (fullMapName === map.supportedMapTypes[i].name) { + map.activeMapType = map.supportedMapTypes[i] + return + } + } + } + MapPolygon { + id: flightArea + color: Qt.rgba(1,0,0,0.2) + border.color: Qt.rgba(1,1,1,0.65) + } + Component.onCompleted: { + updateActiveMapType() + } + Connections { + target: QGroundControl.settingsManager.flightMapSettings.mapType + onRawValueChanged: updateActiveMapType() + } + + Connections { + target: QGroundControl.settingsManager.flightMapSettings.mapProvider + onRawValueChanged: updateActiveMapType() + } + + } + Calendar { + id: fromPicker + anchors.centerIn: parent + visible: false; + onClicked: { + visible = false; + } + } + Calendar { + id: toPicker + anchors.centerIn: parent + visible: false; + minimumDate: fromPicker.selectedDate + onClicked: { + visible = false; + } + } + } + } + } + } +} diff --git a/src/Airmap/AirspaceControl.qml b/src/Airmap/AirspaceControl.qml new file mode 100644 index 0000000000000000000000000000000000000000..ae77edbfdcda3a685b1a0286b543b2ba6057284b --- /dev/null +++ b/src/Airmap/AirspaceControl.qml @@ -0,0 +1,700 @@ +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 QtGraphicalEffects 1.0 + +import QGroundControl 1.0 +import QGroundControl.Airmap 1.0 +import QGroundControl.Airspace 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.FactControls 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.Vehicle 1.0 + +Item { + id: _root + width: parent.width + height: _colapsed ? colapsedRect.height : expandedRect.height + + property bool showColapse: true + property bool planView: true + + property color _airspaceColor: _validAdvisories ? getAispaceColor(QGroundControl.airspaceManager.advisories.airspaceColor) : _colorGray + property bool _validRules: QGroundControl.airspaceManager.connected && QGroundControl.airspaceManager.ruleSets.valid + property bool _validAdvisories: QGroundControl.airspaceManager.connected && QGroundControl.airspaceManager.advisories.valid + property color _textColor: qgcPal.text + property bool _colapsed: !QGroundControl.airspaceManager.airspaceVisible || !QGroundControl.airspaceManager.connected + property int _flightPermit: QGroundControl.airspaceManager.flightPlan.flightPermitStatus + property bool _dirty: false + + 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 _colorLightGray: "#a0a0a0" + readonly property color _colorMidBrown: "#3a322f" + readonly property color _colorYellow: "#d7c61d" + readonly property color _colorWhite: "#ffffff" + readonly property color _colorRed: "#aa1200" + readonly property color _colorGreen: "#125f00" + + QGCPalette { + id: qgcPal + colorGroupEnabled: enabled + } + + function getAispaceColor(color) { + if(color === AirspaceAdvisoryProvider.Green) return _colorGreen; + if(color === AirspaceAdvisoryProvider.Yellow) return _colorYellow; + if(color === AirspaceAdvisoryProvider.Orange) return _colorOrange; + if(color === AirspaceAdvisoryProvider.Red) return _colorRed; + return _colorGray; + } + + function hasBriefRules() { + if(QGroundControl.airspaceManager.flightPlan.rulesViolation.count > 0) + return true; + if(QGroundControl.airspaceManager.flightPlan.rulesInfo.count > 0) + return true; + if(QGroundControl.airspaceManager.flightPlan.rulesReview.count > 0) + return true; + if(QGroundControl.airspaceManager.flightPlan.rulesFollowing.count > 0) + return true; + return false; + } + + on_AirspaceColorChanged: { + if(_validAdvisories) { + if(QGroundControl.airspaceManager.advisories.airspaceColor === AirspaceAdvisoryProvider.Yellow) { + _textColor = "#000000" + return + } + } + _textColor = _colorWhite + } + + //--------------------------------------------------------------- + //-- Colapsed State + Rectangle { + id: colapsedRect + width: parent.width + height: _colapsed ? colapsedRow.height + ScreenTools.defaultFontPixelHeight : 0 + color: QGroundControl.airspaceManager.connected ? (_validAdvisories ? getAispaceColor(QGroundControl.airspaceManager.advisories.airspaceColor) : _colorGray) : _colorGray + 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: _textColor + anchors.verticalCenter: parent.verticalCenter + } + Column { + spacing: 0 + anchors.verticalCenter: parent.verticalCenter + QGCLabel { + text: qsTr("Airspace") + color: _textColor + } + QGCLabel { + text: _validAdvisories ? QGroundControl.airspaceManager.advisories.advisories.count + qsTr(" Advisories") : "" + color: _textColor + visible: _validAdvisories + font.pointSize: ScreenTools.smallFontPointSize + } + } + Item { + width: ScreenTools.defaultFontPixelWidth + height: 1 + } + AirspaceWeather { + iconHeight: ScreenTools.defaultFontPixelHeight * 2 + visible: QGroundControl.airspaceManager.weatherInfo.valid && QGroundControl.airspaceManager.connected + contentColor: _textColor + anchors.verticalCenter: parent.verticalCenter + } + QGCLabel { + text: qsTr("Not Connected") + color: qgcPal.text + visible: !QGroundControl.airspaceManager.connected + anchors.verticalCenter: parent.verticalCenter + } + } + QGCColoredImage { + width: height + height: ScreenTools.defaultFontPixelWidth * 2.5 + sourceSize.height: height + source: "qrc:/airmap/expand.svg" + color: _textColor + fillMode: Image.PreserveAspectFit + visible: QGroundControl.airspaceManager.connected + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth + anchors.verticalCenter: parent.verticalCenter + } + MouseArea { + anchors.fill: parent + enabled: QGroundControl.airspaceManager.connected + onClicked: { + QGroundControl.airspaceManager.airspaceVisible = true + } + } + } + //--------------------------------------------------------------- + //-- Expanded State + Rectangle { + id: expandedRect + width: parent.width + height: !_colapsed ? expandedCol.height + ScreenTools.defaultFontPixelHeight : 0 + color: _validAdvisories ? getAispaceColor(QGroundControl.airspaceManager.advisories.airspaceColor) : _colorGray + radius: _radius + visible: !_colapsed + MouseArea { + anchors.fill: parent + onWheel: { wheel.accepted = true; } + onPressed: { mouse.accepted = true; } + onReleased: { mouse.accepted = true; } + } + 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: _textColor + fillMode: Image.PreserveAspectFit + anchors.verticalCenter: parent.verticalCenter + } + Column { + spacing: 0 + anchors.verticalCenter: parent.verticalCenter + QGCLabel { + text: qsTr("Airspace") + color: _textColor + } + QGCLabel { + text: _validAdvisories ? QGroundControl.airspaceManager.advisories.advisories.count + qsTr(" Advisories") : "" + color: _textColor + visible: _validAdvisories + font.pointSize: ScreenTools.smallFontPointSize + } + } + Item { + width: ScreenTools.defaultFontPixelWidth + height: 1 + } + AirspaceWeather { + visible: QGroundControl.airspaceManager.weatherInfo.valid && showColapse + contentColor: _textColor + anchors.verticalCenter: parent.verticalCenter + } + } + QGCColoredImage { + width: height + height: ScreenTools.defaultFontPixelWidth * 2.5 + sourceSize.height: height + source: "qrc:/airmap/colapse.svg" + color: _textColor + visible: showColapse + fillMode: Image.PreserveAspectFit + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth + anchors.verticalCenter: parent.verticalCenter + MouseArea { + anchors.fill: parent + enabled: showColapse + onClicked: QGroundControl.airspaceManager.airspaceVisible = false + } + } + AirspaceWeather { + visible: QGroundControl.airspaceManager.weatherInfo.valid && !showColapse + contentColor: _textColor + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth + anchors.verticalCenter: parent.verticalCenter + } + } + //-- 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.25 + 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("Advisories based on the selected rules.") + color: _colorWhite + anchors.horizontalCenter: parent.horizontalCenter + font.pointSize: ScreenTools.smallFontPointSize + } + Item { width: 1; height: ScreenTools.defaultFontPixelHeight * 0.125; } + GridLayout { + columns: 2 + anchors.left: parent.left + anchors.right: parent.right + Rectangle { + width: regButton.height + height: width + radius: 2 + color: _colorGray + Layout.alignment: Qt.AlignVCenter + QGCColoredImage { + id: pencilIcon + width: height + height: parent.height * 0.5 + sourceSize.height: height + source: "qrc:/airmap/pencil.svg" + color: _colorWhite + fillMode: Image.PreserveAspectFit + anchors.centerIn: parent + MouseArea { + anchors.fill: parent + onClicked: { + rootLoader.sourceComponent = ruleSelector + mainWindow.disableToolbar() + } + } + } + } + Rectangle { + id: regButton + height: ScreenTools.defaultFontPixelHeight * 1.5 + radius: 2 + color: _colorMidBrown + Layout.fillWidth: true + QGCLabel { + id: regLabel + text: _validRules ? QGroundControl.airspaceManager.ruleSets.selectedRuleSets : qsTr("None") + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + color: _colorWhite + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.verticalCenter: parent.verticalCenter + } + } + } + } + } + Flickable { + clip: true + height: ScreenTools.defaultFontPixelHeight * 8 + contentHeight: advisoryCol.height + flickableDirection: Flickable.VerticalFlick + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.right: parent.right + anchors.rightMargin:ScreenTools.defaultFontPixelWidth * 0.5 + Column { + id: advisoryCol + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + anchors.right: parent.right + anchors.left: parent.left + Repeater { + model: _validAdvisories ? QGroundControl.airspaceManager.advisories.advisories : [] + delegate: AirspaceRegulation { + regTitle: object.typeStr + regText: object.name + regColor: getAispaceColor(object.color) + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth + } + } + } + } + } + } + //-- Footer + QGCButton { + text: planView ? qsTr("File Flight Plan") : qsTr("Flight Brief") + backRadius: 4 + heightFactor: 0.3333 + showBorder: true + width: ScreenTools.defaultFontPixelWidth * 16 + visible: _flightPermit !== AirspaceFlightPlanProvider.PermitNone + anchors.horizontalCenter: parent.horizontalCenter + onClicked: { + rootLoader.sourceComponent = planView ? flightDetails : flightBrief + mainWindow.disableToolbar() + } + } + QGCLabel { + text: qsTr("Powered by AIRMAP") + color: _textColor + font.pointSize: ScreenTools.smallFontPointSize + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + //--------------------------------------------------------------- + //-- Rule Selector + Component { + id: ruleSelector + Rectangle { + width: mainWindow.width + height: mainWindow.height + color: Qt.rgba(0,0,0,0.1) + MouseArea { + anchors.fill: parent + onWheel: { wheel.accepted = true; } + onClicked: { + mainWindow.enableToolbar() + rootLoader.sourceComponent = null + } + } + Rectangle { + id: ruleSelectorShadow + anchors.fill: ruleSelectorRect + radius: ruleSelectorRect.radius + color: qgcPal.window + visible: false + } + DropShadow { + anchors.fill: ruleSelectorShadow + visible: ruleSelectorRect.visible + horizontalOffset: 4 + verticalOffset: 4 + radius: 32.0 + samples: 65 + color: Qt.rgba(0,0,0,0.75) + source: ruleSelectorShadow + } + Rectangle { + id: ruleSelectorRect + x: 0 + y: 0 + color: qgcPal.window + width: rulesCol.width + ScreenTools.defaultFontPixelWidth + height: rulesCol.height + ScreenTools.defaultFontPixelHeight + radius: ScreenTools.defaultFontPixelWidth + Column { + id: rulesCol + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + anchors.centerIn: parent + //-- Regulations + Rectangle { + color: qgcPal.windowShade + height: rulesTitle.height + ScreenTools.defaultFontPixelHeight + width: parent.width * 0.95 + radius: _radius + anchors.horizontalCenter: parent.horizontalCenter + QGCLabel { + id: rulesTitle + text: qsTr("Airspace Regulation Options") + anchors.centerIn: parent + } + } + Flickable { + clip: true + width: ScreenTools.defaultFontPixelWidth * 30 + height: ScreenTools.defaultFontPixelHeight * 14 + contentHeight: rulesetCol.height + flickableDirection: Flickable.VerticalFlick + Column { + id: rulesetCol + spacing: ScreenTools.defaultFontPixelHeight * 0.25 + anchors.right: parent.right + anchors.left: parent.left + anchors.rightMargin: ScreenTools.defaultFontPixelWidth * 2 + anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 2 + Item { + width: 1 + height: 1 + } + ExclusiveGroup { id: rulesGroup } + QGCLabel { + text: qsTr("PICK ONE REGULATION") + font.pointSize: ScreenTools.smallFontPointSize + anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 2 + } + Repeater { + model: _validRules ? QGroundControl.airspaceManager.ruleSets.ruleSets : [] + delegate: RuleSelector { + visible: object.selectionType === AirspaceRuleSet.Pickone + rule: object + exclusiveGroup: rulesGroup + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth + } + } + Item { + width: 1 + height: 1 + } + QGCLabel { + text: qsTr("OPTIONAL") + font.pointSize: ScreenTools.smallFontPointSize + anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 2 + } + Repeater { + model: _validRules ? QGroundControl.airspaceManager.ruleSets.ruleSets : [] + delegate: RuleSelector { + visible: object.selectionType === AirspaceRuleSet.Optional + rule: object + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth + } + } + Item { + width: 1 + height: 1 + } + QGCLabel { + text: qsTr("REQUIRED") + font.pointSize: ScreenTools.smallFontPointSize + anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 2 + } + Repeater { + model: _validRules ? QGroundControl.airspaceManager.ruleSets.ruleSets : [] + delegate: RuleSelector { + visible: object.selectionType === AirspaceRuleSet.Required + rule: object + required: true + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth + anchors.left: parent.left + anchors.leftMargin: ScreenTools.defaultFontPixelWidth + } + } + } + } + } + } + //-- Arrow + QGCColoredImage { + id: arrowIconShadow + anchors.fill: arrowIcon + sourceSize.height: height + source: "qrc:/airmap/right-arrow.svg" + color: qgcPal.window + visible: false + } + DropShadow { + anchors.fill: arrowIconShadow + visible: ruleSelectorRect.visible && qgcPal.globalTheme === QGCPalette.Dark + horizontalOffset: 4 + verticalOffset: 4 + radius: 32.0 + samples: 65 + color: Qt.rgba(0,0,0,0.75) + source: arrowIconShadow + } + QGCColoredImage { + id: arrowIcon + width: height + height: ScreenTools.defaultFontPixelHeight * 2 + sourceSize.height: height + source: "qrc:/airmap/right-arrow.svg" + color: ruleSelectorRect.color + anchors.left: ruleSelectorRect.right + anchors.top: ruleSelectorRect.top + anchors.topMargin: (ScreenTools.defaultFontPixelHeight * 4) - (height * 0.5) + (pencilIcon.height * 0.5) + } + Component.onCompleted: { + mainWindow.disableToolbar() + var target = mainWindow.mapFromItem(pencilIcon, 0, 0) + ruleSelectorRect.x = target.x - ruleSelectorRect.width - (ScreenTools.defaultFontPixelWidth * 7) + ruleSelectorRect.y = target.y - (ScreenTools.defaultFontPixelHeight * 4) + } + } + } + //--------------------------------------------------------------- + //-- Flight Details + Component { + id: flightDetails + Rectangle { + id: flightDetailsRoot + width: mainWindow.width + height: mainWindow.height + color: Qt.rgba(0,0,0,0.1) + property real baseHeight: ScreenTools.defaultFontPixelHeight * 22 + property real baseWidth: ScreenTools.defaultFontPixelWidth * 40 + Component.onCompleted: { + _dirty = false + mainWindow.disableToolbar() + } + MouseArea { + anchors.fill: parent + hoverEnabled: true + onWheel: { wheel.accepted = true; } + onPressed: { mouse.accepted = true; } + onReleased: { mouse.accepted = true; } + } + Rectangle { + id: flightDetailsShadow + anchors.fill: flightDetailsRect + radius: flightDetailsRect.radius + color: qgcPal.window + visible: false + } + DropShadow { + anchors.fill: flightDetailsShadow + visible: flightDetailsRect.visible + horizontalOffset: 4 + verticalOffset: 4 + radius: 32.0 + samples: 65 + color: Qt.rgba(0,0,0,0.75) + source: flightDetailsShadow + } + Rectangle { + id: flightDetailsRect + color: qgcPal.window + width: flDetailsRow.width + (ScreenTools.defaultFontPixelWidth * 4) + height: flDetailsRow.height + (ScreenTools.defaultFontPixelHeight * 2) + radius: ScreenTools.defaultFontPixelWidth + anchors.centerIn: parent + Row { + id: flDetailsRow + spacing: ScreenTools.defaultFontPixelWidth + anchors.centerIn: parent + //--------------------------------------------------------- + //-- Flight Details + FlightDetails { + baseHeight: flightDetailsRoot.baseHeight + baseWidth: flightDetailsRoot.baseWidth + } + //--------------------------------------------------------- + //-- Divider + Rectangle { + color: qgcPal.text + width: 1 + height: parent.height + opacity: 0.25 + anchors.verticalCenter: parent.verticalCenter + } + //--------------------------------------------------------- + //-- Flight Brief + FlightBrief { + baseHeight: flightDetailsRoot.baseHeight + baseWidth: flightDetailsRoot.baseWidth + } + } + } + } + } + //--------------------------------------------------------------- + //-- Flight Brief + Component { + id: flightBrief + Rectangle { + id: flightBriefRoot + width: mainWindow.width + height: mainWindow.height + color: Qt.rgba(0,0,0,0.1) + property real baseHeight: ScreenTools.defaultFontPixelHeight * 22 + property real baseWidth: ScreenTools.defaultFontPixelWidth * 40 + Component.onCompleted: { + _dirty = false + mainWindow.disableToolbar() + } + MouseArea { + anchors.fill: parent + hoverEnabled: true + onWheel: { wheel.accepted = true; } + onPressed: { mouse.accepted = true; } + onReleased: { mouse.accepted = true; } + } + Rectangle { + id: flightBriefShadow + anchors.fill: flightBriefRect + radius: flightBriefRect.radius + color: qgcPal.window + visible: false + } + DropShadow { + anchors.fill: flightBriefShadow + visible: flightBriefRect.visible + horizontalOffset: 4 + verticalOffset: 4 + radius: 32.0 + samples: 65 + color: Qt.rgba(0,0,0,0.75) + source: flightBriefShadow + } + Rectangle { + id: flightBriefRect + color: qgcPal.window + width: flightBriedItem.width + (ScreenTools.defaultFontPixelWidth * 4) + height: flightBriedItem.height + (ScreenTools.defaultFontPixelHeight * 2) + radius: ScreenTools.defaultFontPixelWidth + anchors.centerIn: parent + //--------------------------------------------------------- + //-- Flight Brief + FlightBrief { + id: flightBriedItem + baseHeight: flightBriefRoot.baseHeight + baseWidth: flightBriefRoot.baseWidth + anchors.centerIn: parent + } + } + } + } +} 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/AirspaceWeather.qml b/src/Airmap/AirspaceWeather.qml new file mode 100644 index 0000000000000000000000000000000000000000..dbe91a573a079af183ecc8c0f54ca4699ad7e9a4 --- /dev/null +++ b/src/Airmap/AirspaceWeather.qml @@ -0,0 +1,44 @@ +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.Airmap 1.0 +import QGroundControl.Airspace 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.SettingsManager 1.0 + +Item { + height: _valid ? weatherRow.height : 0 + width: _valid ? weatherRow.width : 0 + property color contentColor: "#ffffff" + property var iconHeight: ScreenTools.defaultFontPixelHeight * 2 + property bool _valid: QGroundControl.airspaceManager.weatherInfo.valid + property bool _celcius: QGroundControl.settingsManager.unitsSettings.temperatureUnits.rawValue === UnitsSettings.TemperatureUnitsCelsius + property int _tempC: _valid ? QGroundControl.airspaceManager.weatherInfo.temperature : 0 + property string _tempS: (_celcius ? _tempC : _tempC * 1.8 + 32).toFixed(0) + (_celcius ? "°C" : "°F") + Row { + id: weatherRow + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + QGCColoredImage { + width: height + height: iconHeight + sourceSize.height: height + source: _valid ? QGroundControl.airspaceManager.weatherInfo.icon : "" + color: contentColor + visible: _valid + fillMode: Image.PreserveAspectFit + anchors.verticalCenter: parent.verticalCenter + } + QGCLabel { + text: _tempS + color: contentColor + visible: _valid + anchors.verticalCenter: parent.verticalCenter + } + } +} diff --git a/src/Airmap/ComplianceRules.qml b/src/Airmap/ComplianceRules.qml new file mode 100644 index 0000000000000000000000000000000000000000..fe8d4aec604ddce49d484b38e411e299bccc2751 --- /dev/null +++ b/src/Airmap/ComplianceRules.qml @@ -0,0 +1,124 @@ +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 +import QGroundControl.SettingsManager 1.0 + +Item { + id: _root + height: checked ? (header.height + content.height) : header.height + property var rules: null + property color color: "white" + property alias text: title.text + property bool checked: false + property ExclusiveGroup exclusiveGroup: null + onExclusiveGroupChanged: { + if (exclusiveGroup) { + exclusiveGroup.bindCheckable(_root) + } + } + QGCPalette { + id: qgcPal + colorGroupEnabled: enabled + } + Rectangle { + id: header + height: ScreenTools.defaultFontPixelHeight * 2 + color: qgcPal.windowShade + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + } + Row { + spacing: ScreenTools.defaultFontPixelWidth * 2 + anchors.fill: header + Rectangle { + height: parent.height + width: ScreenTools.defaultFontPixelWidth * 0.75 + color: _root.color + } + QGCLabel { + id: title + anchors.verticalCenter: parent.verticalCenter + } + } + QGCColoredImage { + source: checked ? "qrc:/airmap/colapse.svg" : "qrc:/airmap/expand.svg" + height: ScreenTools.defaultFontPixelHeight + width: height + color: qgcPal.text + fillMode: Image.PreserveAspectFit + sourceSize.height: height + anchors.right: header.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth + anchors.verticalCenter: header.verticalCenter + } + MouseArea { + anchors.fill: header + onClicked: { + _root.checked = !_root.checked + } + } + Rectangle { + id: content + color: qgcPal.window + visible: checked + height: ScreenTools.defaultFontPixelHeight * 10 + anchors.top: header.bottom + anchors.right: parent.right + anchors.left: parent.left + anchors.margins: ScreenTools.defaultFontPixelWidth + Flickable { + clip: true + anchors.fill: parent + contentHeight: rulesetCol.height + flickableDirection: Flickable.VerticalFlick + Column { + id: rulesetCol + spacing: ScreenTools.defaultFontPixelHeight * 0.25 + anchors.right: parent.right + anchors.left: parent.left + Repeater { + model: _root.rules ? _root.rules : [] + delegate: Item { + height: ruleCol.height + anchors.right: parent.right + anchors.left: parent.left + anchors.margins: ScreenTools.defaultFontPixelWidth + Column { + id: ruleCol + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + anchors.right: parent.right + anchors.left: parent.left + Item { width: 1; height: ScreenTools.defaultFontPixelHeight * 0.25; } + QGCLabel { + text: object.shortText !== "" ? object.shortText : qsTr("Rule") + anchors.right: parent.right + anchors.left: parent.left + wrapMode: Text.WordWrap + } + QGCLabel { + text: object.description + visible: object.description !== "" + font.pointSize: ScreenTools.smallFontPointSize + anchors.right: parent.right + anchors.left: parent.left + wrapMode: Text.WordWrap + anchors.rightMargin: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 0.5 + } + } + } + } + Item { width: 1; height: ScreenTools.defaultFontPixelHeight * 0.25; } + } + } + } +} diff --git a/src/Airmap/FlightBrief.qml b/src/Airmap/FlightBrief.qml new file mode 100644 index 0000000000000000000000000000000000000000..75f7ef13eb01ce780c2e64a93fd677209bcf1305 --- /dev/null +++ b/src/Airmap/FlightBrief.qml @@ -0,0 +1,236 @@ +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.Airmap 1.0 +import QGroundControl.Airspace 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.SettingsManager 1.0 + +Item { + id: _root + implicitHeight: briefRootCol.height + implicitWidth: briefRootCol.width + property real baseHeight: ScreenTools.defaultFontPixelHeight * 22 + property real baseWidth: ScreenTools.defaultFontPixelWidth * 40 + Column { + id: briefRootCol + spacing: ScreenTools.defaultFontPixelHeight * 0.25 + Rectangle { + color: qgcPal.windowShade + anchors.right: parent.right + anchors.left: parent.left + height: briefLabel.height + ScreenTools.defaultFontPixelHeight + QGCLabel { + id: briefLabel + text: qsTr("Flight Brief") + font.pointSize: ScreenTools.mediumFontPointSize + font.family: ScreenTools.demiboldFontFamily + anchors.centerIn: parent + } + } + Item { width: 1; height: ScreenTools.defaultFontPixelHeight * 0.5; } + Flickable { + clip: true + width: baseWidth + height: baseHeight - buttonRow.height - ScreenTools.defaultFontPixelHeight + contentHeight: briefCol.height + flickableDirection: Flickable.VerticalFlick + Column { + id: briefCol + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + anchors.right: parent.right + anchors.left: parent.left + QGCLabel { + text: qsTr("Authorizations") + } + Rectangle { + color: qgcPal.windowShade + anchors.right: parent.right + anchors.left: parent.left + height: authCol.height + ScreenTools.defaultFontPixelHeight + Column { + id: authCol + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.right: parent.right + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + //-- Actual Authorization from some jurisdiction + Repeater { + visible: QGroundControl.airspaceManager.flightPlan.authorizations.count > 0 + model: QGroundControl.airspaceManager.flightPlan.authorizations + Column { + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + anchors.right: parent.right + anchors.left: parent.left + QGCLabel { + text: object.name + font.family: ScreenTools.demiboldFontFamily + anchors.horizontalCenter: parent.horizontalCenter + } + Rectangle { + anchors.right: parent.right + anchors.left: parent.left + height: label.height + (ScreenTools.defaultFontPixelHeight * 0.5) + color: { + if(object.status === AirspaceFlightAuthorization.Pending) + return _colorOrange + if(object.status === AirspaceFlightAuthorization.Accepted || object.status === AirspaceFlightAuthorization.AcceptedOnSubmission) + return _colorGreen + if(object.status === AirspaceFlightAuthorization.Rejected || object.status === AirspaceFlightAuthorization.RejectedOnSubmission) + return _colorRed + return _colorGray + } + QGCLabel { + id: label + color: _colorWhite + text: { + if(object.status === AirspaceFlightAuthorization.Pending) + return qsTr("Authorization Pending") + if(object.status === AirspaceFlightAuthorization.Accepted || object.status === AirspaceFlightAuthorization.AcceptedOnSubmission) + return qsTr("Authorization Accepted") + if(object.status === AirspaceFlightAuthorization.Rejected || object.status === AirspaceFlightAuthorization.RejectedOnSubmission) + return qsTr("Authorization Rejected") + return qsTr("Authorization Unknown") + } + anchors.centerIn: parent + } + } + } + } + //-- Implied Authorization from no jurisdiction + Rectangle { + anchors.right: parent.right + anchors.left: parent.left + height: noAuthLabel.height + (ScreenTools.defaultFontPixelHeight * 0.5) + visible: QGroundControl.airspaceManager.flightPlan.authorizations.count < 1 + color: { + if(_flightPermit === AirspaceFlightPlanProvider.PermitPending) + return _colorOrange + if(_flightPermit === AirspaceFlightPlanProvider.PermitAccepted || _flightPermit === AirspaceFlightPlanProvider.PermitNotRequired) + return _colorGreen + if(_flightPermit === AirspaceFlightPlanProvider.PermitRejected) + return _colorRed + return _colorGray + } + QGCLabel { + id: noAuthLabel + color: _colorWhite + text: { + if(_flightPermit === AirspaceFlightPlanProvider.PermitPending) + return qsTr("Authorization Pending") + if(_flightPermit === AirspaceFlightPlanProvider.PermitAccepted) + return qsTr("Authorization Accepted") + if(_flightPermit === AirspaceFlightPlanProvider.PermitRejected) + return qsTr("Authorization Rejected") + if(_flightPermit === AirspaceFlightPlanProvider.PermitNotRequired) + return qsTr("Authorization Not Required") + return qsTr("Authorization Unknown") + } + anchors.centerIn: parent + } + } + } + } + Item { width: 1; height: ScreenTools.defaultFontPixelHeight * 0.25; } + QGCLabel { + text: qsTr("Rules & Compliance") + visible: hasBriefRules() + } + ExclusiveGroup { id: ruleGroup } + ComplianceRules { + text: qsTr("Rules you may be violating") + rules: violationRules + visible: violationRules && violationRules.count + color: _colorRed + exclusiveGroup: ruleGroup + anchors.right: parent.right + anchors.left: parent.left + property var violationRules: QGroundControl.airspaceManager.flightPlan.rulesViolation + } + ComplianceRules { + text: qsTr("Rules needing more information") + rules: infoRules + color: _colorOrange + visible: infoRules && infoRules.count + exclusiveGroup: ruleGroup + anchors.right: parent.right + anchors.left: parent.left + property var infoRules: QGroundControl.airspaceManager.flightPlan.rulesInfo + } + ComplianceRules { + text: qsTr("Rules you should review") + rules: reviewRules + color: _colorYellow + visible: reviewRules && reviewRules.count + exclusiveGroup: ruleGroup + anchors.right: parent.right + anchors.left: parent.left + property var reviewRules: QGroundControl.airspaceManager.flightPlan.rulesReview + } + ComplianceRules { + text: qsTr("Rules you are following") + rules: followRules + color: _colorGreen + visible: followRules && followRules.count + exclusiveGroup: ruleGroup + anchors.right: parent.right + anchors.left: parent.left + property var followRules: QGroundControl.airspaceManager.flightPlan.rulesFollowing + } + } + } + //------------------------------------------------------------- + //-- File Flight Plan or Close + Item { width: 1; height: ScreenTools.defaultFontPixelHeight; } + Row { + id: buttonRow + spacing: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + QGCButton { + text: qsTr("Update Plan") + backRadius: 4 + heightFactor: 0.3333 + showBorder: true + enabled: _flightPermit !== AirspaceFlightPlanProvider.PermitNone && _dirty + visible: planView + width: ScreenTools.defaultFontPixelWidth * 12 + onClicked: { + _dirty = false + QGroundControl.airspaceManager.flightPlan.updateFlightPlan() + } + } + QGCButton { + text: qsTr("Submit Plan") + backRadius: 4 + heightFactor: 0.3333 + showBorder: true + enabled: _flightPermit === AirspaceFlightPlanProvider.PermitAccepted || _flightPermit === AirspaceFlightPlanProvider.PermitNotRequired + width: ScreenTools.defaultFontPixelWidth * 12 + visible: planView + onClicked: { + QGroundControl.airspaceManager.flightPlan.submitFlightPlan() + mainWindow.enableToolbar() + rootLoader.sourceComponent = null + } + } + QGCButton { + text: qsTr("Close") + backRadius: 4 + heightFactor: 0.3333 + showBorder: true + width: ScreenTools.defaultFontPixelWidth * 12 + onClicked: { + mainWindow.enableToolbar() + rootLoader.sourceComponent = null + } + } + } + } +} diff --git a/src/Airmap/FlightDetails.qml b/src/Airmap/FlightDetails.qml new file mode 100644 index 0000000000000000000000000000000000000000..aecacf34206d161c013b0040dc74603da97f8799 --- /dev/null +++ b/src/Airmap/FlightDetails.qml @@ -0,0 +1,215 @@ +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.Airmap 1.0 +import QGroundControl.Airspace 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.SettingsManager 1.0 + +Item { + id: _root + implicitHeight: detailCol.height + implicitWidth: detailCol.width + property real baseHeight: ScreenTools.defaultFontPixelHeight * 22 + property real baseWidth: ScreenTools.defaultFontPixelWidth * 40 + Column { + id: detailCol + spacing: ScreenTools.defaultFontPixelHeight * 0.25 + Rectangle { + color: qgcPal.windowShade + anchors.right: parent.right + anchors.left: parent.left + height: detailsLabel.height + ScreenTools.defaultFontPixelHeight + QGCLabel { + id: detailsLabel + text: qsTr("Flight Details") + font.pointSize: ScreenTools.mediumFontPointSize + font.family: ScreenTools.demiboldFontFamily + anchors.centerIn: parent + } + } + Item { width: 1; height: ScreenTools.defaultFontPixelHeight * 0.5; } + Flickable { + clip: true + width: baseWidth + height: baseHeight + contentHeight: flContextCol.height + flickableDirection: Flickable.VerticalFlick + Column { + id: flContextCol + spacing: ScreenTools.defaultFontPixelHeight * 0.25 + anchors.right: parent.right + anchors.left: parent.left + QGCLabel { + text: qsTr("Flight Date & Time") + } + Rectangle { + id: dateRect + color: qgcPal.windowShade + anchors.right: parent.right + anchors.left: parent.left + height: datePickerCol.height + (ScreenTools.defaultFontPixelHeight * 2) + Column { + id: datePickerCol + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.right: parent.right + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + RowLayout { + spacing: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.right: parent.right + anchors.left: parent.left + QGCButton { + text: qsTr("Now") + checked: QGroundControl.airspaceManager.flightPlan.flightStartsNow + onClicked: { + _dirty = true + QGroundControl.airspaceManager.flightPlan.flightStartsNow = !QGroundControl.airspaceManager.flightPlan.flightStartsNow + } + } + QGCButton { + text: { + var nowTime = new Date() + var setTime = QGroundControl.airspaceManager.flightPlan.flightStartTime + if(setTime.setHours(0,0,0,0) === nowTime.setHours(0,0,0,0)) { + return qsTr("Today") + } else { + return setTime.toLocaleDateString(Qt.locale()) + } + } + Layout.fillWidth: true + enabled: !QGroundControl.airspaceManager.flightPlan.flightStartsNow + iconSource: "qrc:/airmap/expand.svg" + onClicked: { + _dirty = true + datePicker.visible = true + } + } + } + QGCLabel { + text: qsTr("Flight Start Time") + } + Item { + anchors.right: parent.right + anchors.left: parent.left + height: timeSlider.height + visible: !QGroundControl.airspaceManager.flightPlan.flightStartsNow + QGCSlider { + id: timeSlider + width: parent.width - timeLabel.width - ScreenTools.defaultFontPixelWidth + stepSize: 1 + enabled: !QGroundControl.airspaceManager.flightPlan.flightStartsNow + minimumValue: 0 + maximumValue: 95 // 96 blocks of 15 minutes in 24 hours + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + onValueChanged: { + _dirty = true + var today = QGroundControl.airspaceManager.flightPlan.flightStartTime + today.setHours(Math.floor(timeSlider.value * 0.25)) + today.setMinutes((timeSlider.value * 15) % 60) + today.setSeconds(0) + today.setMilliseconds(0) + QGroundControl.airspaceManager.flightPlan.flightStartTime = today + } + Component.onCompleted: { + updateTime() + } + function updateTime() { + var today = QGroundControl.airspaceManager.flightPlan.flightStartTime + var val = (((today.getHours() * 60) + today.getMinutes()) * (96/1440)) + 1 + if(val > 95) val = 95 + timeSlider.value = Math.ceil(val) + } + } + QGCLabel { + id: timeLabel + text: ('00' + hour).slice(-2) + ":" + ('00' + minute).slice(-2) + width: ScreenTools.defaultFontPixelWidth * 5 + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + property int hour: Math.floor(timeSlider.value * 0.25) + property int minute: (timeSlider.value * 15) % 60 + } + } + QGCLabel { + text: qsTr("Now") + visible: QGroundControl.airspaceManager.flightPlan.flightStartsNow + anchors.horizontalCenter: parent.horizontalCenter + } + QGCLabel { + text: qsTr("Duration") + } + Item { + anchors.right: parent.right + anchors.left: parent.left + height: durationSlider.height + QGCSlider { + id: durationSlider + width: parent.width - durationLabel.width - ScreenTools.defaultFontPixelWidth + stepSize: 1 + minimumValue: 1 + maximumValue: 24 // 24 blocks of 15 minutes in 6 hours + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + onValueChanged: { + var hour = Math.floor(durationSlider.value * 0.25) + var minute = (durationSlider.value * 15) % 60 + var seconds = (hour * 60 * 60) + (minute * 60) + QGroundControl.airspaceManager.flightPlan.flightDuration = seconds + } + Component.onCompleted: { + var val = ((QGroundControl.airspaceManager.flightPlan.flightDuration / 60) * (96/1440)) + 1 + if(val > 24) val = 24 + durationSlider.value = Math.ceil(val) + } + } + QGCLabel { + id: durationLabel + text: ('00' + hour).slice(-2) + ":" + ('00' + minute).slice(-2) + width: ScreenTools.defaultFontPixelWidth * 5 + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + property int hour: Math.floor(durationSlider.value * 0.25) + property int minute: (durationSlider.value * 15) % 60 + } + } + } + } + Item { width: 1; height: ScreenTools.defaultFontPixelHeight * 0.25; } + QGCLabel { + text: qsTr("Flight Context") + visible: QGroundControl.airspaceManager.flightPlan.briefFeatures.count > 0 + } + Repeater { + model: QGroundControl.airspaceManager.flightPlan.briefFeatures + visible: QGroundControl.airspaceManager.flightPlan.briefFeatures.count > 0 + delegate: FlightFeature { + feature: object + visible: object && object.type !== AirspaceRuleFeature.Unknown && object.description !== "" && object.name !== "" + anchors.right: parent.right + anchors.left: parent.left + } + } + } + } + } + Calendar { + id: datePicker + anchors.centerIn: parent + visible: false; + minimumDate: QGroundControl.airspaceManager.flightPlan.flightStartTime + onClicked: { + QGroundControl.airspaceManager.flightPlan.flightStartTime = datePicker.selectedDate + visible = false; + } + } +} diff --git a/src/Airmap/FlightFeature.qml b/src/Airmap/FlightFeature.qml new file mode 100644 index 0000000000000000000000000000000000000000..d7b9d38d6b6f5d0ed6281a5be4eb690e65d0713e --- /dev/null +++ b/src/Airmap/FlightFeature.qml @@ -0,0 +1,87 @@ +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.Airmap 1.0 +import QGroundControl.Airspace 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.SettingsManager 1.0 + +Rectangle { + id: _root + height: questionCol.height + (ScreenTools.defaultFontPixelHeight * 1.25) + color: qgcPal.windowShade + property var feature: null + QGCPalette { + id: qgcPal + colorGroupEnabled: enabled + } + Column { + id: questionCol + spacing: ScreenTools.defaultFontPixelHeight * 0.5 + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.right: parent.right + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + QGCLabel { + text: feature.description + anchors.right: parent.right + anchors.left: parent.left + wrapMode: Text.WordWrap + visible: feature.type !== AirspaceRuleFeature.Boolean + } + QGCTextField { + text: feature.value ? feature.value : "" + visible: feature.type !== AirspaceRuleFeature.Boolean + showUnits: true + unitsLabel: { + if(feature.unit == AirspaceRuleFeature.Kilogram) + return "kg"; + if(feature.unit == AirspaceRuleFeature.Meters) + return "m"; + if(feature.unit == AirspaceRuleFeature.MetersPerSecond) + return "m/s"; + return "" + } + anchors.right: parent.right + anchors.left: parent.left + inputMethodHints: feature.type === AirspaceRuleFeature.Float ? Qt.ImhFormattedNumbersOnly :Qt.ImhNone + onAccepted: { + feature.value = parseFloat(text) + } + onEditingFinished: { + feature.value = parseFloat(text) + } + } + Item { + height: Math.max(checkBox.height, label.height) + anchors.right: parent.right + anchors.left: parent.left + visible: feature.type === AirspaceRuleFeature.Boolean + QGCCheckBox { + id: checkBox + text: "" + onClicked: feature.value = checked + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + Component.onCompleted: { + checked = feature.value === 2 ? false : feature.value + } + } + QGCLabel { + id: label + text: feature.description + anchors.right: parent.right + anchors.left: checkBox.right + anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 0.5 + wrapMode: Text.WordWrap + anchors.verticalCenter: parent.verticalCenter + } + } + } +} diff --git a/src/Airmap/LifetimeChecker.h b/src/Airmap/LifetimeChecker.h new file mode 100644 index 0000000000000000000000000000000000000000..8b50de2b8bc4a4a4ad93b7445d9ad791204b128b --- /dev/null +++ b/src/Airmap/LifetimeChecker.h @@ -0,0 +1,29 @@ +/**************************************************************************** + * + * (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 + +//----------------------------------------------------------------------------- +/** + * @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; +}; diff --git a/src/Airmap/QGroundControl.Airmap.qmldir b/src/Airmap/QGroundControl.Airmap.qmldir new file mode 100644 index 0000000000000000000000000000000000000000..b125667d4406e78ab8a8d8045cc8e5f1c285f215 --- /dev/null +++ b/src/Airmap/QGroundControl.Airmap.qmldir @@ -0,0 +1,10 @@ +Module QGroundControl.Airmap + +AirspaceControl 1.0 AirspaceControl.qml +AirspaceRegulation 1.0 AirspaceRegulation.qml +AirspaceWeather 1.0 AirspaceWeather.qml +ComplianceRules 1.0 ComplianceRules.qml +FlightFeature 1.0 FlightFeature.qml +RuleSelector 1.0 RuleSelector.qml +FlightBrief 1.0 FlightBrief.qml +FlightDetails 1.0 FlightDetails.qml \ No newline at end of file diff --git a/src/Airmap/QJsonWebToken/README.md b/src/Airmap/QJsonWebToken/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8dd2a037bfb3e9e84cfa9a801e7d207b980d486d --- /dev/null +++ b/src/Airmap/QJsonWebToken/README.md @@ -0,0 +1,47 @@ +## Introduction + +QJsonWebToken : JWT (JSON Web Token) Implementation in Qt C++ + +This class implements a subset of the [JSON Web Token](https://en.wikipedia.org/wiki/JSON_Web_Token) +open standard [RFC 7519](https://tools.ietf.org/html/rfc7519). + +Currently this implementation **only supports** the following algorithms: + +Alg | Parameter Value Algorithm +----- | ------------------------------------ +HS256 | HMAC using SHA-256 hash algorithm +HS384 | HMAC using SHA-384 hash algorithm +HS512 | HMAC using SHA-512 hash algorithm + +### Include + +In order to include this class in your project, in the qt project **.pro** file add the lines: + +```cmake +HEADERS += ./src/qjsonwebtoken.h +SOURCES += ./src/qjsonwebtoken.cpp +``` + +### Usage + +The repository of this project includes examples that demonstrate the use of this class: + +* ```./examples/jwtcreator/``` : Example that shows how to create a JWT with your custom *payload*. + +* ```./examples/jwtverifier/``` : Example that shows how to validate a JWT with a given *secret*. + +### Limitations + +Currently, `QJsonWebToken` validator, can **only** validate tokens created by `QJsonWebToken` itself. This limitation is due to the usage of Qt's [QJsonDocument API](http://doc.qt.io/qt-5/qjsondocument.html), see [this issue for further explanation](https://github.com/juangburgos/QJsonWebToken/issues/3#issuecomment-333056575). + +### License + +MIT + +``` +The MIT License(MIT) +Copyright(c) <2016> +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` diff --git a/src/Airmap/QJsonWebToken/src/qjsonwebtoken.cpp b/src/Airmap/QJsonWebToken/src/qjsonwebtoken.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7dd0bcc4b1a8109db8a8743a8d50dac59be3e81d --- /dev/null +++ b/src/Airmap/QJsonWebToken/src/qjsonwebtoken.cpp @@ -0,0 +1,337 @@ +// The MIT License(MIT) +// Copyright(c) <2016> +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "qjsonwebtoken.h" + +#include + +QJsonWebToken::QJsonWebToken() +{ + // create the header with default algorithm + setAlgorithmStr("HS256"); + m_jdocPayload = QJsonDocument::fromJson("{}"); + // default for random generation + m_intRandLength = 10; + m_strRandAlphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; +} + +QJsonWebToken::QJsonWebToken(const QJsonWebToken &other) +{ + this->m_jdocHeader = other.m_jdocHeader; + this->m_jdocPayload = other.m_jdocPayload; + this->m_byteSignature = other.m_byteSignature; + this->m_strSecret = other.m_strSecret; + this->m_strAlgorithm = other.m_strAlgorithm; +} + +QJsonDocument QJsonWebToken::getHeaderJDoc() +{ + return m_jdocHeader; +} + +QString QJsonWebToken::getHeaderQStr(QJsonDocument::JsonFormat format /*= QJsonDocument::JsonFormat::Indented*/) +{ + return m_jdocHeader.toJson(format); +} + +bool QJsonWebToken::setHeaderJDoc(QJsonDocument jdocHeader) +{ + if (jdocHeader.isEmpty() || jdocHeader.isNull() || !jdocHeader.isObject()) + { + return false; + } + + // check if supported algorithm + QString strAlgorithm = jdocHeader.object().value("alg").toString(""); + if (!isAlgorithmSupported(strAlgorithm)) + { + return false; + } + + m_jdocHeader = jdocHeader; + + // set also new algorithm + m_strAlgorithm = strAlgorithm; + + return true; +} + +bool QJsonWebToken::setHeaderQStr(QString strHeader) +{ + QJsonParseError error; + QJsonDocument tmpHeader = QJsonDocument::fromJson(strHeader.toUtf8(), &error); + + // validate and set header + if (error.error != QJsonParseError::NoError || !setHeaderJDoc(tmpHeader)) + { + return false; + } + + return true; +} + +QJsonDocument QJsonWebToken::getPayloadJDoc() +{ + return m_jdocPayload; +} + +QString QJsonWebToken::getPayloadQStr(QJsonDocument::JsonFormat format /*= QJsonDocument::JsonFormat::Indented*/) +{ + return m_jdocPayload.toJson(format); +} + +bool QJsonWebToken::setPayloadJDoc(QJsonDocument jdocPayload) +{ + if (jdocPayload.isEmpty() || jdocPayload.isNull() || !jdocPayload.isObject()) + { + return false; + } + + m_jdocPayload = jdocPayload; + + return true; +} + +bool QJsonWebToken::setPayloadQStr(QString strPayload) +{ + QJsonParseError error; + QJsonDocument tmpPayload = QJsonDocument::fromJson(strPayload.toUtf8(), &error); + + // validate and set payload + if (error.error != QJsonParseError::NoError || !setPayloadJDoc(tmpPayload)) + { + return false; + } + + return true; +} + +QByteArray QJsonWebToken::getSignature() +{ + // recalculate + // get header in compact mode and base64 encoded + QByteArray byteHeaderBase64 = getHeaderQStr(QJsonDocument::JsonFormat::Compact).toUtf8().toBase64(); + // get payload in compact mode and base64 encoded + QByteArray bytePayloadBase64 = getPayloadQStr(QJsonDocument::JsonFormat::Compact).toUtf8().toBase64(); + // calculate signature based on chosen algorithm and secret + m_byteAllData = byteHeaderBase64 + "." + bytePayloadBase64; + if (m_strAlgorithm.compare("HS256", Qt::CaseInsensitive) == 0) // HMAC using SHA-256 hash algorithm + { + m_byteSignature = QMessageAuthenticationCode::hash(m_byteAllData, m_strSecret.toUtf8(), QCryptographicHash::Sha256); + } + else if (m_strAlgorithm.compare("HS384", Qt::CaseInsensitive) == 0) // HMAC using SHA-384 hash algorithm + { + m_byteSignature = QMessageAuthenticationCode::hash(m_byteAllData, m_strSecret.toUtf8(), QCryptographicHash::Sha384); + } + else if (m_strAlgorithm.compare("HS512", Qt::CaseInsensitive) == 0) // HMAC using SHA-512 hash algorithm + { + m_byteSignature = QMessageAuthenticationCode::hash(m_byteAllData, m_strSecret.toUtf8(), QCryptographicHash::Sha512); + } + // TODO : support other algorithms + else + { + m_byteSignature = QByteArray(); + } + // return recalculated + return m_byteSignature; +} + +QByteArray QJsonWebToken::getSignatureBase64() +{ + // note we return through getSignature() to force recalculation + return getSignature().toBase64(); +} + +QString QJsonWebToken::getSecret() +{ + return m_strSecret; +} + +bool QJsonWebToken::setSecret(QString strSecret) +{ + if (strSecret.isEmpty() || strSecret.isNull()) + { + return false; + } + + m_strSecret = strSecret; + + return true; +} + +void QJsonWebToken::setRandomSecret() +{ + m_strSecret.resize(m_intRandLength); + for (int i = 0; i < m_intRandLength; ++i) + { + m_strSecret[i] = m_strRandAlphanum.at(rand() % (m_strRandAlphanum.length() - 1)); + } +} + +QString QJsonWebToken::getAlgorithmStr() +{ + return m_strAlgorithm; +} + +bool QJsonWebToken::setAlgorithmStr(QString strAlgorithm) +{ + // check if supported algorithm + if (!isAlgorithmSupported(strAlgorithm)) + { + return false; + } + // set algorithm + m_strAlgorithm = strAlgorithm; + // modify header + m_jdocHeader = QJsonDocument::fromJson(QObject::trUtf8("{\"typ\": \"JWT\", \"alg\" : \"").toUtf8() + + m_strAlgorithm.toUtf8() + + QObject::trUtf8("\"}").toUtf8()); + + return true; +} + +QString QJsonWebToken::getToken() +{ + // important to execute first to update m_byteAllData which contains header + "." + payload in base64 + QByteArray byteSignatureBase64 = getSignatureBase64(); + // compose token and return it + return m_byteAllData + "." + byteSignatureBase64; +} + +bool QJsonWebToken::setToken(QString strToken) +{ + // assume base64 encoded at first, if not try decoding + bool isBase64Encoded = true; + QStringList listJwtParts = strToken.split("."); + // check correct size + if (listJwtParts.count() != 3) + { + return false; + } + // check all parts are valid using another instance, + // so we dont overwrite this instance in case of error + QJsonWebToken tempTokenObj; + if ( !tempTokenObj.setHeaderQStr(QByteArray::fromBase64(listJwtParts.at(0).toUtf8())) || + !tempTokenObj.setPayloadQStr(QByteArray::fromBase64(listJwtParts.at(1).toUtf8())) ) + { + // try unencoded + if (!tempTokenObj.setHeaderQStr(listJwtParts.at(0)) || + !tempTokenObj.setPayloadQStr(listJwtParts.at(1))) + { + return false; + } + else + { + isBase64Encoded = false; + } + } + // set parts on this instance + setHeaderQStr(tempTokenObj.getHeaderQStr()); + setPayloadQStr(tempTokenObj.getPayloadQStr()); + if (isBase64Encoded) + { // unencode + m_byteSignature = QByteArray::fromBase64(listJwtParts.at(2).toUtf8()); + } + else + { + m_byteSignature = listJwtParts.at(2).toUtf8(); + } + // allData not valid anymore + m_byteAllData.clear(); + // success + return true; +} + +QString QJsonWebToken::getRandAlphanum() +{ + return m_strRandAlphanum; +} + +void QJsonWebToken::setRandAlphanum(QString strRandAlphanum) +{ + if(strRandAlphanum.isNull()) + { + return; + } + m_strRandAlphanum = strRandAlphanum; +} + +int QJsonWebToken::getRandLength() +{ + return m_intRandLength; +} + +void QJsonWebToken::setRandLength(int intRandLength) +{ + if(intRandLength < 0 || intRandLength > 1e6) + { + return; + } + m_intRandLength = intRandLength; +} + +bool QJsonWebToken::isValid() +{ + // calculate token on other instance, + // so we dont overwrite this instance's signature + QJsonWebToken tempTokenObj = *this; + if (m_byteSignature != tempTokenObj.getSignature()) + { + return false; + } + return true; +} + +QJsonWebToken QJsonWebToken::fromTokenAndSecret(QString strToken, QString srtSecret) +{ + QJsonWebToken tempTokenObj; + // set Token + tempTokenObj.setToken(strToken); + // set Secret + tempTokenObj.setSecret(srtSecret); + // return + return tempTokenObj; +} + +void QJsonWebToken::appendClaim(QString strClaimType, QString strValue) +{ + // have to make a copy of the json object, modify the copy and then put it back, sigh + QJsonObject jObj = m_jdocPayload.object(); + jObj.insert(strClaimType, strValue); + m_jdocPayload = QJsonDocument(jObj); +} + +void QJsonWebToken::removeClaim(QString strClaimType) +{ + // have to make a copy of the json object, modify the copy and then put it back, sigh + QJsonObject jObj = m_jdocPayload.object(); + jObj.remove(strClaimType); + m_jdocPayload = QJsonDocument(jObj); +} + +bool QJsonWebToken::isAlgorithmSupported(QString strAlgorithm) +{ + // TODO : support other algorithms + if (strAlgorithm.compare("HS256", Qt::CaseInsensitive) != 0 && // HMAC using SHA-256 hash algorithm + strAlgorithm.compare("HS384", Qt::CaseInsensitive) != 0 && // HMAC using SHA-384 hash algorithm + strAlgorithm.compare("HS512", Qt::CaseInsensitive) != 0 /*&& // HMAC using SHA-512 hash algorithm + strAlgorithm.compare("RS256", Qt::CaseInsensitive) != 0 && // RSA using SHA-256 hash algorithm + strAlgorithm.compare("RS384", Qt::CaseInsensitive) != 0 && // RSA using SHA-384 hash algorithm + strAlgorithm.compare("RS512", Qt::CaseInsensitive) != 0 && // RSA using SHA-512 hash algorithm + strAlgorithm.compare("ES256", Qt::CaseInsensitive) != 0 && // ECDSA using P-256 curve and SHA-256 hash algorithm + strAlgorithm.compare("ES384", Qt::CaseInsensitive) != 0 && // ECDSA using P-384 curve and SHA-384 hash algorithm + strAlgorithm.compare("ES512", Qt::CaseInsensitive) != 0*/) // ECDSA using P-521 curve and SHA-512 hash algorithm + { + return false; + } + return true; +} + +QStringList QJsonWebToken::supportedAlgorithms() +{ + // TODO : support other algorithms + return QStringList() << "HS256" << "HS384" << "HS512"; +} diff --git a/src/Airmap/QJsonWebToken/src/qjsonwebtoken.h b/src/Airmap/QJsonWebToken/src/qjsonwebtoken.h new file mode 100644 index 0000000000000000000000000000000000000000..63482c0b55c35b341dfd3f61d6d5cdf6b1813c1b --- /dev/null +++ b/src/Airmap/QJsonWebToken/src/qjsonwebtoken.h @@ -0,0 +1,404 @@ +/** +\file +\version 1.0 +\date 22/06/2016 +\author JGB +\brief JWT (JSON Web Token) Implementation in Qt C++ +*/ + +// The MIT License(MIT) +// Copyright(c) <2016> +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef QJSONWEBTOKEN_H +#define QJSONWEBTOKEN_H + +#include +#include +#include +#include + +/** + +\brief QJsonWebToken : JWT (JSON Web Token) Implementation in Qt C++ + +## Introduction + +This class implements a subset of the [JSON Web Token](https://en.wikipedia.org/wiki/JSON_Web_Token) +open standard [RFC 7519](https://tools.ietf.org/html/rfc7519). + +Currently this implementation only supports the following algorithms: + +Alg | Parameter Value Algorithm +----- | ------------------------------------ +HS256 | HMAC using SHA-256 hash algorithm +HS384 | HMAC using SHA-384 hash algorithm +HS512 | HMAC using SHA-512 hash algorithm + +### Include + +In order to include this class in your project, in the qt project **.pro** file add the lines: + +``` +HEADERS += ./src/qjsonwebtoken.h +SOURCES += ./src/qjsonwebtoken.cpp +``` + +### Usage + +The repository of this project includes examples that demonstrate the use of this class: + +* ./examples/jwtcreator/ : Example that shows how to create a JWT with your custom *payload*. + +* ./examples/jwtverifier/ : Example that shows how to validate a JWT with a given *secret*. + +*/ +class QJsonWebToken +{ + +public: + + /** + + \brief Constructor. + \return A new instance of QJsonWebToken. + + Creates a default QJsonWebToken instance with *HS256 algorithm*, empty *payload* + and empty *secret*. + + */ + QJsonWebToken(); // TODO : improve with params + + /** + + \brief Copy Construtor. + \param other Other QJsonWebToken to copy from. + \return A new instance of QJsonWebToken with same contents as the *other* instance. + + Copies to the new instance the JWT *header*, *payload*, *signature*, *secret* and *algorithm*. + + */ + QJsonWebToken(const QJsonWebToken &other); + + /** + + \brief Returns the JWT *header* as a QJsonDocument. + \return JWT *header* as a QJsonDocument. + + */ + QJsonDocument getHeaderJDoc(); + + /** + + \brief Returns the JWT *header* as a QString. + \param format Defines the format of the JSON returned. + \return JWT *header* as a QString. + + Format can be *QJsonDocument::JsonFormat::Indented* or *QJsonDocument::JsonFormat::Compact* + + */ + QString getHeaderQStr(QJsonDocument::JsonFormat format = QJsonDocument::JsonFormat::Indented); + + /** + + \brief Sets the JWT *header* from a QJsonDocument. + \param jdocHeader JWT *header* as a QJsonDocument. + \return true if the header was set, false if the header was not set. + + This method checks for a valid header format and returns false if the header is invalid. + + */ + bool setHeaderJDoc(QJsonDocument jdocHeader); + + /** + + \brief Sets the JWT *header* from a QString. + \param jdocHeader JWT *header* as a QString. + \return true if the header was set, false if the header was not set. + + This method checks for a valid header format and returns false if the header is invalid. + + */ + bool setHeaderQStr(QString strHeader); + + /** + + \brief Returns the JWT *payload* as a QJsonDocument. + \return JWT *payload* as a QJsonDocument. + + */ + QJsonDocument getPayloadJDoc(); + + /** + + \brief Returns the JWT *payload* as a QString. + \param format Defines the format of the JSON returned. + \return JWT *payload* as a QString. + + Format can be *QJsonDocument::JsonFormat::Indented* or *QJsonDocument::JsonFormat::Compact* + + */ + QString getPayloadQStr(QJsonDocument::JsonFormat format = QJsonDocument::JsonFormat::Indented); + + /** + + \brief Sets the JWT *payload* from a QJsonDocument. + \param jdocHeader JWT *payload* as a QJsonDocument. + \return true if the payload was set, false if the payload was not set. + + This method checks for a valid payload format and returns false if the payload is invalid. + + */ + bool setPayloadJDoc(QJsonDocument jdocPayload); + + /** + + \brief Sets the JWT *payload* from a QString. + \param jdocHeader JWT *payload* as a QString. + \return true if the payload was set, false if the payload was not set. + + This method checks for a valid payload format and returns false if the payload is invalid. + + */ + bool setPayloadQStr(QString strPayload); + + /** + + \brief Returns the JWT *signature* as a QByteArray. + \return JWT *signature* as a decoded QByteArray. + + Recalculates the JWT signature given the current *header*, *payload*, *algorithm* and + *secret*. + + \warning This method overwrites the old signature internally. This could be undesired when + the signature was obtained by copying from another QJsonWebToken using the copy constructor. + + */ + QByteArray getSignature(); // WARNING overwrites signature + + /** + + \brief Returns the JWT *signature* as a QByteArray. + \return JWT *signature* as a **base64 encoded** QByteArray. + + Recalculates the JWT signature given the current *header*, *payload*, *algorithm* and + *secret*. Then encodes the calculated signature using base64 encoding. + + \warning This method overwrites the old signature internally. This could be undesired when + the signature was obtained by copying from another QJsonWebToken using the copy constructor. + + */ + QByteArray getSignatureBase64(); // WARNING overwrites signature + + /** + + \brief Returns the JWT *secret* as a QString. + \return JWT *secret* as a QString. + + */ + QString getSecret(); + + /** + + \brief Sets the JWT *secret* from a QString. + \param strSecret JWT *secret* as a QString. + \return true if the secret was set, false if the secret was not set. + + This method checks for a valid secret format and returns false if the secret is invalid. + + */ + bool setSecret(QString strSecret); + + /** + + \brief Creates and sets a random secret. + + This method creates a random secret with the length defined by QJsonWebToken::getRandLength(), + and the characters defined by QJsonWebToken::getRandAlphanum(). + + \sa QJsonWebToken::getRandLength(). + \sa QJsonWebToken::getRandAlphanum(). + + */ + void setRandomSecret(); + + /** + + \brief Returns the JWT *algorithm* as a QString. + \return JWT *algorithm* as a QString. + + */ + QString getAlgorithmStr(); + + /** + + \brief Sets the JWT *algorithm* from a QString. + \param strAlgorithm JWT *algorithm* as a QString. + \return true if the algorithm was set, false if the algorithm was not set. + + This method checks for a valid supported algorithm. Valid values are: + + "HS256", "HS384" and "HS512". + + \sa QJsonWebToken::supportedAlgorithms(). + + */ + bool setAlgorithmStr(QString strAlgorithm); + + /** + + \brief Returns the complete JWT as a QString. + \return Complete JWT as a QString. + + The token has the form: + + ``` + xxxxx.yyyyy.zzzzz + ``` + + where: + + - *xxxxx* is the *header* enconded in base64. + - *yyyyy* is the *payload* enconded in base64. + - *zzzzz* is the *signature* enconded in base64. + + */ + QString getToken(); + + /** + + \brief Sets the complete JWT as a QString. + \param strToken Complete JWT as a QString. + \return true if the complete JWT was set, false if not set. + + This method checks for a valid JWT format. It overwrites the *header*, + *payload* , *signature* and *algorithm*. It does **not** overwrite the secret. + + \sa QJsonWebToken::getToken(). + + */ + bool setToken(QString strToken); + + /** + + \brief Returns the current set of characters used to create random secrets. + \return Set of characters as a QString. + + The default value is "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + \sa QJsonWebToken::setRandomSecret() + \sa QJsonWebToken::setRandAlphanum() + + */ + QString getRandAlphanum(); + + /** + + \brief Sets the current set of characters used to create random secrets. + \param strRandAlphanum Set of characters as a QString. + + \sa QJsonWebToken::setRandomSecret() + \sa QJsonWebToken::getRandAlphanum() + + */ + void setRandAlphanum(QString strRandAlphanum); + + /** + + \brief Returns the current length used to create random secrets. + \return Length of random secret as a QString. + + The default value is 10; + + \sa QJsonWebToken::setRandomSecret() + \sa QJsonWebToken::setRandLength() + + */ + int getRandLength(); + + /** + + \brief Sets the current length used to create random secrets. + \param intRandLength Length of random secret. + + \sa QJsonWebToken::setRandomSecret() + \sa QJsonWebToken::getRandLength() + + */ + void setRandLength(int intRandLength); + + /** + + \brief Checks validity of current JWT with respect to secret. + \return true if the JWT is valid with respect to secret, else false. + + Uses the current *secret* to calculate a temporary *signature* and compares it to the + current signature to check if they are the same. If they are, true is returned, if not then + false is returned. + + */ + bool isValid(); + + /** + + \brief Creates a QJsonWebToken instance from the complete JWT and a secret. + \param strToken Complete JWT as a QString. + \param srtSecret Secret as a QString. + \return Instance of QJsonWebToken. + + The JWT provided must have a valid format, else a QJsonWebToken instance with default + values will be returned. + + */ + static QJsonWebToken fromTokenAndSecret(QString strToken, QString srtSecret); + + /** + + \brief Returns a list of the supported algorithms. + \return List of supported algorithms as a QStringList. + + */ + static QStringList supportedAlgorithms(); + + /** + + \brief Convenience method to append a claim to the *payload*. + \param strClaimType The claim type as a QString. + \param strValue The value type as a QString. + + Both parameters must be non-empty. If the claim type already exists, the current + claim value is updated. + + */ + void appendClaim(QString strClaimType, QString strValue); + + /** + + \brief Convenience method to remove a claim from the *payload*. + \param strClaimType The claim type as a QString. + + If the claim type does not exist in the *payload*, then this method does nothins. + + */ + void removeClaim(QString strClaimType); + +private: + // properties + QJsonDocument m_jdocHeader; // unencoded + QJsonDocument m_jdocPayload; // unencoded + QByteArray m_byteSignature; // unencoded + QString m_strSecret; + QString m_strAlgorithm; + + int m_intRandLength ; + QString m_strRandAlphanum; + + // helpers + QByteArray m_byteAllData; + + bool isAlgorithmSupported(QString strAlgorithm); +}; + +#endif // QJSONWEBTOKEN_H diff --git a/src/Airmap/QJsonWebToken/src/qjsonwebtoken.pri b/src/Airmap/QJsonWebToken/src/qjsonwebtoken.pri new file mode 100644 index 0000000000000000000000000000000000000000..3efe5d61fa69fdca5ca6ddf2f76bba7e9c4c7e70 --- /dev/null +++ b/src/Airmap/QJsonWebToken/src/qjsonwebtoken.pri @@ -0,0 +1,7 @@ +CONFIG -= flat + +INCLUDEPATH += $$PWD/ + +SOURCES += $$PWD/qjsonwebtoken.cpp + +HEADERS += $$PWD/qjsonwebtoken.h diff --git a/src/Airmap/README.md b/src/Airmap/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c5d0e3e1ac2ef0eb2c0a214093bc7a20260d49ef --- /dev/null +++ b/src/Airmap/README.md @@ -0,0 +1,25 @@ +## To be deleted when development is complete + +* Traffic monitor timeout is now set to 2 minutes following instructions from Thomas Voß. + +* Group rules jurisdictions + +* Check if we really need libairmap-cpp.dylib + +* ~Check rules sorting order. Repopulating QmlObjectListModel causes the repeater to show the contents in random order.~ + +* ~AirMapRestrictionManager seems to be incomplete (if not redundant). Shouldn't we use the array of AirSpace items returned by AirMapAdvisoryManager instead? In addition to the AirSpace object, it gives you a color to use in order of importance.~ See question below. + +* ~"Required" rules must be pre-selectd and cannot be unselected by the user~ + +* ~Add a "Wrong Way" icon to the airspace widget when not connected~ + + + +Questions: + +Given a same set of coordinates, what is the relationship between the `Airspace` elements returned by `Airspaces::Search` and those returned by `Status::GetStatus` (Advisories)? The former don’t have a “color”. The latter do but those don’t have any geometry. In addition, given two identical set of arguments (coordinates), the resulting sets are not the same. A given airspace may also be repeated several times with different “levels”, such as an airport having several “green” and several “yellow” advisories (but no red or orange). How do you filter what to show on the screen? + +Also, the main glaring airspace nearby, KRDU has one entry in Airspaces whose ID is not in the array returned by the advisories (and therefore no color to assign to it). In fact, KRDU shows up 20 times in Advisories and 11 times in Airspaces. + +In summary, why do I have to make two requests to get two arrays of Airspaces, which should be mostly the same? diff --git a/src/Airmap/RuleSelector.qml b/src/Airmap/RuleSelector.qml new file mode 100644 index 0000000000000000000000000000000000000000..951ba203683325f37a422211ee5f486dc3b6bb76 --- /dev/null +++ b/src/Airmap/RuleSelector.qml @@ -0,0 +1,71 @@ +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 +import QGroundControl.SettingsManager 1.0 + +Rectangle { + id: _root + height: ScreenTools.defaultFontPixelHeight + color: _selected ? qgcPal.windowShade : qgcPal.window + property var rule: null + property bool checked: false + property bool required: false + property bool _selected: { + if (exclusiveGroup) { + return checked + } else { + return rule ? rule.selected : false + } + } + property ExclusiveGroup exclusiveGroup: null + onExclusiveGroupChanged: { + if (exclusiveGroup) { + checked = rule.selected + exclusiveGroup.bindCheckable(_root) + } + } + onCheckedChanged: { + rule.selected = checked + } + QGCPalette { + id: qgcPal + colorGroupEnabled: enabled + } + Row { + id: ruleRow + spacing: ScreenTools.defaultFontPixelWidth + anchors.right: parent.right + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + Rectangle { + width: ScreenTools.defaultFontPixelWidth * 0.75 + height: ScreenTools.defaultFontPixelHeight + color: _selected ? qgcPal.colorGreen : qgcPal.window + anchors.verticalCenter: parent.verticalCenter + } + QGCLabel { + text: rule.name === "" ? rule.shortName : rule.name + font.pointSize: ScreenTools.smallFontPointSize + anchors.verticalCenter: parent.verticalCenter + } + } + MouseArea { + anchors.fill: parent + enabled: !required + onClicked: { + if (exclusiveGroup) { + checked = true + } else { + rule.selected = !rule.selected + } + } + } +} diff --git a/src/Airmap/airmap.qrc b/src/Airmap/airmap.qrc new file mode 100644 index 0000000000000000000000000000000000000000..4e3f47a73e8490d7567c445bb1cc0743f73c0119 --- /dev/null +++ b/src/Airmap/airmap.qrc @@ -0,0 +1,59 @@ + + + AirmapSettings.qml + AirspaceControl.qml + AirspaceRegulation.qml + AirspaceWeather.qml + ComplianceRules.qml + FlightBrief.qml + FlightDetails.qml + FlightFeature.qml + QGroundControl.Airmap.qmldir + RuleSelector.qml + + + AirMap.SettingsGroup.json + + + images/advisory-icon.svg + images/colapse.svg + images/expand.svg + images/pencil.svg + images/right-arrow.svg + images/unavailable.svg + + + images/weather-icons/clear.svg + images/weather-icons/sunny.svg + images/weather-icons/cloudy.svg + images/weather-icons/cloudy_wind.svg + images/weather-icons/drizzle.svg + images/weather-icons/drizzle_day.svg + images/weather-icons/drizzle_night.svg + images/weather-icons/foggy.svg + images/weather-icons/frigid.svg + images/weather-icons/hail.svg + images/weather-icons/heavy_rain.svg + images/weather-icons/hurricane.svg + images/weather-icons/isolated_thunderstorms.svg + images/weather-icons/mostly_clear.svg + images/weather-icons/mostly_cloudy_day.svg + images/weather-icons/mostly_cloudy_night.svg + images/weather-icons/mostly_sunny.svg + images/weather-icons/partly_cloudy_day.svg + images/weather-icons/partly_cloudy_night.svg + images/weather-icons/rain.svg + images/weather-icons/rain_snow.svg + images/weather-icons/scattered_snow_showers_day.svg + images/weather-icons/scattered_snow_showers_night.svg + images/weather-icons/scattered_thunderstorms_day.svg + images/weather-icons/scattered_thunderstorms_night.svg + images/weather-icons/snow.svg + images/weather-icons/snow_storm.svg + images/weather-icons/sunny.svg + images/weather-icons/thunderstorm.svg + images/weather-icons/tornado.svg + images/weather-icons/unknown.svg + images/weather-icons/windy.svg + + diff --git a/src/Airmap/dummy/AirspaceControl.qml b/src/Airmap/dummy/AirspaceControl.qml new file mode 100644 index 0000000000000000000000000000000000000000..ca9b339b981caa987b2080659eb09e16bd63e118 --- /dev/null +++ b/src/Airmap/dummy/AirspaceControl.qml @@ -0,0 +1,6 @@ +import QtQuick 2.3 +Item { + property bool colapsed: true + property bool showColapse: false + property bool planView: false +} diff --git a/src/Airmap/dummy/AirspaceManager.cc b/src/Airmap/dummy/AirspaceManager.cc new file mode 100644 index 0000000000000000000000000000000000000000..73ccffa04e00becdeab745e7142a2191449e597e --- /dev/null +++ b/src/Airmap/dummy/AirspaceManager.cc @@ -0,0 +1,35 @@ +/**************************************************************************** + * + * (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 "AirspaceManager.h" +#include "QGCApplication.h" + +AirspaceManager::AirspaceManager(QGCApplication* app, QGCToolbox* toolbox) + : QGCTool(app, toolbox) +{ + qmlRegisterUncreatableType ("QGroundControl.Airspace", 1, 0, "AirspaceManager", "Reference only"); +} + +AirspaceManager::~AirspaceManager() +{ +} + +void AirspaceManager::setToolbox(QGCToolbox* toolbox) +{ + QGCTool::setToolbox(toolbox); +} + +void AirspaceManager::setROI(const QGeoCoordinate& pointNW, const QGeoCoordinate& pointSE, bool planView, bool reset) +{ + Q_UNUSED(pointNW); + Q_UNUSED(pointSE); + Q_UNUSED(planView); + Q_UNUSED(reset) +} diff --git a/src/Airmap/dummy/AirspaceManager.h b/src/Airmap/dummy/AirspaceManager.h new file mode 100644 index 0000000000000000000000000000000000000000..47e82b858306792c2391e7e6a50d3ae3504cfc25 --- /dev/null +++ b/src/Airmap/dummy/AirspaceManager.h @@ -0,0 +1,58 @@ +/**************************************************************************** + * + * (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 AirspaceManager.h + * Dummy file for when airspace management is disabled + */ + +#include "QGCToolbox.h" +#include + +//----------------------------------------------------------------------------- +/** + * @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() override; + + Q_PROPERTY(QString providerName READ providerName CONSTANT) + Q_PROPERTY(QObject* weatherInfo READ weatherInfo CONSTANT) + Q_PROPERTY(QObject* advisories READ advisories CONSTANT) + Q_PROPERTY(QObject* ruleSets READ ruleSets CONSTANT) + Q_PROPERTY(QObject* airspaces READ airspaces CONSTANT) + Q_PROPERTY(QObject* flightPlan READ flightPlan CONSTANT) + Q_PROPERTY(bool airspaceVisible READ airspaceVisible CONSTANT) + + Q_INVOKABLE void setROI (const QGeoCoordinate& pointNW, const QGeoCoordinate& pointSE, bool planView, bool reset = false); + + QObject* weatherInfo () { return &_dummy; } + QObject* advisories () { return &_dummy; } + QObject* ruleSets () { return &_dummy; } + QObject* airspaces () { return &_dummy; } + QObject* flightPlan () { return &_dummy; } + + void setToolbox(QGCToolbox* toolbox) override; + + virtual QString providerName () const { return QString("None"); } + + virtual bool airspaceVisible () { return false; } + +signals: + void airspaceVisibleChanged (); + +private: + QObject _dummy; +}; diff --git a/src/Airmap/dummy/AirspaceRegulation.qml b/src/Airmap/dummy/AirspaceRegulation.qml new file mode 100644 index 0000000000000000000000000000000000000000..9009b3fa17a6b27579c3d7003bd9ddd06bd90c3f --- /dev/null +++ b/src/Airmap/dummy/AirspaceRegulation.qml @@ -0,0 +1,3 @@ +import QtQuick 2.3 +Item { +} diff --git a/src/Airmap/dummy/AirspaceWeather.qml b/src/Airmap/dummy/AirspaceWeather.qml new file mode 100644 index 0000000000000000000000000000000000000000..a48aaccb6ddf3f0e03023fc4941eaf7e3df21c85 --- /dev/null +++ b/src/Airmap/dummy/AirspaceWeather.qml @@ -0,0 +1,4 @@ +import QtQuick 2.3 +Item { + property var iconHeight: 0 +} diff --git a/src/Airmap/dummy/ComplianceRules.qml b/src/Airmap/dummy/ComplianceRules.qml new file mode 100644 index 0000000000000000000000000000000000000000..5c0536c80a741b79cdfae2faac799539ac6e706e --- /dev/null +++ b/src/Airmap/dummy/ComplianceRules.qml @@ -0,0 +1,5 @@ +import QtQuick 2.3 + +Item { + +} diff --git a/src/Airmap/dummy/FlightBrief.qml b/src/Airmap/dummy/FlightBrief.qml new file mode 100644 index 0000000000000000000000000000000000000000..5c0536c80a741b79cdfae2faac799539ac6e706e --- /dev/null +++ b/src/Airmap/dummy/FlightBrief.qml @@ -0,0 +1,5 @@ +import QtQuick 2.3 + +Item { + +} diff --git a/src/Airmap/dummy/FlightDetails.qml b/src/Airmap/dummy/FlightDetails.qml new file mode 100644 index 0000000000000000000000000000000000000000..5c0536c80a741b79cdfae2faac799539ac6e706e --- /dev/null +++ b/src/Airmap/dummy/FlightDetails.qml @@ -0,0 +1,5 @@ +import QtQuick 2.3 + +Item { + +} diff --git a/src/Airmap/dummy/FlightFeature.qml b/src/Airmap/dummy/FlightFeature.qml new file mode 100644 index 0000000000000000000000000000000000000000..5c0536c80a741b79cdfae2faac799539ac6e706e --- /dev/null +++ b/src/Airmap/dummy/FlightFeature.qml @@ -0,0 +1,5 @@ +import QtQuick 2.3 + +Item { + +} diff --git a/src/Airmap/dummy/QGroundControl.Airmap.qmldir b/src/Airmap/dummy/QGroundControl.Airmap.qmldir new file mode 100644 index 0000000000000000000000000000000000000000..0196743d3c2dd0e3d9143d895ee58e814152f140 --- /dev/null +++ b/src/Airmap/dummy/QGroundControl.Airmap.qmldir @@ -0,0 +1,5 @@ +Module QGroundControl.Airmap + +AirspaceControl 1.0 AirspaceControl.qml +AirspaceRegulation 1.0 AirspaceRegulation.qml +AirspaceWeather 1.0 AirspaceWeather.qml diff --git a/src/Airmap/dummy/RuleSelector.qml b/src/Airmap/dummy/RuleSelector.qml new file mode 100644 index 0000000000000000000000000000000000000000..5c0536c80a741b79cdfae2faac799539ac6e706e --- /dev/null +++ b/src/Airmap/dummy/RuleSelector.qml @@ -0,0 +1,5 @@ +import QtQuick 2.3 + +Item { + +} diff --git a/src/Airmap/dummy/airmap_dummy.qrc b/src/Airmap/dummy/airmap_dummy.qrc new file mode 100644 index 0000000000000000000000000000000000000000..ea382774032019aa49b26aa1ae30fbb00aaf64ee --- /dev/null +++ b/src/Airmap/dummy/airmap_dummy.qrc @@ -0,0 +1,13 @@ + + + QGroundControl.Airmap.qmldir + AirspaceControl.qml + AirspaceRegulation.qml + AirspaceWeather.qml + ComplianceRules.qml + FlightBrief.qml + FlightDetails.qml + FlightFeature.qml + RuleSelector.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/Airmap/images/right-arrow.svg b/src/Airmap/images/right-arrow.svg new file mode 100644 index 0000000000000000000000000000000000000000..811093e67f100e655e9ce7b866fcea13e1276460 --- /dev/null +++ b/src/Airmap/images/right-arrow.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/src/Airmap/images/unavailable.svg b/src/Airmap/images/unavailable.svg new file mode 100644 index 0000000000000000000000000000000000000000..71a646b43ec9c0656ed36959b87e167cdc373cd4 --- /dev/null +++ b/src/Airmap/images/unavailable.svg @@ -0,0 +1,8 @@ + + + + + diff --git a/src/Airmap/images/weather-icons/clear.svg b/src/Airmap/images/weather-icons/clear.svg new file mode 100755 index 0000000000000000000000000000000000000000..5a343228bf0168c46b319850bde1fb092e5a9275 --- /dev/null +++ b/src/Airmap/images/weather-icons/clear.svg @@ -0,0 +1,12 @@ + + + + clear + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/cloudy.svg b/src/Airmap/images/weather-icons/cloudy.svg new file mode 100755 index 0000000000000000000000000000000000000000..33b8f6883fa410c07ddd3264acb2ff685081c460 --- /dev/null +++ b/src/Airmap/images/weather-icons/cloudy.svg @@ -0,0 +1,12 @@ + + + + cloudy + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/cloudy_wind.svg b/src/Airmap/images/weather-icons/cloudy_wind.svg new file mode 100755 index 0000000000000000000000000000000000000000..350f6bd4bf766e5a38a1e12f23ce2eb4a4dfd008 --- /dev/null +++ b/src/Airmap/images/weather-icons/cloudy_wind.svg @@ -0,0 +1,12 @@ + + + + cloudy_wind + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/drizzle.svg b/src/Airmap/images/weather-icons/drizzle.svg new file mode 100755 index 0000000000000000000000000000000000000000..4e3b41122eeb2f3b536735a03fa9a8dc6b1e7bdf --- /dev/null +++ b/src/Airmap/images/weather-icons/drizzle.svg @@ -0,0 +1,12 @@ + + + + drizzle + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/drizzle_day.svg b/src/Airmap/images/weather-icons/drizzle_day.svg new file mode 100755 index 0000000000000000000000000000000000000000..62c8adc308d58ec4734e8017a095fe5c6742ffa3 --- /dev/null +++ b/src/Airmap/images/weather-icons/drizzle_day.svg @@ -0,0 +1,12 @@ + + + + drizzle_day + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/drizzle_night.svg b/src/Airmap/images/weather-icons/drizzle_night.svg new file mode 100755 index 0000000000000000000000000000000000000000..7a95b953d1abe1fc689ab4ac008eae8a4ff7c833 --- /dev/null +++ b/src/Airmap/images/weather-icons/drizzle_night.svg @@ -0,0 +1,12 @@ + + + + drizzle_night + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/foggy.svg b/src/Airmap/images/weather-icons/foggy.svg new file mode 100755 index 0000000000000000000000000000000000000000..4c77893fd9b9ba796bb6a686a90f5d1c0cdda883 --- /dev/null +++ b/src/Airmap/images/weather-icons/foggy.svg @@ -0,0 +1,18 @@ + + + + foggy + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/frigid.svg b/src/Airmap/images/weather-icons/frigid.svg new file mode 100755 index 0000000000000000000000000000000000000000..3fbea9a8f7ceed153160c3e73a08d5a65380a650 --- /dev/null +++ b/src/Airmap/images/weather-icons/frigid.svg @@ -0,0 +1,13 @@ + + + + frigid + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/hail.svg b/src/Airmap/images/weather-icons/hail.svg new file mode 100755 index 0000000000000000000000000000000000000000..58f3ede1b974d144800e96c6a3ea52ae92d0ef3c --- /dev/null +++ b/src/Airmap/images/weather-icons/hail.svg @@ -0,0 +1,12 @@ + + + + hail + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/heavy_rain.svg b/src/Airmap/images/weather-icons/heavy_rain.svg new file mode 100755 index 0000000000000000000000000000000000000000..c955fa19495478aa7926d697e8907b3ea11e0e07 --- /dev/null +++ b/src/Airmap/images/weather-icons/heavy_rain.svg @@ -0,0 +1,12 @@ + + + + heavy_rain + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/hurricane.svg b/src/Airmap/images/weather-icons/hurricane.svg new file mode 100755 index 0000000000000000000000000000000000000000..1a101073a738b4d6c4c74799789c08341b974fe6 --- /dev/null +++ b/src/Airmap/images/weather-icons/hurricane.svg @@ -0,0 +1,14 @@ + + + + hurricane + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/isolated_thunderstorms.svg b/src/Airmap/images/weather-icons/isolated_thunderstorms.svg new file mode 100755 index 0000000000000000000000000000000000000000..c4860704986b220336a6027e63b6e75321563fcc --- /dev/null +++ b/src/Airmap/images/weather-icons/isolated_thunderstorms.svg @@ -0,0 +1,12 @@ + + + + isolated_thunderstorms + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/mostly_clear.svg b/src/Airmap/images/weather-icons/mostly_clear.svg new file mode 100755 index 0000000000000000000000000000000000000000..99465868c4bec1173a0682f1cd692d0b2881f89b --- /dev/null +++ b/src/Airmap/images/weather-icons/mostly_clear.svg @@ -0,0 +1,12 @@ + + + + mostly_clear + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/mostly_cloudy_day.svg b/src/Airmap/images/weather-icons/mostly_cloudy_day.svg new file mode 100755 index 0000000000000000000000000000000000000000..c820b411db5485b6ea50ded43cc45ea82a0d8e1a --- /dev/null +++ b/src/Airmap/images/weather-icons/mostly_cloudy_day.svg @@ -0,0 +1,12 @@ + + + + mostly_cloudy_day + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/mostly_cloudy_night.svg b/src/Airmap/images/weather-icons/mostly_cloudy_night.svg new file mode 100755 index 0000000000000000000000000000000000000000..673b54c7fe8fb13e7a81bf909b250a542b44d18f --- /dev/null +++ b/src/Airmap/images/weather-icons/mostly_cloudy_night.svg @@ -0,0 +1,12 @@ + + + + mostly_cloudy_night + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/mostly_sunny.svg b/src/Airmap/images/weather-icons/mostly_sunny.svg new file mode 100755 index 0000000000000000000000000000000000000000..047ccc4161df0ba25c2c853dfe54c089f9ba1ff0 --- /dev/null +++ b/src/Airmap/images/weather-icons/mostly_sunny.svg @@ -0,0 +1,12 @@ + + + + mostly_sunny + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/partly_cloudy_day.svg b/src/Airmap/images/weather-icons/partly_cloudy_day.svg new file mode 100755 index 0000000000000000000000000000000000000000..456db3b3827bd3646e19b2c88282763657058bf9 --- /dev/null +++ b/src/Airmap/images/weather-icons/partly_cloudy_day.svg @@ -0,0 +1,12 @@ + + + + partly_cloudy_day + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/partly_cloudy_night.svg b/src/Airmap/images/weather-icons/partly_cloudy_night.svg new file mode 100755 index 0000000000000000000000000000000000000000..79374a2544094c627bb6f24dfc4086df59f35b14 --- /dev/null +++ b/src/Airmap/images/weather-icons/partly_cloudy_night.svg @@ -0,0 +1,12 @@ + + + + partyly_cloudy_night + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/rain.svg b/src/Airmap/images/weather-icons/rain.svg new file mode 100755 index 0000000000000000000000000000000000000000..a7ee9edbe932df539095cc8c59ef6b266d58a2d0 --- /dev/null +++ b/src/Airmap/images/weather-icons/rain.svg @@ -0,0 +1,12 @@ + + + + rain + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/rain_snow.svg b/src/Airmap/images/weather-icons/rain_snow.svg new file mode 100755 index 0000000000000000000000000000000000000000..21c2f326385d50103e8f168ba5811bff7d50b3bc --- /dev/null +++ b/src/Airmap/images/weather-icons/rain_snow.svg @@ -0,0 +1,14 @@ + + + + rain_snow + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/scattered_snow_showers_day.svg b/src/Airmap/images/weather-icons/scattered_snow_showers_day.svg new file mode 100755 index 0000000000000000000000000000000000000000..ddaa5635a9fc5876207acf1925afcf9ebed78a62 --- /dev/null +++ b/src/Airmap/images/weather-icons/scattered_snow_showers_day.svg @@ -0,0 +1,14 @@ + + + + scattered_snow_showers_day + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/scattered_snow_showers_night.svg b/src/Airmap/images/weather-icons/scattered_snow_showers_night.svg new file mode 100755 index 0000000000000000000000000000000000000000..11cf9fc396e0510b52c08fb8c0ea41f8e6fba2af --- /dev/null +++ b/src/Airmap/images/weather-icons/scattered_snow_showers_night.svg @@ -0,0 +1,14 @@ + + + + scattered_snow_showers_night + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/scattered_thunderstorms_day.svg b/src/Airmap/images/weather-icons/scattered_thunderstorms_day.svg new file mode 100755 index 0000000000000000000000000000000000000000..22c68ef35bcb06b1d625d7f715122a22fa91e176 --- /dev/null +++ b/src/Airmap/images/weather-icons/scattered_thunderstorms_day.svg @@ -0,0 +1,13 @@ + + + + scattered_thunderstorms_day + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/scattered_thunderstorms_night.svg b/src/Airmap/images/weather-icons/scattered_thunderstorms_night.svg new file mode 100755 index 0000000000000000000000000000000000000000..dd6c2e26742231de86e4ab3ed116cd31e514ada1 --- /dev/null +++ b/src/Airmap/images/weather-icons/scattered_thunderstorms_night.svg @@ -0,0 +1,12 @@ + + + + scattered_thunderstorms_night + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/snow.svg b/src/Airmap/images/weather-icons/snow.svg new file mode 100755 index 0000000000000000000000000000000000000000..26f3fcba3b69e4d805c80ef03f4d6cafb23c0877 --- /dev/null +++ b/src/Airmap/images/weather-icons/snow.svg @@ -0,0 +1,15 @@ + + + + snow + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/snow_storm.svg b/src/Airmap/images/weather-icons/snow_storm.svg new file mode 100755 index 0000000000000000000000000000000000000000..1049f191e33d2229c1a2e774f4a7c746b0ab5729 --- /dev/null +++ b/src/Airmap/images/weather-icons/snow_storm.svg @@ -0,0 +1,15 @@ + + + + snow_storm + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/sunny.svg b/src/Airmap/images/weather-icons/sunny.svg new file mode 100755 index 0000000000000000000000000000000000000000..a5b2d49b3b6721f4459f6de06b58704aacf11420 --- /dev/null +++ b/src/Airmap/images/weather-icons/sunny.svg @@ -0,0 +1,12 @@ + + + + sunny + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/thunderstorm.svg b/src/Airmap/images/weather-icons/thunderstorm.svg new file mode 100755 index 0000000000000000000000000000000000000000..57973e854ff4c6232df9e609bcd9f237c5c8ae0e --- /dev/null +++ b/src/Airmap/images/weather-icons/thunderstorm.svg @@ -0,0 +1,12 @@ + + + + thunderstorm + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/tornado.svg b/src/Airmap/images/weather-icons/tornado.svg new file mode 100755 index 0000000000000000000000000000000000000000..529b69807fd1d99c7deab1e88ea77bb4c4c3a211 --- /dev/null +++ b/src/Airmap/images/weather-icons/tornado.svg @@ -0,0 +1,12 @@ + + + + tornado + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/Airmap/images/weather-icons/unknown.svg b/src/Airmap/images/weather-icons/unknown.svg new file mode 100644 index 0000000000000000000000000000000000000000..4533afdb1992bb14abf5ae401587b6a762b2cdd7 --- /dev/null +++ b/src/Airmap/images/weather-icons/unknown.svg @@ -0,0 +1,14 @@ + + + +sunny +Created with Sketch. + + + + diff --git a/src/Airmap/images/weather-icons/windy.svg b/src/Airmap/images/weather-icons/windy.svg new file mode 100755 index 0000000000000000000000000000000000000000..5b77480d9a5385e737555444795de706ffd96f30 --- /dev/null +++ b/src/Airmap/images/weather-icons/windy.svg @@ -0,0 +1,12 @@ + + + + windy + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/AirspaceManagement/AirspaceAdvisoryProvider.cc b/src/AirspaceManagement/AirspaceAdvisoryProvider.cc new file mode 100644 index 0000000000000000000000000000000000000000..14f3b9c0b442d769a8badafca8a87103bc9e64ec --- /dev/null +++ b/src/AirspaceManagement/AirspaceAdvisoryProvider.cc @@ -0,0 +1,46 @@ +/**************************************************************************** + * + * (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 "AirspaceAdvisoryProvider.h" + +AirspaceAdvisory::AirspaceAdvisory(QObject* parent) + : QObject(parent) +{ +} + +AirspaceAdvisoryProvider::AirspaceAdvisoryProvider(QObject *parent) + : QObject(parent) +{ +} + +//-- TODO: This enum is a bitmask, which implies an airspace can be any +// combination of these types. However, I have not seen any that this +// was the case. + +QString +AirspaceAdvisory::typeStr() +{ + switch(type()) { + case Airport: return QString(tr("Airport")); + case Controlled_airspace: return QString(tr("Controlled Airspace")); + case Special_use_airspace: return QString(tr("Special Use Airspace")); + case Tfr: return QString(tr("TFR")); + case Wildfire: return QString(tr("Wild Fire")); + case Park: return QString(tr("Park")); + case Power_plant: return QString(tr("Power Plant")); + case Heliport: return QString(tr("Heliport")); + case Prison: return QString(tr("Prison")); + case School: return QString(tr("School")); + case Hospital: return QString(tr("Hospital")); + case Fire: return QString(tr("Fire")); + case Emergency: return QString(tr("Emergency")); + case Invalid: return QString(tr("Custom")); + default: return QString(tr("Unknown")); + } +} diff --git a/src/AirspaceManagement/AirspaceAdvisoryProvider.h b/src/AirspaceManagement/AirspaceAdvisoryProvider.h new file mode 100644 index 0000000000000000000000000000000000000000..11bcaff8b0d22791aab9b4ac51508028beefa21e --- /dev/null +++ b/src/AirspaceManagement/AirspaceAdvisoryProvider.h @@ -0,0 +1,104 @@ +/**************************************************************************** + * + * (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 AirspaceAdvisoryProvider.h + * Weather information provided by the Airspace Managemement + */ + +#include "QmlObjectListModel.h" +#include "QGCGeoBoundingCube.h" + +#include +#include + +//----------------------------------------------------------------------------- +class AirspaceAdvisoryProvider : public QObject +{ + Q_OBJECT +public: + + enum AdvisoryColor { + Green, + Yellow, + Orange, + Red + }; + + Q_ENUM(AdvisoryColor) + + AirspaceAdvisoryProvider (QObject *parent = nullptr); + virtual ~AirspaceAdvisoryProvider () {} + + Q_PROPERTY(bool valid READ valid NOTIFY advisoryChanged) + Q_PROPERTY(AdvisoryColor airspaceColor READ airspaceColor NOTIFY advisoryChanged) + Q_PROPERTY(QmlObjectListModel* advisories READ advisories NOTIFY advisoryChanged) + + virtual bool valid () = 0; ///< Current data is valid + virtual AdvisoryColor airspaceColor () = 0; ///< Aispace overall color + virtual QmlObjectListModel* advisories () = 0; ///< List of AirspaceAdvisory + + /** + * Set region of interest that should be queried. When finished, the advisoryChanged() signal will be emmited. + * @param center Center coordinate for ROI + */ + virtual void setROI (const QGCGeoBoundingCube& roi, bool reset = false) = 0; + +signals: + void advisoryChanged (); +}; + +//----------------------------------------------------------------------------- +class AirspaceAdvisory : public QObject +{ + Q_OBJECT +public: + + enum AdvisoryType { + 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, + }; + + Q_ENUM(AdvisoryType) + + AirspaceAdvisory (QObject* parent = nullptr); + + Q_PROPERTY(QString id READ id CONSTANT) + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(AdvisoryType type READ type CONSTANT) + Q_PROPERTY(QString typeStr READ typeStr CONSTANT) + Q_PROPERTY(QGeoCoordinate coordinates READ coordinates CONSTANT) + Q_PROPERTY(qreal radius READ radius CONSTANT) + + Q_PROPERTY(AirspaceAdvisoryProvider::AdvisoryColor color READ color CONSTANT) + + virtual QString id () = 0; + virtual QString name () = 0; + virtual AdvisoryType type () = 0; + virtual QString typeStr (); + virtual QGeoCoordinate coordinates () = 0; + virtual qreal radius () = 0; + + virtual AirspaceAdvisoryProvider::AdvisoryColor color () = 0; +}; + diff --git a/src/AirspaceManagement/AirspaceAuthorization.h b/src/AirspaceManagement/AirspaceAuthorization.h new file mode 100644 index 0000000000000000000000000000000000000000..08d94efce6396a1ba77a60bb33aff124f0dd3d6a --- /dev/null +++ b/src/AirspaceManagement/AirspaceAuthorization.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 + +//----------------------------------------------------------------------------- +/** + * Contains the status of the Airspace authorization + */ +class AirspaceAuthorization : public QObject { + Q_OBJECT +public: + enum PermitStatus { + PermitUnknown = 0, + PermitPending, + PermitAccepted, + PermitRejected, + }; + Q_ENUM(PermitStatus) +}; diff --git a/src/AirspaceManagement/AirspaceFlightPlanProvider.cc b/src/AirspaceManagement/AirspaceFlightPlanProvider.cc new file mode 100644 index 0000000000000000000000000000000000000000..434c5b8745a101ff3bc7791deda4b7a092c5dba7 --- /dev/null +++ b/src/AirspaceManagement/AirspaceFlightPlanProvider.cc @@ -0,0 +1,169 @@ +/**************************************************************************** + * + * (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 "AirspaceManager.h" +#include "AirspaceFlightPlanProvider.h" +#include + +//----------------------------------------------------------------------------- +AirspaceFlightAuthorization::AirspaceFlightAuthorization(QObject *parent) + : QObject(parent) +{ +} + +//----------------------------------------------------------------------------- +AirspaceFlightInfo::AirspaceFlightInfo(QObject *parent) + : QObject(parent) +{ +} + +//----------------------------------------------------------------------------- +AirspaceFlightPlanProvider::AirspaceFlightPlanProvider(QObject *parent) + : QObject(parent) +{ +} + +//----------------------------------------------------------------------------- +AirspaceFlightModel::AirspaceFlightModel(QObject *parent) + : QAbstractListModel(parent) +{ + +} + +//----------------------------------------------------------------------------- +AirspaceFlightInfo* +AirspaceFlightModel::get(int index) +{ + if (index < 0 || index >= _flightEntries.count()) { + return NULL; + } + return _flightEntries[index]; +} + +//----------------------------------------------------------------------------- +int +AirspaceFlightModel::findFlightID(QString flightID) +{ + for(int i = 0; i < _flightEntries.count(); i++) { + if(_flightEntries[i]->flightID() == flightID) { + return i; + } + } + return -1; +} + +//----------------------------------------------------------------------------- +int +AirspaceFlightModel::count() const +{ + return _flightEntries.count(); +} + +//----------------------------------------------------------------------------- +void +AirspaceFlightModel::append(AirspaceFlightInfo* object) +{ + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership); + _flightEntries.append(object); + endInsertRows(); + emit countChanged(); +} + +//----------------------------------------------------------------------------- +void +AirspaceFlightModel::remove(const QString& flightID) +{ + remove(findFlightID(flightID)); +} + +//----------------------------------------------------------------------------- +void +AirspaceFlightModel::remove(int index) +{ + if (index >= 0 && index < _flightEntries.count()) { + beginRemoveRows(QModelIndex(), index, index); + AirspaceFlightInfo* entry = _flightEntries[index]; + if(entry) { + qCDebug(AirspaceManagementLog) << "Deleting flight plan" << entry->flightPlanID(); + entry->deleteLater(); + } + _flightEntries.removeAt(index); + endRemoveRows(); + emit countChanged(); + } +} + +//----------------------------------------------------------------------------- +void +AirspaceFlightModel::clear(void) +{ + if(!_flightEntries.isEmpty()) { + beginResetModel(); + while (_flightEntries.count()) { + AirspaceFlightInfo* entry = _flightEntries.last(); + if(entry) entry->deleteLater(); + _flightEntries.removeLast(); + } + endResetModel(); + emit countChanged(); + } +} + +//----------------------------------------------------------------------------- +static bool +flight_sort(QObject* a, QObject* b) +{ + AirspaceFlightInfo* aa = qobject_cast(a); + AirspaceFlightInfo* bb = qobject_cast(b); + if(!aa || !bb) return false; + return aa->qStartTime() > bb->qStartTime(); +} + +//----------------------------------------------------------------------------- +void +AirspaceFlightModel::sortStartFlight() +{ + beginResetModel(); + std::sort(_flightEntries.begin(), _flightEntries.end(), flight_sort); + endResetModel(); +} + + +//----------------------------------------------------------------------------- +AirspaceFlightInfo* +AirspaceFlightModel::operator[](int index) +{ + return get(index); +} + +//----------------------------------------------------------------------------- +int +AirspaceFlightModel::rowCount(const QModelIndex& /*parent*/) const +{ + return _flightEntries.count(); +} + +//----------------------------------------------------------------------------- +QVariant +AirspaceFlightModel::data(const QModelIndex & index, int role) const { + if (index.row() < 0 || index.row() >= _flightEntries.count()) + return QVariant(); + if (role == ObjectRole) + return QVariant::fromValue(_flightEntries[index.row()]); + return QVariant(); +} + +//----------------------------------------------------------------------------- +QHash +AirspaceFlightModel::roleNames() const { + QHash roles; + roles[ObjectRole] = "flightEntry"; + return roles; +} diff --git a/src/AirspaceManagement/AirspaceFlightPlanProvider.h b/src/AirspaceManagement/AirspaceFlightPlanProvider.h new file mode 100644 index 0000000000000000000000000000000000000000..bd741648eac86389757066d696b0ba9214b25ae4 --- /dev/null +++ b/src/AirspaceManagement/AirspaceFlightPlanProvider.h @@ -0,0 +1,208 @@ +/**************************************************************************** + * + * (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 AirspaceFlightPlanProvider.h + * Create and maintain a flight plan + */ + +#include "AirspaceAdvisoryProvider.h" +#include "QmlObjectListModel.h" + +#include +#include +#include + +class PlanMasterController; +class AirspaceFlightInfo; + +//----------------------------------------------------------------------------- +class AirspaceFlightAuthorization : public QObject +{ + Q_OBJECT +public: + AirspaceFlightAuthorization (QObject *parent = nullptr); + + enum AuthorizationStatus { + Accepted, + Rejected, + Pending, + AcceptedOnSubmission, + RejectedOnSubmission, + Unknown + }; + + Q_ENUM(AuthorizationStatus) + + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString id READ id CONSTANT) + Q_PROPERTY(AuthorizationStatus status READ status CONSTANT) + Q_PROPERTY(QString message READ message CONSTANT) + + virtual QString name () = 0; + virtual QString id () = 0; + virtual AuthorizationStatus status () = 0; + virtual QString message () = 0; + +}; + + +//----------------------------------------------------------------------------- +class AirspaceFlightInfo : public QObject +{ + Q_OBJECT +public: + AirspaceFlightInfo (QObject *parent = nullptr); + + Q_PROPERTY(QString flightID READ flightID CONSTANT) + Q_PROPERTY(QString flightPlanID READ flightPlanID CONSTANT) + Q_PROPERTY(QString createdTime READ createdTime CONSTANT) + Q_PROPERTY(QString startTime READ startTime CONSTANT) + Q_PROPERTY(QString endTime READ endTime CONSTANT) + Q_PROPERTY(QGeoCoordinate takeOff READ takeOff CONSTANT) + Q_PROPERTY(QVariantList boundingBox READ boundingBox CONSTANT) + Q_PROPERTY(bool active READ active NOTIFY activeChanged) + + virtual QString flightID () = 0; + virtual QString flightPlanID () = 0; + virtual QString createdTime () = 0; + virtual QString startTime () = 0; + virtual QDateTime qStartTime () = 0; + virtual QString endTime () = 0; + virtual QGeoCoordinate takeOff () = 0; + virtual QVariantList boundingBox () = 0; + virtual bool active () = 0; + +signals: + void activeChanged (); +}; + +//----------------------------------------------------------------------------- +class AirspaceFlightModel : public QAbstractListModel +{ + Q_OBJECT +public: + + enum QGCLogModelRoles { + ObjectRole = Qt::UserRole + 1 + }; + + AirspaceFlightModel (QObject *parent = 0); + + Q_PROPERTY(int count READ count NOTIFY countChanged) + + Q_INVOKABLE AirspaceFlightInfo* get (int index); + Q_INVOKABLE int findFlightID (QString flightID); + + int count () const; + void append (AirspaceFlightInfo *entry); + void remove (const QString& flightID); + void remove (int index); + void clear (); + void sortStartFlight (); + + AirspaceFlightInfo* + operator[] (int i); + + int rowCount (const QModelIndex & parent = QModelIndex()) const; + QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const; + +signals: + void countChanged (); + +protected: + QHash roleNames() const; +private: + QList _flightEntries; +}; + +//----------------------------------------------------------------------------- +class AirspaceFlightPlanProvider : public QObject +{ + Q_OBJECT +public: + + enum PermitStatus { + PermitNone = 0, //-- No flght plan + PermitPending, + PermitAccepted, + PermitRejected, + PermitNotRequired, + }; + + Q_ENUM(PermitStatus) + + AirspaceFlightPlanProvider (QObject *parent = nullptr); + + ///< Flight Planning and Filing + Q_PROPERTY(QDateTime flightStartTime READ flightStartTime WRITE setFlightStartTime NOTIFY flightStartTimeChanged) ///< Start of flight + Q_PROPERTY(int flightDuration READ flightDuration WRITE setFlightDuration NOTIFY flightDurationChanged) ///< Flight Duration + Q_PROPERTY(bool flightStartsNow READ flightStartsNow WRITE setFlightStartsNow NOTIFY flightStartsNowChanged) + + ///< Flight Briefing + Q_PROPERTY(PermitStatus flightPermitStatus READ flightPermitStatus NOTIFY flightPermitStatusChanged) ///< State of flight permission + Q_PROPERTY(bool valid READ valid NOTIFY advisoryChanged) + Q_PROPERTY(QmlObjectListModel* advisories READ advisories NOTIFY advisoryChanged) + Q_PROPERTY(QmlObjectListModel* ruleSets READ ruleSets NOTIFY advisoryChanged) + Q_PROPERTY(QGCGeoBoundingCube* missionArea READ missionArea NOTIFY missionAreaChanged) + Q_PROPERTY(AirspaceAdvisoryProvider::AdvisoryColor airspaceColor READ airspaceColor NOTIFY advisoryChanged) + Q_PROPERTY(QmlObjectListModel* rulesViolation READ rulesViolation NOTIFY rulesChanged) + Q_PROPERTY(QmlObjectListModel* rulesInfo READ rulesInfo NOTIFY rulesChanged) + Q_PROPERTY(QmlObjectListModel* rulesReview READ rulesReview NOTIFY rulesChanged) + Q_PROPERTY(QmlObjectListModel* rulesFollowing READ rulesFollowing NOTIFY rulesChanged) + Q_PROPERTY(QmlObjectListModel* briefFeatures READ briefFeatures NOTIFY rulesChanged) + Q_PROPERTY(QmlObjectListModel* authorizations READ authorizations NOTIFY rulesChanged) + + ///< Flight Management + Q_PROPERTY(AirspaceFlightModel* flightList READ flightList NOTIFY flightListChanged) + Q_PROPERTY(bool loadingFlightList READ loadingFlightList NOTIFY loadingFlightListChanged) + + //-- TODO: This will submit the current flight plan in memory. + Q_INVOKABLE virtual void submitFlightPlan () = 0; + Q_INVOKABLE virtual void updateFlightPlan () = 0; + Q_INVOKABLE virtual void loadFlightList (QDateTime startTime, QDateTime endTime) = 0; + Q_INVOKABLE virtual void endFlight (QString flighID) = 0; + + virtual PermitStatus flightPermitStatus () const { return PermitNone; } + virtual QDateTime flightStartTime () const = 0; + virtual int flightDuration () const = 0; + virtual bool flightStartsNow () const = 0; + virtual QGCGeoBoundingCube* missionArea () = 0; + virtual bool valid () = 0; ///< Current advisory list is valid + virtual QmlObjectListModel* advisories () = 0; ///< List of AirspaceAdvisory + virtual QmlObjectListModel* ruleSets () = 0; ///< List of AirspaceRuleSet + virtual AirspaceAdvisoryProvider::AdvisoryColor airspaceColor () = 0; ///< Aispace overall color + + virtual QmlObjectListModel* rulesViolation () = 0; ///< List of AirspaceRule in violation + virtual QmlObjectListModel* rulesInfo () = 0; ///< List of AirspaceRule need more information + virtual QmlObjectListModel* rulesReview () = 0; ///< List of AirspaceRule should review + virtual QmlObjectListModel* rulesFollowing () = 0; ///< List of AirspaceRule following + virtual QmlObjectListModel* briefFeatures () = 0; ///< List of AirspaceRule in violation + virtual QmlObjectListModel* authorizations () = 0; ///< List of AirspaceFlightAuthorization + virtual AirspaceFlightModel*flightList () = 0; ///< List of AirspaceFlightInfo + virtual bool loadingFlightList () = 0; + + virtual void setFlightStartTime (QDateTime start) = 0; + virtual void setFlightDuration (int seconds) = 0; + virtual void setFlightStartsNow (bool now) = 0; + virtual void startFlightPlanning (PlanMasterController* planController) = 0; + +signals: + void flightPermitStatusChanged (); + void flightStartTimeChanged (); + void flightStartsNowChanged (); + void flightDurationChanged (); + void advisoryChanged (); + void missionAreaChanged (); + void rulesChanged (); + void flightListChanged (); + void loadingFlightListChanged (); +}; diff --git a/src/AirspaceManagement/AirspaceManager.cc b/src/AirspaceManagement/AirspaceManager.cc new file mode 100644 index 0000000000000000000000000000000000000000..26b9667ac242979399d6e1f08868f66f9e683f76 --- /dev/null +++ b/src/AirspaceManagement/AirspaceManager.cc @@ -0,0 +1,160 @@ +/**************************************************************************** + * + * (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 "AirspaceAdvisoryProvider.h" +#include "AirspaceFlightPlanProvider.h" +#include "AirspaceManager.h" +#include "AirspaceRestriction.h" +#include "AirspaceRestrictionProvider.h" +#include "AirspaceRulesetsProvider.h" +#include "AirspaceVehicleManager.h" +#include "AirspaceWeatherInfoProvider.h" + +#include "Vehicle.h" +#include "QGCApplication.h" + +QGC_LOGGING_CATEGORY(AirspaceManagementLog, "AirspaceManagementLog") + +//----------------------------------------------------------------------------- +AirspaceManager::AirspaceManager(QGCApplication* app, QGCToolbox* toolbox) + : QGCTool(app, toolbox) +{ + _ruleUpdateTimer.setInterval(2000); + _ruleUpdateTimer.setSingleShot(true); + _updateTimer.setInterval(1000); + _updateTimer.setSingleShot(true); + connect(&_ruleUpdateTimer, &QTimer::timeout, this, &AirspaceManager::_updateRulesTimeout); + connect(&_updateTimer, &QTimer::timeout, this, &AirspaceManager::_updateTimeout); + qmlRegisterUncreatableType ("QGroundControl.Airspace", 1, 0, "AirspaceAdvisoryProvider", "Reference only"); + qmlRegisterUncreatableType ("QGroundControl.Airspace", 1, 0, "AirspaceFlightPlanProvider", "Reference only"); + qmlRegisterUncreatableType ("QGroundControl.Airspace", 1, 0, "AirspaceManager", "Reference only"); + qmlRegisterUncreatableType ("QGroundControl.Airspace", 1, 0, "AirspaceRestrictionProvider", "Reference only"); + qmlRegisterUncreatableType ("QGroundControl.Airspace", 1, 0, "AirspaceRule", "Reference only"); + qmlRegisterUncreatableType ("QGroundControl.Airspace", 1, 0, "AirspaceRuleFeature", "Reference only"); + qmlRegisterUncreatableType ("QGroundControl.Airspace", 1, 0, "AirspaceRuleSet", "Reference only"); + qmlRegisterUncreatableType ("QGroundControl.Airspace", 1, 0, "AirspaceRulesetsProvider", "Reference only"); + qmlRegisterUncreatableType ("QGroundControl.Airspace", 1, 0, "AirspaceWeatherInfoProvider", "Reference only"); + qmlRegisterUncreatableType ("QGroundControl.Airspace", 1, 0, "AirspaceFlightAuthorization", "Reference only"); + qmlRegisterUncreatableType ("QGroundControl.Airspace", 1, 0, "AirspaceFlightInfo", "Reference only"); +} + +//----------------------------------------------------------------------------- +AirspaceManager::~AirspaceManager() +{ + if(_advisories) { + delete _advisories; + } + if(_weatherProvider) { + delete _weatherProvider; + } + if(_ruleSetsProvider) { + delete _ruleSetsProvider; + } + if(_airspaces) { + delete _airspaces; + } + if(_flightPlan) { + delete _flightPlan; + } +} + +//----------------------------------------------------------------------------- +void +AirspaceManager::setToolbox(QGCToolbox* toolbox) +{ + QGCTool::setToolbox(toolbox); + // We should not call virtual methods in the constructor, so we instantiate the restriction provider here + _ruleSetsProvider = _instantiateRulesetsProvider(); + _weatherProvider = _instatiateAirspaceWeatherInfoProvider(); + _advisories = _instatiateAirspaceAdvisoryProvider(); + _airspaces = _instantiateAirspaceRestrictionProvider(); + _flightPlan = _instantiateAirspaceFlightPlanProvider(); + //-- Keep track of rule changes + if(_ruleSetsProvider) { + connect(_ruleSetsProvider, &AirspaceRulesetsProvider::selectedRuleSetsChanged, this, &AirspaceManager::_rulesChanged); + } +} + +//----------------------------------------------------------------------------- +void +AirspaceManager::setROI(const QGeoCoordinate& pointNW, const QGeoCoordinate& pointSE, bool planView, bool reset) +{ + if(planView) { + //-- Is there a mission? + if(_flightPlan->flightPermitStatus() != AirspaceFlightPlanProvider::PermitNone) { + //-- Is there a polygon to work with? + if(_flightPlan->missionArea()->isValid() && _flightPlan->missionArea()->area() > 0.0) { + if(reset) { + _roi = *_flightPlan->missionArea(); + _updateToROI(true); + } else { + _setROI(*_flightPlan->missionArea()); + } + return; + } + } + } + //-- Use screen coordinates (what you see is what you get) + if(reset) { + _roi = QGCGeoBoundingCube(pointNW, pointSE); + _updateToROI(true); + } else { + _setROI(QGCGeoBoundingCube(pointNW, pointSE)); + } +} + +//----------------------------------------------------------------------------- +void +AirspaceManager::_setROI(const QGCGeoBoundingCube& roi) +{ + if(_roi != roi) { + _roi = roi; + _updateTimer.start(); + } +} + +//----------------------------------------------------------------------------- +void +AirspaceManager::_updateToROI(bool reset) +{ + if(_airspaces) { + _airspaces->setROI(_roi, reset); + } + if(_ruleSetsProvider) { + _ruleSetsProvider->setROI(_roi, reset); + } + if(_weatherProvider) { + _weatherProvider->setROI(_roi, reset); + } +} + + +//----------------------------------------------------------------------------- +void +AirspaceManager::_updateTimeout() +{ + _updateToROI(false); +} + +//----------------------------------------------------------------------------- +void +AirspaceManager::_rulesChanged() +{ + _ruleUpdateTimer.start(); +} + +//----------------------------------------------------------------------------- +void +AirspaceManager::_updateRulesTimeout() +{ + if (_advisories) { + _advisories->setROI(_roi, true); + } +} diff --git a/src/AirspaceManagement/AirspaceManager.h b/src/AirspaceManagement/AirspaceManager.h new file mode 100644 index 0000000000000000000000000000000000000000..a9f7d1059179b166a76e0d8119a45e165ae3cee1 --- /dev/null +++ b/src/AirspaceManagement/AirspaceManager.h @@ -0,0 +1,148 @@ +/**************************************************************************** + * + * (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 AirspaceManager.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. + * - AirspaceVehicleManager + * this provides the multi-vehicle support - each vehicle has an instance + * - AirspaceAdvisoriesProvider + * Provides airspace advisories and restrictions. Currently only used by AirspaceManager, but + * each vehicle could have its own restrictions. + */ + +#include "QGCToolbox.h" +#include "QGCLoggingCategory.h" +#include "QmlObjectListModel.h" +#include "QGCGeoBoundingCube.h" + +#include +#include +#include +#include +#include + +class AirspaceAdvisoryProvider; +class AirspaceFlightPlanProvider; +class AirspaceRestrictionProvider; +class AirspaceRulesetsProvider; +class AirspaceVehicleManager; +class AirspaceWeatherInfoProvider; +class PlanMasterController; +class QGCApplication; +class Vehicle; + +Q_DECLARE_LOGGING_CATEGORY(AirspaceManagementLog) + +//----------------------------------------------------------------------------- +/** + * @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() override; + + enum AuthStatus { + Unknown, + Anonymous, + Authenticated, + Error + }; + + Q_ENUM(AuthStatus) + + Q_PROPERTY(QString providerName READ providerName CONSTANT) + Q_PROPERTY(AirspaceWeatherInfoProvider* weatherInfo READ weatherInfo CONSTANT) + Q_PROPERTY(AirspaceAdvisoryProvider* advisories READ advisories CONSTANT) + Q_PROPERTY(AirspaceRulesetsProvider* ruleSets READ ruleSets CONSTANT) + Q_PROPERTY(AirspaceRestrictionProvider* airspaces READ airspaces CONSTANT) + Q_PROPERTY(AirspaceFlightPlanProvider* flightPlan READ flightPlan CONSTANT) + Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged) + Q_PROPERTY(QString connectStatus READ connectStatus NOTIFY connectStatusChanged) + Q_PROPERTY(AirspaceManager::AuthStatus authStatus READ authStatus NOTIFY authStatusChanged) + Q_PROPERTY(bool airspaceVisible READ airspaceVisible WRITE setAirspaceVisible NOTIFY airspaceVisibleChanged) + + Q_INVOKABLE void setROI (const QGeoCoordinate& pointNW, const QGeoCoordinate& pointSE, bool planView, bool reset = false); + + AirspaceWeatherInfoProvider* weatherInfo () { return _weatherProvider; } + AirspaceAdvisoryProvider* advisories () { return _advisories; } + AirspaceRulesetsProvider* ruleSets () { return _ruleSetsProvider; } + AirspaceRestrictionProvider* airspaces () { return _airspaces; } + AirspaceFlightPlanProvider* flightPlan () { return _flightPlan; } + + void setToolbox(QGCToolbox* toolbox) override; + + virtual QString providerName () const = 0; ///< Name of the airspace management provider (used in the UI) + + virtual bool airspaceVisible () { return _airspaceVisible; } + virtual void setAirspaceVisible (bool set) { _airspaceVisible = set; emit airspaceVisibleChanged(); } + virtual bool connected () const = 0; + virtual QString connectStatus () const { return QString(); } + virtual double maxAreaOfInterest() const { return _maxAreaOfInterest; } + + virtual AirspaceManager::AuthStatus authStatus () const { return Anonymous; } + + /** + * Factory method to create an AirspaceVehicleManager object + */ + virtual AirspaceVehicleManager* instantiateVehicle (const Vehicle& vehicle) = 0; + +signals: + void airspaceVisibleChanged (); + void connectedChanged (); + void connectStatusChanged (); + void authStatusChanged (); + +protected: + /** + * 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 + */ + virtual void _setROI (const QGCGeoBoundingCube& roi); + + /** + * Factory methods + */ + virtual AirspaceRulesetsProvider* _instantiateRulesetsProvider () = 0; + virtual AirspaceWeatherInfoProvider* _instatiateAirspaceWeatherInfoProvider () = 0; + virtual AirspaceAdvisoryProvider* _instatiateAirspaceAdvisoryProvider () = 0; + virtual AirspaceRestrictionProvider* _instantiateAirspaceRestrictionProvider () = 0; + virtual AirspaceFlightPlanProvider* _instantiateAirspaceFlightPlanProvider () = 0; + +protected: + bool _airspaceVisible = false; + AirspaceRulesetsProvider* _ruleSetsProvider = nullptr; ///< Rulesets + AirspaceWeatherInfoProvider* _weatherProvider = nullptr; ///< Weather info + AirspaceAdvisoryProvider* _advisories = nullptr; ///< Advisory info + AirspaceRestrictionProvider* _airspaces = nullptr; ///< Airspace info + AirspaceFlightPlanProvider* _flightPlan = nullptr; ///< Flight plan management + double _maxAreaOfInterest = 500.0; ///< Ignore area larger than 500km^2 + QTimer _ruleUpdateTimer; + QTimer _updateTimer; + QGCGeoBoundingCube _roi; + +private slots: + void _updateRulesTimeout (); + void _updateTimeout (); + void _rulesChanged (); + +private: + void _updateToROI (bool reset = false); + +}; diff --git a/src/AirspaceManagement/AirspaceRestriction.cc b/src/AirspaceManagement/AirspaceRestriction.cc new file mode 100644 index 0000000000000000000000000000000000000000..150f87f25d3284d2cfa51a8c5901acc7a868c292 --- /dev/null +++ b/src/AirspaceManagement/AirspaceRestriction.cc @@ -0,0 +1,35 @@ +/**************************************************************************** + * + * (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 "AirspaceRestriction.h" + +AirspaceRestriction::AirspaceRestriction(QString advisoryID, QColor color, QColor lineColor, float lineWidth, QObject* parent) + : QObject(parent) + , _advisoryID(advisoryID) + , _color(color) + , _lineColor(lineColor) + , _lineWidth(lineWidth) +{ +} + +AirspacePolygonRestriction::AirspacePolygonRestriction(const QVariantList& polygon, QString advisoryID, QColor color, QColor lineColor, float lineWidth, QObject* parent) + : AirspaceRestriction(advisoryID, color, lineColor, lineWidth, parent) + , _polygon(polygon) +{ + +} + +AirspaceCircularRestriction::AirspaceCircularRestriction(const QGeoCoordinate& center, double radius, QString advisoryID, QColor color, QColor lineColor, float lineWidth, QObject* parent) + : AirspaceRestriction(advisoryID, color, lineColor, lineWidth, parent) + , _center(center) + , _radius(radius) +{ + +} + diff --git a/src/AirspaceManagement/AirspaceRestriction.h b/src/AirspaceManagement/AirspaceRestriction.h new file mode 100644 index 0000000000000000000000000000000000000000..64f1698a1385fd622ca7b47ceca06f15e8e4c1a7 --- /dev/null +++ b/src/AirspaceManagement/AirspaceRestriction.h @@ -0,0 +1,76 @@ +/**************************************************************************** + * + * (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 +#include +#include +#include + +/** + * @class AirspaceRestriction + * Base classe for an airspace restriction + */ + +class AirspaceRestriction : public QObject +{ + Q_OBJECT +public: + AirspaceRestriction(QString advisoryID, QColor color, QColor lineColor, float lineWidth, QObject* parent = nullptr); + Q_PROPERTY(QString advisoryID READ advisoryID CONSTANT) + Q_PROPERTY(QColor color READ color CONSTANT) + Q_PROPERTY(QColor lineColor READ lineColor CONSTANT) + Q_PROPERTY(float lineWidth READ lineWidth CONSTANT) + QString advisoryID () { return _advisoryID; } + QColor color () { return _color; } + QColor lineColor () { return _lineColor; } + float lineWidth () { return _lineWidth; } +protected: + QString _advisoryID; + QColor _color; + QColor _lineColor; + float _lineWidth; +}; + +/** + * @class AirspacePolygonRestriction + * Base classe for an airspace restriction defined by a polygon + */ + +class AirspacePolygonRestriction : public AirspaceRestriction +{ + Q_OBJECT +public: + AirspacePolygonRestriction(const QVariantList& polygon, QString advisoryID, QColor color, QColor lineColor, float lineWidth, QObject* parent = nullptr); + Q_PROPERTY(QVariantList polygon READ polygon CONSTANT) + QVariantList polygon() { return _polygon; } +private: + QVariantList _polygon; +}; + +/** + * @class AirspaceRestriction + * Base classe for an airspace restriction defined by a circle + */ + +class AirspaceCircularRestriction : public AirspaceRestriction +{ + Q_OBJECT +public: + AirspaceCircularRestriction(const QGeoCoordinate& center, double radius, QString advisoryID, QColor color, QColor lineColor, float lineWidth, QObject* parent = nullptr); + Q_PROPERTY(QGeoCoordinate center READ center CONSTANT) + Q_PROPERTY(double radius READ radius CONSTANT) + QGeoCoordinate center () { return _center; } + double radius () { return _radius; } +private: + QGeoCoordinate _center; + double _radius; +}; + diff --git a/src/AirspaceManagement/AirspaceRestrictionProvider.cc b/src/AirspaceManagement/AirspaceRestrictionProvider.cc new file mode 100644 index 0000000000000000000000000000000000000000..191c5bd938c821bbd465ead484e6639f3706e013 --- /dev/null +++ b/src/AirspaceManagement/AirspaceRestrictionProvider.cc @@ -0,0 +1,15 @@ +/**************************************************************************** + * + * (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 "AirspaceRestrictionProvider.h" + +AirspaceRestrictionProvider::AirspaceRestrictionProvider(QObject *parent) + : QObject(parent) +{ +} diff --git a/src/AirspaceManagement/AirspaceRestrictionProvider.h b/src/AirspaceManagement/AirspaceRestrictionProvider.h new file mode 100644 index 0000000000000000000000000000000000000000..f94dff845a363503440b6e87650c82fa4e33d7c1 --- /dev/null +++ b/src/AirspaceManagement/AirspaceRestrictionProvider.h @@ -0,0 +1,45 @@ +/**************************************************************************** + * + * (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 + +/** + * @class AirspaceRestrictionProvider + * Base class that queries for airspace restrictions + */ + +#include "QmlObjectListModel.h" +#include "QGCGeoBoundingCube.h" + +#include +#include +#include + +class AirspacePolygonRestriction; +class AirspaceCircularRestriction; + +class AirspaceRestrictionProvider : public QObject { + Q_OBJECT +public: + AirspaceRestrictionProvider (QObject* parent = nullptr); + ~AirspaceRestrictionProvider () = default; + + Q_PROPERTY(QmlObjectListModel* polygons READ polygons CONSTANT) + Q_PROPERTY(QmlObjectListModel* circles READ circles CONSTANT) + + /** + * 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 QGCGeoBoundingCube& roi, bool reset = false) = 0; + + virtual QmlObjectListModel* polygons () = 0; ///< List of AirspacePolygonRestriction objects + virtual QmlObjectListModel* circles () = 0; ///< List of AirspaceCircularRestriction objects +}; diff --git a/src/AirspaceManagement/AirspaceRulesetsProvider.cc b/src/AirspaceManagement/AirspaceRulesetsProvider.cc new file mode 100644 index 0000000000000000000000000000000000000000..113d62a374f28709d4c3ca49bd822c01877d0882 --- /dev/null +++ b/src/AirspaceManagement/AirspaceRulesetsProvider.cc @@ -0,0 +1,30 @@ +/**************************************************************************** + * + * (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 "AirspaceRulesetsProvider.h" + +AirspaceRuleFeature::AirspaceRuleFeature(QObject* parent) + : QObject(parent) +{ +} + +AirspaceRule::AirspaceRule(QObject *parent) + : QObject(parent) +{ +} + +AirspaceRuleSet::AirspaceRuleSet(QObject* parent) + : QObject(parent) +{ +} + +AirspaceRulesetsProvider::AirspaceRulesetsProvider(QObject *parent) + : QObject(parent) +{ +} diff --git a/src/AirspaceManagement/AirspaceRulesetsProvider.h b/src/AirspaceManagement/AirspaceRulesetsProvider.h new file mode 100644 index 0000000000000000000000000000000000000000..a2804c8303fa10e25af151c3f47c3bb25348a9a9 --- /dev/null +++ b/src/AirspaceManagement/AirspaceRulesetsProvider.h @@ -0,0 +1,173 @@ +/**************************************************************************** + * + * (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 + +//----------------------------------------------------------------------------- +/** + * @class AirspaceRulesetsProvider + * Base class that queries for airspace rulesets + */ + +#include "QmlObjectListModel.h" +#include "QGCGeoBoundingCube.h" + +#include +#include + +//----------------------------------------------------------------------------- +class AirspaceRuleFeature : public QObject +{ + Q_OBJECT +public: + + enum Type { + Unknown, + Boolean, + Float, + String, + }; + + enum Measurement { + UnknownMeasurement, + Speed, + Weight, + Distance, + }; + + enum Unit { + UnknownUnit, + Kilogram, + Meters, + MetersPerSecond, + }; + + Q_ENUM(Type) + Q_ENUM(Measurement) + Q_ENUM(Unit) + + AirspaceRuleFeature(QObject* parent = nullptr); + + Q_PROPERTY(quint32 id READ id CONSTANT) + Q_PROPERTY(Type type READ type CONSTANT) + Q_PROPERTY(Unit unit READ unit CONSTANT) + Q_PROPERTY(Measurement measurement READ measurement CONSTANT) + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString description READ description CONSTANT) + Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged) + + virtual quint32 id () = 0; + virtual Type type () = 0; + virtual Unit unit () = 0; + virtual Measurement measurement () = 0; + virtual QString name () = 0; + virtual QString description () = 0; + virtual QVariant value () = 0; + virtual void setValue (const QVariant val) = 0; + +signals: + void valueChanged (); +}; + +//----------------------------------------------------------------------------- +class AirspaceRule : public QObject +{ + Q_OBJECT +public: + + enum Status { + Conflicting, ///< The rule is conflicting. + MissingInfo, ///< The evaluation requires further information. + NotConflicting, ///< The rule is not conflicting, all good to go. + Informational, ///< The rule is of informational nature. + Unknown, ///< The status of the rule is unknown. + }; + + Q_ENUM(Status) + + AirspaceRule(QObject* parent = nullptr); + + Q_PROPERTY(Status status READ status CONSTANT) + Q_PROPERTY(QString shortText READ shortText CONSTANT) + Q_PROPERTY(QString description READ description CONSTANT) + Q_PROPERTY(QmlObjectListModel* features READ features CONSTANT) + + virtual Status status () = 0; + virtual QString shortText () = 0; + virtual QString description () = 0; + virtual QmlObjectListModel* features () = 0; ///< List of AirspaceRuleFeature +}; + +//----------------------------------------------------------------------------- +class AirspaceRuleSet : public QObject +{ + Q_OBJECT +public: + + enum 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. + }; + + Q_ENUM(SelectionType) + + AirspaceRuleSet(QObject* parent = nullptr); + + Q_PROPERTY(QString id READ id CONSTANT) + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString shortName READ shortName CONSTANT) + Q_PROPERTY(QString description READ description CONSTANT) + Q_PROPERTY(bool isDefault READ isDefault CONSTANT) + Q_PROPERTY(SelectionType selectionType READ selectionType CONSTANT) + Q_PROPERTY(bool selected READ selected WRITE setSelected NOTIFY selectedChanged) + Q_PROPERTY(QmlObjectListModel* rules READ rules CONSTANT) + + virtual QString id () = 0; + virtual QString description () = 0; + virtual bool isDefault () = 0; + virtual QString name () = 0; + virtual QString shortName () = 0; + virtual SelectionType selectionType () = 0; + virtual bool selected () = 0; + virtual void setSelected (bool sel) = 0; + virtual QmlObjectListModel* rules () = 0; ///< List of AirspaceRule + +signals: + void selectedChanged (); + +}; + +//----------------------------------------------------------------------------- +class AirspaceRulesetsProvider : public QObject { + Q_OBJECT +public: + AirspaceRulesetsProvider (QObject* parent = nullptr); + ~AirspaceRulesetsProvider () = default; + + Q_PROPERTY(bool valid READ valid NOTIFY ruleSetsChanged) + Q_PROPERTY(QString selectedRuleSets READ selectedRuleSets NOTIFY selectedRuleSetsChanged) + Q_PROPERTY(QmlObjectListModel* ruleSets READ ruleSets NOTIFY ruleSetsChanged) + + Q_INVOKABLE virtual void clearAllFeatures() {;} ///< Clear all saved (persistent) feature values + + virtual bool valid () = 0; ///< Current ruleset is valid + virtual QmlObjectListModel* ruleSets () = 0; ///< List of AirspaceRuleSet + virtual QString selectedRuleSets() = 0; ///< All selected rules concatenated into a string + /** + * Set region of interest that should be queried. When finished, the rulesChanged() signal will be emmited. + * @param center Center coordinate for ROI + */ + virtual void setROI (const QGCGeoBoundingCube& roi, bool reset = false) = 0; + +signals: + void ruleSetsChanged (); + void selectedRuleSetsChanged (); +}; + diff --git a/src/AirspaceManagement/AirspaceVehicleManager.cc b/src/AirspaceManagement/AirspaceVehicleManager.cc new file mode 100644 index 0000000000000000000000000000000000000000..38c5723108cc1918e02b12d06fbff9f042ddf209 --- /dev/null +++ b/src/AirspaceManagement/AirspaceVehicleManager.cc @@ -0,0 +1,40 @@ +/**************************************************************************** + * + * (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 "AirspaceManager.h" +#include "AirspaceVehicleManager.h" +#include "Vehicle.h" +#include "MissionItem.h" + +AirspaceVehicleManager::AirspaceVehicleManager(const Vehicle& vehicle) + : _vehicle(vehicle) +{ + qCDebug(AirspaceManagementLog) << "Instatiating AirspaceVehicleManager"; + connect(&_vehicle, &Vehicle::armedChanged, this, &AirspaceVehicleManager::_vehicleArmedChanged); + connect(&_vehicle, &Vehicle::mavlinkMessageReceived, this, &AirspaceVehicleManager::vehicleMavlinkMessageReceived); +} + +void AirspaceVehicleManager::_vehicleArmedChanged(bool armed) +{ + if (armed) { + qCDebug(AirspaceManagementLog) << "Starting telemetry"; + startTelemetryStream(); + _vehicleWasInMissionMode = _vehicle.flightMode() == _vehicle.missionFlightMode(); + } else { + qCDebug(AirspaceManagementLog) << "Stopping telemetry"; + 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/AirspaceManagement/AirspaceVehicleManager.h b/src/AirspaceManagement/AirspaceVehicleManager.h new file mode 100644 index 0000000000000000000000000000000000000000..9e8bea279bdddb3a846ce3744d94f6aedc254291 --- /dev/null +++ b/src/AirspaceManagement/AirspaceVehicleManager.h @@ -0,0 +1,58 @@ +/**************************************************************************** + * + * (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 "AirspaceFlightPlanProvider.h" +#include "QGCMAVLink.h" + +#include +#include +#include + +class Vehicle; + +//----------------------------------------------------------------------------- +/** + * @class AirspaceVehicleManager + * Base class for per-vehicle management (each vehicle has one (or zero) of these) + */ + +class AirspaceVehicleManager : public QObject { + Q_OBJECT +public: + AirspaceVehicleManager (const Vehicle& vehicle); + virtual ~AirspaceVehicleManager () = default; + + /** + * Setup the connection and start sending telemetry + */ + virtual void startTelemetryStream () = 0; + virtual void stopTelemetryStream () = 0; + virtual bool isTelemetryStreaming () = 0; + +public slots: + virtual void endFlight () = 0; + +signals: + void trafficUpdate (bool alert, 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/AirspaceManagement/AirspaceWeatherInfoProvider.cc b/src/AirspaceManagement/AirspaceWeatherInfoProvider.cc new file mode 100644 index 0000000000000000000000000000000000000000..231b767861ac24ccf40bb425d0ff16f93614d978 --- /dev/null +++ b/src/AirspaceManagement/AirspaceWeatherInfoProvider.cc @@ -0,0 +1,15 @@ +/**************************************************************************** + * + * (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 "AirspaceWeatherInfoProvider.h" + +AirspaceWeatherInfoProvider::AirspaceWeatherInfoProvider(QObject *parent) + : QObject(parent) +{ +} diff --git a/src/AirspaceManagement/AirspaceWeatherInfoProvider.h b/src/AirspaceManagement/AirspaceWeatherInfoProvider.h new file mode 100644 index 0000000000000000000000000000000000000000..a8972ad42b15c33244e73b5f5d27336c5aa805f8 --- /dev/null +++ b/src/AirspaceManagement/AirspaceWeatherInfoProvider.h @@ -0,0 +1,58 @@ +/**************************************************************************** + * + * (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 AirspaceWeatherInfoProvider.h + * Weather information provided by the Airspace Managemement + */ + +#include "QGCGeoBoundingCube.h" +#include +#include + +class AirspaceWeatherInfoProvider : public QObject +{ + Q_OBJECT +public: + AirspaceWeatherInfoProvider(QObject *parent = nullptr); + virtual ~AirspaceWeatherInfoProvider() {} + + Q_PROPERTY(bool valid READ valid NOTIFY weatherChanged) + Q_PROPERTY(QString condition READ condition NOTIFY weatherChanged) + Q_PROPERTY(QString icon READ icon NOTIFY weatherChanged) + Q_PROPERTY(quint32 windHeading READ windHeading NOTIFY weatherChanged) + Q_PROPERTY(float windSpeed READ windSpeed NOTIFY weatherChanged) + Q_PROPERTY(quint32 windGusting READ windGusting NOTIFY weatherChanged) + Q_PROPERTY(float temperature READ temperature NOTIFY weatherChanged) + Q_PROPERTY(float humidity READ humidity NOTIFY weatherChanged) + Q_PROPERTY(float visibility READ visibility NOTIFY weatherChanged) + Q_PROPERTY(float precipitation READ precipitation NOTIFY weatherChanged) + + virtual bool valid () = 0; ///< Current weather data is valid + virtual QString condition () = 0; ///< The overall weather condition. + virtual QString icon () = 0; ///< 2:1 Aspect ratio icon url ready to be used by an Image QML Item + virtual quint32 windHeading () = 0; ///< The heading in [°]. + virtual float windSpeed () = 0; ///< The speed in [°]. + virtual quint32 windGusting () = 0; + virtual float temperature () = 0; ///< The temperature in [°C]. + virtual float humidity () = 0; + virtual float visibility () = 0; ///< Visibility in [m]. + virtual float precipitation () = 0; ///< The probability of precipitation in [%]. + + /** + * Set region of interest that should be queried. When finished, the weatherChanged() signal will be emmited. + * @param center Center coordinate for ROI + */ + virtual void setROI (const QGCGeoBoundingCube& roi, bool reset = false) = 0; + +signals: + void weatherChanged (); +}; diff --git a/src/AutoPilotPlugins/APM/APMCompassCal.cc b/src/AutoPilotPlugins/APM/APMCompassCal.cc index 2c8d0e8ea7f55844937e14ea2719b335bb66cc98..d07d148fdd4c1e57cead35fa68c43bb01bd07585 100644 --- a/src/AutoPilotPlugins/APM/APMCompassCal.cc +++ b/src/AutoPilotPlugins/APM/APMCompassCal.cc @@ -526,6 +526,7 @@ int CalWorkerThread::sphere_fit_least_squares(const float x[], const float y[], //Iterate N times, ignore stop condition. unsigned int n = 0; +#undef FLT_EPSILON #define FLT_EPSILON 1.1920929e-07F /* 1E-5 */ while (n < max_iterations) { diff --git a/src/FlightDisplay/FlightDisplayView.qml b/src/FlightDisplay/FlightDisplayView.qml index 1eb9e52b9cefbfa02e88bdf6ab7c1e3df8557599..dcc9e410292361e229ce7be7c11a759d11d4e6bb 100644 --- a/src/FlightDisplay/FlightDisplayView.qml +++ b/src/FlightDisplay/FlightDisplayView.qml @@ -19,14 +19,15 @@ import QtQuick.Window 2.2 import QtQml.Models 2.1 import QGroundControl 1.0 +import QGroundControl.Airspace 1.0 +import QGroundControl.Controllers 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.FactSystem 1.0 import QGroundControl.FlightDisplay 1.0 import QGroundControl.FlightMap 1.0 -import QGroundControl.ScreenTools 1.0 -import QGroundControl.Controls 1.0 import QGroundControl.Palette 1.0 +import QGroundControl.ScreenTools 1.0 import QGroundControl.Vehicle 1.0 -import QGroundControl.Controllers 1.0 -import QGroundControl.FactSystem 1.0 /// Flight Display View QGCView { @@ -665,12 +666,57 @@ QGCView { } } + //-- 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: QGroundControl.airmapSupported && _mainIsMap && flightPermit && flightPermit !== AirspaceFlightPlanProvider.PermitNone && !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 === AirspaceFlightPlanProvider.PermitPending) + return qsTr("Approval Pending") + if(airspaceIndicator.flightPermit === AirspaceFlightPlanProvider.PermitAccepted || airspaceIndicator.flightPermit === AirspaceFlightPlanProvider.PermitNotRequired) + return qsTr("Flight Approved") + if(airspaceIndicator.flightPermit === AirspaceFlightPlanProvider.PermitRejected) + return qsTr("Flight Rejected") + } + return "" + } + color: { + if(airspaceIndicator.flightPermit) { + if(airspaceIndicator.flightPermit === AirspaceFlightPlanProvider.PermitPending) + return qgcPal.colorOrange + if(airspaceIndicator.flightPermit === AirspaceFlightPlanProvider.PermitAccepted || airspaceIndicator.flightPermit === AirspaceFlightPlanProvider.PermitNotRequired) + return qgcPal.colorGreen + } + return qgcPal.colorRed + } + anchors.verticalCenter: parent.verticalCenter; + } + } + property var flightPermit: QGroundControl.airmapSupported ? QGroundControl.airspaceManager.flightPlan.flightPermitStatus : null + property string providerName: QGroundControl.airspaceManager.providerName + } + //-- Checklist GUI Component { id: checklistDropPanel - PreFlightCheckList { model: preFlightCheckModel } - } //Component -} //QGC View + } +} diff --git a/src/FlightDisplay/FlightDisplayViewMap.qml b/src/FlightDisplay/FlightDisplayViewMap.qml index d09e2ecf158b1b1cffb4b7423245a3769e541d9e..d6a92e863f71620337845be9b271c85aa743ee3e 100644 --- a/src/FlightDisplay/FlightDisplayViewMap.qml +++ b/src/FlightDisplay/FlightDisplayViewMap.qml @@ -15,13 +15,14 @@ import QtPositioning 5.3 import QtQuick.Dialogs 1.2 import QGroundControl 1.0 +import QGroundControl.Airspace 1.0 +import QGroundControl.Controllers 1.0 +import QGroundControl.Controls 1.0 import QGroundControl.FlightDisplay 1.0 import QGroundControl.FlightMap 1.0 -import QGroundControl.ScreenTools 1.0 -import QGroundControl.Controls 1.0 import QGroundControl.Palette 1.0 +import QGroundControl.ScreenTools 1.0 import QGroundControl.Vehicle 1.0 -import QGroundControl.Controllers 1.0 FlightMap { id: flightMap @@ -50,13 +51,31 @@ FlightMap { property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle property var _activeVehicleCoordinate: _activeVehicle ? _activeVehicle.coordinate : QtPositioning.coordinate() property real _toolButtonTopMargin: parent.height - ScreenTools.availableHeight + (ScreenTools.defaultFontPixelHeight / 2) + property bool _airspaceEnabled: QGroundControl.airmapSupported ? (QGroundControl.settingsManager.airMapSettings.enableAirMap.rawValue && QGroundControl.airspaceManager.connected): false property bool _disableVehicleTracking: false property bool _keepVehicleCentered: _mainIsMap ? false : true + function updateAirspace(reset) { + if(_airspaceEnabled) { + var coordinateNW = flightMap.toCoordinate(Qt.point(0,0), false /* clipToViewPort */) + var coordinateSE = flightMap.toCoordinate(Qt.point(width,height), false /* clipToViewPort */) + if(coordinateNW.isValid && coordinateSE.isValid) { + QGroundControl.airspaceManager.setROI(coordinateNW, coordinateSE, false /*planView*/, reset) + } + } + } + // Track last known map position and zoom from Fly view in settings - onZoomLevelChanged: QGroundControl.flightMapZoom = zoomLevel - onCenterChanged: QGroundControl.flightMapPosition = center + + onZoomLevelChanged: { + QGroundControl.flightMapZoom = zoomLevel + updateAirspace(false) + } + onCenterChanged: { + QGroundControl.flightMapPosition = center + updateAirspace(false) + } // When the user pans the map we stop responding to vehicle coordinate updates until the panRecenterTimer fires onUserPannedChanged: { @@ -68,6 +87,10 @@ FlightMap { } } + on_AirspaceEnabledChanged: { + updateAirspace(true) + } + function pointInRect(point, rect) { return point.x > rect.x && point.x < rect.x + rect.width && @@ -198,15 +221,14 @@ FlightMap { // Add ADSB vehicles to the map MapItemView { - model: _activeVehicle ? _activeVehicle.adsbVehicles : 0 - + model: _activeVehicle ? _activeVehicle.adsbVehicles : [] property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle - delegate: VehicleMapItem { coordinate: object.coordinate altitude: object.altitude callsign: object.callsign heading: object.heading + alert: object.alert map: flightMap z: QGroundControl.zOrderVehicles } @@ -409,4 +431,27 @@ FlightMap { } ] } + + // Airspace overlap support + MapItemView { + model: _airspaceEnabled && QGroundControl.settingsManager.airMapSettings.enableAirspace && QGroundControl.airspaceManager.airspaceVisible ? QGroundControl.airspaceManager.airspaces.circles : [] + delegate: MapCircle { + center: object.center + radius: object.radius + color: object.color + border.color: object.lineColor + border.width: object.lineWidth + } + } + + MapItemView { + model: _airspaceEnabled && QGroundControl.settingsManager.airMapSettings.enableAirspace && QGroundControl.airspaceManager.airspaceVisible ? QGroundControl.airspaceManager.airspaces.polygons : [] + delegate: MapPolygon { + path: object.polygon + color: object.color + border.color: object.lineColor + border.width: object.lineWidth + } + } + } diff --git a/src/FlightDisplay/FlightDisplayViewWidgets.qml b/src/FlightDisplay/FlightDisplayViewWidgets.qml index 1ae1388a5951b3f8b8bc71a2ff46373b52c6c4fa..2999626702caea607352ca935ddd1d8a8cc21bad 100644 --- a/src/FlightDisplay/FlightDisplayViewWidgets.qml +++ b/src/FlightDisplay/FlightDisplayViewWidgets.qml @@ -15,23 +15,27 @@ 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.Airspace 1.0 +import QGroundControl.Airmap 1.0 Item { - id: _root + id: widgetRoot property var qgcView property bool useLightColors property var missionController + property bool showValues: !QGroundControl.airspaceManager.airspaceVisible property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle property bool _isSatellite: _mainIsMap ? (_flightMap ? _flightMap.isSatelliteMap : true) : true property bool _lightWidgetBorders: _isSatellite + property bool _airspaceEnabled: QGroundControl.airmapSupported ? QGroundControl.settingsManager.airMapSettings.enableAirMap.rawValue : false readonly property real _margins: ScreenTools.defaultFontPixelHeight * 0.5 @@ -88,6 +92,13 @@ Item { onValueChanged: _setInstrumentWidget() } + Connections { + target: QGroundControl.airspaceManager + onAirspaceVisibleChanged: { + widgetRoot.showValues = !QGroundControl.airspaceManager.airspaceVisible + } + } + Component.onCompleted: { _setInstrumentWidget() } @@ -128,82 +139,97 @@ Item { text: "The vehicle has failed a pre-arm check. In order to arm the vehicle, resolve the failure." } } - - //-- 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: "topRightMode" - AnchorChanges { - target: instrumentsLoader - anchors.verticalCenter: undefined - anchors.bottom: undefined - anchors.top: _root ? _root.top : undefined - anchors.right: _root ? _root.right : undefined - anchors.left: undefined - } - }, - State { - name: "centerRightMode" - AnchorChanges { - target: instrumentsLoader - anchors.top: undefined - anchors.bottom: undefined - anchors.verticalCenter: _root ? _root.verticalCenter : undefined - anchors.right: _root ? _root.right : undefined - anchors.left: undefined - } - }, - State { - name: "bottomRightMode" - AnchorChanges { - target: instrumentsLoader - anchors.top: undefined - anchors.verticalCenter: undefined - anchors.bottom: _root ? _root.bottom : undefined - anchors.right: _root ? _root.right : undefined - anchors.left: undefined - } - }, - State { - name: "topLeftMode" - AnchorChanges { - target: instrumentsLoader - anchors.verticalCenter: undefined - anchors.bottom: undefined - anchors.top: _root ? _root.top : undefined - anchors.right: undefined - anchors.left: _root ? _root.left : undefined - } - }, - State { - name: "centerLeftMode" - AnchorChanges { - target: instrumentsLoader - anchors.top: undefined - anchors.bottom: undefined - anchors.verticalCenter: _root ? _root.verticalCenter : undefined - anchors.right: undefined - anchors.left: _root ? _root.left : undefined - } - }, - State { - name: "bottomLeftMode" - AnchorChanges { - target: instrumentsLoader - anchors.top: undefined - anchors.verticalCenter: undefined - anchors.bottom: _root ? _root.bottom : undefined - anchors.right: undefined - anchors.left: _root ? _root.left : undefined + //------------------------------------------------------- + // Airmap Airspace Control + AirspaceControl { + id: airspaceControl + width: getPreferredInstrumentWidth() + planView: false + visible: _airspaceEnabled + anchors.margins: ScreenTools.defaultFontPixelHeight * 0.5 + } + //------------------------------------------------------- + //-- 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 + states: [ + State { + name: "topRightMode" + AnchorChanges { + target: instrumentsLoader + anchors.verticalCenter: undefined + anchors.bottom: undefined + anchors.top: _root ? _root.top : undefined + anchors.right: _root ? _root.right : undefined + anchors.left: undefined + } + }, + State { + name: "centerRightMode" + AnchorChanges { + target: instrumentsLoader + anchors.top: undefined + anchors.bottom: undefined + anchors.verticalCenter: _root ? _root.verticalCenter : undefined + anchors.right: _root ? _root.right : undefined + anchors.left: undefined + } + }, + State { + name: "bottomRightMode" + AnchorChanges { + target: instrumentsLoader + anchors.top: undefined + anchors.verticalCenter: undefined + anchors.bottom: _root ? _root.bottom : undefined + anchors.right: _root ? _root.right : undefined + anchors.left: undefined + } + }, + State { + name: "topLeftMode" + AnchorChanges { + target: instrumentsLoader + anchors.verticalCenter: undefined + anchors.bottom: undefined + anchors.top: _root ? _root.top : undefined + anchors.right: undefined + anchors.left: _root ? _root.left : undefined + } + }, + State { + name: "centerLeftMode" + AnchorChanges { + target: instrumentsLoader + anchors.top: undefined + anchors.bottom: undefined + anchors.verticalCenter: _root ? _root.verticalCenter : undefined + anchors.right: undefined + anchors.left: _root ? _root.left : undefined + } + }, + State { + name: "bottomLeftMode" + AnchorChanges { + target: instrumentsLoader + anchors.top: undefined + anchors.verticalCenter: undefined + anchors.bottom: _root ? _root.bottom : undefined + anchors.right: undefined + anchors.left: _root ? _root.left : undefined + } } - } - ] + ] + } } } diff --git a/src/FlightMap/Images/AlertAircraft.svg b/src/FlightMap/Images/AlertAircraft.svg new file mode 100644 index 0000000000000000000000000000000000000000..781b06733ba52bcef97290f251e7e4b0674cd39b --- /dev/null +++ b/src/FlightMap/Images/AlertAircraft.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/src/FlightMap/Images/AwarenessAircraft.svg b/src/FlightMap/Images/AwarenessAircraft.svg new file mode 100644 index 0000000000000000000000000000000000000000..509b5d55eea8edc5e92f25995d3d952ebb61f5cb --- /dev/null +++ b/src/FlightMap/Images/AwarenessAircraft.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + 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/MapItems/VehicleMapItem.qml b/src/FlightMap/MapItems/VehicleMapItem.qml index b4c4d432a7fe1c71cf51fe74bd04114411a94443..5c8855ec66c32dbbd8628030d2f3e2e3ca71f55f 100644 --- a/src/FlightMap/MapItems/VehicleMapItem.qml +++ b/src/FlightMap/MapItems/VehicleMapItem.qml @@ -7,9 +7,10 @@ * ****************************************************************************/ -import QtQuick 2.3 -import QtLocation 5.3 -import QtPositioning 5.3 +import QtQuick 2.3 +import QtLocation 5.3 +import QtPositioning 5.3 +import QtGraphicalEffects 1.0 import QGroundControl 1.0 import QGroundControl.ScreenTools 1.0 @@ -24,6 +25,7 @@ MapQuickItem { property string callsign: "" ///< Vehicle callsign property double heading: vehicle ? vehicle.heading.value : Number.NaN ///< Vehicle heading, NAN for none property real size: _adsbVehicle ? _adsbSize : _uavSize /// Size for icon + property bool alert: false /// Collision alert anchorPoint.x: vehicleItem.width / 2 anchorPoint.y: vehicleItem.height / 2 @@ -31,7 +33,7 @@ MapQuickItem { property bool _adsbVehicle: vehicle ? false : true property real _uavSize: ScreenTools.defaultFontPixelHeight * 5 - property real _adsbSize: ScreenTools.defaultFontPixelHeight * 1.5 + property real _adsbSize: ScreenTools.defaultFontPixelHeight * 2.5 property var _map: map property bool _multiVehicle: QGroundControl.multiVehicleManager.vehicles.count > 1 @@ -41,14 +43,30 @@ MapQuickItem { height: vehicleIcon.height opacity: vehicle ? (vehicle.active ? 1.0 : 0.5) : 1.0 + Rectangle { + id: vehicleShadow + anchors.fill: vehicleIcon + color: Qt.rgba(1,1,1,1) + radius: width * 0.5 + visible: false + } + DropShadow { + anchors.fill: vehicleShadow + visible: vehicleIcon.visible && _adsbVehicle + horizontalOffset: 4 + verticalOffset: 4 + radius: 32.0 + samples: 65 + color: Qt.rgba(0.94,0.91,0,0.5) + source: vehicleShadow + } Image { id: vehicleIcon - source: _adsbVehicle ? "/qmlimages/adsbVehicle.svg" : vehicle.vehicleImageOpaque + source: _adsbVehicle ? (alert ? "/qmlimages/AlertAircraft.svg" : "/qmlimages/AwarenessAircraft.svg") : vehicle.vehicleImageOpaque mipmap: true width: size sourceSize.width: size fillMode: Image.PreserveAspectFit - transform: Rotation { origin.x: vehicleIcon.width / 2 origin.y: vehicleIcon.height / 2 @@ -64,7 +82,6 @@ MapQuickItem { text: vehicleLabelText font.pointSize: ScreenTools.smallFontPointSize visible: _adsbVehicle ? !isNaN(altitude) : _multiVehicle - property string vehicleLabelText: visible ? (_adsbVehicle ? QGroundControl.metersToAppSettingsDistanceUnits(altitude).toFixed(0) + " " + QGroundControl.appSettingsDistanceUnitsString : @@ -80,7 +97,6 @@ MapQuickItem { text: vehicleLabelText font.pointSize: ScreenTools.smallFontPointSize visible: _adsbVehicle ? !isNaN(altitude) : _multiVehicle - property string vehicleLabelText: visible && _adsbVehicle ? callsign : "" } } 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/GPS/Drivers b/src/GPS/Drivers index d7854b7bcf1610bb42d89f4bae553744cbe4408c..e84bb0a7a702320fedade6c83bbdf2324e3be8fb 160000 --- a/src/GPS/Drivers +++ b/src/GPS/Drivers @@ -1 +1 @@ -Subproject commit d7854b7bcf1610bb42d89f4bae553744cbe4408c +Subproject commit e84bb0a7a702320fedade6c83bbdf2324e3be8fb diff --git a/src/JsonHelper.cc b/src/JsonHelper.cc index 94858a42c6c68b1efc40875e43022e5bf6ceae46..6ef592720734dc5d4bc1a70ccbcc4b0ac1fd11aa 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/ComplexMissionItem.h b/src/MissionManager/ComplexMissionItem.h index 41472110e79b98ca501339265c796e21d9a98455..b82950ae354dd30bbd26079be320c9c5335d3f40 100644 --- a/src/MissionManager/ComplexMissionItem.h +++ b/src/MissionManager/ComplexMissionItem.h @@ -11,6 +11,7 @@ #define ComplexMissionItem_H #include "VisualMissionItem.h" +#include "QGCGeo.h" class ComplexMissionItem : public VisualMissionItem { @@ -27,6 +28,10 @@ public: /// Signals complexDistanceChanged virtual double complexDistance(void) const = 0; + /// @return The item bounding cube + /// Signals boundingCubeChanged + virtual QGCGeoBoundingCube boundingCube(void) const { return QGCGeoBoundingCube(); } + /// Load the complex mission item from Json /// @param complexObject Complex mission item json object /// @param sequenceNumber Sequence number for first MISSION_ITEM in survey @@ -49,6 +54,7 @@ public: signals: void complexDistanceChanged (void); + void boundingCubeChanged (void); void greatestDistanceToChanged (void); }; diff --git a/src/MissionManager/GeoFenceManager.cc b/src/MissionManager/GeoFenceManager.cc index ce6ce1933efecfbc1ef788047a598f12afcf245e..05ce7ae5473d02ff67cdcfedb656b3d339da8960 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,9 @@ GeoFenceManager::GeoFenceManager(Vehicle* vehicle) : _vehicle (vehicle) , _planManager (vehicle, MAV_MISSION_TYPE_FENCE) , _firstParamLoadComplete (false) +#if defined(QGC_AIRMAP_ENABLED) + , _airspaceManager (qgcApp()->toolbox()->airspaceManager()) +#endif { 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..e65a0ff208d2204478ebbc8acf7c2e09bbc86810 100644 --- a/src/MissionManager/GeoFenceManager.h +++ b/src/MissionManager/GeoFenceManager.h @@ -13,6 +13,10 @@ #include #include +#if defined(QGC_AIRMAP_ENABLED) +#include "AirspaceManager.h" +#endif + #include "QGCLoggingCategory.h" #include "FactSystem.h" #include "PlanManager.h" @@ -96,6 +100,9 @@ private: bool _firstParamLoadComplete; QList _sendPolygons; QList _sendCircles; +#if defined(QGC_AIRMAP_ENABLED) + AirspaceManager* _airspaceManager; +#endif }; #endif diff --git a/src/MissionManager/MissionController.cc b/src/MissionManager/MissionController.cc index a5054afe4c3e28b53182af839258c22ae30c946e..61891902e772bd59b3cf6f24d7b5f202c7ed7ea1 100644 --- a/src/MissionManager/MissionController.cc +++ b/src/MissionManager/MissionController.cc @@ -35,6 +35,8 @@ #include "QGCQFileDialog.h" #endif +#define UPDATE_TIMEOUT 5000 ///< How often we check for bounding box changes + QGC_LOGGING_CATEGORY(MissionControllerLog, "MissionControllerLog") const char* MissionController::_settingsGroup = "MissionController"; @@ -72,6 +74,8 @@ MissionController::MissionController(PlanMasterController* masterController, QOb { _resetMissionFlightStatus(); managerVehicleChanged(_managerVehicle); + _updateTimer.setSingleShot(true); + connect(&_updateTimer, &QTimer::timeout, this, &MissionController::_updateTimeout); } MissionController::~MissionController() @@ -175,7 +179,7 @@ void MissionController::_newMissionItemsAvailableFromVehicle(bool removeAllReque i = 1; } - for (; iappend(new SimpleMissionItem(_controllerVehicle, _flyView, *missionItem, this)); } @@ -368,7 +372,6 @@ int MissionController::insertSimpleMissionItem(QGeoCoordinate coordinate, int i) _visualItems->insert(i, newItem); _recalcAll(); - return newItem->sequenceNumber(); } @@ -443,9 +446,9 @@ int MissionController::_insertComplexMissionItemWorker(ComplexMissionItem* compl bool surveyStyleItem = qobject_cast(complexItem) || qobject_cast(complexItem); if (surveyStyleItem) { - bool rollSupported = false; + bool rollSupported = false; bool pitchSupported = false; - bool yawSupported = false; + bool yawSupported = false; // If the vehicle is known to have a gimbal then we automatically point the gimbal straight down if not already set @@ -477,6 +480,10 @@ int MissionController::_insertComplexMissionItemWorker(ComplexMissionItem* compl _visualItems->insert(i, complexItem); } + //-- Keep track of bounding box changes in complex items + if(!complexItem->isSimpleItem()) { + connect(complexItem, &ComplexMissionItem::boundingCubeChanged, this, &MissionController::_complexBoundingBoxChanged); + } _recalcAll(); return complexItem->sequenceNumber(); @@ -1471,6 +1478,8 @@ void MissionController::_recalcMissionFlightStatus() } } } + + _updateTimer.start(UPDATE_TIMEOUT); } // This will update the sequence numbers to be sequential starting from 0 @@ -1538,6 +1547,7 @@ void MissionController::_recalcAll(void) _recalcSequence(); _recalcChildItems(); _recalcWaypointLines(); + _updateTimer.start(UPDATE_TIMEOUT); } /// Initializes a new set of mission items @@ -2032,3 +2042,87 @@ void MissionController::setCurrentPlanViewIndex(int sequenceNumber, bool force) emit currentPlanViewItemChanged(); } } + +void MissionController::_updateTimeout() +{ + QGeoCoordinate firstCoordinate; + QGeoCoordinate takeoffCoordinate; + QGCGeoBoundingCube boundingCube; + double north = 0.0; + double south = 180.0; + double east = 0.0; + double west = 360.0; + double minAlt = QGCGeoBoundingCube::MaxAlt; + double maxAlt = QGCGeoBoundingCube::MinAlt; + for (int i = 1; i < _visualItems->count(); i++) { + VisualMissionItem* item = qobject_cast(_visualItems->get(i)); + if(item->isSimpleItem()) { + SimpleMissionItem* pSimpleItem = qobject_cast(item); + if(pSimpleItem) { + switch(pSimpleItem->command()) { + case MAV_CMD_NAV_TAKEOFF: + case MAV_CMD_NAV_WAYPOINT: + case MAV_CMD_NAV_LAND: + if(pSimpleItem->coordinate().isValid()) { + if((MAV_CMD)pSimpleItem->command() == MAV_CMD_NAV_TAKEOFF) { + takeoffCoordinate = pSimpleItem->coordinate(); + } else if(!firstCoordinate.isValid()) { + firstCoordinate = pSimpleItem->coordinate(); + } + double lat = pSimpleItem->coordinate().latitude() + 90.0; + double lon = pSimpleItem->coordinate().longitude() + 180.0; + double alt = pSimpleItem->coordinate().altitude(); + north = fmax(north, lat); + south = fmin(south, lat); + east = fmax(east, lon); + west = fmin(west, lon); + minAlt = fmin(minAlt, alt); + maxAlt = fmax(maxAlt, alt); + } + break; + default: + break; + } + } + } else { + ComplexMissionItem* pComplexItem = qobject_cast(item); + if(pComplexItem) { + QGCGeoBoundingCube bc = pComplexItem->boundingCube(); + if(bc.isValid()) { + if(!firstCoordinate.isValid() && pComplexItem->coordinate().isValid()) { + firstCoordinate = pComplexItem->coordinate(); + } + north = fmax(north, bc.pointNW.latitude() + 90.0); + south = fmin(south, bc.pointSE.latitude() + 90.0); + east = fmax(east, bc.pointNW.longitude() + 180.0); + west = fmin(west, bc.pointSE.longitude() + 180.0); + minAlt = fmin(minAlt, bc.pointNW.altitude()); + maxAlt = fmax(maxAlt, bc.pointSE.altitude()); + } + } + } + } + //-- Figure out where this thing is taking off from + if(!takeoffCoordinate.isValid()) { + if(firstCoordinate.isValid()) { + takeoffCoordinate = firstCoordinate; + } else { + takeoffCoordinate = plannedHomePosition(); + } + } + //-- Build bounding "cube" + boundingCube = QGCGeoBoundingCube( + QGeoCoordinate(north - 90.0, west - 180.0, minAlt), + QGeoCoordinate(south - 90.0, east - 180.0, maxAlt)); + if(_travelBoundingCube != boundingCube || _takeoffCoordinate != takeoffCoordinate) { + _takeoffCoordinate = takeoffCoordinate; + _travelBoundingCube = boundingCube; + emit missionBoundingCubeChanged(); + qCDebug(MissionControllerLog) << "Bounding cube:" << _travelBoundingCube.pointNW << _travelBoundingCube.pointSE; + } +} + +void MissionController::_complexBoundingBoxChanged() +{ + _updateTimer.start(UPDATE_TIMEOUT); +} diff --git a/src/MissionManager/MissionController.h b/src/MissionManager/MissionController.h index 458d97a37e26f4ea06942276c56d5aa294e45597..55387398fdfbe874ffd5251a3896c6f4e18dd90f 100644 --- a/src/MissionManager/MissionController.h +++ b/src/MissionManager/MissionController.h @@ -16,6 +16,8 @@ #include "Vehicle.h" #include "QGCLoggingCategory.h" +#include "QGCGeoBoundingCube.h" + #include class CoordinateVector; @@ -89,6 +91,7 @@ public: Q_PROPERTY(int batteryChangePoint READ batteryChangePoint NOTIFY batteryChangePointChanged) Q_PROPERTY(int batteriesRequired READ batteriesRequired NOTIFY batteriesRequiredChanged) + Q_PROPERTY(QGCGeoBoundingCube* travelBoundingCube READ travelBoundingCube NOTIFY missionBoundingCubeChanged) Q_PROPERTY(QString surveyComplexItemName READ surveyComplexItemName CONSTANT) Q_PROPERTY(QString corridorScanComplexItemName READ corridorScanComplexItemName CONSTANT) @@ -138,6 +141,9 @@ public: bool loadJsonFile(QFile& file, QString& errorString); bool loadTextFile(QFile& file, QString& errorString); + QGCGeoBoundingCube* travelBoundingCube () { return &_travelBoundingCube; } + QGeoCoordinate takeoffCoordinate () { return _takeoffCoordinate; } + // Overrides from PlanElementController bool supported (void) const final { return true; } void start (bool flyView) final; @@ -208,6 +214,7 @@ signals: void currentMissionIndexChanged (int currentMissionIndex); void currentPlanViewIndexChanged (void); void currentPlanViewItemChanged (void); + void missionBoundingCubeChanged (void); private slots: void _newMissionItemsAvailableFromVehicle(bool removeAllRequested); @@ -222,6 +229,8 @@ private slots: void _visualItemsDirtyChanged(bool dirty); void _managerSendComplete(bool error); void _managerRemoveAllComplete(bool error); + void _updateTimeout(); + void _complexBoundingBoxChanged(); private: void _init(void); @@ -277,6 +286,9 @@ private: double _progressPct; int _currentPlanViewIndex; VisualMissionItem* _currentPlanViewItem; + QTimer _updateTimer; + QGCGeoBoundingCube _travelBoundingCube; + QGeoCoordinate _takeoffCoordinate; static const char* _settingsGroup; diff --git a/src/MissionManager/MissionItem.cc b/src/MissionManager/MissionItem.cc index 9eeacb1078bc17109dfe462c5d8a87a821bd3780..c35203fe4e170b8c9493df534417fc5fb2a57af6 100644 --- a/src/MissionManager/MissionItem.cc +++ b/src/MissionManager/MissionItem.cc @@ -418,6 +418,10 @@ void MissionItem::setCoordinate(const QGeoCoordinate& coordinate) QGeoCoordinate MissionItem::coordinate(void) const { + if(!std::isfinite(param5()) || !std::isfinite(param6())) { + //-- If either of these are NAN, return an invalid (QGeoCoordinate::isValid() == false) coordinate + return QGeoCoordinate(); + } return QGeoCoordinate(param5(), param6(), param7()); } diff --git a/src/MissionManager/PlanManager.cc b/src/MissionManager/PlanManager.cc index 38292b268d85e1f2e7035b68beb4c731a8932f89..a30c4cc91bf20dec391013bcf3c522f2aaca2e6f 100644 --- a/src/MissionManager/PlanManager.cc +++ b/src/MissionManager/PlanManager.cc @@ -427,7 +427,6 @@ void PlanManager::_handleMissionItem(const mavlink_message_t& message, bool miss } else if (frame == MAV_FRAME_GLOBAL_RELATIVE_ALT_INT) { frame = MAV_FRAME_GLOBAL_RELATIVE_ALT; } - bool ardupilotHomePositionUpdate = false; if (!_checkForExpectedAck(AckMissionItem)) { diff --git a/src/MissionManager/PlanMasterController.cc b/src/MissionManager/PlanMasterController.cc index d6ece82cc3391c45bfd81dc5f91ad009a39cb2c0..f42d5e433805a8de74cf5d7b1edf5c286f9373fe 100644 --- a/src/MissionManager/PlanMasterController.cc +++ b/src/MissionManager/PlanMasterController.cc @@ -16,6 +16,9 @@ #include "JsonHelper.h" #include "MissionManager.h" #include "KML.h" +#if defined(QGC_AIRMAP_ENABLED) +#include "AirspaceFlightPlanProvider.h" +#endif #include #include @@ -74,6 +77,13 @@ void PlanMasterController::start(bool flyView) connect(_multiVehicleMgr, &MultiVehicleManager::activeVehicleChanged, this, &PlanMasterController::_activeVehicleChanged); _activeVehicleChanged(_multiVehicleMgr->activeVehicle()); + +#if defined(QGC_AIRMAP_ENABLED) + //-- This assumes there is one single instance of PlanMasterController in edit mode. + if(!flyView) { + qgcApp()->toolbox()->airspaceManager()->flightPlan()->startFlightPlanning(this); + } +#endif } void PlanMasterController::startStaticActiveVehicle(Vehicle* vehicle) @@ -546,7 +556,7 @@ void PlanMasterController::_showPlanFromManagerVehicle(void) _managerVehicle->forceInitialPlanRequestComplete(); } - // The crazy if structure is to handle the load propogating by itself through the system + // The crazy if structure is to handle the load propagating by itself through the system if (!_missionController.showPlanFromManagerVehicle()) { if (!_geoFenceController.showPlanFromManagerVehicle()) { _rallyPointController.showPlanFromManagerVehicle(); diff --git a/src/MissionManager/SurveyMissionItem.cc b/src/MissionManager/SurveyMissionItem.cc new file mode 100644 index 0000000000000000000000000000000000000000..d9a05b9f31be6dcb9a63c95ff7aaa9ea791d488a --- /dev/null +++ b/src/MissionManager/SurveyMissionItem.cc @@ -0,0 +1,1304 @@ +/**************************************************************************** + * + * (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 "SurveyMissionItem.h" +#include "JsonHelper.h" +#include "MissionController.h" +#include "QGCGeo.h" +#include "QGroundControlQmlGlobal.h" +#include "QGCQGeoCoordinate.h" +#include "SettingsManager.h" +#include "AppSettings.h" + +#include + +QGC_LOGGING_CATEGORY(SurveyMissionItemLog, "SurveyMissionItemLog") + +const char* SurveyMissionItem::jsonComplexItemTypeValue = "survey"; + +const char* SurveyMissionItem::_jsonGridObjectKey = "grid"; +const char* SurveyMissionItem::_jsonGridAltitudeKey = "altitude"; +const char* SurveyMissionItem::_jsonGridAltitudeRelativeKey = "relativeAltitude"; +const char* SurveyMissionItem::_jsonGridAngleKey = "angle"; +const char* SurveyMissionItem::_jsonGridSpacingKey = "spacing"; +const char* SurveyMissionItem::_jsonGridEntryLocationKey = "entryLocation"; +const char* SurveyMissionItem::_jsonTurnaroundDistKey = "turnAroundDistance"; +const char* SurveyMissionItem::_jsonCameraTriggerDistanceKey = "cameraTriggerDistance"; +const char* SurveyMissionItem::_jsonCameraTriggerInTurnaroundKey = "cameraTriggerInTurnaround"; +const char* SurveyMissionItem::_jsonHoverAndCaptureKey = "hoverAndCapture"; +const char* SurveyMissionItem::_jsonGroundResolutionKey = "groundResolution"; +const char* SurveyMissionItem::_jsonFrontalOverlapKey = "imageFrontalOverlap"; +const char* SurveyMissionItem::_jsonSideOverlapKey = "imageSideOverlap"; +const char* SurveyMissionItem::_jsonCameraSensorWidthKey = "sensorWidth"; +const char* SurveyMissionItem::_jsonCameraSensorHeightKey = "sensorHeight"; +const char* SurveyMissionItem::_jsonCameraResolutionWidthKey = "resolutionWidth"; +const char* SurveyMissionItem::_jsonCameraResolutionHeightKey = "resolutionHeight"; +const char* SurveyMissionItem::_jsonCameraFocalLengthKey = "focalLength"; +const char* SurveyMissionItem::_jsonCameraMinTriggerIntervalKey = "minTriggerInterval"; +const char* SurveyMissionItem::_jsonCameraObjectKey = "camera"; +const char* SurveyMissionItem::_jsonCameraNameKey = "name"; +const char* SurveyMissionItem::_jsonManualGridKey = "manualGrid"; +const char* SurveyMissionItem::_jsonCameraOrientationLandscapeKey = "orientationLandscape"; +const char* SurveyMissionItem::_jsonFixedValueIsAltitudeKey = "fixedValueIsAltitude"; +const char* SurveyMissionItem::_jsonRefly90DegreesKey = "refly90Degrees"; + +const char* SurveyMissionItem::settingsGroup = "Survey"; +const char* SurveyMissionItem::manualGridName = "ManualGrid"; +const char* SurveyMissionItem::gridAltitudeName = "GridAltitude"; +const char* SurveyMissionItem::gridAltitudeRelativeName = "GridAltitudeRelative"; +const char* SurveyMissionItem::gridAngleName = "GridAngle"; +const char* SurveyMissionItem::gridSpacingName = "GridSpacing"; +const char* SurveyMissionItem::gridEntryLocationName = "GridEntryLocation"; +const char* SurveyMissionItem::turnaroundDistName = "TurnaroundDist"; +const char* SurveyMissionItem::cameraTriggerDistanceName = "CameraTriggerDistance"; +const char* SurveyMissionItem::cameraTriggerInTurnaroundName = "CameraTriggerInTurnaround"; +const char* SurveyMissionItem::hoverAndCaptureName = "HoverAndCapture"; +const char* SurveyMissionItem::groundResolutionName = "GroundResolution"; +const char* SurveyMissionItem::frontalOverlapName = "FrontalOverlap"; +const char* SurveyMissionItem::sideOverlapName = "SideOverlap"; +const char* SurveyMissionItem::cameraSensorWidthName = "CameraSensorWidth"; +const char* SurveyMissionItem::cameraSensorHeightName = "CameraSensorHeight"; +const char* SurveyMissionItem::cameraResolutionWidthName = "CameraResolutionWidth"; +const char* SurveyMissionItem::cameraResolutionHeightName = "CameraResolutionHeight"; +const char* SurveyMissionItem::cameraFocalLengthName = "CameraFocalLength"; +const char* SurveyMissionItem::cameraTriggerName = "CameraTrigger"; +const char* SurveyMissionItem::cameraOrientationLandscapeName = "CameraOrientationLandscape"; +const char* SurveyMissionItem::fixedValueIsAltitudeName = "FixedValueIsAltitude"; +const char* SurveyMissionItem::cameraName = "Camera"; + +SurveyMissionItem::SurveyMissionItem(Vehicle* vehicle, QObject* parent) + : ComplexMissionItem(vehicle, parent) + , _sequenceNumber(0) + , _dirty(false) + , _mapPolygon(this) + , _cameraOrientationFixed(false) + , _missionCommandCount(0) + , _refly90Degrees(false) + , _additionalFlightDelaySeconds(0) + , _cameraMinTriggerInterval(0) + , _ignoreRecalc(false) + , _surveyDistance(0.0) + , _cameraShots(0) + , _coveredArea(0.0) + , _timeBetweenShots(0.0) + , _metaDataMap(FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/Survey.SettingsGroup.json"), this)) + , _manualGridFact (settingsGroup, _metaDataMap[manualGridName]) + , _gridAltitudeFact (settingsGroup, _metaDataMap[gridAltitudeName]) + , _gridAltitudeRelativeFact (settingsGroup, _metaDataMap[gridAltitudeRelativeName]) + , _gridAngleFact (settingsGroup, _metaDataMap[gridAngleName]) + , _gridSpacingFact (settingsGroup, _metaDataMap[gridSpacingName]) + , _gridEntryLocationFact (settingsGroup, _metaDataMap[gridEntryLocationName]) + , _turnaroundDistFact (settingsGroup, _metaDataMap[turnaroundDistName]) + , _cameraTriggerDistanceFact (settingsGroup, _metaDataMap[cameraTriggerDistanceName]) + , _cameraTriggerInTurnaroundFact (settingsGroup, _metaDataMap[cameraTriggerInTurnaroundName]) + , _hoverAndCaptureFact (settingsGroup, _metaDataMap[hoverAndCaptureName]) + , _groundResolutionFact (settingsGroup, _metaDataMap[groundResolutionName]) + , _frontalOverlapFact (settingsGroup, _metaDataMap[frontalOverlapName]) + , _sideOverlapFact (settingsGroup, _metaDataMap[sideOverlapName]) + , _cameraSensorWidthFact (settingsGroup, _metaDataMap[cameraSensorWidthName]) + , _cameraSensorHeightFact (settingsGroup, _metaDataMap[cameraSensorHeightName]) + , _cameraResolutionWidthFact (settingsGroup, _metaDataMap[cameraResolutionWidthName]) + , _cameraResolutionHeightFact (settingsGroup, _metaDataMap[cameraResolutionHeightName]) + , _cameraFocalLengthFact (settingsGroup, _metaDataMap[cameraFocalLengthName]) + , _cameraOrientationLandscapeFact (settingsGroup, _metaDataMap[cameraOrientationLandscapeName]) + , _fixedValueIsAltitudeFact (settingsGroup, _metaDataMap[fixedValueIsAltitudeName]) + , _cameraFact (settingsGroup, _metaDataMap[cameraName]) +{ + _editorQml = "qrc:/qml/SurveyItemEditor.qml"; + + // If the user hasn't changed turnaround from the default (which is a fixed wing default) and we are multi-rotor set the multi-rotor default. + // NULL check since object creation during unit testing passes NULL for vehicle + if (_vehicle && _vehicle->multiRotor() && _turnaroundDistFact.rawValue().toDouble() == _turnaroundDistFact.rawDefaultValue().toDouble()) { + // Note this is set to 10 meters to work around a problem with PX4 Pro turnaround behavior. Don't change unless firmware gets better as well. + _turnaroundDistFact.setRawValue(10); + } + + // We override the grid altitude to the mission default + if (_manualGridFact.rawValue().toBool() || _fixedValueIsAltitudeFact.rawValue().toBool()) { + _gridAltitudeFact.setRawValue(qgcApp()->toolbox()->settingsManager()->appSettings()->defaultMissionItemAltitude()->rawValue()); + } + + connect(&_gridSpacingFact, &Fact::valueChanged, this, &SurveyMissionItem::_generateGrid); + connect(&_gridAngleFact, &Fact::valueChanged, this, &SurveyMissionItem::_generateGrid); + connect(&_gridEntryLocationFact, &Fact::valueChanged, this, &SurveyMissionItem::_generateGrid); + connect(&_turnaroundDistFact, &Fact::valueChanged, this, &SurveyMissionItem::_generateGrid); + connect(&_cameraTriggerDistanceFact, &Fact::valueChanged, this, &SurveyMissionItem::_generateGrid); + connect(&_cameraTriggerInTurnaroundFact, &Fact::valueChanged, this, &SurveyMissionItem::_generateGrid); + connect(&_hoverAndCaptureFact, &Fact::valueChanged, this, &SurveyMissionItem::_generateGrid); + connect(this, &SurveyMissionItem::refly90DegreesChanged, this, &SurveyMissionItem::_generateGrid); + + connect(&_gridAltitudeFact, &Fact::valueChanged, this, &SurveyMissionItem::_updateCoordinateAltitude); + + connect(&_gridAltitudeRelativeFact, &Fact::valueChanged, this, &SurveyMissionItem::_setDirty); + + // Signal to Qml when camera value changes so it can recalc + connect(&_groundResolutionFact, &Fact::valueChanged, this, &SurveyMissionItem::_cameraValueChanged); + connect(&_frontalOverlapFact, &Fact::valueChanged, this, &SurveyMissionItem::_cameraValueChanged); + connect(&_sideOverlapFact, &Fact::valueChanged, this, &SurveyMissionItem::_cameraValueChanged); + connect(&_cameraSensorWidthFact, &Fact::valueChanged, this, &SurveyMissionItem::_cameraValueChanged); + connect(&_cameraSensorHeightFact, &Fact::valueChanged, this, &SurveyMissionItem::_cameraValueChanged); + connect(&_cameraResolutionWidthFact, &Fact::valueChanged, this, &SurveyMissionItem::_cameraValueChanged); + connect(&_cameraResolutionHeightFact, &Fact::valueChanged, this, &SurveyMissionItem::_cameraValueChanged); + connect(&_cameraFocalLengthFact, &Fact::valueChanged, this, &SurveyMissionItem::_cameraValueChanged); + connect(&_cameraOrientationLandscapeFact, &Fact::valueChanged, this, &SurveyMissionItem::_cameraValueChanged); + + connect(&_cameraTriggerDistanceFact, &Fact::valueChanged, this, &SurveyMissionItem::timeBetweenShotsChanged); + + connect(&_mapPolygon, &QGCMapPolygon::dirtyChanged, this, &SurveyMissionItem::_polygonDirtyChanged); + connect(&_mapPolygon, &QGCMapPolygon::pathChanged, this, &SurveyMissionItem::_generateGrid); +} + +void SurveyMissionItem::_setSurveyDistance(double surveyDistance) +{ + if (!qFuzzyCompare(_surveyDistance, surveyDistance)) { + _surveyDistance = surveyDistance; + emit complexDistanceChanged(); + } +} + +void SurveyMissionItem::_setBoundingCube(QGCGeoBoundingCube bc) +{ + if (bc != _boundingCube) { + _boundingCube = bc; + emit boundingCubeChanged(); + } +} + +void SurveyMissionItem::_setCameraShots(int cameraShots) +{ + if (_cameraShots != cameraShots) { + _cameraShots = cameraShots; + emit cameraShotsChanged(this->cameraShots()); + } +} + +void SurveyMissionItem::_setCoveredArea(double coveredArea) +{ + if (!qFuzzyCompare(_coveredArea, coveredArea)) { + _coveredArea = coveredArea; + emit coveredAreaChanged(_coveredArea); + } +} + +void SurveyMissionItem::_clearInternal(void) +{ + // Bug workaround + while (_simpleGridPoints.count() > 1) { + _simpleGridPoints.takeLast(); + } + emit gridPointsChanged(); + _simpleGridPoints.clear(); + _transectSegments.clear(); + + _missionCommandCount = 0; + + setDirty(true); + + emit specifiesCoordinateChanged(); + emit lastSequenceNumberChanged(lastSequenceNumber()); +} + +int SurveyMissionItem::lastSequenceNumber(void) const +{ + return _sequenceNumber + _missionCommandCount; +} + +void SurveyMissionItem::setCoordinate(const QGeoCoordinate& coordinate) +{ + if (_coordinate != coordinate) { + _coordinate = coordinate; + emit coordinateChanged(_coordinate); + } +} + +void SurveyMissionItem::setDirty(bool dirty) +{ + if (_dirty != dirty) { + _dirty = dirty; + emit dirtyChanged(_dirty); + } +} + +void SurveyMissionItem::save(QJsonArray& missionItems) +{ + QJsonObject saveObject; + + saveObject[JsonHelper::jsonVersionKey] = 3; + saveObject[VisualMissionItem::jsonTypeKey] = VisualMissionItem::jsonTypeComplexItemValue; + saveObject[ComplexMissionItem::jsonComplexItemTypeKey] = jsonComplexItemTypeValue; + saveObject[_jsonManualGridKey] = _manualGridFact.rawValue().toBool(); + saveObject[_jsonFixedValueIsAltitudeKey] = _fixedValueIsAltitudeFact.rawValue().toBool(); + saveObject[_jsonHoverAndCaptureKey] = _hoverAndCaptureFact.rawValue().toBool(); + saveObject[_jsonRefly90DegreesKey] = _refly90Degrees; + saveObject[_jsonCameraTriggerDistanceKey] = _cameraTriggerDistanceFact.rawValue().toDouble(); + saveObject[_jsonCameraTriggerInTurnaroundKey] = _cameraTriggerInTurnaroundFact.rawValue().toBool(); + + QJsonObject gridObject; + gridObject[_jsonGridAltitudeKey] = _gridAltitudeFact.rawValue().toDouble(); + gridObject[_jsonGridAltitudeRelativeKey] = _gridAltitudeRelativeFact.rawValue().toBool(); + gridObject[_jsonGridAngleKey] = _gridAngleFact.rawValue().toDouble(); + gridObject[_jsonGridSpacingKey] = _gridSpacingFact.rawValue().toDouble(); + gridObject[_jsonGridEntryLocationKey] = _gridEntryLocationFact.rawValue().toDouble(); + gridObject[_jsonTurnaroundDistKey] = _turnaroundDistFact.rawValue().toDouble(); + + saveObject[_jsonGridObjectKey] = gridObject; + + if (!_manualGridFact.rawValue().toBool()) { + QJsonObject cameraObject; + cameraObject[_jsonCameraNameKey] = _cameraFact.rawValue().toString(); + cameraObject[_jsonCameraOrientationLandscapeKey] = _cameraOrientationLandscapeFact.rawValue().toBool(); + cameraObject[_jsonCameraSensorWidthKey] = _cameraSensorWidthFact.rawValue().toDouble(); + cameraObject[_jsonCameraSensorHeightKey] = _cameraSensorHeightFact.rawValue().toDouble(); + cameraObject[_jsonCameraResolutionWidthKey] = _cameraResolutionWidthFact.rawValue().toDouble(); + cameraObject[_jsonCameraResolutionHeightKey] = _cameraResolutionHeightFact.rawValue().toDouble(); + cameraObject[_jsonCameraFocalLengthKey] = _cameraFocalLengthFact.rawValue().toDouble(); + cameraObject[_jsonCameraMinTriggerIntervalKey] = _cameraMinTriggerInterval; + cameraObject[_jsonGroundResolutionKey] = _groundResolutionFact.rawValue().toDouble(); + cameraObject[_jsonFrontalOverlapKey] = _frontalOverlapFact.rawValue().toInt(); + cameraObject[_jsonSideOverlapKey] = _sideOverlapFact.rawValue().toInt(); + + saveObject[_jsonCameraObjectKey] = cameraObject; + } + + // Polygon shape + _mapPolygon.saveToJson(saveObject); + + missionItems.append(saveObject); +} + +void SurveyMissionItem::setSequenceNumber(int sequenceNumber) +{ + if (_sequenceNumber != sequenceNumber) { + _sequenceNumber = sequenceNumber; + emit sequenceNumberChanged(sequenceNumber); + emit lastSequenceNumberChanged(lastSequenceNumber()); + } +} + +bool SurveyMissionItem::load(const QJsonObject& complexObject, int sequenceNumber, QString& errorString) +{ + QJsonObject v2Object = complexObject; + + // We need to pull version first to determine what validation/conversion needs to be performed. + QList versionKeyInfoList = { + { JsonHelper::jsonVersionKey, QJsonValue::Double, true }, + }; + if (!JsonHelper::validateKeys(v2Object, versionKeyInfoList, errorString)) { + return false; + } + + int version = v2Object[JsonHelper::jsonVersionKey].toInt(); + if (version != 2 && version != 3) { + errorString = tr("%1 does not support this version of survey items").arg(qgcApp()->applicationName()); + return false; + } + if (version == 2) { + // Convert to v3 + if (v2Object.contains(VisualMissionItem::jsonTypeKey) && v2Object[VisualMissionItem::jsonTypeKey].toString() == QStringLiteral("survey")) { + v2Object[VisualMissionItem::jsonTypeKey] = VisualMissionItem::jsonTypeComplexItemValue; + v2Object[ComplexMissionItem::jsonComplexItemTypeKey] = jsonComplexItemTypeValue; + } + } + + QList mainKeyInfoList = { + { JsonHelper::jsonVersionKey, QJsonValue::Double, true }, + { VisualMissionItem::jsonTypeKey, QJsonValue::String, true }, + { ComplexMissionItem::jsonComplexItemTypeKey, QJsonValue::String, true }, + { QGCMapPolygon::jsonPolygonKey, QJsonValue::Array, true }, + { _jsonGridObjectKey, QJsonValue::Object, true }, + { _jsonCameraObjectKey, QJsonValue::Object, false }, + { _jsonCameraTriggerDistanceKey, QJsonValue::Double, true }, + { _jsonManualGridKey, QJsonValue::Bool, true }, + { _jsonFixedValueIsAltitudeKey, QJsonValue::Bool, true }, + { _jsonHoverAndCaptureKey, QJsonValue::Bool, false }, + { _jsonRefly90DegreesKey, QJsonValue::Bool, false }, + { _jsonCameraTriggerInTurnaroundKey, QJsonValue::Bool, false }, // Should really be required, but it was missing from initial code due to bug + }; + if (!JsonHelper::validateKeys(v2Object, mainKeyInfoList, errorString)) { + return false; + } + + QString itemType = v2Object[VisualMissionItem::jsonTypeKey].toString(); + QString complexType = v2Object[ComplexMissionItem::jsonComplexItemTypeKey].toString(); + if (itemType != VisualMissionItem::jsonTypeComplexItemValue || complexType != jsonComplexItemTypeValue) { + errorString = tr("%1 does not support loading this complex mission item type: %2:%3").arg(qgcApp()->applicationName()).arg(itemType).arg(complexType); + return false; + } + + _ignoreRecalc = true; + + _mapPolygon.clear(); + + setSequenceNumber(sequenceNumber); + + _manualGridFact.setRawValue (v2Object[_jsonManualGridKey].toBool(true)); + _fixedValueIsAltitudeFact.setRawValue (v2Object[_jsonFixedValueIsAltitudeKey].toBool(true)); + _gridAltitudeRelativeFact.setRawValue (v2Object[_jsonGridAltitudeRelativeKey].toBool(true)); + _hoverAndCaptureFact.setRawValue (v2Object[_jsonHoverAndCaptureKey].toBool(false)); + _cameraTriggerInTurnaroundFact.setRawValue (v2Object[_jsonCameraTriggerInTurnaroundKey].toBool(true)); + + _refly90Degrees = v2Object[_jsonRefly90DegreesKey].toBool(false); + + QList gridKeyInfoList = { + { _jsonGridAltitudeKey, QJsonValue::Double, true }, + { _jsonGridAltitudeRelativeKey, QJsonValue::Bool, true }, + { _jsonGridAngleKey, QJsonValue::Double, true }, + { _jsonGridSpacingKey, QJsonValue::Double, true }, + { _jsonGridEntryLocationKey, QJsonValue::Double, false }, + { _jsonTurnaroundDistKey, QJsonValue::Double, true }, + }; + QJsonObject gridObject = v2Object[_jsonGridObjectKey].toObject(); + if (!JsonHelper::validateKeys(gridObject, gridKeyInfoList, errorString)) { + return false; + } + _gridAltitudeFact.setRawValue (gridObject[_jsonGridAltitudeKey].toDouble()); + _gridAngleFact.setRawValue (gridObject[_jsonGridAngleKey].toDouble()); + _gridSpacingFact.setRawValue (gridObject[_jsonGridSpacingKey].toDouble()); + _turnaroundDistFact.setRawValue (gridObject[_jsonTurnaroundDistKey].toDouble()); + _cameraTriggerDistanceFact.setRawValue (v2Object[_jsonCameraTriggerDistanceKey].toDouble()); + if (gridObject.contains(_jsonGridEntryLocationKey)) { + _gridEntryLocationFact.setRawValue(gridObject[_jsonGridEntryLocationKey].toDouble()); + } else { + _gridEntryLocationFact.setRawValue(_gridEntryLocationFact.rawDefaultValue()); + } + + if (!_manualGridFact.rawValue().toBool()) { + if (!v2Object.contains(_jsonCameraObjectKey)) { + errorString = tr("%1 but %2 object is missing").arg("manualGrid = false").arg("camera"); + return false; + } + + QJsonObject cameraObject = v2Object[_jsonCameraObjectKey].toObject(); + + // Older code had typo on "imageSideOverlap" incorrectly being "imageSizeOverlap" + QString incorrectImageSideOverlap = "imageSizeOverlap"; + if (cameraObject.contains(incorrectImageSideOverlap)) { + cameraObject[_jsonSideOverlapKey] = cameraObject[incorrectImageSideOverlap]; + cameraObject.remove(incorrectImageSideOverlap); + } + + QList cameraKeyInfoList = { + { _jsonGroundResolutionKey, QJsonValue::Double, true }, + { _jsonFrontalOverlapKey, QJsonValue::Double, true }, + { _jsonSideOverlapKey, QJsonValue::Double, true }, + { _jsonCameraSensorWidthKey, QJsonValue::Double, true }, + { _jsonCameraSensorHeightKey, QJsonValue::Double, true }, + { _jsonCameraResolutionWidthKey, QJsonValue::Double, true }, + { _jsonCameraResolutionHeightKey, QJsonValue::Double, true }, + { _jsonCameraFocalLengthKey, QJsonValue::Double, true }, + { _jsonCameraNameKey, QJsonValue::String, true }, + { _jsonCameraOrientationLandscapeKey, QJsonValue::Bool, true }, + { _jsonCameraMinTriggerIntervalKey, QJsonValue::Double, false }, + }; + if (!JsonHelper::validateKeys(cameraObject, cameraKeyInfoList, errorString)) { + return false; + } + + _cameraFact.setRawValue(cameraObject[_jsonCameraNameKey].toString()); + _cameraOrientationLandscapeFact.setRawValue(cameraObject[_jsonCameraOrientationLandscapeKey].toBool(true)); + + _groundResolutionFact.setRawValue (cameraObject[_jsonGroundResolutionKey].toDouble()); + _frontalOverlapFact.setRawValue (cameraObject[_jsonFrontalOverlapKey].toInt()); + _sideOverlapFact.setRawValue (cameraObject[_jsonSideOverlapKey].toInt()); + _cameraSensorWidthFact.setRawValue (cameraObject[_jsonCameraSensorWidthKey].toDouble()); + _cameraSensorHeightFact.setRawValue (cameraObject[_jsonCameraSensorHeightKey].toDouble()); + _cameraResolutionWidthFact.setRawValue (cameraObject[_jsonCameraResolutionWidthKey].toDouble()); + _cameraResolutionHeightFact.setRawValue (cameraObject[_jsonCameraResolutionHeightKey].toDouble()); + _cameraFocalLengthFact.setRawValue (cameraObject[_jsonCameraFocalLengthKey].toDouble()); + _cameraMinTriggerInterval = cameraObject[_jsonCameraMinTriggerIntervalKey].toDouble(0); + } + + // Polygon shape + /// Load a polygon from json + /// @param json Json object to load from + /// @param required true: no polygon in object will generate error + /// @param errorString Error string if return is false + /// @return true: success, false: failure (errorString set) + if (!_mapPolygon.loadFromJson(v2Object, true /* required */, errorString)) { + _mapPolygon.clear(); + return false; + } + + _ignoreRecalc = false; + _generateGrid(); + + return true; +} + +double SurveyMissionItem::greatestDistanceTo(const QGeoCoordinate &other) const +{ + double greatestDistance = 0.0; + for (int i=0; i<_simpleGridPoints.count(); i++) { + QGeoCoordinate currentCoord = _simpleGridPoints[i].value(); + double distance = currentCoord.distanceTo(other); + if (distance > greatestDistance) { + greatestDistance = distance; + } + } + return greatestDistance; +} + +void SurveyMissionItem::_setExitCoordinate(const QGeoCoordinate& coordinate) +{ + if (_exitCoordinate != coordinate) { + _exitCoordinate = coordinate; + emit exitCoordinateChanged(coordinate); + } +} + +bool SurveyMissionItem::specifiesCoordinate(void) const +{ + return _mapPolygon.count() > 2; +} + +void _calcCameraShots() +{ + +} + +void SurveyMissionItem::_convertTransectToGeo(const QList>& transectSegmentsNED, const QGeoCoordinate& tangentOrigin, QList>& transectSegmentsGeo) +{ + transectSegmentsGeo.clear(); + + for (int i=0; i transectCoords; + const QList& transectPoints = transectSegmentsNED[i]; + + for (int j=0; j>& transects) +{ + QList> rgReversedTransects; + for (int i=transects.count() - 1; i>=0; i--) { + rgReversedTransects.append(transects[i]); + } + transects = rgReversedTransects; +} + +/// Reverse the order of all points withing each transect, First point becomes last and so forth. +void SurveyMissionItem::_reverseInternalTransectPoints(QList>& transects) +{ + for (int i=0; i rgReversedCoords; + QList& rgOriginalCoords = transects[i]; + for (int j=rgOriginalCoords.count()-1; j>=0; j--) { + rgReversedCoords.append(rgOriginalCoords[j]); + } + transects[i] = rgReversedCoords; + } +} + +/// Reorders the transects such that the first transect is the shortest distance to the specified coordinate +/// and the first point within that transect is the shortest distance to the specified coordinate. +/// @param distanceCoord Coordinate to measure distance against +/// @param transects Transects to test and reorder +void SurveyMissionItem::_optimizeTransectsForShortestDistance(const QGeoCoordinate& distanceCoord, QList>& transects) +{ + double rgTransectDistance[4]; + rgTransectDistance[0] = transects.first().first().distanceTo(distanceCoord); + rgTransectDistance[1] = transects.first().last().distanceTo(distanceCoord); + rgTransectDistance[2] = transects.last().first().distanceTo(distanceCoord); + rgTransectDistance[3] = transects.last().last().distanceTo(distanceCoord); + + int shortestIndex = 0; + double shortestDistance = rgTransectDistance[0]; + for (int i=1; i<3; i++) { + if (rgTransectDistance[i] < shortestDistance) { + shortestIndex = i; + shortestDistance = rgTransectDistance[i]; + } + } + + if (shortestIndex > 1) { + // We need to reverse the order of segments + _reverseTransectOrder(transects); + } + if (shortestIndex & 1) { + // We need to reverse the points within each segment + _reverseInternalTransectPoints(transects); + } +} + +void SurveyMissionItem::_appendGridPointsFromTransects(QList>& rgTransectSegments) +{ + qCDebug(SurveyMissionItemLog) << "Entry point _appendGridPointsFromTransects" << rgTransectSegments.first().first(); + + for (int i=0; i& points, int index1, int index2) +{ + QPointF temp = points[index1]; + points[index1] = points[index2]; + points[index2] = temp; +} + +/// Returns true if the current grid angle generates north/south oriented transects +bool SurveyMissionItem::_gridAngleIsNorthSouthTransects() +{ + // Grid angle ranges from -360<->360 + double gridAngle = qAbs(_gridAngleFact.rawValue().toDouble()); + return gridAngle < 45.0 || (gridAngle > 360.0 - 45.0) || (gridAngle > 90.0 + 45.0 && gridAngle < 270.0 - 45.0); +} + +void SurveyMissionItem::_adjustTransectsToEntryPointLocation(QList>& transects) +{ + if (transects.count() == 0) { + return; + } + + int entryLocation = _gridEntryLocationFact.rawValue().toInt(); + bool reversePoints = false; + bool reverseTransects = false; + + if (entryLocation == EntryLocationBottomLeft || entryLocation == EntryLocationBottomRight) { + reversePoints = true; + } + if (entryLocation == EntryLocationTopRight || entryLocation == EntryLocationBottomRight) { + reverseTransects = true; + } + + if (reversePoints) { + qCDebug(SurveyMissionItemLog) << "Reverse Points"; + _reverseInternalTransectPoints(transects); + } + if (reverseTransects) { + qCDebug(SurveyMissionItemLog) << "Reverse Transects"; + _reverseTransectOrder(transects); + } + + qCDebug(SurveyMissionItemLog) << "Modified entry point" << transects.first().first(); +} + +int SurveyMissionItem::_calcMissionCommandCount(QList>& transectSegments) +{ + int missionCommandCount= 0; + for (int i=0; i& transectSegment = transectSegments[i]; + + missionCommandCount += transectSegment.count(); // This accounts for all waypoints + if (_hoverAndCaptureEnabled()) { + // Internal camera trigger points are entry point, plus all points before exit point + missionCommandCount += transectSegment.count() - (_hasTurnaround() ? 2 : 0) - 1; + } else if (_triggerCamera() && !_imagesEverywhere()) { + // Camera on/off at entry/exit of each transect + missionCommandCount += 2; + } + } + if (transectSegments.count() && _triggerCamera() && _imagesEverywhere()) { + // Camera on/off for entire survey + missionCommandCount += 2; + } + + return missionCommandCount; +} + +void SurveyMissionItem::_calcBoundingCube() +{ + // Calc bounding cube + double north = 0.0; + double south = 180.0; + double east = 0.0; + double west = 360.0; + for (int i = 0; i < _simpleGridPoints.count(); i++) { + QGeoCoordinate coord = _simpleGridPoints[i].value(); + double lat = coord.latitude() + 90.0; + double lon = coord.longitude() + 180.0; + north = fmax(north, lat); + south = fmin(south, lat); + east = fmax(east, lon); + west = fmin(west, lon); + } + _setBoundingCube(QGCGeoBoundingCube( + QGeoCoordinate(north - 90.0, west - 180.0, _gridAltitudeFact.rawValue().toDouble()), + QGeoCoordinate(south - 90.0, east - 180.0, _gridAltitudeFact.rawValue().toDouble()))); +} + +void SurveyMissionItem::_generateGrid(void) +{ + if (_ignoreRecalc) { + return; + } + + if (_mapPolygon.count() < 3 || _gridSpacingFact.rawValue().toDouble() <= 0) { + _clearInternal(); + return; + } + + _simpleGridPoints.clear(); + _transectSegments.clear(); + _reflyTransectSegments.clear(); + _additionalFlightDelaySeconds = 0; + + QList polygonPoints; + QList> transectSegments; + + // Convert polygon to NED + QGeoCoordinate tangentOrigin = _mapPolygon.pathModel().value(0)->coordinate(); + qCDebug(SurveyMissionItemLog) << "Convert polygon to NED - tangentOrigin" << tangentOrigin; + for (int i=0; i<_mapPolygon.count(); i++) { + double y, x, down; + QGeoCoordinate vertex = _mapPolygon.pathModel().value(i)->coordinate(); + if (i == 0) { + // This avoids a nan calculation that comes out of convertGeoToNed + x = y = 0; + } else { + convertGeoToNed(vertex, tangentOrigin, &y, &x, &down); + } + polygonPoints += QPointF(x, y); + qCDebug(SurveyMissionItemLog) << "vertex:x:y" << vertex << polygonPoints.last().x() << polygonPoints.last().y(); + } + + double coveredArea = 0.0; + for (int i=0; i(); + QGeoCoordinate coord2 = _simpleGridPoints[i].value(); + surveyDistance += coord1.distanceTo(coord2); + } + _setSurveyDistance(surveyDistance); + // Calc bounding cube + _calcBoundingCube(); + + if (cameraShots == 0 && _triggerCamera()) { + cameraShots = (int)floor(surveyDistance / _triggerDistance()); + // Take into account immediate camera trigger at waypoint entry + cameraShots++; + } + _setCameraShots(cameraShots); + + if (_hoverAndCaptureEnabled()) { + _additionalFlightDelaySeconds = cameraShots * _hoverAndCaptureDelaySeconds; + } + emit additionalTimeDelayChanged(); + + emit gridPointsChanged(); + + // Determine command count for lastSequenceNumber + _missionCommandCount = _calcMissionCommandCount(_transectSegments); + _missionCommandCount += _calcMissionCommandCount(_reflyTransectSegments); + emit lastSequenceNumberChanged(lastSequenceNumber()); + + // Set exit coordinate + if (_simpleGridPoints.count()) { + QGeoCoordinate coordinate = _simpleGridPoints.first().value(); + coordinate.setAltitude(_gridAltitudeFact.rawValue().toDouble()); + setCoordinate(coordinate); + QGeoCoordinate exitCoordinate = _simpleGridPoints.last().value(); + exitCoordinate.setAltitude(_gridAltitudeFact.rawValue().toDouble()); + _setExitCoordinate(exitCoordinate); + } + + setDirty(true); +} + +void SurveyMissionItem::_updateCoordinateAltitude(void) +{ + _coordinate.setAltitude(_gridAltitudeFact.rawValue().toDouble()); + _exitCoordinate.setAltitude(_gridAltitudeFact.rawValue().toDouble()); + emit coordinateChanged(_coordinate); + emit exitCoordinateChanged(_exitCoordinate); + setDirty(true); +} + +QPointF SurveyMissionItem::_rotatePoint(const QPointF& point, const QPointF& origin, double angle) +{ + QPointF rotated; + double radians = (M_PI / 180.0) * -angle; + + rotated.setX(((point.x() - origin.x()) * cos(radians)) - ((point.y() - origin.y()) * sin(radians)) + origin.x()); + rotated.setY(((point.x() - origin.x()) * sin(radians)) + ((point.y() - origin.y()) * cos(radians)) + origin.y()); + + return rotated; +} + +void SurveyMissionItem::_intersectLinesWithRect(const QList& lineList, const QRectF& boundRect, QList& resultLines) +{ + QLineF topLine (boundRect.topLeft(), boundRect.topRight()); + QLineF bottomLine (boundRect.bottomLeft(), boundRect.bottomRight()); + QLineF leftLine (boundRect.topLeft(), boundRect.bottomLeft()); + QLineF rightLine (boundRect.topRight(), boundRect.bottomRight()); + + for (int i=0; i& lineList, const QPolygonF& polygon, QList& resultLines) +{ + resultLines.clear(); + + for (int i=0; i intersections; + + // Intersect the line with all the polygon edges + for (int j=0; j 1) { + QPointF firstPoint; + QPointF secondPoint; + double currentMaxDistance = 0; + + for (int i=0; i currentMaxDistance) { + firstPoint = intersections[i]; + secondPoint = intersections[j]; + currentMaxDistance = newMaxDistance; + } + } + } + + resultLines += QLineF(firstPoint, secondPoint); + } + } +} + +/// Adjust the line segments such that they are all going the same direction with respect to going from P1->P2 +void SurveyMissionItem::_adjustLineDirection(const QList& lineList, QList& resultLines) +{ + qreal firstAngle = 0; + for (int i=0; i 1.0) { + adjustedLine.setP1(line.p2()); + adjustedLine.setP2(line.p1()); + } else { + adjustedLine = line; + } + + resultLines += adjustedLine; + } +} + +double SurveyMissionItem::_clampGridAngle90(double gridAngle) +{ + // Clamp grid angle to -90<->90. This prevents transects from being rotated to a reversed order. + if (gridAngle > 90.0) { + gridAngle -= 180.0; + } else if (gridAngle < -90.0) { + gridAngle += 180; + } + return gridAngle; +} + +int SurveyMissionItem::_gridGenerator(const QList& polygonPoints, QList>& transectSegments, bool refly) +{ + int cameraShots = 0; + + double gridAngle = _gridAngleFact.rawValue().toDouble(); + double gridSpacing = _gridSpacingFact.rawValue().toDouble(); + + gridAngle = _clampGridAngle90(gridAngle); + gridAngle += refly ? 90 : 0; + qCDebug(SurveyMissionItemLog) << "Clamped grid angle" << gridAngle; + + qCDebug(SurveyMissionItemLog) << "SurveyMissionItem::_gridGenerator gridSpacing:gridAngle:refly" << gridSpacing << gridAngle << refly; + + transectSegments.clear(); + + // Convert polygon to bounding rect + + qCDebug(SurveyMissionItemLog) << "Polygon"; + QPolygonF polygon; + for (int i=0; i lineList; + + // Transects are generated to be as long as the largest width/height of the bounding rect plus some fudge factor. + // This way they will always be guaranteed to intersect with a polygon edge no matter what angle they are rotated to. + // They are initially generated with the transects flowing from west to east and then points within the transect north to south. + double maxWidth = qMax(boundingRect.width(), boundingRect.height()) + 2000.0; + double halfWidth = maxWidth / 2.0; + double transectX = boundingCenter.x() - halfWidth; + double transectXMax = transectX + maxWidth; + while (transectX < transectXMax) { + double transectYTop = boundingCenter.y() - halfWidth; + double transectYBottom = boundingCenter.y() + halfWidth; + + lineList += QLineF(_rotatePoint(QPointF(transectX, transectYTop), boundingCenter, gridAngle), _rotatePoint(QPointF(transectX, transectYBottom), boundingCenter, gridAngle)); + transectX += gridSpacing; + } + + // Now intersect the lines with the polygon + QList intersectLines; +#if 1 + _intersectLinesWithPolygon(lineList, polygon, intersectLines); +#else + // This is handy for debugging grid problems, not for release + intersectLines = lineList; +#endif + + // Less than two transects intersected with the polygon: + // Create a single transect which goes through the center of the polygon + // Intersect it with the polygon + if (intersectLines.count() < 2) { + _mapPolygon.center(); + QLineF firstLine = lineList.first(); + QPointF lineCenter = firstLine.pointAt(0.5); + QPointF centerOffset = boundingCenter - lineCenter; + firstLine.translate(centerOffset); + lineList.clear(); + lineList.append(firstLine); + intersectLines = lineList; + _intersectLinesWithPolygon(lineList, polygon, intersectLines); + } + + // Make sure all lines are going to same direction. Polygon intersection leads to line which + // can be in varied directions depending on the order of the intesecting sides. + QList resultLines; + _adjustLineDirection(intersectLines, resultLines); + + // Calc camera shots here if there are no images in turnaround + if (_triggerCamera() && !_imagesEverywhere()) { + for (int i=0; i transectPoints; + const QLineF& line = resultLines[i]; + + float turnaroundPosition = _turnaroundDistance() / line.length(); + + if (i & 1) { + transectLine = QLineF(line.p2(), line.p1()); + } else { + transectLine = QLineF(line.p1(), line.p2()); + } + + // Build the points along the transect + + if (_hasTurnaround()) { + transectPoints.append(transectLine.pointAt(-turnaroundPosition)); + } + + // Polygon entry point + transectPoints.append(transectLine.p1()); + + // For hover and capture we need points for each camera location + if (_triggerCamera() && _hoverAndCaptureEnabled()) { + if (_triggerDistance() < transectLine.length()) { + int innerPoints = floor(transectLine.length() / _triggerDistance()); + qCDebug(SurveyMissionItemLog) << "innerPoints" << innerPoints; + float transectPositionIncrement = _triggerDistance() / transectLine.length(); + for (int i=0; i& items, int seqNum, QGeoCoordinate& coord, CameraTriggerCode cameraTrigger, QObject* missionItemParent) +{ + double altitude = _gridAltitudeFact.rawValue().toDouble(); + bool altitudeRelative = _gridAltitudeRelativeFact.rawValue().toBool(); + + qCDebug(SurveyMissionItemLog) << "_appendWaypointToMission seq:trigger" << seqNum << (cameraTrigger != CameraTriggerNone); + + MissionItem* item = new MissionItem(seqNum++, + MAV_CMD_NAV_WAYPOINT, + altitudeRelative ? MAV_FRAME_GLOBAL_RELATIVE_ALT : MAV_FRAME_GLOBAL, + cameraTrigger == CameraTriggerHoverAndCapture ? _hoverAndCaptureDelaySeconds : 0, // Hold time (delay for hover and capture to settle vehicle before image is taken) + 0.0, 0.0, + std::numeric_limits::quiet_NaN(), // Yaw unchanged + coord.latitude(), + coord.longitude(), + altitude, + true, // autoContinue + false, // isCurrentItem + missionItemParent); + items.append(item); + + switch (cameraTrigger) { + case CameraTriggerOff: + case CameraTriggerOn: + item = new MissionItem(seqNum++, + MAV_CMD_DO_SET_CAM_TRIGG_DIST, + MAV_FRAME_MISSION, + cameraTrigger == CameraTriggerOn ? _triggerDistance() : 0, + 0, // shutter integration (ignore) + cameraTrigger == CameraTriggerOn ? 1 : 0, // trigger immediately when starting + 0, 0, 0, 0, // param 4-7 unused + true, // autoContinue + false, // isCurrentItem + missionItemParent); + items.append(item); + break; + case CameraTriggerHoverAndCapture: + item = new MissionItem(seqNum++, + MAV_CMD_IMAGE_START_CAPTURE, + MAV_FRAME_MISSION, + 0, // Reserved (Set to 0) + 0, // Interval (none) + 1, // Take 1 photo + NAN, NAN, NAN, NAN, // param 4-7 reserved + true, // autoContinue + false, // isCurrentItem + missionItemParent); + items.append(item); +#if 0 + // This generates too many commands. Pulling out for now, to see if image quality is still high enough. + item = new MissionItem(seqNum++, + MAV_CMD_NAV_DELAY, + MAV_FRAME_MISSION, + 0.5, // Delay in seconds, give some time for image to be taken + -1, -1, -1, // No time + 0, 0, 0, // Param 5-7 unused + true, // autoContinue + false, // isCurrentItem + missionItemParent); + items.append(item); +#endif + default: + break; + } + + return seqNum; +} + +bool SurveyMissionItem::_nextTransectCoord(const QList& transectPoints, int pointIndex, QGeoCoordinate& coord) +{ + if (pointIndex > transectPoints.count()) { + qWarning() << "Bad grid generation"; + return false; + } + + coord = transectPoints[pointIndex]; + return true; +} + +/// Appends the mission items for the survey +/// @param items Mission items are appended to this list +/// @param missionItemParent Parent object for newly created MissionItem objects +/// @param seqNum[in,out] Sequence number to start from +/// @param hasRefly true: misison has a refly section +/// @param buildRefly: true: build the refly section, false: build the first section +/// @return false: Generation failed +bool SurveyMissionItem::_appendMissionItemsWorker(QList& items, QObject* missionItemParent, int& seqNum, bool hasRefly, bool buildRefly) +{ + bool firstWaypointTrigger = false; + + qCDebug(SurveyMissionItemLog) << QStringLiteral("hasTurnaround(%1) triggerCamera(%2) hoverAndCapture(%3) imagesEverywhere(%4) hasRefly(%5) buildRefly(%6) ").arg(_hasTurnaround()).arg(_triggerCamera()).arg(_hoverAndCaptureEnabled()).arg(_imagesEverywhere()).arg(hasRefly).arg(buildRefly); + + QList>& transectSegments = buildRefly ? _reflyTransectSegments : _transectSegments; + + if (!buildRefly && _imagesEverywhere()) { + firstWaypointTrigger = true; + } + + for (int segmentIndex=0; segmentIndex& segment = transectSegments[segmentIndex]; + + qCDebug(SurveyMissionItemLog) << "segment.count" << segment.count(); + + if (_hasTurnaround()) { + // Add entry turnaround point + if (!_nextTransectCoord(segment, pointIndex++, coord)) { + return false; + } + seqNum = _appendWaypointToMission(items, seqNum, coord, firstWaypointTrigger ? CameraTriggerOn : CameraTriggerNone, missionItemParent); + firstWaypointTrigger = false; + } + + // Add polygon entry point + if (!_nextTransectCoord(segment, pointIndex++, coord)) { + return false; + } + if (firstWaypointTrigger) { + cameraTrigger = CameraTriggerOn; + } else { + cameraTrigger = _imagesEverywhere() || !_triggerCamera() ? CameraTriggerNone : (_hoverAndCaptureEnabled() ? CameraTriggerHoverAndCapture : CameraTriggerOn); + } + seqNum = _appendWaypointToMission(items, seqNum, coord, cameraTrigger, missionItemParent); + firstWaypointTrigger = false; + + // Add internal hover and capture points + if (_hoverAndCaptureEnabled()) { + int lastHoverAndCaptureIndex = segment.count() - 1 - (_hasTurnaround() ? 1 : 0); + qCDebug(SurveyMissionItemLog) << "lastHoverAndCaptureIndex" << lastHoverAndCaptureIndex; + for (; pointIndex < lastHoverAndCaptureIndex; pointIndex++) { + if (!_nextTransectCoord(segment, pointIndex, coord)) { + return false; + } + seqNum = _appendWaypointToMission(items, seqNum, coord, CameraTriggerHoverAndCapture, missionItemParent); + } + } + + // Add polygon exit point + if (!_nextTransectCoord(segment, pointIndex++, coord)) { + return false; + } + cameraTrigger = _imagesEverywhere() || !_triggerCamera() ? CameraTriggerNone : (_hoverAndCaptureEnabled() ? CameraTriggerNone : CameraTriggerOff); + seqNum = _appendWaypointToMission(items, seqNum, coord, cameraTrigger, missionItemParent); + + if (_hasTurnaround()) { + // Add exit turnaround point + if (!_nextTransectCoord(segment, pointIndex++, coord)) { + return false; + } + seqNum = _appendWaypointToMission(items, seqNum, coord, CameraTriggerNone, missionItemParent); + } + + qCDebug(SurveyMissionItemLog) << "last PointIndex" << pointIndex; + } + + if (((hasRefly && buildRefly) || !hasRefly) && _imagesEverywhere()) { + // Turn off camera at end of survey + MissionItem* item = new MissionItem(seqNum++, + MAV_CMD_DO_SET_CAM_TRIGG_DIST, + MAV_FRAME_MISSION, + 0.0, // trigger distance (off) + 0, 0, 0, 0, 0, 0, // param 2-7 unused + true, // autoContinue + false, // isCurrentItem + missionItemParent); + items.append(item); + } + + return true; +} + +void SurveyMissionItem::appendMissionItems(QList& items, QObject* missionItemParent) +{ + int seqNum = _sequenceNumber; + + if (!_appendMissionItemsWorker(items, missionItemParent, seqNum, _refly90Degrees, false /* buildRefly */)) { + return; + } + + if (_refly90Degrees) { + _appendMissionItemsWorker(items, missionItemParent, seqNum, _refly90Degrees, true /* buildRefly */); + } +} + +int SurveyMissionItem::cameraShots(void) const +{ + return _triggerCamera() ? _cameraShots : 0; +} + +void SurveyMissionItem::_cameraValueChanged(void) +{ + emit cameraValueChanged(); +} + +double SurveyMissionItem::timeBetweenShots(void) const +{ + return _cruiseSpeed == 0 ? 0 : _triggerDistance() / _cruiseSpeed; +} + +void SurveyMissionItem::setMissionFlightStatus(MissionController::MissionFlightStatus_t& missionFlightStatus) +{ + ComplexMissionItem::setMissionFlightStatus(missionFlightStatus); + if (!qFuzzyCompare(_cruiseSpeed, missionFlightStatus.vehicleSpeed)) { + _cruiseSpeed = missionFlightStatus.vehicleSpeed; + emit timeBetweenShotsChanged(); + } +} + +void SurveyMissionItem::_setDirty(void) +{ + setDirty(true); +} + +bool SurveyMissionItem::hoverAndCaptureAllowed(void) const +{ + return _vehicle->multiRotor() || _vehicle->vtol(); +} + +double SurveyMissionItem::_triggerDistance(void) const { + return _cameraTriggerDistanceFact.rawValue().toDouble(); +} + +bool SurveyMissionItem::_triggerCamera(void) const +{ + return _triggerDistance() > 0; +} + +bool SurveyMissionItem::_imagesEverywhere(void) const +{ + return _triggerCamera() && _cameraTriggerInTurnaroundFact.rawValue().toBool(); +} + +bool SurveyMissionItem::_hoverAndCaptureEnabled(void) const +{ + return hoverAndCaptureAllowed() && !_imagesEverywhere() && _triggerCamera() && _hoverAndCaptureFact.rawValue().toBool(); +} + +bool SurveyMissionItem::_hasTurnaround(void) const +{ + return _turnaroundDistance() > 0; +} + +double SurveyMissionItem::_turnaroundDistance(void) const +{ + return _turnaroundDistFact.rawValue().toDouble(); +} + +void SurveyMissionItem::applyNewAltitude(double newAltitude) +{ + _fixedValueIsAltitudeFact.setRawValue(true); + _gridAltitudeFact.setRawValue(newAltitude); + _calcBoundingCube(); +} + +void SurveyMissionItem::setRefly90Degrees(bool refly90Degrees) +{ + if (refly90Degrees != _refly90Degrees) { + _refly90Degrees = refly90Degrees; + emit refly90DegreesChanged(refly90Degrees); + } +} + +void SurveyMissionItem::_polygonDirtyChanged(bool dirty) +{ + if (dirty) { + setDirty(true); + } +} diff --git a/src/MissionManager/SurveyMissionItem.h b/src/MissionManager/SurveyMissionItem.h new file mode 100644 index 0000000000000000000000000000000000000000..61533ec8976a31a3611164b3fb4b04a8ca746038 --- /dev/null +++ b/src/MissionManager/SurveyMissionItem.h @@ -0,0 +1,305 @@ +/**************************************************************************** + * + * (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. + * + ****************************************************************************/ + + +#ifndef SurveyMissionItem_H +#define SurveyMissionItem_H + +#include "ComplexMissionItem.h" +#include "MissionItem.h" +#include "SettingsFact.h" +#include "QGCLoggingCategory.h" +#include "QGCMapPolygon.h" + +Q_DECLARE_LOGGING_CATEGORY(SurveyMissionItemLog) + +class SurveyMissionItem : public ComplexMissionItem +{ + Q_OBJECT + +public: + SurveyMissionItem(Vehicle* vehicle, QObject* parent = NULL); + + Q_PROPERTY(Fact* gridAltitude READ gridAltitude CONSTANT) + Q_PROPERTY(Fact* gridAltitudeRelative READ gridAltitudeRelative CONSTANT) + Q_PROPERTY(Fact* gridAngle READ gridAngle CONSTANT) + Q_PROPERTY(Fact* gridSpacing READ gridSpacing CONSTANT) + Q_PROPERTY(Fact* gridEntryLocation READ gridEntryLocation CONSTANT) + Q_PROPERTY(Fact* turnaroundDist READ turnaroundDist CONSTANT) + Q_PROPERTY(Fact* cameraTriggerDistance READ cameraTriggerDistance CONSTANT) + Q_PROPERTY(Fact* cameraTriggerInTurnaround READ cameraTriggerInTurnaround CONSTANT) + Q_PROPERTY(Fact* hoverAndCapture READ hoverAndCapture CONSTANT) + Q_PROPERTY(Fact* groundResolution READ groundResolution CONSTANT) + Q_PROPERTY(Fact* frontalOverlap READ frontalOverlap CONSTANT) + Q_PROPERTY(Fact* sideOverlap READ sideOverlap CONSTANT) + Q_PROPERTY(Fact* cameraSensorWidth READ cameraSensorWidth CONSTANT) + Q_PROPERTY(Fact* cameraSensorHeight READ cameraSensorHeight CONSTANT) + Q_PROPERTY(Fact* cameraResolutionWidth READ cameraResolutionWidth CONSTANT) + Q_PROPERTY(Fact* cameraResolutionHeight READ cameraResolutionHeight CONSTANT) + Q_PROPERTY(Fact* cameraFocalLength READ cameraFocalLength CONSTANT) + Q_PROPERTY(Fact* cameraOrientationLandscape READ cameraOrientationLandscape CONSTANT) + Q_PROPERTY(Fact* fixedValueIsAltitude READ fixedValueIsAltitude CONSTANT) + Q_PROPERTY(Fact* manualGrid READ manualGrid CONSTANT) + Q_PROPERTY(Fact* camera READ camera CONSTANT) + + Q_PROPERTY(bool cameraOrientationFixed MEMBER _cameraOrientationFixed NOTIFY cameraOrientationFixedChanged) + Q_PROPERTY(bool hoverAndCaptureAllowed READ hoverAndCaptureAllowed CONSTANT) + Q_PROPERTY(bool refly90Degrees READ refly90Degrees WRITE setRefly90Degrees NOTIFY refly90DegreesChanged) + Q_PROPERTY(double cameraMinTriggerInterval MEMBER _cameraMinTriggerInterval NOTIFY cameraMinTriggerIntervalChanged) + + Q_PROPERTY(double timeBetweenShots READ timeBetweenShots NOTIFY timeBetweenShotsChanged) + Q_PROPERTY(QVariantList gridPoints READ gridPoints NOTIFY gridPointsChanged) + Q_PROPERTY(int cameraShots READ cameraShots NOTIFY cameraShotsChanged) + Q_PROPERTY(double coveredArea READ coveredArea NOTIFY coveredAreaChanged) + + Q_PROPERTY(QGCMapPolygon* mapPolygon READ mapPolygon CONSTANT) + + QVariantList gridPoints (void) { return _simpleGridPoints; } + + Fact* manualGrid (void) { return &_manualGridFact; } + Fact* gridAltitude (void) { return &_gridAltitudeFact; } + Fact* gridAltitudeRelative (void) { return &_gridAltitudeRelativeFact; } + Fact* gridAngle (void) { return &_gridAngleFact; } + Fact* gridSpacing (void) { return &_gridSpacingFact; } + Fact* gridEntryLocation (void) { return &_gridEntryLocationFact; } + Fact* turnaroundDist (void) { return &_turnaroundDistFact; } + Fact* cameraTriggerDistance (void) { return &_cameraTriggerDistanceFact; } + Fact* cameraTriggerInTurnaround (void) { return &_cameraTriggerInTurnaroundFact; } + Fact* hoverAndCapture (void) { return &_hoverAndCaptureFact; } + Fact* groundResolution (void) { return &_groundResolutionFact; } + Fact* frontalOverlap (void) { return &_frontalOverlapFact; } + Fact* sideOverlap (void) { return &_sideOverlapFact; } + Fact* cameraSensorWidth (void) { return &_cameraSensorWidthFact; } + Fact* cameraSensorHeight (void) { return &_cameraSensorHeightFact; } + Fact* cameraResolutionWidth (void) { return &_cameraResolutionWidthFact; } + Fact* cameraResolutionHeight (void) { return &_cameraResolutionHeightFact; } + Fact* cameraFocalLength (void) { return &_cameraFocalLengthFact; } + Fact* cameraOrientationLandscape(void) { return &_cameraOrientationLandscapeFact; } + Fact* fixedValueIsAltitude (void) { return &_fixedValueIsAltitudeFact; } + Fact* camera (void) { return &_cameraFact; } + + int cameraShots (void) const; + double coveredArea (void) const { return _coveredArea; } + double timeBetweenShots (void) const; + bool hoverAndCaptureAllowed (void) const; + bool refly90Degrees (void) const { return _refly90Degrees; } + QGCMapPolygon* mapPolygon (void) { return &_mapPolygon; } + + void setRefly90Degrees(bool refly90Degrees); + + // Overrides from ComplexMissionItem + + double complexDistance (void) const final { return _surveyDistance; } + QGCGeoBoundingCube boundingCube (void) const final { return _boundingCube; } + double additionalTimeDelay (void) const final { return _additionalFlightDelaySeconds; } + int lastSequenceNumber (void) const final; + bool load (const QJsonObject& complexObject, int sequenceNumber, QString& errorString) final; + double greatestDistanceTo (const QGeoCoordinate &other) const final; + QString mapVisualQML (void) const final { return QStringLiteral("SurveyMapVisual.qml"); } + + // Overrides from VisualMissionItem + + bool dirty (void) const final { return _dirty; } + bool isSimpleItem (void) const final { return false; } + bool isStandaloneCoordinate (void) const final { return false; } + bool specifiesCoordinate (void) const final; + bool specifiesAltitudeOnly (void) const final { return false; } + QString commandDescription (void) const final { return "Survey"; } + QString commandName (void) const final { return "Survey"; } + QString abbreviation (void) const final { return "S"; } + QGeoCoordinate coordinate (void) const final { return _coordinate; } + QGeoCoordinate exitCoordinate (void) const final { return _exitCoordinate; } + int sequenceNumber (void) const final { return _sequenceNumber; } + double specifiedFlightSpeed (void) final { return std::numeric_limits::quiet_NaN(); } + double specifiedGimbalYaw (void) final { return std::numeric_limits::quiet_NaN(); } + double specifiedGimbalPitch (void) final { return std::numeric_limits::quiet_NaN(); } + void appendMissionItems (QList& items, QObject* missionItemParent) final; + void setMissionFlightStatus (MissionController::MissionFlightStatus_t& missionFlightStatus) final; + void applyNewAltitude (double newAltitude) final; + + bool coordinateHasRelativeAltitude (void) const final { return _gridAltitudeRelativeFact.rawValue().toBool(); } + bool exitCoordinateHasRelativeAltitude (void) const final { return _gridAltitudeRelativeFact.rawValue().toBool(); } + bool exitCoordinateSameAsEntry (void) const final { return false; } + + void setDirty (bool dirty) final; + void setCoordinate (const QGeoCoordinate& coordinate) final; + void setSequenceNumber (int sequenceNumber) final; + void setTurnaroundDist (double dist) { _turnaroundDistFact.setRawValue(dist); } + void save (QJsonArray& missionItems) final; + + // Must match json spec for GridEntryLocation + enum EntryLocation { + EntryLocationTopLeft, + EntryLocationTopRight, + EntryLocationBottomLeft, + EntryLocationBottomRight, + }; + + static const char* jsonComplexItemTypeValue; + + static const char* settingsGroup; + static const char* manualGridName; + static const char* gridAltitudeName; + static const char* gridAltitudeRelativeName; + static const char* gridAngleName; + static const char* gridSpacingName; + static const char* gridEntryLocationName; + static const char* turnaroundDistName; + static const char* cameraTriggerDistanceName; + static const char* cameraTriggerInTurnaroundName; + static const char* hoverAndCaptureName; + static const char* groundResolutionName; + static const char* frontalOverlapName; + static const char* sideOverlapName; + static const char* cameraSensorWidthName; + static const char* cameraSensorHeightName; + static const char* cameraResolutionWidthName; + static const char* cameraResolutionHeightName; + static const char* cameraFocalLengthName; + static const char* cameraTriggerName; + static const char* cameraOrientationLandscapeName; + static const char* fixedValueIsAltitudeName; + static const char* cameraName; + +signals: + void gridPointsChanged (void); + void cameraShotsChanged (int cameraShots); + void coveredAreaChanged (double coveredArea); + void cameraValueChanged (void); + void gridTypeChanged (QString gridType); + void timeBetweenShotsChanged (void); + void cameraOrientationFixedChanged (bool cameraOrientationFixed); + void refly90DegreesChanged (bool refly90Degrees); + void cameraMinTriggerIntervalChanged (double cameraMinTriggerInterval); + +private slots: + void _setDirty(void); + void _polygonDirtyChanged(bool dirty); + void _clearInternal(void); + +private: + enum CameraTriggerCode { + CameraTriggerNone, + CameraTriggerOn, + CameraTriggerOff, + CameraTriggerHoverAndCapture + }; + + void _setExitCoordinate(const QGeoCoordinate& coordinate); + void _generateGrid(void); + void _updateCoordinateAltitude(void); + int _gridGenerator(const QList& polygonPoints, QList>& transectSegments, bool refly); + QPointF _rotatePoint(const QPointF& point, const QPointF& origin, double angle); + void _intersectLinesWithRect(const QList& lineList, const QRectF& boundRect, QList& resultLines); + void _intersectLinesWithPolygon(const QList& lineList, const QPolygonF& polygon, QList& resultLines); + void _adjustLineDirection(const QList& lineList, QList& resultLines); + void _setSurveyDistance(double surveyDistance); + void _setBoundingCube(QGCGeoBoundingCube bc); + void _setCameraShots(int cameraShots); + void _setCoveredArea(double coveredArea); + void _cameraValueChanged(void); + int _appendWaypointToMission(QList& items, int seqNum, QGeoCoordinate& coord, CameraTriggerCode cameraTrigger, QObject* missionItemParent); + bool _nextTransectCoord(const QList& transectPoints, int pointIndex, QGeoCoordinate& coord); + double _triggerDistance(void) const; + bool _triggerCamera(void) const; + bool _imagesEverywhere(void) const; + bool _hoverAndCaptureEnabled(void) const; + bool _hasTurnaround(void) const; + double _turnaroundDistance(void) const; + void _convertTransectToGeo(const QList>& transectSegmentsNED, const QGeoCoordinate& tangentOrigin, QList>& transectSegmentsGeo); + bool _appendMissionItemsWorker(QList& items, QObject* missionItemParent, int& seqNum, bool hasRefly, bool buildRefly); + void _optimizeTransectsForShortestDistance(const QGeoCoordinate& distanceCoord, QList>& transects); + void _appendGridPointsFromTransects(QList>& rgTransectSegments); + qreal _ccw(QPointF pt1, QPointF pt2, QPointF pt3); + qreal _dp(QPointF pt1, QPointF pt2); + void _swapPoints(QList& points, int index1, int index2); + void _reverseTransectOrder(QList>& transects); + void _reverseInternalTransectPoints(QList>& transects); + void _adjustTransectsToEntryPointLocation(QList>& transects); + bool _gridAngleIsNorthSouthTransects(); + double _clampGridAngle90(double gridAngle); + int _calcMissionCommandCount(QList>& transectSegments); + void _calcBoundingCube(); + + int _sequenceNumber; + bool _dirty; + QGCMapPolygon _mapPolygon; + QVariantList _simpleGridPoints; ///< Grid points for drawing simple grid visuals + QList> _transectSegments; ///< Internal transect segments including grid exit, turnaround and internal camera points + QList> _reflyTransectSegments; ///< Refly segments + QGeoCoordinate _coordinate; + QGeoCoordinate _exitCoordinate; + bool _cameraOrientationFixed; + int _missionCommandCount; + bool _refly90Degrees; + double _additionalFlightDelaySeconds; + double _cameraMinTriggerInterval; + + bool _ignoreRecalc; + double _surveyDistance; + int _cameraShots; + double _coveredArea; + double _timeBetweenShots; + double _cruiseSpeed; + + QGCGeoBoundingCube _boundingCube; + QMap _metaDataMap; + + SettingsFact _manualGridFact; + SettingsFact _gridAltitudeFact; + SettingsFact _gridAltitudeRelativeFact; + SettingsFact _gridAngleFact; + SettingsFact _gridSpacingFact; + SettingsFact _gridEntryLocationFact; + SettingsFact _turnaroundDistFact; + SettingsFact _cameraTriggerDistanceFact; + SettingsFact _cameraTriggerInTurnaroundFact; + SettingsFact _hoverAndCaptureFact; + SettingsFact _groundResolutionFact; + SettingsFact _frontalOverlapFact; + SettingsFact _sideOverlapFact; + SettingsFact _cameraSensorWidthFact; + SettingsFact _cameraSensorHeightFact; + SettingsFact _cameraResolutionWidthFact; + SettingsFact _cameraResolutionHeightFact; + SettingsFact _cameraFocalLengthFact; + SettingsFact _cameraOrientationLandscapeFact; + SettingsFact _fixedValueIsAltitudeFact; + SettingsFact _cameraFact; + + static const char* _jsonGridObjectKey; + static const char* _jsonGridAltitudeKey; + static const char* _jsonGridAltitudeRelativeKey; + static const char* _jsonGridAngleKey; + static const char* _jsonGridSpacingKey; + static const char* _jsonGridEntryLocationKey; + static const char* _jsonTurnaroundDistKey; + static const char* _jsonCameraTriggerDistanceKey; + static const char* _jsonCameraTriggerInTurnaroundKey; + static const char* _jsonHoverAndCaptureKey; + static const char* _jsonGroundResolutionKey; + static const char* _jsonFrontalOverlapKey; + static const char* _jsonSideOverlapKey; + static const char* _jsonCameraSensorWidthKey; + static const char* _jsonCameraSensorHeightKey; + static const char* _jsonCameraResolutionWidthKey; + static const char* _jsonCameraResolutionHeightKey; + static const char* _jsonCameraFocalLengthKey; + static const char* _jsonCameraMinTriggerIntervalKey; + static const char* _jsonManualGridKey; + static const char* _jsonCameraObjectKey; + static const char* _jsonCameraNameKey; + static const char* _jsonCameraOrientationLandscapeKey; + static const char* _jsonFixedValueIsAltitudeKey; + static const char* _jsonRefly90DegreesKey; + + static const int _hoverAndCaptureDelaySeconds = 4; +}; + +#endif diff --git a/src/MissionManager/TransectStyleComplexItem.cc b/src/MissionManager/TransectStyleComplexItem.cc index 8e7e4e2ae40339b8de23094495bfdc2cd6adea8a..22126e744a033c0503f846ea29917225835f3bb5 100644 --- a/src/MissionManager/TransectStyleComplexItem.cc +++ b/src/MissionManager/TransectStyleComplexItem.cc @@ -362,13 +362,32 @@ void TransectStyleComplexItem::_rebuildTransects(void) } } + // Calc bounding cube + double north = 0.0; + double south = 180.0; + double east = 0.0; + double west = 360.0; + double bottom = 100000.; + double top = 0.; // Generate the visuals transect representation _visualTransectPoints.clear(); foreach (const QList& transect, _transects) { foreach (const CoordInfo_t& coordInfo, transect) { _visualTransectPoints.append(QVariant::fromValue(coordInfo.coord)); + double lat = coordInfo.coord.latitude() + 90.0; + double lon = coordInfo.coord.longitude() + 180.0; + north = fmax(north, lat); + south = fmin(south, lat); + east = fmax(east, lon); + west = fmin(west, lon); + bottom = fmin(bottom, coordInfo.coord.altitude()); + top = fmax(top, coordInfo.coord.altitude()); } } + //-- Update bounding cube for airspace management control + _setBoundingCube(QGCGeoBoundingCube( + QGeoCoordinate(north - 90.0, west - 180.0, bottom), + QGeoCoordinate(south - 90.0, east - 180.0, top))); emit visualTransectPointsChanged(); _coordinate = _visualTransectPoints.count() ? _visualTransectPoints.first().value() : QGeoCoordinate(); @@ -382,6 +401,14 @@ void TransectStyleComplexItem::_rebuildTransects(void) emit timeBetweenShotsChanged(); } +void TransectStyleComplexItem::_setBoundingCube(QGCGeoBoundingCube bc) +{ + if (bc != _boundingCube) { + _boundingCube = bc; + emit boundingCubeChanged(); + } +} + void TransectStyleComplexItem::_queryTransectsPathHeightInfo(void) { _transectsPathHeightInfo.clear(); diff --git a/src/MissionManager/TransectStyleComplexItem.h b/src/MissionManager/TransectStyleComplexItem.h index f1c43071da0e0b34b3c7127ba22449414db22e5b..de84be6c46a40756b981c7dae16a1303d0efc45b 100644 --- a/src/MissionManager/TransectStyleComplexItem.h +++ b/src/MissionManager/TransectStyleComplexItem.h @@ -74,9 +74,10 @@ public: // Overrides from ComplexMissionItem - int lastSequenceNumber (void) const final; - QString mapVisualQML (void) const override = 0; - bool load (const QJsonObject& complexObject, int sequenceNumber, QString& errorString) override = 0; + int lastSequenceNumber (void) const final; + QString mapVisualQML (void) const override = 0; + bool load (const QJsonObject& complexObject, int sequenceNumber, QString& errorString) override = 0; + QGCGeoBoundingCube boundingCube (void) const override { return _boundingCube; } double complexDistance (void) const final { return _complexDistance; } double greatestDistanceTo (const QGeoCoordinate &other) const final; @@ -146,12 +147,14 @@ protected: double _triggerDistance (void) const; bool _hasTurnaround (void) const; double _turnaroundDistance (void) const; - - int _sequenceNumber; - bool _dirty; - QGeoCoordinate _coordinate; - QGeoCoordinate _exitCoordinate; - QGCMapPolygon _surveyAreaPolygon; + void _setBoundingCube (QGCGeoBoundingCube bc); + + int _sequenceNumber; + bool _dirty; + QGeoCoordinate _coordinate; + QGeoCoordinate _exitCoordinate; + QGCMapPolygon _surveyAreaPolygon; + QGCGeoBoundingCube _boundingCube; enum CoordType { CoordTypeInterior, ///< Interior waypoint for flight path only diff --git a/src/PlanView/GeoFenceEditor.qml b/src/PlanView/GeoFenceEditor.qml index 8cf3cb745a4a3eded03811ca3ffd91b9fa31e400..b5d12f23fa91c6da37496d6f63449f237c50361f 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/PlanView.qml b/src/PlanView/PlanView.qml index 3f772c219dff4c39f609fba92b72903cb48c7276..bf1b07c38d102712956afdd3be336b20d0001b86 100644 --- a/src/PlanView/PlanView.qml +++ b/src/PlanView/PlanView.qml @@ -25,6 +25,8 @@ import QGroundControl.FactControls 1.0 import QGroundControl.Palette 1.0 import QGroundControl.Controllers 1.0 import QGroundControl.KMLFileHelper 1.0 +import QGroundControl.Airspace 1.0 +import QGroundControl.Airmap 1.0 /// Mission Editor @@ -33,27 +35,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 _airspaceEnabled: QGroundControl.airmapSupported ? (QGroundControl.settingsManager.airMapSettings.enableAirMap.rawValue && QGroundControl.airspaceManager.connected): false + 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 @@ -67,9 +73,9 @@ QGCView { function addComplexItem(complexItemName) { var coordinate = editorMap.center - coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces) + coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces) coordinate.longitude = coordinate.longitude.toFixed(_decimalPlaces) - coordinate.altitude = coordinate.altitude.toFixed(_decimalPlaces) + coordinate.altitude = coordinate.altitude.toFixed(_decimalPlaces) insertComplexMissionItem(complexItemName, coordinate, _missionController.visualItems.count) } @@ -83,6 +89,16 @@ QGCView { _missionController.setCurrentPlanViewIndex(sequenceNumber, true) } + function updateAirspace(reset) { + if(_airspaceEnabled) { + var coordinateNW = editorMap.toCoordinate(Qt.point(0,0), false /* clipToViewPort */) + var coordinateSE = editorMap.toCoordinate(Qt.point(width,height), false /* clipToViewPort */) + if(coordinateNW.isValid && coordinateSE.isValid) { + QGroundControl.airspaceManager.setROI(coordinateNW, coordinateSE, true /*planView*/, reset) + } + } + } + property bool _firstMissionLoadComplete: false property bool _firstFenceLoadComplete: false property bool _firstRallyLoadComplete: false @@ -95,6 +111,19 @@ QGCView { planMasterController: _planMasterController } + on_AirspaceEnabledChanged: { + if(QGroundControl.airmapSupported) { + if(_airspaceEnabled) { + planControlColapsed = QGroundControl.airspaceManager.airspaceVisible + updateAirspace(true) + } else { + planControlColapsed = false + } + } else { + planControlColapsed = false + } + } + Connections { target: QGroundControl.settingsManager.appSettings.defaultMissionItemAltitude @@ -151,9 +180,15 @@ QGCView { } } + Connections { + target: QGroundControl.airspaceManager + onAirspaceVisibleChanged: { + planControlColapsed = QGroundControl.airspaceManager.airspaceVisible + } + } + Component { id: noItemForKML - QGCViewMessage { message: qsTr("You need at least one item to create a KML.") } @@ -410,7 +445,7 @@ QGCView { qgcView: _qgcView // 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 @@ -429,6 +464,9 @@ QGCView { QGCMapPalette { id: mapPal; lightColors: editorMap.isSatelliteMap } + onZoomLevelChanged: updateAirspace(false) + onCenterChanged: updateAirspace(false) + MouseArea { //-- It's a whole lot faster to just fill parent and deal with top offset below // than computing the coordinate offset. @@ -511,6 +549,28 @@ QGCView { planView: true } + // Airspace overlap support + MapItemView { + model: _airspaceEnabled && QGroundControl.airspaceManager.airspaceVisible ? QGroundControl.airspaceManager.airspaces.circles : [] + delegate: MapCircle { + center: object.center + radius: object.radius + color: object.color + border.color: object.lineColor + border.width: object.lineWidth + } + } + + MapItemView { + model: _airspaceEnabled && QGroundControl.airspaceManager.airspaceVisible ? QGroundControl.airspaceManager.airspaces.polygons : [] + delegate: MapPolygon { + path: object.polygon + color: object.color + border.color: object.lineColor + border.width: object.lineWidth + } + } + ToolStrip { id: toolStrip anchors.leftMargin: ScreenTools.defaultFontPixelWidth @@ -590,105 +650,192 @@ 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 + opacity: planExpanded.visible ? 0.2 : 0 + 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: _airspaceEnabled + planView: true + showColapse: true + } + //------------------------------------------------------- + // Mission Controls (Colapsed) + Rectangle { + width: parent.width + height: planControlColapsed ? colapsedRow.height + ScreenTools.defaultFontPixelHeight : 0 + color: qgcPal.missionItemEditor + radius: _radius + visible: planControlColapsed && _airspaceEnabled + 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: QGroundControl.airmapSupported ? "qrc:/airmap/expand.svg" : "" + color: "white" + visible: QGroundControl.airmapSupported + anchors.right: parent.right + anchors.rightMargin: ScreenTools.defaultFontPixelWidth + anchors.verticalCenter: parent.verticalCenter + } + MouseArea { + anchors.fill: parent + enabled: QGroundControl.airmapSupported + onClicked: { + QGroundControl.airspaceManager.airspaceVisible = false } } } - - 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 - } - - 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 || !_airspaceEnabled) ? expandedCol.height + ScreenTools.defaultFontPixelHeight : 0 + color: qgcPal.missionItemEditor + radius: _radius + visible: !planControlColapsed || !_airspaceEnabled + 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 @@ -696,9 +843,7 @@ QGCView { width: parent.width readOnly: false rootQgcView: _qgcView - onClicked: _missionController.setCurrentPlanViewIndex(object.sequenceNumber, false) - onRemove: { var removeIndex = index _missionController.removeMissionItem(removeIndex) @@ -707,17 +852,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 @@ -725,30 +868,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 @@ -769,7 +909,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/QGCApplication.cc b/src/QGCApplication.cc index 1f8f2151eb9619f01ef68c79c1b2402bd1984f6d..5508783295e012fec306ad07c47ca1e1d3292c0d 100644 --- a/src/QGCApplication.cc +++ b/src/QGCApplication.cc @@ -60,6 +60,7 @@ #include "JoystickConfigController.h" #include "JoystickManager.h" #include "QmlObjectListModel.h" +#include "QGCGeoBoundingCube.h" #include "MissionManager.h" #include "QGroundControlQmlGlobal.h" #include "FlightMapSettings.h" @@ -379,6 +380,8 @@ void QGCApplication::_initCommon(void) qmlRegisterUncreatableType ("QGroundControl.Controllers", 1, 0, "VisualMissionItem", "Reference only"); qmlRegisterUncreatableType("QGroundControl.FactControls", 1, 0, "FactValueSliderListModel","Reference only"); + qmlRegisterType ("QGroundControl", 1, 0, "QGCGeoBoundingCube"); + qmlRegisterType ("QGroundControl.Controllers", 1, 0, "ParameterEditorController"); qmlRegisterType ("QGroundControl.Controllers", 1, 0, "ESP8266ComponentController"); qmlRegisterType ("QGroundControl.Controllers", 1, 0, "ScreenToolsController"); diff --git a/src/QGCMapPalette.cc b/src/QGCMapPalette.cc index 0bdd60f77d678b797e6821cb28cf6aa59ffbd4d1..47af675f1ad355c3939cf33af327e2c80df076c3 100644 --- a/src/QGCMapPalette.cc +++ b/src/QGCMapPalette.cc @@ -15,7 +15,7 @@ QColor QGCMapPalette::_thumbJoystick[QGCMapPalette::_cColorGroups] = { QColor(255,255,255,127), QColor(0,0,0,127) }; QColor QGCMapPalette::_text [QGCMapPalette::_cColorGroups] = { QColor(255,255,255), QColor(0,0,0) }; -QColor QGCMapPalette::_textOutline [QGCMapPalette::_cColorGroups] = { QColor(0,0,0), QColor(255,255,255) }; +QColor QGCMapPalette::_textOutline [QGCMapPalette::_cColorGroups] = { QColor(0,0,0,192), QColor(255,255,255,192) }; QGCMapPalette::QGCMapPalette(QObject* parent) : QObject(parent) diff --git a/src/QGCPalette.cc b/src/QGCPalette.cc index 90635f5216f566d2892a52d25de59c7b1c47beda..6c54fea799dd5984588e165132a48ff5fab931b6 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..386b8139f6d08ece4c7c5f3e48556e17836ac9da 100644 --- a/src/QGCToolbox.cc +++ b/src/QGCToolbox.cc @@ -30,32 +30,38 @@ #include "QGCOptions.h" #include "SettingsManager.h" #include "QGCApplication.h" +#if defined(QGC_AIRMAP_ENABLED) +#include "AirMapManager.h" +#else +#include "AirspaceManager.h" +#endif #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 +86,14 @@ QGCToolbox::QGCToolbox(QGCApplication* app) _followMe = new FollowMe (app, this); _videoManager = new VideoManager (app, this); _mavlinkLogManager = new MAVLinkLogManager (app, this); + //-- Airmap Manager + //-- This should be "pluggable" so an arbitrary AirSpace manager can be used + //-- For now, we instantiate the one and only AirMap provider +#if defined(QGC_AIRMAP_ENABLED) + _airspaceManager = new AirMapManager (app, this); +#else + _airspaceManager = new AirspaceManager (app, this); +#endif } void QGCToolbox::setChildToolboxes(void) @@ -106,6 +120,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..3c5476fbcdd073006c78ae7f277f8a5dbb876aba 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,7 +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; } #endif @@ -86,7 +87,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/QGCButton.qml b/src/QmlControls/QGCButton.qml index f7adf355b18b4908e3a15c49410a2dd183191727..8337459bfe1db8606b929d47c91908011baa8933 100644 --- a/src/QmlControls/QGCButton.qml +++ b/src/QmlControls/QGCButton.qml @@ -8,12 +8,15 @@ import QGroundControl.ScreenTools 1.0 Button { activeFocusOnPress: true - property bool primary: false ///< primary button for a group of buttons - property real pointSize: ScreenTools.defaultFontPointSize ///< Point size for button text + property bool primary: false ///< primary button for a group of buttons + property real pointSize: ScreenTools.defaultFontPointSize ///< Point size for button text + property bool showBorder: _qgcPal.globalTheme === QGCPalette.Light + property bool iconLeft: false + property real backRadius: 0 + property real heightFactor: 0.5 property var _qgcPal: QGCPalette { colorGroupEnabled: enabled } property bool _showHighlight: (pressed | hovered | checked) && !__forceHoverOff - property bool _showBorder: _qgcPal.globalTheme === QGCPalette.Light // This fixes the issue with button hover where if a Button is near the edge oa QQuickWidget you can // move the mouse fast enough such that the MouseArea does not trigger an onExited. This is turn @@ -24,7 +27,7 @@ Button { property int __lastGlobalMouseX: 0 property int __lastGlobalMouseY: 0 property int _horizontalPadding: ScreenTools.defaultFontPixelWidth - property int _verticalPadding: Math.round(ScreenTools.defaultFontPixelHeight / 2) + property int _verticalPadding: Math.round(ScreenTools.defaultFontPixelHeight * heightFactor) Connections { target: __behavior @@ -60,9 +63,11 @@ Button { /*! This defines the background of the button. */ background: Rectangle { + id: backRect implicitWidth: ScreenTools.implicitButtonWidth implicitHeight: ScreenTools.implicitButtonHeight - border.width: _showBorder ? 1: 0 + radius: backRadius + border.width: showBorder ? 1 : 0 border.color: _qgcPal.buttonText color: _showHighlight ? control._qgcPal.buttonHighlight : @@ -71,31 +76,35 @@ Button { /*! This defines the label of the button. */ label: Item { - implicitWidth: row.implicitWidth - implicitHeight: row.implicitHeight - baselineOffset: row.y + text.y + text.baselineOffset + implicitWidth: text.implicitWidth + icon.width + implicitHeight: text.implicitHeight + baselineOffset: text.y + text.baselineOffset - Row { - id: row - anchors.centerIn: parent - spacing: ScreenTools.defaultFontPixelWidth * 0.25 - - Image { - source: control.iconSource - anchors.verticalCenter: parent.verticalCenter - } + QGCColoredImage { + id: icon + source: control.iconSource + height: source === "" ? 0 : text.height + width: height + color: text.color + fillMode: Image.PreserveAspectFit + sourceSize.height: height + anchors.left: control.iconLeft ? parent.left : undefined + anchors.leftMargin: control.iconLeft ? ScreenTools.defaultFontPixelWidth : undefined + anchors.right: !control.iconLeft ? parent.right : undefined + anchors.rightMargin: !control.iconLeft ? ScreenTools.defaultFontPixelWidth : undefined + anchors.verticalCenter: parent.verticalCenter + } - Text { - id: text - anchors.verticalCenter: parent.verticalCenter - antialiasing: true - text: control.text - font.pointSize: pointSize - font.family: ScreenTools.normalFontFamily - color: _showHighlight ? - control._qgcPal.buttonHighlightText : - (primary ? control._qgcPal.primaryButtonText : control._qgcPal.buttonText) - } + Text { + id: text + anchors.centerIn: parent + antialiasing: true + text: control.text + font.pointSize: pointSize + font.family: ScreenTools.normalFontFamily + color: _showHighlight ? + control._qgcPal.buttonHighlightText : + (primary ? control._qgcPal.primaryButtonText : control._qgcPal.buttonText) } } } diff --git a/src/QmlControls/QGCGeoBoundingCube.cc b/src/QmlControls/QGCGeoBoundingCube.cc new file mode 100644 index 0000000000000000000000000000000000000000..acddeefb0dee51a11780f6583da59287d6d43dbe --- /dev/null +++ b/src/QmlControls/QGCGeoBoundingCube.cc @@ -0,0 +1,130 @@ +/**************************************************************************** + * + * (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 "QGCGeoBoundingCube.h" +#include +#include + +double QGCGeoBoundingCube::MaxAlt = 1000000.0; +double QGCGeoBoundingCube::MinAlt = -1000000.0; +double QGCGeoBoundingCube::MaxNorth = 90.0; +double QGCGeoBoundingCube::MaxSouth = -90.0; +double QGCGeoBoundingCube::MaxWest = -180.0; +double QGCGeoBoundingCube::MaxEast = 180.0; + +//----------------------------------------------------------------------------- +bool +QGCGeoBoundingCube::isValid() const +{ + return pointNW.isValid() && pointSE.isValid() && pointNW.latitude() != MaxSouth && pointSE.latitude() != MaxNorth && \ + pointNW.longitude() != MaxEast && pointSE.longitude() != MaxWest && pointNW.altitude() < MaxAlt && pointSE.altitude() > MinAlt; +} + +//----------------------------------------------------------------------------- +void +QGCGeoBoundingCube::reset() +{ + pointSE = QGeoCoordinate(); + pointNW = QGeoCoordinate(); +} + +//----------------------------------------------------------------------------- +QGeoCoordinate +QGCGeoBoundingCube::center() const +{ + if(!isValid()) + return QGeoCoordinate(); + double lat = (((pointNW.latitude() + 90.0) + (pointSE.latitude() + 90.0)) / 2.0) - 90.0; + double lon = (((pointNW.longitude() + 180.0) + (pointSE.longitude() + 180.0)) / 2.0) - 180.0; + double alt = (pointNW.altitude() + pointSE.altitude()) / 2.0; + return QGeoCoordinate(lat, lon, alt); +} + +//----------------------------------------------------------------------------- +QList +QGCGeoBoundingCube::polygon2D(double clipTo) const +{ + QList coords; + if(isValid()) { + //-- Should we clip it? + if(clipTo > 0.0 && area() > clipTo) { + //-- Clip it to a square of given area centered on current bounding box center. + double side = sqrt(clipTo); + QGeoCoordinate c = center(); + double a = pow((side * 0.5), 2); + double h = sqrt(a + a) * 1000.0; + QGeoCoordinate nw = c.atDistanceAndAzimuth(h, 315.0); + QGeoCoordinate se = c.atDistanceAndAzimuth(h, 135.0); + coords.append(QGeoCoordinate(nw.latitude(), nw.longitude(), se.altitude())); + coords.append(QGeoCoordinate(nw.latitude(), se.longitude(), se.altitude())); + coords.append(QGeoCoordinate(se.latitude(), se.longitude(), se.altitude())); + coords.append(QGeoCoordinate(se.latitude(), nw.longitude(), se.altitude())); + coords.append(QGeoCoordinate(nw.latitude(), nw.longitude(), se.altitude())); + } else { + coords.append(QGeoCoordinate(pointNW.latitude(), pointNW.longitude(), pointSE.altitude())); + coords.append(QGeoCoordinate(pointNW.latitude(), pointSE.longitude(), pointSE.altitude())); + coords.append(QGeoCoordinate(pointSE.latitude(), pointSE.longitude(), pointSE.altitude())); + coords.append(QGeoCoordinate(pointSE.latitude(), pointNW.longitude(), pointSE.altitude())); + coords.append(QGeoCoordinate(pointNW.latitude(), pointNW.longitude(), pointSE.altitude())); + } + } + return coords; +} + +//----------------------------------------------------------------------------- +bool +QGCGeoBoundingCube::operator ==(const QList& coords) const +{ + QList c = polygon2D(); + if(c.size() != coords.size()) return false; + for(int i = 0; i < c.size(); i++) { + if(c[i] != coords[i]) return false; + } + return true; +} + +//----------------------------------------------------------------------------- +double +QGCGeoBoundingCube::width() const +{ + if(!isValid()) + return 0.0; + QGeoCoordinate ne = QGeoCoordinate(pointNW.latitude(), pointSE.longitude()); + return pointNW.distanceTo(ne); +} + +//----------------------------------------------------------------------------- +double +QGCGeoBoundingCube::height() const +{ + if(!isValid()) + return 0.0; + QGeoCoordinate sw = QGeoCoordinate(pointSE.latitude(), pointNW.longitude()); + return pointNW.distanceTo(sw); +} + +//----------------------------------------------------------------------------- +double +QGCGeoBoundingCube::area() const +{ + if(!isValid()) + return 0.0; + // Area in km^2 + double a = (height() / 1000.0) * (width() / 1000.0); + return a; +} + +//----------------------------------------------------------------------------- +double +QGCGeoBoundingCube::radius() const +{ + if(!isValid()) + return 0.0; + return pointNW.distanceTo(pointSE) / 2.0; +} diff --git a/src/QmlControls/QGCGeoBoundingCube.h b/src/QmlControls/QGCGeoBoundingCube.h new file mode 100644 index 0000000000000000000000000000000000000000..d8e4e14a1c62b02b4c4a2482f1a395a7c3464546 --- /dev/null +++ b/src/QmlControls/QGCGeoBoundingCube.h @@ -0,0 +1,82 @@ +/**************************************************************************** + * + * (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 +#include + +// A bounding "cube" for small surface areas (doesn't take in consideration earth's curvature) +// Coordinate system makes NW Up Left Bottom (0,0,0) and SE Bottom Right Top (y,x,z) +class QGCGeoBoundingCube : public QObject { + Q_OBJECT +public: + QGCGeoBoundingCube(const QGCGeoBoundingCube& other) + : QObject() + { + pointNW = other.pointNW; + pointSE = other.pointSE; + } + + QGCGeoBoundingCube() + : pointNW(QGeoCoordinate(MaxSouth, MaxEast, MaxAlt)) + , pointSE(QGeoCoordinate(MaxNorth, MaxWest, MinAlt)) + { + } + + QGCGeoBoundingCube(QGeoCoordinate p1, QGeoCoordinate p2) + : pointNW(p1) + , pointSE(p2) + { + } + + Q_PROPERTY(QGeoCoordinate pointNW MEMBER pointNW CONSTANT) + Q_PROPERTY(QGeoCoordinate pointSE MEMBER pointNW CONSTANT) + + Q_INVOKABLE void reset (); + Q_INVOKABLE bool isValid () const; + Q_INVOKABLE QGeoCoordinate center () const; + + inline bool operator ==(const QGCGeoBoundingCube& other) + { + return pointNW == other.pointNW && pointSE == other.pointSE; + } + + bool operator ==(const QList& coords) const; + + inline bool operator !=(const QGCGeoBoundingCube& other) + { + return !(*this == other); + } + + inline const QGCGeoBoundingCube& operator =(const QGCGeoBoundingCube& other) + { + pointNW = other.pointNW; + pointSE = other.pointSE; + return *this; + } + + //-- 2D + QList polygon2D(double clipTo = 0.0) const; + + Q_INVOKABLE double width () const; + Q_INVOKABLE double height () const; + Q_INVOKABLE double area () const; + Q_INVOKABLE double radius () const; + + QGeoCoordinate pointNW; + QGeoCoordinate pointSE; + static double MaxAlt; + static double MinAlt; + static double MaxNorth; + static double MaxSouth; + static double MaxWest; + static double MaxEast; +}; diff --git a/src/QmlControls/QGroundControlQmlGlobal.cc b/src/QmlControls/QGroundControlQmlGlobal.cc index 40c5b65273933b59498fb01baae68f03007b1426..f4c07df38b66baaaaf3ccc7cd33689f2f018b47a 100644 --- a/src/QmlControls/QGroundControlQmlGlobal.cc +++ b/src/QmlControls/QGroundControlQmlGlobal.cc @@ -40,6 +40,7 @@ QGroundControlQmlGlobal::QGroundControlQmlGlobal(QGCApplication* app, QGCToolbox , _corePlugin(NULL) , _firmwarePluginManager(NULL) , _settingsManager(NULL) + , _airspaceManager(NULL) , _skipSetupPage(false) { // We clear the parent on this object since we run into shutdown problems caused by hybrid qml app. Instead we let it leak on shutdown. @@ -76,6 +77,7 @@ void QGroundControlQmlGlobal::setToolbox(QGCToolbox* toolbox) _corePlugin = toolbox->corePlugin(); _firmwarePluginManager = toolbox->firmwarePluginManager(); _settingsManager = toolbox->settingsManager(); + _airspaceManager = toolbox->airspaceManager(); #ifndef __mobile__ GPSManager *gpsManager = toolbox->gpsManager(); diff --git a/src/QmlControls/QGroundControlQmlGlobal.h b/src/QmlControls/QGroundControlQmlGlobal.h index 3f0fd2a5c28987ead2480fcf856ad313ac07de3a..bbabeb8ab11950b7dc6586c9d1bbf1c4b26ca706 100644 --- a/src/QmlControls/QGroundControlQmlGlobal.h +++ b/src/QmlControls/QGroundControlQmlGlobal.h @@ -22,6 +22,7 @@ #include "SimulatedPosition.h" #include "QGCLoggingCategory.h" #include "AppSettings.h" +#include "AirspaceManager.h" #ifndef __mobile__ #include "GPS/GPSManager.h" #endif /* __mobile__ */ @@ -53,6 +54,8 @@ public: Q_PROPERTY(QGCCorePlugin* corePlugin READ corePlugin CONSTANT) Q_PROPERTY(SettingsManager* settingsManager READ settingsManager CONSTANT) Q_PROPERTY(FactGroup* gpsRtk READ gpsRtkFactGroup CONSTANT) + Q_PROPERTY(AirspaceManager* airspaceManager READ airspaceManager CONSTANT) + Q_PROPERTY(bool airmapSupported READ airmapSupported CONSTANT) Q_PROPERTY(int supportedFirmwareCount READ supportedFirmwareCount CONSTANT) @@ -142,6 +145,7 @@ public: QGCCorePlugin* corePlugin () { return _corePlugin; } SettingsManager* settingsManager () { return _settingsManager; } FactGroup* gpsRtkFactGroup () { return &_gpsRtkFactGroup; } + AirspaceManager* airspaceManager () { return _airspaceManager; } static QGeoCoordinate flightMapPosition () { return _coord; } static double flightMapZoom () { return _zoom; } @@ -171,6 +175,11 @@ public: QString qgcVersion(void) const { return qgcApp()->applicationVersion(); } +#if defined(QGC_AIRMAP_ENABLED) + bool airmapSupported() { return true; } +#else + bool airmapSupported() { return false; } +#endif // Overrides from QGCTool virtual void setToolbox(QGCToolbox* toolbox); @@ -202,6 +211,7 @@ private: FirmwarePluginManager* _firmwarePluginManager; SettingsManager* _settingsManager; GPSRTKFactGroup _gpsRtkFactGroup; + AirspaceManager* _airspaceManager; bool _skipSetupPage; diff --git a/src/QmlControls/QmlObjectListModel.cc b/src/QmlControls/QmlObjectListModel.cc index 7244b35e5feb9f317881498e9dd4cbb318f3ac68..51ad32ed7447119eb7064e92f42cb1c5f2124552 100644 --- a/src/QmlControls/QmlObjectListModel.cc +++ b/src/QmlControls/QmlObjectListModel.cc @@ -132,7 +132,7 @@ const QObject* QmlObjectListModel::operator[](int index) const return _objectList[index]; } -void QmlObjectListModel::clear(void) +void QmlObjectListModel::clear() { while (rowCount()) { removeAt(0); @@ -222,7 +222,7 @@ QObjectList QmlObjectListModel::swapObjectList(const QObjectList& newlist) return oldlist; } -int QmlObjectListModel::count(void) const +int QmlObjectListModel::count() const { return rowCount(); } @@ -251,7 +251,7 @@ void QmlObjectListModel::_childDirtyChanged(bool dirty) emit dirtyChanged(_dirty); } -void QmlObjectListModel::deleteListAndContents(void) +void QmlObjectListModel::deleteListAndContents() { for (int i=0; i<_objectList.count(); i++) { _objectList[i]->deleteLater(); @@ -259,10 +259,12 @@ void QmlObjectListModel::deleteListAndContents(void) deleteLater(); } -void QmlObjectListModel::clearAndDeleteContents(void) +void QmlObjectListModel::clearAndDeleteContents() { + beginResetModel(); for (int i=0; i<_objectList.count(); i++) { _objectList[i]->deleteLater(); } clear(); + endResetModel(); } diff --git a/src/QmlControls/QmlObjectListModel.h b/src/QmlControls/QmlObjectListModel.h index 976405ee5794118ae6bca4c8bf2d2d9fc7830aba..d7db98d753d8caa2624066f4425ff4b85669b5c3 100644 --- a/src/QmlControls/QmlObjectListModel.h +++ b/src/QmlControls/QmlObjectListModel.h @@ -31,47 +31,50 @@ public: // Property accessors - int count(void) const; - - bool dirty(void) const { return _dirty; } - void setDirty(bool dirty); - - void append(QObject* object); - void append(QList objects); - QObjectList swapObjectList(const QObjectList& newlist); - void clear(void); - QObject* removeAt(int i); - QObject* removeOne(QObject* object) { return removeAt(indexOf(object)); } - void insert(int i, QObject* object); - void insert(int i, QList objects); - QObject* operator[](int i); - const QObject* operator[](int i) const; - bool contains(QObject* object) { return _objectList.indexOf(object) != -1; } - int indexOf(QObject* object) { return _objectList.indexOf(object); } - template T value(int index) { return qobject_cast(_objectList[index]); } + int count () const; + bool dirty () const { return _dirty; } + void setDirty (bool dirty); + void append (QObject* object); + void append (QList objects); + QObjectList swapObjectList (const QObjectList& newlist); + void clear (); + QObject* removeAt (int i); + QObject* removeOne (QObject* object) { return removeAt(indexOf(object)); } + void insert (int i, QObject* object); + void insert (int i, QList objects); + bool contains (QObject* object) { return _objectList.indexOf(object) != -1; } + int indexOf (QObject* object) { return _objectList.indexOf(object); } + + QObject* operator[] (int i); + const QObject* operator[] (int i) const; + template T value (int index) { return qobject_cast(_objectList[index]); } + QList* objectList () { return &_objectList; } /// Calls deleteLater on all items and this itself. - void deleteListAndContents(void); + void deleteListAndContents (); /// Clears the list and calls deleteLater on each entry - void clearAndDeleteContents(void); + void clearAndDeleteContents (); + + void beginReset () { beginResetModel(); } + void endReset () { endResetModel(); } signals: - void countChanged(int count); - void dirtyChanged(bool dirtyChanged); + void countChanged (int count); + void dirtyChanged (bool dirtyChanged); private slots: - void _childDirtyChanged(bool dirty); + void _childDirtyChanged (bool dirty); private: // Overrides from QAbstractListModel - int rowCount(const QModelIndex & parent = QModelIndex()) const override; - QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; + int rowCount (const QModelIndex & parent = QModelIndex()) const override; + QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const override; + bool insertRows (int position, int rows, const QModelIndex &index = QModelIndex()) override; + bool removeRows (int position, int rows, const QModelIndex &index = QModelIndex()) override; + bool setData (const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QHash roleNames(void) const override; - bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override; - bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override; - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - + private: QList _objectList; diff --git a/src/QmlControls/QmlTest.qml b/src/QmlControls/QmlTest.qml index 830352630c7fb9eb0635863da2858a78faeed40c..b6918617d6f54d752c80f6cc201672cbd81a0697 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/SettingsGroup.h b/src/Settings/SettingsGroup.h index db85bb427334d2effea47d2cc25b4c40b504ba30..89e99992dddddd72a27dd277f59989e8abc8cdd0 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..d002b491137b857237e4978bc10ef54a4542dc35 100644 --- a/src/Settings/SettingsManager.cc +++ b/src/Settings/SettingsManager.cc @@ -14,6 +14,9 @@ SettingsManager::SettingsManager(QGCApplication* app, QGCToolbox* toolbox) : QGCTool(app, toolbox) +#if defined(QGC_AIRMAP_ENABLED) + , _airMapSettings (NULL) +#endif , _appSettings (NULL) , _unitsSettings (NULL) , _autoConnectSettings (NULL) @@ -40,4 +43,7 @@ void SettingsManager::setToolbox(QGCToolbox *toolbox) _rtkSettings = new RTKSettings(this); _guidedSettings = new GuidedSettings(this); _brandImageSettings = new BrandImageSettings(this); +#if defined(QGC_AIRMAP_ENABLED) + _airMapSettings = new AirMapSettings(this); +#endif } diff --git a/src/Settings/SettingsManager.h b/src/Settings/SettingsManager.h index a69b7f6bc55fcb7eec112da77aeaa0d7322a6c59..5ee29a188bed3927523bbda8de4a14949f68aab0 100644 --- a/src/Settings/SettingsManager.h +++ b/src/Settings/SettingsManager.h @@ -22,7 +22,9 @@ #include "RTKSettings.h" #include "GuidedSettings.h" #include "BrandImageSettings.h" - +#if defined(QGC_AIRMAP_ENABLED) +#include "AirMapSettings.h" +#endif #include /// Provides access to all app settings @@ -33,6 +35,9 @@ class SettingsManager : public QGCTool public: SettingsManager(QGCApplication* app, QGCToolbox* toolbox); +#if defined(QGC_AIRMAP_ENABLED) + Q_PROPERTY(QObject* airMapSettings READ airMapSettings CONSTANT) +#endif Q_PROPERTY(QObject* appSettings READ appSettings CONSTANT) Q_PROPERTY(QObject* unitsSettings READ unitsSettings CONSTANT) Q_PROPERTY(QObject* autoConnectSettings READ autoConnectSettings CONSTANT) @@ -45,6 +50,9 @@ public: // Override from QGCTool virtual void setToolbox(QGCToolbox *toolbox); +#if defined(QGC_AIRMAP_ENABLED) + AirMapSettings* airMapSettings (void) { return _airMapSettings; } +#endif AppSettings* appSettings (void) { return _appSettings; } UnitsSettings* unitsSettings (void) { return _unitsSettings; } AutoConnectSettings* autoConnectSettings (void) { return _autoConnectSettings; } @@ -55,6 +63,9 @@ public: BrandImageSettings* brandImageSettings (void) { return _brandImageSettings; } private: +#if defined(QGC_AIRMAP_ENABLED) + AirMapSettings* _airMapSettings; +#endif AppSettings* _appSettings; UnitsSettings* _unitsSettings; AutoConnectSettings* _autoConnectSettings; diff --git a/src/Vehicle/ADSBVehicle.cc b/src/Vehicle/ADSBVehicle.cc index bd796cf0bc7d942abc2f1ce5978d17005cc5e449..25e5c04f655f3a8345c9b4c7eafb0cfa741f2d70 100644 --- a/src/Vehicle/ADSBVehicle.cc +++ b/src/Vehicle/ADSBVehicle.cc @@ -18,15 +18,36 @@ ADSBVehicle::ADSBVehicle(mavlink_adsb_vehicle_t& adsbVehicle, QObject* parent) , _callsign (adsbVehicle.callsign) , _altitude (NAN) , _heading (NAN) + , _alert (false) { if (!(adsbVehicle.flags | ADSB_FLAGS_VALID_COORDS)) { qWarning() << "At least coords must be valid"; return; } - update(adsbVehicle); } +ADSBVehicle::ADSBVehicle(const QGeoCoordinate& location, float heading, bool alert, QObject* parent) + : QObject(parent) + , _icaoAddress(0) + , _alert(alert) +{ + update(alert, location, heading); +} + +void ADSBVehicle::update(bool alert, const QGeoCoordinate& location, float heading) +{ + _coordinate = location; + _altitude = location.altitude(); + _heading = heading; + _alert = alert; + emit coordinateChanged(); + emit altitudeChanged(); + emit headingChanged(); + emit alertChanged(); + _lastUpdateTimer.restart(); +} + void ADSBVehicle::update(mavlink_adsb_vehicle_t& adsbVehicle) { if (_icaoAddress != adsbVehicle.ICAO_address) { @@ -42,13 +63,13 @@ void ADSBVehicle::update(mavlink_adsb_vehicle_t& adsbVehicle) if (currCallsign != _callsign) { _callsign = currCallsign; - emit callsignChanged(_callsign); + emit callsignChanged(); } QGeoCoordinate newCoordinate(adsbVehicle.lat / 1e7, adsbVehicle.lon / 1e7); if (newCoordinate != _coordinate) { _coordinate = newCoordinate; - emit coordinateChanged(_coordinate); + emit coordinateChanged(); } double newAltitude = NAN; @@ -57,7 +78,7 @@ void ADSBVehicle::update(mavlink_adsb_vehicle_t& adsbVehicle) } if (!(qIsNaN(newAltitude) && qIsNaN(_altitude)) && !qFuzzyCompare(newAltitude, _altitude)) { _altitude = newAltitude; - emit altitudeChanged(_altitude); + emit altitudeChanged(); } double newHeading = NAN; @@ -66,6 +87,12 @@ void ADSBVehicle::update(mavlink_adsb_vehicle_t& adsbVehicle) } if (!(qIsNaN(newHeading) && qIsNaN(_heading)) && !qFuzzyCompare(newHeading, _heading)) { _heading = newHeading; - emit headingChanged(_heading); + emit headingChanged(); } + _lastUpdateTimer.restart(); +} + +bool ADSBVehicle::expired() +{ + return _lastUpdateTimer.hasExpired(expirationTimeoutMs); } diff --git a/src/Vehicle/ADSBVehicle.h b/src/Vehicle/ADSBVehicle.h index fe6046fc1515118e101f181ad3d33cd571d0b997..3ae09da44893a6549985e7cfda3639aa0eb3118a 100644 --- a/src/Vehicle/ADSBVehicle.h +++ b/src/Vehicle/ADSBVehicle.h @@ -11,6 +11,7 @@ #include #include +#include #include "QGCMAVLink.h" @@ -21,31 +22,50 @@ class ADSBVehicle : public QObject public: ADSBVehicle(mavlink_adsb_vehicle_t& adsbVehicle, QObject* parent = NULL); + ADSBVehicle(const QGeoCoordinate& location, float heading, bool alert = false, 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) Q_PROPERTY(double altitude READ altitude NOTIFY altitudeChanged) // NaN for not available Q_PROPERTY(double heading READ heading NOTIFY headingChanged) // NaN for not available + Q_PROPERTY(bool alert READ alert NOTIFY alertChanged) // Collision path int icaoAddress (void) const { return _icaoAddress; } QString callsign (void) const { return _callsign; } QGeoCoordinate coordinate (void) const { return _coordinate; } double altitude (void) const { return _altitude; } double heading (void) const { return _heading; } + bool alert (void) const { return _alert; } /// Update the vehicle with new information void update(mavlink_adsb_vehicle_t& adsbVehicle); + void update(bool alert, 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); - void altitudeChanged(double altitude); - void headingChanged(double heading); + void coordinateChanged (); + void callsignChanged (); + void altitudeChanged (); + void headingChanged (); + void alertChanged (); private: + /* According with Thomas Voß, we should be using 2 minutes for the time being + 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. + */ + static constexpr qint64 expirationTimeoutMs = 120000; + uint32_t _icaoAddress; QString _callsign; QGeoCoordinate _coordinate; double _altitude; double _heading; + bool _alert; + + QElapsedTimer _lastUpdateTimer; }; diff --git a/src/Vehicle/Vehicle.cc b/src/Vehicle/Vehicle.cc index b019b8052426d2810b0a2c219335708041c5901c..a6d01db903ca846810905235763163cffac599a9 100644 --- a/src/Vehicle/Vehicle.cc +++ b/src/Vehicle/Vehicle.cc @@ -39,6 +39,9 @@ #include "QGCCameraManager.h" #include "VideoReceiver.h" #include "VideoManager.h" +#if defined(QGC_AIRMAP_ENABLED) +#include "AirspaceVehicleManager.h" +#endif QGC_LOGGING_CATEGORY(VehicleLog, "VehicleLog") @@ -147,6 +150,9 @@ Vehicle::Vehicle(LinkInterface* link, , _rallyPointManager(nullptr) , _rallyPointManagerInitialRequestSent(false) , _parameterManager(nullptr) +#if defined(QGC_AIRMAP_ENABLED) + , _airspaceVehicleManager(nullptr) +#endif , _armed(false) , _base_mode(0) , _custom_mode(0) @@ -273,6 +279,10 @@ 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); } // Disconnected Vehicle for offline editing @@ -334,6 +344,9 @@ Vehicle::Vehicle(MAV_AUTOPILOT firmwareType, , _rallyPointManager(nullptr) , _rallyPointManagerInitialRequestSent(false) , _parameterManager(nullptr) +#if defined(QGC_AIRMAP_ENABLED) + , _airspaceVehicleManager(nullptr) +#endif , _armed(false) , _base_mode(0) , _custom_mode(0) @@ -466,6 +479,17 @@ void Vehicle::_commonInit(void) _flightDistanceFact.setRawValue(0); _flightTimeFact.setRawValue(0); + + //-- Airspace Management +#if defined(QGC_AIRMAP_ENABLED) + AirspaceManager* airspaceManager = _toolbox->airspaceManager(); + if (airspaceManager) { + _airspaceVehicleManager = airspaceManager->instantiateVehicle(*this); + if (_airspaceVehicleManager) { + connect(_airspaceVehicleManager, &AirspaceVehicleManager::trafficUpdate, this, &Vehicle::_trafficUpdate); + } + } +#endif } Vehicle::~Vehicle() @@ -481,6 +505,11 @@ Vehicle::~Vehicle() delete _mav; _mav = nullptr; +#if defined(QGC_AIRMAP_ENABLED) + if (_airspaceVehicleManager) { + delete _airspaceVehicleManager; + } +#endif } void Vehicle::prepareDelete() @@ -3595,6 +3624,35 @@ void Vehicle::_updateHighLatencyLink(bool sendCommand) } } +void Vehicle::_trafficUpdate(bool alert, 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(alert, location, heading); + } else { + ADSBVehicle* vehicle = new ADSBVehicle(location, heading, alert, 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; + } + } +} + void Vehicle::_mavlinkMessageStatus(int uasId, uint64_t totalSent, uint64_t totalReceived, uint64_t totalLoss, float lossPercent) { if(uasId == _id) { diff --git a/src/Vehicle/Vehicle.h b/src/Vehicle/Vehicle.h index e960ef6ddb4f1ab0bf4d99d807e0b1237ef486bd..ff745c59a91a2194a223f58c57ff26faa557c4fb 100644 --- a/src/Vehicle/Vehicle.h +++ b/src/Vehicle/Vehicle.h @@ -35,6 +35,9 @@ class UASMessage; class SettingsManager; class ADSBVehicle; class QGCCameraManager; +#if defined(QGC_AIRMAP_ENABLED) +class AirspaceVehicleManager; +#endif Q_DECLARE_LOGGING_CATEGORY(VehicleLog) @@ -1191,6 +1194,9 @@ private slots: void _sendQGCTimeToVehicle(void); void _mavlinkMessageStatus(int uasId, uint64_t totalSent, uint64_t totalReceived, uint64_t totalLoss, float lossPercent); + void _trafficUpdate (bool alert, QString traffic_id, QString vehicle_id, QGeoCoordinate location, float heading); + void _adsbTimerTimeout (); + private: bool _containsLink(LinkInterface* link); void _addLink(LinkInterface* link); @@ -1353,7 +1359,11 @@ private: RallyPointManager* _rallyPointManager; bool _rallyPointManagerInitialRequestSent; - ParameterManager* _parameterManager; + ParameterManager* _parameterManager; + +#if defined(QGC_AIRMAP_ENABLED) + AirspaceVehicleManager* _airspaceVehicleManager; +#endif bool _armed; ///< true: vehicle is armed uint8_t _base_mode; ///< base_mode from HEARTBEAT @@ -1384,6 +1394,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 6478ad503c776da860f8ff32695e431bbf117a89..5302be57df48df13d0105017301ea448cc7d4597 100644 --- a/src/api/QGCCorePlugin.cc +++ b/src/api/QGCCorePlugin.cc @@ -31,6 +31,9 @@ public: : pGeneral (nullptr) , pCommLinks (nullptr) , pOfflineMaps (nullptr) + #if defined(QGC_AIRMAP_ENABLED) + , pAirmap (nullptr) + #endif , pMAVLink (nullptr) , pConsole (nullptr) , pHelp (nullptr) @@ -55,6 +58,10 @@ public: delete pCommLinks; if(pOfflineMaps) delete pOfflineMaps; +#if defined(QGC_AIRMAP_ENABLED) + if(pAirmap) + delete pAirmap; +#endif if(pMAVLink) delete pMAVLink; if(pConsole) @@ -72,6 +79,9 @@ public: QmlComponentInfo* pGeneral; QmlComponentInfo* pCommLinks; QmlComponentInfo* pOfflineMaps; +#if defined(QGC_AIRMAP_ENABLED) + QmlComponentInfo* pAirmap; +#endif QmlComponentInfo* pMAVLink; QmlComponentInfo* pConsole; QmlComponentInfo* pHelp; @@ -130,6 +140,12 @@ QVariantList &QGCCorePlugin::settingsPages() QUrl::fromUserInput("qrc:/qml/OfflineMap.qml"), QUrl::fromUserInput("qrc:/res/waves.svg")); _p->settingsList.append(QVariant::fromValue(reinterpret_cast(_p->pOfflineMaps))); +#if defined(QGC_AIRMAP_ENABLED) + _p->pAirmap = new QmlComponentInfo(tr("AirMap"), + QUrl::fromUserInput("qrc:/qml/AirmapSettings.qml"), + QUrl::fromUserInput("")); + _p->settingsList.append(QVariant::fromValue(reinterpret_cast(_p->pAirmap))); +#endif _p->pMAVLink = new QmlComponentInfo(tr("MAVLink"), QUrl::fromUserInput("qrc:/qml/MavlinkSettings.qml"), QUrl::fromUserInput("qrc:/res/waves.svg")); diff --git a/src/ui/MainWindow.cc b/src/ui/MainWindow.cc index d04a82a377c8a9ef6af500a9f0ed514dae50c97f..931d32e4f9e5e7dd87c4f5bd13edd801d3e710e5 100644 --- a/src/ui/MainWindow.cc +++ b/src/ui/MainWindow.cc @@ -132,8 +132,8 @@ MainWindow::MainWindow() _ui.setupUi(this); // Make sure tool bar elements all fit before changing minimum width - setMinimumWidth(1008); - setMinimumHeight(520); + setMinimumWidth(1024); + setMinimumHeight(620); configureWindowName(); // Setup central widget with a layout to hold the views