diff --git a/CHANGELOG.md b/CHANGELOG.md index 653b77ea..c5bd31c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,14 +39,20 @@ RELEASING: 13. Upload the package to https://plugins.qgis.org/plugins/ORStools/ (Manage > Add Version) 14. Create new release in GitHub with tag version and release title of `vX.X.X` --> - -## Unreleased +## Unrealeased +### Added +- Depiction of live preview route statistics + +## [2.1.0] - 2025-12-09 ### Added - pre-commit configuration with Ruff linter for code quality enforcement -- Improve cursor behaviour during digitization - tooltip hints in processing algorithms ([#196](https://github.com/GIScience/orstools-qgis-plugin/issues/196)) -- Improve isochrone color ramp with many ranges +- Improve isochrone color ramp with many ranges ([#264](https://github.com/GIScience/orstools-qgis-plugin/issues/264)) +- Enabled usage of custom endpoints in main application + +### Changed +- Improve cursor behaviour during digitization ([#357](https://github.com/GIScience/orstools-qgis-plugin/pull/357)) ### Fixed - Delete annotations when plugin is uninstalled ([#346](https://github.com/GIScience/orstools-qgis-plugin/issues/346)) @@ -324,7 +330,8 @@ RELEASING: - first working version of ORS Tools, after replacing OSM Tools plugin -[unreleased]: https://github.com/GIScience/orstools-qgis-plugin/compare/v2.0.1...HEAD +[unreleased]: https://github.com/GIScience/orstools-qgis-plugin/compare/v2.1.0...HEAD +[2.1.0]: https://github.com/GIScience/orstools-qgis-plugin/compare/v2.0.1...v2.1.0 [2.0.1]: https://github.com/GIScience/orstools-qgis-plugin/compare/v2.0.0...v2.0.1 [2.0.0]: https://github.com/GIScience/orstools-qgis-plugin/compare/v1.10.0...v2.0.0 [1.10.0]: https://github.com/GIScience/orstools-qgis-plugin/compare/v1.9.0...v1.10.0 diff --git a/ORStools/common/client.py b/ORStools/common/client.py index 81857392..da1e8e54 100644 --- a/ORStools/common/client.py +++ b/ORStools/common/client.py @@ -33,6 +33,7 @@ from typing import Union, Dict, List, Optional from urllib.parse import urlencode + from qgis.PyQt.QtCore import QObject, pyqtSignal, QUrl, QTimer, QEventLoop from qgis.PyQt.QtNetwork import QNetworkRequest, QNetworkReply from qgis.core import QgsSettings, QgsBlockingNetworkRequest diff --git a/ORStools/gui/ORStoolsDialog.py b/ORStools/gui/ORStoolsDialog.py index 0b3f761e..b2b3314c 100644 --- a/ORStools/gui/ORStoolsDialog.py +++ b/ORStools/gui/ORStoolsDialog.py @@ -27,8 +27,8 @@ ***************************************************************************/ """ -import json import os +import json from datetime import datetime from typing import Optional @@ -58,7 +58,11 @@ QgsAnnotation, QgsCoordinateTransform, ) -from qgis.gui import QgsMapCanvasAnnotationItem, QgsCollapsibleGroupBox, QgisInterface +from qgis.gui import ( + QgsMapCanvasAnnotationItem, + QgsCollapsibleGroupBox, + QgisInterface, +) from qgis.PyQt.QtCore import QSizeF, QPointF, QCoreApplication from qgis.PyQt.QtGui import QTextDocument from qgis.PyQt.QtWidgets import QAction, QDialog, QApplication, QMenu, QMessageBox, QDialogButtonBox @@ -291,11 +295,23 @@ def run_gui_control(self) -> None: except exceptions.ApiError as e: # Error thrown by ORStools/common/client.py, line 243, in _check_status - parsed = json.loads(e.message) - error_code = int(parsed["error"]["code"]) + try: + parsed = json.loads(e.message) + error_code = int(parsed["error"]["code"]) + except KeyError: + error_code = e.status + if error_code == 2010: maptools.LineTool(self.dlg).radius_message_box(e) return + elif error_code == "404": + self.iface.messageBar().pushMessage( + "Error 404: Not Found", + "Are your endpoints set correctly in the Provider Settings?", + level=Qgis.MessageLevel.Warning, + ) + else: + raise e def tr(self, string: str) -> str: return QCoreApplication.translate(str(self.__class__.__name__), string) @@ -412,6 +428,7 @@ def __init__(self, iface: QgisInterface, parent=None) -> None: child.toggled.connect(self.reload_rubber_band) self.rubber_band = None + self.set_live_preview_stats_visibility(False) def _save_vertices_to_layer(self) -> None: """Saves the vertices list to a temp layer""" @@ -441,6 +458,22 @@ def _save_vertices_to_layer(self) -> None: self.tr("Success"), self.tr("Vertices saved to layer."), level=Qgis.MessageLevel.Success ) + def set_live_preview_stats_visibility(self, visible: bool) -> None: + self.label_distance.setVisible(visible) + self.live_distance.setVisible(visible) + self.label_duration.setVisible(visible) + self.live_duration.setVisible(visible) + + def set_live_preview_stats(self, duration: float, distance: float) -> None: + def format_duration(hours: float) -> str: + h = int(hours) + m = int((hours - h) * 60) + return f"{h:02d}:{m:02d}" + + self.set_live_preview_stats_visibility(self.toggle_preview.isChecked()) + self.live_duration.setText(f"{format_duration(duration)} h") + self.live_distance.setText(f"{distance} km") + def _on_prov_refresh_click(self) -> None: """Populates provider dropdown with fresh list from config.yml""" @@ -471,6 +504,7 @@ def _clear_listwidget(self) -> None: self.rubber_band.reset() del self.line_tool self.line_tool = maptools.LineTool(self) + self.set_live_preview_stats(0, 0) def _linetool_annotate_point( self, point: QgsPointXY, idx: int, crs: Optional[QgsCoordinateReferenceSystem] = None diff --git a/ORStools/gui/ORStoolsDialogUI.ui b/ORStools/gui/ORStoolsDialogUI.ui index 4b48c8a2..ca0f8f51 100644 --- a/ORStools/gui/ORStoolsDialogUI.ui +++ b/ORStools/gui/ORStoolsDialogUI.ui @@ -6,8 +6,8 @@ 0 0 - 519 - 868 + 541 + 805 @@ -175,7 +175,7 @@ - + 0 0 @@ -194,7 +194,7 @@ - + 0 0 @@ -208,7 +208,13 @@ - + + + 0 + 0 + + + QLayout::SetDefaultConstraint @@ -233,13 +239,26 @@ + + + + Qt::Horizontal + + + + 20 + 20 + + + + - + 0 0 @@ -256,114 +275,173 @@ 16777215 - + QLayout::SetDefaultConstraint - - - - - 0 - 0 - - - - <html><head/><body><p>Add waypoints interactively from the map canvas.</p><p>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.</p></body></html> - - - - - - - :/plugins/ORStools/img/icon_add.png:/plugins/ORStools/img/icon_add.png - - - - - - - - 0 - 0 - - - - <html><head/><body><p>If waypoints are selected in the list, only these will be deleted. Else all waypoints will be deleted.</p></body></html> - - - - - - - :/plugins/ORStools/img/icon_clear.png:/plugins/ORStools/img/icon_clear.png - - - - - - - - 0 - 0 - - - - <html><head/><body><p>Save points in list to layer. Use the processing algorithms (batch jobs) to work with points from layers.</p></body></html> - - - - - - - :/plugins/ORStools/img/icon_save.png:/plugins/ORStools/img/icon_save.png - - - - - - - LivePreview - - + + + + 10 + + + QLayout::SetNoConstraint + + + 200 + + + + + + 0 + 0 + + + + <html><head/><body><p>Add waypoints interactively from the map canvas.</p><p>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.</p></body></html> + + + + + + + :/plugins/ORStools/img/icon_add.png:/plugins/ORStools/img/icon_add.png + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Save points in list to layer. Use the processing algorithms (batch jobs) to work with points from layers.</p></body></html> + + + + + + + :/plugins/ORStools/img/icon_save.png:/plugins/ORStools/img/icon_save.png + + + + + + + + 0 + 0 + + + + <html><head/><body><p>If waypoints are selected in the list, only these will be deleted. Else all waypoints will be deleted.</p></body></html> + + + + + + + :/plugins/ORStools/img/icon_clear.png:/plugins/ORStools/img/icon_clear.png + + + + - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Select waypoints from the map! - - - QFrame::Sunken - - - QAbstractItemView::InternalMove - - - QAbstractItemView::MultiSelection - - + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Select waypoints from the map! + + + QFrame::Sunken + + + QAbstractItemView::InternalMove + + + QAbstractItemView::MultiSelection + + + + + + + + + + + 0 + 0 + + + + LivePreview + + + + + + + + + Distance: + + + + + + + 0 + + + + + + + Duration: + + + + + + + 0 + + + + + + + @@ -375,7 +453,7 @@ 16777215 - 20 + 23 @@ -414,7 +492,7 @@ 16777215 - 20 + 23 @@ -610,7 +688,7 @@ p, li { white-space: pre-wrap; } 16777215 - 20 + 23 @@ -656,7 +734,7 @@ p, li { white-space: pre-wrap; } 16777215 - 20 + 23 @@ -861,7 +939,7 @@ p, li { white-space: pre-wrap; } 16777215 - 20 + 23 @@ -903,15 +981,15 @@ p, li { white-space: pre-wrap; } QTextEdit::AutoBulletList + + 80.000000000000000 + Queries and errors will be printed here. true - - 80 - diff --git a/ORStools/metadata.txt b/ORStools/metadata.txt index 79be2c74..da50ab09 100644 --- a/ORStools/metadata.txt +++ b/ORStools/metadata.txt @@ -4,31 +4,33 @@ qgisMinimumVersion=3.4.8 description=openrouteservice routing, isochrones and matrix calculations for QGIS supportsQt6=True -version=2.0.1 +version=2.1.0 author=HeiGIT gGmbH email=support@smartmobility.heigit.org about=ORS Tools provides access to most of the functions of openrouteservice.org, based on OpenStreetMap. The tool set includes routing, isochrones and matrix calculations, either interactive in the map canvas or from point files within the processing framework. Extensive attributes are set for output files, incl. duration, length and start/end locations. -changelog=2025/06/01 v2.0.1 +changelog=2025/12/09 v2.1.0 Added - - readded old icons and add new icons for processing algorithms + - pre-commit configuration with Ruff linter for code quality enforcement + - tooltip hints in processing algorithms + - Improve isochrone color ramp with many ranges + - Enabled usage of custom endpoints in main application - 2025/05/23 v2.0.0 - Added - - Prepare plugin for Qt6 - - Keep the selected provider when closing/reopening - - Allow endpoint configuration for processing algorithms - - Support for Snap endpoint - - Make vertex marker on map drag and droppable, add live preview - - Test correctness of processing algorithm output + Changed + - Improve cursor behaviour during digitization Fixed - - Improve error handling with radius error - - Rename output names of procs and append date and time - - Move comments and link about signup to new link - - Improve naming of batch jobs tab - - Less than two vertices not accepted with optimization and live preview + - Delete annotations when plugin is uninstalled + - Reset hand cursor after deactivating line tool + - Check API key being set before running tests + - Set url slashes correctly with optimization requests in procs + - Qt6 incompatibility with QVariant + - Switch to QgsBlockingNetworkRequest + + 2025/06/01 v2.0.1 + Added + - readded old icons and add new icons for processing algorithms Complete changelog of all versions: https://github.com/GIScience/orstools-qgis-plugin/blob/main/CHANGELOG.md Wiki: https://github.com/GIScience/orstools-qgis-plugin/wiki diff --git a/ORStools/utils/maptools.py b/ORStools/utils/maptools.py index 9af4459b..3f5b121b 100644 --- a/ORStools/utils/maptools.py +++ b/ORStools/utils/maptools.py @@ -288,6 +288,8 @@ def create_rubber_band(self) -> None: feature = next(route_layer.getFeatures()) self.dlg.rubber_band.addGeometry(feature.geometry(), route_layer) self.dlg.rubber_band.show() + + self.dlg.set_live_preview_stats(feature["DURATION_H"], feature["DIST_KM"]) else: self.dlg._clear_annotations() else: @@ -314,7 +316,8 @@ def get_error_code(self, e: QEvent) -> int: json_end_index = e.message.rfind("}") + 1 json_str = e.message[json_start_index:json_end_index] error_dict = json.loads(json_str) - return error_dict["error"]["code"] + + return error_dict["error"]["code"] if error_dict["error"]["code"] else e.status def radius_message_box(self, e) -> None: parsed = json.loads(e.message) @@ -335,14 +338,24 @@ def api_key_message_bar(self) -> None: ) def _toggle_preview(self) -> None: + self.dlg.set_live_preview_stats(0, 0) + self.dlg.set_live_preview_stats_visibility(self.dlg.toggle_preview.isChecked()) + if self.dlg.routing_fromline_list.count() > 0: state = not self.dlg.toggle_preview.isChecked() try: self.create_rubber_band() except ApiError as e: self.dlg.toggle_preview.setChecked(state) - if self.get_error_code(e) == 2010: + error_code = self.get_error_code(e) + if error_code == 2010: self.radius_message_box(e) + elif error_code == "404": + self.dlg._iface.messageBar().pushMessage( + "Error 404: Not Found", + "Are your endpoints set correctly in the Provider Settings?", + level=Qgis.MessageLevel.Warning, + ) else: raise e except Exception as e: diff --git a/ORStools/utils/router.py b/ORStools/utils/router.py index 67e76d42..40759803 100644 --- a/ORStools/utils/router.py +++ b/ORStools/utils/router.py @@ -85,8 +85,11 @@ def route_as_layer(dlg): logger.log(msg, 0) dlg.debug_text.setText(msg) return + + endpoint = provider["endpoints"]["directions"] + response = clnt.fetch_with_retry( - "/v2/directions/" + profile + "/geojson", {}, post_json=params + f"/v2/{endpoint}/" + profile + "/geojson", {}, post_json=params ) feat = directions_core.get_output_feature_directions( response, profile, params["preference"], directions.options diff --git a/tests/conftest.py b/tests/conftest.py index 69faa44d..4c8ead41 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,7 @@ from ORStools.ORStoolsPlugin import ORStools from tests.utils.utilities import get_qgis_app + def pytest_sessionstart(session): """ Called after the Session object has been created and diff --git a/tests/test_gui.py b/tests/test_gui.py index fc5b47e7..374122fc 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -358,3 +358,45 @@ def test_ORStoolsDialogConfig_url(self): "POINT(8.67251100000000008 49.39887900000000087)", next(layer.getFeatures()).geometry().asPolyline()[0].asWkt(), ) + + def test_custom_endpoints(self): + from ORStools.gui.ORStoolsDialogConfig import ORStoolsDialogConfigMain + + # Set and reset config to test whether the reset works + dlg_config = ORStoolsDialogConfigMain() + provider = dlg_config.providers.findChildren(QgsCollapsibleGroupBox)[0] + + # set endpoint of directions to non-existent value + line_edit = provider.findChild(QLineEdit, "openrouteservice_directions_endpoint") + line_edit.setText("custom_directions_endpoint") + dlg_config.accept() + + settings_directions_endpoint = QgsSettings().value("ORStools/config")["providers"][0][ + "endpoints" + ]["directions"] + + proc = TestProc() + proc.setUpClass() + + self.assertEqual(settings_directions_endpoint, "custom_directions_endpoint") + + layer = proc.get_directions_points_layer() + self.assertEqual(layer.featureCount(), 0) + + # reset endpoints + dlg_config._reset_endpoints() + dlg_config.accept() + + settings_directions_endpoint = QgsSettings().value("ORStools/config")["providers"][0][ + "endpoints" + ]["directions"] + + self.assertEqual(settings_directions_endpoint, "directions") + + layer = proc.get_directions_points_layer() + self.assertEqual(layer.featureCount(), 93) + + self.assertEqual( + "POINT(8.67251100000000008 49.39887900000000087)", + next(layer.getFeatures()).geometry().asPolyline()[0].asWkt(), + )