diff --git a/.github/ci/packages.apt b/.github/ci/packages.apt index b728dcc98..1b607e28b 100644 --- a/.github/ci/packages.apt +++ b/.github/ci/packages.apt @@ -14,6 +14,8 @@ qml-module-qt-labs-platform qml-module-qt-labs-settings qml-module-qtcharts qml-module-qtgraphicaleffects +qml-module-qtlocation +qml-module-qtpositioning qml-module-qtqml-models2 qml-module-qtquick-controls qml-module-qtquick-controls2 diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e11a590a..163b56865 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,7 +64,7 @@ set(IGN_RENDERING_VER ${ignition-rendering6_VERSION_MAJOR}) #-------------------------------------- # Find ignition-msgs -ign_find_package(ignition-msgs8 REQUIRED) +ign_find_package(ignition-msgs8 REQUIRED VERSION 8.2) set(IGN_MSGS_VER ${ignition-msgs8_VERSION_MAJOR}) #-------------------------------------- diff --git a/Migration.md b/Migration.md index b482ad451..5832cd950 100644 --- a/Migration.md +++ b/Migration.md @@ -5,6 +5,10 @@ Deprecated code produces compile-time warnings. These warning serve as notification to users that their code should be upgraded. The next major release will remove the deprecated code. +## Ignition GUI 6.2 to 6.3 + +* New QML dependencies, only needed for the NavSatMap plugin: `qml-module-qtlocation`, `qml-module-qtpositioning` + ## Ignition GUI 6.1 to 6.2 * All features from `Grid3D` have been incorportated into `GridConfig`. The code diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 7666bb759..0f9e0af61 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -123,6 +123,7 @@ add_subdirectory(plotting) add_subdirectory(publisher) add_subdirectory(marker_manager) add_subdirectory(minimal_scene) +add_subdirectory(navsat_map) add_subdirectory(scene3d) add_subdirectory(screenshot) add_subdirectory(tape_measure) diff --git a/src/plugins/navsat_map/CMakeLists.txt b/src/plugins/navsat_map/CMakeLists.txt new file mode 100644 index 000000000..4738f999d --- /dev/null +++ b/src/plugins/navsat_map/CMakeLists.txt @@ -0,0 +1,7 @@ +ign_gui_add_plugin(NavSatMap + SOURCES + NavSatMap.cc + QT_HEADERS + NavSatMap.hh +) + diff --git a/src/plugins/navsat_map/NavSatMap.cc b/src/plugins/navsat_map/NavSatMap.cc new file mode 100644 index 000000000..f19972204 --- /dev/null +++ b/src/plugins/navsat_map/NavSatMap.cc @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include "NavSatMap.hh" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ignition/gui/Application.hh" + +namespace ignition +{ +namespace gui +{ +namespace plugins +{ + class NavSatMapPrivate + { + /// \brief List of topics publishing navSat messages. + public: QStringList topicList; + + /// \brief Holds data to set as the next navSat + public: msgs::NavSat navSatMsg; + + /// \brief Node for communication. + public: transport::Node node; + + /// \brief Mutex for accessing navSat data + public: std::recursive_mutex navSatMutex; + }; +} +} +} + +using namespace ignition; +using namespace gui; +using namespace plugins; + +///////////////////////////////////////////////// +NavSatMap::NavSatMap() + : Plugin(), dataPtr(new NavSatMapPrivate) +{ +} + +///////////////////////////////////////////////// +NavSatMap::~NavSatMap() +{ +} + +///////////////////////////////////////////////// +void NavSatMap::LoadConfig(const tinyxml2::XMLElement *_pluginElem) +{ + // Default name in case user didn't define one + if (this->title.empty()) + this->title = "Navigation satellite map"; + + std::string topic; + bool topicPicker = true; + + // Read configuration + if (_pluginElem) + { + if (auto topicElem = _pluginElem->FirstChildElement("topic")) + topic = topicElem->GetText(); + + if (auto pickerElem = _pluginElem->FirstChildElement("topic_picker")) + pickerElem->QueryBoolText(&topicPicker); + } + + if (topic.empty() && !topicPicker) + { + ignwarn << "Can't hide topic picker without a default topic." << std::endl; + topicPicker = true; + } + + this->PluginItem()->setProperty("showPicker", topicPicker); + + if (!topic.empty()) + { + this->SetTopicList({QString::fromStdString(topic)}); + this->OnTopic(QString::fromStdString(topic)); + } + else + this->OnRefresh(); +} + +///////////////////////////////////////////////// +void NavSatMap::ProcessMessage() +{ + std::lock_guard lock(this->dataPtr->navSatMutex); + + this->newMessage(this->dataPtr->navSatMsg.latitude_deg(), + this->dataPtr->navSatMsg.longitude_deg()); +} + +///////////////////////////////////////////////// +void NavSatMap::OnMessage(const msgs::NavSat &_msg) +{ + std::lock_guard lock(this->dataPtr->navSatMutex); + this->dataPtr->navSatMsg = _msg; + + // Signal to main thread that the navSat changed + QMetaObject::invokeMethod(this, "ProcessMessage"); +} + +///////////////////////////////////////////////// +void NavSatMap::OnTopic(const QString _topic) +{ + auto topic = _topic.toStdString(); + if (topic.empty()) + return; + + // Unsubscribe + auto subs = this->dataPtr->node.SubscribedTopics(); + for (auto sub : subs) + this->dataPtr->node.Unsubscribe(sub); + + // Subscribe to new topic + if (!this->dataPtr->node.Subscribe(topic, &NavSatMap::OnMessage, + this)) + { + ignerr << "Unable to subscribe to topic [" << topic << "]" << std::endl; + } +} + +///////////////////////////////////////////////// +void NavSatMap::OnRefresh() +{ + // Clear + this->dataPtr->topicList.clear(); + + // Get updated list + std::vector allTopics; + this->dataPtr->node.TopicList(allTopics); + for (auto topic : allTopics) + { + std::vector publishers; + this->dataPtr->node.TopicInfo(topic, publishers); + for (auto pub : publishers) + { + if (pub.MsgTypeName() == "ignition.msgs.NavSat") + { + this->dataPtr->topicList.push_back(QString::fromStdString(topic)); + break; + } + } + } + + // Select first one + if (this->dataPtr->topicList.count() > 0) + this->OnTopic(this->dataPtr->topicList.at(0)); + this->TopicListChanged(); +} + +///////////////////////////////////////////////// +QStringList NavSatMap::TopicList() const +{ + return this->dataPtr->topicList; +} + +///////////////////////////////////////////////// +void NavSatMap::SetTopicList(const QStringList &_topicList) +{ + this->dataPtr->topicList = _topicList; + this->TopicListChanged(); +} + +// Register this plugin +IGNITION_ADD_PLUGIN(ignition::gui::plugins::NavSatMap, + ignition::gui::Plugin) diff --git a/src/plugins/navsat_map/NavSatMap.hh b/src/plugins/navsat_map/NavSatMap.hh new file mode 100644 index 000000000..eadddbb88 --- /dev/null +++ b/src/plugins/navsat_map/NavSatMap.hh @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#ifndef IGNITION_GUI_PLUGINS_IMAGEDISPLAY_HH_ +#define IGNITION_GUI_PLUGINS_IMAGEDISPLAY_HH_ + +#include +#ifdef _MSC_VER +#pragma warning(push, 0) +#endif +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "ignition/gui/Plugin.hh" + +namespace ignition +{ +namespace gui +{ +namespace plugins +{ + class NavSatMapPrivate; + + /// \brief Display NavSat messages coming through an Ignition transport topic + /// on top of a map. + /// + /// ## Configuration + /// + /// \ : Set the topic to receive NavSat messages. + /// \ : Whether to show the topic picker, true by default. If + /// this is false, a \ must be specified. + class NavSatMap : public Plugin + { + Q_OBJECT + + /// \brief Topic list + Q_PROPERTY( + QStringList topicList + READ TopicList + WRITE SetTopicList + NOTIFY TopicListChanged + ) + + /// \brief Constructor + public: NavSatMap(); + + /// \brief Destructor + public: virtual ~NavSatMap(); + + // Documentation inherited + public: virtual void LoadConfig(const tinyxml2::XMLElement *_pluginElem); + + /// \brief Callback when refresh button is pressed. + public slots: void OnRefresh(); + + /// \brief Callback when a new topic is chosen on the combo box. + public slots: void OnTopic(const QString _topic); + + /// \brief Get the list of topics publishing NavSat messages + /// \return List of topics + public: Q_INVOKABLE QStringList TopicList() const; + + /// \brief Set the topic list + /// \param[in] _topicList List of topics + public: Q_INVOKABLE void SetTopicList(const QStringList &_topicList); + + /// \brief Notify that topic list has changed + signals: void TopicListChanged(); + + /// \brief Notify that a new message has been received. + /// \param[in] _latitudeDeg Latitude in degrees + /// \param[in] _longitudeDeg Longitude in degrees + signals: void newMessage(double _latitudeDeg, double _longitudeDeg); + + /// \brief Callback in main thread when message changes + private slots: void ProcessMessage(); + + /// \brief Subscriber callback when new message is received + /// \param[in] _msg New message + private: void OnMessage(const ignition::msgs::NavSat &_msg); + + /// \internal + /// \brief Pointer to private data. + private: std::unique_ptr dataPtr; + }; +} +} +} + +#endif diff --git a/src/plugins/navsat_map/NavSatMap.qml b/src/plugins/navsat_map/NavSatMap.qml new file mode 100644 index 000000000..9977fad9e --- /dev/null +++ b/src/plugins/navsat_map/NavSatMap.qml @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.1 +import QtLocation 5.6 +import QtPositioning 5.6 + +Item { + id: navSatMap + property double latitude: 0.0 + property double longitude: 0.0 + property bool centering: true + + Layout.minimumWidth: 280 + Layout.minimumHeight: 455 + anchors.topMargin: 5 + anchors.leftMargin: 5 + anchors.rightMargin: 5 + anchors.fill: parent + + ColumnLayout { + id: configColumn + width: parent.width + + RowLayout { + width: parent.width + + Layout.fillWidth: true + Layout.fillHeight: true + + RoundButton { + text: "\u21bb" + Material.background: Material.primary + onClicked: { + NavSatMap.OnRefresh(); + } + } + + ComboBox { + id: combo + Layout.fillWidth: true + model: NavSatMap.topicList + onCurrentIndexChanged: { + if (currentIndex < 0) { + return; + } + + NavSatMap.OnTopic(textAt(currentIndex)); + } + ToolTip.visible: hovered + ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval + ToolTip.text: qsTr("Ignition Transport topics publishing NavSat messages") + } + } + + RowLayout { + width: parent.width + Item { + height: 1 + Layout.fillWidth: true + } + Text { + text: "Latitude: " + latitude.toFixed(6) + } + + Text { + text: "Longitude: " + longitude.toFixed(6) + } + Item { + height: 1 + Layout.fillWidth: true + } + } + } + + Plugin { + id: mapPlugin + name: "osm" + } + + Map { + id: map + anchors.top: configColumn.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 5 + anchors.leftMargin: -5 + anchors.rightMargin: -5 + + MapCircle { + id: circle + center { + latitude: latitude + longitude: longitude + } + radius: 23.0 + color: '#4285f4' + border.width: 3 + border.color: "white" + } + + plugin: mapPlugin + center: centering ? QtPositioning.coordinate(latitude, longitude) : center + copyrightsVisible: false + zoomLevel: 16 + + gesture.onPanStarted: { + centering = false + } + + onZoomLevelChanged: { + // Scaling the location marker according to zoom level. + // Marker radius = meters per pixel * 10 + circle.radius = 156543.03392 * Math.cos(latitude * Math.PI / 180) / Math.pow(2, map.zoomLevel) * 10 + } + } + + RoundButton { + height: 40 + width: 40 + anchors.top: map.top + anchors.right: map.right + + // Strangely, \u2316 doesn't work on Bionic + text: "\u2732" + font.pixelSize: 25 + + Material.background: Material.primary + Material.foreground: centering ? "white" : "black" + + onClicked: { + centering = true + } + + hoverEnabled: true + + ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval + ToolTip.visible: hovered + ToolTip.text: qsTr("Track position") + } + + Connections { + target: NavSatMap + onNewMessage: { + latitude = _latitudeDeg + longitude = _longitudeDeg + } + } +} diff --git a/src/plugins/navsat_map/NavSatMap.qrc b/src/plugins/navsat_map/NavSatMap.qrc new file mode 100644 index 000000000..a52e2c872 --- /dev/null +++ b/src/plugins/navsat_map/NavSatMap.qrc @@ -0,0 +1,5 @@ + + + NavSatMap.qml + +