Skip to content
63 changes: 59 additions & 4 deletions ORStools/gui/ORStoolsDialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

from qgis.PyQt.QtWidgets import QCheckBox

from ..utils.gui import LayerMessageBox
from ..utils.router import route_as_layer

try:
Expand All @@ -57,6 +58,7 @@
Qgis, # noqa: F811
QgsAnnotation,
QgsCoordinateTransform,
QgsWkbTypes,
)
from qgis.gui import QgsMapCanvasAnnotationItem, QgsCollapsibleGroupBox, QgisInterface
from qgis.PyQt.QtCore import QSizeF, QPointF, QCoreApplication
Expand Down Expand Up @@ -343,6 +345,7 @@ def __init__(self, iface: QgisInterface, parent=None) -> None:
self.routing_fromline_map.clicked.connect(self._on_linetool_init)
self.routing_fromline_clear.clicked.connect(self._clear_listwidget)
self.save_vertices.clicked.connect(self._save_vertices_to_layer)
self.load_vertices.clicked.connect(self.load_vertices_from_layer)

# Batch
self.pushButton_routing_points.clicked.connect(
Expand Down Expand Up @@ -385,6 +388,7 @@ def __init__(self, iface: QgisInterface, parent=None) -> None:
self.provider_config.setIcon(gui.GuiUtils.get_icon("icon_settings.png"))
self.about_button.setIcon(gui.GuiUtils.get_icon("icon_about.png"))
self.help_button.setIcon(gui.GuiUtils.get_icon("icon_help.png"))
self.load_vertices.setIcon(gui.GuiUtils.get_icon("icon_load.png"))

# Connect signals to the color_duplicate_items function
self.routing_fromline_list.model().rowsRemoved.connect(
Expand Down Expand Up @@ -507,12 +511,17 @@ def _on_linetool_init(self) -> None:
self.line_tool = maptools.LineTool(self)
self.canvas.setMapTool(self.line_tool)

def create_vertex(self, point, idx):
def create_vertex(self, point: QgsPointXY, idx: int, epsg: int = None) -> None:
"""Adds an item to QgsListWidget and annotates the point in the map canvas"""
map_crs = self.canvas.mapSettings().destinationCrs()
if not epsg:
map_crs = self.canvas.mapSettings().destinationCrs()

transformer = transform.transformToWGS(map_crs)
point_wgs = transformer.transform(point)
else:
transformer = transform.transformToWGS(QgsCoordinateReferenceSystem(f"EPSG:{epsg}"))
point_wgs = transformer.transform(point)

transformer = transform.transformToWGS(map_crs)
point_wgs = transformer.transform(point)
self.routing_fromline_list.addItem(f"Point {idx}: {point_wgs.x():.6f}, {point_wgs.y():.6f}")

crs = self.canvas.mapSettings().destinationCrs()
Expand Down Expand Up @@ -585,3 +594,49 @@ def show(self):
"""Load the saved state when the window is shown"""
super().show()
self.load_provider_combo_state()

def load_vertices_from_layer(self, testing: str = "") -> None:
if not self.line_tool:
self.line_tool = maptools.LineTool(self)

box = LayerMessageBox()

if testing == "ok":
result = QMessageBox.Ok
elif testing == "not_ok":
result = QMessageBox.Cancel
else:
result = box.exec_()

if result == QMessageBox.Ok:
layer = box.selectedLayer()
try:
self.routing_fromline_list.clear()

for id, feat in enumerate(layer.getFeatures()):
geom = feat.geometry()
if not geom:
continue

if geom.type() == QgsWkbTypes.PointGeometry and QgsWkbTypes.isSingleType(
geom.wkbType()
):
pt = geom.asPoint()
self.create_vertex(pt, id, 4326)
self.line_tool.create_rubber_band()

elif geom.type() == QgsWkbTypes.PointGeometry and QgsWkbTypes.isMultiType(
geom.wkbType()
):
pts = geom.asMultiPoint()
for pt in pts:
self.create_vertex(pt, id)
self.line_tool.create_rubber_band()
except Exception:
self._clear_annotations()
self._iface.messageBar().pushMessage(
self.tr("Could not load points from Layer"),
self.tr("Please select a valid point layer"),
level=Qgis.MessageLevel.Warning,
duration=3,
)
98 changes: 57 additions & 41 deletions ORStools/gui/ORStoolsDialogUI.ui
Original file line number Diff line number Diff line change
Expand Up @@ -260,104 +260,120 @@
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item row="0" column="0">
<widget class="QPushButton" name="routing_fromline_map">
<item row="1" column="0">
<widget class="QPushButton" name="routing_fromline_clear">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Add waypoints interactively from the map canvas.&lt;/p&gt;&lt;p&gt;Right- or double-click will pause waypoint selection, drag and drop will still be enabled. Another click on the green + button will continue the selection process. The ESC-button will terminate it and delete all waypoints.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If waypoints are selected in the list, only these will be deleted. Else all waypoints will be deleted.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>:/plugins/ORStools/img/icon_add.png</normaloff>:/plugins/ORStools/img/icon_add.png</iconset>
<normaloff>:/plugins/ORStools/img/icon_clear.png</normaloff>:/plugins/ORStools/img/icon_clear.png</iconset>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="routing_fromline_clear">
<item row="0" column="2" rowspan="6">
<widget class="QListWidget" name="routing_fromline_list">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If waypoints are selected in the list, only these will be deleted. Else all waypoints will be deleted.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>Select waypoints from the map!</string>
</property>
<property name="text">
<string/>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="icon">
<iconset>
<normaloff>:/plugins/ORStools/img/icon_clear.png</normaloff>:/plugins/ORStools/img/icon_clear.png</iconset>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::MultiSelection</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="save_vertices">
<item row="0" column="0">
<widget class="QPushButton" name="routing_fromline_map">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Save points in list to layer. Use the processing algorithms (batch jobs) to work with points from layers.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Add waypoints interactively from the map canvas.&lt;/p&gt;&lt;p&gt;Right- or double-click will pause waypoint selection, drag and drop will still be enabled. Another click on the green + button will continue the selection process. The ESC-button will terminate it and delete all waypoints.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>:/plugins/ORStools/img/icon_save.png</normaloff>:/plugins/ORStools/img/icon_save.png</iconset>
<normaloff>:/plugins/ORStools/img/icon_add.png</normaloff>:/plugins/ORStools/img/icon_add.png</iconset>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="toggle_preview">
<widget class="QPushButton" name="load_vertices">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Load point verices from a point layer into the plugin.</string>
</property>
<property name="text">
<string>LivePreview</string>
<string/>
</property>
</widget>
</item>
<item row="0" column="1" rowspan="4">
<widget class="QListWidget" name="routing_fromline_list">
<item row="2" column="0">
<widget class="QPushButton" name="save_vertices">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Select waypoints from the map!</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Save points in list to layer. Use the processing algorithms (batch jobs) to work with points from layers.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
<property name="text">
<string/>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
<property name="icon">
<iconset>
<normaloff>:/plugins/ORStools/img/icon_save.png</normaloff>:/plugins/ORStools/img/icon_save.png</iconset>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::MultiSelection</enum>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="toggle_preview">
<property name="text">
<string>LivePreview</string>
</property>
</widget>
</item>
Expand Down
Binary file added ORStools/gui/img/icon_load.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions ORStools/utils/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@

import os

from qgis.PyQt.QtWidgets import QMessageBox
from qgis.PyQt.QtGui import (
QIcon,
)
from qgis._core import QgsMapLayerProxyModel
from qgis.gui import QgsMapLayerComboBox


class GuiUtils:
Expand Down Expand Up @@ -51,3 +54,21 @@ def get_ui_file_path(file: str) -> str:
return path

return path


class LayerMessageBox(QMessageBox):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Choose a Layer")
self.setText("Select a point layer from the list:")
self.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)

self.layer_combo = QgsMapLayerComboBox(self)
self.layer_combo.setFilters(QgsMapLayerProxyModel.PointLayer)
self.layer_combo.setMinimumWidth(200)

layout = self.layout()
layout.addWidget(self.layer_combo, 1, 1, 1, 2)

def selectedLayer(self):
return self.layer_combo.currentLayer()
53 changes: 52 additions & 1 deletion tests/test_gui.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
from qgis.PyQt.QtWidgets import QLineEdit
from qgis.core import QgsSettings
from qgis.core import (
QgsSettings,
QgsVectorLayer,
QgsFeature,
QgsGeometry,
QgsProject,
QgsPointXY,
)
from qgis.gui import QgsCollapsibleGroupBox
from qgis.testing import unittest

Expand All @@ -22,6 +29,11 @@

@pytest.mark.filterwarnings("ignore:.*imp module is deprecated.*")
class TestGui(unittest.TestCase):
def tearDown(self):
"""Run after each test"""
# Clean up layers
QgsProject.instance().removeAllMapLayers()

def test_without_live_preview(self):
from ORStools.gui.ORStoolsDialog import ORStoolsDialog
from ORStools.gui.ORStoolsDialogConfig import ORStoolsDialogConfigMain
Expand Down Expand Up @@ -358,3 +370,42 @@ def test_ORStoolsDialogConfig_url(self):
"POINT(8.67251100000000008 49.39887900000000087)",
next(layer.getFeatures()).geometry().asPolyline()[0].asWkt(),
)

def test_load_valid_point_layer(self):
"""Test loading vertices from valid point layer."""
from ORStools.gui.ORStoolsDialog import ORStoolsDialogMain

dialog_main = ORStoolsDialogMain(IFACE)
dialog_main._init_gui_control()

# Create test layers
self.point_layer = QgsVectorLayer("Point?crs=EPSG:4326", "test_points", "memory")
self.line_layer = QgsVectorLayer("LineString?crs=EPSG:4326", "test_lines", "memory")

# Add 3 features to point layer
for coords in [(1, 2), (3, 4), (5, 6)]:
feat = QgsFeature()
feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(*coords)))
self.point_layer.dataProvider().addFeature(feat)

QgsProject.instance().addMapLayers([self.point_layer, self.line_layer])

# Run test
dialog_main.dlg.load_vertices_from_layer("ok")

# Verify
self.assertTrue(dialog_main.dlg.line_tool is not None)
self.assertEqual(dialog_main.dlg.routing_fromline_list.count(), 3)
self.assertIsInstance(dialog_main.dlg.rubber_band, QgsRubberBand)

def test_user_cancels_operation(self):
"""Test when user cancels the dialog."""
from ORStools.gui.ORStoolsDialog import ORStoolsDialogMain

dialog_main = ORStoolsDialogMain(IFACE)
dialog_main._init_gui_control()
dialog_main.dlg.load_vertices_from_layer("not_ok")

self.assertTrue(dialog_main.dlg.line_tool is not None)
self.assertEqual(dialog_main.dlg.routing_fromline_list.count(), 0)
self.assertNotIsInstance(dialog_main.dlg.rubber_band, QgsRubberBand)