diff --git a/CMakeLists.txt b/CMakeLists.txt index d5b939d..c791cb4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,7 @@ include_directories( install( PROGRAMS scripts/start_qgis_ros + scripts/navsatfix DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} ) diff --git a/README.md b/README.md index ca99e9b..028a903 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ # QGIS-ROS + +**Warning: This package is not currently maintained and may not work without some additional technical care. It looks like a newer version of QGIS has changed a bunch of the Python interfaces. Try using QGIS 3.10 or perhaps a bit older. PRs are welcome!** + A QGIS plugin for interacting with data from ROS topics and bags. The ROSCon 2018 Presentation on QGIS-ROS can be found here: https://vimeo.com/293539252. The presentation PDF can be found here: https://roscon.ros.org/2018/presentations/ROSCon2018_UnleashingGISToolbox.pdf ## Requirements + - Ubuntu >= 16.04 - Python 3.5 - QGIS >= 3.1 @@ -12,10 +16,11 @@ The ROSCon 2018 Presentation on QGIS-ROS can be found here: https://vimeo.com/29 QGIS can be installed from [the QGIS download page.][1] You need to add a PPA if using Ubuntu 16.04. ## Installation (source) + ```bash cd ~/my_ros_ws/src/ git clone git@github.com:locusrobotics/qgis_ros.git -git clone git@github.com/clearpathrobotics/wireless.git +git clone git@github.com:clearpathrobotics/wireless.git git clone git@github.com:locusrobotics/json_transport.git catkin build @@ -24,6 +29,7 @@ pip3 install -r requirements.txt ``` ## Usage (source) + ```bash rosrun qgis_ros start_qgis_ros ``` @@ -31,12 +37,14 @@ rosrun qgis_ros start_qgis_ros Once QGIS is loaded, navigate to Plugins -> Installed -> and enable QGIS_ROS with the checkbox. You only need to do this once. With custom ROS Message Translators: + ```bash export QGIS_ROS_EXTRA_TRANSLATORS='custompythonmodule.translators' rosrun qgis_ros start_qgis_ros ``` ## Usage (docker) + It can often be very tricky to comfortably resolve all dependencies in your workspace because of the combination of ROS1, Python3, GDAL 2, and QGIS. It uses a lazy/reckless way to expose X server to the container. For alternatives, check out the [ROS Docker GUI Tutorial][2]. @@ -52,6 +60,7 @@ xhost -local:root # Remember to remove X server permissions after! To use extra translators, you'll need to mount a volume with them and/or extend the image. ## ROS Message Translators + QGIS is able to read and write data from hundreds of formats and sources by translating these formats into a common in-memory format. In order to represent ROS messages in this format, we target common interchange formats (GeoJSON and GeoTIFF) to make it easy to extend. If you want to make use of a custom ROS message type, all you have to do is: 1. subclass `qgis_ros.core.translators.Translator` @@ -64,20 +73,21 @@ Check out the source code for more details. ## Troubleshooting ### My topic does not appear in the list of topics + Only topics that have ROS Message Translators will appear. Not every message has been implemented. If your message is custom, look above for how to create a custom translator. If it is standard to ROS, create an issue or raise a Pull Request with a new one.TODO: add an example to the ROSCon presentation repository and link here. 51 - ## Contributions + Contributions are appreciated. Please open a pull request. Developers will notice that `camelCase` is being used in Python. This may seem unusual, but PyQT and QGIS both use `camelCase`.So we follow that standard in accordance with PEP8. ## Contact + Queries that don't fit well as GitHub issues can be directed to Andrew Blakey at ablakey@locusrobotics.com Being a robogeographer is a lonely life. If there are any others out there, don't hesitate to say hi! - [1]: https://qgis.org/en/site/forusers/download.html [2]: http://wiki.ros.org/docker/Tutorials/GUI diff --git a/SCRATCHPAD.py b/SCRATCHPAD.py deleted file mode 100644 index 37f2305..0000000 --- a/SCRATCHPAD.py +++ /dev/null @@ -1,96 +0,0 @@ - - -# Try to init ROS node or fail out immediately to avoid blocking the UI. -from socket import error as socket_error -import rosgraph -import rospy - -try: - rosgraph.Master('/rostopic').getPid() -except socket_error: - raise rospy.ROSInitException('Cannot load QGIS ROS. ROS Master was not found.') -else: - rospy.init_node('qgis_ros_toolbox') - - - - - - - - - - - - - - - -from functools import partial -from pathlib import Path -import os - -from PyQt5 import uic, QtCore, QtWidgets -from qgis.core import QgsProject -import rospy -from ..core import TranslatorRegistry - - -FORM_CLASS, _ = uic.loadUiType(str(Path(os.path.dirname(__file__)) / 'data_loader_widget.ui')) - - -class DataLoaderWidget(QtWidgets.QWidget, FORM_CLASS): - - def __init__(self, parent=None): - '''Occurs on init, even if dialog is not shown.''' - super(DataLoaderWidget, self).__init__(parent) - - self.setupUi(self) - - self.topicList.currentItemChanged.connect(self._onTopicListChange) - self.createLayerButton.clicked.connect(self._onCreateLayer) - self.subscribeButton.clicked.connect( - partial(self._onCreateLayer, subscribe=True)) - - self._selectedTopicName = None - self._selectedTopicType = None - - def showEvent(self, event): - self._populate_topic_list() - - def _populate_topic_list(self): - self.topicList.clear() - - topics = rospy.get_published_topics() - - # TODO: sort. - for t in topics: - if t[1] in TranslatorRegistry.instance().translatableTypeNames: - item = QtWidgets.QListWidgetItem(t[0]) - item.setData(QtCore.Qt.UserRole, t[1]) - self.topicList.addItem(item) - - def _onTopicListChange(self, current: QtWidgets.QListWidgetItem, previous: QtWidgets.QListWidgetItem): - if current is not None: - topicType = current.data(QtCore.Qt.UserRole) - topicName = current.text() - dataModelType = TranslatorRegistry.instance().get(topicType).dataModelType - self.selectedTopicDetails.setEnabled(True) - else: - topicType = None - topicName = None - dataModelType = None - self.selectedTopicDetails.setEnabled(False) - - # Update GUI - self.topicTypeLabel.setText(topicType or '') - self.topicNameLabel.setText(topicName or '') - self.dataModelTypeLabel.setText(dataModelType or '') - - self._selectedTopicName = topicName - self._selectedTopicType = topicType - - def _onCreateLayer(self, subscribe=False): - translator = TranslatorRegistry.instance().get(self._selectedTopicType) - layer = translator.createLayer(self._selectedTopicName, subscribe=subscribe) - QgsProject.instance().addMapLayer(layer) diff --git a/scripts/navsatfix_test_pub b/scripts/navsatfix_test_pub new file mode 100755 index 0000000..a95a0c4 --- /dev/null +++ b/scripts/navsatfix_test_pub @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +""" +An example publisher of basic NavSatFix data, mostly for testing. +""" +from random import random + +import rospy +from sensor_msgs.msg import NavSatFix + + +def navsatfix_test_pub(): + pub = rospy.Publisher("navsat", NavSatFix, queue_size=1) + rospy.init_node("navsatfix_test_pub") + + rate = rospy.Rate(1) + + longitude = 0 + + while not rospy.is_shutdown(): + pub.publish(latitude=0, longitude=longitude) + longitude += random() + rate.sleep() + + +if __name__ == "__main__": + try: + navsatfix_test_pub() + except rospy.ROSInterruptException: + pass