def __init__(self, timetable_files, graph_with_knn): # timetable_files = list(timetable_files) self.routes = { ROUTE_KEY(tt['route']): tt['route'] for tt in map(commons.zipjson_load, timetable_files) } # Note: 'datetime64[ms]' appears necessary here to properly parse the JSON # but the resulting datatype is '...[ns]' self.timetables = { ROUTE_KEY(tt['route']): pd.read_json(tt['timetable_df'], dtype='datetime64[ms]').dropna().astype(pd.Timestamp) for tt in map(commons.zipjson_load, timetable_files) } self.stop_pos = { stop['StopUID']: commons.inspect({'StopPosition': ('PositionLat', 'PositionLon')})(stop) for route in self.routes.values() for stop in route['Stops'] } (g, knn) = commons.inspect(('g', 'knn'))(graph_with_knn) self.node_pos = nx.get_node_attributes(g, 'pos') self.bb = BusstopBusser(self.routes, self.stop_pos, self.timetables) self.bw = BusstopWalker(self.stop_pos) self.gw = GraphWalker(g, knn, self.node_pos)
def get_neighbors(knn, x, radius=None, k=None): tree: sklearn.neighbors.BallTree (I, tree) = commons.inspect(('node_ids', 'knn_tree'))(knn) if radius: return [I[j] for j in tree.query_radius([x], r=radius)[0]] if k: return [I[j] for j in tree.query([x], k=k, return_distance=False)[0]] raise ValueError( "Either *radius* or *number of neighbors* should be provided")
def matchratio_ab(motc_route) : # motc_name = motc_route['RouteName']['Zh_tw'] for (dir, stops) in zip_listify(motc_route['Direction'], motc_route['Stops']) : (motc_a, motc_b) = map(commons.inspect({'StopName' : 'Zh_tw'}), [stops[0], stops[-1]]) ab_ratio = (matchratio_stop_names(route_a, motc_a) + matchratio_stop_names(route_b, motc_b)) / 2 assert((0 <= ab_ratio) and (ab_ratio <= 1)) yield (ab_ratio, { 'SubRouteUID' : motc_route['SubRouteUID'], 'Direction' : dir })
def keep_ttfile(fn): return True J = commons.zipjson_load(fn) # "Inner" Kaohsiung bbox = (120.2593, 22.5828, 120.3935, 22.6886) (left, bottom, right, top) = bbox (lat, lon) = map( np.asarray, zip(*map( commons.inspect( {'StopPosition': ('PositionLat', 'PositionLon')}), J['route']['Stops']))) return all([bottom <= lat, lat <= top, left <= lon, lon <= right])
def make_figure(result) -> dict: ax: plt.Axes if ('plt' in result): (fig, ax) = commons.inspect({'plt': ('fig', 'ax')})(result) else: (fig, ax) = plt.subplots() result['plt'] = {'fig': fig, 'ax': ax} result['auto_close_fig'] = commons.UponDel(lambda: plt.close(fig)) ax.cla() ax.set_title("{} ({}%)".format( result['status'], math.floor(100 * result.get('progress', 0)))) if ('waypoints_all' in result): (y, x) = zip(*result['waypoints_all']) ax.plot(x, y, 'o', c='m', markersize=2) if ('geo_path' in result): (y, x) = zip(*result['geo_path']) ax.plot(x, y, 'b--', linewidth=2, zorder=100) return result['plt']
def bus_at_stops(run, stops): # Note: pint does not serialize well Units = pint.UnitRegistry() # Return this if the input seems nonsensical default_result = [None] * len(stops) # These are sparse samples of a bus trajectory candidate_gps = list(map(tuple, run['BusPosition'])) # Timestamps of GPS records as datetime objects candidate_tdt = list(map(dateutil.parser.parse, run['GPSTime'])) # These are fixed platform locations reference_gps = list( commons.inspect({'StopPosition': ('PositionLat', 'PositionLon')})(stop) for stop in stops) # Goal: obtain an estimate ref_guess_tdt of when the bus is nearest to the platforms #print(candidate_gps) #print(run['GPSTime']) #print(candidate_tdt) #print(reference_gps) segments = list(zip(candidate_gps, candidate_gps[1:])) segm_tdt = list(zip(candidate_tdt, candidate_tdt[1:])) M = np.vstack([graph.dist_to_segment(r, s)[0] for s in segments] for r in reference_gps) assert (M.size), "Undefined behavior for missing data" # M contains distances in meters # We expect the bus to travel about 17km/h on average, say 5m/s # So, a penalty of p = 0.1m/s should not make much of a difference, # unless the bus is idling # Penalty rate p = -0.1 # m/s # Penalty matrix P = np.vstack( len(reference_gps) * [np.cumsum([p * (t1 - t0).seconds for (t0, t1) in segm_tdt])]) # Add idling penalty M += P # Make a copy for imshow M_image = M.copy() match = commons.align(M_image) segments = [segments[j] for j in match] segm_tdt = [segm_tdt[j] for j in match] seg_dist = [ graph.dist_to_segment(r, s) for (r, s) in zip(reference_gps, segments) ] ref_guess_tdt = [ t0 + q * (t1 - t0) for ((d, q), (t0, t1)) in zip(seg_dist, segm_tdt) ] if not (PARAM['min_near_run'] <= sum( (d <= PARAM['tail_eta_patch_dist']) for (d, q) in seg_dist)): return default_result try: # Patch ETA at the tails using this travel speed estimate typical_speed = np.median([s for s in run['Speed'] if s ]) * (Units.km / Units.hour) # Look at front and back tails for (direction, traversal) in [(+1, commons.identity), (-1, reversed)]: # Indices and distances tail = [(n, d, d <= PARAM['tail_eta_patch_dist']) for (n, (d, q)) in traversal(list(enumerate(seg_dist)))] # Tail characterized by large distances tail = tail[0:([is_close for (n, d, is_close) in tail].index(True) + 1)] # Indices of the tail tail = [n for (n, d, is_close) in tail] # No tail means there is nothing to patch if not tail: continue # First visited waypoint first = tail.pop() # Patch ETA for prior waypoints for n in tail: if (typical_speed == 0): ref_guess_tdt[n] = None else: td = dt.timedelta( seconds=(commons.geodesic(reference_gps[n], reference_gps[first]) * Units.meter / typical_speed).to(Units.second).magnitude) ref_guess_tdt[n] = ref_guess_tdt[first] - direction * td except: print("Warning: Patching tail ETA failed") print(traceback.format_exc()) is_monotone = all( (s < t) for (s, t) in zip(ref_guess_tdt, ref_guess_tdt[1:])) #assert(is_monotone), "Time stamps not monotone" # Diagnostics if False: mpl.use('GTK3Agg') import matplotlib.pyplot as plt print("Match:", match) print("Dist wpt to closest seg:", seg_dist) print("ETA (sec):", [ round((t - ref_guess_tdt[0]).total_seconds()) for t in ref_guess_tdt ]) print("Strict monotonicity of ETA:", is_monotone) (fig, ax_array) = plt.subplots(nrows=1, ncols=2) (ax1, ax2) = ax_array ax1.imshow(M_image) (y, x) = zip(*candidate_gps) ax2.plot(x, y, '-') (y, x) = zip(*reference_gps) ax2.plot(x, y, 'bo') for ((wy, wx), ((ay, ax), (by, bx)), (d, q)) in zip(reference_gps, segments, seg_dist): x = (wx, ax + q * (bx - ax)) y = (wy, ay + q * (by - ay)) ax2.plot(x, y, 'r-') while plt.fignum_exists(fig.number): try: plt.pause(0.1) except: break plt.close(fig) # Sanity check assert (len(ref_guess_tdt) == len(stops) ), "Stop and ETA vector length mismatch" # Convert times to UTC timezone ref_guess_tdt = [t.astimezone(dt.timezone.utc) for t in ref_guess_tdt] return ref_guess_tdt
def distill_geopath_ver2(sources): # Processing flowchart: # # geopaths ==> templates --> candidates --> route # ^===============" all_waypoints = set( map(tuple, chain.from_iterable(src['waypoints_used'] for src in sources))) geopaths = [tuple(src['geo_path']) for src in sources] if (len(geopaths) < 2): raise ValueError("At least two paths are required") # Image of provided route variants and the original waypoints with open( OFILE['progress'].format( stage='candidates_{round:02d}'.format(round=0), ext='png'), 'wb') as fd: maps.write_track_img(waypoints=all_waypoints, tracks=geopaths, fd=fd, mapbox_api_token=PARAM['mapbox_api_token']) # Return for each point in 'pp' its minimal distance to the cloud of points 'cloud' def dist2closest(pp: list, cloud: set): knn = graph.compute_geo_knn(dict(enumerate(cloud)), leaf_size=20)['knn_tree'] return [ np.min(knn.query(np.asarray(p).reshape(1, -1), k=1)[0]) for p in pp ] from collections import Counter # Returns a dictionary # Directed edge --> Counter of possible next points def predictor(paths): from collections import defaultdict node_next = defaultdict(Counter) for p in paths: p = list(p) + [None] for (e, c) in zip(zip(p, p[1:]), p[2:]): node_next[e].update([c]) return dict(node_next) # Estimate path tail from an edge using a predictor def complete(e, node_next, use_most_common=True): node_next = deepcopy(node_next) while node_next.get(e): nn: Counter nn = node_next[e] if use_most_common: a = nn.most_common(1).pop()[0] else: a = random.choices(list(nn.keys()), weights=list(nn.values()), k=1).pop() # Reduce the likelihood of this choice for next time to avoid loops nn[a] *= 0.5 e = (e[1], a) if a: yield a # def get_candidates_from(templates, rounds=0): # # Show all route candidates in one image # with open(OFILE['progress'].format(stage='templates_{round:02d}'.format(round=rounds), ext='png'), 'wb') as fd : # maps.write_track_img(waypoints=[], tracks=templates, fd=fd, mapbox_api_token=PARAM['mapbox_api_token']) if (rounds >= PARAM['candidates_rounds']): return templates if (len(set(templates)) < 2): return templates # commons.logger.info("Launched candidate round {} with {} templates...".format(rounds, len(templates))) node_forw = predictor(templates) node_back = predictor(map(reversed, templates)) # Collect route candidates, some of them multiple times candidates = [] while (len(candidates) < PARAM['candidates_min#']) or ( len(candidates) <= len(set(candidates)) * (1 + PARAM['candidates_oversampling'])): if (len(candidates) >= PARAM['candidates_max#']): break # Pick an edge to extend a route in both directions root_edge = tuple( random.choice( list( chain.from_iterable( zip(gp, gp[1:]) for gp in templates)))) # Extended route candidate = tuple( reversed(list(complete(tuple(reversed(root_edge)), node_back)))) + root_edge + tuple( complete(root_edge, node_forw)) # Record candidate if (len(candidate) >= 2): candidates.append(candidate) assert (len(candidates)), "No route candidates!" # Next hierarchy round return get_candidates_from(candidates, rounds + 1) # COLLECT ALL CANDIDATES REPEATEDLY commons.logger.info("Collecting candidates...") candidates = list( chain.from_iterable( Parallel(n_jobs=PARAM['#jobs'], batch_size=1)( delayed(get_candidates_from)(geopaths) for __ in progressbar(range(PARAM['candidates_all_repeat']))))) # Compute the relative frequency of the candidates, which we interpret as likelihood of being the correct route route_freq = { route: (len(list(g)) / len(candidates)) for (route, g) in groupby(sorted(candidates)) } # Keep only the most frequent candidates route_freq = { r: f for (r, f) in route_freq.items() if (f >= min(sorted(route_freq.values(), reverse=True)[0:10])) } # Q: individual coverage for each set of waypoints? def compute_route_metrics(route): # Subdivide long edges fine_route = refine_georoute(route) # Collect the metrics # commons.logger.debug("Total number of waypoints: {}, length of route candidate: {}".format(len(all_waypoints), len(fine_route))) metrics = { 'path': route, # (*): Less is better # Coverage of waypoints (*) 'covr': np.mean(dist2closest(all_waypoints, fine_route)), # Deviation from waypoints (*) 'miss': np.mean(dist2closest(fine_route, all_waypoints)), # Total length (*) 'dist': sum(commons.geodesic(*e) for e in zip(route, route[1:])), # Total turns, in degrees (*) 'turn': sum( abs(graph.angle(p, q, r)) for (p, q, r) in zip(route, route[1:], route[2:])), } # Final score metrics['CALL'] = PARAM['route_fitness'](metrics) return metrics # COLLECT METRICS FOR EACH CANDIDATE commons.logger.info("Computing candidate metrics...") candidate_metrics = Parallel(n_jobs=PARAM['#jobs'], batch_size=1)( delayed(compute_route_metrics)(route) for route in progressbar(route_freq)) # Rekey by route candidate_metrics = {m['path']: m for m in candidate_metrics} # Additional metrics, in the order of decreasing prior likelihood candidate_metrics = { route: { 'frrk': n, 'freq': route_freq[route], **candidate_metrics[route] } for (n, route ) in enumerate(sorted(route_freq, key=(lambda r: -route_freq[r]))) } # Attach rank by CALL for (r, m) in enumerate( sorted(candidate_metrics.values(), key=commons.inspect('CALL'))): m['rank'] = r # WINNER CANDIDATE route = min(candidate_metrics, key=(lambda r: candidate_metrics[r]['CALL'])) # Display metrics commons.logger.info("Candidates summary:") for m in sorted(candidate_metrics.values(), key=commons.inspect('rank')): commons.logger.info( "Candidate #{rank:02d}: CALL={call:E}, freqrank={frrk}".format( rank=m['rank'], call=m['CALL'], frrk=m['frrk'])) # Make images commons.logger.info("Making candidate images...") for metrics in candidate_metrics.values(): fn = OFILE['progress'].format( stage='candidate_{rank:02d}'.format(rank=metrics['rank']), ext='png') with open(fn, 'wb') as fd: maps.write_track_img(waypoints=[], tracks=[metrics['path']], fd=fd, mapbox_api_token=PARAM['mapbox_api_token']) return route
def mapmatch_runs(scenario, runs): # Road network (main graph component) with nearest-neighbor tree for the nodes g: nx.DiGraph (g, knn) = commons.inspect(['g', 'knn'])(pickle.load( open(IFILE['OSM_graph_file'], 'rb'))['main_component_with_knn']) g = trim_graph_to_busable(g) # Nearest edges computer kne = (lambda q: graph.estimate_kne(g, knn, q, ke=20)) mpl.use('Agg') mpl.rcParams['figure.max_open_warning'] = 100 import matplotlib.pyplot as plt # def make_figure(result) -> dict: ax: plt.Axes if ('plt' in result): (fig, ax) = commons.inspect({'plt': ('fig', 'ax')})(result) else: (fig, ax) = plt.subplots() result['plt'] = {'fig': fig, 'ax': ax} result['auto_close_fig'] = commons.UponDel(lambda: plt.close(fig)) ax.cla() ax.set_title("{} ({}%)".format( result['status'], math.floor(100 * result.get('progress', 0)))) if ('waypoints_all' in result): (y, x) = zip(*result['waypoints_all']) ax.plot(x, y, 'o', c='m', markersize=2) if ('geo_path' in result): (y, x) = zip(*result['geo_path']) ax.plot(x, y, 'b--', linewidth=2, zorder=100) return result['plt'] # def mm_callback(result) -> None: if (result['status'] == "opti"): if (dt.datetime.now() < result.get('nfu', dt.datetime.min)): return # Log into a GPX file if ('waypoints_all' in result): with open(OFILE['progress'].format(ext="gpx"), 'w') as fd: fd.write( graph.simple_gpx(result['waypoints_all'], [result.get('geo_path', [])]).to_xml()) # Save figure make_figure(result) with open(OFILE['progress'].format(ext="png"), 'wb') as fd: fig.savefig(fd, bbox_inches='tight', pad_inches=0) # Next figure update result['nfu'] = dt.datetime.now() + dt.timedelta(seconds=5) # Collect all bus runs runs_by_runid = {run[KEYS.runid]: run for run in runs} # Collect all waypoints waypoints_by_runid = { runid: list(map(tuple, run[KEYS.pos])) for (runid, run) in runs_by_runid.items() } #commons.logger.debug(json.dumps(runs, indent=2)) commons.logger.info("Running mapmatch on {} runs".format(len(runs))) # MAPMATCH RUNS results = graph.mapmatch(waypoints_by_runid, g, kne, knn=knn, callback=None, stubborn=0.2, many_partial=True) for result in results: commons.logger.info("Got mapmatch with waypoints {}".format( result['waypoints_used'])) # The run on which mapmatch operated run = runs_by_runid[result['waypoint_setid']] # Collect initial info about the mapmatch attempt mapmatch_attempt = { k: run[k] for k in [KEYS.routeid, KEYS.dir, KEYS.runid, KEYS.busid] } # Attach a unique identifier for this mapmatch mapmatch_attempt['MapMatchUUID'] = uuid.uuid4().hex # Filename without the extension fn = OFILE['mapmatched'].format( scenario=scenario, routeid=mapmatch_attempt[KEYS.routeid], direction=mapmatch_attempt[KEYS.dir], mapmatch_uuid=mapmatch_attempt['MapMatchUUID'], ext="{ext}") # Copy relevant fields from the mapmatcher result for k in ['waypoints_used', 'path', 'geo_path', 'mapmatcher_version']: mapmatch_attempt[k] = result[k] # Save the result in different formats, in this directory commons.makedirs(fn.format(ext='~~~')) # o) Image try: # Make and save the figure fig = make_figure(result)['fig'] with open(fn.format(ext="png"), 'wb') as fd: fig.savefig(fd, bbox_inches='tight', pad_inches=0) except Exception as e: commons.logger.warning("Could not save figure {} ({})".format( fn.format(ext="png"), e)) # o) JSON try: with open(fn.format(ext="json"), 'w') as fd: json.dump(mapmatch_attempt, fd) except Exception as e: commons.logger.warning( "Failed to write mapmatch file {} ({})".format( fn.format(ext="json"), e)) # o) GPX try: with open(fn.format(ext="gpx"), 'w') as fd: fd.write( graph.simple_gpx(mapmatch_attempt['waypoints_used'], [mapmatch_attempt['geo_path']]).to_xml()) except Exception as e: commons.logger.warning("Failed to write GPX file {} ({})".format( fn.format(ext="gpx"), e)) time.sleep(1)
def mm_callback(result): if (result['status'] == "zero"): print("(Preparing)") if (result['status'] == "init"): print("(Optimizing)") if (result['status'] == "opti"): if (dt.datetime.now() < result.get('nfu', dt.datetime.min)): return if (result['status'] == "done"): print("(Done)") if (result['status'] == "zero"): fig: plt.Figure ax: plt.Axes (fig, ax) = plt.subplots() for (n, (y, x)) in enumerate(result['waypoints']): ax.plot(x, y, 'bo') ax.axis(commons.niceaxis(ax.axis(), expand=1.1)) ax.autoscale(enable=False) # Download the background map try: mapi = maps.get_map_by_bbox(maps.ax2mb(*ax.axis()), token=mapbox_token) except maps.URLError: commons.logger.warning("No map (no connection?)") mapi = None result['plt'] = { 'fig': fig, 'ax': ax, 'map': mapi, 'bbox': ax.axis() } if (result['status'] in ["zero", "opti", "done"]): fig: plt.Figure ax: plt.Axes (fig, ax, mapi, bbox) = commons.inspect({'plt': ('fig', 'ax', 'map', 'bbox')})(result) # Clear the axes ax.cla() # Apply the background map if mapi: img = ax.imshow(mapi, extent=bbox, interpolation='none', zorder=-100) for (n, (y, x)) in enumerate(result['waypoints']): ax.plot(x, y, 'o', c='m', markersize=4) if ('geo_path' in result): (y, x) = zip(*result['geo_path']) ax.plot(x, y, 'b--', linewidth=2, zorder=-50) if ('(edge_clouds)' in result): for (nc, pc) in enumerate(result['(edge_clouds)']): for (e, p) in pc.items(): (y, x) = zip(*[g.nodes[i]['pos'] for i in e]) c = ('g' if (result['(active_edges)'][nc] == e) else 'r') ax.plot(x, y, '-', c=c, linewidth=2, alpha=(p / max(pc.values())), zorder=150) ax.axis(bbox) plt.pause(0.1) if (result['status'] == "opti"): # Next figure update result['nfu'] = dt.datetime.now() + dt.timedelta(seconds=4) if (result['status'] == "done"): open("graph_mm_callback.gpx", 'w').write( simple_gpx(result['waypoints'], [result['geo_path']]).to_xml())
def test_mapmatch(): mapbox_token = open("../.credentials/UV/mapbox-token.txt", 'r').read() osm_graph_file = "../OUTPUT/02/UV/kaohsiung.pkl" print("Loading OSM...") OSM = pickle.load(open(osm_graph_file, 'rb')) print("Retrieving the KNN tree...") # Road network (main graph component) with nearest-neighbor tree for the nodes g: nx.DiGraph (g, knn) = commons.inspect(('g', 'knn'))(OSM['main_component_with_knn']) kne = (lambda q: estimate_kne(g, knn, q, ke=20)) # Try to free up memory del OSM # # # Get some waypoints # routes_file = "../OUTPUT/00/ORIGINAL_MOTC/Kaohsiung/CityBusApi_StopOfRoute.json" # # motc_routes = commons.index_dicts_by_key( # commons.zipjson_load(routes_file), # lambda r: r['RouteUID'], # preserve_singletons=['Direction', 'Stops'] # ) # Waypoints # # (route_id, direction) = ('KHH1221', 0) # # (route_id, direction) = ('KHH29', 0) # # (route_id, direction) = ('KHH38', 0) # # (route_id, direction) = ('KHH87', 1) # # (route_id, direction) = ('KHH121', 1) # (route_id, direction) = ('KHH11', 1) # # # waypoints = list(map(commons.inspect({'StopPosition': ('PositionLat', 'PositionLon')}), motc_routes[route_id]['Stops'][direction])) #waypoints = [(22.622249, 120.368713), (22.621929, 120.367332), (22.622669, 120.367736), (22.623569, 120.366722), (22.624959, 120.364402), (22.625329, 120.36338), (22.625379, 120.362777), (22.62565, 120.361061), (22.62594, 120.359947), (22.62602, 120.354911), (22.62577, 120.351226), (22.625219, 120.34732), (22.62494, 120.3442), (22.624849, 120.34317), (22.62597, 120.342582), (22.626169, 120.344428), (22.62811, 120.344451), (22.62968, 120.33908), (22.63017, 120.337562), (22.63042, 120.336341), (22.631919, 120.331932), (22.632989, 120.327766), (22.632789, 120.325233), (22.632829, 120.324371), (22.633199, 120.32283), (22.633449, 120.321639), (22.63459, 120.31707), (22.636629, 120.314437), (22.63758, 120.308952), (22.6375, 120.307777), (22.637899, 120.301162), (22.63788, 120.298866), (22.637899, 120.297393), (22.63718, 120.294151), (22.636989, 120.293609), (22.6354, 120.288566), (22.635179, 120.287719), (22.634139, 120.284576), (22.632179, 120.28379), (22.631229, 120.283309), (22.628789, 120.28199), (22.62507, 120.28054), (22.624259, 120.282028), (22.622869, 120.284973), (22.62247, 120.285827), (22.623029, 120.286407), (22.62531, 120.28524)] #waypoints = [(22.62269, 120.367767), (22.623899, 120.366409), (22.626039, 120.359397), (22.62615, 120.357887), (22.62602, 120.35337), (22.625059, 120.345809), (22.625989, 120.342529), (22.625999, 120.343856), (22.626169, 120.344413), (22.628049, 120.344436), (22.628969, 120.340843), (22.62993, 120.338348), (22.63025, 120.337356), (22.631309, 120.334068), (22.63269, 120.329841), (22.63307, 120.328491), (22.63297, 120.326713), (22.632949, 120.324851), (22.63385, 120.319831), (22.637609, 120.307678), (22.637609, 120.305633), (22.63762, 120.304847), (22.637859, 120.300231), (22.63796, 120.297439), (22.63787, 120.296707), (22.63739, 120.294357), (22.637079, 120.293472), (22.6359, 120.289939), (22.63537, 120.288353), (22.634149, 120.284728), (22.629299, 120.28228), (22.62652, 120.280738), (22.62354, 120.283637), (22.622549, 120.28572), (22.622999, 120.28627), (22.625379, 120.285156)] #waypoints = [(22.62202, 120.368789), (22.62198, 120.368133), (22.62191, 120.367233), (22.62384, 120.366401), (22.624929, 120.364402), (22.625329, 120.363342), (22.62593, 120.363357), (22.62569, 120.360771), (22.6261, 120.357803), (22.62601, 120.355743), (22.62578, 120.351692), (22.625539, 120.349586), (22.62494, 120.344642), (22.62515, 120.3423), (22.62598, 120.343742), (22.627559, 120.344482), (22.629569, 120.339309), (22.630359, 120.336929), (22.63124, 120.333846), (22.6322, 120.330856), (22.632869, 120.326393), (22.632879, 120.324172), (22.63344, 120.321502), (22.63418, 120.318351), (22.637369, 120.312362), (22.637639, 120.303802), (22.637779, 120.301971), (22.63787, 120.30104), (22.63775, 120.300231), (22.637859, 120.297416), (22.6373, 120.294448), (22.63697, 120.293418), (22.636289, 120.291076), (22.635129, 120.287742), (22.634969, 120.287078), (22.631259, 120.283332), (22.627559, 120.281402), (22.626689, 120.280967), (22.624849, 120.280937), (22.623979, 120.282623), (22.623739, 120.283187), (22.62317, 120.286453), (22.625259, 120.285423)] #waypoints = [(22.62203, 120.368293), (22.62195, 120.367401), (22.624559, 120.36515), (22.624929, 120.364448), (22.62585, 120.363113), (22.625549, 120.36177), (22.6261, 120.357627), (22.625509, 120.349677), (22.62503, 120.345596), (22.62589, 120.342307), (22.627979, 120.344459), (22.628539, 120.34201), (22.629989, 120.33805), (22.63025, 120.337219), (22.63211, 120.331581), (22.633039, 120.328659), (22.63307, 120.327308), (22.63294, 120.326156), (22.632989, 120.323699), (22.63342, 120.321418), (22.63743, 120.310119), (22.63755, 120.305641), (22.637639, 120.304267), (22.636949, 120.293319), (22.6355, 120.289062), (22.63454, 120.285987), (22.63076, 120.283088), (22.62968, 120.282478), (22.627229, 120.281188), (22.62647, 120.280693), (22.62516, 120.280387), (22.624099, 120.282401), (22.622669, 120.285308), (22.62313, 120.286369), (22.625169, 120.285667)] #waypoints = [(22.666889, 120.358613), (22.666389, 120.358893), (22.667886, 120.357973), (22.669096, 120.35728), (22.672, 120.356413), (22.673586, 120.356866), (22.67395, 120.35764), (22.670996, 120.359653), (22.669636, 120.360426), (22.667536, 120.361346), (22.665766, 120.361893), (22.663703, 120.362173), (22.661463, 120.362533), (22.66128, 120.363266), (22.659683, 120.364013), (22.65876, 120.361999), (22.658496, 120.360746), (22.656686, 120.360226), (22.653473, 120.359653), (22.650909, 120.359719), (22.650743, 120.357439), (22.65097, 120.356733), (22.650973, 120.355746), (22.651303, 120.351026), (22.651933, 120.349693), (22.65304, 120.349733), (22.652703, 120.349173), (22.651806, 120.348826), (22.65078, 120.348439), (22.649753, 120.348079), (22.643733, 120.346253), (22.642279, 120.345799), (22.642226, 120.343906), (22.642313, 120.343186), (22.642483, 120.342386), (22.639283, 120.340866), (22.639399, 120.340186), (22.639863, 120.338213), (22.640733, 120.332533), (22.639569, 120.3322), (22.638956, 120.332173), (22.639066, 120.328613), (22.639433, 120.326866), (22.639306, 120.326026), (22.6397, 120.322373), (22.639946, 120.319919), (22.640243, 120.316946), (22.637166, 120.311866), (22.637503, 120.306773), (22.63753, 120.304773), (22.637616, 120.304079), (22.637709, 120.302853), (22.636179, 120.302306), (22.635326, 120.302373), (22.634396, 120.302373), (22.631286, 120.301599), (22.630943, 120.300679), (22.630089, 120.298066), (22.628786, 120.294079), (22.627633, 120.290586), (22.62723, 120.289253), (22.62659, 120.287533), (22.62601, 120.286213), (22.626173, 120.28564), (22.624866, 120.285866), (22.623696, 120.2866), (22.623153, 120.286386), (22.621536, 120.284959), (22.620976, 120.284999)] #waypoints = [(22.666556, 120.358786), (22.66751, 120.35836), (22.668576, 120.357613), (22.672313, 120.356319), (22.673593, 120.356906), (22.665803, 120.361893), (22.663703, 120.362239), (22.662706, 120.362399), (22.659723, 120.363933), (22.658963, 120.363186), (22.658523, 120.360759), (22.6508, 120.357293), (22.65096, 120.356733), (22.650733, 120.355213), (22.650686, 120.352519), (22.652966, 120.34976), (22.645576, 120.346626), (22.643599, 120.346106), (22.641999, 120.345106), (22.642226, 120.343879), (22.642373, 120.343119), (22.642366, 120.341933), (22.640166, 120.341533), (22.639253, 120.341279), (22.639373, 120.34028), (22.639679, 120.338506), (22.640713, 120.332719), (22.63971, 120.33216), (22.638663, 120.332093), (22.638593, 120.331199), (22.638933, 120.32952), (22.639263, 120.326546), (22.63934, 120.325826), (22.639453, 120.324773), (22.639633, 120.323279), (22.63988, 120.320959), (22.640173, 120.317826), (22.637606, 120.3048), (22.637653, 120.304106), (22.637756, 120.302306), (22.636829, 120.302213), (22.636233, 120.302199)] #waypoints = [(22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60642, 120.338256), (22.60651, 120.338188), (22.606119, 120.338569), (22.606109, 120.33834), (22.605649, 120.333862), (22.60655, 120.33345), (22.60831, 120.333213), (22.617879, 120.332069), (22.619409, 120.331848), (22.61968, 120.33184), (22.622409, 120.331458), (22.622329, 120.329803), (22.622289, 120.329437), (22.62141, 120.327056), (22.62124, 120.326507), (22.62095, 120.325752), (22.62008, 120.323219), (22.618999, 120.319877), (22.61882, 120.319168), (22.61853, 120.318038), (22.617969, 120.316169), (22.617969, 120.316169), (22.617319, 120.314498), (22.61683, 120.313072), (22.616478999999998, 120.31208), (22.616478999999998, 120.31208), (22.615419, 120.308982), (22.615159, 120.30812), (22.61498, 120.307182), (22.61498, 120.307182), (22.614429, 120.305999), (22.614429, 120.305999), (22.614099, 120.305229), (22.61411, 120.30516), (22.61375, 120.303573), (22.61359, 120.303199), (22.61359, 120.303199), (22.613389, 120.302856), (22.613109, 120.301681), (22.612779, 120.301078), (22.61264, 120.300666), (22.61255, 120.300239), (22.6123, 120.298408), (22.61313, 120.29811), (22.621259, 120.295913), (22.62136, 120.296142), (22.62174, 120.296768), (22.622539, 120.29956), (22.622659, 120.299919), (22.62294, 120.300529), (22.62302, 120.300811), (22.62351, 120.301116), (22.624648, 120.301299), (22.6268, 120.301521)] waypoints = [(22.60642, 120.338256), (22.60651, 120.338188), (22.606119, 120.338569), (22.606109, 120.33834), (22.605649, 120.333862), (22.60655, 120.33345), (22.60831, 120.333213), (22.617879, 120.332069)] # waypoints = [(22.60642, 120.338256), (22.605649, 120.333862), (22.60655, 120.33345), (22.60831, 120.333213), (22.617879, 120.332069), (22.619409, 120.331848), (22.622409, 120.331458), (22.622329, 120.329803), (22.62141, 120.327056), (22.62095, 120.325752), (22.62008, 120.323219), (22.618999, 120.319877), (22.61882, 120.319168), (22.61853, 120.318038), (22.617969, 120.316169), (22.617319, 120.314498), (22.61683, 120.313072), (22.616478999999998, 120.31208), (22.615419, 120.308982), (22.615159, 120.30812), (22.61498, 120.307182), (22.614429, 120.305999), (22.614099, 120.305229), (22.61375, 120.303573), (22.613389, 120.302856), (22.613109, 120.301681), (22.612779, 120.301078), (22.61255, 120.300239), (22.6123, 120.298408), (22.61313, 120.29811), (22.621259, 120.295913), (22.62174, 120.296768), (22.622539, 120.29956), (22.62294, 120.300529), (22.62351, 120.301116), (22.624648, 120.301299), (22.6268, 120.301521)] # wp1 = (22.61313, 120.29811) # lower # wp2 = (22.621259, 120.295913) # upper # print(waypoints.index(wp1), waypoints.index(wp2)) # 29 30 # waypoints = [(22.591286, 120.305706), (22.593253, 120.305906), (22.59474, 120.305306), (22.597746, 120.304186), (22.596886, 120.305133)] # waypoints = [(22.613109, 120.301681), (22.612779, 120.301078), (22.61255, 120.300239), (22.6123, 120.298408), (22.61313, 120.29811), (22.621259, 120.295913), (22.62174, 120.296768), (22.622539, 120.29956)] # # Off-graph # waypoints = [(23.158953, 120.764319), (23.159556, 120.766213), (23.159126, 120.764453), (23.158566, 120.76336), (23.15574, 120.760866), (23.154659, 120.760319), (23.151839, 120.757533), (23.14992, 120.75664), (23.14823, 120.755399), (23.146363, 120.754159), (23.145453, 120.750733), (23.144263, 120.749773), (23.138233, 120.738666), (23.137466, 120.7296), (23.13557, 120.724706), (23.134416, 120.723146), (23.133646, 120.72244), (23.132079, 120.720439), (23.130616, 120.71916), (23.12967, 120.717573), (23.129436, 120.71688), (23.127803, 120.714626), (23.119859, 120.710013), (23.116743, 120.712706), (23.11632, 120.713466), (23.114913, 120.713279), (23.110603, 120.712013), (23.109433, 120.710333), (23.110526, 120.7086), (23.108769, 120.699866), (23.105703, 120.694919), (23.103096, 120.690733), (23.101586, 120.688879), (23.100159, 120.685826), (23.095286, 120.682013), (23.09337, 120.681706), (23.08988, 120.682866), (23.08783, 120.681039), (23.086396, 120.679666), (23.08473, 120.679426), (23.079733, 120.676773), (23.079039, 120.676333), (23.077429, 120.675373), (23.073783, 120.673479), (23.073319, 120.673133), (23.072413, 120.672933), (23.06987, 120.672719), (23.067569, 120.673386), (23.06427, 120.672773), (23.064096, 120.671453), (23.062183, 120.670893), (23.052396, 120.667506), (23.049193, 120.669039), (23.046186, 120.667866), (23.042326, 120.667413), (23.03734, 120.665693), (23.025093, 120.663466), (23.020016, 120.664373), (23.015033, 120.665293), (23.012633, 120.665346), (23.007923, 120.664519), (23.00765, 120.662826), (23.006326, 120.654946), (23.006913, 120.651799), (23.006509, 120.649106), (23.004886, 120.647413), (23.003593, 120.646879), (22.999726, 120.644453), (22.996513, 120.643013), (22.995793, 120.642453), (22.996606, 120.641493), (22.996259, 120.64068), (22.995889, 120.634906), (22.996983, 120.63436), (22.99773, 120.634213)] # commons.seed() # mapmatch(waypoints, g, kne, None, stubborn=0.2) # commons.seed() # mapmatch(waypoints, g, kne, None, stubborn=0.2) # return # # TODO: abort if nearest edges are too far # TODO: no path to node issue # Includes renderer selection: import matplotlib.pyplot as plt def mm_callback(result): if (result['status'] == "zero"): print("(Preparing)") if (result['status'] == "init"): print("(Optimizing)") if (result['status'] == "opti"): if (dt.datetime.now() < result.get('nfu', dt.datetime.min)): return if (result['status'] == "done"): print("(Done)") if (result['status'] == "zero"): fig: plt.Figure ax: plt.Axes (fig, ax) = plt.subplots() for (n, (y, x)) in enumerate(result['waypoints']): ax.plot(x, y, 'bo') ax.axis(commons.niceaxis(ax.axis(), expand=1.1)) ax.autoscale(enable=False) # Download the background map try: mapi = maps.get_map_by_bbox(maps.ax2mb(*ax.axis()), token=mapbox_token) except maps.URLError: commons.logger.warning("No map (no connection?)") mapi = None result['plt'] = { 'fig': fig, 'ax': ax, 'map': mapi, 'bbox': ax.axis() } if (result['status'] in ["zero", "opti", "done"]): fig: plt.Figure ax: plt.Axes (fig, ax, mapi, bbox) = commons.inspect({'plt': ('fig', 'ax', 'map', 'bbox')})(result) # Clear the axes ax.cla() # Apply the background map if mapi: img = ax.imshow(mapi, extent=bbox, interpolation='none', zorder=-100) for (n, (y, x)) in enumerate(result['waypoints']): ax.plot(x, y, 'o', c='m', markersize=4) if ('geo_path' in result): (y, x) = zip(*result['geo_path']) ax.plot(x, y, 'b--', linewidth=2, zorder=-50) if ('(edge_clouds)' in result): for (nc, pc) in enumerate(result['(edge_clouds)']): for (e, p) in pc.items(): (y, x) = zip(*[g.nodes[i]['pos'] for i in e]) c = ('g' if (result['(active_edges)'][nc] == e) else 'r') ax.plot(x, y, '-', c=c, linewidth=2, alpha=(p / max(pc.values())), zorder=150) ax.axis(bbox) plt.pause(0.1) if (result['status'] == "opti"): # Next figure update result['nfu'] = dt.datetime.now() + dt.timedelta(seconds=4) if (result['status'] == "done"): open("graph_mm_callback.gpx", 'w').write( simple_gpx(result['waypoints'], [result['geo_path']]).to_xml()) print("Calling mapmatch...") plt.ion() commons.seed() for _ in range(2): try: result = mapmatch(waypoints, g, kne, mm_callback, stubborn=0.2) print(result['path']) except MapMatchingError as e: print("Mapmatch failed ({})".format(e)) plt.pause(5) plt.ioff() plt.show() return
def bus_at_stops(run, stops) : # TODO: some candidate_tdt have large time-gaps # These are sparse samples of a bus trajectory candidate_gps = list(zip(run['PositionLat'], run['PositionLon'])) # Timestamps of GPS records as datetime objects candidate_tdt = list(map(dateutil.parser.parse, run['GPSTime'])) # These are fixed platform locations reference_gps = list(commons.inspect({'StopPosition': ('PositionLat', 'PositionLon')})(stop) for stop in stops) # Goal: obtain an estimate ref_guess_tdt of when the bus is nearest to the platforms print(candidate_gps) print(run['GPSTime']) print(candidate_tdt) print(reference_gps) segments = list(zip(candidate_gps[:-1], candidate_gps[1:])) segm_tdt = list(zip(candidate_tdt[:-1], candidate_tdt[1:])) M = np.vstack([dist_to_segment(r, s)[0] for s in segments] for r in reference_gps) if not M.size : return # M contains distances in meters # We expect the bus to travel about 17km/h on average, say 5m/s # So, a penalty of p = 0.1m/s should not make much of a difference, # unless the bus is idling # Penalty rate p = 0.1 # m/s # Penalty matrix P = np.vstack(len(reference_gps) * [np.cumsum([p * (t1 - t0).seconds for (t0, t1) in segm_tdt])]) # Add idling penalty M += P match = commons.align(M) print(match) segments = [segments[j] for j in match] segm_tdt = [segm_tdt[j] for j in match] seg_dist = [dist_to_segment(r, s) for (r, s) in zip(reference_gps, segments)] ref_guess_tdt = [ t0 + q * (t1 - t0) for ((d, q), (t0, t1)) in zip(seg_dist, segm_tdt) ] for t in ref_guess_tdt : print(t) is_monotone = all((s <= t) for (s, t) in zip(ref_guess_tdt[:-1], ref_guess_tdt[1:])) print("Monotonicity:", is_monotone) (fig, ax) = plt.subplots() ax.imshow(M) while plt.fignum_exists(fig.number) : try : plt.pause(0.1) except : break plt.close(fig) pass
def vis1() : # OSM = pickle.load(open(IFILE['OSM'], 'rb')) # for (route_id, route) in OSM['rels']['route'].items(): # # Skip non-bus routes # if not (route['t'].get('route') == 'bus'): continue # # route_name = route['t'].get('name') # # route_ref = route['t']['ref'] # #if (route_ref == '88') : # print(route_name, route_id, route['t']) # exit(39) routeid_of = (lambda r: r['SubRouteUID']) # List of filenames, one file per physical bus, identified by plate number bus_files = commons.ls(IFILE['busses'].format(busid="*")) # Refile bus runs by their route ID runs_by_route = defaultdict(list) for fn in bus_files : runs = commons.zipjson_load(fn) for run in runs : runs_by_route[routeid_of(run)].append(run) # route_stops = commons.index_dicts_by_key(commons.zipjson_load(IFILE['route-stops']), routeid_of) # Are those valid route ID that can be found among the routes? unknown_route_ids = sorted(set(runs_by_route.keys()) - set(route_stops.keys())) if unknown_route_ids : print("The following route IDs from bus records are unknown:") print(", ".join(unknown_route_ids)) raise KeyError("Unkown route IDs in bus records") # route_uid = 'KHH24' runs = runs_by_route[route_uid] route = route_stops[route_uid] # Kaohsiung (left, bottom, right, top) bbox = (120.2593, 22.5828, 120.3935, 22.6886) (left, bottom, right, top) = bbox # Download the background map i = maps.get_map_by_bbox(bbox, token=PARAM['mapbox_api_token']) # Show the background map (fig, ax) = plt.subplots() plt.ion() ax.axis([left, right, bottom, top]) ax.imshow(i, extent=(left, right, bottom, top), interpolation='quadric') #fig.canvas.draw_idle() plt.pause(0.1) stops_by_direction = dict(zip(route['Direction'], route['Stops'])) # Draw stops for both route directions for (dir, stops) in stops_by_direction.items() : # Stop locations (y, x) = zip(*[ commons.inspect({'StopPosition': ('PositionLat', 'PositionLon')})(stop) for stop in stops ]) # Plot as dots ax.scatter(x, y, c=('b' if dir else 'g'), marker='o', s=4) # Show bus location for run in runs : # Trace bus (y, x) = (run['PositionLat'], run['PositionLon']) h1 = ax.plot(x, y, '--+', c='r', linewidth=1) h2 = ax.plot(x[0], y[0], 'o', c='r') h3 = ax.plot(x[-1], y[-1], 's', c='r') plt.title(run['PlateNumb']) #plt.savefig("{}.png".format(route_uid), dpi=180) plt.pause(0.1) bus_at_stops(run, stops_by_direction[run['Direction']]) plt.pause(0.1) [h[0].remove() for h in [h1, h2, h3]] return