Skip to content

Commit

Permalink
Merge branch 'main' into run-pyqt5-to-6-script
Browse files Browse the repository at this point in the history
  • Loading branch information
koebi authored May 17, 2024
2 parents e9ef078 + baa4049 commit d0a633a
Show file tree
Hide file tree
Showing 40 changed files with 5,532 additions and 4,935 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,16 @@ RELEASING:
- QGis crashes when selecting more than two vertices for deletion ([#230](https://github.com/GIScience/orstools-qgis-plugin/issues/230))
- 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))
- Add hint for joining with `Layer ID Field` ([#143](https://github.com/GIScience/orstools-qgis-plugin/issues/143))
- Add option to export order of optimization route points ([#145](https://github.com/GIScience/orstools-qgis-plugin/issues/145))

### Changed
- Rename `Ok` button in configuration window to `Save` for clarification([#241](https://github.com/GIScience/orstools-qgis-plugin/issues/241))

## [1.7.1] - 2024-01-15

Expand Down
7 changes: 4 additions & 3 deletions ORStools/ORStoolsPlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
***************************************************************************/
"""

from qgis.gui import QgisInterface
from qgis.core import QgsApplication, QgsSettings
from qgis.PyQt.QtCore import QTranslator, qVersion, QCoreApplication
import os.path
Expand All @@ -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
Expand All @@ -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()
20 changes: 20 additions & 0 deletions ORStools/common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,26 @@
"INPUT_AVOID_COUNTRIES",
"INPUT_AVOID_POLYGONS",
"INPUT_SMOOTHING",
"EXTRA_INFO",
"CSV_FACTOR",
"CSV_COLUMN",
]

LOCATION_TYPES = ["start", "destination"]

EXTRA_INFOS = [
"steepness",
"suitability",
"surface",
"waytype",
"waycategory",
"tollways",
"traildifficulty",
"osmid",
"roadaccessrestrictions",
"countryinfo",
"green",
"noise",
"csv",
"shadow",
]
16 changes: 12 additions & 4 deletions ORStools/common/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 qgis.PyQt.QtCore import QObject, pyqtSignal
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
94 changes: 73 additions & 21 deletions ORStools/common/directions_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 qgis.PyQt.QtCore import QVariant

from ORStools.utils import convert
from ORStools.utils import convert, logger


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
Expand Down Expand Up @@ -75,12 +75,13 @@ 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,
extra_info: list = [],
) -> QgsFields:
"""
Builds output fields for directions response layer.
Expand All @@ -104,21 +105,32 @@ def get_fields(
"""

fields = QgsFields()
fields.append(QgsField("DIST_KM", QVariant.Double))
fields.append(QgsField("DURATION_H", QVariant.Double))
fields.append(QgsField("PROFILE", QVariant.String))
fields.append(QgsField("PREF", QVariant.String))
fields.append(QgsField("OPTIONS", QVariant.String))
fields.append(QgsField(from_name, from_type))
if not extra_info:
fields.append(QgsField("DIST_KM", QVariant.Double))
fields.append(QgsField("DURATION_H", QVariant.Double))
fields.append(QgsField("PROFILE", QVariant.String))
fields.append(QgsField("PREF", QVariant.String))
fields.append(QgsField("OPTIONS", QVariant.String))
fields.append(QgsField(from_name, from_type))
if not line:
fields.append(QgsField(to_name, to_type))
for info in extra_info:
field_type = QVariant.Int
if info in ["waytype", "surface", "waycategory", "roadaccessrestrictions", "steepness"]:
field_type = QVariant.String
fields.append(QgsField(info.upper(), field_type))

return 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.
Expand Down Expand Up @@ -165,7 +177,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.
Expand Down Expand Up @@ -205,9 +219,10 @@ 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,
extra_info: Optional[list] = None,
) -> dict:
"""
Build default parameters for directions endpoint. Either uses a list of QgsPointXY to create the coordinates
Expand Down Expand Up @@ -239,6 +254,43 @@ def build_default_parameters(
"elevation": True,
"id": None,
"options": options,
"extra_info": extra_info,
}

return params


def get_extra_info_features_directions(response: dict, extra_info_order: list[str]):
extra_info_order = [
key if key != "waytype" else "waytypes" for key in extra_info_order
] # inconsistency in API
response_mini = response["features"][0]
coordinates = response_mini["geometry"]["coordinates"]
feats = list()
extra_info = response_mini["properties"]["extras"]
logger.log(str(extra_info))
extras_list = {i: [] for i in extra_info_order}
for key in extra_info_order:
try:
values = extra_info[key]["values"]
except KeyError:
logger.log(f"{key} is not available as extra_info.")
continue
for val in values:
for i in range(val[0], val[1]):
value = convert.decode_extrainfo(key, val[2])
extras_list[key].append(value)

for i in range(len(coordinates) - 1):
feat = QgsFeature()
qgis_coords = [QgsPoint(x, y, z) for x, y, z in coordinates[i : i + 2]]
feat.setGeometry(QgsGeometry.fromPolyline(qgis_coords))
attrs = list()
for j in extras_list:
extra = extras_list[j]
attr = extra[i]
attrs.append(attr)
feat.setAttributes(attrs)
feats.append(feat)

return feats
22 changes: 16 additions & 6 deletions ORStools/common/isochrones_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
***************************************************************************/
"""

from typing import Any, Generator

from qgis._core import QgsMapLayer
from qgis.core import (
QgsPointXY,
QgsFeature,
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
Loading

0 comments on commit d0a633a

Please sign in to comment.