forked from SAUSy-Lab/retro-gtfs
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmap_api.py
More file actions
137 lines (116 loc) · 4.19 KB
/
Copy pathmap_api.py
File metadata and controls
137 lines (116 loc) · 4.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# Map match the GPS track to the street/rail network using OSRM.
# Try altering some parameters if the match is poor.
import requests, json
from conf import conf
from numpy import mean
from shapely.geometry import MultiLineString, asShape
class match(object):
"""map match result object"""
def __init__(self,vehicles):
# initialize some variables
self.vehicles = vehicles
self.confidence = None # average match confidence
self.geom = MultiLineString() # multiline shapely geom
self.error_radius = conf['error_radius']
self.use_times = True # whether times are sent to OSRM
self.response = {} # python-parsed formerly-JSON object
self.is_useable = True # good enough to be used elsewhere?
self.num_attempts = 0
# send the query right away
self.send()
# validate the results - can we likely improve on them?
self.validate()
# output
if self.is_useable:
print '\tconf. is',self.confidence,'on',len(self.response['matchings']),'match(es) after',self.num_attempts,'tries'
else:
print '\tmatching failed'
def send(self):
"""construct the query and send it to OSRM"""
# structure it as API requires
lons, lats, times, radii = [], [], [], []
for v in self.vehicles:
lons.append(v['lon'])
lats.append(v['lat'])
times.append( int( round( v['time'] ) ) )
coords = ';'.join( [str(lon)+','+str(lat) for (lon,lat) in zip(lons,lats)] )
times = ';'.join( [str(time) for time in times] )
radii = ';'.join( [str(int(round(self.error_radius)))]*len(lons) )
# construct and send the request
options = {
'radiuses':radii,
'steps':'false',
'geometries':'geojson',
'annotations':'false',
'overview':'full',
'gaps':'ignore', # don't split based on time gaps - shouldn't be any
'tidy':'true',
'generate_hints':'false'
}
# optionally include timestamps
if self.use_times:
options['timestamps'] = times
# make the request
raw_response = requests.get(
conf['OSRMserver']['url']+'/match/v1/transit/'+coords,
params=options,
timeout=conf['OSRMserver']['timeout']
)
# parse the result to a python object
self.response = json.loads(raw_response.text)
# note the attempt
self.num_attempts += 1
def validate(self):
"""if improved matches are possible, try to make them"""
while self.may_be_improved():
self.error_radius *= 1.5
self.send()
def may_be_improved(self):
"""can this match likely be improved by anything we can control here?"""
# check for error codes in the response
if self.response['code'] != 'Ok':
self.is_useable = False
return False
# estimate the match(es) confidence
confidences = [ m['confidence'] for m in self.response['matchings'] ]
self.confidence = mean(confidences)
# if the confidence is literally zero
if self.confidence == 0:
self.is_useable = False
return False
# # TODO I was testing a thing here, but have left off
return False
# if (
# self.confidence / len(self.response['matchings']) < 0.2
# and self.error_radius < 2*conf['error_radius']
# ):
# return True
# else:
# return False
def geometry(self):
"""return the multi-line geometry from one or more matches"""
# get a list of lists of coords
lines = [asShape(matching['geometry']) for matching in self.response['matchings']]
return MultiLineString(lines)
def vehicles_used(self):
"""for each vehicle, return a boolean list in the same order telling that
vehicle was used to construct the match result"""
# these are the matched points of the input cordinates
# null (None) entries indicate an omitted (outlier) point
tracepoints = self.response['tracepoints']
# true where not none
return [ point is not None for point in tracepoints ]
def cum_distances(self):
"""return the cumulative distances for each vehicle point along the
track, which is based on the leg distances provided by OSRM. Each
leg is just the trip between matched points. Each match has one more
vehicle record associated with it than legs"""
cum_dist = 0
dist_list = []
for matching in self.response['matchings']:
# the first point is at 0 per match
dist_list.append(cum_dist)
for leg in matching['legs']:
cum_dist += leg['distance']
dist_list.append(cum_dist)
return dist_list