From 15d659595e951dbfd071934548083253f913403e Mon Sep 17 00:00:00 2001 From: Matteo Tortora Date: Wed, 19 Oct 2022 11:31:46 +0200 Subject: [PATCH 1/8] Update __init__.py --- pytrack/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pytrack/__init__.py b/pytrack/__init__.py index de40ea7..3484afa 100644 --- a/pytrack/__init__.py +++ b/pytrack/__init__.py @@ -1 +1,2 @@ +from ._version import __version__ __import__('pkg_resources').declare_namespace(__name__) From 1d435b41e64debba9bf8bb96663fdc6131f1078e Mon Sep 17 00:00:00 2001 From: Matteo Tortora Date: Wed, 19 Oct 2022 11:31:47 +0200 Subject: [PATCH 2/8] Create _version.py --- pytrack/_version.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 pytrack/_version.py diff --git a/pytrack/_version.py b/pytrack/_version.py new file mode 100644 index 0000000..d8c5d45 --- /dev/null +++ b/pytrack/_version.py @@ -0,0 +1,3 @@ +"""PyTrack package version.""" + +__version__ = "2.0.5" From 8779b009256cc13c2098b4f7a15cb95d0d8d8e1d Mon Sep 17 00:00:00 2001 From: Matteo Tortora Date: Wed, 19 Oct 2022 11:31:53 +0200 Subject: [PATCH 3/8] Update visualization.py --- pytrack/analytics/visualization.py | 89 +++++++++++++++++++----------- 1 file changed, 57 insertions(+), 32 deletions(-) diff --git a/pytrack/analytics/visualization.py b/pytrack/analytics/visualization.py index aaab362..f9bfa39 100644 --- a/pytrack/analytics/visualization.py +++ b/pytrack/analytics/visualization.py @@ -132,21 +132,21 @@ def add_graph(self, G, plot_nodes=False, edge_color="#3388ff", edge_width=3, plot_nodes: bool, optional, default: False If true, it will show the vertices of the graph. edge_color: str, optional, default: "#3388ff" - Colour of graph edges + Colour of graph edges. edge_width: float, optional, default: 3 - Width of graph edges + Width of graph edges. edge_opacity: float, optional, default: 1 - Opacity of graph edges + Opacity of graph edges. radius: float, optional, default: 1.7 - Radius of graph vertices + Radius of graph vertices. node_color: str, optional, default: "red" - Colour of graph vertices + Colour of graph vertices. fill: bool, optional, default: True Whether to fill the nodes with color. Set it to false to disable filling on the nodes. fill_color: str or NoneType, default: None Fill color. Defaults to the value of the color option. fill_opacity: float, optional, default: 1 - Fill opacity + Fill opacity. """ edge_attr = dict() edge_attr["color"] = edge_color @@ -156,15 +156,10 @@ def add_graph(self, G, plot_nodes=False, edge_color="#3388ff", edge_width=3, node_attr = dict() node_attr["color"] = node_color node_attr["fill"] = fill - - if not fill_color: - node_attr["fill_color"] = fill_color - else: - node_attr["fill_color"] = fill_color + node_attr["fill_color"] = fill_color node_attr["fill_opacity"] = fill_opacity nodes, edges = utils.graph_to_gdfs(G) - edges = utils.graph_to_gdfs(G, nodes=False) fg_graph = folium.FeatureGroup(name='Graph edges', show=True) self.add_child(fg_graph) @@ -182,7 +177,9 @@ def add_graph(self, G, plot_nodes=False, edge_color="#3388ff", edge_width=3, folium.LayerControl().add_to(self) - def draw_candidates(self, candidates, radius): + def draw_candidates(self, candidates, radius, point_radius=1, point_color="black", point_fill=True, + point_fill_opacity=1, area_weight=1, area_color="black", area_fill=True, area_fill_opacity=0.2, + cand_radius=1, cand_color="orange", cand_fill=True, cand_fill_opacity=1): """ Draw the candidate nodes of the HMM matcher Parameters @@ -190,20 +187,46 @@ def draw_candidates(self, candidates, radius): candidates: dict Candidates' dictionary computed via ``pytrack.matching.candidate.get_candidates`` method radius: float - Candidate search radius + Candidate search radius. + point_radius: float, optional, default: 1 + Radius of the actual GPS points. + point_color: str, optional, default: "black" + Colour of actual GPS points. + point_fill: bool, optional, default: True + Whether to fill the actual GPS points with color. Set it to false to disable filling on the nodes. + point_fill_opacity: float, optional, default: 1 + Fill opacity of the actual GPS points. + area_weight: float, optional, default: 1 + Stroke width in pixels of the search area. + area_color: str, optional, default: "black" + Colour of search area. + area_fill: bool, optional, default: True + Whether to fill the search area with color. Set it to false to disable filling on the nodes. + area_fill_opacity: float, optional, default: 0.2 + Fill opacity of the search area. + cand_radius: float, optional, default: 2 + Radius of the candidate points. + cand_color: str, optional, default: "orange" + Colour of candidate points. + cand_fill: bool, optional, default: True + Whether to fill the candidate points with color. Set it to false to disable filling on the nodes. + cand_fill_opacity: float, optional, default: 1 + Fill opacity of the candidate GPS points. """ fg_cands = folium.FeatureGroup(name='Candidates', show=True, control=True) fg_gps = folium.FeatureGroup(name="Actual GPS points", show=True, control=True) + fg_area = folium.FeatureGroup(name="Candidate search area", show=True, control=True) self.add_child(fg_cands) self.add_child(fg_gps) + self.add_child(fg_area) for i, obs in enumerate(candidates.keys()): - folium.Circle(location=candidates[obs]["observation"], radius=radius, weight=1, color="black", fill=True, - fill_opacity=0.2).add_to(fg_gps) + folium.Circle(location=candidates[obs]["observation"], radius=radius, weight=area_weight, color=area_color, + fill=area_fill, fill_opacity=area_fill_opacity).add_to(fg_area) popup = f'{i}-th point \n Latitude: {candidates[obs]["observation"][0]}\n Longitude: ' \ f'{candidates[obs]["observation"][1]}' - folium.Circle(location=candidates[obs]["observation"], popup=popup, radius=1, color="black", - fill=True, fill_opacity=1).add_to(fg_gps) + folium.Circle(location=candidates[obs]["observation"], popup=popup, radius=point_radius, color=point_color, + fill=point_fill, point_fill_opacity=point_fill_opacity).add_to(fg_gps) # plot candidates for cand, label, cand_type in zip(candidates[obs]["candidates"], candidates[obs]["edge_osmid"], @@ -213,14 +236,15 @@ def draw_candidates(self, candidates, radius): folium.Circle(location=cand, popup=popup, radius=2, color="yellow", fill=True, fill_opacity=1).add_to(fg_cands) else: - folium.Circle(location=cand, popup=popup, radius=1, color="orange", fill=True, - fill_opacity=1).add_to(fg_cands) + folium.Circle(location=cand, popup=popup, radius=cand_radius, color=cand_color, fill=cand_fill, + fill_opacity=cand_fill_opacity).add_to(fg_cands) del self._children[next(k for k in self._children.keys() if k.startswith('layer_control'))] self.add_child(folium.LayerControl()) self._render_reset() - def draw_path(self, G, trellis, predecessor, path_name="Matched path"): + def draw_path(self, G, trellis, predecessor, path_name="Matched path", path_color="green", path_weight=4, + path_opacity=1): """ Draw the map-matched path Parameters @@ -231,8 +255,14 @@ def draw_path(self, G, trellis, predecessor, path_name="Matched path"): Trellis DAG graph created with ``pytrack.matching.mpmatching_utils.create_trellis`` method predecessor: dict Predecessors' dictionary computed with ``pytrack.matching.mpmatching.viterbi_search`` method - path_name: str + path_name: str, optional, default: "Matched path" Name of the path to be drawn + path_color: str, optional, default: "green" + Stroke color + path_weight: float, optional, default: 4 + Stroke width in pixels + path_opacity: float, optional, default: 1 + Stroke opacity """ fg_matched = folium.FeatureGroup(name=path_name, show=True, control=True) @@ -241,19 +271,19 @@ def draw_path(self, G, trellis, predecessor, path_name="Matched path"): path_elab = mpmatching_utils.create_path(G, trellis, predecessor) edge_attr = dict() - edge_attr["color"] = "green" - edge_attr["weight"] = 4 - edge_attr["opacity"] = 1 + edge_attr["color"] = path_color + edge_attr["weight"] = path_weight + edge_attr["opacity"] = path_opacity edge = [(lat, lng) for lng, lat in LineString([G.nodes[node]["geometry"] for node in path_elab]).coords] - folium.PolyLine(locations=edge, **edge_attr, ).add_to(fg_matched) + folium.PolyLine(locations=edge, **edge_attr).add_to(fg_matched) del self._children[next(k for k in self._children.keys() if k.startswith('layer_control'))] self.add_child(folium.LayerControl()) self._render_reset() -def draw_trellis(T, figsize=None, dpi=None, node_size=500, font_size=8, **kwargs): +def draw_trellis(T, figsize=(15, 12), dpi=300, node_size=500, font_size=8, **kwargs): """ Draw a trellis graph Parameters @@ -304,11 +334,6 @@ def draw_trellis(T, figsize=None, dpi=None, node_size=500, font_size=8, **kwargs nx_kwargs = {k: v for k, v in kwargs.items() if k in valid_nx_kwargs} plt_kwargs = {k: v for k, v in kwargs.items() if k in valid_plt_kwargs} - if figsize is None: - figsize = (15, 12) - if dpi is None: - dpi = 300 - plt.figure(figsize=figsize, dpi=dpi, **plt_kwargs) pos = nx.drawing.nx_pydot.graphviz_layout(T, prog='dot', root='start') From 1a4ccb1e40620bc1affe90478bf8b7315359b84d Mon Sep 17 00:00:00 2001 From: Matteo Tortora Date: Wed, 19 Oct 2022 11:31:55 +0200 Subject: [PATCH 4/8] Update graph.py --- pytrack/graph/graph.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pytrack/graph/graph.py b/pytrack/graph/graph.py index 7058528..8eb4fc1 100644 --- a/pytrack/graph/graph.py +++ b/pytrack/graph/graph.py @@ -4,6 +4,7 @@ import networkx as nx from shapely.geometry import Point, LineString +import pytrack from . import distance from . import download @@ -227,8 +228,9 @@ def create_graph(response_json): # per grafo create the graph as a MultiDiGraph and set its meta-attributes metadata = { 'created_date': "{:%Y-%m-%d %H:%M:%S}".format(dt.datetime.now()), - 'created_with': f"PyTrack 1.0.0", + 'created_with': f"PyTrack {pytrack.__version__}", 'crs': "epsg:4326", + 'geometry': False } G = nx.MultiDiGraph(**metadata) From ad5107b503134fe72e6f76e4f0da4b0c00b5cbb6 Mon Sep 17 00:00:00 2001 From: Matteo Tortora Date: Wed, 19 Oct 2022 11:31:57 +0200 Subject: [PATCH 5/8] Update utils.py --- pytrack/graph/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pytrack/graph/utils.py b/pytrack/graph/utils.py index 851de96..b254061 100644 --- a/pytrack/graph/utils.py +++ b/pytrack/graph/utils.py @@ -77,6 +77,8 @@ def graph_to_gdfs(G, nodes=True, edges=True, node_geometry=True, edge_geometry=T if edges: u, v, k, data = zip(*G.edges(keys=True, data=True)) if edge_geometry: + G.graph["geometry"] = True + longs = G.nodes(data="x") lats = G.nodes(data="y") From b3f15164d556a5819cf016fb1b6ccdf062631fe7 Mon Sep 17 00:00:00 2001 From: Matteo Tortora Date: Wed, 19 Oct 2022 11:31:59 +0200 Subject: [PATCH 6/8] Update candidate.py --- pytrack/matching/candidate.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pytrack/matching/candidate.py b/pytrack/matching/candidate.py index bb0caa1..ab0ccf1 100644 --- a/pytrack/matching/candidate.py +++ b/pytrack/matching/candidate.py @@ -63,7 +63,11 @@ def get_candidates(G, points, interp_dist=1, closest=True, radius=10): G = G.copy() if interp_dist: - G = distance.interpolate_graph(G, dist=interp_dist) + if G.graph["geometry"]: + G = distance.interpolate_graph(G, dist=interp_dist) + else: + _ = utils.graph_to_gdfs(G, nodes=False) + G = distance.interpolate_graph(G, dist=interp_dist) geoms = utils.graph_to_gdfs(G, nodes=False).set_index(["u", "v"])[["osmid", "geometry"]] From 5ee0c6284c4fbf89beeac819248c2ca5a5c608ae Mon Sep 17 00:00:00 2001 From: Matteo Tortora Date: Wed, 19 Oct 2022 11:32:02 +0200 Subject: [PATCH 7/8] Update mpmatching.py --- pytrack/matching/mpmatching.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/pytrack/matching/mpmatching.py b/pytrack/matching/mpmatching.py index 847c125..32accc7 100644 --- a/pytrack/matching/mpmatching.py +++ b/pytrack/matching/mpmatching.py @@ -1,5 +1,5 @@ -from collections import deque import math +from collections import deque from . import mpmatching_utils @@ -43,6 +43,8 @@ def viterbi_search(G, trellis, start="start", target="target", beta=mpmatching_u for u_name in trellis.nodes(): joint_prob[u_name] = -float('inf') predecessor = {} + predecessor_val = {} + queue = deque() queue.append(start) @@ -60,15 +62,21 @@ def viterbi_search(G, trellis, start="start", target="target", beta=mpmatching_u v = trellis.nodes[v_name]["candidate"] try: - new_prob = joint_prob[u_name] + math.log10(mpmatching_utils.transition_prob(G, u, v, beta)) + \ - math.log10(mpmatching_utils.emission_prob(v, sigma)) - except Exception as e: - print(e) - - if joint_prob[v_name] < new_prob: - joint_prob[v_name] = new_prob - predecessor[v_name.split("_")[0]] = u_name - if v_name not in queue: - queue.append(v_name) + new_prob = joint_prob[u_name] + math.log10(mpmatching_utils.emission_prob(v, sigma)) \ + + math.log10(mpmatching_utils.transition_prob(G, u, v, beta)) + + if joint_prob[v_name] < new_prob: + joint_prob[v_name] = new_prob + if v_name.split("_")[0] not in predecessor: + predecessor[v_name.split("_")[0]] = u_name + predecessor_val[v_name.split("_")[0]] = new_prob + elif v_name.split("_")[0] in predecessor and predecessor_val[v_name.split("_")[0]] < new_prob: + predecessor[v_name.split("_")[0]] = u_name + predecessor_val[v_name.split("_")[0]] = new_prob + if v_name not in queue: + queue.append(v_name) + + except Exception as error: + print(error) return joint_prob[target], predecessor From a1552e4197b6b3eb713dd3475de3c419d3b1a7f6 Mon Sep 17 00:00:00 2001 From: Matteo Tortora Date: Wed, 19 Oct 2022 12:18:40 +0200 Subject: [PATCH 8/8] Update setup.py --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 0866b93..e555c9e 100644 --- a/setup.py +++ b/setup.py @@ -2,12 +2,12 @@ import os -def strip_comments(l): - return l.split('#', 1)[0].strip() +def strip_comments(line): + return line.split('#', 1)[0].strip() def reqs(*f): - return list(filter(None, [strip_comments(l) for l in open( + return list(filter(None, [strip_comments(line) for line in open( os.path.join(os.getcwd(), *f)).readlines()])) @@ -16,7 +16,7 @@ def reqs(*f): setuptools.setup( name='PyTrack-lib', - version='2.0.4', + version='2.0.5', packages=setuptools.find_packages(), # namespace_packages=['pytrack'], url='https://github.com/cosbidev/PyTrack',