• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mthh / routingpy / 19018276231

02 Nov 2025 09:17PM UTC coverage: 88.943%. First build
19018276231

Pull #150

github

web-flow
Merge e90838d9f into eb20b436a
Pull Request #150: feat: add geotiff support valhalla isochrones

9 of 9 new or added lines in 1 file covered. (100.0%)

1649 of 1854 relevant lines covered (88.94%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.51
/routingpy/valhalla_attributes.py
1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2021 GIS OPS UG
3
#
4
#
5
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
6
# use this file except in compliance with the License. You may obtain a copy of
7
# the License at
8
#
9
#     http://www.apache.org/licenses/LICENSE-2.0
10
#
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
# License for the specific language governing permissions and limitations under
15
# the License.
16
#
17

18
from enum import Enum
1✔
19
from typing import List, Optional, Tuple, Union
1✔
20

21
from .utils import decode_polyline6
1✔
22

23

24
class MatchDiscontinuity(str, Enum):
1✔
25
    BEGIN = "begin"
1✔
26
    END = "end"
1✔
27
    NONE = ""
1✔
28

29

30
class MatchType(str, Enum):
1✔
31
    UNMATCHED = "unmatched"
1✔
32
    MATCHED = "matched"
1✔
33
    INTERPOLATED = "interpolated"
1✔
34
    NONE = ""
1✔
35

36

37
class Traversability(str, Enum):
1✔
38
    FORWARD = "forward"
1✔
39
    BACKWARD = "backward"
1✔
40
    BOTH = "both"
1✔
41
    NONE = ""
1✔
42

43

44
class DrivingSide(str, Enum):
1✔
45
    RIGHT = "right"
1✔
46
    LEFT = "left"
1✔
47
    NONE = ""
1✔
48

49

50
class Sidewalk(str, Enum):
1✔
51
    RIGHT = "right"
1✔
52
    LEFT = "left"
1✔
53
    BOTH = "both"
1✔
54
    NONE = ""
1✔
55

56

57
class Surface(str, Enum):
1✔
58
    PAVED_SMOOTH = "paved_smooth"
1✔
59
    PAVED = "paved"
1✔
60
    PAVED_ROUGH = "paved_rough"
1✔
61
    COMPACTED = "compacted"
1✔
62
    DIRT = "dirt"
1✔
63
    GRAVEL = "gravel"
1✔
64
    PATH = "path"
1✔
65
    IMPASSABLE = "impassable"
1✔
66
    NONE = ""
1✔
67

68

69
class RoadClass(str, Enum):
1✔
70
    MOTORWAY = "motorway"
1✔
71
    TRUCK = "trunk"
1✔
72
    PRIMARY = "primary"
1✔
73
    SECONDARY = "secondary"
1✔
74
    TERTIARY = "tertiary"
1✔
75
    UNCLASSIFIED = "unclassified"
1✔
76
    RESIDENTIAL = "residential"
1✔
77
    SERVICE_OTHER = "service_other"
1✔
78
    NONE = ""
1✔
79

80

81
class Use(str, Enum):
1✔
82
    TRAM = "tram"
1✔
83
    ROAD = "road"
1✔
84
    RAMP = "ramp"
1✔
85
    TURN_CHANNEL = "turn_channel"
1✔
86
    TRACK = "track"
1✔
87
    DRIVEWAY = "driveway"
1✔
88
    ALLEY = "alley"
1✔
89
    PARKING_AISLE = "parking_aisle"
1✔
90
    EMERGENCY_ACCESS = "emergency_access"
1✔
91
    DRIVE_TROUGH = "drive_through"
1✔
92
    CULDESAC = "culdesac"
1✔
93
    CYCLEWAY = "cycleway"
1✔
94
    MOUNTAIN_BIKE = "mountain_bike"
1✔
95
    SIDEWALK = "sidewalk"
1✔
96
    FOOTWAY = "footway"
1✔
97
    STEPS = "steps"
1✔
98
    OTHER = "other"
1✔
99
    RAIL_FERRY = "rail-ferry"
1✔
100
    FERRY = "ferry"
1✔
101
    RAIL = "rail"
1✔
102
    BUS = "bus"
1✔
103
    RAIL_CONNECTION = "rail_connection"
1✔
104
    BUS_CONNECTION = "bus_connnection"
1✔
105
    TRANSIT_CONNECTION = "transit_connection"
1✔
106
    NONE = ""
1✔
107

108

109
class MatchedEdge:
1✔
110
    """
111
    Contains a parsed single line string and its attributes, if specified in the request.
112
    Access via properties ``geometry``, ``distances`` ``durations``, ``costs``, ``edge_ids``, ``statuses``.
113
    """
114

115
    def __init__(self, edge: dict, coords: List[List[float]]):
1✔
116
        self._geometry = coords
1✔
117
        self._traversability: Optional[Traversability] = (
1✔
118
            Traversability(edge.get("traversability", "")) or None
119
        )
120
        self._toll: Optional[bool] = edge.get("toll")
1✔
121
        self._use: Optional[Use] = Use(edge.get("use")) or None
1✔
122
        self._tunnel: Optional[bool] = edge.get("tunnel")
1✔
123
        self._names: Optional[List[str]] = edge.get("names")
1✔
124

125
        driving_side = edge.get("drive_on_right")
1✔
126
        if driving_side is True:
1✔
127
            self._driving_side = DrivingSide("right")
1✔
128
        elif driving_side is False:
×
129
            self._driving_side = DrivingSide("right")
×
130
        else:
131
            self._driving_side = None
×
132

133
        self._roundabout: Optional[bool] = edge.get("roundabout")
1✔
134
        self._bridge: Optional[bool] = edge.get("bridge")
1✔
135
        self._surface: Optional[Surface] = Surface(edge.get("surface", "")) or None
1✔
136
        self._edge_id: Optional[int] = edge.get("id")
1✔
137
        self._osm_way_id: Optional[int] = edge.get("way_id")
1✔
138
        self._speed_limit = edge.get("speed_limit")
1✔
139
        self._cycle_lane = edge.get("cycle_lane")
1✔
140
        self._sidewalk = Sidewalk(edge.get("sidewalk")) or None
1✔
141
        self._lane_count: Optional[int] = edge.get("lane_count")
1✔
142
        self._mean_elevation: Optional[int] = edge.get("mean_elevation")
1✔
143
        self._weighted_grade: Optional[int] = edge.get("weighted_grade")
1✔
144
        self._road_class: Optional[RoadClass] = RoadClass(edge.get("road_class", "")) or None
1✔
145
        self._speed: Optional[int] = edge.get("speed")
1✔
146
        self._length: Optional[int] = edge.get("length")
1✔
147

148
    @property
1✔
149
    def geometry(self) -> List[List[float]]:
1✔
150
        """
151
        The geometry of the edge as [[lon1, lat1], [lon2, lat2]] list.
152
        """
153
        return self._geometry
×
154

155
    @property
1✔
156
    def traversability(self) -> Optional[Traversability]:
1✔
157
        """
158
        The traversability of the edge, one of :class:`Traversability`.
159
        """
160
        return self._traversability
×
161

162
    @property
1✔
163
    def toll(self) -> Optional[bool]:
1✔
164
        """
165
        Is this a toll road?
166
        """
167
        return self._toll
×
168

169
    @property
1✔
170
    def use(self) -> Optional[Use]:
1✔
171
        """
172
        The Use of this edge as :class:`Use`.
173
        """
174
        return self._use
×
175

176
    @property
1✔
177
    def tunnel(self) -> Optional[bool]:
1✔
178
        """
179
        Is this part of a tunnel?
180
        """
181
        return self._tunnel
×
182

183
    @property
1✔
184
    def names(self) -> Optional[List[str]]:
1✔
185
        """
186
        Returns the list of names and aliases.
187
        """
188
        return self._names
×
189

190
    @property
1✔
191
    def driving_side(self) -> Optional[DrivingSide]:
1✔
192
        """
193
        Returns the :class:`DrivingSide` of the road.
194
        """
195
        return self._driving_side
×
196

197
    @property
1✔
198
    def roundabout(self) -> Optional[bool]:
1✔
199
        """
200
        Is this part of a roundabout?
201
        """
202
        return self._roundabout
×
203

204
    @property
1✔
205
    def bridge(self) -> Optional[bool]:
1✔
206
        """
207
        Is this part of a bridge?
208
        """
209
        return self._bridge
×
210

211
    @property
1✔
212
    def surface(self) -> Optional[Surface]:
1✔
213
        """
214
        Returns the :class:`Surface` value for this road.
215
        """
216
        return self._surface
1✔
217

218
    @property
1✔
219
    def edge_id(self) -> Optional[int]:
1✔
220
        """
221
        Returns the edge's GraphId?
222
        """
223
        return self._edge_id
×
224

225
    @property
1✔
226
    def osm_way_id(self) -> Optional[int]:
1✔
227
        """
228
        Returns the way's OSM ID.
229
        """
230
        return self._osm_way_id
×
231

232
    @property
1✔
233
    def speed_limit(self) -> Optional[int]:
1✔
234
        """
235
        The legal speed limit, if available
236
        """
237
        return self._speed_limit
×
238

239
    @property
1✔
240
    def cycle_lane(self) -> Optional[str]:
1✔
241
        """
242
        Returns the type (if any) of bicycle lane along this edge.
243
        """
244
        return self._cycle_lane
×
245

246
    @property
1✔
247
    def sidewalk(self) -> Optional[Sidewalk]:
1✔
248
        """
249
        Returns the :class:`Sidewalk` value for this road.
250
        """
251
        return self._sidewalk
1✔
252

253
    @property
1✔
254
    def lane_count(self) -> Optional[int]:
1✔
255
        """
256
        How many lanes does this road have?
257
        """
258
        return self._lane_count
×
259

260
    @property
1✔
261
    def mean_elevation(self) -> Optional[int]:
1✔
262
        """
263
        The mean elevation of this edge in meters.
264
        """
265
        return self._mean_elevation
×
266

267
    @property
1✔
268
    def weighted_grade(self) -> Optional[float]:
1✔
269
        """
270
        The weighted grade factor. Valhalla manufactures a weighted_grade from elevation data.
271
        It is a measure used for hill avoidance in routing - sort of a relative energy use along
272
        an edge. But since an edge in Valhalla can possibly go up and down over several hills it
273
        might not equate to what most folks think of as grade.
274
        """
275
        return self._weighted_grade
×
276

277
    @property
1✔
278
    def road_class(self) -> Optional[RoadClass]:
1✔
279
        """
280
        Returns the :class:`RoadClass` of this edge.
281
        """
282
        return self._road_class
1✔
283

284
    @property
1✔
285
    def speed(self) -> Optional[int]:
1✔
286
        """
287
        Returns the actual speed of the edge, as used by Valhalla.
288
        """
289
        return self._speed
×
290

291
    @property
1✔
292
    def length(self) -> Optional[int]:
1✔
293
        """
294
        The length of this edge in meters.
295
        """
296
        return self._length
×
297

298
    def __repr__(self):  # pragma: no cover
299
        return "Edge({})".format(", ".join([f"{k[1:]}: {v}" for k, v in vars(self).items() if v]))
300

301

302
class MatchedPoint:
1✔
303
    """
304
    A single matched point
305
    """
306

307
    def __init__(self, point: dict):
1✔
308
        self._geometry: List[float] = [point["lon"], point["lat"]]
1✔
309
        self._match_type = MatchType(point.get("type", "")) or None
1✔
310
        self._dist_along_edge: Optional[float] = point.get("distance_along_edge")
1✔
311
        self._dist_from_input: Optional[int] = point.get("distance_from_trace_point")
1✔
312
        self._edge_index: Optional[int] = point.get("distance_along_edge")
1✔
313
        self._discontinuity: Optional[MatchDiscontinuity] = None
1✔
314
        if point.get("begin_route_discontinuity"):
1✔
315
            self._discontinuity = MatchDiscontinuity("begin")
×
316
        elif point.get("end_route_discontinuity"):
1✔
317
            self._discontinuity = MatchDiscontinuity("end")
×
318

319
    @property
1✔
320
    def geometry(self) -> Union[Tuple[float, float], List[float]]:
1✔
321
        """
322
        The geometry of the point as [lon1, lat1] list.
323
        """
324
        return self._geometry
×
325

326
    @property
1✔
327
    def match_type(self) -> MatchType:
1✔
328
        """
329
        Returns the match type.
330
        """
331
        return self._match_type
1✔
332

333
    @property
1✔
334
    def distance_along_edge(self) -> Optional[float]:
1✔
335
        """
336
        Returns the relative distance along the matched edge. E.g. if the point
337
        projects to the center of an edge, the return value will be 0.5.
338
        """
339
        return self._dist_along_edge
×
340

341
    @property
1✔
342
    def distance_from_input(self) -> Optional[int]:
1✔
343
        """
344
        Returns the distance of the matched point from the corresponding input location.
345
        """
346
        return self._dist_from_input
×
347

348
    @property
1✔
349
    def edge_index(self) -> Optional[int]:
1✔
350
        """
351
        Returns the edge index.
352
        """
353
        return self._edge_index
1✔
354

355
    @property
1✔
356
    def discontinuity(self) -> Optional[MatchDiscontinuity]:
1✔
357
        """
358
        Returns the :class:`MatchDiscontinuity` status.
359
        """
360
        return self._discontinuity
×
361

362

363
class MatchedResults:
1✔
364
    """
365
    Contains a list of :class:`Expansion`, which can be iterated over or accessed by index. The property ΒΈ`raw`` contains
366
    the complete raw response of the expansion request.
367
    """
368

369
    def __init__(self, response: Optional[dict] = None):
1✔
370
        self._edges: List[MatchedEdge] = list()
1✔
371
        self._points: List[MatchedPoint] = list()
1✔
372
        self._raw = response
1✔
373

374
        if not response:
1✔
375
            return
×
376

377
        geometry = decode_polyline6(response["shape"])
1✔
378
        # fill the edges
379
        for edge in response["edges"]:
1✔
380
            coords: List[List[float]] = geometry[
1✔
381
                response.get("begin_shape_index")
382
                or 0 : (response.get("end_shape_index") or (len(geometry) - 1)) + 1
383
            ]
384
            self._edges.append(MatchedEdge(edge, coords))
1✔
385

386
        # and the nodes
387
        for pt in response["matched_points"]:
1✔
388
            self._points.append(MatchedPoint(pt))
1✔
389

390
    @property
1✔
391
    def raw(self) -> Optional[dict]:
1✔
392
        """
393
        Returns the trace_attribute's raw, unparsed response. For details, consult the documentation
394
         at https://valhalla.readthedocs.io/en/latest/api/map-matching/api-reference/.
395

396
        :rtype: dict or None
397
        """
398
        return self._raw
×
399

400
    @property
1✔
401
    def matched_edges(self) -> Optional[List[MatchedEdge]]:
1✔
402
        """Returns the list of :class:`MatchedEdge`"""
403
        return self._edges
1✔
404

405
    @property
1✔
406
    def matched_points(self) -> Optional[List[MatchedPoint]]:
1✔
407
        """Returns the list of :class:`MatchedEdge`"""
408
        return self._points
1✔
409

410
    def __repr__(self):  # pragma: no cover
411
        if len(self._edges) < 10 and len(self._points) < 10:
412
            return "Expansions({}, {}, {})".format(self._edges, self._points, self.raw)
413
        else:
414
            return "Expansions({}, ..., {}; {}, ..., {})".format(
415
                ", ".join([str(e) for e in self._edges[:3]]),
416
                ", ".join(str(e) for e in self._edges[-3:]),
417
                ", ".join([str(e) for e in self._points[:3]]),
418
                ", ".join(str(e) for e in self._points[-3:]),
419
            )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc