From 0131012f56769c91b20e1ee2876c7bb920b537a3 Mon Sep 17 00:00:00 2001 From: Till Frankenbach Date: Tue, 27 Aug 2024 15:15:48 +0200 Subject: [PATCH] fix: alert on duplicate points traveling salesman (#275) Co-authored-by: Jakob Schnell --- ORStools/gui/ORStoolsDialog.py | 52 +++++++++++- ORStools/i18n/orstools_de.ts | 84 +++++++++---------- ORStools/proc/directions_points_layer_proc.py | 9 ++ ORStools/utils/exceptions.py | 8 ++ 4 files changed, 109 insertions(+), 44 deletions(-) diff --git a/ORStools/gui/ORStoolsDialog.py b/ORStools/gui/ORStoolsDialog.py index 95cefdb5..4d6a4c70 100644 --- a/ORStools/gui/ORStoolsDialog.py +++ b/ORStools/gui/ORStoolsDialog.py @@ -54,7 +54,7 @@ from qgis.gui import QgsMapCanvasAnnotationItem from qgis.PyQt.QtCore import QSizeF, QPointF, QCoreApplication -from qgis.PyQt.QtGui import QIcon, QTextDocument +from qgis.PyQt.QtGui import QIcon, QTextDocument, QColor from qgis.PyQt.QtWidgets import ( QAction, QDialog, @@ -106,6 +106,8 @@ def on_help_click() -> None: def on_about_click(parent: QWidget) -> None: """Slot for click event of About button/menu entry.""" + # ruff will add trailing comma to last string line which breaks pylupdate5 + # fmt: off info = QCoreApplication.translate( "@default", 'ORS Tools provides access to None: 'Web: {2}
' 'Repo: ' "github.com/GIScience/orstools-qgis-plugin
" - "Version: {3}", + "Version: {3}" ).format(DEFAULT_COLOR, __email__, __web__, __version__) + # fmt: on QMessageBox.information( parent, QCoreApplication.translate("@default", "About {}").format(PLUGIN_NAME), info @@ -324,6 +327,27 @@ def run_gui_control(self) -> None: try: params = directions.get_parameters() if self.dlg.optimization_group.isChecked(): + # check for duplicate points + points = [ + self.dlg.routing_fromline_list.item(x).text() + for x in range(self.dlg.routing_fromline_list.count()) + ] + if len(points) != len(set(points)): + QMessageBox.warning( + self.dlg, + self.tr("Duplicates"), + self.tr( + """ + There are duplicate points in the input layer. Traveling Salesman Optimization does not allow this. + Either remove the duplicates or deselect Traveling Salesman. + """ + ), + ) + msg = self.tr("The request has been aborted!") + logger.log(msg, 0) + self.dlg.debug_text.setText(msg) + return + if len(params["jobs"]) <= 1: # Start/end locations don't count as job QMessageBox.critical( self.dlg, @@ -494,6 +518,14 @@ def __init__(self, iface: QgisInterface, parent=None) -> None: self.routing_fromline_list.model().rowsMoved.connect(self._reindex_list_items) self.routing_fromline_list.model().rowsRemoved.connect(self._reindex_list_items) + # Connect signals to the color_duplicate_items function + self.routing_fromline_list.model().rowsRemoved.connect( + lambda: self.color_duplicate_items(self.routing_fromline_list) + ) + self.routing_fromline_list.model().rowsInserted.connect( + lambda: self.color_duplicate_items(self.routing_fromline_list) + ) + self.annotation_canvas = self._iface.mapCanvas() def _save_vertices_to_layer(self) -> None: @@ -637,3 +669,19 @@ def _on_linetool_map_doubleclick(self) -> None: QApplication.restoreOverrideCursor() self._iface.mapCanvas().setMapTool(self.last_maptool) self.show() + + def color_duplicate_items(self, list_widget): + item_dict = {} + for index in range(list_widget.count()): + item = list_widget.item(index) + text = item.text() + if text in item_dict: + item_dict[text].append(index) + else: + item_dict[text] = [index] + + for indices in item_dict.values(): + if len(indices) > 1: + for index in indices: + item = list_widget.item(index) + item.setBackground(QColor("lightsalmon")) diff --git a/ORStools/i18n/orstools_de.ts b/ORStools/i18n/orstools_de.ts index a3bd32b9..4325a73a 100644 --- a/ORStools/i18n/orstools_de.ts +++ b/ORStools/i18n/orstools_de.ts @@ -3,16 +3,16 @@ @default - - - <b>ORS Tools</b> provides access to <a href="https://openrouteservice.org" style="color: {0}">openrouteservice</a> routing functionalities.<br><br><center><a href="https://heigit.org/de/willkommen"><img src=":/plugins/ORStools/img/logo_heigit_300.png"/></a><br><br></center>Author: HeiGIT gGmbH<br>Email: <a href="mailto:Openrouteservice <{1}>">{1}</a><br>Web: <a href="{2}">{2}</a><br>Repo: <a href="https://github.com/GIScience/orstools-qgis-plugin">github.com/GIScience/orstools-qgis-plugin</a><br>Version: {3} - <b>ORS Tools</b> bietet Zugriff auf <a href="https://openrouteservice.org" style="color: {0}">openrouteservice</a> Berechnungen.<br><br><center><a href="https://heigit.org/de/willkommen"><img src=":/plugins/ORStools/img/logo_heigit_300.png"/></a><br><br></center>Author: HeiGIT gGmbH<br>Email: <a href="mailto:Openrouteservice <{1}>">{1}</a><br>Web: <a href="{2}">{2}</a><br>Repo: <a href="https://github.com/GIScience/orstools-qgis-plugin">github.com/GIScience/orstools-qgis-plugin</a><br>Version: {3} - About {} Über {} + + + <b>ORS Tools</b> provides access to <a href="https://openrouteservice.org" style="color: {0}">openrouteservice</a> routing functionalities.<br><br><center><a href="https://heigit.org/de/willkommen"><img src=":/plugins/ORStools/img/logo_heigit_300.png"/></a><br><br></center>Author: HeiGIT gGmbH<br>Email: <a href="mailto:Openrouteservice <{1}>">{1}</a><br>Web: <a href="{2}">{2}</a><br>Repo: <a href="https://github.com/GIScience/orstools-qgis-plugin">github.com/GIScience/orstools-qgis-plugin</a><br>Version: {3} + <b>ORS Tools</b> bietet Zugriff auf <a href="https://openrouteservice.org" style="color: {0}">openrouteservice</a> Berechnungen.<br><br><center><a href="https://heigit.org/de/willkommen"><img src=":/plugins/ORStools/img/logo_heigit_300.png"/></a><br><br></center>Author: HeiGIT gGmbH<br>Email: <a href="mailto:Openrouteservice <{1}>">{1}</a><br>Web: <a href="{2}">{2}</a><br>Repo: <a href="https://github.com/GIScience/orstools-qgis-plugin">github.com/GIScience/orstools-qgis-plugin</a><br>Version: {3} + ORSBaseProcessingAlgorithm @@ -79,7 +79,7 @@ Wegpunktoptimierung (sonstige Konfiguration wird nicht berücksichtigt) - + Directions from 1 Polyline-Layer Routenberechnung aus einem Polyline-Layer @@ -104,34 +104,6 @@ Csv Spalte (benötigt Csv Faktor und csv in Extra Info) - - ORSDirectionsLinesAlgorithm - - - Input Line layer - Eingabelayer (Linien) - - - - Layer ID Field - ID-Attribut - - - - Travel preference - Routenpräferenz - - - - Traveling Salesman (omits other configurations) - Wegpunktoptimierung (sonstige Konfiguration wird nicht berücksichtigt) - - - - Directions from 1 Polyline-Layer - Routenberechnung aus einem Polyline-Layer - - ORSDirectionsPointsLayerAlgo @@ -155,7 +127,7 @@ Wegpunktoptimierung (sonstige Konfiguration wird nicht berücksichtigt) - + Directions from 1 Point-Layer Routenberechnung aus einem Punkt-Layer @@ -165,7 +137,7 @@ ID-Attribut (zum Beispiel für joins) - + Export order of jobs Reihenfolge exportieren @@ -175,15 +147,24 @@ Extra Info - + Csv Factor (needs Csv Column and csv in Extra Info) Csv Faktor (benötigt Csv Spalte und csv in Extra Info) - + Csv Column (needs Csv Factor and csv in Extra Info) Csv Spalte (benötigt Csv Faktor und csv in Extra Info) + + + + There are duplicate points in the input layer. Traveling Salesman Optimization does not allow this. + Either remove the duplicates or deselect Traveling Salesman. + + Das Eingabelayer enthält duplizierte Punkte. Dies ist mit der Wegpunktoptimierung nicht erlaubt. +Duplikate entfernen oder Wegpunktoptimierung abwählen. + ORSDirectionsPointsLayersAlgo @@ -228,7 +209,7 @@ Zuordnungs-Verfahren - + Directions from 2 Point-Layers Routenberechnung aus zwei Punkt-Layern @@ -350,12 +331,12 @@ ORStoolsDialog - + Apply Anwenden - + Close Schließen @@ -462,7 +443,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" padding: 10px; -qt-block-indent:0; text-indent:0px ; background-color:#e7f2fa; color: #999999"><img stype="margin: 10px" src=":/plugins/ORStools/img/icon_about.png" width=16 height=16 /> Sämtliche Einstellungen werden überschrieben</p></body></html> @@ -729,5 +710,24 @@ p, li { white-space: pre-wrap; } About Über + + + Duplicates + Duplikate + + + + + There are duplicate points in the input layer. Traveling Salesman Optimization does not allow this. + Either remove the duplicates or deselect Traveling Salesman. + + Das Eingabelayer enthält duplizierte Punkte. Dies ist mit der Wegpunktoptimierung nicht erlaubt. +Duplikate entfernen oder Wegpunktoptimierung abwählen. + + + + The request has been aborted! + Die Anfrage wurde abgebrochen! + diff --git a/ORStools/proc/directions_points_layer_proc.py b/ORStools/proc/directions_points_layer_proc.py index 68f0e598..f3544b08 100644 --- a/ORStools/proc/directions_points_layer_proc.py +++ b/ORStools/proc/directions_points_layer_proc.py @@ -218,6 +218,15 @@ def sort(f): try: if optimization_mode is not None: + # check for duplicate points + if len(points) != len(set(points)): + raise exceptions.DuplicateError( + self.tr(""" + There are duplicate points in the input layer. Traveling Salesman Optimization does not allow this. + Either remove the duplicates or deselect Traveling Salesman. + """) + ) + params = get_params_optimize(points, profile, optimization_mode) response = ors_client.request("/optimization", {}, post_json=params) diff --git a/ORStools/utils/exceptions.py b/ORStools/utils/exceptions.py index ac8fe7d7..177a7b46 100644 --- a/ORStools/utils/exceptions.py +++ b/ORStools/utils/exceptions.py @@ -92,3 +92,11 @@ def __str__(self): return self.status else: return f"{self.status} ({self.message})" + + +class DuplicateError(Exception): + def __init__(self, message=None): + self.message = message + + def __str__(self): + return self.message