From 85ebed2a2ec1f4de7274a316696e56f66004d6ff Mon Sep 17 00:00:00 2001 From: Till Frankenbach <81414045+merydian@users.noreply.github.com> Date: Wed, 15 May 2024 10:38:18 -0400 Subject: [PATCH] feat: improve type hinting (#240) --- CHANGELOG.md | 1 + ORStools/ORStoolsPlugin.py | 7 ++- ORStools/common/client.py | 16 ++++-- ORStools/common/directions_core.py | 35 +++++++----- ORStools/common/isochrones_core.py | 22 ++++++-- ORStools/common/networkaccessmanager.py | 22 ++++---- ORStools/gui/ORStoolsDialog.py | 55 ++++++++++++------- ORStools/gui/ORStoolsDialogConfig.py | 16 +++--- ORStools/proc/base_processing_algorithm.py | 12 ++-- ORStools/proc/directions_lines_proc.py | 30 ++++++---- ORStools/proc/directions_points_layer_proc.py | 29 ++++++---- .../proc/directions_points_layers_proc.py | 45 ++++++++++----- ORStools/proc/isochrones_layer_proc.py | 32 +++++++---- ORStools/proc/isochrones_point_proc.py | 30 ++++++---- ORStools/proc/matrix_proc.py | 22 +++++--- ORStools/proc/provider.py | 12 ++-- ORStools/utils/configmanager.py | 6 +- ORStools/utils/convert.py | 2 +- ORStools/utils/logger.py | 2 +- ORStools/utils/maptools.py | 13 +++-- ORStools/utils/transform.py | 2 +- 21 files changed, 251 insertions(+), 160 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd4fe703..2253c1dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ RELEASING: - Vertices on canvas not depicted fully with n having more than one digit in length ([#235](https://github.com/GIScience/orstools-qgis-plugin/issues/235)) - Replace qt QSettings with QgsSettings for centralized configuration management ([#239](https://github.com/GIScience/orstools-qgis-plugin/issues/239)) - Fix: Point Annotations stay after saving project and not deleting them manually([#229](https://github.com/GIScience/orstools-qgis-plugin/issues/229)) +- Improved type hints ### Added - Add support for decimal ranges with isochrones([#237](https://github.com/GIScience/orstools-qgis-plugin/issues/237)) diff --git a/ORStools/ORStoolsPlugin.py b/ORStools/ORStoolsPlugin.py index b6cc99f6..e675f834 100644 --- a/ORStools/ORStoolsPlugin.py +++ b/ORStools/ORStoolsPlugin.py @@ -27,6 +27,7 @@ ***************************************************************************/ """ +from qgis.gui import QgisInterface from qgis.core import QgsApplication, QgsSettings from PyQt5.QtCore import QTranslator, qVersion, QCoreApplication import os.path @@ -40,7 +41,7 @@ class ORStools: # noinspection PyTypeChecker,PyArgumentList,PyCallByClass - def __init__(self, iface): + def __init__(self, iface: QgisInterface) -> None: """Constructor. :param iface: An interface instance that will be passed to this class @@ -65,13 +66,13 @@ def __init__(self, iface): if qVersion() > "4.3.3": QCoreApplication.installTranslator(self.translator) - def initGui(self): + def initGui(self) -> None: """Create the menu entries and toolbar icons inside the QGIS GUI.""" QgsApplication.processingRegistry().addProvider(self.provider) self.dialog.initGui() - def unload(self): + def unload(self) -> None: """remove menu entry and toolbar icons""" QgsApplication.processingRegistry().removeProvider(self.provider) self.dialog.unload() diff --git a/ORStools/common/client.py b/ORStools/common/client.py index b3115bc6..e4ff6821 100644 --- a/ORStools/common/client.py +++ b/ORStools/common/client.py @@ -31,6 +31,7 @@ import random import time from datetime import datetime, timedelta +from typing import Union, Dict, List, Optional from urllib.parse import urlencode from PyQt5.QtCore import QObject, pyqtSignal @@ -48,7 +49,7 @@ class Client(QObject): """Performs requests to the ORS API services.""" - def __init__(self, provider=None, agent=None): + def __init__(self, provider: Optional[dict] = None, agent: Optional[str] = None) -> None: """ :param provider: A openrouteservice provider from config.yml :type provider: dict @@ -87,7 +88,14 @@ def __init__(self, provider=None, agent=None): overQueryLimit = pyqtSignal() - def request(self, url, params, first_request_time=None, retry_counter=0, post_json=None): + def request( + self, + url: str, + params: dict, + first_request_time: Optional[datetime.time] = None, + retry_counter: int = 0, + post_json: Optional[dict] = None, + ): """Performs HTTP GET/POST with credentials, returning the body as JSON. @@ -194,7 +202,7 @@ def request(self, url, params, first_request_time=None, retry_counter=0, post_js return json.loads(content.decode("utf-8")) - def _check_status(self): + def _check_status(self) -> None: """ Casts JSON response to dict @@ -231,7 +239,7 @@ def _check_status(self): elif status_code != 200: raise exceptions.GenericServerError(str(status_code), message) - def _generate_auth_url(self, path, params): + def _generate_auth_url(self, path: str, params: Union[Dict, List]) -> str: """Returns the path and query string portion of the request URL, first adding any necessary parameters. diff --git a/ORStools/common/directions_core.py b/ORStools/common/directions_core.py index e7496ac3..0f523045 100644 --- a/ORStools/common/directions_core.py +++ b/ORStools/common/directions_core.py @@ -29,14 +29,14 @@ from itertools import product from qgis.core import QgsPoint, QgsPointXY, QgsGeometry, QgsFeature, QgsFields, QgsField -from typing import List +from typing import List, Generator, Tuple, Any, Optional from PyQt5.QtCore import QVariant from ORStools.utils import convert -def get_request_point_features(route_dict, row_by_row): +def get_request_point_features(route_dict: dict, row_by_row: str) -> Generator[List, Tuple, None]: """ Processes input point features depending on the layer to layer relation in directions settings @@ -75,12 +75,12 @@ def get_request_point_features(route_dict, row_by_row): def get_fields( - from_type=QVariant.String, - to_type=QVariant.String, - from_name="FROM_ID", - to_name="TO_ID", - line=False, -): + from_type: QVariant.Type = QVariant.String, + to_type: QVariant.Type = QVariant.String, + from_name: str = "FROM_ID", + to_name: str = "TO_ID", + line: bool = False, +) -> QgsFields: """ Builds output fields for directions response layer. @@ -117,8 +117,13 @@ def get_fields( def get_output_feature_directions( - response, profile, preference, options=None, from_value=None, to_value=None -): + response: dict, + profile: str, + preference: str, + options: Optional[str] = None, + from_value: Any = None, + to_value: Any = None, +) -> QgsFeature: """ Build output feature based on response attributes for directions endpoint. @@ -165,7 +170,9 @@ def get_output_feature_directions( return feat -def get_output_features_optimization(response, profile, from_value=None): +def get_output_features_optimization( + response: dict, profile: str, from_value: Any = None +) -> QgsFeature: """ Build output feature based on response attributes for optimization endpoint. @@ -205,9 +212,9 @@ def get_output_features_optimization(response, profile, from_value=None): def build_default_parameters( preference: str, - point_list: List[QgsPointXY] = None, - coordinates: list = None, - options: dict = None, + point_list: Optional[List[QgsPointXY]] = None, + coordinates: Optional[list] = None, + options: Optional[dict] = None, ) -> dict: """ Build default parameters for directions endpoint. Either uses a list of QgsPointXY to create the coordinates diff --git a/ORStools/common/isochrones_core.py b/ORStools/common/isochrones_core.py index 39c90150..218f966c 100644 --- a/ORStools/common/isochrones_core.py +++ b/ORStools/common/isochrones_core.py @@ -27,6 +27,9 @@ ***************************************************************************/ """ +from typing import Any, Generator + +from qgis._core import QgsMapLayer from qgis.core import ( QgsPointXY, QgsFeature, @@ -49,7 +52,7 @@ class Isochrones: """convenience class to build isochrones""" - def __init__(self): + def __init__(self) -> None: # Will all be set in self.set_parameters(), bcs Processing Algo has to initialize this class before it # knows about its own parameters self.profile = None @@ -60,8 +63,13 @@ def __init__(self): self.field_dimension_name = None def set_parameters( - self, profile, dimension, factor, id_field_type=QVariant.String, id_field_name="ID" - ): + self, + profile: str, + dimension: str, + factor: int, + id_field_type: QVariant.String = QVariant.String, + id_field_name: str = "ID", + ) -> None: """ Sets all parameters defined in __init__, because processing algorithm calls this class when it doesn't know its parameters yet. @@ -89,7 +97,7 @@ def set_parameters( self.field_dimension_name = "AA_MINS" if self.dimension == "time" else "AA_METERS" - def get_fields(self): + def get_fields(self) -> QgsFields: """ Set all fields for output isochrone layer. @@ -106,7 +114,9 @@ def get_fields(self): return fields - def get_features(self, response, id_field_value): + def get_features( + self, response: dict, id_field_value: Any + ) -> Generator[QgsFeature, None, None]: """ Generator to return output isochrone features from response. @@ -158,7 +168,7 @@ def get_features(self, response, id_field_value): # # return dissolved - def stylePoly(self, layer): + def stylePoly(self, layer: QgsMapLayer) -> None: """ Style isochrone polygon layer. diff --git a/ORStools/common/networkaccessmanager.py b/ORStools/common/networkaccessmanager.py index c2dd8132..a1cbc62b 100644 --- a/ORStools/common/networkaccessmanager.py +++ b/ORStools/common/networkaccessmanager.py @@ -153,7 +153,7 @@ def __init__( exception_class=None, debug=True, timeout=60, - ): + ) -> None: self.disable_ssl_certificate_validation = disable_ssl_certificate_validation self.authid = authid self.reply = None @@ -175,17 +175,19 @@ def __init__( ) self.timeout = timeout - def msg_log(self, msg): + def msg_log(self, msg: str) -> None: if self.debug: QgsMessageLog.logMessage(msg, "NetworkAccessManager") - def httpResult(self): + def httpResult(self) -> None: return self.http_call_result - def auth_manager(self): + def auth_manager(self) -> None: return QgsApplication.authManager() - def request(self, url, method="GET", body=None, headers=None, blocking=True): + def request( + self, url: str, method: str = "GET", body=None, headers=None, blocking: bool = True + ): """ Make a network request by calling QgsNetworkAccessManager. redirections argument is ignored and is here only for httplib2 compatibility. @@ -279,19 +281,19 @@ def request(self, url, method="GET", body=None, headers=None, blocking=True): return self.http_call_result, self.http_call_result.content - def downloadProgress(self, bytesReceived, bytesTotal): + def downloadProgress(self, bytesReceived, bytesTotal) -> None: """Keep track of the download progress""" # self.msg_log("downloadProgress %s of %s ..." % (bytesReceived, bytesTotal)) pass # noinspection PyUnusedLocal - def requestTimedOut(self, reply): + def requestTimedOut(self, reply) -> None: """Trap the timeout. In Async mode requestTimedOut is called after replyFinished""" # adapt http_call_result basing on receiving qgs timer timout signal self.exception_class = RequestsExceptionTimeout self.http_call_result.exception = RequestsExceptionTimeout("Timeout error") - def replyFinished(self): + def replyFinished(self) -> None: err = self.reply.error() httpStatus = self.reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) httpStatusMessage = self.reply.attribute(QNetworkRequest.HttpReasonPhraseAttribute) @@ -401,7 +403,7 @@ def replyFinished(self): else: self.msg_log("Reply was already deleted ...") - def sslErrors(self, ssl_errors): + def sslErrors(self, ssl_errors) -> None: """ Handle SSL errors, logging them if debug is on and ignoring them if disable_ssl_certificate_validation is set. @@ -412,7 +414,7 @@ def sslErrors(self, ssl_errors): if self.disable_ssl_certificate_validation: self.reply.ignoreSslErrors() - def abort(self): + def abort(self) -> None: """ Handle request to cancel HTTP call """ diff --git a/ORStools/gui/ORStoolsDialog.py b/ORStools/gui/ORStoolsDialog.py index cce7e325..34ed9a5b 100644 --- a/ORStools/gui/ORStoolsDialog.py +++ b/ORStools/gui/ORStoolsDialog.py @@ -29,10 +29,13 @@ import json import os +from typing import Optional + import processing import webbrowser -from qgis._core import Qgis +from qgis._core import Qgis, QgsAnnotation +from qgis._gui import QgisInterface from qgis.core import ( QgsProject, QgsVectorLayer, @@ -48,7 +51,15 @@ from PyQt5.QtCore import QSizeF, QPointF, QCoreApplication from PyQt5.QtGui import QIcon, QTextDocument -from PyQt5.QtWidgets import QAction, QDialog, QApplication, QMenu, QMessageBox, QDialogButtonBox +from PyQt5.QtWidgets import ( + QAction, + QDialog, + QApplication, + QMenu, + QMessageBox, + QDialogButtonBox, + QWidget, +) from ORStools import ( RESOURCE_PREFIX, @@ -83,12 +94,12 @@ def on_config_click(parent): config_dlg.exec_() -def on_help_click(): +def on_help_click() -> None: """Open help URL from button/menu entry.""" webbrowser.open(__help__) -def on_about_click(parent): +def on_about_click(parent: QWidget) -> None: """Slot for click event of About button/menu entry.""" info = QCoreApplication.translate( @@ -116,7 +127,7 @@ def on_about_click(parent): class ORStoolsDialogMain: """Defines all mandatory QGIS things about dialog.""" - def __init__(self, iface): + def __init__(self, iface: QgisInterface) -> None: """ :param iface: the current QGIS interface @@ -132,10 +143,10 @@ def __init__(self, iface): self.actions = None # noinspection PyUnresolvedReferences - def initGui(self): + def initGui(self) -> None: """Called when plugin is activated (on QGIS startup or when activated in Plugin Manager).""" - def create_icon(f): + def create_icon(f: str) -> QIcon: """ internal function to create action icons @@ -187,7 +198,7 @@ def create_icon(f): # Add keyboard shortcut self.iface.registerMainWindowAction(self.actions[0], "Ctrl+R") - def unload(self): + def unload(self) -> None: """Called when QGIS closes or plugin is deactivated in Plugin Manager""" self.iface.webMenu().removeAction(self.menu.menuAction()) @@ -214,7 +225,7 @@ def unload(self): # text.append(os.environ[var]) # return '/'.join(text) - def _init_gui_control(self): + def _init_gui_control(self) -> None: """Slot for main plugin button. Initializes the GUI and shows it.""" # Only populate GUI if it's the first start of the plugin within the QGIS session @@ -237,7 +248,7 @@ def _init_gui_control(self): self.dlg.show() - def run_gui_control(self): + def run_gui_control(self) -> None: """Slot function for OK button of main dialog.""" layer_out = QgsVectorLayer("LineString?crs=EPSG:4326", "Route_ORS", "memory") @@ -404,14 +415,14 @@ def run_gui_control(self): clnt_msg += f'{clnt.url}
Parameters:
{json.dumps(params, indent=2)}' self.dlg.debug_text.setHtml(clnt_msg) - def tr(self, string): + def tr(self, string: str) -> str: return QCoreApplication.translate(str(self.__class__.__name__), string) class ORStoolsDialog(QDialog, Ui_ORStoolsDialogBase): """Define the custom behaviour of Dialog""" - def __init__(self, iface, parent=None): + def __init__(self, iface: QgisInterface, parent=None) -> None: """ :param iface: QGIS interface :type iface: QgisInterface @@ -481,7 +492,7 @@ def __init__(self, iface, parent=None): self.annotation_canvas = self._iface.mapCanvas() - def _save_vertices_to_layer(self): + def _save_vertices_to_layer(self) -> None: """Saves the vertices list to a temp layer""" items = [ self.routing_fromline_list.item(x).text() @@ -508,7 +519,7 @@ def _save_vertices_to_layer(self): "Success", "Vertices saved to layer.", level=Qgis.Success ) - def _on_prov_refresh_click(self): + def _on_prov_refresh_click(self) -> None: """Populates provider dropdown with fresh list from config.yml""" providers = configmanager.read_config()["providers"] @@ -516,7 +527,7 @@ def _on_prov_refresh_click(self): for provider in providers: self.provider_combo.addItem(provider["name"], provider) - def _on_clear_listwidget_click(self): + def _on_clear_listwidget_click(self) -> None: """Clears the contents of the QgsListWidget and the annotations.""" items = self.routing_fromline_list.selectedItems() if items: @@ -535,7 +546,9 @@ def _on_clear_listwidget_click(self): if self.line_tool: self.line_tool.canvas.scene().removeItem(self.line_tool.rubberBand) - def _linetool_annotate_point(self, point, idx, crs=None): + def _linetool_annotate_point( + self, point: QgsPointXY, idx: int, crs: Optional[QgsCoordinateReferenceSystem] = None + ) -> QgsAnnotation: if not crs: crs = self._iface.mapCanvas().mapSettings().destinationCrs() @@ -554,7 +567,7 @@ def _linetool_annotate_point(self, point, idx, crs=None): return QgsMapCanvasAnnotationItem(annotation, self.annotation_canvas).annotation() - def _clear_annotations(self): + def _clear_annotations(self) -> None: """Clears annotations""" for annotation_item in self.annotation_canvas.annotationItems(): annotation = annotation_item.annotation() @@ -562,7 +575,7 @@ def _clear_annotations(self): self.project.annotationManager().removeAnnotation(annotation) self.annotations = [] - def _on_linetool_init(self): + def _on_linetool_init(self) -> None: """Hides GUI dialog, inits line maptool and add items to line list box.""" # Remove blue lines (rubber band) if self.line_tool: @@ -580,7 +593,7 @@ def _on_linetool_init(self): ) self.line_tool.doubleClicked.connect(self._on_linetool_map_doubleclick) - def _on_linetool_map_click(self, point, idx): + def _on_linetool_map_click(self, point: QgsPointXY, idx: int) -> None: """Adds an item to QgsListWidget and annotates the point in the map canvas""" map_crs = self._iface.mapCanvas().mapSettings().destinationCrs() @@ -591,7 +604,7 @@ def _on_linetool_map_click(self, point, idx): annotation = self._linetool_annotate_point(point, idx) self.project.annotationManager().addAnnotation(annotation) - def _reindex_list_items(self): + def _reindex_list_items(self) -> None: """Resets the index when an item in the list is moved""" items = [ self.routing_fromline_list.item(x).text() @@ -610,7 +623,7 @@ def _reindex_list_items(self): annotation = self._linetool_annotate_point(point, idx, crs) self.project.annotationManager().addAnnotation(annotation) - def _on_linetool_map_doubleclick(self): + def _on_linetool_map_doubleclick(self) -> None: """ Populate line list widget with coordinates, end line drawing and show dialog again. """ diff --git a/ORStools/gui/ORStoolsDialogConfig.py b/ORStools/gui/ORStoolsDialogConfig.py index 8983a90b..ed2b15f6 100644 --- a/ORStools/gui/ORStoolsDialogConfig.py +++ b/ORStools/gui/ORStoolsDialogConfig.py @@ -41,7 +41,7 @@ class ORStoolsDialogConfigMain(QDialog, Ui_ORStoolsDialogConfigBase): """Builds provider config dialog.""" - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: """ :param parent: Parent window for modality. :type parent: QDialog @@ -62,7 +62,7 @@ def __init__(self, parent=None): # Change OK to Save in config window self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("Save")) - def accept(self): + def accept(self) -> None: """When the OK Button is clicked, in-memory temp_config is updated and written to config.yml""" collapsible_boxes = self.providers.findChildren(QgsCollapsibleGroupBox) @@ -84,7 +84,7 @@ def accept(self): self.close() @staticmethod - def _adjust_timeout_input(input_line_edit: QLineEdit): + def _adjust_timeout_input(input_line_edit: QLineEdit) -> None: """ Corrects the value of the input to the top or bottom value of the specified range of the QIntValidator for the field. @@ -100,7 +100,7 @@ def _adjust_timeout_input(input_line_edit: QLineEdit): elif int(text) > val.top(): input_line_edit.setText(str(val.top())) - def _build_ui(self): + def _build_ui(self) -> None: """Builds the UI on dialog startup.""" for provider_entry in self.temp_config["providers"]: @@ -119,7 +119,7 @@ def _build_ui(self): self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) - def _add_provider(self): + def _add_provider(self) -> None: """Adds an empty provider box to be filled out by the user.""" self._collapse_boxes() @@ -130,7 +130,7 @@ def _add_provider(self): if ok: self._add_box(provider_name, "http://localhost:8082/ors", "", 60, new=True) - def _remove_provider(self): + def _remove_provider(self) -> None: """Remove list of providers from list.""" providers = [provider["name"] for provider in self.temp_config["providers"]] @@ -152,13 +152,13 @@ def _remove_provider(self): provider_id = providers.index(provider) del self.temp_config["providers"][provider_id] - def _collapse_boxes(self): + def _collapse_boxes(self) -> None: """Collapse all QgsCollapsibleGroupBoxes.""" collapsible_boxes = self.providers.findChildren(QgsCollapsibleGroupBox) for box in collapsible_boxes: box.setCollapsed(True) - def _add_box(self, name, url, key, timeout, new=False): + def _add_box(self, name: str, url: str, key: str, timeout: int, new: bool = False) -> None: """ Adds a provider box to the QWidget layout and self.temp_config. diff --git a/ORStools/proc/base_processing_algorithm.py b/ORStools/proc/base_processing_algorithm.py index 47e136d3..61df4b88 100644 --- a/ORStools/proc/base_processing_algorithm.py +++ b/ORStools/proc/base_processing_algorithm.py @@ -40,7 +40,7 @@ QgsProcessingFeedback, QgsSettings, ) -from typing import Any +from typing import Any, Dict from PyQt5.QtGui import QIcon @@ -55,7 +55,7 @@ class ORSBaseProcessingAlgorithm(QgsProcessingAlgorithm): """Base algorithm class for ORS algorithms""" - def __init__(self): + def __init__(self) -> None: """ Default attributes used in all child classes """ @@ -92,7 +92,7 @@ def name(self) -> str: """ return self.ALGO_NAME - def shortHelpString(self): + def shortHelpString(self) -> str: """ Displays the sidebar help in the algorithm window """ @@ -101,7 +101,7 @@ def shortHelpString(self): return read_help_file(algorithm=self.ALGO_NAME, locale=locale) @staticmethod - def helpUrl(): + def helpUrl() -> str: """ Will be connected to the Help button in the Algorithm window """ @@ -222,7 +222,7 @@ def parseOptions(self, parameters: dict, context: QgsProcessingContext) -> dict: return options # noinspection PyUnusedLocal - def initAlgorithm(self, configuration): + def initAlgorithm(self, configuration: Dict) -> None: """ Combines default and algorithm parameters and adds them in order to the algorithm dialog window. @@ -244,6 +244,6 @@ def initAlgorithm(self, configuration): self.addParameter(param) - def tr(self, string, context=None): + def tr(self, string: str, context=None) -> str: context = context or self.__class__.__name__ return QCoreApplication.translate(context, string) diff --git a/ORStools/proc/directions_lines_proc.py b/ORStools/proc/directions_lines_proc.py index 7398d194..24ec00b4 100644 --- a/ORStools/proc/directions_lines_proc.py +++ b/ORStools/proc/directions_lines_proc.py @@ -27,6 +27,8 @@ ***************************************************************************/ """ +from typing import List, Dict, Generator + from qgis._core import ( QgsFeature, QgsVectorLayer, @@ -42,6 +44,9 @@ QgsProcessingParameterFeatureSource, QgsProcessingParameterEnum, QgsPointXY, + QgsProcessingFeatureSource, + QgsProcessingContext, + QgsProcessingFeedback, ) from ORStools.common import directions_core, PROFILES, PREFERENCES, OPTIMIZATION_MODES @@ -56,15 +61,15 @@ class ORSDirectionsLinesAlgo(ORSBaseProcessingAlgorithm): def __init__(self): super().__init__() - self.ALGO_NAME = "directions_from_polylines_layer" - self.GROUP = "Directions" - self.IN_LINES = "INPUT_LINE_LAYER" - self.IN_FIELD = "INPUT_LAYER_FIELD" - self.IN_PREFERENCE = "INPUT_PREFERENCE" - self.IN_OPTIMIZE = "INPUT_OPTIMIZE" - self.IN_MODE = "INPUT_MODE" - self.EXPORT_ORDER = "EXPORT_ORDER" - self.PARAMETERS = [ + self.ALGO_NAME: str = "directions_from_polylines_layer" + self.GROUP: str = "Directions" + self.IN_LINES: str = "INPUT_LINE_LAYER" + self.IN_FIELD: str = "INPUT_LAYER_FIELD" + self.IN_PREFERENCE: str = "INPUT_PREFERENCE" + self.IN_OPTIMIZE: str = "INPUT_OPTIMIZE" + self.IN_MODE: str = "INPUT_MODE" + self.EXPORT_ORDER: str = "EXPORT_ORDER" + self.PARAMETERS: List = [ QgsProcessingParameterFeatureSource( name=self.IN_LINES, description=self.tr("Input Line layer"), @@ -93,7 +98,9 @@ def __init__(self): QgsProcessingParameterBoolean(self.EXPORT_ORDER, self.tr("Export order of jobs")), ] - def processAlgorithm(self, parameters, context, feedback): + def processAlgorithm( + self, parameters: dict, context: QgsProcessingContext, feedback: QgsProcessingFeedback + ) -> Dict[str, str]: ors_client = self._get_ors_client_from_provider(parameters[self.IN_PROVIDER], feedback) profile = dict(enumerate(PROFILES))[parameters[self.IN_PROFILE]] @@ -199,7 +206,7 @@ def processAlgorithm(self, parameters, context, feedback): return {self.OUT: dest_id} @staticmethod - def _get_sorted_lines(layer, field_name): + def _get_sorted_lines(layer: QgsProcessingFeatureSource, field_name: str) -> Generator: """ Generator to yield geometry and ID value sorted by feature ID. Careful: feat.id() is not necessarily permanent @@ -229,7 +236,6 @@ def _get_sorted_lines(layer, field_name): line = [ x_former.transform(QgsPointXY(point)) for point in feat.geometry().asPolyline() ] - yield line, field_value def displayName(self) -> str: diff --git a/ORStools/proc/directions_points_layer_proc.py b/ORStools/proc/directions_points_layer_proc.py index 56030ceb..5d914476 100644 --- a/ORStools/proc/directions_points_layer_proc.py +++ b/ORStools/proc/directions_points_layer_proc.py @@ -27,6 +27,9 @@ ***************************************************************************/ """ +from typing import Dict, List + + from qgis._core import ( QgsVectorLayer, QgsFeature, @@ -42,6 +45,8 @@ QgsProcessingParameterFeatureSource, QgsProcessingParameterEnum, QgsPointXY, + QgsProcessingContext, + QgsProcessingFeedback, ) from ORStools.common import directions_core, PROFILES, PREFERENCES, OPTIMIZATION_MODES @@ -56,16 +61,16 @@ class ORSDirectionsPointsLayerAlgo(ORSBaseProcessingAlgorithm): def __init__(self): super().__init__() - self.ALGO_NAME = "directions_from_points_1_layer" - self.GROUP = "Directions" - self.IN_POINTS = "INPUT_POINT_LAYER" - self.IN_FIELD = "INPUT_LAYER_FIELD" - self.IN_PREFERENCE = "INPUT_PREFERENCE" - self.IN_OPTIMIZE = "INPUT_OPTIMIZE" - self.IN_MODE = "INPUT_MODE" - self.IN_SORTBY = "INPUT_SORTBY" - self.EXPORT_ORDER = "EXPORT_ORDER" - self.PARAMETERS = [ + self.ALGO_NAME: str = "directions_from_points_1_layer" + self.GROUP: str = "Directions" + self.IN_POINTS: str = "INPUT_POINT_LAYER" + self.IN_FIELD: str = "INPUT_LAYER_FIELD" + self.IN_PREFERENCE: str = "INPUT_PREFERENCE" + self.IN_OPTIMIZE: str = "INPUT_OPTIMIZE" + self.IN_MODE: str = "INPUT_MODE" + self.IN_SORTBY: str = "INPUT_SORTBY" + self.EXPORT_ORDER: str = "EXPORT_ORDER" + self.PARAMETERS: List = [ QgsProcessingParameterFeatureSource( name=self.IN_POINTS, description=self.tr("Input (Multi)Point layer"), @@ -101,7 +106,9 @@ def __init__(self): QgsProcessingParameterBoolean(self.EXPORT_ORDER, self.tr("Export order of jobs")), ] - def processAlgorithm(self, parameters, context, feedback): + def processAlgorithm( + self, parameters: dict, context: QgsProcessingContext, feedback: QgsProcessingFeedback + ) -> Dict[str, str]: ors_client = self._get_ors_client_from_provider(parameters[self.IN_PROVIDER], feedback) profile = dict(enumerate(PROFILES))[parameters[self.IN_PROFILE]] diff --git a/ORStools/proc/directions_points_layers_proc.py b/ORStools/proc/directions_points_layers_proc.py index b7e00379..10634bb0 100644 --- a/ORStools/proc/directions_points_layers_proc.py +++ b/ORStools/proc/directions_points_layers_proc.py @@ -27,6 +27,9 @@ ***************************************************************************/ """ +from typing import Dict + +from qgis._core import QgsField from qgis.core import ( QgsWkbTypes, QgsCoordinateReferenceSystem, @@ -34,6 +37,9 @@ QgsProcessingParameterField, QgsProcessingParameterFeatureSource, QgsProcessingParameterEnum, + QgsProcessingFeatureSource, + QgsProcessingContext, + QgsProcessingFeedback, ) from ORStools.common import directions_core, PROFILES, PREFERENCES @@ -45,18 +51,18 @@ class ORSDirectionsPointsLayersAlgo(ORSBaseProcessingAlgorithm): def __init__(self): super().__init__() - self.ALGO_NAME = "directions_from_points_2_layers" - self.GROUP = "Directions" + self.ALGO_NAME: str = "directions_from_points_2_layers" + self.GROUP: str = "Directions" self.MODE_SELECTION: list = ["Row-by-Row", "All-by-All"] - self.IN_START = "INPUT_START_LAYER" - self.IN_START_FIELD = "INPUT_START_FIELD" - self.IN_SORT_START_BY = "INPUT_SORT_START_BY" - self.IN_END = "INPUT_END_LAYER" - self.IN_END_FIELD = "INPUT_END_FIELD" - self.IN_SORT_END_BY = "INPUT_SORT_END_BY" - self.IN_PREFERENCE = "INPUT_PREFERENCE" - self.IN_MODE = "INPUT_MODE" - self.PARAMETERS = [ + self.IN_START: str = "INPUT_START_LAYER" + self.IN_START_FIELD: str = "INPUT_START_FIELD" + self.IN_SORT_START_BY: str = "INPUT_SORT_START_BY" + self.IN_END: str = "INPUT_END_LAYER" + self.IN_END_FIELD: str = "INPUT_END_FIELD" + self.IN_SORT_END_BY: str = "INPUT_SORT_END_BY" + self.IN_PREFERENCE: str = "INPUT_PREFERENCE" + self.IN_MODE: str = "INPUT_MODE" + self.PARAMETERS: list = [ QgsProcessingParameterFeatureSource( name=self.IN_START, description=self.tr("Input Start Point layer"), @@ -111,7 +117,9 @@ def __init__(self): # TODO: preprocess parameters to options the range cleanup below: # https://www.qgis.org/pyqgis/master/core/Processing/QgsProcessingAlgorithm.html#qgis.core.QgsProcessingAlgorithm.preprocessParameters - def processAlgorithm(self, parameters, context, feedback): + def processAlgorithm( + self, parameters: dict, context: QgsProcessingContext, feedback: QgsProcessingFeedback + ) -> Dict[str, str]: ors_client = self._get_ors_client_from_provider(parameters[self.IN_PROVIDER], feedback) profile = dict(enumerate(PROFILES))[parameters[self.IN_PROFILE]] @@ -211,18 +219,25 @@ def sort_end(f): return {self.OUT: dest_id} @staticmethod - def _get_route_dict(source, source_field, sort_start, destination, destination_field, sort_end): + def _get_route_dict( + source: QgsProcessingFeatureSource, + source_field: QgsField, + sort_start, + destination: QgsProcessingFeatureSource, + destination_field: QgsField, + sort_end, + ) -> dict: """ Compute route_dict from input layer. :param source: Input from layer - :type source: QgsProcessingParameterFeatureSource + :type source: QgsProcessingFeatureSource :param source_field: ID field from layer. :type source_field: QgsField :param destination: Input to layer. - :type destination: QgsProcessingParameterFeatureSource + :type destination: QgsProcessingFeatureSource :param destination_field: ID field to layer. :type destination_field: QgsField diff --git a/ORStools/proc/isochrones_layer_proc.py b/ORStools/proc/isochrones_layer_proc.py index 0ce3bc53..8577ee6b 100644 --- a/ORStools/proc/isochrones_layer_proc.py +++ b/ORStools/proc/isochrones_layer_proc.py @@ -27,6 +27,8 @@ ***************************************************************************/ """ +from typing import Dict + from qgis.core import ( QgsWkbTypes, QgsCoordinateReferenceSystem, @@ -38,6 +40,8 @@ QgsProcessingParameterString, QgsProcessingParameterEnum, QgsProcessingParameterNumber, + QgsProcessingContext, + QgsProcessingFeedback, ) from ORStools.common import isochrones_core, PROFILES, DIMENSIONS, LOCATION_TYPES @@ -52,16 +56,16 @@ def __init__(self): self.ALGO_NAME = "isochrones_from_layer" self.GROUP = "Isochrones" - self.IN_POINTS = "INPUT_POINT_LAYER" - self.IN_FIELD = "INPUT_FIELD" - self.IN_METRIC = "INPUT_METRIC" - self.IN_RANGES = "INPUT_RANGES" - self.IN_KEY = "INPUT_APIKEY" - self.IN_DIFFERENCE = "INPUT_DIFFERENCE" - self.USE_SMOOTHING = "USE_SMOOTHING" - self.IN_SMOOTHING = "INPUT_SMOOTHING" - self.LOCATION_TYPE = "LOCATION_TYPE" - self.PARAMETERS = [ + self.IN_POINTS: str = "INPUT_POINT_LAYER" + self.IN_FIELD: str = "INPUT_FIELD" + self.IN_METRIC: str = "INPUT_METRIC" + self.IN_RANGES: str = "INPUT_RANGES" + self.IN_KEY: str = "INPUT_APIKEY" + self.IN_DIFFERENCE: str = "INPUT_DIFFERENCE" + self.USE_SMOOTHING: str = "USE_SMOOTHING" + self.IN_SMOOTHING: str = "INPUT_SMOOTHING" + self.LOCATION_TYPE: str = "LOCATION_TYPE" + self.PARAMETERS: list = [ QgsProcessingParameterFeatureSource( name=self.IN_POINTS, description=self.tr("Input Point layer"), @@ -113,7 +117,9 @@ def __init__(self): # TODO: preprocess parameters to options the range cleanup below: # https://www.qgis.org/pyqgis/master/core/Processing/QgsProcessingAlgorithm.html#qgis.core.QgsProcessingAlgorithm.prepareAlgorithm - def processAlgorithm(self, parameters, context, feedback): + def processAlgorithm( + self, parameters: dict, context: QgsProcessingContext, feedback: QgsProcessingFeedback + ) -> Dict[str, str]: ors_client = self._get_ors_client_from_provider(parameters[self.IN_PROVIDER], feedback) profile = dict(enumerate(PROFILES))[parameters[self.IN_PROFILE]] @@ -203,7 +209,9 @@ def processAlgorithm(self, parameters, context, feedback): return {self.OUT: self.dest_id} # noinspection PyUnusedLocal - def postProcessAlgorithm(self, context, feedback): + def postProcessAlgorithm( + self, context: QgsProcessingContext, feedback: QgsProcessingFeedback + ) -> Dict[str, str]: """Style polygon layer in post-processing step.""" # processed_layer = self.isochrones.calculate_difference(self.dest_id, context) processed_layer = QgsProcessingUtils.mapLayerFromString(self.dest_id, context) diff --git a/ORStools/proc/isochrones_point_proc.py b/ORStools/proc/isochrones_point_proc.py index 63b2a3cb..d9ced450 100644 --- a/ORStools/proc/isochrones_point_proc.py +++ b/ORStools/proc/isochrones_point_proc.py @@ -27,6 +27,8 @@ ***************************************************************************/ """ +from typing import Dict + from qgis.core import ( QgsWkbTypes, QgsCoordinateReferenceSystem, @@ -35,6 +37,8 @@ QgsProcessingParameterEnum, QgsProcessingParameterPoint, QgsProcessingParameterNumber, + QgsProcessingContext, + QgsProcessingFeedback, ) from ORStools.common import isochrones_core, PROFILES, DIMENSIONS, LOCATION_TYPES @@ -46,16 +50,16 @@ class ORSIsochronesPointAlgo(ORSBaseProcessingAlgorithm): def __init__(self): super().__init__() - self.ALGO_NAME = "isochrones_from_point" - self.GROUP = "Isochrones" - self.IN_POINT = "INPUT_POINT" - self.IN_METRIC = "INPUT_METRIC" - self.IN_RANGES = "INPUT_RANGES" - self.IN_KEY = "INPUT_APIKEY" - self.IN_DIFFERENCE = "INPUT_DIFFERENCE" - self.IN_SMOOTHING = "INPUT_SMOOTHING" - self.LOCATION_TYPE = "LOCATION_TYPE" - self.PARAMETERS = [ + self.ALGO_NAME: str = "isochrones_from_point" + self.GROUP: str = "Isochrones" + self.IN_POINT: str = "INPUT_POINT" + self.IN_METRIC: str = "INPUT_METRIC" + self.IN_RANGES: str = "INPUT_RANGES" + self.IN_KEY: str = "INPUT_APIKEY" + self.IN_DIFFERENCE: str = "INPUT_DIFFERENCE" + self.IN_SMOOTHING: str = "INPUT_SMOOTHING" + self.LOCATION_TYPE: str = "LOCATION_TYPE" + self.PARAMETERS: list = [ QgsProcessingParameterPoint( name=self.IN_POINT, description=self.tr( @@ -99,7 +103,9 @@ def __init__(self): # TODO: preprocess parameters to options the range cleanup below: # https://www.qgis.org/pyqgis/master/core/Processing/QgsProcessingAlgorithm.html#qgis.core.QgsProcessingAlgorithm.preprocessParameters - def processAlgorithm(self, parameters, context, feedback): + def processAlgorithm( + self, parameters: dict, context: QgsProcessingContext, feedback: QgsProcessingFeedback + ) -> Dict[str, str]: ors_client = self._get_ors_client_from_provider(parameters[self.IN_PROVIDER], feedback) profile = dict(enumerate(PROFILES))[parameters[self.IN_PROFILE]] @@ -159,7 +165,7 @@ def processAlgorithm(self, parameters, context, feedback): return {self.OUT: self.dest_id} # noinspection PyUnusedLocal - def postProcessAlgorithm(self, context, feedback): + def postProcessAlgorithm(self, context, feedback) -> Dict[str, str]: """Style polygon layer in post-processing step.""" processed_layer = QgsProcessingUtils.mapLayerFromString(self.dest_id, context) self.isochrones.stylePoly(processed_layer) diff --git a/ORStools/proc/matrix_proc.py b/ORStools/proc/matrix_proc.py index 5a365fb2..7bb98998 100644 --- a/ORStools/proc/matrix_proc.py +++ b/ORStools/proc/matrix_proc.py @@ -27,6 +27,8 @@ ***************************************************************************/ """ +from typing import Dict + from qgis.core import ( QgsWkbTypes, QgsFeature, @@ -36,6 +38,8 @@ QgsProcessingException, QgsProcessingParameterField, QgsProcessingParameterFeatureSource, + QgsProcessingContext, + QgsProcessingFeedback, ) from PyQt5.QtCore import QVariant @@ -49,13 +53,13 @@ class ORSMatrixAlgo(ORSBaseProcessingAlgorithm): def __init__(self): super().__init__() - self.ALGO_NAME = "matrix_from_layers" - self.GROUP = "Matrix" - self.IN_START = "INPUT_START_LAYER" - self.IN_START_FIELD = "INPUT_START_FIELD" - self.IN_END = "INPUT_END_LAYER" - self.IN_END_FIELD = "INPUT_END_FIELD" - self.PARAMETERS = [ + self.ALGO_NAME: str = "matrix_from_layers" + self.GROUP: str = "Matrix" + self.IN_START: str = "INPUT_START_LAYER" + self.IN_START_FIELD: str = "INPUT_START_FIELD" + self.IN_END: str = "INPUT_END_LAYER" + self.IN_END_FIELD: str = "INPUT_END_FIELD" + self.PARAMETERS: list = [ QgsProcessingParameterFeatureSource( name=self.IN_START, description=self.tr("Input Start Point layer"), @@ -82,7 +86,9 @@ def __init__(self): ), ] - def processAlgorithm(self, parameters, context, feedback): + def processAlgorithm( + self, parameters: dict, context: QgsProcessingContext, feedback: QgsProcessingFeedback + ) -> Dict[str, str]: ors_client = self._get_ors_client_from_provider(parameters[self.IN_PROVIDER], feedback) # Get profile value diff --git a/ORStools/proc/provider.py b/ORStools/proc/provider.py index 324b2b58..0a10e029 100644 --- a/ORStools/proc/provider.py +++ b/ORStools/proc/provider.py @@ -44,7 +44,7 @@ class ORStoolsProvider(QgsProcessingProvider): def __init__(self): QgsProcessingProvider.__init__(self) - def unload(self): + def unload(self) -> None: """ Unloads the provider. Any tear-down steps required by the provider should be implemented here. @@ -52,7 +52,7 @@ def unload(self): pass # noinspection PyPep8Naming - def loadAlgorithms(self): + def loadAlgorithms(self) -> None: """ Loads all algorithms belonging to this provider. """ @@ -65,11 +65,11 @@ def loadAlgorithms(self): self.addAlgorithm(ORSMatrixAlgo()) @staticmethod - def icon(): + def icon() -> QIcon: return QIcon(RESOURCE_PREFIX + "icon_orstools.png") @staticmethod - def id(): + def id() -> str: """ Returns the unique provider id, used for identifying the provider. This string should be a unique, short, character only string, eg "qgis" or @@ -78,7 +78,7 @@ def id(): return PLUGIN_NAME.strip() @staticmethod - def name(): + def name() -> str: """ Returns the provider name, which is used to describe the provider within the GUI. @@ -89,7 +89,7 @@ def name(): # noinspection PyPep8Naming @staticmethod - def longName(): + def longName() -> str: """ Returns the a longer version of the provider name, which can include extra details such as version numbers. E.g. "Lastools LIDAR tools diff --git a/ORStools/utils/configmanager.py b/ORStools/utils/configmanager.py index 0005e0c4..e8d1c534 100644 --- a/ORStools/utils/configmanager.py +++ b/ORStools/utils/configmanager.py @@ -34,7 +34,7 @@ from ORStools import CONFIG_PATH -def read_config(): +def read_config() -> dict: """ Reads config.yml from file and returns the parsed dict. @@ -47,7 +47,7 @@ def read_config(): return doc -def write_config(new_config): +def write_config(new_config: dict) -> None: """ Dumps new config @@ -58,7 +58,7 @@ def write_config(new_config): yaml.safe_dump(new_config, f) -def write_env_var(key, value): +def write_env_var(key: str, value: str) -> None: """ Update quota env variables diff --git a/ORStools/utils/convert.py b/ORStools/utils/convert.py index 88de03db..1b6ab08f 100644 --- a/ORStools/utils/convert.py +++ b/ORStools/utils/convert.py @@ -28,7 +28,7 @@ """ -def decode_polyline(polyline, is3d=False): +def decode_polyline(polyline: str, is3d: bool = False) -> dict: """Decodes a Polyline string into a GeoJSON geometry. :param polyline: An encoded polyline, only the geometry. diff --git a/ORStools/utils/logger.py b/ORStools/utils/logger.py index cd6bf301..b8926392 100644 --- a/ORStools/utils/logger.py +++ b/ORStools/utils/logger.py @@ -32,7 +32,7 @@ from ORStools import PLUGIN_NAME -def log(message, level_in=0): +def log(message: str, level_in: int = 0): """ Writes to QGIS inbuilt logger accessible through panel. diff --git a/ORStools/utils/maptools.py b/ORStools/utils/maptools.py index ae347d0b..ad0668b5 100644 --- a/ORStools/utils/maptools.py +++ b/ORStools/utils/maptools.py @@ -27,6 +27,7 @@ ***************************************************************************/ """ +from qgis._gui import QgsMapCanvas, QgsMapMouseEvent from qgis.core import QgsWkbTypes from qgis.gui import QgsMapToolEmitPoint, QgsRubberBand @@ -39,7 +40,7 @@ class LineTool(QgsMapToolEmitPoint): """Line Map tool to capture mapped lines.""" - def __init__(self, canvas): + def __init__(self, canvas: QgsMapCanvas) -> None: """ :param canvas: current map canvas :type canvas: QgsMapCanvas @@ -58,7 +59,7 @@ def __init__(self, canvas): self.points = [] self.reset() - def reset(self): + def reset(self) -> None: """reset rubber band and captured points.""" self.points = [] @@ -66,7 +67,7 @@ def reset(self): pointDrawn = pyqtSignal(["QgsPointXY", "int"]) - def canvasReleaseEvent(self, e): + def canvasReleaseEvent(self, e: QgsMapMouseEvent) -> None: """Add marker to canvas and shows line.""" new_point = self.toMapCoordinates(e.pos()) self.points.append(new_point) @@ -75,7 +76,7 @@ def canvasReleaseEvent(self, e): self.pointDrawn.emit(new_point, self.points.index(new_point)) self.showLine() - def showLine(self): + def showLine(self) -> None: """Builds rubber band from all points and adds it to the map canvas.""" self.rubberBand.reset(geometryType=QgsWkbTypes.LineGeometry) for point in self.points: @@ -87,12 +88,12 @@ def showLine(self): doubleClicked = pyqtSignal() # noinspection PyUnusedLocal - def canvasDoubleClickEvent(self, e): + def canvasDoubleClickEvent(self, e: QgsMapMouseEvent) -> None: """Ends line drawing and deletes rubber band and markers from map canvas.""" # noinspection PyUnresolvedReferences self.doubleClicked.emit() self.canvas.scene().removeItem(self.rubberBand) - def deactivate(self): + def deactivate(self) -> None: super(LineTool, self).deactivate() self.deactivated.emit() diff --git a/ORStools/utils/transform.py b/ORStools/utils/transform.py index cd603e02..5fe5c20b 100644 --- a/ORStools/utils/transform.py +++ b/ORStools/utils/transform.py @@ -30,7 +30,7 @@ from qgis.core import QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsProject -def transformToWGS(old_crs): +def transformToWGS(old_crs: QgsCoordinateReferenceSystem) -> QgsCoordinateTransform: """ Returns a transformer to WGS84