Note
Go to the end to download the full example code.
Matching with a generic network#
import uuid
from os.path import join
from tempfile import gettempdir
import geopandas as gpd
import numpy as np
from aequilibrae.utils.create_example import create_example
from aequilibrae.paths import Graph
from mapmatcher import MapMatcher
from mapmatcher.examples import nauru_data
nauru_gps = nauru_data()
# Let's see if the data has all the fields we need
nauru_gps.head()
Since it does not, let’s fix it
nauru_gps.rename(columns={"x": "longitude", "y": "latitude", "vehicle_unique_id": "trace_id"}, inplace=True)
Let’s get a Nauru example from AequilibraE and extract the link network from it
project = create_example(join(gettempdir(), uuid.uuid4().hex), "nauru")
with project.db_connection as conn:
sql = "SELECT link_id, a_node, b_node, direction, distance, Hex(ST_AsBinary(geometry)) as geom FROM links"
gdf = gpd.GeoDataFrame.from_postgis(sql, conn, geom_col="geom", crs=4326)
gdf.head()
Let’s build a graph with the network. For that, you need ta few key fields in the network: [link_id, a_node, b_node, direction] You also need a cost field for your graph, which is distance in our case
g = Graph()
g.cost = gdf["distance"].to_numpy()
g.network = gdf
# Let's say nodes 1 through 133 are centroids
g.prepare_graph(centroids=np.arange(133) + 1)
g.set_graph("distance")
g.set_skimming(["distance"])
g.set_blocked_centroid_flows(False)
let’s build the map-matcher object and run it!
mmatcher = MapMatcher()
mmatcher.load_network(graph=g, links=gdf)
mmatcher.load_gps_traces(nauru_gps)
# Let's run it single-threaded
mmatcher.map_match(parallel_threads=1)
building trips: 0%| | 0/100 [00:00<?, ?it/s]
building trips: 5%|▌ | 5/100 [00:00<00:02, 43.25it/s]
building trips: 11%|█ | 11/100 [00:00<00:01, 48.56it/s]
building trips: 16%|█▌ | 16/100 [00:00<00:01, 48.15it/s]
building trips: 22%|██▏ | 22/100 [00:00<00:01, 48.84it/s]
building trips: 27%|██▋ | 27/100 [00:00<00:01, 48.41it/s]
building trips: 32%|███▏ | 32/100 [00:00<00:01, 46.18it/s]
building trips: 37%|███▋ | 37/100 [00:00<00:01, 46.45it/s]
building trips: 42%|████▏ | 42/100 [00:00<00:01, 46.14it/s]
building trips: 47%|████▋ | 47/100 [00:01<00:01, 45.83it/s]
building trips: 52%|█████▏ | 52/100 [00:01<00:01, 44.66it/s]
building trips: 57%|█████▋ | 57/100 [00:01<00:00, 44.54it/s]
building trips: 62%|██████▏ | 62/100 [00:01<00:00, 45.45it/s]
building trips: 68%|██████▊ | 68/100 [00:01<00:00, 46.54it/s]
building trips: 73%|███████▎ | 73/100 [00:01<00:00, 46.83it/s]
building trips: 78%|███████▊ | 78/100 [00:01<00:00, 45.98it/s]
building trips: 83%|████████▎ | 83/100 [00:01<00:00, 44.10it/s]
building trips: 88%|████████▊ | 88/100 [00:01<00:00, 42.05it/s]
building trips: 93%|█████████▎| 93/100 [00:02<00:00, 41.93it/s]
building trips: 98%|█████████▊| 98/100 [00:02<00:00, 42.13it/s]
building trips: 100%|██████████| 100/100 [00:02<00:00, 44.90it/s]
Map matching trips: 0%| | 0/100 [00:00<?, ?it/s]
Map matching trips: 6%|▌ | 6/100 [00:00<00:04, 22.50it/s]
Map matching trips: 11%|█ | 11/100 [00:00<00:04, 20.09it/s]
Map matching trips: 14%|█▍ | 14/100 [00:01<00:08, 10.64it/s]
Map matching trips: 18%|█▊ | 18/100 [00:01<00:07, 10.76it/s]
Map matching trips: 20%|██ | 20/100 [00:01<00:08, 9.84it/s]
Map matching trips: 25%|██▌ | 25/100 [00:02<00:07, 10.29it/s]
Map matching trips: 33%|███▎ | 33/100 [00:02<00:04, 14.68it/s]
Map matching trips: 36%|███▌ | 36/100 [00:03<00:06, 10.49it/s]
Map matching trips: 42%|████▏ | 42/100 [00:03<00:04, 13.88it/s]
Map matching trips: 45%|████▌ | 45/100 [00:03<00:05, 10.66it/s]
Map matching trips: 53%|█████▎ | 53/100 [00:04<00:03, 13.92it/s]
Map matching trips: 62%|██████▏ | 62/100 [00:04<00:02, 16.37it/s]
Map matching trips: 64%|██████▍ | 64/100 [00:04<00:02, 14.86it/s]
Map matching trips: 66%|██████▌ | 66/100 [00:05<00:03, 10.43it/s]
Map matching trips: 70%|███████ | 70/100 [00:05<00:03, 9.40it/s]
Map matching trips: 73%|███████▎ | 73/100 [00:06<00:03, 8.96it/s]
Map matching trips: 76%|███████▌ | 76/100 [00:06<00:02, 8.53it/s]
Map matching trips: 79%|███████▉ | 79/100 [00:06<00:02, 9.33it/s]
Map matching trips: 81%|████████ | 81/100 [00:07<00:02, 8.71it/s]
Map matching trips: 93%|█████████▎| 93/100 [00:07<00:00, 14.97it/s]
Map matching trips: 95%|█████████▌| 95/100 [00:07<00:00, 12.77it/s]
Map matching trips: 100%|██████████| 100/100 [00:07<00:00, 12.54it/s]
for trip in mmatcher.trips:
if trip.success:
break
import folium
import geopandas as gpd
import numpy as np
Create a geometry list from the GeoDataFrame
trace = trip.trace.to_crs(4326)
geo_df_list = [[point.xy[1][0], point.xy[0][0], ts] for point, ts in zip(trace.geometry, trace.timestamp)]
result_layer = folium.FeatureGroup("Map-match result")
trace_layer = folium.FeatureGroup("GPS traces")
# Iterate through list and add a marker for each GPS ping.
i = 0
for lat, lng, ts in geo_df_list:
trace_layer.add_child(
folium.CircleMarker(
location=[lat, lng],
radius=1,
fill=True, # Set fill to True
color="black",
tooltip=str(ts),
fill_opacity=1.0,
)
)
gdf = gpd.GeoDataFrame({"d": [1]}, geometry=[trip.path_shape], crs=3857).to_crs(4326).explode(ignore_index=True)
for _, rec in gdf.iterrows():
coords = rec.geometry.xy
coordinates = [[y, x] for x, y in zip(coords[0], coords[1])]
folium.PolyLine(coordinates, weight=5, color="red").add_to(result_layer)
map = folium.Map(location=[np.mean(coords[1]), np.mean(coords[0])], tiles="OpenStreetMap", zoom_start=15)
result_layer.add_to(map)
trace_layer.add_to(map)
folium.LayerControl(position="bottomright").add_to(map)
map
trip.result
Total running time of the script: (0 minutes 10.520 seconds)