Skip to content

Commit

Permalink
feat: add processing algo for export endpoint (#269)
Browse files Browse the repository at this point in the history
Co-authored-by: Jakob Schnell <[email protected]>
  • Loading branch information
merydian and koebi authored Oct 18, 2024
1 parent 78064b1 commit 8687fe3
Show file tree
Hide file tree
Showing 9 changed files with 257 additions and 18 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ RELEASING:
-->

## Unreleased
### Added
- Processing algorithms for the Network Export endpoint ([#210](https://github.com/GIScience/orstools-qgis-plugin/issues/210))

### Changed
- Use QgsSettings instead of config.yml file to avoid deletion of providers on update ([#108](https://github.com/GIScience/orstools-qgis-plugin/issues/108))
Expand Down
9 changes: 9 additions & 0 deletions ORStools/help/export_network_from_map.help
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Export the base graph for different modes of transport.

You need to have a valid API key ('Web' menu ► 'ORS Tools' ► 'Configuration') or sign up at <a href="https://openrouteservice.org/sign-up/">https://openrouteservice.org/sign-up/</a>.
Current <a href="https://openrouteservice.org/restrictions/">restriction limits</a> for the openrouteservice API apply.

<i>Travel Mode</i>: determines the profile used.

<i>Input Extent</i>: Choose an extent, the content of which will be exported.

7 changes: 7 additions & 0 deletions ORStools/help/export_network_from_map_de.help
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Das Basisgraph für verschiedene Verkehrsmittel exportieren.

Ein gültiger API-Schlüssel ist erforderlich ('Web'-Menü ► 'ORS Tools' ► 'Konfiguration') oder eine Anmeldung unter <a href="https://openrouteservice.org/sign-up/">https://openrouteservice.org/sign-up/</a>. Aktuelle <a href="https://openrouteservice.org/restrictions/">Beschränkungslimits</a> für die openrouteservice API gelten.

<i>Verkehrsmittel</i>: bestimmt das genutzte Reise-Profil

<i>Input-Extent</i>: Es ist ein Bereich auszuwählen, dessen Inhalt exportiert wird.
37 changes: 25 additions & 12 deletions ORStools/i18n/orstools_de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
<context>
<name>@default</name>
<message>
<location filename="../gui/ORStoolsDialog.py" line="122"/>
<location filename="../gui/ORStoolsDialog.py" line="129"/>
<source>About {}</source>
<translation>Über {}</translation>
</message>
<message>
<location filename="../gui/ORStoolsDialog.py" line="105"/>
<location filename="../gui/ORStoolsDialog.py" line="111"/>
<source>&lt;b&gt;ORS Tools&lt;/b&gt; provides access to &lt;a href=&quot;https://openrouteservice.org&quot; style=&quot;color: {0}&quot;&gt;openrouteservice&lt;/a&gt; routing functionalities.&lt;br&gt;&lt;br&gt;&lt;center&gt;&lt;a href=&quot;https://heigit.org/de/willkommen&quot;&gt;&lt;img src=&quot;:/plugins/ORStools/img/logo_heigit_300.png&quot;/&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;&lt;/center&gt;Author: HeiGIT gGmbH&lt;br&gt;Email: &lt;a href=&quot;mailto:Openrouteservice &lt;{1}&gt;&quot;&gt;{1}&lt;/a&gt;&lt;br&gt;Web: &lt;a href=&quot;{2}&quot;&gt;{2}&lt;/a&gt;&lt;br&gt;Repo: &lt;a href=&quot;https://github.com/GIScience/orstools-qgis-plugin&quot;&gt;github.com/GIScience/orstools-qgis-plugin&lt;/a&gt;&lt;br&gt;Version: {3}</source>
<translation>&lt;b&gt;ORS Tools&lt;/b&gt; bietet Zugriff auf &lt;a href=&quot;https://openrouteservice.org&quot; style=&quot;color: {0}&quot;&gt;openrouteservice&lt;/a&gt; Berechnungen.&lt;br&gt;&lt;br&gt;&lt;center&gt;&lt;a href=&quot;https://heigit.org/de/willkommen&quot;&gt;&lt;img src=&quot;:/plugins/ORStools/img/logo_heigit_300.png&quot;/&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;&lt;/center&gt;Author: HeiGIT gGmbH&lt;br&gt;Email: &lt;a href=&quot;mailto:Openrouteservice &lt;{1}&gt;&quot;&gt;{1}&lt;/a&gt;&lt;br&gt;Web: &lt;a href=&quot;{2}&quot;&gt;{2}&lt;/a&gt;&lt;br&gt;Repo: &lt;a href=&quot;https://github.com/GIScience/orstools-qgis-plugin&quot;&gt;github.com/GIScience/orstools-qgis-plugin&lt;/a&gt;&lt;br&gt;Version: {3}</translation>
</message>
Expand Down Expand Up @@ -127,7 +127,7 @@
<translation>Wegpunktoptimierung (sonstige Konfiguration wird nicht berücksichtigt)</translation>
</message>
<message>
<location filename="../proc/directions_points_layer_proc.py" line="298"/>
<location filename="../proc/directions_points_layer_proc.py" line="297"/>
<source>Directions from 1 Point-Layer</source>
<translation>Routenberechnung aus einem Punkt-Layer</translation>
</message>
Expand Down Expand Up @@ -229,6 +229,19 @@ Duplikate entfernen oder Wegpunktoptimierung abwählen.</translation>
<translation>Csv Spalte (benötigt Csv Faktor und csv in Extra Info)</translation>
</message>
</context>
<context>
<name>ORSExportAlgo</name>
<message>
<location filename="../proc/export_proc.py" line="67"/>
<source>Input Extent</source>
<translation>Ausdehnung</translation>
</message>
<message>
<location filename="../proc/export_proc.py" line="179"/>
<source>Export Network from Map</source>
<translation>Netzwerk von Karte exportieren</translation>
</message>
</context>
<context>
<name>ORSIsochronesLayerAlgo</name>
<message>
Expand Down Expand Up @@ -331,12 +344,12 @@ Duplikate entfernen oder Wegpunktoptimierung abwählen.</translation>
<context>
<name>ORStoolsDialog</name>
<message>
<location filename="../gui/ORStoolsDialog.py" line="473"/>
<location filename="../gui/ORStoolsDialog.py" line="481"/>
<source>Apply</source>
<translation>Anwenden</translation>
</message>
<message>
<location filename="../gui/ORStoolsDialog.py" line="474"/>
<location filename="../gui/ORStoolsDialog.py" line="482"/>
<source>Close</source>
<translation>Schließen</translation>
</message>
Expand Down Expand Up @@ -443,7 +456,7 @@ p, li { white-space: pre-wrap; }
<translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; padding: 10px; -qt-block-indent:0; text-indent:0px ; background-color:#e7f2fa; color: #999999&quot;&gt;&lt;img stype=&quot;margin: 10px&quot; src=&quot;:/plugins/ORStools/img/icon_about.png&quot; width=16 height=16 /&gt; Sämtliche Einstellungen werden überschrieben&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
Expand Down Expand Up @@ -696,27 +709,27 @@ p, li { white-space: pre-wrap; }
<context>
<name>ORStoolsDialogMain</name>
<message>
<location filename="../gui/ORStoolsDialog.py" line="178"/>
<location filename="../gui/ORStoolsDialog.py" line="185"/>
<source>Help</source>
<translation>Hilfe</translation>
</message>
<message>
<location filename="../gui/ORStoolsDialog.py" line="170"/>
<location filename="../gui/ORStoolsDialog.py" line="177"/>
<source>Provider Settings</source>
<translation>Dienst-Einstellungen</translation>
</message>
<message>
<location filename="../gui/ORStoolsDialog.py" line="176"/>
<location filename="../gui/ORStoolsDialog.py" line="183"/>
<source>About</source>
<translation>Über</translation>
</message>
<message>
<location filename="../gui/ORStoolsDialog.py" line="329"/>
<location filename="../gui/ORStoolsDialog.py" line="336"/>
<source>Duplicates</source>
<translation>Duplikate</translation>
</message>
<message>
<location filename="../gui/ORStoolsDialog.py" line="329"/>
<location filename="../gui/ORStoolsDialog.py" line="336"/>
<source>
There are duplicate points in the input layer. Traveling Salesman Optimization does not allow this.
Either remove the duplicates or deselect Traveling Salesman.
Expand All @@ -725,7 +738,7 @@ p, li { white-space: pre-wrap; }
Duplikate entfernen oder Wegpunktoptimierung abwählen.</translation>
</message>
<message>
<location filename="../gui/ORStoolsDialog.py" line="338"/>
<location filename="../gui/ORStoolsDialog.py" line="346"/>
<source>The request has been aborted!</source>
<translation>Die Anfrage wurde abgebrochen!</translation>
</message>
Expand Down
1 change: 1 addition & 0 deletions ORStools/i18n/translate.pro
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ SOURCES = ../common/directions_core.py \
../proc/isochrones_layer_proc.py \
../proc/isochrones_point_proc.py \
../proc/matrix_proc.py \
../proc/export_proc.py \
../gui/ORStoolsDialog.py \
../gui/ORStoolsDialogConfig.py

Expand Down
19 changes: 13 additions & 6 deletions ORStools/proc/base_processing_algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,19 @@ def initAlgorithm(self, configuration: Dict) -> None:
Combines default and algorithm parameters and adds them in order to the
algorithm dialog window.
"""
parameters = (
[self.provider_parameter(), self.profile_parameter()]
+ self.PARAMETERS
+ self.option_parameters()
+ [self.output_parameter()]
)
if self.ALGO_NAME not in ["export_network_from_map"]:
parameters = (
[self.provider_parameter(), self.profile_parameter()]
+ self.PARAMETERS
+ self.option_parameters()
+ [self.output_parameter()]
)
else:
parameters = (
[self.provider_parameter(), self.profile_parameter()]
+ self.PARAMETERS
+ [self.output_parameter()]
)
for param in parameters:
if param.name() in ADVANCED_PARAMETERS:
if self.GROUP == "Matrix":
Expand Down
175 changes: 175 additions & 0 deletions ORStools/proc/export_proc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# -*- coding: utf-8 -*-
"""
/***************************************************************************
ORStools
A QGIS plugin
QGIS client to query openrouteservice
-------------------
begin : 2017-02-01
git sha : $Format:%H$
copyright : (C) 2021 by HeiGIT gGmbH
email : [email protected]
***************************************************************************/
This plugin provides access to openrouteservice API functionalities
(https://openrouteservice.org), developed and
maintained by the openrouteservice team of HeiGIT gGmbH, Germany. By using
this plugin you agree to the ORS terms of service
(https://openrouteservice.org/terms-of-service/).
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
"""

from typing import Dict

from qgis.core import (
QgsWkbTypes,
QgsFeature,
QgsField,
QgsFields,
QgsCoordinateReferenceSystem,
QgsProcessingParameterExtent,
QgsProcessingParameterFeatureSink,
QgsProcessingContext,
QgsProcessingFeedback,
QgsPointXY,
QgsGeometry,
)

from qgis.PyQt.QtCore import QVariant


from ORStools.common import PROFILES
from ORStools.utils import exceptions, logger
from .base_processing_algorithm import ORSBaseProcessingAlgorithm


# noinspection PyPep8Naming
class ORSExportAlgo(ORSBaseProcessingAlgorithm):
def __init__(self):
super().__init__()
self.ALGO_NAME: str = "export_network_from_map"
self.GROUP: str = "Export"
self.IN_EXPORT: str = "INPUT_EXPORT"
self.OUT_POINT = "OUTPUT_POINT"
self.PARAMETERS: list = [
QgsProcessingParameterExtent(
name=self.IN_EXPORT,
description=self.tr("Input Extent"),
),
QgsProcessingParameterFeatureSink(
name=self.OUT_POINT,
description="Node Export",
),
]

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
profile = dict(enumerate(PROFILES))[parameters[self.IN_PROFILE]]

target_crs = QgsCoordinateReferenceSystem("EPSG:4326")
rect = self.parameterAsExtent(parameters, self.IN_EXPORT, context, crs=target_crs)

extent = [[rect.xMinimum(), rect.yMinimum()], [rect.xMaximum(), rect.yMaximum()]]

params = {
"bbox": extent,
"id": "export_request",
}

(sink_line, dest_id_line) = self.parameterAsSink(
parameters,
self.OUT,
context,
self.get_fields_line(),
QgsWkbTypes.Type.LineString,
QgsCoordinateReferenceSystem.fromEpsgId(4326),
)

(sink_point, dest_id_point) = self.parameterAsSink(
parameters,
self.OUT_POINT,
context,
self.get_fields_point(),
QgsWkbTypes.Type.Point,
QgsCoordinateReferenceSystem.fromEpsgId(4326),
)

# Make request and catch ApiError
try:
response = ors_client.request("/v2/export/" + profile, {}, post_json=params)
nodes_dict = {item["nodeId"]: item["location"] for item in response["nodes"]}
edges = response["edges"]
for edge in edges:
from_id = edge["fromId"]
to_id = edge["toId"]
weight = edge["weight"]

to_coords = nodes_dict[to_id]
from_coords = nodes_dict[from_id]

geometry = QgsGeometry.fromPolylineXY(
[
QgsPointXY(from_coords[0], from_coords[1]),
QgsPointXY(to_coords[0], to_coords[1]),
]
)

feat = QgsFeature()
feat.setGeometry(geometry)
feat.setAttributes([from_id, to_id, weight])
sink_line.addFeature(feat)

unique_coordinates = {
tuple(item["location"]): item["nodeId"] for item in response["nodes"]
}
points = [(coords, node_id) for coords, node_id in unique_coordinates.items()]
for item in points:
point = QgsPointXY(item[0][0], item[0][1])
point_geometry = QgsGeometry.fromPointXY(point)

point_feat = QgsFeature()
point_feat.setGeometry(point_geometry)
point_feat.setAttributes([item[1]])
sink_point.addFeature(point_feat)

except (exceptions.ApiError, exceptions.InvalidKey, exceptions.GenericServerError) as e:
msg = f"{e.__class__.__name__}: {str(e)}"
feedback.reportError(msg)
logger.log(msg)

return {self.OUT: dest_id_line, self.OUT_POINT: dest_id_point}

@staticmethod
def get_fields_line():
fields = QgsFields()
fields.append(QgsField("FROM_ID", QVariant.Double))
fields.append(QgsField("TO_ID", QVariant.Double))
fields.append(QgsField("WEIGHT", QVariant.Double))

return fields

@staticmethod
def get_fields_point():
fields = QgsFields()
fields.append(QgsField("ID", QVariant.Int))

return fields

def displayName(self) -> str:
"""
Algorithm name shown in QGIS toolbox
:return:
"""
return self.tr("Export Network from Map")
2 changes: 2 additions & 0 deletions ORStools/proc/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from .directions_lines_proc import ORSDirectionsLinesAlgo
from .directions_points_layer_proc import ORSDirectionsPointsLayerAlgo
from .directions_points_layers_proc import ORSDirectionsPointsLayersAlgo
from .export_proc import ORSExportAlgo
from .isochrones_layer_proc import ORSIsochronesLayerAlgo
from .isochrones_point_proc import ORSIsochronesPointAlgo
from .matrix_proc import ORSMatrixAlgo
Expand Down Expand Up @@ -63,6 +64,7 @@ def loadAlgorithms(self) -> None:
self.addAlgorithm(ORSIsochronesLayerAlgo())
self.addAlgorithm(ORSIsochronesPointAlgo())
self.addAlgorithm(ORSMatrixAlgo())
self.addAlgorithm(ORSExportAlgo())

@staticmethod
def icon() -> QIcon:
Expand Down
Loading

0 comments on commit 8687fe3

Please sign in to comment.