├── .gitignore ├── README.md ├── _config.yml ├── batch_processing.py ├── data_interface.py ├── data_wrangling.py ├── hmm.py ├── hmm_extensions.py ├── naive_estimation.py ├── requirements.txt ├── results ├── estimates.p ├── measurements.csv ├── measurements.p ├── naive_estimates.p ├── routes.p └── simulation_parameters.p ├── run_batch.py ├── run_single.py ├── run_single_remotely.py ├── simulation.py ├── tools.py ├── validation.py └── visualization.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.iml 3 | *.xml 4 | *.pyc 5 | .vscode/.ropeproject/objectdb 6 | .vscode/.ropeproject/config.py 7 | .vscode/settings.json 8 | *.ipynb 9 | P.npy 10 | *.png 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MapMatchingHMM 2 | 3 | **This project has been abandoned.The functionality is replaced by [`tmmpy`](https://github.com/klaapbakken/tmmpy) and [`hmmpy`](https://github.com/klaapbakken/hmmpy).** 4 | 5 | Map Matching using Hidden Markov Models. 6 | 7 | The data used to represent road networks is obtained from OpenStreetMap. 8 | 9 | A route is simulated together with related observations. The observations are intended to resemble GPS - measurements and signals received from objects such as WiFi Access Points and cellphone towers. 10 | 11 | The conditional probabilties of the state sequences given observations are estimated using the Forward-Backward algorithm. The MAP sequence of segments is found using Viterbi. 12 | 13 | The method can be tested by calling 14 | 15 | ``` 16 | python run_single_remotely.py 17 | ``` 18 | 19 | Requirements are listed in requirements.txt. Installation instructions (using conda) can be seen at the top of the file. 20 | Use ``pip install utm`` and ``pip install osmapi`` to get remaining dependencies. 21 | 22 | Please note that this is a work in progresss. 23 | 24 | **Update, 5th of September 2019**: This project has been abandoned in favour of two other projects, [`tmmpy`](https://github.com/klaapbakken/tmmpy) and [`hmmpy`](https://github.com/klaapbakken/hmmpy). `hmmpy` aims to implement to common Hidden Markov Model functionality for arbitrary state spaces, observations, transition probabilities and emission probabilties. `tmmpy` leverages the functionality of `hmmpy` in order to do map matching. Both projects are already in a better state than this one, and will be worked on actively at least for a couple of months. I expect both projects to eventually end up on PyPI. 25 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /batch_processing.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | 4 | import random 5 | 6 | from simulation import simulate_route 7 | from simulation import simulate_observations 8 | 9 | from hmm import transition_probabilties_by_weighting_route_length 10 | from hmm import viterbi 11 | 12 | from validation import distance_to_true_state 13 | 14 | from hmm_extensions import emission_probabilities 15 | 16 | from naive_estimation import spatially_closest_states 17 | 18 | def simulate_routes(n, highway_dict, intersections, route_length): 19 | routes = list() 20 | for i in range(n): 21 | 22 | starting_highway = random.choice(list(highway_dict.keys())) 23 | starting_node = random.choice(highway_dict[starting_highway]['data']['nd']) 24 | routes.append(simulate_route(highway_dict, starting_node, starting_highway, intersections, route_length)) 25 | 26 | return routes 27 | 28 | def simulate_measurements(node_dict, state_space, polling_frequency, missing_data, routes, base_locations, base_max_range, gps_variance, speed_limit): 29 | print(base_locations.shape) 30 | gps_measurements_list = list() 31 | signal_measurements_list = list() 32 | measurement_states_list = list() 33 | 34 | 35 | for i, route in enumerate(routes): 36 | print("Route #{}".format(i + 1)) 37 | gps_measurements, signal_measurements, measurement_states = simulate_observations(route, node_dict, gps_variance, polling_frequency,\ 38 | [speed_limit]*len(route), base_locations, np.array([base_max_range]*base_locations.shape[0]), state_space) 39 | gps_measurements_list.append(gps_measurements) 40 | signal_measurements_list.append(signal_measurements) 41 | 42 | if missing_data: 43 | np.random.seed(250) 44 | N = gps_measurements.shape[0] 45 | missing_indices = np.random.choice(np.arange(N), np.floor(N/5).astype(int), replace=False) 46 | gps_measurements[missing_indices, :] = np.nan 47 | 48 | measurement_states_list.append(measurement_states) 49 | 50 | return gps_measurements_list, signal_measurements_list, measurement_states_list 51 | 52 | def get_estimates(state_space, gps_measurements_list, signal_measurements_list, emission_variance, transition_decay, maximum_route_length, base_locations, base_max_range): 53 | estimated_states_list = list() 54 | naive_estimates_list = list() 55 | 56 | i = 0 57 | for gps_measurements, signal_measurements in zip(gps_measurements_list, signal_measurements_list): 58 | print("Route #{}".format(i + 1)) 59 | 60 | print("Transition probabilities..") 61 | 62 | tp = transition_probabilties_by_weighting_route_length(state_space,\ 63 | transition_decay, maximum_route_length) 64 | 65 | print("Emission probabilities..") 66 | ep = emission_probabilities(gps_measurements, emission_variance, signal_measurements,\ 67 | base_locations, np.array([base_max_range]*base_locations.shape[0]), state_space) 68 | 69 | print("Viterbi..") 70 | pi = np.ones((len(state_space), ))/len(state_space) 71 | estimated_states = viterbi(tp, ep, pi) 72 | estimated_states_list.append(estimated_states) 73 | 74 | naive_estimate = spatially_closest_states(gps_measurements, state_space) 75 | naive_estimates_list.append(naive_estimate) 76 | 77 | i += 1 78 | 79 | return estimated_states_list, naive_estimates_list 80 | 81 | def results_as_dataframe(measurements, estimates, naive_estimates,\ 82 | simulation_parameters, estimation_parameters, state_space): 83 | measurements_df = pd.DataFrame(columns=["route_id", "polling_frequency", "no_of_bases",\ 84 | "missing_data", "transition_decay", "emission_variance", \ 85 | "hmm_accuracy", "benchmark_accuracy", 86 | "hmm_dist", "benchmark_dist"]) 87 | row = 0 88 | number_of_routes = len(measurements[0][0]) 89 | for n in range(number_of_routes): 90 | for i, measurement in enumerate(measurements): 91 | 92 | polling_frequency = simulation_parameters[i][0] 93 | bases = simulation_parameters[i][1].shape[0] 94 | missing_data = simulation_parameters[i][2] 95 | 96 | 97 | m = len(estimation_parameters) 98 | k = 0 99 | for j in range(m*i, m*(i+1)): 100 | true_states = measurement[2][n] 101 | hmm_acc = np.mean(estimates[j][n] == true_states) 102 | hmm_dist = np.mean(distance_to_true_state(estimates[j][n], true_states, state_space)) 103 | benchmark_acc = np.mean(naive_estimates[j][n] == true_states) 104 | benchmark_dist = np.mean(distance_to_true_state(naive_estimates[j][n], true_states, state_space)) 105 | emission_variance = estimation_parameters[k][0] 106 | transition_decay = estimation_parameters[k][1] 107 | measurements_df.loc[row] = [n + 1, polling_frequency, bases, missing_data,\ 108 | transition_decay, emission_variance, 109 | hmm_acc, benchmark_acc, 110 | hmm_dist, benchmark_dist] 111 | k += 1 112 | row += 1 113 | 114 | return measurements_df -------------------------------------------------------------------------------- /data_interface.py: -------------------------------------------------------------------------------- 1 | import psycopg2 2 | import geopandas as gpd 3 | import numpy as np 4 | from shapely.geometry import mapping 5 | from osmapi import OsmApi 6 | import json 7 | import os 8 | 9 | from tools import convert_to_utm 10 | 11 | 12 | def load_coords_from_json(json_file): 13 | with open(json_file) as f: 14 | track = json.load(f) 15 | longitude = list(filter(lambda x: x != 0.0, [track['points'][i]['lon'] for i in range(len(track['points']))])) 16 | latitude = list(filter(lambda x: x != 0.0, [track['points'][i]['lat'] for i in range(len(track['points']))])) 17 | coordinate_array = np.array((latitude, longitude)).T 18 | utm_coordinate_array = convert_to_utm(coordinate_array) 19 | return utm_coordinate_array, coordinate_array 20 | 21 | def load_coords_from_folder(json_folder): 22 | utm_coordinate_array = np.empty((0,2)) 23 | coordinate_array = np.empty((0,2)) 24 | json_files = os.listdir(json_folder) 25 | for file in json_files: 26 | abs_path = os.path.join(json_folder, file) 27 | utm_coordinates, coordinates = load_coords_from_json(abs_path) 28 | utm_coordinate_array = np.concatenate((utm_coordinate_array, utm_coordinates)) 29 | coordinate_array = np.concatenate((coordinate_array, coordinates)) 30 | return utm_coordinate_array, coordinate_array 31 | 32 | def node_row_to_dict(row): 33 | e = list(filter(lambda x: any([i.isalnum() for i in x]), row['tags'].split('"'))) 34 | lonlat = mapping(row['geom'])['coordinates'] 35 | node_dict = {'data' : {'id' : row['id'], 36 | 'lat' : lonlat[1], 37 | 'lon' : lonlat[0], 38 | 'tag' : {e[2*i] : e[2*i + 1] for i in range(len(e)//2)} 39 | }, 40 | 'type' : 'node'} 41 | return node_dict 42 | 43 | def ways_row_to_dict(row): 44 | e = list(filter(lambda x: any([i.isalnum() for i in x]), row['tags'].split('"'))) 45 | ways_dict = {'data' : {'id' : row['id'], 46 | 'nd' : row['nodes'], 47 | 'tag' : {e[2*i] : e[2*i + 1] for i in range(len(e)//2)} 48 | }, 49 | 'type' : 'way'} 50 | return ways_dict 51 | 52 | def query_nodes_postgis_db(node_ids, password): 53 | initial_query = "select * from nodes where id = " + str(node_ids[0]) 54 | base_query = " union select * from nodes where id = " 55 | sql_query = initial_query 56 | for node_id in node_ids[1:]: 57 | sql_query += base_query + str(node_id) 58 | sql_query += ';' 59 | con = psycopg2.connect(database="geodatabase", user="postgres", password=password, 60 | host="localhost") 61 | node_df = gpd.GeoDataFrame.from_postgis(sql_query, con, geom_col='geom') 62 | nodes = [] 63 | for i in range(len(node_df)): 64 | row = node_df.iloc[i, :] 65 | nodes.append(node_row_to_dict(row)) 66 | return nodes 67 | 68 | def query_osm_api(bbox): 69 | my_api = OsmApi() 70 | city = my_api.Map(bbox[0], bbox[1], bbox[2], bbox[3]) 71 | nodes = [element for element in city if element['type'] == 'node'] 72 | ways = [element for element in city if element['type'] == 'way'] 73 | return nodes, ways 74 | 75 | def query_ways_postgis_db(bbox, password): 76 | con = psycopg2.connect(database="geodatabase", user="postgres", password=password, 77 | host="localhost") 78 | ways_sql = "select * from ways where ways.linestring && ST_MakeEnvelope({0}, {1}, {2}, {3});".format(bbox[0], bbox[1], bbox[2], bbox[3]) 79 | ways_df = gpd.GeoDataFrame.from_postgis(ways_sql, con, geom_col='linestring' ) 80 | ways = [] 81 | for j in range(len(ways_df)): 82 | row = ways_df.iloc[j, :] 83 | ways.append(ways_row_to_dict(row)) 84 | return ways -------------------------------------------------------------------------------- /data_wrangling.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import utm 3 | import random 4 | 5 | from tools import convert_to_utm 6 | 7 | def create_node_dict(nodes): 8 | return {node['data']['id'] : node for node in nodes} 9 | 10 | def get_accepted_highways(ways): 11 | all_highways = [way for way in ways if 'highway' in way['data']['tag'].keys()] 12 | accepted_highway_types = set(['motorway', 'trunk', 'primary', 'secondary', 'tertiary', 'unclassified', 'residential', 'service', 13 | 'motorway_link', 'trunk_link', 'primary_link', 'secondary_link', 'tertiary_link', 14 | 'living_street', 'pedestrian', 'road', 'escape', 'track']) 15 | accepted_highways = [highway for highway in all_highways if highway['data']['tag']['highway'] in accepted_highway_types] 16 | return accepted_highways 17 | 18 | def create_highway_dict(highways): 19 | return {highway['data']['id'] : highway for highway in highways} 20 | 21 | def find_shared_nodes(highway_dict): 22 | node_count = {} 23 | for highway_id in highway_dict: 24 | node_list = highway_dict[highway_id]['data']['nd'] 25 | for node in node_list: 26 | if node not in node_count: 27 | node_count[node] = (1, [highway_id]) 28 | else: 29 | node_count[node] = (node_count[node][0] + 1, node_count[node][1] + [highway_id]) 30 | return node_count 31 | 32 | def get_required_nodes(highways): 33 | highway_nodes = [highway['data']['nd'] for highway in highways] 34 | highway_nodes_array = np.array([node for node_list in highway_nodes for node in node_list]) 35 | required_nodes = np.unique(highway_nodes_array) 36 | return required_nodes 37 | 38 | def find_intersections(highway_dict, node_dict): 39 | node_count = find_shared_nodes(highway_dict) 40 | intersections = {} 41 | for node in node_count: 42 | count = node_count[node][0] 43 | if count > 1 and node in node_dict: 44 | intersections[node] = node_count[node][1] 45 | return intersections 46 | 47 | def get_coordinates_of_nodes(node_ids, node_dict): 48 | node_list = [node_dict[node_id] for node_id in node_ids if node_id in node_dict] 49 | longitude = np.array([node['data']['lon'] for node in node_list]) 50 | latitude = np.array([node['data']['lat'] for node in node_list]) 51 | return np.array((latitude, longitude)).T 52 | 53 | def create_segment(p1, p2): 54 | x1 = p1[0] 55 | y1 = p1[1] 56 | x2 = p2[0] 57 | y2 = p2[1] 58 | return lambda x: (y2 - y1)/(x2 - x1)*(x - x1) + y1 59 | 60 | def create_segment_list(node_ids, node_dict): 61 | coordinate_array = get_coordinates_of_nodes(node_ids, node_dict) 62 | utm_coordinate_array = convert_to_utm(coordinate_array) 63 | return [create_segment(utm_coordinate_array[i, :], utm_coordinate_array[i+1, :]) 64 | for i in range(utm_coordinate_array.shape[0]-1)], utm_coordinate_array 65 | 66 | def create_state_space_representations(highways, node_dict): 67 | id_tag = 0 68 | state_space = list() 69 | for highway in highways: 70 | for i in range(1, len(highway['data']['nd'])): 71 | node_a = node_dict[highway['data']['nd'][i-1]] 72 | node_b = node_dict[highway['data']['nd'][i]] 73 | 74 | latlon_a = (node_a['data']['lat'], node_a['data']['lon']) 75 | latlon_b = (node_b['data']['lat'], node_b['data']['lon']) 76 | 77 | coords_a = convert_to_utm(np.array(latlon_a).reshape(1, 2)) 78 | coords_b = convert_to_utm(np.array(latlon_b).reshape(1, 2)) 79 | 80 | if not np.all(coords_a == coords_b): 81 | 82 | state = {'id' : id_tag, 83 | 'edge' : (node_a['data']['id'], node_b['data']['id']), 84 | 'edge_set' : set((node_a['data']['id'], node_b['data']['id'])), 85 | 'function' : create_segment(coords_a.reshape((2,)), coords_b.reshape((2,))), 86 | 'domain' : (coords_a[0, 0], coords_b[0, 0]) 87 | } 88 | 89 | state_space.append(state) 90 | id_tag += 1 91 | 92 | return state_space 93 | 94 | def add_neighbours(network_set, node, edges): 95 | for edge in edges: 96 | if node in edge: 97 | network_set = network_set.union(set(edge)) 98 | return network_set 99 | 100 | def find_connected_graphs(state_space): 101 | all_edges = [set(state['edge']) for state in state_space] 102 | all_nodes = set.union(*all_edges) 103 | 104 | nodes_in_a_network = set() 105 | all_networks = [] 106 | for node in all_nodes: 107 | if node not in nodes_in_a_network: 108 | network = set([node]) 109 | already_visited = set() 110 | current_node = node 111 | proceed = True 112 | while proceed: 113 | network = add_neighbours(network, current_node, all_edges) 114 | already_visited.add(current_node) 115 | if len(network.difference(already_visited)) != 0: 116 | current_node = random.choice(list(network.difference(already_visited))) 117 | else: 118 | proceed = False 119 | nodes_in_a_network = nodes_in_a_network.union(network) 120 | all_networks.append(network) 121 | return all_networks 122 | 123 | def remove_unconnected_states(state_space): 124 | all_networks = find_connected_graphs(state_space) 125 | 126 | majority_network = all_networks[0] 127 | for network in all_networks: 128 | if len(majority_network) < len(network): 129 | majority_network = network 130 | 131 | new_state_space = list() 132 | for state in state_space: 133 | node_a, node_b = state['edge'] 134 | if node_a in majority_network or node_b in majority_network: 135 | new_state_space.append(state) 136 | 137 | for i, _ in enumerate(new_state_space): 138 | new_state_space[i]['id'] = i 139 | 140 | return new_state_space 141 | 142 | def remove_unconnected_highways(highways, state_space): 143 | all_edges = [set(state['edge']) for state in state_space] 144 | all_nodes = set.union(*all_edges) 145 | accepted_highways = list() 146 | 147 | for highway in highways: 148 | if len(set(highway['data']['nd']).intersection(all_nodes)) != 0: 149 | accepted_highways.append(highway) 150 | return(accepted_highways) -------------------------------------------------------------------------------- /hmm.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from math import fsum 4 | 5 | def closest_point(state_function, state_domain, z): 6 | b = state_function(0) 7 | a = state_function(1) - b 8 | closest_x = (z[0] + z[1]*a - a*b)/(a**2 +1) 9 | if not (np.min(state_domain) <= closest_x <= np.max(state_domain)): 10 | closest_x = state_domain[np.argmin((np.abs(closest_x - state_domain[0]), 11 | np.abs(closest_x - state_domain[1])))] 12 | return closest_x, state_function(closest_x) 13 | 14 | def get_states_in_proximity(current_state_id, state_space, epsilon): 15 | current_state_domain = state_space[current_state_id]['domain'] 16 | current_state_function = state_space[current_state_id]['function'] 17 | 18 | state_ids = list(range(len(state_space))) 19 | states_in_reach = list() 20 | for state_id in state_ids: 21 | state_domain = state_space[state_id]['domain'] 22 | state_function = state_space[state_id]['function'] 23 | distances = list() 24 | for x1 in current_state_domain: 25 | y1 = current_state_function(x1) 26 | point1 = np.array((x1, y1)) 27 | for x2 in state_domain: 28 | y2 = state_function(x2) 29 | point2 = np.array((x2, y2)) 30 | distances.append(np.linalg.norm(point1 - point2)) 31 | if np.min(distances) < epsilon: 32 | states_in_reach.append(state_id) 33 | return set(states_in_reach) 34 | 35 | def search_for_connected_nodes(node_id, all_edges): 36 | connected_nodes = list() 37 | for edge in all_edges: 38 | if node_id in edge: 39 | i = np.where(np.array(edge) != node_id)[0][0] 40 | connected_nodes.append(edge[i]) 41 | return connected_nodes 42 | 43 | def add_reachable_edges(node_id, all_edges, reachable_edges, i, limit): 44 | if i < limit: 45 | reachable_nodes = search_for_connected_nodes(node_id, all_edges) 46 | connected_edges = [(node_id, reachable_node) for reachable_node in reachable_nodes] 47 | reversed_connected_edges = [(edge[1], edge[0]) for edge in connected_edges] 48 | reachable_edges.update(connected_edges) 49 | reachable_edges.update(reversed_connected_edges) 50 | for reachable_node_id in reachable_nodes: 51 | add_reachable_edges(reachable_node_id, all_edges, reachable_edges, i + 1, limit) 52 | else: 53 | return reachable_edges 54 | 55 | def get_reachable_edges(starting_state_id, state_space, accepted_edges, limit): 56 | node_a = state_space[starting_state_id]['edge'][0] 57 | node_b = state_space[starting_state_id]['edge'][1] 58 | reachable_edges = set(list([(node_a, node_b), (node_b, node_a)])) 59 | add_reachable_edges(node_a, accepted_edges, reachable_edges, 0, limit) 60 | add_reachable_edges(node_b, accepted_edges, reachable_edges, 0, limit) 61 | return reachable_edges 62 | 63 | def emission_probabilities(z, state_space, variance): 64 | ep = np.empty((len(state_space))) 65 | for state_id in range(len(state_space)): 66 | state_function = state_space[state_id]['function'] 67 | state_domain = state_space[state_id]['domain'] 68 | closest_x, closest_y = closest_point(state_function, state_domain, z) 69 | ep[state_id] = 1/(np.sqrt(2*np.pi)*variance)*np.exp(-1*np.linalg.norm([z[0] - closest_x, z[1] - closest_y])/(2*variance**2)) 70 | return ep 71 | 72 | def transition_probabilities(state_space, edges_to_cross, required_proximity): 73 | tp = np.empty((len(state_space), len(state_space))) 74 | state_ids = list(range(len(state_space))) 75 | for state_id in state_ids: 76 | print(state_id) 77 | close_states = get_states_in_proximity(state_id, state_space, required_proximity) 78 | close_edges = [state_space[state_id]['edge'] for state_id in close_states] 79 | reversed_close_edges = [(edge[1], edge[0]) for edge in close_edges] 80 | close_edge_set = set(close_edges).union(set(reversed_close_edges)) 81 | reachable_edges = get_reachable_edges(state_id, state_space, close_edges, edges_to_cross) 82 | allowed_edges = close_edge_set.intersection(reachable_edges) 83 | allowed_state_ids = [state['id'] for state in state_space if state['edge'] in allowed_edges] 84 | if len(allowed_state_ids) == 0: 85 | tp[state_id, state_id] = 1 86 | else: 87 | weight = 1/len(allowed_state_ids) 88 | tp[state_id, np.array(allowed_state_ids)] = weight 89 | return tp 90 | 91 | def distance_from_endpoint_to_segment(line_domain, line_function, endpoint_domain, endpoint_function, endpoint_index): 92 | endpoint = np.array([endpoint_domain[endpoint_index], endpoint_function(endpoint_domain[endpoint_index])]) 93 | x, y = closest_point(line_function, line_domain, endpoint) 94 | return np.linalg.norm(np.array([x, y]) - endpoint) 95 | 96 | #Import closest_point(state_function, state_domain, z) 97 | def distance_between_segments(state_id_a, state_id_b, state_space): 98 | state_a_function = state_space[state_id_a]['function'] 99 | state_a_domain = state_space[state_id_a]['domain'] 100 | state_b_function = state_space[state_id_b]['function'] 101 | state_b_domain = state_space[state_id_b]['domain'] 102 | #Assumes state_space is sorted on state_space['id'] ascending. Confirm this. 103 | a_to_first_endpoint_of_b = distance_from_endpoint_to_segment(state_a_domain, state_a_function,\ 104 | state_b_domain, state_b_function, 0) 105 | a_to_second_endpoint_of_b = distance_from_endpoint_to_segment(state_a_domain, state_a_function,\ 106 | state_b_domain, state_b_function, 1) 107 | b_to_first_endpoint_of_a = distance_from_endpoint_to_segment(state_b_domain, state_b_function,\ 108 | state_a_domain, state_a_function, 0) 109 | b_to_second_endpoint_of_a = distance_from_endpoint_to_segment(state_b_domain, state_b_function,\ 110 | state_a_domain, state_a_function, 1) 111 | return np.min(np.array([a_to_first_endpoint_of_b, a_to_second_endpoint_of_b, b_to_first_endpoint_of_a, b_to_second_endpoint_of_a])) 112 | 113 | def alternative_transition_probabilties(state_space, speed, frequency, exp_scale, max_distance): 114 | n = len(state_space) 115 | expected_distance = speed/frequency 116 | tp = np.zeros((n,n)) 117 | for i in range(n): 118 | dist_calc = lambda j: distance_between_segments(i, j, state_space) 119 | for j in range(n): 120 | d_ij = dist_calc(j) 121 | if d_ij < max_distance: 122 | tp[i, j] = exp_scale*np.exp(-exp_scale*d_ij) 123 | tp[i, :] /= np.sum(tp[i, :]) 124 | return tp 125 | 126 | def create_connection_dictionary(state_space): 127 | connections = dict() 128 | for state in state_space: 129 | state_edges = state['edge'] 130 | connected_states = list() 131 | for edge in state_edges: 132 | for potentially_connected_state in state_space: 133 | if edge in potentially_connected_state['edge_set'] and potentially_connected_state != state: 134 | connected_states.append(potentially_connected_state['id']) 135 | connections[state['id']] = connected_states 136 | return connections 137 | 138 | def recursive_neighbour_search(state_id, dictionary_of_reachable_states, connection_dictionary, state_space, distance, maximum_distance): 139 | if state_id not in dictionary_of_reachable_states: 140 | dictionary_of_reachable_states[state_id] = distance 141 | elif dictionary_of_reachable_states[state_id] > distance: 142 | dictionary_of_reachable_states[state_id] = distance 143 | else: 144 | return 145 | domain = state_space[state_id]['domain'] 146 | function = state_space[state_id]['function'] 147 | state_length = np.linalg.norm(np.array([domain[0], function(domain[0])]) - np.array([domain[1], function(domain[1])])) 148 | connected_states = connection_dictionary[state_id] 149 | for state in connected_states: 150 | new_distance = state_length + distance 151 | if new_distance < maximum_distance: 152 | recursive_neighbour_search(state, dictionary_of_reachable_states,\ 153 | connection_dictionary, state_space, new_distance, maximum_distance) 154 | return dictionary_of_reachable_states 155 | 156 | def transition_probabilties_by_weighting_route_length(state_space, beta, max_distance): 157 | connection_dictionary = create_connection_dictionary(state_space) 158 | n = len(state_space) 159 | #Create matrix 160 | tp = np.zeros((n,n)) 161 | #For each state 162 | for state_id in range(n): 163 | if np.mod(n, 100) == 0: 164 | print("Iteration: {}".format(state_id)) 165 | state_reachability_dictionary = recursive_neighbour_search(state_id, dict(),\ 166 | connection_dictionary, state_space, 0, max_distance) 167 | for reachable_state in state_reachability_dictionary: 168 | d = state_reachability_dictionary[reachable_state] 169 | tp[state_id, reachable_state] = beta*np.exp(-beta*d) 170 | tp[state_id, :] /= np.sum(tp[state_id, :]) 171 | return tp 172 | 173 | def observation_emissions(observations, state_space, variance): 174 | ep = np.empty((observations.shape[0], len(state_space))) 175 | for i, observation in enumerate(observations): 176 | ep[i, :] = emission_probabilities(observation, state_space, variance) 177 | return ep 178 | 179 | def forward_recursions(P, l, pi): 180 | n_states = P.shape[0] 181 | n_observations = l.shape[0] 182 | 183 | alpha = np.zeros((n_observations, n_states)) 184 | 185 | alpha[0, :] = pi*l[0, :] 186 | C_0 = np.sum(alpha[0, :]) 187 | alpha[0, :] /= C_0 188 | 189 | for t in range(n_observations - 1): 190 | for j in range(n_states): 191 | alpha[t+1, j] = np.matmul(alpha[t, :], P[:, j])*l[t, j] 192 | #C_t = np.sum(alpha[t+1, :]) 193 | #print(C_t) 194 | #alpha[t+1, :] /= C_t 195 | 196 | return alpha 197 | 198 | def backward_recursions(P, l, alpha): 199 | n_states = P.shape[0] 200 | n_observations = l.shape[0] 201 | 202 | beta = np.zeros((n_observations, n_states)) 203 | 204 | beta[n_observations - 1, :] = 1 205 | 206 | for t in range(n_observations - 2, -1, -1): 207 | for i in range(n_states): 208 | beta[t, i] = np.sum(P[i, :]*(l[t + 1, :]/np.sum(l[t+1, :]))*beta[t + 1, :]) 209 | return beta 210 | 211 | def viterbi(P, l, pi): 212 | n_states = P.shape[0] 213 | n_observations = l.shape[0] 214 | 215 | delta = np.zeros((n_observations, n_states)) 216 | delta[0, :] = pi*l[0, :] 217 | 218 | phi = np.zeros((n_observations, n_states)) 219 | phi[0, :] = 0 220 | 221 | for t in range(1, n_observations): 222 | for j in range(n_states): 223 | delta[t, j] = np.max(delta[t-1, :]*P[:, j]*l[t, j]) 224 | phi[t, j] = np.argmax(delta[t-1, :]*P[:, j]) 225 | 226 | q_star = np.zeros((n_observations, )) 227 | 228 | P_star = np.max(delta[-1, :]) 229 | q_star[-1] = np.argmax(delta[-1, :]) 230 | 231 | for t in range(n_observations - 2, -1, -1): 232 | q_star[t] = phi[t+1, int(q_star[t+1])] 233 | 234 | return q_star 235 | 236 | def viterbi_estimate_to_state_estimate(viterbi_estimate, state_space): 237 | state_estimate = list() 238 | for estimate in viterbi_estimate: 239 | state_estimate.append(state_space[int(estimate)]['id']) 240 | return np.array(state_estimate) -------------------------------------------------------------------------------- /hmm_extensions.py: -------------------------------------------------------------------------------- 1 | from hmm import closest_point 2 | 3 | import numpy as np 4 | 5 | 6 | from scipy.stats import beta, bernoulli 7 | 8 | def probability_of_signal_given_state(signal_strength, closest_z, base_position, max_range): 9 | closest_x, closest_y = closest_z 10 | #Idea: Use measurement position instead of closest_point_in_state 11 | #Problem: Missing data will cascade through algorithm 12 | #Solution: Use measurement position if available. Otherwise current solution 13 | distance_to_state = np.linalg.norm(base_position - np.array([closest_x, closest_y])) 14 | if np.isnan(signal_strength): 15 | return 1 16 | elif signal_strength == 0: 17 | return bernoulli.pmf(0, max(0, 1 - distance_to_state/max_range)) 18 | else: 19 | return bernoulli.pmf(1, max(0, 1 - distance_to_state/max_range))*beta.pdf(signal_strength, 2, 5*distance_to_state/max_range) 20 | 21 | def probability_of_position_given_state(position, state, variance, position_measurements, position_index): 22 | if np.isnan(position).any(): 23 | variance = 1e12 24 | position = locate_last_non_missing_position(position_index, position_measurements) 25 | closest_x, closest_y = closest_point(state["function"], state["domain"], position) 26 | return 1/(np.sqrt(2*np.pi)*variance)*np.exp(-1*np.linalg.norm([position[0] - closest_x, position[1] - closest_y])/(2*variance**2)) 27 | 28 | def locate_last_non_missing_position(position_index, position_measurements): 29 | for position in reversed(position_measurements[:position_index, :]): 30 | if not np.isnan(position).any(): 31 | return position 32 | for position in position_measurements[position_index:, :]: 33 | if not np.isnan(position).any(): 34 | return position 35 | return np.apply_along_axis(np.mean, 0, position_measurements[np.invert(np.isnan(position_measurements))].reshape(-1, 2)) 36 | 37 | def emission_probabilities(position_measurements, variance, signal_measurements, base_locations, base_ranges, state_space): 38 | ep = np.ones((position_measurements.shape[0], len(state_space))) 39 | points_closest_to_base_array = points_closest_to_bases(state_space, base_locations) 40 | for row, position in enumerate(position_measurements): 41 | for column, state in enumerate(state_space): 42 | for i, signal_strength in enumerate(signal_measurements[row, :]): 43 | ep[row, column] = ep[row, column]*probability_of_signal_given_state(signal_strength,\ 44 | points_closest_to_base_array[column, i], base_locations[i, :], base_ranges[i]) 45 | ep[row, column] = ep[row, column]*probability_of_position_given_state(position,\ 46 | state, variance, position_measurements, row) 47 | return ep 48 | 49 | def points_closest_to_bases(state_space, base_locations): 50 | points_closest_to_base_array = np.zeros((len(state_space), base_locations.shape[0], 2)) 51 | for i, state in enumerate(state_space): 52 | for j in range(base_locations.shape[0]): 53 | closest_x, closest_y = closest_point(state['function'], state['domain'], base_locations[j, :]) 54 | points_closest_to_base_array[i, j, :] = np.array([closest_x, closest_y]) 55 | 56 | return points_closest_to_base_array 57 | 58 | #Return, for each observation (vector), find the probability of making 59 | #this observation given each state (so observation x state space) 60 | 61 | #For each possible state 62 | #Calculate the probability that one would observe the n signal strengths and 63 | #the position measurement 64 | #In the case of missing data, use high-variance with mean measurment, or high-variance with last observation 65 | 66 | 67 | -------------------------------------------------------------------------------- /naive_estimation.py: -------------------------------------------------------------------------------- 1 | from hmm import closest_point 2 | 3 | from hmm_extensions import locate_last_non_missing_position 4 | 5 | import numpy as np 6 | 7 | def spatially_closest_states(measurement_array, state_space): 8 | closest_states = list() 9 | for row in range(measurement_array.shape[0]): 10 | z = measurement_array[row, :] 11 | if np.isnan(z).any(): 12 | z = locate_last_non_missing_position(row, measurement_array) 13 | distance_to_states = dict() 14 | distance_to_closest_state = np.inf 15 | id_of_closest_state = None 16 | for state in state_space: 17 | closest_x, closest_y = closest_point(state['function'], state['domain'], z) 18 | distance = np.linalg.norm(z - np.array([closest_x, closest_y])) 19 | if distance < distance_to_closest_state: 20 | distance_to_closest_state = distance 21 | id_of_closest_state = state['id'] 22 | closest_states.append(id_of_closest_state) 23 | return np.array(closest_states) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # This file may be used to create an environment using: 2 | # $ conda create --name --file 3 | # platform: win-64 4 | attrs=18.2.0=py_0 5 | backcall=0.1.0=py_0 6 | blas=1.0=mkl 7 | boost-cpp=1.68.0=h6a4c333_1000 8 | bzip2=1.0.6=hfa6e2cd_1002 9 | ca-certificates=2018.11.29=ha4d7672_0 10 | certifi=2018.11.29=py36_1000 11 | click=7.0=py_0 12 | click-plugins=1.0.4=py_0 13 | cligj=0.5.0=py_0 14 | colorama=0.4.1=py_0 15 | curl=7.63.0=h4496350_1000 16 | cycler=0.10.0=py_1 17 | decorator=4.3.2=py_0 18 | descartes=1.1.0=py_2 19 | expat=2.2.5=he025d50_1002 20 | fiona=1.8.4=py36hce0be61_1002 21 | freetype=2.9.1=h5db478b_1005 22 | freexl=1.0.5=hd288d7e_1002 23 | gdal=2.4.0=py36hdf5ee75_1002 24 | geopandas=0.4.0=py_1 25 | geos=3.7.1=he025d50_1000 26 | geotiff=1.4.3=h8408f58_1000 27 | gettext=0.19.8.1=hb01d8f6_1001 28 | glib=2.58.2=hc0c2ac7_1001 29 | hdf4=4.2.13=hf8e6fe8_1002 30 | hdf5=1.10.4=nompi_hcc15c50_1105 31 | icc_rt=2019.0.0=h0cc432a_1 32 | icu=58.2=ha66f8fd_1 33 | intel-openmp=2019.1=144 34 | ipykernel=5.1.0=py36h39e3cac_1001 35 | ipython=7.2.0=py36h39e3cac_1000 36 | ipython_genutils=0.2.0=py_1 37 | jedi=0.13.2=py36_1000 38 | jpeg=9c=hfa6e2cd_1001 39 | jupyter_client=5.2.4=py_1 40 | jupyter_core=4.4.0=py_0 41 | kealib=1.4.10=heacb130_1002 42 | kiwisolver=1.0.1=py36he980bc4_1002 43 | krb5=1.16.3=h038dc86_1000 44 | libcurl=7.63.0=h4496350_1000 45 | libffi=3.2.1=h6538335_1005 46 | libgdal=2.4.0=haf7df99_1002 47 | libiconv=1.15=hfa6e2cd_1004 48 | libkml=1.3.0=h4fd0f3b_1009 49 | libnetcdf=4.6.2=h396784b_1001 50 | libpng=1.6.36=h7602738_1000 51 | libpq=10.6=h4c6b53d_1000 52 | libsodium=1.0.16=h2fa13f4_1001 53 | libspatialindex=1.8.5=he025d50_1003 54 | libspatialite=4.3.0a=h6a0152f_1026 55 | libssh2=1.8.0=hc4dcbb0_1003 56 | libtiff=4.0.10=h016b793_1002 57 | libxml2=2.9.8=h9ce36c8_1005 58 | m2w64-expat=2.1.1=2 59 | m2w64-gcc-libgfortran=5.3.0=6 60 | m2w64-gcc-libs=5.3.0=7 61 | m2w64-gcc-libs-core=5.3.0=7 62 | m2w64-gettext=0.19.7=2 63 | m2w64-gmp=6.1.0=2 64 | m2w64-libiconv=1.14=6 65 | m2w64-libwinpthread-git=5.0.0.4634.697f757=2 66 | m2w64-xz=5.2.2=2 67 | matplotlib=3.0.2=py36h8a2030e_1001 68 | matplotlib-base=3.0.2=py36h3e3dc42_1001 69 | mkl=2019.1=144 70 | mkl_fft=1.0.10=py36hfa6e2cd_1 71 | mkl_random=1.0.2=py36h830ac7b_2 72 | msys2-conda-epoch=20160418=1 73 | munch=2.3.2=py_0 74 | numpy=1.15.4=py36h19fb1c0_0 75 | numpy-base=1.15.4=py36hc3f5095_0 76 | openjpeg=2.3.0=h25a6d84_1003 77 | openssl=1.0.2p=hfa6e2cd_1002 78 | pandas=0.24.0=py36h6538335_0 79 | parso=0.3.2=py_0 80 | patsy=0.5.1=py_0 81 | pcre=8.41=h6538335_1003 82 | pickleshare=0.7.5=py36_1000 83 | pip=18.1=py36_1000 84 | poppler=0.67.0=heddaa77_6 85 | poppler-data=0.4.9=1 86 | postgresql=10.6=hdce66b5_1000 87 | proj4=5.2.0=hfa6e2cd_1001 88 | prompt_toolkit=2.0.7=py_0 89 | psycopg2=2.7.6.1=py36h74b6da3_1000 90 | pygments=2.3.1=py_0 91 | pyparsing=2.3.1=py_0 92 | pyproj=1.9.6=py36h1fcc0e4_1000 93 | pyqt=5.6.0=py36h764d66f_1008 94 | pysal=1.14.4.post2=py36_1001 95 | python=3.6.6=he025d50_0 96 | python-dateutil=2.7.5=py_0 97 | pytz=2018.9=py_0 98 | pyzmq=17.1.2=py36hf576995_1001 99 | qt=5.6.2=h2639256_8 100 | rtree=0.8.3=py36_1000 101 | scipy=1.2.0=py36h29ff71c_0 102 | seaborn=0.9.0=py_0 103 | setuptools=40.6.3=py36_0 104 | shapely=1.6.4=py36h8921fb9_1002 105 | sip=4.18.1=py36h6538335_1000 106 | six=1.12.0=py36_1000 107 | sqlalchemy=1.2.16=py36hfa6e2cd_1000 108 | sqlite=3.26.0=hfa6e2cd_1000 109 | statsmodels=0.9.0=py36hfa6e2cd_1000 110 | tk=8.6.9=hfa6e2cd_1000 111 | tornado=5.1.1=py36hfa6e2cd_1000 112 | traitlets=4.3.2=py36_1000 113 | vc=14.1=h0510ff6_4 114 | vs2015_runtime=14.15.26706=h3a45250_0 115 | wcwidth=0.1.7=py_1 116 | wheel=0.32.3=py36_0 117 | wincertstore=0.2=py36_1002 118 | xerces-c=3.2.2=h6538335_1001 119 | xz=5.2.4=h2fa13f4_1001 120 | zeromq=4.2.5=he025d50_1006 121 | zlib=1.2.11=h2fa13f4_1004 122 | zstd=1.3.3=vc14_1 123 | -------------------------------------------------------------------------------- /results/estimates.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaapbakken/MapMatchingHMM/422ff1fc6895efd38aa6f92d614deaa5aeb54a96/results/estimates.p -------------------------------------------------------------------------------- /results/measurements.csv: -------------------------------------------------------------------------------- 1 | ,route_id,polling_frequency,no_of_bases,missing_data,transition_decay,emission_variance,hmm_accuracy,benchmark_accuracy,hmm_dist,benchmark_dist 2 | 0,1,0.06666666666666667,0,True,0.01,0.5,0.5333333333333333,0.5666666666666667,19.507488577921908,14.449435081998631 3 | 1,1,0.06666666666666667,0,True,0.004,0.5,0.5,0.5666666666666667,21.255180039493258,14.449435081998631 4 | 2,1,0.06666666666666667,0,True,0.002,0.5,0.4666666666666667,0.5666666666666667,23.444161356809545,14.449435081998631 5 | 3,1,0.06666666666666667,0,True,0.01,1.0,0.5333333333333333,0.5666666666666667,19.507488577921908,14.449435081998631 6 | 4,1,0.06666666666666667,0,True,0.004,1.0,0.5,0.5666666666666667,21.255180039493258,14.449435081998631 7 | 5,1,0.06666666666666667,0,True,0.002,1.0,0.4666666666666667,0.5666666666666667,23.444161356809545,14.449435081998631 8 | 6,1,0.06666666666666667,0,True,0.01,2.0,0.5,0.5666666666666667,20.330802715065477,14.449435081998631 9 | 7,1,0.06666666666666667,0,True,0.004,2.0,0.43333333333333335,0.5666666666666667,22.795154699613594,14.449435081998631 10 | 8,1,0.06666666666666667,0,True,0.002,2.0,0.4666666666666667,0.5666666666666667,22.938217358672425,14.449435081998631 11 | 9,1,0.06666666666666667,0,False,0.01,0.5,0.6333333333333333,0.6333333333333333,9.089496599150307,9.38159917356677 12 | 10,1,0.06666666666666667,0,False,0.004,0.5,0.6333333333333333,0.6333333333333333,9.089496599150307,9.38159917356677 13 | 11,1,0.06666666666666667,0,False,0.002,0.5,0.5666666666666667,0.6333333333333333,10.404632185680919,9.38159917356677 14 | 12,1,0.06666666666666667,0,False,0.01,1.0,0.6,0.6333333333333333,10.44837520851494,9.38159917356677 15 | 13,1,0.06666666666666667,0,False,0.004,1.0,0.6,0.6333333333333333,11.032580357347864,9.38159917356677 16 | 14,1,0.06666666666666667,0,False,0.002,1.0,0.5333333333333333,0.6333333333333333,12.055613369462014,9.38159917356677 17 | 15,1,0.06666666666666667,0,False,0.01,2.0,0.6,0.6333333333333333,11.74585266670474,9.38159917356677 18 | 16,1,0.06666666666666667,0,False,0.004,2.0,0.6333333333333333,0.6333333333333333,10.2092662202043,9.38159917356677 19 | 17,1,0.06666666666666667,0,False,0.002,2.0,0.5666666666666667,0.6333333333333333,11.232299232318448,9.38159917356677 20 | 18,1,0.06666666666666667,50,True,0.01,0.5,0.5,0.5,12.385009285054478,14.768895627884765 21 | 19,1,0.06666666666666667,50,True,0.004,0.5,0.5,0.5,12.385009285054478,14.768895627884765 22 | 20,1,0.06666666666666667,50,True,0.002,0.5,0.5,0.5,12.189474385398439,14.768895627884765 23 | 21,1,0.06666666666666667,50,True,0.01,1.0,0.5,0.5,12.385009285054478,14.768895627884765 24 | 22,1,0.06666666666666667,50,True,0.004,1.0,0.5,0.5,12.385009285054478,14.768895627884765 25 | 23,1,0.06666666666666667,50,True,0.002,1.0,0.5,0.5,12.189474385398439,14.768895627884765 26 | 24,1,0.06666666666666667,50,True,0.01,2.0,0.36666666666666664,0.5,14.493298437140481,14.768895627884765 27 | 25,1,0.06666666666666667,50,True,0.004,2.0,0.43333333333333335,0.5,12.447379778883029,14.768895627884765 28 | 26,1,0.06666666666666667,50,True,0.002,2.0,0.43333333333333335,0.5,12.251844879226992,14.768895627884765 29 | 27,1,0.06666666666666667,50,False,0.01,0.5,0.6666666666666666,0.6333333333333333,9.212868039643599,9.38159917356677 30 | 28,1,0.06666666666666667,50,False,0.004,0.5,0.6666666666666666,0.6333333333333333,7.653046563157011,9.38159917356677 31 | 29,1,0.06666666666666667,50,False,0.002,0.5,0.6333333333333333,0.6333333333333333,8.239156709878324,9.38159917356677 32 | 30,1,0.06666666666666667,50,False,0.01,1.0,0.7333333333333333,0.6333333333333333,6.401263025217598,9.38159917356677 33 | 31,1,0.06666666666666667,50,False,0.004,1.0,0.7666666666666667,0.6333333333333333,5.97670507665729,9.38159917356677 34 | 32,1,0.06666666666666667,50,False,0.002,1.0,0.7333333333333333,0.6333333333333333,6.562815223378603,9.38159917356677 35 | 33,1,0.06666666666666667,50,False,0.01,2.0,0.7,0.6333333333333333,8.092708615230036,9.38159917356677 36 | 34,1,0.06666666666666667,50,False,0.004,2.0,0.7333333333333333,0.6333333333333333,6.763450479949353,9.38159917356677 37 | 35,1,0.06666666666666667,50,False,0.002,2.0,0.7,0.6333333333333333,7.349560626670666,9.38159917356677 38 | 36,2,0.06666666666666667,0,True,0.01,0.5,0.6304347826086957,0.6086956521739131,18.406665915128528,26.121032141784376 39 | 37,2,0.06666666666666667,0,True,0.004,0.5,0.5652173913043478,0.6086956521739131,28.816808953595746,26.121032141784376 40 | 38,2,0.06666666666666667,0,True,0.002,0.5,0.5217391304347826,0.6086956521739131,36.29349624290573,26.121032141784376 41 | 39,2,0.06666666666666667,0,True,0.01,1.0,0.6739130434782609,0.6086956521739131,22.76097505065813,26.121032141784376 42 | 40,2,0.06666666666666667,0,True,0.004,1.0,0.6521739130434783,0.6086956521739131,25.419010808744765,26.121032141784376 43 | 41,2,0.06666666666666667,0,True,0.002,1.0,0.6086956521739131,0.6086956521739131,31.230517367677027,26.121032141784376 44 | 42,2,0.06666666666666667,0,True,0.01,2.0,0.6521739130434783,0.6086956521739131,29.319861456649754,26.121032141784376 45 | 43,2,0.06666666666666667,0,True,0.004,2.0,0.6521739130434783,0.6086956521739131,29.380040266367487,26.121032141784376 46 | 44,2,0.06666666666666667,0,True,0.002,2.0,0.6304347826086957,0.6086956521739131,32.77014221029282,26.121032141784376 47 | 45,2,0.06666666666666667,0,False,0.01,0.5,0.7608695652173914,0.7608695652173914,10.103336921522732,7.7258983634019485 48 | 46,2,0.06666666666666667,0,False,0.004,0.5,0.7608695652173914,0.7608695652173914,10.103336921522732,7.7258983634019485 49 | 47,2,0.06666666666666667,0,False,0.002,0.5,0.7391304347826086,0.7608695652173914,10.260087935140406,7.7258983634019485 50 | 48,2,0.06666666666666667,0,False,0.01,1.0,0.7391304347826086,0.7608695652173914,11.885911318014804,7.7258983634019485 51 | 49,2,0.06666666666666667,0,False,0.004,1.0,0.7391304347826086,0.7608695652173914,11.766807950486717,7.7258983634019485 52 | 50,2,0.06666666666666667,0,False,0.002,1.0,0.7391304347826086,0.7608695652173914,11.766807950486717,7.7258983634019485 53 | 51,2,0.06666666666666667,0,False,0.01,2.0,0.7608695652173914,0.7608695652173914,10.883747277481712,7.7258983634019485 54 | 52,2,0.06666666666666667,0,False,0.004,2.0,0.7608695652173914,0.7608695652173914,10.93532760357724,7.7258983634019485 55 | 53,2,0.06666666666666667,0,False,0.002,2.0,0.7391304347826086,0.7608695652173914,11.885911318014804,7.7258983634019485 56 | 54,2,0.06666666666666667,50,True,0.01,0.5,0.4782608695652174,0.6086956521739131,40.33771136062602,26.121032141784376 57 | 55,2,0.06666666666666667,50,True,0.004,0.5,0.4782608695652174,0.6086956521739131,42.817401709631106,26.121032141784376 58 | 56,2,0.06666666666666667,50,True,0.002,0.5,0.4782608695652174,0.6086956521739131,43.69071357860578,26.121032141784376 59 | 57,2,0.06666666666666667,50,True,0.01,1.0,0.6086956521739131,0.6086956521739131,19.983094576012263,26.121032141784376 60 | 58,2,0.06666666666666667,50,True,0.004,1.0,0.6086956521739131,0.6086956521739131,21.037655955989145,26.121032141784376 61 | 59,2,0.06666666666666667,50,True,0.002,1.0,0.6086956521739131,0.6086956521739131,21.91096782496382,26.121032141784376 62 | 60,2,0.06666666666666667,50,True,0.01,2.0,0.6304347826086957,0.6086956521739131,20.131814243201056,26.121032141784376 63 | 61,2,0.06666666666666667,50,True,0.004,2.0,0.5869565217391305,0.6086956521739131,26.06490687595591,26.121032141784376 64 | 62,2,0.06666666666666667,50,True,0.002,2.0,0.5869565217391305,0.6086956521739131,26.938218744930577,26.121032141784376 65 | 63,2,0.06666666666666667,50,False,0.01,0.5,0.782608695652174,0.8478260869565217,7.164045116089563,4.352829972937275 66 | 64,2,0.06666666666666667,50,False,0.004,0.5,0.782608695652174,0.8478260869565217,7.164045116089563,4.352829972937275 67 | 65,2,0.06666666666666667,50,False,0.002,0.5,0.782608695652174,0.8478260869565217,7.164045116089563,4.352829972937275 68 | 66,2,0.06666666666666667,50,False,0.01,1.0,0.7608695652173914,0.8478260869565217,8.112659100632936,4.352829972937275 69 | 67,2,0.06666666666666667,50,False,0.004,1.0,0.7608695652173914,0.8478260869565217,9.008515737046222,4.352829972937275 70 | 68,2,0.06666666666666667,50,False,0.002,1.0,0.7608695652173914,0.8478260869565217,8.917135207466075,4.352829972937275 71 | 69,2,0.06666666666666667,50,False,0.01,2.0,0.7391304347826086,0.8478260869565217,8.460395617298138,4.352829972937275 72 | 70,2,0.06666666666666667,50,False,0.004,2.0,0.6086956521739131,0.8478260869565217,10.765141808828004,4.352829972937275 73 | 71,2,0.06666666666666667,50,False,0.002,2.0,0.5652173913043478,0.8478260869565217,12.395638969538565,4.352829972937275 74 | 72,3,0.06666666666666667,0,True,0.01,0.5,0.6470588235294118,0.6470588235294118,22.805264514759948,20.72266263527614 75 | 73,3,0.06666666666666667,0,True,0.004,0.5,0.6764705882352942,0.6470588235294118,14.394611024015642,20.72266263527614 76 | 74,3,0.06666666666666667,0,True,0.002,0.5,0.6470588235294118,0.6470588235294118,31.53672829613592,20.72266263527614 77 | 75,3,0.06666666666666667,0,True,0.01,1.0,0.6176470588235294,0.6470588235294118,23.97813934000761,20.72266263527614 78 | 76,3,0.06666666666666667,0,True,0.004,1.0,0.6764705882352942,0.6470588235294118,14.394611024015642,20.72266263527614 79 | 77,3,0.06666666666666667,0,True,0.002,1.0,0.6470588235294118,0.6470588235294118,31.53672829613592,20.72266263527614 80 | 78,3,0.06666666666666667,0,True,0.01,2.0,0.5882352941176471,0.6470588235294118,24.705896775818616,20.72266263527614 81 | 79,3,0.06666666666666667,0,True,0.004,2.0,0.6470588235294118,0.6470588235294118,15.567485849263301,20.72266263527614 82 | 80,3,0.06666666666666667,0,True,0.002,2.0,0.6176470588235294,0.6470588235294118,32.70960312138358,20.72266263527614 83 | 81,3,0.06666666666666667,0,False,0.01,0.5,0.7352941176470589,0.7352941176470589,5.8091286968172895,5.903234826964463 84 | 82,3,0.06666666666666667,0,False,0.004,0.5,0.7352941176470589,0.7352941176470589,5.8091286968172895,5.903234826964463 85 | 83,3,0.06666666666666667,0,False,0.002,0.5,0.7352941176470589,0.7352941176470589,5.8091286968172895,5.903234826964463 86 | 84,3,0.06666666666666667,0,False,0.01,1.0,0.7647058823529411,0.7352941176470589,5.548501261864381,5.903234826964463 87 | 85,3,0.06666666666666667,0,False,0.004,1.0,0.7647058823529411,0.7352941176470589,5.548501261864381,5.903234826964463 88 | 86,3,0.06666666666666667,0,False,0.002,1.0,0.7352941176470589,0.7352941176470589,5.8091286968172895,5.903234826964463 89 | 87,3,0.06666666666666667,0,False,0.01,2.0,0.7941176470588235,0.7352941176470589,4.06782101837275,5.903234826964463 90 | 88,3,0.06666666666666667,0,False,0.004,2.0,0.7941176470588235,0.7352941176470589,4.06782101837275,5.903234826964463 91 | 89,3,0.06666666666666667,0,False,0.002,2.0,0.7941176470588235,0.7352941176470589,5.039249110288832,5.903234826964463 92 | 90,3,0.06666666666666667,50,True,0.01,0.5,0.6470588235294118,0.6470588235294118,12.65837351782887,20.72266263527614 93 | 91,3,0.06666666666666667,50,True,0.004,0.5,0.6764705882352942,0.6470588235294118,10.95569423182788,20.72266263527614 94 | 92,3,0.06666666666666667,50,True,0.002,0.5,0.6764705882352942,0.6470588235294118,10.95569423182788,20.72266263527614 95 | 93,3,0.06666666666666667,50,True,0.01,1.0,0.5882352941176471,0.6470588235294118,13.827411684535477,20.72266263527614 96 | 94,3,0.06666666666666667,50,True,0.004,1.0,0.5882352941176471,0.6470588235294118,13.297607223782148,20.72266263527614 97 | 95,3,0.06666666666666667,50,True,0.002,1.0,0.6176470588235294,0.6470588235294118,12.569849787971137,20.72266263527614 98 | 96,3,0.06666666666666667,50,True,0.01,2.0,0.5882352941176471,0.6470588235294118,12.963579309980915,20.72266263527614 99 | 97,3,0.06666666666666667,50,True,0.004,2.0,0.5882352941176471,0.6470588235294118,12.433774849227587,20.72266263527614 100 | 98,3,0.06666666666666667,50,True,0.002,2.0,0.6176470588235294,0.6470588235294118,11.260900023979925,20.72266263527614 101 | 99,3,0.06666666666666667,50,False,0.01,0.5,0.7058823529411765,0.6764705882352942,5.266666924544476,16.076454506188917 102 | 100,3,0.06666666666666667,50,False,0.004,0.5,0.7058823529411765,0.6764705882352942,5.266666924544476,16.076454506188917 103 | 101,3,0.06666666666666667,50,False,0.002,0.5,0.7058823529411765,0.6764705882352942,5.266666924544476,16.076454506188917 104 | 102,3,0.06666666666666667,50,False,0.01,1.0,0.6176470588235294,0.6764705882352942,8.161179172825843,16.076454506188917 105 | 103,3,0.06666666666666667,50,False,0.004,1.0,0.6470588235294118,0.6764705882352942,6.9883043475781825,16.076454506188917 106 | 104,3,0.06666666666666667,50,False,0.002,1.0,0.6470588235294118,0.6764705882352942,6.9883043475781825,16.076454506188917 107 | 105,3,0.06666666666666667,50,False,0.01,2.0,0.6764705882352942,0.6764705882352942,6.0964347652478255,16.076454506188917 108 | 106,3,0.06666666666666667,50,False,0.004,2.0,0.6176470588235294,0.6764705882352942,8.326373112831467,16.076454506188917 109 | 107,3,0.06666666666666667,50,False,0.002,2.0,0.6176470588235294,0.6764705882352942,8.609073520232439,16.076454506188917 110 | 108,4,0.06666666666666667,0,True,0.01,0.5,0.5483870967741935,0.45161290322580644,11.667437301216122,49.530632326144165 111 | 109,4,0.06666666666666667,0,True,0.004,0.5,0.5806451612903226,0.45161290322580644,14.661774217398449,49.530632326144165 112 | 110,4,0.06666666666666667,0,True,0.002,0.5,0.5806451612903226,0.45161290322580644,18.06989978320171,49.530632326144165 113 | 111,4,0.06666666666666667,0,True,0.01,1.0,0.5483870967741935,0.45161290322580644,11.667437301216122,49.530632326144165 114 | 112,4,0.06666666666666667,0,True,0.004,1.0,0.5806451612903226,0.45161290322580644,14.661774217398449,49.530632326144165 115 | 113,4,0.06666666666666667,0,True,0.002,1.0,0.5806451612903226,0.45161290322580644,18.06989978320171,49.530632326144165 116 | 114,4,0.06666666666666667,0,True,0.01,2.0,0.5161290322580645,0.45161290322580644,14.602513580152454,49.530632326144165 117 | 115,4,0.06666666666666667,0,True,0.004,2.0,0.4838709677419355,0.45161290322580644,17.279512587740548,49.530632326144165 118 | 116,4,0.06666666666666667,0,True,0.002,2.0,0.5161290322580645,0.45161290322580644,20.241605214770505,49.530632326144165 119 | 117,4,0.06666666666666667,0,False,0.01,0.5,0.6774193548387096,0.6129032258064516,6.159362478700939,28.545705609492554 120 | 118,4,0.06666666666666667,0,False,0.004,0.5,0.6774193548387096,0.6129032258064516,6.159362478700939,28.545705609492554 121 | 119,4,0.06666666666666667,0,False,0.002,0.5,0.6129032258064516,0.6129032258064516,15.016326606153173,28.545705609492554 122 | 120,4,0.06666666666666667,0,False,0.01,1.0,0.6774193548387096,0.6129032258064516,5.261394459573129,28.545705609492554 123 | 121,4,0.06666666666666667,0,False,0.004,1.0,0.6451612903225806,0.6129032258064516,6.521511446475609,28.545705609492554 124 | 122,4,0.06666666666666667,0,False,0.002,1.0,0.6451612903225806,0.6129032258064516,6.931734406544592,28.545705609492554 125 | 123,4,0.06666666666666667,0,False,0.01,2.0,0.6774193548387096,0.6129032258064516,6.547773300167338,28.545705609492554 126 | 124,4,0.06666666666666667,0,False,0.004,2.0,0.6774193548387096,0.6129032258064516,5.261394459573129,28.545705609492554 127 | 125,4,0.06666666666666667,0,False,0.002,2.0,0.6774193548387096,0.6129032258064516,5.6716174196421125,28.545705609492554 128 | 126,4,0.06666666666666667,50,True,0.01,0.5,0.5806451612903226,0.45161290322580644,11.377912416186414,49.530632326144165 129 | 127,4,0.06666666666666667,50,True,0.004,0.5,0.5806451612903226,0.45161290322580644,16.82073122465955,49.530632326144165 130 | 128,4,0.06666666666666667,50,True,0.002,0.5,0.5806451612903226,0.45161290322580644,16.82073122465955,49.530632326144165 131 | 129,4,0.06666666666666667,50,True,0.01,1.0,0.5806451612903226,0.45161290322580644,9.768310155644716,49.530632326144165 132 | 130,4,0.06666666666666667,50,True,0.004,1.0,0.6129032258064516,0.45161290322580644,14.848979996343184,49.530632326144165 133 | 131,4,0.06666666666666667,50,True,0.002,1.0,0.6129032258064516,0.45161290322580644,14.848979996343184,49.530632326144165 134 | 132,4,0.06666666666666667,50,True,0.01,2.0,0.5161290322580645,0.45161290322580644,9.510083264478022,49.530632326144165 135 | 133,4,0.06666666666666667,50,True,0.004,2.0,0.45161290322580644,0.45161290322580644,16.481572846280017,49.530632326144165 136 | 134,4,0.06666666666666667,50,True,0.002,2.0,0.45161290322580644,0.45161290322580644,16.481572846280017,49.530632326144165 137 | 135,4,0.06666666666666667,50,False,0.01,0.5,0.8064516129032258,0.7741935483870968,11.108336106166545,13.020934772233144 138 | 136,4,0.06666666666666667,50,False,0.004,0.5,0.8064516129032258,0.7741935483870968,11.108336106166545,13.020934772233144 139 | 137,4,0.06666666666666667,50,False,0.002,0.5,0.8064516129032258,0.7741935483870968,11.108336106166545,13.020934772233144 140 | 138,4,0.06666666666666667,50,False,0.01,1.0,0.8064516129032258,0.7741935483870968,3.007122117276478,13.020934772233144 141 | 139,4,0.06666666666666667,50,False,0.004,1.0,0.8064516129032258,0.7741935483870968,3.007122117276478,13.020934772233144 142 | 140,4,0.06666666666666667,50,False,0.002,1.0,0.7419354838709677,0.7741935483870968,11.898665288031596,13.020934772233144 143 | 141,4,0.06666666666666667,50,False,0.01,2.0,0.7741935483870968,0.7741935483870968,3.6669971138744804,13.020934772233144 144 | 142,4,0.06666666666666667,50,False,0.004,2.0,0.7096774193548387,0.7741935483870968,3.8898913232330234,13.020934772233144 145 | 143,4,0.06666666666666667,50,False,0.002,2.0,0.6774193548387096,0.7741935483870968,4.457094691027842,13.020934772233144 146 | 144,5,0.06666666666666667,0,True,0.01,0.5,0.42857142857142855,0.35714285714285715,15.796299593681924,15.3973169944492 147 | 145,5,0.06666666666666667,0,True,0.004,0.5,0.42857142857142855,0.35714285714285715,15.796299593681924,15.3973169944492 148 | 146,5,0.06666666666666667,0,True,0.002,0.5,0.35714285714285715,0.35714285714285715,28.447434002244762,15.3973169944492 149 | 147,5,0.06666666666666667,0,True,0.01,1.0,0.42857142857142855,0.35714285714285715,15.796299593681924,15.3973169944492 150 | 148,5,0.06666666666666667,0,True,0.004,1.0,0.42857142857142855,0.35714285714285715,15.796299593681924,15.3973169944492 151 | 149,5,0.06666666666666667,0,True,0.002,1.0,0.35714285714285715,0.35714285714285715,28.447434002244762,15.3973169944492 152 | 150,5,0.06666666666666667,0,True,0.01,2.0,0.42857142857142855,0.35714285714285715,21.0023664541385,15.3973169944492 153 | 151,5,0.06666666666666667,0,True,0.004,2.0,0.42857142857142855,0.35714285714285715,19.209875961520538,15.3973169944492 154 | 152,5,0.06666666666666667,0,True,0.002,2.0,0.2857142857142857,0.35714285714285715,33.709430513958786,15.3973169944492 155 | 153,5,0.06666666666666667,0,False,0.01,0.5,0.21428571428571427,0.21428571428571427,37.02195687020899,37.02195687020899 156 | 154,5,0.06666666666666667,0,False,0.004,0.5,0.21428571428571427,0.21428571428571427,37.02195687020899,37.02195687020899 157 | 155,5,0.06666666666666667,0,False,0.002,0.5,0.21428571428571427,0.21428571428571427,37.02195687020899,37.02195687020899 158 | 156,5,0.06666666666666667,0,False,0.01,1.0,0.21428571428571427,0.21428571428571427,37.02195687020899,37.02195687020899 159 | 157,5,0.06666666666666667,0,False,0.004,1.0,0.21428571428571427,0.21428571428571427,37.02195687020899,37.02195687020899 160 | 158,5,0.06666666666666667,0,False,0.002,1.0,0.21428571428571427,0.21428571428571427,37.02195687020899,37.02195687020899 161 | 159,5,0.06666666666666667,0,False,0.01,2.0,0.2857142857142857,0.21428571428571427,27.10349911953718,37.02195687020899 162 | 160,5,0.06666666666666667,0,False,0.004,2.0,0.21428571428571427,0.21428571428571427,37.02195687020899,37.02195687020899 163 | 161,5,0.06666666666666667,0,False,0.002,2.0,0.21428571428571427,0.21428571428571427,35.160431195563255,37.02195687020899 164 | 162,5,0.06666666666666667,50,True,0.01,0.5,0.42857142857142855,0.35714285714285715,15.745509025981798,15.3973169944492 165 | 163,5,0.06666666666666667,50,True,0.004,0.5,0.35714285714285715,0.35714285714285715,23.559031160125347,15.3973169944492 166 | 164,5,0.06666666666666667,50,True,0.002,0.5,0.35714285714285715,0.35714285714285715,23.559031160125347,15.3973169944492 167 | 165,5,0.06666666666666667,50,True,0.01,1.0,0.42857142857142855,0.35714285714285715,15.4928679937083,15.3973169944492 168 | 166,5,0.06666666666666667,50,True,0.004,1.0,0.35714285714285715,0.35714285714285715,23.306390127851845,15.3973169944492 169 | 167,5,0.06666666666666667,50,True,0.002,1.0,0.35714285714285715,0.35714285714285715,23.306390127851845,15.3973169944492 170 | 168,5,0.06666666666666667,50,True,0.01,2.0,0.42857142857142855,0.35714285714285715,20.616503342308864,15.3973169944492 171 | 169,5,0.06666666666666667,50,True,0.004,2.0,0.35714285714285715,0.35714285714285715,28.430025476452403,15.3973169944492 172 | 170,5,0.06666666666666667,50,True,0.002,2.0,0.2857142857142857,0.35714285714285715,29.706766222183496,15.3973169944492 173 | 171,5,0.06666666666666667,50,False,0.01,0.5,0.42857142857142855,0.5,33.57875829669426,28.180453297180524 174 | 172,5,0.06666666666666667,50,False,0.004,0.5,0.5,0.5,28.180453297180524,28.180453297180524 175 | 173,5,0.06666666666666667,50,False,0.002,0.5,0.5,0.5,28.180453297180524,28.180453297180524 176 | 174,5,0.06666666666666667,50,False,0.01,1.0,0.42857142857142855,0.5,29.047897540019495,28.180453297180524 177 | 175,5,0.06666666666666667,50,False,0.004,1.0,0.5,0.5,28.180453297180524,28.180453297180524 178 | 176,5,0.06666666666666667,50,False,0.002,1.0,0.5,0.5,28.180453297180524,28.180453297180524 179 | 177,5,0.06666666666666667,50,False,0.01,2.0,0.35714285714285715,0.5,33.43842801231461,28.180453297180524 180 | 178,5,0.06666666666666667,50,False,0.004,2.0,0.42857142857142855,0.5,33.21972570460294,28.180453297180524 181 | 179,5,0.06666666666666667,50,False,0.002,2.0,0.42857142857142855,0.5,33.21972570460294,28.180453297180524 182 | -------------------------------------------------------------------------------- /results/measurements.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaapbakken/MapMatchingHMM/422ff1fc6895efd38aa6f92d614deaa5aeb54a96/results/measurements.p -------------------------------------------------------------------------------- /results/naive_estimates.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaapbakken/MapMatchingHMM/422ff1fc6895efd38aa6f92d614deaa5aeb54a96/results/naive_estimates.p -------------------------------------------------------------------------------- /results/routes.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaapbakken/MapMatchingHMM/422ff1fc6895efd38aa6f92d614deaa5aeb54a96/results/routes.p -------------------------------------------------------------------------------- /results/simulation_parameters.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaapbakken/MapMatchingHMM/422ff1fc6895efd38aa6f92d614deaa5aeb54a96/results/simulation_parameters.p -------------------------------------------------------------------------------- /run_batch.py: -------------------------------------------------------------------------------- 1 | from data_interface import query_ways_postgis_db 2 | from data_interface import query_nodes_postgis_db 3 | 4 | from data_wrangling import get_accepted_highways 5 | from data_wrangling import create_node_dict 6 | from data_wrangling import create_highway_dict 7 | from data_wrangling import get_required_nodes 8 | from data_wrangling import find_intersections 9 | from data_wrangling import create_state_space_representations 10 | from data_wrangling import remove_unconnected_states 11 | from data_wrangling import remove_unconnected_highways 12 | 13 | from simulation import simulate_route 14 | from simulation import simulate_observations 15 | 16 | from hmm import viterbi 17 | from hmm import backward_recursions 18 | from hmm import forward_recursions 19 | from hmm import transition_probabilties_by_weighting_route_length 20 | 21 | from hmm_extensions import emission_probabilities 22 | 23 | from visualization import plot_results 24 | 25 | from tools import state_sequence_to_node_sequence 26 | from tools import get_accuracy_of_estimate 27 | from tools import generate_base_locations 28 | 29 | from naive_estimation import spatially_closest_states 30 | 31 | from batch_processing import simulate_routes 32 | from batch_processing import simulate_measurements 33 | from batch_processing import get_estimates 34 | from batch_processing import results_as_dataframe 35 | 36 | from itertools import product 37 | 38 | import random 39 | import pickle 40 | import sys 41 | import os 42 | 43 | import numpy as np 44 | import pandas as pd 45 | 46 | random.seed(3265) 47 | np.random.seed(3265) 48 | 49 | password = sys.argv[1] 50 | 51 | print("Fetching and processing data..") 52 | 53 | bbox = [10.3914799928,63.4271680224,10.4036679506,63.4323125008] 54 | ways = query_ways_postgis_db(bbox, password) 55 | 56 | accepted_highways = get_accepted_highways(ways) 57 | 58 | required_nodes = get_required_nodes(accepted_highways) 59 | 60 | nodes = query_nodes_postgis_db(required_nodes, password) 61 | 62 | node_dict = create_node_dict(nodes) 63 | 64 | untrimmed_state_space = create_state_space_representations(accepted_highways, node_dict) 65 | state_space = remove_unconnected_states(untrimmed_state_space) 66 | 67 | highways_in_state_space = remove_unconnected_highways(accepted_highways, state_space) 68 | 69 | print("Size of state space: {}".format(len(state_space))) 70 | 71 | highway_dict = create_highway_dict(highways_in_state_space) 72 | intersections = find_intersections(highway_dict, node_dict) 73 | 74 | n = 5 75 | route_length = 200 76 | 77 | routes = simulate_routes(n, highway_dict, intersections, route_length) 78 | 79 | base_max_range = 100 80 | base_locations = [generate_base_locations(bbox, 0), generate_base_locations(bbox, 50)] 81 | 82 | polling_frequencies = [1/15] 83 | missing_data = [True, False] 84 | observation_simulation_parameters = list(product(polling_frequencies, base_locations, missing_data)) 85 | 86 | measurements = list() 87 | 88 | gps_variance = 5 89 | speed_limit = 8 90 | 91 | base_locations_list = list() 92 | for i, parameter_list in enumerate(observation_simulation_parameters): 93 | print("Measurement run #{}".format(i + 1)) 94 | 95 | polling_frequency = parameter_list[0] 96 | base_locations = parameter_list[1] 97 | missing_data = parameter_list[2] 98 | 99 | gps_measurements_list, signal_measurements_list, measurement_states_list =\ 100 | simulate_measurements(node_dict, state_space, polling_frequency, missing_data, routes, base_locations, base_max_range, gps_variance, speed_limit) 101 | 102 | 103 | base_locations_list.append(base_locations) 104 | measurements.append([gps_measurements_list, signal_measurements_list, measurement_states_list]) 105 | 106 | 107 | pickling_routes_on = open("routes.p", "wb") 108 | pickle.dump(routes, pickling_routes_on) 109 | pickling_measurements_on = open("measurements.p", "wb") 110 | pickle.dump(measurements, pickling_measurements_on) 111 | pickling_routes_on.close() 112 | pickling_measurements_on.close() 113 | 114 | 115 | maximum_route_length = speed_limit/min(polling_frequencies)*2 116 | emission_variances = [1/2, 1, 2] 117 | transition_decays = [1/100, 1/250, 1/500] 118 | estimation_parameters = list(product(emission_variances, transition_decays)) 119 | 120 | pickling_estimation_parameters_on = open("estimation_parameters.", "wb") 121 | pickling_simulation_parameters_on = open("simulation_parameters.p", "wb") 122 | 123 | pickle.dump(estimation_parameters, pickling_estimation_parameters_on) 124 | pickle.dump(observation_simulation_parameters, pickling_simulation_parameters_on) 125 | 126 | print("Estimating states..") 127 | 128 | estimates = list() 129 | naive_estimates = list() 130 | 131 | for i, measurement in enumerate(measurements): 132 | print("Measurement run #{}".format(i + 1)) 133 | gps_measurements_list = measurement[0] 134 | signal_measurements_list = measurement[1] 135 | base_locations = base_locations_list[i] 136 | for j, parameter_list in enumerate(estimation_parameters): 137 | print("Estimation run #{}".format(j + 1)) 138 | emission_variance = parameter_list[0] 139 | transition_decay = parameter_list[1] 140 | print("Estimation..") 141 | estimated_states_list, naive_estimates_list =\ 142 | get_estimates(state_space, gps_measurements_list, signal_measurements_list, emission_variance,\ 143 | transition_decay, maximum_route_length, base_locations, base_max_range) 144 | print("Benchmark estimation..") 145 | estimates.append(estimated_states_list) 146 | naive_estimates.append(naive_estimates_list) 147 | 148 | pickling_estimates_on = open("estimates.p", "wb") 149 | pickle.dump(estimates, pickling_estimates_on) 150 | pickling_naive_estimates_on = open("naive_estimates.p", "wb") 151 | pickle.dump(naive_estimates, pickling_naive_estimates_on) 152 | pickling_estimates_on.close() 153 | pickling_naive_estimates_on.close() 154 | 155 | measurements_df = results_as_dataframe(measurements, estimates, naive_estimates, \ 156 | observation_simulation_parameters, estimation_parameters, state_space) 157 | 158 | measurements_df.to_csv(os.path.join(os.curdir, "measurements.csv")) -------------------------------------------------------------------------------- /run_single.py: -------------------------------------------------------------------------------- 1 | from data_interface import query_ways_postgis_db 2 | from data_interface import query_nodes_postgis_db 3 | 4 | from data_wrangling import get_accepted_highways 5 | from data_wrangling import create_node_dict 6 | from data_wrangling import create_highway_dict 7 | from data_wrangling import get_required_nodes 8 | from data_wrangling import find_intersections 9 | from data_wrangling import create_state_space_representations 10 | from data_wrangling import remove_unconnected_states 11 | 12 | from simulation import simulate_route 13 | from simulation import simulate_observations 14 | 15 | from hmm import transition_probabilties_by_weighting_route_length 16 | from hmm import viterbi 17 | from hmm import backward_recursions 18 | from hmm import forward_recursions 19 | 20 | from hmm_extensions import emission_probabilities 21 | 22 | from visualization import plot_results 23 | 24 | from tools import state_sequence_to_node_sequence 25 | from tools import get_accuracy_of_estimate 26 | from tools import generate_base_locations 27 | 28 | from naive_estimation import spatially_closest_states 29 | 30 | import random 31 | import numpy as np 32 | 33 | import sys 34 | 35 | np.random.seed(3265) 36 | random.seed(3265) 37 | 38 | password = sys.argv[1] 39 | 40 | print("Fetching and processing data..") 41 | 42 | bbox = [10.3914799928,63.4271680224,10.4036679506,63.4323125008] 43 | ways = query_ways_postgis_db(bbox, password) 44 | 45 | accepted_highways = get_accepted_highways(ways) 46 | 47 | required_nodes = get_required_nodes(accepted_highways) 48 | 49 | highway_dict = create_highway_dict(accepted_highways) 50 | 51 | nodes = query_nodes_postgis_db(required_nodes, password) 52 | 53 | node_dict = create_node_dict(nodes) 54 | 55 | untrimmed_state_space = create_state_space_representations(accepted_highways, node_dict) 56 | state_space = remove_unconnected_states(untrimmed_state_space) 57 | 58 | print("Size of state space: {}".format(len(state_space))) 59 | 60 | intersections = find_intersections(highway_dict, node_dict) 61 | 62 | starting_highway = random.choice(list(highway_dict.keys())) 63 | starting_node = random.choice(highway_dict[starting_highway]['data']['nd']) 64 | 65 | speed_limit = 8 66 | polling_frequency = 1/15 67 | gps_variance = 5 68 | measurement_variance = 1 69 | transition_decay = 1/500 70 | maximum_route_length = speed_limit/polling_frequency*2 71 | no_of_bases = 50 72 | base_max_range = 50 73 | route_length = 200 74 | 75 | print("Simulating route..") 76 | 77 | base_locations = generate_base_locations(bbox, no_of_bases) 78 | 79 | simulated_route = simulate_route(highway_dict, starting_node, starting_highway, intersections, route_length) 80 | gps_measurements, signal_measurements, measurement_states = simulate_observations(simulated_route, node_dict, gps_variance, polling_frequency,\ 81 | [speed_limit]*len(simulated_route), base_locations, np.array([base_max_range]*no_of_bases), state_space) 82 | 83 | 84 | print("Calculating transition probabilities..") 85 | tp = transition_probabilties_by_weighting_route_length(state_space, transition_decay, maximum_route_length) 86 | 87 | print("Calculating emission probabilities..") 88 | ep = emission_probabilities(gps_measurements, measurement_variance, signal_measurements, base_locations, np.array([500]*no_of_bases), state_space) 89 | 90 | N = len(state_space) 91 | 92 | print("Running Viterbi..") 93 | estimated_states = viterbi(tp, ep, np.array([1/N]*N)) 94 | 95 | 96 | naive_estimate = spatially_closest_states(gps_measurements, state_space) 97 | 98 | print("Accuracy with naive method: {}".format(np.mean(measurement_states == naive_estimate))) 99 | print("Accuracy with hidden markov model: {}".format(np.mean(estimated_states == measurement_states))) -------------------------------------------------------------------------------- /run_single_remotely.py: -------------------------------------------------------------------------------- 1 | from data_interface import query_osm_api 2 | 3 | from data_wrangling import get_accepted_highways 4 | from data_wrangling import create_node_dict 5 | from data_wrangling import create_highway_dict 6 | from data_wrangling import get_required_nodes 7 | from data_wrangling import find_intersections 8 | from data_wrangling import create_state_space_representations 9 | from data_wrangling import remove_unconnected_states 10 | 11 | from simulation import simulate_route 12 | from simulation import simulate_observations 13 | 14 | from hmm import transition_probabilties_by_weighting_route_length 15 | from hmm import viterbi 16 | from hmm import backward_recursions 17 | from hmm import forward_recursions 18 | 19 | from hmm_extensions import emission_probabilities 20 | 21 | from visualization import plot_results 22 | 23 | from tools import state_sequence_to_node_sequence 24 | from tools import get_accuracy_of_estimate 25 | from tools import generate_base_locations 26 | 27 | from naive_estimation import spatially_closest_states 28 | 29 | import random 30 | import numpy as np 31 | import matplotlib.pyplot as plt 32 | 33 | import sys 34 | 35 | np.random.seed(3265) 36 | random.seed(3265) 37 | 38 | print("Fetching and processing data..") 39 | 40 | bbox = [10.411165,63.415631,10.432451,63.425788] 41 | nodes, ways = query_osm_api(bbox) 42 | 43 | accepted_highways = get_accepted_highways(ways) 44 | 45 | required_nodes = get_required_nodes(accepted_highways) 46 | 47 | highway_dict = create_highway_dict(accepted_highways) 48 | 49 | node_dict = create_node_dict(nodes) 50 | 51 | untrimmed_state_space = create_state_space_representations(accepted_highways, node_dict) 52 | state_space = remove_unconnected_states(untrimmed_state_space) 53 | 54 | print("Size of state space: {}".format(len(state_space))) 55 | 56 | intersections = find_intersections(highway_dict, node_dict) 57 | 58 | starting_highway = random.choice(list(highway_dict.keys())) 59 | starting_node = random.choice(highway_dict[starting_highway]['data']['nd']) 60 | 61 | speed_limit = 5 62 | polling_frequency = 1/15 63 | gps_variance = 5 64 | measurement_variance = 2 65 | transition_decay = 1/100 66 | maximum_route_length = 200 67 | no_of_bases = 5 68 | base_max_range = 500 69 | route_length = 50 70 | 71 | print("Simulating route..") 72 | 73 | base_locations = generate_base_locations(bbox, no_of_bases) 74 | 75 | simulated_route = simulate_route(highway_dict, starting_node, starting_highway, intersections, route_length) 76 | gps_measurements, signal_measurements, measurement_states = simulate_observations(simulated_route, node_dict, gps_variance, polling_frequency,\ 77 | [speed_limit]*len(simulated_route), base_locations, np.array([base_max_range]*no_of_bases), state_space) 78 | 79 | 80 | print("Calculating transition probabilities..") 81 | tp = transition_probabilties_by_weighting_route_length(state_space, transition_decay, maximum_route_length) 82 | 83 | print("Calculating emission probabilities..") 84 | ep = emission_probabilities(gps_measurements, measurement_variance, signal_measurements, base_locations, np.array([500]*no_of_bases), state_space) 85 | 86 | N = len(state_space) 87 | 88 | print("Running Viterbi..") 89 | estimated_states = viterbi(tp, ep, np.array([1/N]*N)) 90 | 91 | 92 | naive_estimate = spatially_closest_states(gps_measurements, state_space) 93 | 94 | print("Accuracy with naive method: {}".format(np.mean(measurement_states == naive_estimate))) 95 | print("Accuracy with hidden markov model: {}".format(np.mean(estimated_states == measurement_states))) -------------------------------------------------------------------------------- /simulation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.stats import beta, norm, bernoulli 3 | 4 | from data_wrangling import create_segment_list 5 | 6 | from tools import edges_to_states 7 | 8 | def simulate_route(highway_dict, starting_node, starting_highway, intersections, n): 9 | current_highway_id = starting_highway 10 | current_node_id = starting_node 11 | move = True 12 | steps = 0 13 | route = [] 14 | direction = 1 15 | 16 | while move is True: 17 | route.append(current_node_id) 18 | if current_node_id in intersections: 19 | new_highway_candidates = intersections[current_node_id] 20 | new_highway_id = np.random.choice(new_highway_candidates) 21 | new_highway_nodes = highway_dict[new_highway_id]['data']['nd'] 22 | position_on_new_highway = np.where(current_node_id == np.array(new_highway_nodes))[0][0] 23 | if position_on_new_highway == len(new_highway_nodes) - 1: 24 | direction = -1 25 | elif position_on_new_highway == 0: 26 | direction = 1 27 | current_node_id = new_highway_nodes[position_on_new_highway + direction] 28 | current_highway_id = new_highway_id 29 | else: 30 | current_highway_nodes = highway_dict[current_highway_id]['data']['nd'] 31 | position_on_current_highway = np.where(current_node_id == np.array(current_highway_nodes))[0][0] 32 | if position_on_current_highway == len(current_highway_nodes) - 1: 33 | direction = -1 34 | elif position_on_current_highway == 0: 35 | direction = 1 36 | current_node_id = current_highway_nodes[position_on_current_highway + direction] 37 | steps += 1 38 | if steps > n: 39 | move = False 40 | return route 41 | 42 | def simulate_gps_signals(route, node_dict, variance, polling_rate, max_speed): 43 | measurements = [] 44 | measurement_edges = [] 45 | segments, coordinate_array = create_segment_list(route, node_dict) 46 | measurements.append((coordinate_array[0, 0] + norm.rvs(0, variance), coordinate_array[0, 1] + norm.rvs(0, variance))) 47 | measurement_edges.append((route[0], route[1])) 48 | remaining_space = 0 49 | offset = 0 50 | for i in range(len(route) - 1): 51 | edge = (route[i], route[i + 1]) 52 | #Segment 53 | s_i = segments[i] 54 | #Speed limit 55 | l_i = max_speed[i] 56 | #Length of segment 57 | d_i = np.linalg.norm(coordinate_array[i+1, :] - coordinate_array[i, :].T) 58 | #Total time when driving at speed limit 59 | t_i = d_i/l_i 60 | #Number of measurements 61 | m_i = t_i*polling_rate 62 | #Distance between each measurement 63 | dpm = d_i/m_i 64 | #Slope 65 | a = (coordinate_array[i + 1, 1] - coordinate_array[i, 1])/(np.abs(coordinate_array[i + 1, 0] - coordinate_array[i, 0])) 66 | #Angle 67 | theta = np.arctan(a) 68 | #Direction of movement on x - axis 69 | direction = np.sign(coordinate_array[i+1, 0] - coordinate_array[i, 0]) 70 | #Available space 71 | available_space = d_i 72 | required_space = dpm - remaining_space 73 | #Counter 74 | #While points can be added on current segment 75 | while available_space >= required_space: 76 | #Length of movement along x - axis 77 | delta_x = np.cos(theta)*(required_space) 78 | offset += delta_x 79 | #Measurements 80 | x = coordinate_array[i, 0] 81 | measurements.append((x + direction*offset + norm.rvs(0, variance), s_i(x + direction*offset) + norm.rvs(0, variance))) 82 | measurement_edges.append(edge) 83 | #New distance on segment has been covered 84 | available_space -= required_space 85 | #Residual distance is zero after adding it once on one segment 86 | required_space = dpm 87 | remaining_space = 0 88 | #Iterating counter 89 | #Residual distance is updated. 90 | remaining_space += available_space 91 | offset = 0 92 | return np.array(measurements), np.array(measurement_edges) 93 | 94 | def simulate_gps_observations(route, node_dict, variance, polling_rate, max_speed): 95 | true_positions = [] 96 | measurements = [] 97 | measurement_edges = [] 98 | segments, coordinate_array = create_segment_list(route, node_dict) 99 | measurements.append((coordinate_array[0, 0] + norm.rvs(0, variance), coordinate_array[0, 1] + norm.rvs(0, variance))) 100 | true_positions.append((coordinate_array[0, 0], coordinate_array[0, 1])) 101 | measurement_edges.append((route[0], route[1])) 102 | remaining_space = 0 103 | offset = 0 104 | for i in range(len(route) - 1): 105 | edge = (route[i], route[i + 1]) 106 | #Segment 107 | s_i = segments[i] 108 | #Speed limit 109 | l_i = max_speed[i] 110 | #Length of segment 111 | d_i = np.linalg.norm(coordinate_array[i+1, :] - coordinate_array[i, :].T) 112 | #Total time when driving at speed limit 113 | t_i = d_i/l_i 114 | #Number of measurements 115 | m_i = t_i*polling_rate 116 | #Distance between each measurement 117 | dpm = d_i/m_i 118 | #Slope 119 | a = (coordinate_array[i + 1, 1] - coordinate_array[i, 1])/(np.abs(coordinate_array[i + 1, 0] - coordinate_array[i, 0])) 120 | #Angle 121 | theta = np.arctan(a) 122 | #Direction of movement on x - axis 123 | direction = np.sign(coordinate_array[i+1, 0] - coordinate_array[i, 0]) 124 | #Available space 125 | available_space = d_i 126 | required_space = dpm - remaining_space 127 | #Counter 128 | #While points can be added on current segment 129 | while available_space >= required_space: 130 | #Length of movement along x - axis 131 | delta_x = np.cos(theta)*(required_space) 132 | offset += delta_x 133 | #Measurements 134 | x = coordinate_array[i, 0] 135 | measurements.append((x + direction*offset + norm.rvs(0, variance), s_i(x + direction*offset) + norm.rvs(0, variance))) 136 | true_positions.append((x + direction*offset, s_i(x + direction*offset))) 137 | measurement_edges.append(edge) 138 | #New distance on segment has been covered 139 | available_space -= required_space 140 | #Residual distance is zero after adding it once on one segment 141 | required_space = dpm 142 | remaining_space = 0 143 | #Iterating counter 144 | #Residual distance is updated. 145 | remaining_space += available_space 146 | offset = 0 147 | return np.array(measurements), np.array(measurement_edges), np.array(true_positions) 148 | 149 | from scipy.stats import beta, bernoulli 150 | 151 | def generate_signal_strength(measurement_locations, base_locations, base_max_ranges): 152 | measurement_observations = np.zeros((measurement_locations.shape[0], base_locations.shape[0])) 153 | for measurement_id, position in enumerate(measurement_locations): 154 | signal_strengths = list() 155 | for base_index, base_position in enumerate(base_locations): 156 | distance = np.linalg.norm(position - base_position) 157 | signal_received = bernoulli.rvs(max(0, 1 - distance/(base_max_ranges[base_index]))) 158 | signal_strength = 0 159 | if signal_received: 160 | signal_strength = beta.rvs(2, 5*distance/base_max_ranges[base_index]) 161 | signal_strengths.append(signal_strength*signal_received) 162 | measurement_observations[measurement_id, :] = np.array(signal_strengths) 163 | return measurement_observations 164 | 165 | def simulate_observations(route, node_dict, gps_variance, gps_polling_rate, route_speed_limits, base_locations, base_max_ranges, state_space): 166 | gps_measurements, measurement_edges, true_measurement_positions = simulate_gps_observations(route, node_dict, gps_variance, gps_polling_rate, route_speed_limits) 167 | measurement_states = edges_to_states(measurement_edges, state_space) 168 | signal_measurements = generate_signal_strength(true_measurement_positions, base_locations, base_max_ranges) 169 | return gps_measurements, signal_measurements, measurement_states -------------------------------------------------------------------------------- /tools.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import utm 3 | 4 | def convert_to_utm(coordinate_array): 5 | return np.array([utm.from_latlon(coordinate_array[i, 0], coordinate_array[i, 1])[:2] 6 | for i in range(coordinate_array.shape[0])]) 7 | 8 | def edges_to_nodes(edge_set): 9 | node_list = list() 10 | for edge in edge_set: 11 | node_list += [edge[0]] + [edge[1]] 12 | return node_list 13 | 14 | def state_sequence_to_node_sequence(state_sequence, state_space): 15 | node_sequence = list() 16 | first_state = state_sequence[0] 17 | last_state = state_sequence[-1] 18 | for state_id in state_sequence: 19 | edge = state_space[int(state_id)]['edge'] 20 | if state_id in (first_state, last_state): 21 | node_sequence.append(edge[0]) 22 | node_sequence.append(edge[1]) 23 | else: 24 | node_sequence.append(edge[1]) 25 | 26 | return node_sequence 27 | 28 | def get_accuracy_of_estimate(measurement_edges, estimated_states, state_space): 29 | N = estimated_states.shape[0] 30 | estimated_edges = np.array([state_space[int(state)]['edge'] for state in estimated_states]) 31 | no = 0 32 | for i in range(N): 33 | no += int(np.all(np.array(measurement_edges)[i, :] == estimated_edges[i, :])) 34 | return no/N 35 | 36 | def edge_to_state(edge, state_space): 37 | unordered_edge = set(edge) 38 | search = True 39 | for state in state_space: 40 | if state['edge_set'] == unordered_edge: 41 | return state['id'] 42 | return None 43 | 44 | def edges_to_states(edge_array, state_space): 45 | states = list() 46 | for row in range(edge_array.shape[0]): 47 | states.append(edge_to_state(edge_array[row, :], state_space)) 48 | return states 49 | 50 | def generate_base_locations(bbox, n): 51 | bbox_array = np.array(bbox).reshape(2,2) 52 | 53 | ll_corner = utm.from_latlon(bbox[1], bbox[0])[:2] 54 | ur_corner = utm.from_latlon(bbox[3], bbox[2])[:2] 55 | 56 | x = np.random.uniform(ll_corner[0], ur_corner[0], n) 57 | y = np.random.uniform(ll_corner[1], ur_corner[1], n) 58 | 59 | return np.array(list(zip(x,y))) -------------------------------------------------------------------------------- /validation.py: -------------------------------------------------------------------------------- 1 | from hmm import create_connection_dictionary 2 | from hmm import recursive_neighbour_search 3 | 4 | import numpy as np 5 | 6 | def estimation_accuracy(measurement_edges, estimated_states): 7 | N = estimated_states.shape[0] 8 | estimated_edges = np.array([state_space[int(state)]['edge'] for state in estimated_states]) 9 | no = 0 10 | for i in range(N): 11 | no += int(np.all(np.array(measurement_edges)[i, :] == estimated_edges[i, :])) 12 | return no/N 13 | 14 | def distance_to_true_state(estimated_states, true_states, state_space): 15 | distances = np.zeros((estimated_states.shape)) 16 | connection_dictionary = create_connection_dictionary(state_space) 17 | for i, state_id in enumerate(estimated_states): 18 | distance_dict = recursive_neighbour_search(int(state_id), dict(), connection_dictionary, state_space, 0, 1000) 19 | distances[i] = distance_dict[true_states[i]] 20 | return distances -------------------------------------------------------------------------------- /visualization.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | 4 | from scipy.stats import norm 5 | 6 | from data_interface import query_ways_postgis_db 7 | from data_interface import query_nodes_postgis_db 8 | 9 | from data_wrangling import get_accepted_highways 10 | from data_wrangling import create_node_dict 11 | from data_wrangling import create_highway_dict 12 | from data_wrangling import get_required_nodes 13 | from data_wrangling import get_coordinates_of_nodes 14 | 15 | from tools import convert_to_utm 16 | 17 | 18 | def plot_highway(highway, node_dict, color, alpha=1): 19 | node_ids = highway['data']['nd'] 20 | coordinate_array = get_coordinates_of_nodes(node_ids, node_dict) 21 | utm_coordinate_array = convert_to_utm(coordinate_array) 22 | plt.plot(utm_coordinate_array[:, 0], utm_coordinate_array[:, 1], c=color, alpha=alpha) 23 | 24 | def plot_nodes(node_ids, node_dict, color, limit_axis=True, scatter=False, alpha=1, linestyle='-'): 25 | coordinate_array = get_coordinates_of_nodes(node_ids, node_dict) 26 | utm_coordinate_array = convert_to_utm(coordinate_array) 27 | if scatter: 28 | plt.scatter(utm_coordinate_array[:, 0], utm_coordinate_array[:, 1], c=color, alpha=alpha) 29 | else: 30 | plt.plot(utm_coordinate_array[:, 0], utm_coordinate_array[:, 1], c=color, alpha=alpha, linestyle=linestyle) 31 | if limit_axis: 32 | ax = plt.gca() 33 | ax.set_xlim([np.min(utm_coordinate_array[:, 0])-100, np.max(utm_coordinate_array[:, 0])+100]) 34 | ax.set_ylim([np.min(utm_coordinate_array[:, 1])-100, np.max(utm_coordinate_array[:, 1])+100]) 35 | ax.set_aspect(1.0) 36 | 37 | def plot_road_network(highways, node_dict, intersections): 38 | for highway in highways: 39 | plot_highway(highway, node_dict, 'b', alpha=0.2) 40 | plot_nodes(list(intersections.keys()), node_dict, 'y', scatter=True) 41 | plt.show() 42 | 43 | def plot_route(highways, node_dict, route, measurements=None): 44 | for highway in highways: 45 | plot_highway(highway, node_dict, 'b', alpha=0.2) 46 | plot_nodes(route, node_dict, 'y', scatter=False) 47 | if measurements is not None: 48 | plt.scatter(measurements[:, 0], measurements[:, 1], s=50, c='r') 49 | plt.show() 50 | 51 | def plot_edge(edge, node_dict, color, alpha=1, linestyle='-'): 52 | node_ids = list(edge) 53 | coordinate_array = get_coordinates_of_nodes(node_ids, node_dict) 54 | utm_coordinate_array = convert_to_utm(coordinate_array) 55 | plt.plot(utm_coordinate_array[:, 0], utm_coordinate_array[:, 1], c=color, alpha=alpha, linestyle=linestyle) 56 | 57 | def plot_results(state_space, node_dict, measurements, true_edges, estimated_states): 58 | for state in state_space: 59 | plot_edge(state['edge'], node_dict, color='b', alpha=0.2) 60 | plt.scatter(measurements[:, 0], measurements[:, 1], s=5, color='m') 61 | for edge in true_edges: 62 | plot_edge(edge, node_dict, color='g', alpha=0.8) 63 | for state in estimated_states: 64 | plot_edge(state_space[int(state)]['edge'], node_dict, color='y', linestyle=':') 65 | ax = plt.gca() 66 | ax.set_xlim([np.min(measurements[:, 0])-100, np.max(measurements[:, 0])+100]) 67 | ax.set_ylim([np.min(measurements[:, 1])-100, np.max(measurements[:, 1])+100]) 68 | plt.show() 69 | 70 | from data_wrangling import get_coordinates_of_nodes 71 | 72 | from tools import convert_to_utm 73 | 74 | import matplotlib.pyplot as plt 75 | import numpy as np 76 | 77 | from scipy.stats import norm 78 | 79 | class MapMatchingVisualization: 80 | def __init__(self, highways, node_dict, state_space, figsize): 81 | self.node_dict = node_dict 82 | self.node_sequences = self.convert_highways_to_nodes(highways, node_dict) 83 | self.coordinate_arrays = list() 84 | for node_sequence in self.node_sequences: 85 | utm_coordinate_array = self.convert_nodes_to_coordinate_array(node_sequence) 86 | self.coordinate_arrays.append(utm_coordinate_array) 87 | self.state_space = state_space 88 | 89 | self.fig, self.ax = plt.subplots(figsize=figsize); 90 | 91 | def convert_nodes_to_coordinate_array(self, nodes): 92 | coordinate_array = get_coordinates_of_nodes(nodes, self.node_dict) 93 | return convert_to_utm(coordinate_array) 94 | 95 | def convert_highways_to_nodes(self, highways, node_dict): 96 | list_of_node_sequences = list() 97 | for highway in highways: 98 | nodes = highway['data']['nd'] 99 | list_of_node_sequences.append(nodes) 100 | return list_of_node_sequences 101 | 102 | 103 | 104 | def plot_road_network(self, color, size, alpha): 105 | for coord_array in self.coordinate_arrays: 106 | self.ax.plot(coord_array[:, 0], coord_array[:, 1], lw=size, color=color, alpha=alpha); 107 | 108 | def plot_state_sequence(self, state_sequence, color, size, label=False): 109 | for i, state_id in enumerate(state_sequence): 110 | state = self.state_space[state_id] 111 | nodes = list(state['edge']) 112 | coord_array = self.convert_nodes_to_coordinate_array(nodes) 113 | self.ax.plot(coord_array[:, 0], coord_array[:, 1], lw=size, color=color) 114 | if label: 115 | self.ax.text(np.mean(coord_array[:, 0]) + norm.rvs(0, 10), np.mean(coord_array[:, 1]) + norm.rvs(0,10), str(i)); 116 | 117 | def plot_bases(self, base_locations, color, size): 118 | self.ax.scatter(base_locations[:, 0], base_locations[:, 1], color=color, s=size); 119 | pass 120 | 121 | def plot_base_range(self, base_locations, base_max_range): 122 | for location in base_locations: 123 | circle = plt.Circle(location, base_max_range, fill=False, color='black') 124 | self.ax.add_artist(circle); 125 | 126 | def shrink_to_fit_state_sequence(self, state_sequence, margin): 127 | min_x = np.inf 128 | max_x = -np.inf 129 | min_y = np.inf 130 | max_y = -np.inf 131 | 132 | for state_id in state_sequence: 133 | nodes = list(self.state_space[state_id]['edge']) 134 | array = self.convert_nodes_to_coordinate_array(nodes) 135 | if np.min(array[:, 0]) < min_x: 136 | min_x = np.min(array[:, 0]) 137 | if np.max(array[:, 0]) > max_x: 138 | max_x = np.max(array[:, 0]) 139 | if np.min(array[:, 1]) < min_y: 140 | min_y = np.min(array[:, 1]) 141 | if np.max(array[:, 1]) > max_y: 142 | max_y = np.max(array[:, 1]) 143 | bbox = [min_x, min_y, max_x, max_y] 144 | self.shrink_plot_to_fit(bbox, margin) 145 | 146 | def plot_nodes(self, nodes, color, label=False): 147 | position = self.convert_nodes_to_coordinate_array(nodes) 148 | self.ax.plot(position[:, 0], position[:, 1], color=color) 149 | if label: 150 | self.ax.text(np.mean(position[:, 0]) + norm.rvs(0, 10), np.mean(position[:, 1]) + norm.rvs(0,10), str(label)); 151 | 152 | def plot_node_sequence(self, node_sequence, color, label=False): 153 | for i in range(1, len(node_sequence)): 154 | if label: 155 | self.plot_nodes([node_sequence[i-1], node_sequence[i]], color=color, label=i) 156 | else: 157 | self.plot_nodes([node_sequence[i-1], node_sequence[i]], color=color) 158 | 159 | def plot_coordinate_array(self, array, color, size, signal_measurements=False): 160 | if isinstance(signal_measurements, np.ndarray): 161 | max_signal = np.apply_along_axis(np.max, 1, signal_measurements) 162 | self.ax.scatter(array[:, 0], array[:, 1], c=max_signal, cmap='inferno', s=size) 163 | else: 164 | self.ax.scatter(array[:, 0], array[:, 1], color=color, s=size) 165 | 166 | def plot_estimation_performance(self, estimated_states, true_states, size, alpha): 167 | for i, state_id in enumerate(estimated_states): 168 | correct = true_states[i] == state_id 169 | if correct: 170 | color = 'green' 171 | else: 172 | color = 'red' 173 | state = self.state_space[state_id] 174 | nodes = list(state['edge']) 175 | coord_array = self.convert_nodes_to_coordinate_array(nodes) 176 | self.ax.plot(coord_array[:, 0], coord_array[:, 1], color=color, lw=size, alpha=alpha); 177 | 178 | 179 | def shrink_plot_to_fit(self, bbox, margin): 180 | self.ax.set_xlim(bbox[0] - margin, bbox[2] + margin) 181 | self.ax.set_ylim(bbox[1] - margin, bbox[3] + margin) 182 | 183 | def show_interactive(self): 184 | return self.fig 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | --------------------------------------------------------------------------------