3535from qgis .PyQt .QtWidgets import QCheckBox
3636
3737from ..utils .gui import LayerMessageBox
38- from ..utils .router import route_as_layer
38+ from ..utils .router import route_as_layer , get_routing_parameters
39+ from ORStools .gui import directions_gui
3940
4041try :
4142 import processing
5960 QgsAnnotation ,
6061 QgsCoordinateTransform ,
6162 QgsWkbTypes ,
63+ QgsTask ,
64+ QgsApplication ,
6265)
6366from qgis .gui import (
6467 QgsMapCanvasAnnotationItem ,
6568 QgsCollapsibleGroupBox ,
6669 QgisInterface ,
6770)
6871from qgis .PyQt .QtCore import QSizeF , QPointF , QCoreApplication
69- from qgis .PyQt .QtGui import QTextDocument
70- from qgis .PyQt .QtWidgets import QAction , QDialog , QApplication , QMenu , QMessageBox , QDialogButtonBox
72+ from qgis .PyQt .QtGui import QTextDocument , QKeySequence
73+ from qgis .PyQt .QtWidgets import (
74+ QAction ,
75+ QDialog ,
76+ QApplication ,
77+ QMenu ,
78+ QMessageBox ,
79+ QDialogButtonBox ,
80+ QShortcut ,
81+ )
7182from qgis .PyQt .QtGui import QColor
7283from qgis .PyQt .QtWidgets import (
7384 QWidget ,
@@ -256,55 +267,49 @@ def _init_gui_control(self) -> None:
256267
257268 self .dlg .show ()
258269
259- def run_gui_control (self ) -> None :
260- """Slot function for OK button of main dialog."""
261- if self .dlg .routing_fromline_list .count () == 0 :
262- return
270+ def handle_task_exception (self , exception : Exception ) -> None :
271+ """Handles exceptions thrown by routing task."""
272+ if isinstance (exception , exceptions .InvalidInput ):
273+ QMessageBox .critical (
274+ self .dlg ,
275+ self .tr ("Wrong number of waypoints" ),
276+ self .tr ("""At least 3 or 4 waypoints are needed to perform routing optimization.
277+ Remember, the first and last location are not part of the optimization.
278+ """ ),
279+ )
263280
264- try :
265- basepath = os .path .dirname (__file__ )
266-
267- layer_out = route_as_layer (self .dlg )
268-
269- # style output layer
270- qml_path = os .path .join (basepath , "linestyle.qml" )
271- layer_out .loadNamedStyle (qml_path , True )
272- layer_out .triggerRepaint ()
273-
274- self .project .addMapLayer (layer_out )
275-
276- # add ors svg path
277- my_new_path = os .path .join (basepath , "img/svg" )
278- svg_paths = QgsSettings ().value ("svg/searchPathsForSVG" ) or []
279- if my_new_path not in svg_paths :
280- svg_paths .append (my_new_path )
281- QgsSettings ().setValue ("svg/searchPathsForSVG" , svg_paths )
282-
283- # Associate annotations with map layer, so they get deleted when layer is deleted
284- for annotation in self .dlg .annotations :
285- # Has the potential to be pretty cool: instead of deleting, associate with mapLayer
286- # , you can change order after optimization
287- # Then in theory, when the layer is remove, the annotation is removed as well
288- # Doesn't work though, the annotations are still there when project is re-opened
289- # annotation.setMapLayer(layer_out)
290- self .project .annotationManager ().removeAnnotation (annotation )
281+ elif isinstance (exception , exceptions .EmptyLayerError ):
282+ QMessageBox .warning (
283+ self .dlg ,
284+ self .tr ("Empty layer" ),
285+ self .tr ("""
286+ The specified avoid polygon(s) layer does not contain any features.
287+ Please add polygons to the layer or uncheck avoid polygons.
288+ """ ),
289+ )
291290
292- self .dlg .annotations = []
293- self .dlg .rubber_band .reset ()
291+ elif isinstance (exception , exceptions .InvalidKey ):
292+ QMessageBox .critical (
293+ self .dlg ,
294+ self .tr ("Missing API key" ),
295+ self .tr ("""
296+ Did you forget to set an <b>API key</b> for openrouteservice?<br><br>
294297
295- self .dlg ._clear_listwidget ()
296- self .dlg .line_tool = maptools .LineTool (self .dlg )
298+ If you don't have an API key, please visit https://openrouteservice.org/sign-up to get one. <br><br>
299+ Then enter the API key for openrouteservice provider in Web ► ORS Tools ► Provider Settings or the
300+ settings symbol in the main ORS Tools GUI, next to the provider dropdown.""" ),
301+ )
297302
298- except exceptions .ApiError as e :
303+ elif isinstance ( exception , exceptions .ApiError ) :
299304 # Error thrown by ORStools/common/client.py, line 243, in _check_status
300305 try :
301- parsed = json .loads (e .message )
306+ parsed = json .loads (exception .message )
302307 error_code = int (parsed ["error" ]["code" ])
303308 except KeyError :
304- error_code = e .status
309+ error_code = exception .status
305310
306311 if error_code == 2010 :
307- maptools .LineTool (self .dlg ).radius_message_box (e )
312+ maptools .LineTool (self .dlg ).radius_message_box (exception )
308313 return
309314 elif error_code == "404" :
310315 self .iface .messageBar ().pushMessage (
@@ -313,7 +318,56 @@ def run_gui_control(self) -> None:
313318 level = Qgis .MessageLevel .Warning ,
314319 )
315320 else :
316- raise e
321+ raise exception
322+
323+ def on_finished (self , exception , layer_out = None ) -> None :
324+ """
325+ Callback when task finishes.
326+
327+ :param exception: Exception if task failed, None otherwise
328+ :param result: The layer_out returned from the task function
329+ """
330+ if exception is not None :
331+ self .handle_task_exception (exception )
332+ return
333+
334+ basepath = os .path .dirname (__file__ )
335+ qml_path = os .path .join (basepath , "linestyle.qml" )
336+ layer_out .loadNamedStyle (qml_path , True )
337+ layer_out .triggerRepaint ()
338+
339+ self .project .addMapLayer (layer_out )
340+
341+ my_new_path = os .path .join (basepath , "img/svg" )
342+ svg_paths = QgsSettings ().value ("svg/searchPathsForSVG" ) or []
343+ if my_new_path not in svg_paths :
344+ svg_paths .append (my_new_path )
345+ QgsSettings ().setValue ("svg/searchPathsForSVG" , svg_paths )
346+
347+ def run_gui_control (self ) -> None :
348+ """Slot function for OK button of main dialog."""
349+ if self .dlg .routing_fromline_list .count () == 0 :
350+ return
351+
352+ provider , profile , optimize = get_routing_parameters (self .dlg )
353+ directions = directions_gui .Directions (self .dlg )
354+
355+ self .task = QgsTask .fromFunction (
356+ "ORStools Routing Task" ,
357+ route_as_layer ,
358+ provider = provider ,
359+ profile = profile ,
360+ optimize = optimize ,
361+ directions = directions .get_directions (),
362+ on_finished = self .on_finished ,
363+ )
364+
365+ self .dlg .setDisabled (True )
366+
367+ QgsApplication .taskManager ().addTask (self .task )
368+
369+ self .task .taskCompleted .connect (lambda : self .dlg .setDisabled (False ))
370+ self .task .taskTerminated .connect (lambda : self .dlg .setDisabled (False ))
317371
318372 def tr (self , string : str ) -> str :
319373 return QCoreApplication .translate (str (self .__class__ .__name__ ), string )
@@ -396,6 +450,8 @@ def __init__(self, iface: QgisInterface, parent=None) -> None:
396450 self .pushButton_export .clicked .connect (
397451 lambda : processing .execAlgorithmDialog (f"{ PLUGIN_NAME } :export_network_from_map" )
398452 )
453+ shortcut = QShortcut (QKeySequence ("Ctrl+Return" ), self )
454+ shortcut .activated .connect (lambda : self .global_buttons .accepted .emit ())
399455
400456 # Reset index of list items every time something is moved or deleted
401457 self .routing_fromline_list .model ().rowsMoved .connect (self ._reindex_list_items )
0 commit comments