Browse Source

add isochrones_join algo

copies the input layer, but applies isochrone geometry\nalso allows application of range for each feature via column name
feature/processing_qgs2
Norwin Roosen 4 months ago
parent
commit
50f2f4ee20
3 changed files with 184 additions and 17 deletions
  1. 28
    17
      core/isochrones.py
  2. 154
    0
      osmtools_processing/isochrones_join.py
  3. 2
    0
      osmtools_processing/provider.py

+ 28
- 17
core/isochrones.py View File

@@ -78,6 +78,28 @@ def requestFromPoint(client, point, metric, ranges, profile):
78 78
     return client.request(ISOCHRONES_API, params)
79 79
 
80 80
 
81
+def featuresFromResponses(responses):
82
+    feats = []
83
+    for response in responses:
84
+        profile = response['info']['query']['profile']
85
+        metric = response['info']['query']['range_type']
86
+
87
+        resFeats = sorted(response['features'], key=lambda x: x['properties']['value'], reverse=True)
88
+        for isochrone in resFeats:
89
+            feat = QgsFeature()
90
+            coordinates = isochrone['geometry']['coordinates']
91
+            points = [QgsPoint(x, y) for x, y in coordinates[0]]
92
+            feat.setGeometry(QgsGeometry.fromPolygon([points]))
93
+            iso_value = isochrone['properties']['value']
94
+            feat.setAttributes([
95
+                #isochrone['properties']['group_index'],
96
+                float(iso_value) / 60 if metric == 'time' else None,
97
+                iso_value if metric == 'distance' else None,
98
+                profile
99
+            ])
100
+            feats.append(feat)
101
+    return feats
102
+
81 103
 def layerFromRequests(responses, layer=None):
82 104
     """
83 105
     Parses a list of ORS isochrones JSON responses into polygons and
@@ -111,23 +133,11 @@ def layerFromRequests(responses, layer=None):
111 133
 
112 134
     # Sort features based on the isochrone value, so that longest isochrone
113 135
     # is added first. This will plot the isochrones on top of each other.
114
-    l = lambda x: x['properties']['value']
115
-    for response in responses:
116
-        profile = response['info']['query']['profile']
117
-        metric = response['info']['query']['range_type']
118
-
119
-        for isochrone in sorted(response['features'], key=l, reverse=True):
120
-            feat = QgsFeature()
121
-            coordinates = isochrone['geometry']['coordinates']
122
-            iso_value = isochrone['properties']['value']
123
-            qgis_coords = [QgsPoint(x, y) for x, y in coordinates[0]]
124
-            feat.setGeometry(QgsGeometry.fromPolygon([qgis_coords]))
125
-            feat.setAttributes([float(iso_value) / 60 if metric == 'time' else None,
126
-                                iso_value if metric == 'distance' else None,
127
-                                profile])
128
-            poly_out.dataProvider().addFeatures([feat])
129
-
136
+    feats = featuresFromResponses(responses)
137
+    poly_out.dataProvider().addFeatures(feats)
130 138
     poly_out.updateExtents()
139
+
140
+    metric = responses[0]['info']['query']['range_type']
131 141
     _stylePoly(poly_out, metric)
132 142
     return poly_out
133 143
 
@@ -174,6 +184,7 @@ def _stylePoly(layer, metric):
174 184
         # replace default symbol layer with the configured one
175 185
         if symbol_layer is not None:
176 186
             symbol.changeSymbolLayer(0, symbol_layer)
187
+        symbol.setAlpha(0.8)
177 188
 
178 189
         # entry for the list of category items
179 190
         legendtext = "{:.2f}".format(unique_value) + legend_suffix if unique_value else ''
@@ -186,6 +197,6 @@ def _stylePoly(layer, metric):
186 197
     # assign the created renderer to the layer
187 198
     if renderer is not None:
188 199
         layer.setRendererV2(renderer)
189
-    layer.setLayerTransparency(50)
200
+    layer.setLayerTransparency(30)
190 201
 
191 202
     layer.triggerRepaint()

+ 154
- 0
osmtools_processing/isochrones_join.py View File

@@ -0,0 +1,154 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+"""
4
+/***************************************************************************
5
+ OSMtools processing
6
+                             -------------------
7
+        begin                : 2017-02-25
8
+        copyright            : (C) 2017 by Norwin Roosen
9
+        email                : github.com/noerw
10
+        git sha              : $Format:%H$
11
+ ***************************************************************************/
12
+
13
+/***************************************************************************
14
+ *                                                                         *
15
+ *   This program is free software; you can redistribute it and/or modify  *
16
+ *   it under the terms of the GNU General Public License as published by  *
17
+ *   the Free Software Foundation; either version 2 of the License, or     *
18
+ *   (at your option) any later version.                                   *
19
+ *                                                                         *
20
+ ***************************************************************************/
21
+"""
22
+
23
+__author__ = 'Norwin Roosen'
24
+__date__ = '2018-02-25'
25
+__copyright__ = '(C) 2018 by Norwin Roosen'
26
+
27
+# This will get replaced with a git SHA1 when you do a git archive
28
+
29
+__revision__ = '$Format:%H$'
30
+
31
+from os.path import dirname, join
32
+from PyQt4.QtGui import QIcon
33
+from qgis.core import (
34
+    QGis,
35
+    QgsCoordinateReferenceSystem,
36
+    QgsCoordinateTransform,
37
+    QgsField,
38
+    QgsFeature,
39
+)
40
+
41
+from processing import features, runalg
42
+from processing.core.GeoAlgorithm import GeoAlgorithm
43
+from processing.core.parameters import (
44
+    ParameterVector,
45
+    ParameterString,
46
+    ParameterBoolean
47
+)
48
+from processing.core.outputs import OutputVector
49
+from processing.tools.vector import VectorWriter
50
+from processing.tools.dataobjects import getObjectFromUri, getTempFilename
51
+
52
+from OSMtools.core.exceptions import InvalidParameterException
53
+from OSMtools.core.client import Client
54
+from OSMtools.core.isochrones import (
55
+    ISOCHRONES_METRICS,
56
+    ISOCHRONES_PROFILES,
57
+    requestFromPoint,
58
+    featuresFromResponses,
59
+    _stylePoly
60
+)
61
+
62
+class IsochronesJoinGeoAlg(GeoAlgorithm):
63
+    """
64
+    GeoAlgorithm fetching isochrone polygons for a point layer via ORS
65
+    """
66
+
67
+    # these names will be visible in console & outputs
68
+    IN_POINTS = 'INPUT_POINTS'
69
+    IN_PROFILE = 'INPUT_PROFILE'
70
+    IN_METRIC = 'INPUT_METRIC'
71
+    IN_RANGE = 'INPUT_RANGE'
72
+    IN_KEY    = 'INPUT_APIKEY'
73
+    OUT = 'OUTPUT'
74
+
75
+    def getIcon(self):
76
+        return QIcon(join(dirname(__file__), '../icon.png'))
77
+
78
+    def defineCharacteristics(self):
79
+        self.name = 'ORS Isochrones Join To Layer'
80
+        self.group = 'API'
81
+
82
+        self.addParameter(ParameterVector(self.IN_POINTS,
83
+                                          self.tr('Central Points Layer'),
84
+                                          ParameterVector.VECTOR_TYPE_POINT))
85
+        self.addParameter(ParameterString(self.IN_PROFILE,
86
+                                          ' '.join([self.tr('Profile'), str(ISOCHRONES_PROFILES)]),
87
+                                          ISOCHRONES_PROFILES[0]))
88
+        self.addParameter(ParameterString(self.IN_METRIC,
89
+                                          ' '.join([self.tr('Metric'), str(ISOCHRONES_METRICS)]),
90
+                                          ISOCHRONES_METRICS[0]))
91
+        self.addParameter(ParameterString(self.IN_RANGE,
92
+                                          self.tr('Range (seconds or meters), alternatively fieldname to extract range from.'),
93
+                                          '180'))
94
+        self.addParameter(ParameterString(self.IN_KEY,
95
+                                          self.tr('API Key (can be omitted if set in config.yml)'),
96
+                                          optional=True))
97
+
98
+        self.addOutput(OutputVector(self.OUT, self.tr('Isochrones')))
99
+
100
+    def processAlgorithm(self, progress):
101
+        progress.setPercentage(0)
102
+        progress.setInfo('Initializing')
103
+
104
+        apiKey = self.getParameterValue(self.IN_KEY)
105
+        profile = self.getParameterValue(self.IN_PROFILE)
106
+        metric = self.getParameterValue(self.IN_METRIC)
107
+        dist = self.getParameterValue(self.IN_RANGE)
108
+        distFromField = None
109
+        try:
110
+            dist = int(dist)
111
+        except ValueError: # a string, so we try a fieldname
112
+            distFromField = dist
113
+
114
+        client = Client(None, apiKey)
115
+        pointLayer = getObjectFromUri(self.getParameterValue(self.IN_POINTS))
116
+
117
+        # ORS understands WGS84 only, so we convert all points before sending
118
+        # don't use auxiliary.checkCRS(), bc we don't want any GUI dependencies in processing
119
+        inCrs = pointLayer.crs()
120
+        outCrs = QgsCoordinateReferenceSystem(4326)
121
+        toWgs = QgsCoordinateTransform(inCrs, outCrs)
122
+        fromWgs = QgsCoordinateTransform(outCrs, inCrs)
123
+
124
+        processedFeatureCount = 0
125
+        totalFeatureCount = pointLayer.featureCount()
126
+        totalFeatureCount = 100.0 / max(totalFeatureCount, 1)
127
+
128
+        output = self.getOutputFromName(self.OUT)
129
+        writer = output.getVectorWriter(pointLayer.fields(), QGis.WKBPolygon, inCrs)
130
+
131
+        progress.setInfo('Requesting each selected point')
132
+        responses = []
133
+        for feature in features(pointLayer):
134
+            feature.geometry().transform(toWgs)
135
+            point = feature.geometry().asPoint()
136
+            try:
137
+                ranges = [feature[distFromField] if distFromField else dist]
138
+            except KeyError:
139
+                raise InvalidParameterException(self.IN_RANGE, distFromField)
140
+
141
+            response = requestFromPoint(client, point, metric, ranges, profile)
142
+            isochrone = featuresFromResponses([response])[0]
143
+
144
+            outFeat = QgsFeature()
145
+            outFeat.setAttributes(feature.attributes())
146
+            outFeat.setGeometry(isochrone.geometry())
147
+            outFeat.geometry().transform(fromWgs)
148
+            writer.addFeature(outFeat)
149
+
150
+            processedFeatureCount += 1
151
+            progress.setPercentage(int(processedFeatureCount * totalFeatureCount))
152
+
153
+
154
+        _stylePoly(output.layer, metric) # apply style to new layer

+ 2
- 0
osmtools_processing/provider.py View File

@@ -25,6 +25,7 @@ from PyQt4.QtGui import QIcon
25 25
 from processing.core.AlgorithmProvider import AlgorithmProvider
26 26
 
27 27
 from OSMtools.osmtools_processing.isochrones import IsochronesGeoAlg
28
+from OSMtools.osmtools_processing.isochrones_join import IsochronesJoinGeoAlg
28 29
 
29 30
 class OSMtoolsAlgoProvider(AlgorithmProvider):
30 31
     """
@@ -39,6 +40,7 @@ class OSMtoolsAlgoProvider(AlgorithmProvider):
39 40
         # Load algorithms
40 41
         self.alglist = [
41 42
             IsochronesGeoAlg(),
43
+            IsochronesJoinGeoAlg(),
42 44
         ]
43 45
 
44 46
         for alg in self.alglist:

Loading…
Cancel
Save