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")
sql = "SELECT link_id, a_node, b_node, direction, distance, Hex(ST_AsBinary(geometry)) as geom FROM links"
gdf = gpd.GeoDataFrame.from_postgis(sql, project.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, 40.10it/s]
building trips: 10%|█ | 10/100 [00:00<00:02, 44.46it/s]
building trips: 15%|█▌ | 15/100 [00:00<00:01, 45.41it/s]
building trips: 20%|██ | 20/100 [00:00<00:01, 45.91it/s]
building trips: 25%|██▌ | 25/100 [00:00<00:01, 44.96it/s]
building trips: 30%|███ | 30/100 [00:00<00:01, 44.32it/s]
building trips: 35%|███▌ | 35/100 [00:00<00:01, 43.51it/s]
building trips: 40%|████ | 40/100 [00:00<00:01, 42.89it/s]
building trips: 45%|████▌ | 45/100 [00:01<00:01, 43.21it/s]
building trips: 50%|█████ | 50/100 [00:01<00:01, 42.03it/s]
building trips: 55%|█████▌ | 55/100 [00:01<00:01, 41.34it/s]
building trips: 60%|██████ | 60/100 [00:01<00:00, 41.75it/s]
building trips: 65%|██████▌ | 65/100 [00:01<00:00, 42.55it/s]
building trips: 70%|███████ | 70/100 [00:01<00:00, 43.63it/s]
building trips: 75%|███████▌ | 75/100 [00:01<00:00, 42.45it/s]
building trips: 80%|████████ | 80/100 [00:01<00:00, 43.22it/s]
building trips: 85%|████████▌ | 85/100 [00:01<00:00, 41.31it/s]
building trips: 90%|█████████ | 90/100 [00:02<00:00, 40.28it/s]
building trips: 95%|█████████▌| 95/100 [00:02<00:00, 41.50it/s]
building trips: 100%|██████████| 100/100 [00:02<00:00, 40.51it/s]
building trips: 100%|██████████| 100/100 [00:02<00:00, 42.38it/s]
Map matching trips: 0%| | 0/100 [00:00<?, ?it/s]
Map matching trips: 6%|▌ | 6/100 [00:00<00:04, 20.78it/s]
Map matching trips: 11%|█ | 11/100 [00:00<00:04, 18.56it/s]
Map matching trips: 13%|█▎ | 13/100 [00:00<00:06, 12.81it/s]
Map matching trips: 15%|█▌ | 15/100 [00:01<00:08, 10.46it/s]
Map matching trips: 18%|█▊ | 18/100 [00:01<00:08, 9.33it/s]
Map matching trips: 19%|█▉ | 19/100 [00:01<00:09, 8.12it/s]
Map matching trips: 21%|██ | 21/100 [00:01<00:08, 9.57it/s]
Map matching trips: 25%|██▌ | 25/100 [00:02<00:08, 9.20it/s]
Map matching trips: 33%|███▎ | 33/100 [00:02<00:04, 14.05it/s]
Map matching trips: 36%|███▌ | 36/100 [00:03<00:06, 9.44it/s]
Map matching trips: 42%|████▏ | 42/100 [00:03<00:04, 12.86it/s]
Map matching trips: 45%|████▌ | 45/100 [00:04<00:05, 9.64it/s]
Map matching trips: 53%|█████▎ | 53/100 [00:04<00:03, 12.81it/s]
Map matching trips: 62%|██████▏ | 62/100 [00:05<00:02, 15.06it/s]
Map matching trips: 64%|██████▍ | 64/100 [00:05<00:02, 13.73it/s]
Map matching trips: 66%|██████▌ | 66/100 [00:05<00:03, 9.16it/s]
Map matching trips: 69%|██████▉ | 69/100 [00:05<00:02, 10.93it/s]
Map matching trips: 71%|███████ | 71/100 [00:06<00:03, 8.43it/s]
Map matching trips: 73%|███████▎ | 73/100 [00:06<00:03, 7.29it/s]
Map matching trips: 76%|███████▌ | 76/100 [00:07<00:03, 7.17it/s]
Map matching trips: 79%|███████▉ | 79/100 [00:07<00:02, 8.15it/s]
Map matching trips: 81%|████████ | 81/100 [00:07<00:02, 7.80it/s]
Map matching trips: 93%|█████████▎| 93/100 [00:08<00:00, 13.61it/s]
Map matching trips: 95%|█████████▌| 95/100 [00:08<00:00, 11.74it/s]
Map matching trips: 100%|██████████| 100/100 [00:08<00:00, 11.46it/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 11.303 seconds)