Skip to content

Commit

Permalink
Merge pull request #6 from cosbidev/v2.0.5
Browse files Browse the repository at this point in the history
v2.0.5
  • Loading branch information
matteotortora authored Oct 19, 2022
2 parents f8860ac + a1552e4 commit 92f2c1d
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 49 deletions.
1 change: 1 addition & 0 deletions pytrack/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from ._version import __version__
__import__('pkg_resources').declare_namespace(__name__)
3 changes: 3 additions & 0 deletions pytrack/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""PyTrack package version."""

__version__ = "2.0.5"
89 changes: 57 additions & 32 deletions pytrack/analytics/visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -182,28 +177,56 @@ 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
----------
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"],
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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')
Expand Down
4 changes: 3 additions & 1 deletion pytrack/graph/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import networkx as nx
from shapely.geometry import Point, LineString

import pytrack
from . import distance
from . import download

Expand Down Expand Up @@ -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)

Expand Down
2 changes: 2 additions & 0 deletions pytrack/graph/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
6 changes: 5 additions & 1 deletion pytrack/matching/candidate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]]

Expand Down
30 changes: 19 additions & 11 deletions pytrack/matching/mpmatching.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from collections import deque
import math
from collections import deque

from . import mpmatching_utils

Expand Down Expand Up @@ -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)
Expand All @@ -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
8 changes: 4 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()]))


Expand All @@ -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',
Expand Down

0 comments on commit 92f2c1d

Please sign in to comment.