From baa40490f617f0117ff096a9c5cde667442612ad Mon Sep 17 00:00:00 2001 From: Jakob Schnell Date: Fri, 17 May 2024 11:10:38 +0200 Subject: [PATCH] feat: add support for `extra_info` parameter (#251) Add extra_info to advanced parameters in directions processing algorithms. When requesting extra_info, a layer is created with one feature per line segment, containing the requested extra_info as attributes. Requested extra_info that is not available (such as OSMID or green atm) will result in an empty attribute column, with a log message accompanying it. The returned layer could be optimized, this is discussed in #252 . Some extra_info is decoded from the API-returned integers into human-readable strings, so different field types are needed. This needs to be updated, if the API ever changes. Currently, not all extra_info where this would be available is covered. The csv extra_info needs csv_column and csv_factor, a corresponding hint is on the latter two parameters Co-authored-by: Merydian --- ORStools/common/__init__.py | 20 +++ ORStools/common/directions_core.py | 59 +++++++- ORStools/i18n/orstools_de.ts | 137 ++++++++++++------ ORStools/proc/directions_lines_proc.py | 54 ++++++- ORStools/proc/directions_points_layer_proc.py | 60 +++++++- .../proc/directions_points_layers_proc.py | 52 ++++++- ORStools/utils/convert.py | 106 +++++++++++++- 7 files changed, 413 insertions(+), 75 deletions(-) diff --git a/ORStools/common/__init__.py b/ORStools/common/__init__.py index f684c4af..2782c156 100644 --- a/ORStools/common/__init__.py +++ b/ORStools/common/__init__.py @@ -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", +] diff --git a/ORStools/common/directions_core.py b/ORStools/common/directions_core.py index 0f523045..9389e49f 100644 --- a/ORStools/common/directions_core.py +++ b/ORStools/common/directions_core.py @@ -33,7 +33,7 @@ from PyQt5.QtCore import QVariant -from ORStools.utils import convert +from ORStools.utils import convert, logger def get_request_point_features(route_dict: dict, row_by_row: str) -> Generator[List, Tuple, None]: @@ -80,6 +80,7 @@ def get_fields( from_name: str = "FROM_ID", to_name: str = "TO_ID", line: bool = False, + extra_info: list = [], ) -> QgsFields: """ Builds output fields for directions response layer. @@ -104,14 +105,20 @@ 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 @@ -215,6 +222,7 @@ def build_default_parameters( 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 @@ -246,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 diff --git a/ORStools/i18n/orstools_de.ts b/ORStools/i18n/orstools_de.ts index 6f672c4c..a3bd32b9 100644 --- a/ORStools/i18n/orstools_de.ts +++ b/ORStools/i18n/orstools_de.ts @@ -9,7 +9,7 @@ <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 {} @@ -59,35 +59,50 @@ ORSDirectionsLinesAlgo - + 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 - + Export order of jobs Reihenfolge exportieren + + + Extra Info + 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) + ORSDirectionsLinesAlgorithm @@ -120,123 +135,153 @@ ORSDirectionsPointsLayerAlgo - + Input (Multi)Point layer Eingabelayer ((Multi)Point) - + Sort Points by Punkte sortieren nach - + Travel preference Routenpräferenz - + Traveling Salesman (omits other configurations) Wegpunktoptimierung (sonstige Konfiguration wird nicht berücksichtigt) - + Directions from 1 Point-Layer Routenberechnung aus einem Punkt-Layer - + Layer ID Field (can be used for joining) ID-Attribut (zum Beispiel für joins) - + Export order of jobs Reihenfolge exportieren + + + Extra Info + 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) + ORSDirectionsPointsLayersAlgo - + Input Start Point layer Startpunkt-Layer wählen - + Start ID Field (can be used for joining) ID-Attribut Startpunkte (für joins nutzbar) - + Sort Start Points by Startpunkte sortieren nach - + Input End Point layer Endpunkt-Layer wählen - + End ID Field (can be used for joining) ID-Attribut Endpunkte (für joins nutzbar) - + Sort End Points by Endpunkte sortieren nach - + Travel preference Routenpräferenz - + Layer mode Zuordnungs-Verfahren - + Directions from 2 Point-Layers Routenberechnung aus zwei Punkt-Layern + + + Extra Info + 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) + ORSIsochronesLayerAlgo - + Input Point layer Eingabelayer (Punkte) - + Input layer ID Field (mutually exclusive with Point option) ID-Attribut (schließt Punkt-Option aus) - + Dimension Dimension - + Comma-separated ranges [min or m] Komma-getrennte Reichweiten [min oder m] - + Isochrones from Point-Layer Isochronen aus Punkt-Layer - + Smoothing factor between 0 [detailed] and 100 [generalized] Glättungsfaktor zwischen 0 [detailliert] und 100 [verallgemeinert] - + Location Type Ortstyp @@ -244,32 +289,32 @@ ORSIsochronesPointAlgo - + Input Point from map canvas (mutually exclusive with layer option) Eingabepunkt aus Kartenansicht (schließt Ebenen-Option aus) - + Dimension Dimension - + Comma-separated ranges [min or m] Komma-getrennte Reichweiten [min oder m] - + Isochrones from Point Isochronen von einzelnem Punkt - + Smoothing factor between 0 [detailed] and 100 [generalized] Glättungsfaktor zwischen 0 [detailliert] und 100 [verallgemeinert] - + Location Type Ortstyp @@ -277,27 +322,27 @@ ORSMatrixAlgo - + Input Start Point layer Startpunkt-Layer wählen - + Start ID Field (can be used for joining) ID-Attribut Startpunkte (für joins nutzbar) - + Input End Point layer Endpunkt-Layer wählen - + End ID Field (can be used for joining) ID-Attribut Endpunkte (für joins nutzbar) - + Matrix from Layers Matrix-Berechnung aus Layer @@ -305,12 +350,12 @@ ORStoolsDialog - + Apply Anwenden - + Close Schließen @@ -670,17 +715,17 @@ p, li { white-space: pre-wrap; } ORStoolsDialogMain - + Help Hilfe - + Provider Settings Dienst-Einstellungen - + About Über diff --git a/ORStools/proc/directions_lines_proc.py b/ORStools/proc/directions_lines_proc.py index 24ec00b4..eed326de 100644 --- a/ORStools/proc/directions_lines_proc.py +++ b/ORStools/proc/directions_lines_proc.py @@ -44,12 +44,14 @@ QgsProcessingParameterFeatureSource, QgsProcessingParameterEnum, QgsPointXY, + QgsProcessingParameterNumber, + QgsProcessingParameterString, QgsProcessingFeatureSource, QgsProcessingContext, QgsProcessingFeedback, ) -from ORStools.common import directions_core, PROFILES, PREFERENCES, OPTIMIZATION_MODES +from ORStools.common import directions_core, PROFILES, PREFERENCES, OPTIMIZATION_MODES, EXTRA_INFOS from ORStools.utils import transform, exceptions, logger from .base_processing_algorithm import ORSBaseProcessingAlgorithm from ..utils.processing import get_params_optimize @@ -69,6 +71,9 @@ def __init__(self): self.IN_OPTIMIZE: str = "INPUT_OPTIMIZE" self.IN_MODE: str = "INPUT_MODE" self.EXPORT_ORDER: str = "EXPORT_ORDER" + self.EXTRA_INFO: str = "EXTRA_INFO" + self.CSV_FACTOR: str = "CSV_FACTOR" + self.CSV_COLUMN: str = "CSV_COLUMN" self.PARAMETERS: List = [ QgsProcessingParameterFeatureSource( name=self.IN_LINES, @@ -95,6 +100,27 @@ def __init__(self): defaultValue=None, optional=True, ), + QgsProcessingParameterEnum( + self.EXTRA_INFO, + self.tr("Extra Info"), + options=EXTRA_INFOS, + allowMultiple=True, + optional=True, + ), + QgsProcessingParameterNumber( + self.CSV_FACTOR, + self.tr("Csv Factor (needs Csv Column and csv in Extra Info)"), + type=QgsProcessingParameterNumber.Double, + minValue=0, + maxValue=1, + defaultValue=None, + optional=True, + ), + QgsProcessingParameterString( + self.CSV_COLUMN, + self.tr("Csv Column (needs Csv Factor and csv in Extra Info)"), + optional=True, + ), QgsProcessingParameterBoolean(self.EXPORT_ORDER, self.tr("Export order of jobs")), ] @@ -111,6 +137,13 @@ def processAlgorithm( options = self.parseOptions(parameters, context) + csv_factor = self.parameterAsDouble(parameters, self.CSV_FACTOR, context) + if csv_factor > 0: + options["profile_params"] = {"weightings": {"csv_factor": csv_factor}} + + extra_info = self.parameterAsEnums(parameters, self.EXTRA_INFO, context) + extra_info = [EXTRA_INFOS[i] for i in extra_info] + # Get parameter values source = self.parameterAsSource(parameters, self.IN_LINES, context) @@ -129,7 +162,9 @@ def processAlgorithm( from_name=source_field_name, ) - sink_fields = directions_core.get_fields(**get_fields_options, line=True) + sink_fields = directions_core.get_fields( + **get_fields_options, line=True, extra_info=extra_info + ) (sink, dest_id) = self.parameterAsSink( parameters, @@ -184,17 +219,22 @@ def processAlgorithm( else: params = directions_core.build_default_parameters( - preference, point_list=line, options=options + preference, point_list=line, options=options, extra_info=extra_info ) response = ors_client.request( "/v2/directions/" + profile + "/geojson", {}, post_json=params ) - sink.addFeature( - directions_core.get_output_feature_directions( - response, profile, preference, from_value=field_value + if extra_info: + feats = directions_core.get_extra_info_features_directions(response) + for feat in feats: + sink.addFeature(feat) + else: + sink.addFeature( + directions_core.get_output_feature_directions( + response, profile, preference, from_value=field_value + ) ) - ) except (exceptions.ApiError, exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = f"Feature ID {num} caused a {e.__class__.__name__}:\n{str(e)}" feedback.reportError(msg) diff --git a/ORStools/proc/directions_points_layer_proc.py b/ORStools/proc/directions_points_layer_proc.py index 5d914476..858567d5 100644 --- a/ORStools/proc/directions_points_layer_proc.py +++ b/ORStools/proc/directions_points_layer_proc.py @@ -45,11 +45,13 @@ QgsProcessingParameterFeatureSource, QgsProcessingParameterEnum, QgsPointXY, + QgsProcessingParameterNumber, + QgsProcessingParameterString, QgsProcessingContext, QgsProcessingFeedback, ) -from ORStools.common import directions_core, PROFILES, PREFERENCES, OPTIMIZATION_MODES +from ORStools.common import directions_core, PROFILES, PREFERENCES, OPTIMIZATION_MODES, EXTRA_INFOS from ORStools.utils import transform, exceptions, logger from .base_processing_algorithm import ORSBaseProcessingAlgorithm from ..utils.processing import get_params_optimize @@ -69,6 +71,9 @@ def __init__(self): self.IN_OPTIMIZE: str = "INPUT_OPTIMIZE" self.IN_MODE: str = "INPUT_MODE" self.IN_SORTBY: str = "INPUT_SORTBY" + self.EXTRA_INFO: str = "EXTRA_INFO" + self.CSV_FACTOR: str = "CSV_FACTOR" + self.CSV_COLUMN: str = "CSV_COLUMN" self.EXPORT_ORDER: str = "EXPORT_ORDER" self.PARAMETERS: List = [ QgsProcessingParameterFeatureSource( @@ -103,6 +108,27 @@ def __init__(self): defaultValue=None, optional=True, ), + QgsProcessingParameterEnum( + self.EXTRA_INFO, + self.tr("Extra Info"), + options=EXTRA_INFOS, + allowMultiple=True, + optional=True, + ), + QgsProcessingParameterNumber( + self.CSV_FACTOR, + self.tr("Csv Factor (needs Csv Column and csv in Extra Info)"), + type=QgsProcessingParameterNumber.Double, + minValue=0, + maxValue=1, + defaultValue=None, + optional=True, + ), + QgsProcessingParameterString( + self.CSV_COLUMN, + self.tr("Csv Column (needs Csv Factor and csv in Extra Info)"), + optional=True, + ), QgsProcessingParameterBoolean(self.EXPORT_ORDER, self.tr("Export order of jobs")), ] @@ -119,6 +145,17 @@ def processAlgorithm( options = self.parseOptions(parameters, context) + csv_column = self.parameterAsString(parameters, self.CSV_COLUMN, context) + + csv_factor = self.parameterAsDouble(parameters, self.CSV_FACTOR, context) + if csv_factor > 0: + options["profile_params"] = { + "weightings": {"csv_factor": round(csv_factor, 2), "csv_column": csv_column} + } + + extra_info = self.parameterAsEnums(parameters, self.EXTRA_INFO, context) + extra_info = [EXTRA_INFOS[i] for i in extra_info] + # Get parameter values source = self.parameterAsSource(parameters, self.IN_POINTS, context) @@ -130,7 +167,9 @@ def processAlgorithm( from_name=source_field_name, ) - sink_fields = directions_core.get_fields(**get_fields_options, line=True) + sink_fields = directions_core.get_fields( + **get_fields_options, line=True, extra_info=extra_info + ) (sink, dest_id) = self.parameterAsSink( parameters, @@ -213,17 +252,24 @@ def sort(f): else: params = directions_core.build_default_parameters( - preference, point_list=points, options=options + preference, point_list=points, options=options, extra_info=extra_info ) response = ors_client.request( "/v2/directions/" + profile + "/geojson", {}, post_json=params ) - sink.addFeature( - directions_core.get_output_feature_directions( - response, profile, preference, from_value=from_value + if extra_info: + feats = directions_core.get_extra_info_features_directions( + response, extra_info + ) + for feat in feats: + sink.addFeature(feat) + else: + sink.addFeature( + directions_core.get_output_feature_directions( + response, profile, preference, from_value=from_value + ) ) - ) except (exceptions.ApiError, exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = f"Feature ID {from_value} caused a {e.__class__.__name__}:\n{str(e)}" feedback.reportError(msg) diff --git a/ORStools/proc/directions_points_layers_proc.py b/ORStools/proc/directions_points_layers_proc.py index 10634bb0..c68fc5ce 100644 --- a/ORStools/proc/directions_points_layers_proc.py +++ b/ORStools/proc/directions_points_layers_proc.py @@ -37,12 +37,14 @@ QgsProcessingParameterField, QgsProcessingParameterFeatureSource, QgsProcessingParameterEnum, + QgsProcessingParameterNumber, + QgsProcessingParameterString, QgsProcessingFeatureSource, QgsProcessingContext, QgsProcessingFeedback, ) -from ORStools.common import directions_core, PROFILES, PREFERENCES +from ORStools.common import directions_core, PROFILES, PREFERENCES, EXTRA_INFOS from ORStools.utils import transform, exceptions, logger from .base_processing_algorithm import ORSBaseProcessingAlgorithm @@ -62,6 +64,9 @@ def __init__(self): self.IN_SORT_END_BY: str = "INPUT_SORT_END_BY" self.IN_PREFERENCE: str = "INPUT_PREFERENCE" self.IN_MODE: str = "INPUT_MODE" + self.EXTRA_INFO: str = "EXTRA_INFO" + self.CSV_FACTOR: str = "CSV_FACTOR" + self.CSV_COLUMN: str = "CSV_COLUMN" self.PARAMETERS: list = [ QgsProcessingParameterFeatureSource( name=self.IN_START, @@ -113,6 +118,27 @@ def __init__(self): self.MODE_SELECTION, defaultValue=self.MODE_SELECTION[0], ), + QgsProcessingParameterEnum( + self.EXTRA_INFO, + self.tr("Extra Info"), + options=EXTRA_INFOS, + allowMultiple=True, + optional=True, + ), + QgsProcessingParameterNumber( + self.CSV_FACTOR, + self.tr("Csv Factor (needs Csv Column and csv in Extra Info)"), + type=QgsProcessingParameterNumber.Double, + minValue=0, + maxValue=1, + defaultValue=None, + optional=True, + ), + QgsProcessingParameterString( + self.CSV_COLUMN, + self.tr("Csv Column (needs Csv Factor and csv in Extra Info)"), + optional=True, + ), ] # TODO: preprocess parameters to options the range cleanup below: @@ -130,6 +156,13 @@ def processAlgorithm( options = self.parseOptions(parameters, context) + csv_factor = self.parameterAsDouble(parameters, self.CSV_FACTOR, context) + if csv_factor > 0: + options["profile_params"] = {"weightings": {"csv_factor": csv_factor}} + + extra_info = self.parameterAsEnums(parameters, self.EXTRA_INFO, context) + extra_info = [EXTRA_INFOS[i] for i in extra_info] + # Get parameter values source = self.parameterAsSource(parameters, self.IN_START, context) @@ -176,7 +209,7 @@ def sort_end(f): field_types.update({"from_type": source_field.type()}) if destination_field: field_types.update({"to_type": destination_field.type()}) - sink_fields = directions_core.get_fields(**field_types) + sink_fields = directions_core.get_fields(**field_types, extra_info=extra_info) (sink, dest_id) = self.parameterAsSink( parameters, @@ -194,7 +227,7 @@ def sort_end(f): break params = directions_core.build_default_parameters( - preference, coordinates=coordinates, options=options + preference, coordinates=coordinates, options=options, extra_info=extra_info ) try: @@ -207,11 +240,16 @@ def sort_end(f): logger.log(msg) continue - sink.addFeature( - directions_core.get_output_feature_directions( - response, profile, preference, from_value=values[0], to_value=values[1] + if extra_info: + feats = directions_core.get_extra_info_features_directions(response) + for feat in feats: + sink.addFeature(feat) + else: + sink.addFeature( + directions_core.get_output_feature_directions( + response, profile, preference, from_value=values[0], to_value=values[1] + ) ) - ) counter += 1 feedback.setProgress(int(100.0 / route_count * counter)) diff --git a/ORStools/utils/convert.py b/ORStools/utils/convert.py index 1b6ab08f..eef4d8ab 100644 --- a/ORStools/utils/convert.py +++ b/ORStools/utils/convert.py @@ -28,7 +28,7 @@ """ -def decode_polyline(polyline: str, is3d: bool = False) -> dict: +def decode_polyline(polyline: str, is3d: bool = False) -> list: """Decodes a Polyline string into a GeoJSON geometry. :param polyline: An encoded polyline, only the geometry. @@ -87,3 +87,107 @@ def decode_polyline(polyline: str, is3d: bool = False) -> dict: points.append([round(lng * 1e-5, 6), round(lat * 1e-5, 6)]) return points + + +def decode_extrainfo(extra_info: str, key: int) -> str | int: + waytypes = [ + "Unknown", + "state Road", + "Road", + "Street", + "Path", + "Track", + "Cycleway", + "Footway", + "Ferry", + "Construction", + ] + surfaces = [ + "Unknown", + "Paved", + "Unpaved", + "Asphalt", + "Concrete", + "Cobblestone", + "Metal", + "Wood", + "Compacted Gravel", + "Fine Grave", + "Gravel", + "Dirt", + "Ground", + "Ice", + "Paving Stones", + "Sand", + "Woodchips", + "Grass", + "Grass Paver", + ] + waycategory = ["Ford", "Ferry", "Steps", "Tollways", "Highway"] + restrictions = ["Permissive", "Private", "Delivery", "Destination", "Customers", "No"] + steepness = [ + ">=16% decline", + "10% - <16% decline", + "7% - <10% decline", + "4% - <7% decline", + "1% - <4% decline", + "0% - <1% decline", + "1% - <4% incline", + "4% - <7% incline", + "7% - <10% incline", + "10% - <16% incline", + ">=16% incline", + ] + + match extra_info: + case "waytypes": + try: + return waytypes[key] + except IndexError: + return "Unknown" + case "surface": + try: + return surfaces[key] + except IndexError: + return "Unknown" + case "waycategory": + binary = list(bin(key))[2:] + padding = ["0"] * (len(waycategory) - len(binary)) + padded_binary = padding + binary + category = "" + + for set_bit, value in zip(padded_binary, waycategory): + if set_bit == "1": + category += value + + if category == "": + return "No category" + + return category + case "roadaccessrestrictions": + binary = list(bin(key))[2:] + padding = ["0"] * (len(restrictions) - len(binary)) + padded_binary = padding + binary + restriction = "" + + for set_bit, value in zip(padded_binary, restrictions): + if set_bit == "1": + restriction += value + restriction += " " + + if restriction == "": + return "None" + + return restriction + case "steepness": + # We get values from -5 to 5 here, but our decoded array is 11 values long. + key += 5 + try: + return steepness[key] + except IndexError: + return "No steepness available" + case "traildifficulty": + # TODO: we need to differentiate the profile here… + return key + case _: + return key