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()
x y timestamp gps_fix_id vehicle_unique_id geometry
0 166.942502 -0.546496 2018-05-09 18:13:56 1 2 POINT (166.9425 -0.5465)
1 166.943424 -0.546774 2018-05-09 18:14:05 2 2 POINT (166.94342 -0.54677)
2 166.943201 -0.547001 2018-05-09 18:14:08 3 2 POINT (166.9432 -0.547)
3 166.940958 -0.549704 2018-05-09 18:14:39 4 2 POINT (166.94096 -0.5497)
4 166.940674 -0.550305 2018-05-09 18:14:45 5 2 POINT (166.94067 -0.5503)


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()
link_id a_node b_node direction distance geom
0 1 2 1 0 440.126443 LINESTRING (166.93718 -0.52822, 166.93689 -0.5...
1 2 4 3 0 97.588811 LINESTRING (166.9364 -0.50389, 166.93625 -0.50...
2 3 5 4 0 44.419756 LINESTRING (166.93604 -0.50376, 166.9363 -0.50...
3 4 4 6 0 34.209608 LINESTRING (166.9364 -0.50389, 166.93659 -0.50...
4 5 6 7 0 14.783125 LINESTRING (166.93669 -0.504, 166.93678 -0.50409)


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
Make this Notebook Trusted to load map: File -> Trust Notebook


trip.result
links direction milepost geometry
0 1228 -1 1124.584887 LINESTRING (166.92574 -0.51213, 166.92589 -0.5...
1 910 1 1175.350264 LINESTRING (166.92574 -0.51213, 166.92542 -0.5...
2 911 1 1194.939370 LINESTRING (166.92542 -0.51246, 166.9253 -0.51...
3 912 1 1201.665519 LINESTRING (166.9253 -0.51258, 166.92488 -0.51...
4 1240 1 1208.386598 LINESTRING (166.92488 -0.51302, 166.92511 -0.5...
5 1240 -1 1215.107678 LINESTRING (166.92488 -0.51302, 166.92511 -0.5...
6 913 1 1276.267386 LINESTRING (166.92488 -0.51302, 166.92451 -0.5...
7 914 1 1290.152053 LINESTRING (166.92451 -0.51343, 166.92443 -0.5...
8 915 1 1292.893482 LINESTRING (166.92443 -0.51353, 166.92426 -0.5...
9 916 1 1344.025141 LINESTRING (166.92426 -0.5137, 166.92394 -0.51...
10 917 1 1416.300787 LINESTRING (166.92394 -0.51404, 166.9235 -0.51...
11 918 1 1423.168806 LINESTRING (166.9235 -0.51452, 166.92308 -0.51...
12 919 1 1437.002929 LINESTRING (166.92308 -0.51497, 166.92299 -0.5...
13 920 1 1452.636599 LINESTRING (166.92299 -0.51507, 166.9229 -0.51...
14 921 1 1488.030956 LINESTRING (166.9229 -0.51517, 166.92268 -0.51...
15 922 1 1523.208037 LINESTRING (166.92268 -0.51541, 166.9225 -0.51...
16 923 1 1568.370584 LINESTRING (166.9225 -0.51567, 166.92231 -0.51...
17 924 1 1599.011011 LINESTRING (166.92231 -0.51603, 166.92218 -0.5...
18 925 1 1601.218148 LINESTRING (166.92218 -0.51627, 166.92206 -0.5...
19 926 1 1638.344782 LINESTRING (166.92206 -0.51643, 166.92183 -0.5...
20 927 1 1671.654432 LINESTRING (166.92183 -0.51667, 166.9216 -0.51...
21 1116 -1 1682.671559 LINESTRING (166.92088 -0.51726, 166.92091 -0.5...
22 1226 -1 1693.861510 LINESTRING (166.9206 -0.51753, 166.92047 -0.51...
23 931 1 1706.166402 LINESTRING (166.9206 -0.51753, 166.9205 -0.51758)
24 932 1 1755.165865 LINESTRING (166.9205 -0.51758, 166.92014 -0.51...
25 933 1 1768.063688 LINESTRING (166.92014 -0.51784, 166.91926 -0.5...
26 934 1 1775.853776 LINESTRING (166.91926 -0.51859, 166.91878 -0.5...
27 935 1 1779.828479 LINESTRING (166.91878 -0.5191, 166.91854 -0.51...
28 936 1 1800.035146 LINESTRING (166.91854 -0.51937, 166.91842 -0.5...
29 937 1 1820.835860 LINESTRING (166.91842 -0.5195, 166.91828 -0.51...
30 938 1 1828.613960 LINESTRING (166.91828 -0.51963, 166.9177 -0.52...
31 939 1 1906.245998 LINESTRING (166.9177 -0.52003, 166.91715 -0.52...
32 940 1 1964.071072 LINESTRING (166.91715 -0.52046, 166.91675 -0.5...
33 941 1 2047.465681 LINESTRING (166.91675 -0.5208, 166.91617 -0.52...
34 942 1 2054.335952 LINESTRING (166.91617 -0.52127, 166.9157 -0.52...
35 943 1 2061.268315 LINESTRING (166.9157 -0.52167, 166.91565 -0.52...
36 944 1 2116.540548 LINESTRING (166.91565 -0.52171, 166.91527 -0.5...
37 945 1 2144.694510 LINESTRING (166.91527 -0.52203, 166.91507 -0.5...
38 946 1 2172.066185 LINESTRING (166.91507 -0.52219, 166.9149 -0.52...
39 947 1 2199.383492 LINESTRING (166.9149 -0.52237, 166.91476 -0.52...
40 948 1 2202.123860 LINESTRING (166.91476 -0.52257, 166.91464 -0.5...
41 949 1 2205.246512 LINESTRING (166.91464 -0.52279, 166.91454 -0.5...
42 950 1 2236.437781 LINESTRING (166.91454 -0.52305, 166.91443 -0.5...
43 951 1 2239.368459 LINESTRING (166.91443 -0.52331, 166.91438 -0.5...


Total running time of the script: (0 minutes 11.303 seconds)

Gallery generated by Sphinx-Gallery