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#
import pathlib
# 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
import movingpandas as mpd
# plot libraries
import folium
import pyarrow as pa
from lonboard import Map, PathLayer, viz
from lonboard.colormap import apply_categorical_cmap
from lonboard.experimental import TripsLayer
print("This notebook is executed with OpenTNSim version {}".format(opentnsim.__version__))
This notebook is executed with OpenTNSim version 1.3.7
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:31, in load_network(version)
23 """load the pickle version of the fairway information system network
24
25 Parameters
(...) 28 The version of the network to load. Choose 0.2 or 0.3. Default is "0.3".
29 """
30 url = urls[version]
---> 31 resp = requests.get(url)
32 # convert the response to a file
33 f = io.BytesIO(resp.content)
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/requests/api.py:73, in get(url, params, **kwargs)
62 def get(url, params=None, **kwargs):
63 r"""Sends a GET request.
64
65 :param url: URL for the new :class:`Request` object.
(...) 70 :rtype: requests.Response
71 """
---> 73 return request("get", url, params=params, **kwargs)
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/requests/api.py:59, in request(method, url, **kwargs)
55 # By using the 'with' statement we are sure the session is closed, thus we
56 # avoid leaving sockets open which can trigger a ResourceWarning in some
57 # cases, and look like a memory leak in others.
58 with sessions.Session() as session:
---> 59 return session.request(method=method, url=url, **kwargs)
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/requests_cache/session.py:183, in CacheMixin.request(self, method, url, headers, expire_after, only_if_cached, refresh, force_refresh, *args, **kwargs)
181 headers = set_request_headers(headers, expire_after, only_if_cached, refresh, force_refresh)
182 with patch_form_boundary() if kwargs.get('files') else nullcontext():
--> 183 return super().request(method, url, *args, headers=headers, **kwargs)
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/requests/sessions.py:589, in Session.request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
584 send_kwargs = {
585 "timeout": timeout,
586 "allow_redirects": allow_redirects,
587 }
588 send_kwargs.update(settings)
--> 589 resp = self.send(prep, **send_kwargs)
591 return resp
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/requests_cache/session.py:230, in CacheMixin.send(self, request, expire_after, only_if_cached, refresh, force_refresh, **kwargs)
228 response = self._resend(request, actions, cached_response, **kwargs) # type: ignore
229 elif actions.send_request:
--> 230 response = self._send_and_cache(request, actions, cached_response, **kwargs)
231 else:
232 response = cached_response # type: ignore # Guaranteed to be non-None by this point
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/requests_cache/session.py:254, in CacheMixin._send_and_cache(self, request, actions, cached_response, **kwargs)
250 """Send a request and cache the response, unless disabled by settings or headers.
251 If applicable, also handle conditional requests.
252 """
253 request = actions.update_request(request)
--> 254 response = super().send(request, **kwargs)
255 actions.update_from_response(response)
257 if not actions.skip_write:
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/requests/sessions.py:724, in Session.send(self, request, **kwargs)
721 if allow_redirects:
722 # Redirect resolving generator.
723 gen = self.resolve_redirects(r, request, **kwargs)
--> 724 history = [resp for resp in gen]
725 else:
726 history = []
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/requests/sessions.py:265, in SessionRedirectMixin.resolve_redirects(self, resp, req, stream, timeout, verify, cert, proxies, yield_requests, **adapter_kwargs)
263 yield req
264 else:
--> 265 resp = self.send(
266 req,
267 stream=stream,
268 timeout=timeout,
269 verify=verify,
270 cert=cert,
271 proxies=proxies,
272 allow_redirects=False,
273 **adapter_kwargs,
274 )
276 extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
278 # extract redirect url, if any, for the next loop
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/requests_cache/session.py:230, in CacheMixin.send(self, request, expire_after, only_if_cached, refresh, force_refresh, **kwargs)
228 response = self._resend(request, actions, cached_response, **kwargs) # type: ignore
229 elif actions.send_request:
--> 230 response = self._send_and_cache(request, actions, cached_response, **kwargs)
231 else:
232 response = cached_response # type: ignore # Guaranteed to be non-None by this point
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/requests_cache/session.py:254, in CacheMixin._send_and_cache(self, request, actions, cached_response, **kwargs)
250 """Send a request and cache the response, unless disabled by settings or headers.
251 If applicable, also handle conditional requests.
252 """
253 request = actions.update_request(request)
--> 254 response = super().send(request, **kwargs)
255 actions.update_from_response(response)
257 if not actions.skip_write:
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/requests/sessions.py:746, in Session.send(self, request, **kwargs)
743 pass
745 if not stream:
--> 746 r.content
748 return r
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/requests/models.py:902, in Response.content(self)
900 self._content = None
901 else:
--> 902 self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
904 self._content_consumed = True
905 # don't need to release the connection; that's been handled by urllib3
906 # since we exhausted the data.
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/requests/models.py:820, in Response.iter_content.<locals>.generate()
818 if hasattr(self.raw, "stream"):
819 try:
--> 820 yield from self.raw.stream(chunk_size, decode_content=True)
821 except ProtocolError as e:
822 raise ChunkedEncodingError(e)
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/urllib3/response.py:1091, in HTTPResponse.stream(self, amt, decode_content)
1089 else:
1090 while not is_fp_closed(self._fp) or len(self._decoded_buffer) > 0:
-> 1091 data = self.read(amt=amt, decode_content=decode_content)
1093 if data:
1094 yield data
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/urllib3/response.py:980, in HTTPResponse.read(self, amt, decode_content, cache_content)
977 if len(self._decoded_buffer) >= amt:
978 return self._decoded_buffer.get(amt)
--> 980 data = self._raw_read(amt)
982 flush_decoder = amt is None or (amt != 0 and not data)
984 if not data and len(self._decoded_buffer) == 0:
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/urllib3/response.py:904, in HTTPResponse._raw_read(self, amt, read1)
901 fp_closed = getattr(self._fp, "closed", False)
903 with self._error_catcher():
--> 904 data = self._fp_read(amt, read1=read1) if not fp_closed else b""
905 if amt is not None and amt != 0 and not data:
906 # Platform-specific: Buggy versions of Python.
907 # Close the connection when no data is returned
(...) 912 # not properly close the connection in all cases. There is
913 # no harm in redundantly calling close.
914 self._fp.close()
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/urllib3/response.py:887, in HTTPResponse._fp_read(self, amt, read1)
884 return self._fp.read1(amt) if amt is not None else self._fp.read1()
885 else:
886 # StringIO doesn't like amt=None
--> 887 return self._fp.read(amt) if amt is not None else self._fp.read()
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/http/client.py:479, in HTTPResponse.read(self, amt)
476 if self.length is not None and amt > self.length:
477 # clip the read to the "end of response"
478 amt = self.length
--> 479 s = self.fp.read(amt)
480 if not s and amt:
481 # Ideally, we would raise IncompleteRead if the content-length
482 # wasn't satisfied, but it might break compatibility.
483 self._close_conn()
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/socket.py:719, in SocketIO.readinto(self, b)
717 raise OSError("cannot read from timed out object")
718 try:
--> 719 return self._sock.recv_into(b)
720 except timeout:
721 self._timeout_occurred = True
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/ssl.py:1304, in SSLSocket.recv_into(self, buffer, nbytes, flags)
1300 if flags != 0:
1301 raise ValueError(
1302 "non-zero flags not allowed in calls to recv_into() on %s" %
1303 self.__class__)
-> 1304 return self.read(nbytes, buffer)
1305 else:
1306 return super().recv_into(buffer, nbytes, flags)
File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/ssl.py:1138, in SSLSocket.read(self, len, buffer)
1136 try:
1137 if buffer is not None:
-> 1138 return self._sslobj.read(len, buffer)
1139 else:
1140 return self._sslobj.read(len)
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)
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 = "black", weight = 3, popup = edge[2]["Name"]).add_to(m)
else:
folium.PolyLine(line, color = "blue", weight = 1, popup = edge[2]["Name"]).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