Skip to content

Commit 92f2c1d

Browse files
Merge pull request #6 from cosbidev/v2.0.5
v2.0.5
2 parents f8860ac + a1552e4 commit 92f2c1d

8 files changed

Lines changed: 94 additions & 49 deletions

File tree

pytrack/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
from ._version import __version__
12
__import__('pkg_resources').declare_namespace(__name__)

pytrack/_version.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""PyTrack package version."""
2+
3+
__version__ = "2.0.5"

pytrack/analytics/visualization.py

Lines changed: 57 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -132,21 +132,21 @@ def add_graph(self, G, plot_nodes=False, edge_color="#3388ff", edge_width=3,
132132
plot_nodes: bool, optional, default: False
133133
If true, it will show the vertices of the graph.
134134
edge_color: str, optional, default: "#3388ff"
135-
Colour of graph edges
135+
Colour of graph edges.
136136
edge_width: float, optional, default: 3
137-
Width of graph edges
137+
Width of graph edges.
138138
edge_opacity: float, optional, default: 1
139-
Opacity of graph edges
139+
Opacity of graph edges.
140140
radius: float, optional, default: 1.7
141-
Radius of graph vertices
141+
Radius of graph vertices.
142142
node_color: str, optional, default: "red"
143-
Colour of graph vertices
143+
Colour of graph vertices.
144144
fill: bool, optional, default: True
145145
Whether to fill the nodes with color. Set it to false to disable filling on the nodes.
146146
fill_color: str or NoneType, default: None
147147
Fill color. Defaults to the value of the color option.
148148
fill_opacity: float, optional, default: 1
149-
Fill opacity
149+
Fill opacity.
150150
"""
151151
edge_attr = dict()
152152
edge_attr["color"] = edge_color
@@ -156,15 +156,10 @@ def add_graph(self, G, plot_nodes=False, edge_color="#3388ff", edge_width=3,
156156
node_attr = dict()
157157
node_attr["color"] = node_color
158158
node_attr["fill"] = fill
159-
160-
if not fill_color:
161-
node_attr["fill_color"] = fill_color
162-
else:
163-
node_attr["fill_color"] = fill_color
159+
node_attr["fill_color"] = fill_color
164160
node_attr["fill_opacity"] = fill_opacity
165161

166162
nodes, edges = utils.graph_to_gdfs(G)
167-
edges = utils.graph_to_gdfs(G, nodes=False)
168163

169164
fg_graph = folium.FeatureGroup(name='Graph edges', show=True)
170165
self.add_child(fg_graph)
@@ -182,28 +177,56 @@ def add_graph(self, G, plot_nodes=False, edge_color="#3388ff", edge_width=3,
182177

183178
folium.LayerControl().add_to(self)
184179

185-
def draw_candidates(self, candidates, radius):
180+
def draw_candidates(self, candidates, radius, point_radius=1, point_color="black", point_fill=True,
181+
point_fill_opacity=1, area_weight=1, area_color="black", area_fill=True, area_fill_opacity=0.2,
182+
cand_radius=1, cand_color="orange", cand_fill=True, cand_fill_opacity=1):
186183
""" Draw the candidate nodes of the HMM matcher
187184
188185
Parameters
189186
----------
190187
candidates: dict
191188
Candidates' dictionary computed via ``pytrack.matching.candidate.get_candidates`` method
192189
radius: float
193-
Candidate search radius
190+
Candidate search radius.
191+
point_radius: float, optional, default: 1
192+
Radius of the actual GPS points.
193+
point_color: str, optional, default: "black"
194+
Colour of actual GPS points.
195+
point_fill: bool, optional, default: True
196+
Whether to fill the actual GPS points with color. Set it to false to disable filling on the nodes.
197+
point_fill_opacity: float, optional, default: 1
198+
Fill opacity of the actual GPS points.
199+
area_weight: float, optional, default: 1
200+
Stroke width in pixels of the search area.
201+
area_color: str, optional, default: "black"
202+
Colour of search area.
203+
area_fill: bool, optional, default: True
204+
Whether to fill the search area with color. Set it to false to disable filling on the nodes.
205+
area_fill_opacity: float, optional, default: 0.2
206+
Fill opacity of the search area.
207+
cand_radius: float, optional, default: 2
208+
Radius of the candidate points.
209+
cand_color: str, optional, default: "orange"
210+
Colour of candidate points.
211+
cand_fill: bool, optional, default: True
212+
Whether to fill the candidate points with color. Set it to false to disable filling on the nodes.
213+
cand_fill_opacity: float, optional, default: 1
214+
Fill opacity of the candidate GPS points.
194215
"""
195216
fg_cands = folium.FeatureGroup(name='Candidates', show=True, control=True)
196217
fg_gps = folium.FeatureGroup(name="Actual GPS points", show=True, control=True)
218+
fg_area = folium.FeatureGroup(name="Candidate search area", show=True, control=True)
197219
self.add_child(fg_cands)
198220
self.add_child(fg_gps)
221+
self.add_child(fg_area)
199222

200223
for i, obs in enumerate(candidates.keys()):
201-
folium.Circle(location=candidates[obs]["observation"], radius=radius, weight=1, color="black", fill=True,
202-
fill_opacity=0.2).add_to(fg_gps)
224+
folium.Circle(location=candidates[obs]["observation"], radius=radius, weight=area_weight, color=area_color,
225+
fill=area_fill, fill_opacity=area_fill_opacity).add_to(fg_area)
203226
popup = f'{i}-th point \n Latitude: {candidates[obs]["observation"][0]}\n Longitude: ' \
204227
f'{candidates[obs]["observation"][1]}'
205-
folium.Circle(location=candidates[obs]["observation"], popup=popup, radius=1, color="black",
206-
fill=True, fill_opacity=1).add_to(fg_gps)
228+
folium.Circle(location=candidates[obs]["observation"], popup=popup, radius=point_radius, color=point_color,
229+
fill=point_fill, point_fill_opacity=point_fill_opacity).add_to(fg_gps)
207230

208231
# plot candidates
209232
for cand, label, cand_type in zip(candidates[obs]["candidates"], candidates[obs]["edge_osmid"],
@@ -213,14 +236,15 @@ def draw_candidates(self, candidates, radius):
213236
folium.Circle(location=cand, popup=popup, radius=2, color="yellow", fill=True,
214237
fill_opacity=1).add_to(fg_cands)
215238
else:
216-
folium.Circle(location=cand, popup=popup, radius=1, color="orange", fill=True,
217-
fill_opacity=1).add_to(fg_cands)
239+
folium.Circle(location=cand, popup=popup, radius=cand_radius, color=cand_color, fill=cand_fill,
240+
fill_opacity=cand_fill_opacity).add_to(fg_cands)
218241

219242
del self._children[next(k for k in self._children.keys() if k.startswith('layer_control'))]
220243
self.add_child(folium.LayerControl())
221244
self._render_reset()
222245

223-
def draw_path(self, G, trellis, predecessor, path_name="Matched path"):
246+
def draw_path(self, G, trellis, predecessor, path_name="Matched path", path_color="green", path_weight=4,
247+
path_opacity=1):
224248
""" Draw the map-matched path
225249
226250
Parameters
@@ -231,8 +255,14 @@ def draw_path(self, G, trellis, predecessor, path_name="Matched path"):
231255
Trellis DAG graph created with ``pytrack.matching.mpmatching_utils.create_trellis`` method
232256
predecessor: dict
233257
Predecessors' dictionary computed with ``pytrack.matching.mpmatching.viterbi_search`` method
234-
path_name: str
258+
path_name: str, optional, default: "Matched path"
235259
Name of the path to be drawn
260+
path_color: str, optional, default: "green"
261+
Stroke color
262+
path_weight: float, optional, default: 4
263+
Stroke width in pixels
264+
path_opacity: float, optional, default: 1
265+
Stroke opacity
236266
"""
237267

238268
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"):
241271
path_elab = mpmatching_utils.create_path(G, trellis, predecessor)
242272

243273
edge_attr = dict()
244-
edge_attr["color"] = "green"
245-
edge_attr["weight"] = 4
246-
edge_attr["opacity"] = 1
274+
edge_attr["color"] = path_color
275+
edge_attr["weight"] = path_weight
276+
edge_attr["opacity"] = path_opacity
247277

248278
edge = [(lat, lng) for lng, lat in LineString([G.nodes[node]["geometry"] for node in path_elab]).coords]
249-
folium.PolyLine(locations=edge, **edge_attr, ).add_to(fg_matched)
279+
folium.PolyLine(locations=edge, **edge_attr).add_to(fg_matched)
250280

251281
del self._children[next(k for k in self._children.keys() if k.startswith('layer_control'))]
252282
self.add_child(folium.LayerControl())
253283
self._render_reset()
254284

255285

256-
def draw_trellis(T, figsize=None, dpi=None, node_size=500, font_size=8, **kwargs):
286+
def draw_trellis(T, figsize=(15, 12), dpi=300, node_size=500, font_size=8, **kwargs):
257287
""" Draw a trellis graph
258288
259289
Parameters
@@ -304,11 +334,6 @@ def draw_trellis(T, figsize=None, dpi=None, node_size=500, font_size=8, **kwargs
304334
nx_kwargs = {k: v for k, v in kwargs.items() if k in valid_nx_kwargs}
305335
plt_kwargs = {k: v for k, v in kwargs.items() if k in valid_plt_kwargs}
306336

307-
if figsize is None:
308-
figsize = (15, 12)
309-
if dpi is None:
310-
dpi = 300
311-
312337
plt.figure(figsize=figsize, dpi=dpi, **plt_kwargs)
313338

314339
pos = nx.drawing.nx_pydot.graphviz_layout(T, prog='dot', root='start')

pytrack/graph/graph.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import networkx as nx
55
from shapely.geometry import Point, LineString
66

7+
import pytrack
78
from . import distance
89
from . import download
910

@@ -227,8 +228,9 @@ def create_graph(response_json):
227228
# per grafo create the graph as a MultiDiGraph and set its meta-attributes
228229
metadata = {
229230
'created_date': "{:%Y-%m-%d %H:%M:%S}".format(dt.datetime.now()),
230-
'created_with': f"PyTrack 1.0.0",
231+
'created_with': f"PyTrack {pytrack.__version__}",
231232
'crs': "epsg:4326",
233+
'geometry': False
232234
}
233235
G = nx.MultiDiGraph(**metadata)
234236

pytrack/graph/utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ def graph_to_gdfs(G, nodes=True, edges=True, node_geometry=True, edge_geometry=T
7777
if edges:
7878
u, v, k, data = zip(*G.edges(keys=True, data=True))
7979
if edge_geometry:
80+
G.graph["geometry"] = True
81+
8082
longs = G.nodes(data="x")
8183
lats = G.nodes(data="y")
8284

pytrack/matching/candidate.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ def get_candidates(G, points, interp_dist=1, closest=True, radius=10):
6363
G = G.copy()
6464

6565
if interp_dist:
66-
G = distance.interpolate_graph(G, dist=interp_dist)
66+
if G.graph["geometry"]:
67+
G = distance.interpolate_graph(G, dist=interp_dist)
68+
else:
69+
_ = utils.graph_to_gdfs(G, nodes=False)
70+
G = distance.interpolate_graph(G, dist=interp_dist)
6771

6872
geoms = utils.graph_to_gdfs(G, nodes=False).set_index(["u", "v"])[["osmid", "geometry"]]
6973

pytrack/matching/mpmatching.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from collections import deque
21
import math
2+
from collections import deque
33

44
from . import mpmatching_utils
55

@@ -43,6 +43,8 @@ def viterbi_search(G, trellis, start="start", target="target", beta=mpmatching_u
4343
for u_name in trellis.nodes():
4444
joint_prob[u_name] = -float('inf')
4545
predecessor = {}
46+
predecessor_val = {}
47+
4648
queue = deque()
4749

4850
queue.append(start)
@@ -60,15 +62,21 @@ def viterbi_search(G, trellis, start="start", target="target", beta=mpmatching_u
6062
v = trellis.nodes[v_name]["candidate"]
6163

6264
try:
63-
new_prob = joint_prob[u_name] + math.log10(mpmatching_utils.transition_prob(G, u, v, beta)) + \
64-
math.log10(mpmatching_utils.emission_prob(v, sigma))
65-
except Exception as e:
66-
print(e)
67-
68-
if joint_prob[v_name] < new_prob:
69-
joint_prob[v_name] = new_prob
70-
predecessor[v_name.split("_")[0]] = u_name
71-
if v_name not in queue:
72-
queue.append(v_name)
65+
new_prob = joint_prob[u_name] + math.log10(mpmatching_utils.emission_prob(v, sigma)) \
66+
+ math.log10(mpmatching_utils.transition_prob(G, u, v, beta))
67+
68+
if joint_prob[v_name] < new_prob:
69+
joint_prob[v_name] = new_prob
70+
if v_name.split("_")[0] not in predecessor:
71+
predecessor[v_name.split("_")[0]] = u_name
72+
predecessor_val[v_name.split("_")[0]] = new_prob
73+
elif v_name.split("_")[0] in predecessor and predecessor_val[v_name.split("_")[0]] < new_prob:
74+
predecessor[v_name.split("_")[0]] = u_name
75+
predecessor_val[v_name.split("_")[0]] = new_prob
76+
if v_name not in queue:
77+
queue.append(v_name)
78+
79+
except Exception as error:
80+
print(error)
7381

7482
return joint_prob[target], predecessor

setup.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
import os
33

44

5-
def strip_comments(l):
6-
return l.split('#', 1)[0].strip()
5+
def strip_comments(line):
6+
return line.split('#', 1)[0].strip()
77

88

99
def reqs(*f):
10-
return list(filter(None, [strip_comments(l) for l in open(
10+
return list(filter(None, [strip_comments(line) for line in open(
1111
os.path.join(os.getcwd(), *f)).readlines()]))
1212

1313

@@ -16,7 +16,7 @@ def reqs(*f):
1616

1717
setuptools.setup(
1818
name='PyTrack-lib',
19-
version='2.0.4',
19+
version='2.0.5',
2020
packages=setuptools.find_packages(),
2121
# namespace_packages=['pytrack'],
2222
url='https://github.com/cosbidev/PyTrack',

0 commit comments

Comments
 (0)