Basic trip over a real-world graph#

In this notebook, we set up a basic simulation where one vessel moves over a real-world graph. The vessel will sail from a location in the Maasvlakte, to a location in the Port of Moerdijk.

0. Import libraries#

# package(s) used for creating and geo-locating the graph
import networkx as nx
import shapely

# package(s) related to the simulation (creating the vessel, running the simulation)
import datetime
import simpy
import opentnsim
import opentnsim.fis as fis
from opentnsim.core.logutils import logbook2eventtable
from opentnsim.core.plotutils import generate_vessel_gantt_chart

# package(s) needed for inspecting the output
import pandas as pd
import geopandas as gpd

# plot libraries
import folium

print("This notebook is executed with OpenTNSim version {}".format(opentnsim.__version__))
This notebook is executed with OpenTNSim version 0.1.dev1+gcb6bdda4a

1. Define object classes#

# make your preferred Vessel class out of available mix-ins.
Vessel = type(
    "Vessel", 
    (
        opentnsim.core.Identifiable, # allows to give the object a name and a random ID,
        opentnsim.core.Movable,      # allows the object to move, with a fixed speed, while logging this activity
    ), 
    {}
)

2. Create graph#

Next we create a network (a graph) along which the vessel can move. For this case we use the Fairway Information System graph, and make the vessel sail from a container terminal at the Maasvlakte to an container terminal at the Port of Moerdijk.

# load the processed version from the Fairway Information System graph provided by Rijkswaterstaat
FG = fis.load_network(version="0.3")
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[3], line 2
      1 # load the processed version from the Fairway Information System graph provided by Rijkswaterstaat
----> 2 FG = fis.load_network(version="0.3")

File ~/work/OpenTNSim/OpenTNSim/opentnsim/fis.py:53, in load_network(network, version)
     51 for n in graph.nodes:
     52     node = graph.nodes[n]
---> 53     node["geometry"] = shapely.geometry.shape(node["geometry"])
     55 return graph

File /opt/hostedtoolcache/Python/3.13.11/x64/lib/python3.13/site-packages/shapely/geometry/geo.py:101, in shape(context)
     99     return _empty_shape_for_no_coordinates(geom_type)
    100 elif geom_type == "point":
--> 101     return Point(ob["coordinates"])
    102 elif geom_type == "linestring":
    103     return LineString(ob["coordinates"])

File /opt/hostedtoolcache/Python/3.13.11/x64/lib/python3.13/site-packages/shapely/geometry/point.py:81, in Point.__new__(self, *args)
     79 if not np.issubdtype(coords.dtype, np.number):
     80     coords = [float(c) for c in coords]
---> 81 geom = shapely.points(coords)
     82 if not isinstance(geom, Point):
     83     raise ValueError("Invalid values passed to Point constructor")

File /opt/hostedtoolcache/Python/3.13.11/x64/lib/python3.13/site-packages/shapely/decorators.py:173, in deprecate_positional.<locals>.decorator.<locals>.wrapper(*args, **kwargs)
    171 @wraps(func)
    172 def wrapper(*args, **kwargs):
--> 173     result = func(*args, **kwargs)
    175     n = len(args)
    176     if n > warn_from:

File /opt/hostedtoolcache/Python/3.13.11/x64/lib/python3.13/site-packages/shapely/decorators.py:88, in multithreading_enabled.<locals>.wrapped(*args, **kwargs)
     86     for arr in array_args:
     87         arr.flags.writeable = False
---> 88     return func(*args, **kwargs)
     89 finally:
     90     for arr, old_flag in zip(array_args, old_flags):

File /opt/hostedtoolcache/Python/3.13.11/x64/lib/python3.13/site-packages/shapely/creation.py:124, in points(coords, y, z, indices, handle_nan, out, **kwargs)
    122     handle_nan = HandleNaN.get_value(handle_nan)
    123 if indices is None:
--> 124     return lib.points(coords, np.intc(handle_nan), out=out, **kwargs)
    125 else:
    126     return simple_geometries_1d(
    127         coords, indices, GeometryType.POINT, handle_nan=handle_nan, out=out
    128     )

KeyboardInterrupt: 
maasvlakte = shapely.Point(4.0566946, 51.9471624)
moerdijk = shapely.Point(4.5944738, 51.6829037)
nodes_gdf = gpd.GeoDataFrame(FG.nodes.values(), index=FG.nodes.keys())
distances, idx = nodes_gdf.sindex.nearest(maasvlakte)
maasvlakte_node = nodes_gdf.iloc[idx[0]]

distances, idx = nodes_gdf.sindex.nearest(moerdijk)
moerdijk_node = nodes_gdf.iloc[idx[0]]
route = nx.shortest_path(FG, maasvlakte_node.name, moerdijk_node.name, weight='length_m')
# Create a map centered between the two points
m = folium.Map(location=[51.83, 4.33], zoom_start = 10, tiles="cartodb positron")

for edge in FG.edges(data = True):
    points_x = list(edge[2]["geometry"].coords.xy[0])
    points_y = list(edge[2]["geometry"].coords.xy[1])
    
    line = []
    for i, _ in enumerate(points_x):
        line.append((points_y[i], points_x[i]))

    if edge[0] in route and edge[1] in route:
        folium.PolyLine(line, color = "red", weight = 3, popup = edge[2]["Name"]).add_to(m)
    else:
        folium.PolyLine(line, color = "black", weight = 3, popup = edge[2]["Name"]).add_to(m)

for node in FG.nodes(data = True):
    point = list(node[1]["geometry"].coords.xy)
    folium.CircleMarker(location=[point[1][0],point[0][0]], color='black',fill_color = "black", fill=True, radus = 1, popup = node[0]).add_to(m)
        
# Add round marker for Maasvlakte with popup
folium.CircleMarker(
    location=[maasvlakte_node.Y, maasvlakte_node.X],
    radius=8,
    color='green',
    fill=True,
    fill_color='green',
    fill_opacity=0.7,
    popup='Maasvlakte'
).add_to(m)

# Add round marker for Moerdijk with popup
folium.CircleMarker(
    location=[moerdijk_node.Y, moerdijk_node.X],
    radius=8,
    color='blue',
    fill=True,
    fill_color='blue',
    fill_opacity=0.7,
    popup='Moerdijk'
).add_to(m)

# Display the map
m
Make this Notebook Trusted to load map: File -> Trust Notebook