Repository: TUDelft-CITG/OpenTNSim (language: python)

Issue:
SALTISolutions_Port_Accessibility_to_be_merged
# TODO for the merge
# first step
- [x] check in lock and output in new branch
- [x] review lock and output modules
- [x] add basic tests for output and lock module
- [x] check in vessel_traffic_services modules in new branch
- [x] remove old lock example notebook

# second step
- [ ] Add real world lock notebook to example book, based on student example 
    - [ ] Create  graph + lock on fis graph
- [ ] review new modules
- [ ] cherry pick existing modules
- [ ] write the abstract lock / core functionality unit tests
- [ ] manual re-merge the changed modules
- [ ] add real world unit test
- [ ] add schematic lock figure (for terminology)
- [ ] refactor most important parts (add lock complex to network, flexible approach to digraph/multidigraph, make log variants consistent, consider use of HasLockInformation, make variable names more consistent (Wlev vs WLev))
- [ ] Rerun previous simulation (check e.g. if leave_opposite_lineup_area directions are correct)
- [ ] Check code that uses things like `current_velocity[2]` or `current_velocity[-1]`. That is unreliable without checks. 
- [ ] Extend real world lock notebook to example book 
    - [ ] Create schematic graph + lock
    - [ ] Create fis graph based lock
    - [ ] Create Weurt + Graven reconstruction 
    - [ ] Create Lock + Salt (IJmuiden)
    - [ ] Create Volkerak + Energy + Compare with AIS energy





Discussion / hints:
Check out this pull request on&nbsp; <a href="https://app.reviewnb.com/TUDelft-CITG/OpenTNSim/pull/49"><img align="absmiddle"  alt="ReviewNB" height="28" class="BotMessageButtonImage" src="https://raw.githubusercontent.com/ReviewNB/support/master/images/button_reviewnb.png"/></a> 

 See visual diffs & provide feedback on Jupyter Notebooks. 

---

 <i>Powered by <a href='https://www.reviewnb.com/?utm_source=gh'>ReviewNB</a></i>

Fix patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 78c2e062..15c25f9e 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -29,7 +29,7 @@ jobs:
           name: Build docs
           command: |
             docker exec -it opentnsim bash -c "pip install sphinx"
-            docker exec -it opentnsim bash -c "python setup.py docs"
+            docker exec -it opentnsim bash -c "sphinx-build -b html docs docs/_build/html"
             docker cp opentnsim:/OpenTNSim/docs/_build/html /tmp/artifacts/docs
 
       - run:
diff --git a/.github/workflows/azure-static-web-apps-happy-bush-0c5d10603.yml b/.github/workflows/azure-static-web-apps-happy-bush-0c5d10603.yml
index 1f95c8ce..22572187 100644
--- a/.github/workflows/azure-static-web-apps-happy-bush-0c5d10603.yml
+++ b/.github/workflows/azure-static-web-apps-happy-bush-0c5d10603.yml
@@ -4,10 +4,12 @@ on:
   push:
     branches:
       - master
-  pull_request:
-    types: [opened, synchronize, reopened, closed]
-    branches:
-      - master
+      - feature/book
+  # # disable PR due to site limit @ azure
+  # pull_request:
+  #   types: [opened, synchronize, reopened, closed]
+  #   branches:
+  #     - master
 
 jobs:
   build_and_deploy_job:
diff --git a/.gitignore b/.gitignore
index 9942962c..2fbae146 100644
--- a/.gitignore
+++ b/.gitignore
@@ -75,3 +75,5 @@ MANIFEST
 *.mp4
 *.kml
 .vscode/settings.json
+notebooks/Port_of_Rotterdam/geospatial_data
+notebooks/Port_of_Rotterdam
diff --git a/notebooks/Example 17 - Lock.ipynb b/notebooks/Example 17 - Lock.ipynb
deleted file mode 100644
index 1595edb6..00000000
--- a/notebooks/Example 17 - Lock.ipynb
+++ /dev/null
@@ -1,1485 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "markdown",
-   "id": "fc9020be",
-   "metadata": {},
-   "source": [
-    "## Vessels passing a lock\n",
-    "In this example we will discuss how to implement a lock in OpenTNSim and how to analyse specific locking output.\n",
-    "\n",
-    "We take the following steps:\n",
-    "\n",
-    "1. [Imports](#1.-Imports)\n",
-    "2. [Create vessel](#2.-Create-vessel)\n",
-    "3. [Create graph](#3.-Create-graph)\n",
-    "4. [Run simulation](#4.-Run-simulation)\n",
-    "5. [Inspect output](#5.-Inspect-output)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "id": "819ac0e6",
-   "metadata": {},
-   "source": [
-    "### 1. Imports\n",
-    "We start with importing required libraries"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 1,
-   "id": "7819173c",
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "This notebook is executed with OpenTNSim version 1.1.2\n"
-     ]
-    }
-   ],
-   "source": [
-    "# package(s) used for creating and geo-locating the graph\n",
-    "import networkx as nx  \n",
-    "import shapely.geometry\n",
-    "import pyproj\n",
-    "\n",
-    "# package(s) related to the simulation (creating the vessel, running the simulation)\n",
-    "import datetime, time\n",
-    "import simpy\n",
-    "import opentnsim\n",
-    "import opentnsim.lock\n",
-    "from opentnsim import core\n",
-    "\n",
-    "\n",
-    "\n",
-    "# package(s) needed for data handling and inspecting output\n",
-    "import numpy as np\n",
-    "import pandas as pd\n",
-    "\n",
-    "# package(s) needed for plotting\n",
-    "import matplotlib.pyplot as plt\n",
-    "from matplotlib.ticker import MaxNLocator\n",
-    "\n",
-    "print('This notebook is executed with OpenTNSim version {}'.format(opentnsim.__version__))"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "id": "b6e9ab4c",
-   "metadata": {},
-   "source": [
-    "### 2. Create vessel\n",
-    "We start with creating a vessel class. We call this class a *Vessel*, and add a number of OpenTNSim mix-ins to this class. Each mix-in requires certain input parameters. \n",
-    "\n",
-    "The following mix-ins are sufficient to create a vessel for our problem: \n",
-    "* _Identifiable_ - allows to give the vessel a name and a random ID, \n",
-    "* _Movable_ - allows the vessel to move, with a fixed speed, while logging this activity,\n",
-    "   * Movable in turn relies on the mix-ins: _Locatable_, _Routeable_, and _Log_\n",
-    "* _VesselProperties_ - allows to give the vessel specific properties, \n",
-    "* _HasContainer_ - allows vessel to have cargo on board, \n",
-    "* _HasResource_ - allows vessel to be requested   "
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 2,
-   "id": "097510a6",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "TransportResource = type(\n",
-    "    \"Vessel\",\n",
-    "    (\n",
-    "        core.Identifiable,\n",
-    "        core.Movable,\n",
-    "        core.VesselProperties,\n",
-    "        core.HasContainer,\n",
-    "        core.HasResource,\n",
-    "        core.ExtraMetadata,\n",
-    "    ),\n",
-    "    {},\n",
-    ")"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 3,
-   "id": "e29fdab4",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "data_vessel = {\n",
-    "    \"env\": None,\n",
-    "    \"name\": \"Vessel\",\n",
-    "    \"route\": None, \n",
-    "    \"geometry\": None, \n",
-    "    \"capacity\": 1_000,\n",
-    "    \"vessel_type\": \"CEMT - Va\",\n",
-    "    \"width\": 11.40,\n",
-    "    \"height_empty\": 7.10,\n",
-    "    \"height_full\": 5.40,\n",
-    "    \"draught_empty\": 1.8,\n",
-    "    \"draught_full\": 3.5,\n",
-    "    \"v\": 3,\n",
-    "    \"B\": 11.4,\n",
-    "    \"L\": 135,\n",
-    "    \"length\": 135.0,\n",
-    "    \"type\": \"CEMT - Va\",\n",
-    "}\n",
-    "\n",
-    "vessel = TransportResource(**data_vessel)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "id": "c6e92801",
-   "metadata": {},
-   "source": [
-    "### 2. Create graph\n",
-    "Next we create a 1D network (a graph) along which the vessel can move. Basics are the same. However, instead here it is chosen to build a graph with distances, as it is more convenient to impose a lock with certain dimensions on such a graph."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 4,
-   "id": "a59ffc41",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# specify a number of coordinate along your route (coords are: lon, lat)\n",
-    "coords = []\n",
-    "coords.append([0, 0])\n",
-    "\n",
-    "# for each edge specify the distance (m)\n",
-    "distances = [550, 500, 300, 500, 150, 150, 500, 300, 500, 550]\n",
-    "\n",
-    "# create world coordinates based on the indicated distances\n",
-    "for d in range(len(distances)):\n",
-    "    coords.append(\n",
-    "        [\n",
-    "            pyproj.Geod(ellps=\"WGS84\").fwd(\n",
-    "                coords[d][0], coords[d][1], 90, distances[d]\n",
-    "            )[0],\n",
-    "            0,\n",
-    "        ]\n",
-    "    )"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 5,
-   "id": "09614e58",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# create a graph \n",
-    "FG = nx.DiGraph()\n",
-    "nodes = []\n",
-    "path = []\n",
-    "\n",
-    "# make your preferred Site class out of available mix-ins.\n",
-    "Node = type(\"Site\", (core.Identifiable, core.Log, core.Locatable, core.HasResource), {})\n",
-    "\n",
-    "# add nodes\n",
-    "for d in range(len(coords)):\n",
-    "    data_node = {\n",
-    "        \"env\": [],\n",
-    "        \"name\": str(d + 1),\n",
-    "        \"geometry\": shapely.geometry.Point(coords[d][0], coords[d][1]),\n",
-    "    }\n",
-    "    node = Node(**data_node)\n",
-    "    nodes.append(node)\n",
-    "\n",
-    "for i in range(2):\n",
-    "    if i == 0:\n",
-    "        for j in range(len(nodes) - 1):\n",
-    "            path.append([nodes[j], nodes[j + 1]])\n",
-    "    if i == 1:\n",
-    "        for j in range(len(nodes) - 1):\n",
-    "            path.append([nodes[j + 1], nodes[j]])\n",
-    "\n",
-    "positions = {}\n",
-    "for node in nodes:\n",
-    "    positions[node.name] = (node.geometry.x, node.geometry.y)\n",
-    "    FG.add_node(node.name, geometry=node.geometry)\n",
-    "\n",
-    "# add edges\n",
-    "for edge in path:\n",
-    "    FG.add_edge(edge[0].name, edge[1].name, weight=1)\n",
-    "\n",
-    "# fig, ax = plt.subplots(figsize=(10, 10))\n",
-    "# nx.draw(FG, positions, ax=ax)\n",
-    "# plt.show()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 6,
-   "id": "89a8c5f6",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# create a positions dict for the purpose of plotting\n",
-    "positions = {}\n",
-    "for node in FG.nodes:\n",
-    "    positions[node] = (FG.nodes[node]['geometry'].x, FG.nodes[node]['geometry'].y)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 7,
-   "id": "60983f12",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# collect node labels.\n",
-    "labels = {}\n",
-    "for node in FG.nodes:\n",
-    "    labels[node] = node"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 8,
-   "id": "96e7b099",
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Number of nodes is 11:\n",
-      "Number of edges is 20:\n"
-     ]
-    },
-    {
-     "data": {
-      "image/png": "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t9lqu3U9t9Vu5LDDvpof/7jp+c4xN0LDqf/xx8/OW96yZ3bYYVq6uyflvvuW5vLLF+SjH70hjz66cqiXFFmHdn195StfmHe9a9+8+MUz09dX5+c/X5izzrq21TkuKbSvJRuow3bbTesezvVnR0eV00/fJyedtHu23fYZeeSRFZk//46ceeZPsnjxioFm6rCRjLRe733vATn88G2y777PzqabTkqSHHfct3PRRbcObqZejDqP56YEX05jsq8h7bbbjPzDP7xopOucnMZEYvsNubSqOpMcneRFSc5PXbdMESaEqjoojZnxv526/lmLlk3r9U//tF923nn6mn9vssmkzJy5aY444vl53eu+la9+9bZm62xXr44kr0ny4iT/nrq+q01vxq6qemGSE5P8PMm3Utd9LVoPWYuXvGTrzJnzgnV+Nnv29MyePT177TUzr3zlZa32oF0tDkjyyiRXpq6va7WitqpqdpK/S3JNku+m9TcHLc8TSdLV1ZHzzjtiJHvQvK9VVSU5LMkRSf4jdX3TSFY8plTVVklOTvL7JJemrlsleW3rMHL15KRqesxtklX/sTxdU+sWg4DPO++InHrqXuv8bOedp2ezzSYPGdRU6ZtSp7ooyb5DrrCqps7MWVc9lE1bbncoy5atarW43ftrryRHJbk6df2DEW14LKmqzdL49vuxJF9OXT/eonXLY27u3Jfk3HMPXednO+20Zd797v1y+OHb5IADLh7qZe3qsE2S45L8McmX2pyHW6uqaUlOTfJAkotT1ytatG7a1+OPn50LLzwyHR3Vmp+97GXPy8EHPydHHnlZfvjDe5qts11ft0/yuiS3p66/1rozY1hVTUry5jT+P76Uun64ResvJ5ky3OvPz33uL3PSSWvbzZq1WU4+ec8cfPBzst9+F2XFip6kfR22TPKGJH1JPpu6bnkiGfeqqivJSUk2S6NeD7VoPaJ6nX763pk2bWq7Zq3rBU8Dtz4xqqpq3l5JdkuLY/H++5flQx/6Wf76ry/P9df/cbir7kyyW/+j+QZvsDNV9bokv07y9STvTfKFJ7Hr40tVdafxiMIzk/w0VXVlqmr/DZu1rtejj67Me95zTZ7//H/L5pt/Ih/60Nq8573vPaDVHjSrV0eq6pgkNye5vH//vjSCno1FFyV5T5L5SX6Vqvqb/qBqHa1qcdNND+WYY76ZWbM+k+7uf8mrX/2NgQvFHHnkC7LFFi0vUJrV4oBU1XeSXJdGHa5MVbW90mljfhp9vSqN425Of0Cy3qbbnyeS5B3v2Du77DK93YihwTbsa1VVqaqXJflxkh/279//HaoG48j/SfL+NN5bt6eqTur/ULOO4dbh7rsfS1XNW+dPi9E0SarOyenZ+++rv33Vehuc/f1qx6uS7F6nY4PjYsBf/dUL14Q0X/vabdlhh8+nu/tfsuuuF+Scc64f8jV1Ojqmpmeff6v2f3d/cD+wzampqlNvyDb3LM2UFwy13fX71tX1sTzwwLIkyW23Lcovf/lgi742fX/tlaqan+TGJGcl+c9U1RatVjTGzU3ykSSfSXJ3quqdqaoNblkYzjF33HG7JEl6evry0pdemunTP7nmWmH//RvfrA+hWR22SVV9KskdSc5JckGSl460c+v5YP+fLyS5M1V16lDnzlZ97eys8rGPHZaOjip33vlott323/LCF56fu+9+LJMnd+Yzn/mLVttv1tftU1UXJPld//59NVW1+5Ps41jwt0nOS/KxNI65D6eqZqzfaHAdhnP9uf/+z14T0lx++e2ZMeNTede7GuHwi160Vd7+9hcPNG1Why1TVf+c5O4kH0/yiTRCwonuqDTOD+emUa9zU1XPWr/RSOuVJBdccEv+/u+vyvved22r7Q9dL3gajecLT8aG09Lm29lf/GJhzjzzJ/n2t3+f5ct7RrLuyf3rXz+guSTJLoPabT+SlY5TM9P41mLAnAwd2JyWFvX6i7/4es455/rcc8+SLFu2Omee+ZM89lhj2Pn2209rtw+D6zU4oPl6Gr+EB4z3eg3u34uSXJahA5vT0qQW3/nO3bnsstvzxz8+nhUrevKtb92Z3/xm7ZeHq1f3ttuHwbUYHNC8fFCbZyTZajgdamFwX/dLcmWGDmxOS5vzxKxZm+W97z0gDz74eM4//79Hsg+Nvq4b0PwgySGD2jwnyZ8bSpVscB22TXJ+hg5sTstTPpqmoTdVZ2+qK1JVF/TX/5Ikt3wpL37FyjYP7Xjb2xofhO6667Ecd9y3c8cdj2bFip789reP5DOfuanp61ano7o2256T5Depqjemqt6a5M4k530yB27VbrsDjjpqh8ya1Th9fu5zNw/nJYPfX4MDmtes12abYe3A2DT4mJuR5KMZOrA5LW2OuZ6exmCXhQsfzzXX3JdFi1bke9+7e83y7u6mg8cH12FwQHNy/7Kh9vXJGPz6WWmEBUMFNqelSV93221GttpqkyTJZZfdnj/8YUnuuuuxXH55466MHXfcMi95ydat9mFwXwcHNCdk3afijOffr4P7tmmSd2XowOa09NdhONeff/d3ay8nP/KRG/LII8tz7rk/z5IlKzdYnnXrMDigeU+SzZvs60S13aC/d6cR7g4V2JyWEdQrSU4//Ue58MJb8oc/LGm3D2vrBaNAUMNom5P2j857srqSek6LgIb21g9sWtZr/ZEMkyd3prOz8Xn7/vuXtdvWQL2aBTQT2VCBzbDeO1OnduXVr94uu+7auA79yld+m2XL2o446ZqUnlc1CWg2tqECm7Z9nTfv0Gy+eeM2lyZzUjTTNTk9r87QAc1Etm02DGyGdczNmrVZHn74lKxc+Y7cdtuJOfvsgzJ5cuuX9aYz381OVRofHK9M41aM6jvZKb0tNtnRUeWgg56TJLnvvqX5xjeOyuLFp+aRR07JV77yymy9dfN5JXvTme9kpyTZKcmFSf41jQ/Sabfdwd785sa36cuXr86FF/5mOC/p6krvXzcJaCayoQKbtsfc+ef/Okny7GdvmkMOeW623HJq/vIvt02S3H//0txyS9M7XLo60vdXLQKajWmowKZpX1uETWvstdcGgw0G6+pM71+1CGgmqqECmxFdl774xTPX/P322xcnSfr66vz+948ladx2POj811Wl75UtAhpaGyqw2cifIzJnI60b2jJHDaOmquZtkWSjDu2elN6Zi9N9yRZZ3qrZc1NVt7ZqMAFscJvDeuYsTvecSenJ6hGcNubO3Sebbda47v3CF349jJ3onbk43V9vU68Z47xerd4TL0py2aJ0r+hK75SeFtcmm246KcuWvX2dn1122e058cSrhrsfWy5O98vb1OKHqaoRDXNbT9PbWdIf2CxK9/Ku9E5t1ddDD90mr3/9Lrn22vvzxS/+Ju9734Ej2ok62WJxug9p09cbU1Xjdfb957dYtm2S8xel+9Nd6e1qVYcBkyd3Zvr0xlyoO+64Zc4664Dsu+/WOfLIlnMjZVG6szjdGajD4v5/tzJ9eveaySAPOeS56yw79tjZ2WefrbPXXl9q+u3q+tsc7nYHbLfdtBxxROO/72tfu33wxKEtVamnL073a9occ/NTVSNKHceQWS2WzUjy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ACiEoAYAAACgEIIaAAAAgEIIagAAAAAKIagBAAAAKISgBgAAAKAQghoAAACAQghqAAAAAAohqAEAAAAohKAGAAAAoBCCGgAAAIBCCGoAAAAACiGoAQAAACiEoAYAAACgEIIaAAAAgEIIagAAAAAKIagBAAAAKISgBgAAAKAQghoAAACAQghqAAAAAAohqAEAAAAohKAGAAAAoBCCGgAAAIBCCGoAAAAACiGoAQAAACiEoAYAAACgEIIaAAAAgEIIagAAAAAKIagBAAAAKISgBgAAAKAQghoAAACAQghqAAAAAAohqAEAAAAohKAGAAAAoBCCGgAAAIBCCGoAAAAACiGoAQAAACiEoAYAAACgEIIaAAAAgEIIagAAAAAKIagBAAAAKISgBgAAAKAQghoAAACAQghqAAAAAAohqAEAAAAohKAGAAAAoBCCGgAAAIBCCGoAAAAACiGoAQAAACiEoAYAAACgEIIaAAAAgEIIagAAAAAKIagBAAAAKISgBgAAAKAQghoAAACAQghqAAAAAAohqAEAAAAohKAGAAAAoBCCGgAAAIBCCGoAAAAACiGoAQAAACiEoAYAAACgEIIaAAAAgEIIagAAAAAKIagBAAAAKMT/A1gxzkJygaVKAAAAAElFTkSuQmCC\n",
-      "text/plain": [
-       "<Figure size 1440x360 with 1 Axes>"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    }
-   ],
-   "source": [
-    "print('Number of nodes is {}:'.format(len(FG.nodes)))\n",
-    "print('Number of edges is {}:'.format(len(FG.edges)))\n",
-    "\n",
-    "fig, ax = plt.subplots(figsize=(20, 5))\n",
-    "\n",
-    "# draw edges, nodes and labels.\n",
-    "nx.draw_networkx_edges(FG,  pos=positions, width=3, edge_color=\"red\", alpha=1, arrowsize=20)\n",
-    "nx.draw_networkx_nodes(FG,  pos=positions, node_color=\"darkblue\", node_size=600)\n",
-    "nx.draw_networkx_labels(FG, pos=positions, labels=labels, font_size=15, font_weight='bold', font_color=\"white\")\n",
-    "\n",
-    "plt.axis(\"off\")\n",
-    "plt.show()"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "id": "69edbb25",
-   "metadata": {},
-   "source": [
-    "### 4. Run simulation\n",
-    "Now we can define the run. After we define the path that the vessel will sail, we make an environment and add the graph to the environment. Then we add one vessel, to which we will append the environment and the route. Lastly, we give the vessel the process of moving from the origin to the destination of the defined path and subsequently run the model."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 9,
-   "id": "19567f02",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "start_point = \"1\"\n",
-    "end_point = \"11\"\n",
-    "\n",
-    "path = nx.dijkstra_path(FG, start_point, end_point, weight=\"length\")\n",
-    "origin = FG.nodes[start_point][\"geometry\"]"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 10,
-   "id": "07dc4c50",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "simulation_start = datetime.datetime.now()\n",
-    "env = simpy.Environment(initial_time=time.mktime(simulation_start.timetuple()))\n",
-    "env.epoch = time.mktime(simulation_start.timetuple())"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "id": "dd36574d",
-   "metadata": {},
-   "source": [
-    "A lock complex consists of a lock chamber, and a waiting area and line-up area on both sides of the chamber. You need to impose certain parameters."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 11,
-   "id": "cb15d739",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# define the lock\n",
-    "waiting_area_1 = opentnsim.lock.IsLockWaitingArea(\n",
-    "    env=env, nr_resources=1, priority=True, name=\"Volkeraksluizen_1\", node=\"2\"\n",
-    ")\n",
-    "\n",
-    "lineup_area_1 = opentnsim.lock.IsLockLineUpArea(\n",
-    "    env=env,\n",
-    "    nr_resources=1,\n",
-    "    priority=True,\n",
-    "    name=\"Volkeraksluizen_1\",\n",
-    "    node=\"3\",\n",
-    "    lineup_length=300,\n",
-    ")\n",
-    "\n",
-    "lock_1 = opentnsim.lock.IsLock(\n",
-    "    env=env,\n",
-    "    nr_resources=100,\n",
-    "    priority=True,\n",
-    "    name=\"Volkeraksluizen_1\",\n",
-    "    node_1=\"5\",\n",
-    "    node_2=\"6\",\n",
-    "    node_3=\"7\",\n",
-    "    lock_length=300,\n",
-    "    lock_width=24,\n",
-    "    lock_depth=4.5,\n",
-    "    doors_open=10 * 60,\n",
-    "    doors_close=10 * 60,\n",
-    "    wlev_dif=2.0,\n",
-    "    disch_coeff=0.8,\n",
-    "    grav_acc=9.81,\n",
-    "    opening_area=4.0,\n",
-    "    opening_depth=5.0,\n",
-    "    simulation_start=simulation_start,\n",
-    "    operating_time=25 * 60,\n",
-    ")\n",
-    "\n",
-    "waiting_area_2 = opentnsim.lock.IsLockWaitingArea(\n",
-    "    env=env, nr_resources=1, priority=True, name=\"Volkeraksluizen_1\", node=\"10\"\n",
-    ")\n",
-    "\n",
-    "lineup_area_2 = opentnsim.lock.IsLockLineUpArea(\n",
-    "    env=env,\n",
-    "    nr_resources=1,\n",
-    "    priority=True,\n",
-    "    name=\"Volkeraksluizen_1\",\n",
-    "    node=\"9\",\n",
-    "    lineup_length=300,\n",
-    ")"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "id": "0946ba45",
-   "metadata": {},
-   "source": [
-    "Now, we need to impose the areas on the graph."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 12,
-   "id": "442e338f",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "lock_1.water_level = \"5\"\n",
-    "FG.nodes[\"6\"][\"Lock\"] = [lock_1]\n",
-    "\n",
-    "FG.nodes[\"2\"][\"Waiting area\"] = [waiting_area_1]\n",
-    "FG.nodes[\"3\"][\"Line-up area\"] = [lineup_area_1]\n",
-    "\n",
-    "FG.nodes[\"10\"][\"Waiting area\"] = [waiting_area_2]\n",
-    "FG.nodes[\"9\"][\"Line-up area\"] = [lineup_area_2]"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 13,
-   "id": "011e9de1",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# Add graph to environment\n",
-    "env.FG = FG\n",
-    "\n",
-    "# Add environment and path to the vessel\n",
-    "vessels = []\n",
-    "\n",
-    "vessel.env = env  # the created environment\n",
-    "vessel.name = \"Vessel No.1\"\n",
-    "vessel.route = path  # the route (the sequence of nodes, as stored as the second column in the path)\n",
-    "vessel.geometry = env.FG.nodes[path[0]][\n",
-    "    \"geometry\"\n",
-    "]  # a shapely.geometry.Point(lon,lat) (here taken as the starting node of the vessel)\n",
-    "\n",
-    "vessels.append(vessel)\n",
-    "\n",
-    "# Start the simulation\n",
-    "for vessel in vessels:\n",
-    "    vessel.env = env\n",
-    "    env.process(vessel.move())\n",
-    "    \n",
-    "env.run()"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "id": "f5850ce8",
-   "metadata": {},
-   "source": [
-    "### Output"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 14,
-   "id": "a0b31d71",
-   "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "text/html": [
-       "<div>\n",
-       "<style scoped>\n",
-       "    .dataframe tbody tr th:only-of-type {\n",
-       "        vertical-align: middle;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe tbody tr th {\n",
-       "        vertical-align: top;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe thead th {\n",
-       "        text-align: right;\n",
-       "    }\n",
-       "</style>\n",
-       "<table border=\"1\" class=\"dataframe\">\n",
-       "  <thead>\n",
-       "    <tr style=\"text-align: right;\">\n",
-       "      <th></th>\n",
-       "      <th>Message</th>\n",
-       "      <th>Timestamp</th>\n",
-       "      <th>Value</th>\n",
-       "      <th>Geometry</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>Sailing from node 1 to node 2 start</td>\n",
-       "      <td>2023-05-23 23:59:56.000000</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>Sailing from node 1 to node 2 stop</td>\n",
-       "      <td>2023-05-24 00:02:59.333333</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.0049407340626574 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>Sailing from node 2 to node 3 start</td>\n",
-       "      <td>2023-05-24 00:02:59.333333</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.0049407340626574 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>3</th>\n",
-       "      <td>Sailing from node 2 to node 3 stop</td>\n",
-       "      <td>2023-05-24 00:05:46.000000</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.009432310483255 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>4</th>\n",
-       "      <td>Sailing from node 3 to node 4 start</td>\n",
-       "      <td>2023-05-24 00:05:46.000000</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.009432310483255 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>5</th>\n",
-       "      <td>Sailing from node 3 to node 4 stop</td>\n",
-       "      <td>2023-05-24 00:07:26.000000</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.0121272563356135 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>6</th>\n",
-       "      <td>Sailing from node 4 to node 5 start</td>\n",
-       "      <td>2023-05-24 00:07:26.000000</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.0121272563356135 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>7</th>\n",
-       "      <td>Sailing from node 4 to node 5 stop</td>\n",
-       "      <td>2023-05-24 00:10:12.666667</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.0166188327562111 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>8</th>\n",
-       "      <td>Sailing from node 5 to node 6 start</td>\n",
-       "      <td>2023-05-24 00:10:12.666667</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.0166188327562111 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>9</th>\n",
-       "      <td>Sailing from node 5 to node 6 stop</td>\n",
-       "      <td>2023-05-24 00:11:02.666667</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.0179663056823904 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>10</th>\n",
-       "      <td>Sailing from node 6 to node 7 start</td>\n",
-       "      <td>2023-05-24 00:11:02.666667</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.0179663056823904 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>11</th>\n",
-       "      <td>Sailing from node 6 to node 7 stop</td>\n",
-       "      <td>2023-05-24 00:11:52.666667</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.0193137786085697 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>12</th>\n",
-       "      <td>Sailing from node 7 to node 8 start</td>\n",
-       "      <td>2023-05-24 00:11:52.666667</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.0193137786085697 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>13</th>\n",
-       "      <td>Sailing from node 7 to node 8 stop</td>\n",
-       "      <td>2023-05-24 00:14:39.333333</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.0238053550291673 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>14</th>\n",
-       "      <td>Sailing from node 8 to node 9 start</td>\n",
-       "      <td>2023-05-24 00:14:39.333333</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.0238053550291673 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>15</th>\n",
-       "      <td>Sailing from node 8 to node 9 stop</td>\n",
-       "      <td>2023-05-24 00:16:19.333333</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.0265003008815259 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>16</th>\n",
-       "      <td>Sailing from node 9 to node 10 start</td>\n",
-       "      <td>2023-05-24 00:16:19.333333</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.0265003008815259 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>17</th>\n",
-       "      <td>Sailing from node 9 to node 10 stop</td>\n",
-       "      <td>2023-05-24 00:19:06.000000</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.0309918773021235 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>18</th>\n",
-       "      <td>Sailing from node 10 to node 11 start</td>\n",
-       "      <td>2023-05-24 00:19:06.000000</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.0309918773021235 0)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>19</th>\n",
-       "      <td>Sailing from node 10 to node 11 stop</td>\n",
-       "      <td>2023-05-24 00:22:09.333333</td>\n",
-       "      <td>0</td>\n",
-       "      <td>POINT (0.0359326113647809 0)</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "                                  Message                  Timestamp  Value  \\\n",
-       "0     Sailing from node 1 to node 2 start 2023-05-23 23:59:56.000000      0   \n",
-       "1      Sailing from node 1 to node 2 stop 2023-05-24 00:02:59.333333      0   \n",
-       "2     Sailing from node 2 to node 3 start 2023-05-24 00:02:59.333333      0   \n",
-       "3      Sailing from node 2 to node 3 stop 2023-05-24 00:05:46.000000      0   \n",
-       "4     Sailing from node 3 to node 4 start 2023-05-24 00:05:46.000000      0   \n",
-       "5      Sailing from node 3 to node 4 stop 2023-05-24 00:07:26.000000      0   \n",
-       "6     Sailing from node 4 to node 5 start 2023-05-24 00:07:26.000000      0   \n",
-       "7      Sailing from node 4 to node 5 stop 2023-05-24 00:10:12.666667      0   \n",
-       "8     Sailing from node 5 to node 6 start 2023-05-24 00:10:12.666667      0   \n",
-       "9      Sailing from node 5 to node 6 stop 2023-05-24 00:11:02.666667      0   \n",
-       "10    Sailing from node 6 to node 7 start 2023-05-24 00:11:02.666667      0   \n",
-       "11     Sailing from node 6 to node 7 stop 2023-05-24 00:11:52.666667      0   \n",
-       "12    Sailing from node 7 to node 8 start 2023-05-24 00:11:52.666667      0   \n",
-       "13     Sailing from node 7 to node 8 stop 2023-05-24 00:14:39.333333      0   \n",
-       "14    Sailing from node 8 to node 9 start 2023-05-24 00:14:39.333333      0   \n",
-       "15     Sailing from node 8 to node 9 stop 2023-05-24 00:16:19.333333      0   \n",
-       "16   Sailing from node 9 to node 10 start 2023-05-24 00:16:19.333333      0   \n",
-       "17    Sailing from node 9 to node 10 stop 2023-05-24 00:19:06.000000      0   \n",
-       "18  Sailing from node 10 to node 11 start 2023-05-24 00:19:06.000000      0   \n",
-       "19   Sailing from node 10 to node 11 stop 2023-05-24 00:22:09.333333      0   \n",
-       "\n",
-       "                        Geometry  \n",
-       "0                    POINT (0 0)  \n",
-       "1   POINT (0.0049407340626574 0)  \n",
-       "2   POINT (0.0049407340626574 0)  \n",
-       "3    POINT (0.009432310483255 0)  \n",
-       "4    POINT (0.009432310483255 0)  \n",
-       "5   POINT (0.0121272563356135 0)  \n",
-       "6   POINT (0.0121272563356135 0)  \n",
-       "7   POINT (0.0166188327562111 0)  \n",
-       "8   POINT (0.0166188327562111 0)  \n",
-       "9   POINT (0.0179663056823904 0)  \n",
-       "10  POINT (0.0179663056823904 0)  \n",
-       "11  POINT (0.0193137786085697 0)  \n",
-       "12  POINT (0.0193137786085697 0)  \n",
-       "13  POINT (0.0238053550291673 0)  \n",
-       "14  POINT (0.0238053550291673 0)  \n",
-       "15  POINT (0.0265003008815259 0)  \n",
-       "16  POINT (0.0265003008815259 0)  \n",
-       "17  POINT (0.0309918773021235 0)  \n",
-       "18  POINT (0.0309918773021235 0)  \n",
-       "19  POINT (0.0359326113647809 0)  "
-      ]
-     },
-     "execution_count": 14,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "df = pd.DataFrame.from_dict(vessels[0].logbook)\n",
-    "df"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 15,
-   "id": "962bdb81",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "locking_cycle_start_dir1 = simulation_start.timestamp()\n",
-    "locking_cycle_starts_dir1 = []\n",
-    "locking_cycle_stop_dir1 = simulation_start.timestamp()\n",
-    "locking_cycle_stops_dir1 = []\n",
-    "locking_cycle_start_dir2 = simulation_start.timestamp()\n",
-    "locking_cycle_starts_dir2 = []\n",
-    "locking_cycle_stop_dir2 = simulation_start.timestamp()\n",
-    "locking_cycle_stops_dir2 = []\n",
-    "\n",
-    "for v in range(0, len(vessels)):\n",
-    "    for t in range(0, len(vessels[v].logbook) - 1):\n",
-    "        row = vessels[v].logbook[t]\n",
-    "        if vessels[v].route[0] == \"Node 1\":\n",
-    "            if row[\"Message\"] == \"Passing lock start\":\n",
-    "                if (\n",
-    "                    locking_cycle_start_dir1\n",
-    "                    <= row[\"Timestamp\"].timestamp()\n",
-    "                    <= locking_cycle_stop_dir1\n",
-    "                ):\n",
-    "                    pass\n",
-    "                else:\n",
-    "                    locking_cycle_start_dir1 = (\n",
-    "                        vessels[v].logbook[t-2][\"Timestamp\"].timestamp()\n",
-    "                    )\n",
-    "                    locking_cycle_starts_dir1.append(\n",
-    "                        [vessels[v].logbook[t-2][\"Timestamp\"].timestamp(), \"Start\"]\n",
-    "                    )\n",
-    "                    locking_cycle_stop_dir1 = (\n",
-    "                        vessels[v].logbook[t + 1][\"Timestamp\"].timestamp()\n",
-    "                    )\n",
-    "                    for v2 in range(v, len(vessels)):\n",
-    "                        for t2 in range(0, len(vessels[v2].logbook) - 1):\n",
-    "                            if vessels[v2].logbook[t2][\"Message\"] == \"Passing lock stop\":\n",
-    "                                if (\n",
-    "                                    locking_cycle_start_dir1\n",
-    "                                    <= vessels[v2].logbook[t2 - 1][\"Timestamp\"].timestamp()\n",
-    "                                    <= locking_cycle_stop_dir1\n",
-    "                                    and vessels[v2].route[0] == \"Node 1\"\n",
-    "                                ):\n",
-    "                                    if len(locking_cycle_starts_dir1) != len(\n",
-    "                                        locking_cycle_stops_dir1\n",
-    "                                    ):\n",
-    "                                        locking_cycle_stops_dir1.append(\n",
-    "                                            [\n",
-    "                                                vessels[v2]\n",
-    "                                                .logbook[t2 + 2][\"Timestamp\"]\n",
-    "                                                .timestamp(),\n",
-    "                                                \"Stop\",\n",
-    "                                            ]\n",
-    "                                        )\n",
-    "                                    elif (\n",
-    "                                        vessels[v2].logbook[t2 + 2][\"Timestamp\"].timestamp()\n",
-    "                                        > locking_cycle_stops_dir1[-1][0]\n",
-    "                                    ):\n",
-    "                                        locking_cycle_stops_dir1[-1][0] = (\n",
-    "                                            vessels[v2]\n",
-    "                                            .logbook[t2 + 2][\"Timestamp\"]\n",
-    "                                            .timestamp()\n",
-    "                                        )\n",
-    "\n",
-    "        elif vessels[v].route[0] == \"Node 21\":\n",
-    "            if vessels[v].logbook[t][\"Message\"] == \"Passing lock start\":\n",
-    "                if (\n",
-    "                    locking_cycle_start_dir2\n",
-    "                    <= vessels[v].logbook[t][\"Timestamp\"].timestamp()\n",
-    "                    <= locking_cycle_stop_dir2\n",
-    "                ):\n",
-    "                    pass\n",
-    "                else:\n",
-    "                    locking_cycle_start_dir2 = (\n",
-    "                        vessels[v].logbook[t - 2][\"Timestamp\"].timestamp()\n",
-    "                    )\n",
-    "                    locking_cycle_starts_dir2.append(\n",
-    "                        [vessels[v].logbook[t - 2][\"Timestamp\"].timestamp(), \"Start\"]\n",
-    "                    )\n",
-    "                    locking_cycle_stop_dir2 = (\n",
-    "                        vessels[v].logbook[t + 1][\"Timestamp\"].timestamp()\n",
-    "                    )\n",
-    "                    for v2 in range(v, len(vessels)):\n",
-    "                        for t2 in range(0, len(vessels[v2].logbook) - 1):\n",
-    "                            if vessels[v2].logbook[t2][\"Message\"] == \"Passing lock stop\":\n",
-    "                                if (\n",
-    "                                    locking_cycle_start_dir2\n",
-    "                                    <= vessels[v2].logbook[t2 - 1][\"Timestamp\"].timestamp()\n",
-    "                                    <= locking_cycle_stop_dir2\n",
-    "                                    and vessels[v2].route[0] == \"Node 21\"\n",
-    "                                ):\n",
-    "                                    if len(locking_cycle_starts_dir2) != len(\n",
-    "                                        locking_cycle_stops_dir2\n",
-    "                                    ):\n",
-    "                                        locking_cycle_stops_dir2.append(\n",
-    "                                            [\n",
-    "                                                vessels[v2]\n",
-    "                                                .logbook[t2 + 2][\"Timestamp\"]\n",
-    "                                                .timestamp(),\n",
-    "                                                \"Stop\",\n",
-    "                                            ]\n",
-    "                                        )\n",
-    "                                    elif (\n",
-    "                                        vessels[v2].logbook[t2 + 2][\"Timestamp\"].timestamp()\n",
-    "                                        > locking_cycle_stops_dir2[-1][0]\n",
-    "                                    ):\n",
-    "                                        locking_cycle_stops_dir2[-1][0] = (\n",
-    "                                            vessels[v2]\n",
-    "                                            .logbook[t2 + 2][\"Timestamp\"]\n",
-    "                                            .timestamp()\n",
-    "                                        )\n",
-    "\n",
-    "inoperation_time_doors1 = locking_cycle_starts_dir2 + locking_cycle_stops_dir1\n",
-    "inoperation_time_doors1.sort()\n",
-    "inoperation_time_doors2 = locking_cycle_starts_dir1 + locking_cycle_stops_dir2\n",
-    "inoperation_time_doors2.sort()\n",
-    "loop_time_doors1 = []\n",
-    "loop_time_doors2 = []\n",
-    "\n",
-    "for i in range(len(inoperation_time_doors1)):\n",
-    "    if (\n",
-    "        i != 0\n",
-    "        and inoperation_time_doors1[i][1] == \"Start\"\n",
-    "        and inoperation_time_doors1[i - 1][1] != \"Start\"\n",
-    "    ):\n",
-    "        loop_time_doors1.append(\n",
-    "            (inoperation_time_doors1[i][0] - inoperation_time_doors1[i - 1][0]) / 60\n",
-    "        )\n",
-    "\n",
-    "for i in range(len(inoperation_time_doors2)):\n",
-    "    if (\n",
-    "        i != 0\n",
-    "        and inoperation_time_doors2[i][1] == \"Start\"\n",
-    "        and inoperation_time_doors2[i - 1][1] != \"Start\"\n",
-    "    ):\n",
-    "        loop_time_doors2.append(\n",
-    "            (inoperation_time_doors2[i][0] - inoperation_time_doors2[i - 1][0]) / 60\n",
-    "        )"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 16,
-   "id": "98e729f6",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "waiting_to_waiting_area = []\n",
-    "waiting_in_waiting_area = []\n",
-    "waiting_in_lineup_area = []\n",
-    "waiting_in_lock = []\n",
-    "total_waiting_time = np.zeros(len(vessels))\n",
-    "\n",
-    "for v in range(0, len(vessels)):\n",
-    "    for t in range(0, len(vessels[v].logbook) - 1):\n",
-    "        if vessels[v].logbook[t][\"Message\"] == \"Waiting to enter waiting area stop\":\n",
-    "            waiting_to_waiting_area.append(vessels[v].logbook[t][\"Value\"] / 60)\n",
-    "        if vessels[v].logbook[t][\"Message\"] == \"Waiting in waiting area stop\":\n",
-    "            waiting_in_waiting_area.append(vessels[v].logbook[t][\"Value\"] / 60)\n",
-    "        if vessels[v].logbook[t][\"Message\"] == \"Waiting in line-up area stop\":\n",
-    "            waiting_in_lineup_area.append(vessels[v].logbook[t][\"Value\"] / 60)\n",
-    "        if vessels[v].logbook[t][\"Message\"] == \"Passing lock stop\":\n",
-    "            waiting_in_lock.append(vessels[v].logbook[t][\"Value\"] / 60)\n",
-    "\n",
-    "for v in range(len(vessels)):\n",
-    "    for t in range(0, len(vessels[v].logbook) - 1):\n",
-    "        if (\n",
-    "            vessels[v].logbook[t][\"Message\"] == \"Waiting to enter waiting area stop\"\n",
-    "            or vessels[v].logbook[t][\"Message\"] == \"Waiting in waiting area stop\"\n",
-    "            or vessels[v].logbook[t][\"Message\"] == \"Waiting in line-up area stop\"\n",
-    "            or vessels[v].logbook[t][\"Message\"] == \"Passing lock stop\"\n",
-    "        ):\n",
-    "            total_waiting_time[v] += vessels[v].logbook[t][\"Value\"] / 60"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 17,
-   "id": "d8363a48",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "def calculate_distance(orig, dest):\n",
-    "    wgs84 = pyproj.Geod(ellps=\"WGS84\")\n",
-    "\n",
-    "    distance = wgs84.inv(orig[0], orig[1], dest[0], dest[1])[2]\n",
-    "\n",
-    "    return distance\n",
-    "\n",
-    "\n",
-    "vessel_path_x = []\n",
-    "vessel_path_t = []\n",
-    "origin = \"1\"\n",
-    "destination = \"11\"\n",
-    "\n",
-    "for v in range(0, len(vessels)):\n",
-    "    vessel_path_xt = []\n",
-    "    vessel_path_tt = []\n",
-    "    for t in range(0, len(vessels[v].logbook)):\n",
-    "        if vessels[v].route[0] == \"1\":\n",
-    "            vessel_path_x0 = (\n",
-    "                vessels[v].logbook[0][\"Geometry\"].x,\n",
-    "                vessels[v].logbook[0][\"Geometry\"].y,\n",
-    "            )\n",
-    "            vessel_path_t0 = simulation_start.timestamp()\n",
-    "            vessel_path_xt.append(\n",
-    "                calculate_distance(\n",
-    "                    vessel_path_x0,\n",
-    "                    (vessels[v].logbook[t][\"Geometry\"].x, vessels[v].logbook[t][\"Geometry\"].y),\n",
-    "                )\n",
-    "            )\n",
-    "            vessel_path_tt.append(\n",
-    "                vessels[v].logbook[t][\"Timestamp\"].timestamp() - vessel_path_t0\n",
-    "            )\n",
-    "        if vessels[v].route[0] == \"11\":\n",
-    "            vessel_path_x0 = (\n",
-    "                vessels[v].logbook[0][\"Geometry\"].x,\n",
-    "                vessels[v].logbook[0][\"Geometry\"].y,\n",
-    "            )\n",
-    "            vessel_path_t0 = simulation_start.timestamp()\n",
-    "            vessel_path_xt.append(\n",
-    "                calculate_distance(\n",
-    "                    (\n",
-    "                        env.FG.nodes[destination][\"geometry\"].x,\n",
-    "                        env.FG.nodes[destination][\"geometry\"].y,\n",
-    "                    ),\n",
-    "                    (\n",
-    "                        env.FG.nodes[origin][\"geometry\"].x,\n",
-    "                        env.FG.nodes[origin][\"geometry\"].y,\n",
-    "                    ),\n",
-    "                )\n",
-    "                - calculate_distance(\n",
-    "                    vessel_path_x0,\n",
-    "                    (vessels[v].logbook[t][\"Geometry\"].x, vessels[v].logbook[t][\"Geometry\"].y),\n",
-    "                )\n",
-    "            )\n",
-    "            vessel_path_tt.append(\n",
-    "                vessels[v].logbook[t][\"Timestamp\"].timestamp() - vessel_path_t0\n",
-    "            )\n",
-    "    vessel_path_x.append(vessel_path_xt)\n",
-    "    vessel_path_t.append(vessel_path_tt)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 18,
-   "id": "f1511a97",
-   "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "image/png": "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\n",
-      "text/plain": [
-       "<Figure size 1008x180 with 5 Axes>"
-      ]
-     },
-     "metadata": {
-      "needs_background": "light"
-     },
-     "output_type": "display_data"
-    }
-   ],
-   "source": [
-    "data = [\n",
-    "    waiting_to_waiting_area,\n",
-    "    waiting_in_waiting_area,\n",
-    "    waiting_in_lineup_area,\n",
-    "    waiting_in_lock,\n",
-    "    total_waiting_time,\n",
-    "]\n",
-    "xaxes = \"time [min]\"\n",
-    "titles = [\n",
-    "    \"Waiting to Waiting Area\",\n",
-    "    \"Waiting in Waiting Area\",\n",
-    "    \"Waiting in Line-up Area\",\n",
-    "    \"Waiting in Lock\",\n",
-    "    \"Total Waiting Time\",\n",
-    "]\n",
-    "bins = [\n",
-    "    range(0, 200, 10),\n",
-    "    range(0, 200, 10),\n",
-    "    range(0, 200, 10),\n",
-    "    range(0, 100, 5),\n",
-    "    range(0, 400, 20),\n",
-    "]\n",
-    "\n",
-    "f, a = plt.subplots(1, 5, figsize=(14, 2.5))\n",
-    "a = a.ravel()\n",
-    "for idx, ax in enumerate(a):\n",
-    "    if idx == 0:\n",
-    "        ax.set_ylabel(\"Number of vessels\")\n",
-    "    ax.hist(data[idx], bins=bins[idx])\n",
-    "    ax.set_title(titles[idx])\n",
-    "    ax.set_xlabel(xaxes)\n",
-    "    ax.set_ylim(0, max(ax.hist(data[idx], bins=bins[idx])[0]) + 1)\n",
-    "    ax.yaxis.set_major_locator(MaxNLocator(integer=True))\n",
-    "plt.tight_layout()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 19,
-   "id": "a611e70d",
-   "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAmoAAAJ2CAYAAADvxq6fAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAAB/IElEQVR4nO3dd5xU1f3/8deH3psUAVFUUMFdRapoIgomUQM/Y1DsoCIoCel+ExMNbNQU0xMTFBREQUVRkxBTLGCLIr0tIIrYqLv0Xnb3/P6Yi644uzvA7J57Zt7Px4MHO/2z5z3DfLj33HvMOYeIiIiIxE813wWIiIiISHJq1ERERERiSo2aiIiISEypURMRERGJKTVqIiIiIjGlRk1EREQkptSoiRwhM3vFzJyZ3XAkt1diXR9Er/u16PLE6HJeVdZRWcysnZm9bGZ7ot+rv++aqkL0uzoza++7lsPh8/1nZnnRa09M43Om7fcxs/YHc01DaZKhavguQCQdzOwD4IRy7nKBc+6VqqnmE08DC4FlVfy6h3oB2Aq85bmOdPkxcD6QD0wHVnmtRipSJe+/Us3Oic65DyrztUSqkho1yRQTgGbRzyOAWsAzwOroutWl72xmNZ1zByqzIOfcXyrz+VPlnHsceLwyX8PMqkWvVVKZrxM5Jfr7D865CUf6JFXxHpCqef+JZDLt+pSM4Jy7yzn3Xefcd4E90dV/KXXdu9Euhu+a2fvACgAze9zM1pjZPjPbYWYzzCz34POaWTMz+7OZvWdme81sVbJdbWZWzcyejF7jOTOrfeiuz1K7TB4ws3+a2W4zW2xmXUo9zxfMbImZ7TKzSWY2JXrMH8v63c3sG2b2sZkVmtn/Jbn9M7tqzOxLZrbAzLaZ2QEz+9DMflbq/mZmd5lZQTQ215fa7dYlus/B3+1eM5sF7AeON7PbzOzdqP59ZrbIzC5PUsujZvafaPflC2Z2gpk9Ez1uppmdWMbv+grQL7o4/uCuQDOrb2a/iXLaaWYLzez6Uo87uAvsaTN7ysz2ANce8twNoscWmVnL6Lp6pa47Nrrupuj32hn9rj8xsxrRbe3N7L9mtiX63VYcHFsza2pmU81sY/Reet/MxpZ6/Rwz+1c07oXReBxfTu7fjX7ffdFzvmJmp5Zx34Pjfn807rvNbO4h772KPgtlvp6ZXWNmy6LfeXOU4RcOee28JFk8Go3jSjO7sNRrdTazt6I6/2Vm90WP+XsZv1/pXYfvR/c9v9R1dc1sfBmvdYyZjbXElIEdZvaGmX2xrHFP8tpmZsPt08/tSjO7x8zqlLrP2dH7vDCq4S0zq5fkuTqY2TozKzazwanWIJlNjZpkm18Ar5HYHQOJ3aWvAA8B84ELgKfgk61Efwe+BdQGJpPYzXbSIc9pwIPAIOBfwEDn3L5yargFKALeB3KB+6LXawL8E8gBZgMtgSvK+2WiL6O/Am2i3+k6oF15jwHaAhuBKcAkoCEwysyuim6/Afgp0Ah4Ecgr57n+DygAngD2AScCS4CJwD+A04HJ9vl5VdcBO4HNwJeARUATEuN7NnB3Ga/3NLAm+vlF4E/AduBh4DagmER+HYFHzezqQx4/EDg5+r3Xl77BObcT+BtQPbofwFeB+sALzrn1ZnYLMB5oGr3OHuDnwB3R/e8BvgLMAR4FPgZ6Rbf9ALgceDeqdzlwDkDUBL4WjcX/SLwnvw48b2a1Dx0EM+sA/IFERg+TyP54oHUZ43bQLcAuErvkuwHPlWooyvsslPl6ZlaXRN4nAI+R+Aw0IjHO5RlI4n2bH913QvRaNYBpJMZtGYkxHlHBc/2p1M8PR5dLb0W/Iqrv0NeqRuJ9Ohz4KPp9c4EXymp6kxgBjCXxuXuSxJ6qOw7WZGY5JMb1SyQyfxJoTmKr/yfMrB2JXfktgRudc4+m+PqS6Zxz+qM/GfWHxHwYB5xf6joX/bnpkPu2JdGI/YpEw3Twfm2A7tHPe4DWpR5TM/r7lej2FdHfzwG1S93v4O03RJcnRpf/FV2+ILq8M7p8XXT5PcCi6xZG1/2xjN/1oej28dHlZiS2bjnga4e8bl50uRpwCXAniS/fOdHt46LbX4ouj44un1VqXLoc8rs9ekg99YHBJBqtPwDrovtdc0gtL0WX86LL66K6BkSXl5aT76Hj2rJUfSdE130nuvzmIa/zHlCjnOf+UnS/V6LLU6PLV0WXl0aXnwb+SKJ5d8D66PYno8s/jsatDlA9uu3eg1kCPYEGpW77v+i2ZdHtfyTRADvgokPew+2BTtHPi4AvA8dF96lexu91cNz/dvA9DGyIrvtqCp+FMl8v+j2KSTRG/YGTStfC599/B7PIJ/GfnBNLvVZz4AvRzzuA+tFj/hFd9/dysvtkfEpdV9Fr9Yh+3l5q3OdH1/2qgrE8+Pssiy4PiS6fGV0ujvL/S3T5H6WeozqJ93v7UvWsiB4z2Pe/ofoTrz+aoybZ5o2DP5hZRxL/KDdIcr8WJP5RB/jIObfu4A3u8/OaDs6ZGu/K35J20ILo763R3/Wjv9tGf69wzrno5+Uk/uEvyyePiWrbbGabgGPLecz9JLYgHKrFIc+5PPq7vIMhSo9nLRITxnPKee6DDj731ujvlc65EjPbEV2uT+raR3/vcc59GP38dvT3CYfcd7Zzrqic55pOYovdF6OtSJcA20hsWS39WgMPeVwrM2tAojE4jkSj+gsSWxnvI9GI/ZFElt8g0UgWA09aYhftweftFP0prcOhRTrnlpvZaODbwPMAZraCxBa7/HJ+v+XR4w+Y2SoSTe5xFX0WnHOLyno951y+mY0ARpPYIoyZrQauJ9FUl2Whc86Z2dZS1zXg0/ffx865XdHPy4D/V85zVaSs12of/dyQRCalfW7cy3DwOQ6+pw++96qR2Mp28N+RTw6mcM4VA5hZ6ec5BfiAaAxFDtKuT8k2pRupr5L4x3ohid1urUrdZiR2TUJi7tUnjU+0a6a0J0jsypxsZuekUMPBRsEdcv3BXXqlvyBOq+C5Dj7m4FyhZsAxFTzmyujv60n8z/7+6PLBb42Dz9kxhRpKj2dnEk1aEYndS9X4tMmzQx5XXMHlw/FB9HfdUnO6Du62+vCQ+5bbSLvEwRCTSdQ+AagHTHXO7T3ktS51ztnBPyS2Iu0EVjnnzgUak9hqthm4Ldqttdk5dxGJpuBMElvnrgHOLfW8fzvkeVuT2NX6GWZWHfi5c645iWb03uh3/l55vx9RE2hmNfl0F/5qKvgspPB6jzjn2pLY+vYdEs3qTyuopaLPwXHRblWo+HMAcPBAlmTfa2W91gfR3+uAOqXGvR4wMoXXLP0cB2s8+N4rIbHr++C/Iwd3gR+c03roZ+JREk3fc8nmr0n20hY1yWYbor9PITGfpMsht88HXge+CMwxs/+S+AL6D/DnUvd7gcTuwvEk/pE9zzlX3laNsjxHYgtTRzN7icSXyxkVPOZxYChwQzTXKIeKP9cbSDQS3wYuBi475PbJJCbs3xFtVUp1YvVGEl9ONYDfkWhIOpb7iDRwzhWY2dMktia9aGZvkJgvCIndTofrUeBHfPp7l54r9BdgDDDJzP5GoinoTmI35fnAmGhu0woSTXBzEk3oTuB2M/t/JObw7efTLTHbSMzt+glwmZk9T+LL/2SgD4kx/OCQGtsBs8zstei1z42u31rB7/b/orFqQ2Jr2lpgBok5ZVD2Z6Gi19tgiQM91pKY45VKLWV5C1hJ4j8sr5jZR6S2Ne1jEk3kX8zsHT6dN1ieecBMoDeJz/ibJLZG9yHRhE5M4Tn+SuJ98Scz6wP0ja4f75zba2YPADcDl0Zj9C6J3bu9D3mem0nkciHwtJldmmTrvWQhbVGTbPYUieaqmMQ/jr8sfWO0deVrJHZdHSAx9+pUPv0fcun7TgB+RmKS+fNJJs9XyDm3lcQcrXwS/4gXkphUDWVsCXLOzSDRcK0j0XQ9TWJSdHluJrF7JpdEMzX2kNsfIbHrbieJifG/KnVbmVuknHOrScxx2kDiy2oe8GYFtaTLTSTmxNUiscVwFYkJ2Yd9Wgjn3DJgbnTxfRKT+w86+KW7ikRjeAmJnB6Kbn+TxJapQdGfFcC1zrktJBr/IhLvqcEkxunbzrnFzrm1JJqD50g0SdeR2AX4VxIN8KG2kzjg5FxgGIkv+CkkDmYozxgSB8Z0ieoZ4JzbQwWfhRRe70WgK4n/NJxO4oCCH1RQS1LRrulLgVkk3qP1+HSrYnlbRH9EYuvgRSS26tUt574HX6skeq0HSDSrN5CYW/gvUj/v2xgSu7PXAFeT+M/KL6MaiP7Tdj6J/8zlkNiKuo1Es166lgMkdqkvIfFZfjjJVjfJQgcnLItIDJhZY+fctujnaiR2j50G3Oyc+9wusEqqoTqJ3UC7osu9STQgxSQmd6cyD09ixBJn5h8C/Mw5l+e3moqV/hxEl58ncRDDPc65inapimQU7foUiZeHzKyIxMTkPiSatLUkTt5bVRoC+Wb2FLCXxBc8wFg1aVJF8szsZBJbNruQaNJ2EZ1WQySbqFETiZf5JHajfJ3EXKAngTuj3aJVZR+JeTQ3kdiV+AGJXXC/q8IaJLstIjEv7SskDsj4N/BT59znph2IZDrt+hQRERGJKR1MICIiIhJTatREREREYkqNmoiIiEhMqVHLQmb2kJl1jn7+ySG3VdV5r4JjZjujv9tEJw2VDHIw3yN43Ctm1j3d9chn6fMXb/peqTw6mCDLmdlO51yy9f3kEBqrzHak+UZnm7/NOTe3ovvKkdPnLxzKKr20RS1QZvZ/Zvbt6Oc/mNmM6Oe+ZvZY9PP9ZjbXzJaa2c9KPfYVM+tuZr8isT7iwlKPOfi/1vOj+z1tZm+b2WMHz5JtZpdE180zsz+b2XNV/Ot7ZWbtzSw/+vkGM3vWzP5rZu+a2a9L3e/LZjbTzOab2dRo0e5Dn+v80uNnZn8xsxuinz8ws1+b2RIzmx0t5ySVzBJ+Y2b50dhfWeq2H0XXLYo+P6UfV83MJppZRasDyFHQ56/y6HslntSohevgGpSQWGuwgSUWWv4i8Fp0/R3Oue4k1ovsY2afWTfSOXc7sMc518U5d22S1zgL+C6JxbZPAs61xHqSY4GLnXPdgBbp/bWC1IXEskW5wJVm1s7MmgN3Ahc657qSOHHn94/gubc553JJrCX4x/SUKxX4OolMzySxnNJvzKy1mV1MYrmhXs65M4Ffl3pMDRLrdb7rnLuziuvNdl3Q5y9d9L0SQ2rUwjUP6GZmjUicoHQmiQ/WF0l82AAGmdl8YAGJ9fc6H+ZrzHbOrY7Ww1tIYhHp04BVpU48+cTR/BIZYrpzbptzbi+wjMTC0GeTGO83zGwhibP7n3AEz/1Eqb8PXcRZKscXgCecc8XOuQ3Aq0APEk3bw8653QDOuc2lHjMWyHfO/bzKqxV9/tJH3ysxpJUJAuWcO2Bm75NYRPhNYDFwAdABWG5mJwK3AT2cc1sssdZfncN8mdLLBRWj90tZko2TAS86564ufUcz68Wni6CPInHW9dL/YTo0I1fGzxIvbwIXmNnvooZBqo4+f2mi75V40ha1sL1O4kPzWvTzrcAClzhCpBGJtfG2mVkr4OIynuNAtGk7VSuAk8ysfXT5ynLum83eIrFJvwOAmdU3s1Occ7OiXQJdnHPTgA+BzmZW28yaAP0OeZ4rS/09s6qKz3Kvk9iFVt3MWgDnAbOBF4EbzawegJk1K/WY8SSWOXrKzPTF458+f0dO3ysxo39QwvY6cAcw0zm3y8z2RtfhnFtkZguAt4GPgTfKeI5xwGIzm1/GfILPcM7tMbNvAP81s13AnHT8IpnGOVcYTUp+wsxqR1ffCbxzyP0+tsTi5/nA+yR2J5TW1MwWk/hf6NVIVfgbid1ci0hsRfmhc249ifd8F2Cume0n0Zh9choC59zvzawxMMnMro127YgH+vwdFX2vxIxOzyGHzcwaOOd2Rkfr/JXEBOo/+K4r05jZB0B359xG37WIZBt9/qqWvlfKpl2fciSGRRN0lwKN+XTOh4iIyJHQ90oZtEVNREREJKa0RU1EREQkptSoiUhWMrM8M7utEp73BjP7S7qfV0Sykxo1+YSZVfddQ7bQWEtZ9N6oXBrfqqOxTg81alminPXZPjCze6MzTV9hZayPZ2ajzGyOJdY/HHdwfTb5PI11/JjZRdE4LzKz6aVu6myJtQdXWbTGYXT/v1tizcGlZja81PU7LbEO6FIze8nMepZ6/P8r9bztouvfNbPRpR5/nSXWjVxoZmMPfpFFz/s7M1tEdpwBv1Los1d1NNZVyDmnP1nwB2gW/V0deAU4I7r8AYnzRAE0J3GSw/rR5R8Bo0o/Pvp5EjDA9+8U1z8a63j9IbFu4MfAiYfkk0fi7Ou1ozw2ATUPuU9dEufYOia67EisRwiJ8629ANQksS7owuj6G4B1wDGlHt8d6AT8s9RrjAEGl3reQb7HKvQ/+uxprDPxj054mz0GRVsGagCtSazPtji67cno79Lr4wHU4tOzcV9gZj8E6gHNSBxC/c+qKT04Gut4ORt4zUXrCLrPrtH5L+fcPmCfmRUArYDVwLfN7LLoPu2AjiQauf3Af6PrlwD7XGLZnSUk1iw86EXn3CYAM3uWxPqhRUA3YE6UeV2gILp/MfBM2n7j7KXPXtXRWFcRNWpZwCpen23XwbuSfH28OiT+99/dJc7kncfhr++WFTTWwfncuoNmdj6JBdh7O+d2m9krfJrBARdtAgBKDj7eOVdin1066tDzHjkSmT/inPtxkjr2OueKj+YXyXb67FUdjXXV0hy17JDq+mxJ18fj0w/Qxmh+weWVXXDANNbx8xZwXvTlcuganck0BrZETdppJLYKHK4vmVkzM6sLfI3EUjvTgcvNrOXBOszshCN4bklOn72qo7GuQtqilgVciuuzuTLWx3POvWNmD5KYa7MercNWJo11/ERjPRx41syqkdjd+KVyHvJf4FYzW05isei3juBlZ5PYlXkcMNk5NxfAzO4EXojqOAB8k8TC4HKU9NmrOhrrqqWVCURERERiSrs+RURERGJKjZqIiIhITKlRExEREYmpjDyYoHnz5q59+/a+ywjK2rVradOmje8yYuejjz6iRYsWvsuo0JYtW5RfEsovbMovbPpeSd28efM2OueSvtkz8mCC7t27u7lz5/ouIyhmRia+F46WmbFkyRLfZVQoNzdX+SWh/MKm/MKm75XUmdk851z3ZLdp16eIiIhITKlREwBat27tu4RYCmG3Cyi/sii/sCm/sGlc0kONmgCJuQTyeTNmzPBdQkqUX3LKL2zKL2wal/RQoyYA5OXl+S4hlsaMGeO7hJQov+SUX9iUX9g0LumhgwkE0KTPsmgyc9iUX9iUX9j0vZI6HUwgIiIiEiA1aiIiIiIxpUZNANCu4uSmTJniu4SUKL/klF/YlF/YNC7poUZNREREJKZ0MIEAmvRZFk1mDpvyC5vyC5u+V1KngwlEREREAqRGTURERCSm1KgJAKNHj/ZdQiyNGDHCdwkpUX7JKb+wKb+waVzSQ3PURMqRn5/vu4SU5OTk+C4hlpRf2JSfZAvNUZMKtWnTxncJsdS3b1/fJaRE+SWn/MKm/MKmcUkPNWoCwLp163yXEEuFhYW+S0iJ8ktO+YVN+YVN45IeatREREREYkqNmgDQtWtX3yXEUqdOnXyXkBLll5zyC5vyC5vGJT10MIFIOTSZOWzKL2zKT7KFDiaQCg0fPtx3CbGUl5fnu4SUKL/klF/YlF/YNC7poS1qAmipj7JoCZuwKb+wKb+w6XslddqiJiIiIhIgNWoiIiIiMaVGTQBYs2aN7xJiafr06b5LSInyS075hU35hU3jkh5q1ASAefPm+S4hlpYtW+a7hJQov+SUX9iUX9g0LqnZV1Rc7u06mEAATfosiyYzh035hU35hU3fKxXbta+IWyfPY/LNZ5d5MEGNqi5KREREJNtt3b2fGyfOYdHHW8u9n3Z9ioiIiFShDdv3MmjsTJau2c7913Ur977aoiYAjB071ncJsTRq1CjfJaRE+SWn/MKm/MKmcUnug427uG78LLbs2s/Em3pwzsnNy72/5qiJlENL2IRN+YVN+UmmWbZ2O4MnzKa4pIRHburJGcc1AXTCW0mBmfkuIZZyc3N9l5AS5Zec8gub8gubxuWz5n6wmSvHzaRmdWPqred80qRVRLs+RURERCrRy28XMOKxebRpXJdJN/eibZO6KT9WjZqIiIhIJfnHwjX84KlFnNa6IRNv7EnzBrUP6/Fq1ASA/v37+y4hlvr06eO7hJQov+SUX9iUX9g0LjBp5geMmraUnu2b8dCQ7jSsU/Own0MHE4iUQ5OZw6b8wqb8JFTOOe6bsZLfv/gOF3ZqxV+uOYs6NauXeX8dTCAVGjBggO8SYmnkyJG+S0iJ8ktO+YVN+YUtW8elpMRx13PL+P2L7/D1rm154Lqu5TZpFdEWNQG01EdZtIRN2JRf2JRf2LLxe+VAcQk/emYxz85fw03nnsidX+1EtWoVH/1a3hY1zVETEREROUp7DxQz8vH5vLS8gB986RRG9u2QllOUqFETEREROQrb9x5g2CNzmf3BZu6+9HSu790+bc+tRk0Asm7zdKpC2O0Cyq8syi9syi9s2TIuG3fuY8iE2axYv4M/XtmFS7u0Tevz62ACAWDcuHG+S4ilqVOn+i4hJcovOeUXNuUXtmwYl9VbdjPogZm8V7iTB4d0T3uTBjqYQCLZOOkzFZrMHDblFzblF7ZM/15ZWbCD68fPZue+Ih6+oQfd2zc74ufSwQQiIiIiabJ49VaGTJhN9WrVeOqW3nRq3ajSXkuNmoiIiEiK3ly5kWGPzqVZg1pMHtqLE46pX6mvp0ZNAJg2bZrvEmLpvvvu811CSpRfcsovbMovbJk4Ls8vXc+3Hl9A++b1mDS0F60a1an011SjJgB069bNdwmx1LlzZ98lpET5Jaf8wqb8wpZp4/LU3I+5/ZnFnNmuCQ/f0IMm9WpVyevqqE8BoG3b9B+pkgn69evnu4SUKL/klF/YlF/YMmlcHnxtFT98ejHndmjOYzf3qrImDbRFTURERCQp5xy/eX4FY155j6/mtub3V55J7RpHvm7nkVCjJiIiInKI4hLHT/+Rz+OzPuLqnsdzz9dyqJ7Cup3ppkZNABg2bJjvEmJp4MCBvktIifJLTvmFTfmFLeRx2V9UwveeWsi/Fq/jG+efzP995dS0rNt5JHTCW5Fy5Ofn+y4hJTk5Ob5LiCXlFzblJz7s3l/ELZPm8fq7G7njkk4MO++kSn/N8k54q4MJBMi8o3PSZdCgQb5LSInyS075hU35hS3Ecdm6ez/XPTSLN1Zu5NcDz6iSJq0i2vUpAMyfP993CbG0fPly3yWkRPklp/zCpvzCFtq4bNi+l8HjZ/P+xl2MubYbF+Uc67skQI2aiIiIZLkPN+3iuvGz2LxzPw/f2INzOzT3XdIn1KgJAK1bt/ZdQiy1aNHCdwkpUX7JKb+wKb+whTIuy9dtZ/CE2RQVl/D4sLM5s10T3yV9hho1AWDt2rW+S4ilGTNm+C4hJcovOeUXNuUXthDGZe4Hm7lx4hzq16rBE7f2pkPLhr5L+hwdTCAA5OXl+S4hlsaMGeO7hJQov+SUX9iUX9jiPi4vryjguvGzaN6gNk+PiGeTBjo9h0TMjEx8LxwtM2PJkiW+y6hQbm6u8ktC+YVN+YUtzt8r/1i4hh88tYhTj23IIzf1pHmD2l7rKe/0HNr1KSIiIllj0lsfMuof+fRo34yHhnSnUZ2avksqlxo1ERERyXjOOf4yYyW/e/EdLuzUkr9c05U6Nat23c4joUZNANCu4uSmTJniu4SUKL/klF/YlF/Y4jQuJSWOe/61nAlvvM/Xz2rLvZefQc3qYUzTV6MmIiIiGauouIQfPbOEZ+av5sZz2/PTr3ammofF1Y+UDiYQIN6TPn3SZOawKb+wKb+wxeF7Ze+BYkY+voCXlm/g+186hW/17eBtcfXy6GACERERySo79h7g5kfmMuv9zdx16ekM7t3ed0lHRI2aiIiIZJRNO/cx5OHZvL1uB3+6qguXdmnru6QjpkZNABg9erTvEmJpxIgRvktIifJLTvmFTfmFzde4rNm6h+vHz2LNlj08OLg7F5zW0ksd6aI5aiLlyM/P911CSnJycnyXEEvKL2zKTw7XyoKdXD9+Fjv3FTHhhh70aN/Md0kpKW+OWhjHpkqla9Omje8SYqlv376+S0iJ8ktO+YVN+YWtqsdl8eqtDBo7kwPFjieH9w6mSauIdn0KAOvWrfNdQiwVFhb6LiElyi855Rc25Re2qhyXN9/byLBH5tK0fi0mD+1F++b1q+y1K5saNREREQnW80vX860nFtD+mHo8elMvjm1cx3dJaaVGTQDo2rWr7xJiqVOnTr5LSInyS075hU35ha0qxmXq3I/50TOLOeO4Jky8sQdN6tWq9NesajqYQKQcmswcNuUXNuUn5Xno9VXc86/lfLFjcx64rhv1a4e77UkHE0iFhg8f7ruEWMrLy/NdQkqUX3LKL2zKL2yVNS7OOX77/Aru+ddyLsk9loeGdA+6SatIpW1RM7MJQH+gwDmXE133G2AAsB94D7jRObc1uu3HwFCgGPi2c+756PqLgD8B1YGHnHO/qui1tUXt8MVhqY840hI2YVN+YVN+YauM75XiEseof+Tz2KyPuLpnO+75Wi7VA1q3syy+tqhNBC465LoXgRzn3BnAO8CPowI7A1cBp0ePGWNm1c2sOvBX4GKgM3B1dF8RERHJIvuLSvjOlAU8NusjRpx/Mr+4LDOatIpU2rZC59xrZtb+kOteKHXxLeDy6OdLgSnOuX3A+2a2EugZ3bbSObcKwMymRPddVll1i4iISLzs3l/ErZPn89o7hfz44tO4pc/JvkuqMj7nqN0E/Cf6uS3wcanbVkfXlXX955jZcDOba2ZzQzn3TpysWbPGdwmxNH36dN8lpET5Jaf8wqb8wpaucdm2+wDXPTSL/71byL0Dc7OqSQNPjZqZ3QEUAY+l6zmdc+Occ92dc91btGiRrqfNGvPmzfNdQiwtWxbGxlvll5zyC5vyC1s6xqVg+14GjZ1J/prtjLm2K1f2OD4NlYWlUk/PEe36fO7gwQTRdTcAtwD9nHO7o+t+DOCc+2V0+XkgL3pInnPuK8nuVxYdTHD4dDBBcprMHDblFzblF7aj/V75cNMurhs/i0079zPu+u58oWPzNFYXL7E5PUd0BOcPgf93sEmLTAOuMrPaZnYi0BGYDcwBOprZiWZWi8QBB9OqsmYRERGpWm+v387lD8xkx94iHh92dkY3aRWptIMJzOwJ4HyguZmtBkaTOMqzNvCimQG85Zy71Tm31MyeInGQQBHwTedccfQ8I4HnSZyeY4Jzbmll1SwiIiJ+zftwMzc+PId6tWrw+C296diqoe+SvKrMoz6vTnL1+HLu/3Pg50mu/zfw7zSWJkmMHTvWdwmxNGrUKN8lpET5Jaf8wqb8wnYk4/LKigJunTyP1o3rMmloT45rWq8SKguLlpASKYeWsAmb8gub8ssu/1y0lu8/tZCOLRvyyE09adGwtu+Sqkxs5qhJfEW7ouUQubm5vktIifJLTvmFTfmF7XDGZfJbH/LtKQs4q11TptxydlY1aRXJ3MWxREREJNacc/z15ZX89oV36HdaS/56bVfq1Kzuu6xYUaMmIiIiVa6kxPHzfy9n/P/e57Kz2vLry8+gZnXt6DuUGjUBoH///r5LiKU+ffr4LiElyi855Rc25Re28salqLiE259dwtPzVnPDOe0Z1b8z1bJg3c4joYMJRMqhycxhU35hU36Zae+BYr71xAJeXLaB717Yke/065j18/x0MIFUaMCAAb5LiKWRI0f6LiElyi855Rc25Re2ZOOyY+8Bbnx4Di8u20DegM5898JTsr5Jq4i2qAmgJaTKoiVswqb8wqb8wnbo98qmnfu44eE5LFu3nd9dcSZfO6utx+ripbwtapqjJiIiIpVq7dY9XDd+Fmu27OHBwd3oe1or3yUFQ42aiIiIVJqVBTsZPH4WO/YWMWloL3qe2Mx3SUFRoyYA2mxfhhB2u4DyK4vyC5vyC5tzjiWrtzHk4dlUM5hyy9mc3qax77KCo4MJBIBx48b5LiGWpk6d6ruElCi/5JRf2JRf2O7400SufvAt6tasztRbz1GTdoR0MIEAOpigLJrMHDblFzblF64Xlq5n2MMzOaVNMyYN7cWxjev4LinWdHoOERERqRJPz1vNiMfms7/gfZ66pbeatKOkRk1ERETS4qHXV3Hb1EWcfVIzNky5g6b1a/kuKXg6mEAAmDZtmu8SYum+++7zXUJKlF9yyi9syi8czjl+98I7/OXllVyccyx/vKoLV7V6yndZGUGNmgDQrVs33yXEUufOnX2XkBLll5zyC5vyC0NxiWP0tHwmv/URV3Zvxy++nkv1apb145Iu2vUpALRtqzNEJ9OvXz/fJaRE+SWn/MKm/OJvf1EJ35mygMlvfcQtfU7iVwMTTRpk97ikk7aoiYiIyGHbs7+YWyfP49V3Crn94tO4tc/JvkvKSGrURERE5LBs232Amx6Zw4KPtvCrr+dyVc/jfZeUsdSoCQDDhg3zXUIsDRw40HcJKVF+ySm/sCm/eCrYvpfBE2azqnAXf7mmK5fktk56v2wbl8qiE96KlCM/P993CSnJycnxXUIsKb+wKb/4+WjTbq4bP4uNO/cx7vrufKFjc98lZQSd8FYqpKNzkhs0aJDvElKi/JJTfmFTfvHy9vrtXP7Am2zfe4DHbu5VYZOWLeNS2bTrUwCYP3++7xJiafny5b5LSInyS075hU35xce8D7dw48OzqVurOk/d0ptTWjWs8DHZMC5VQY2aiIiIlOnVdwq5ddI8WjWqzaShvWjXrJ7vkrKKGjUBoHXr5JNBs12LFi18l5AS5Zec8gub8vPvucVr+d6TC+nYsiGP3NSTFg1rp/zYTB6XqqRGTQBYu3at7xJiacaMGb5LSInyS075hU35+fXYrA+58+/5dD+hKQ8N6UHjujUP6/GZOi5VTQcTCAB5eXm+S4ilMWPG+C4hJcovOeUXNuXnh3OOv768kjv+ls8Fp7bk0Zt6HXaTBpk3Lr7o9BwCgJmRie+Fo2VmLFmyxHcZFcrNzVV+SSi/sCm/quec4xf/Xs6Dr7/PpV3a8NsrzqRm9SPbpqPvldSVd3oO7foUERERiopL+PGzS5g6bzVDep/A6AGnUy1at1P8UaMmIiKS5fYeKObbTyzghWUb+E6/jnz3wo6YqUmLAzVqAoB2FSc3ZcoU3yWkRPklp/zCpvyqxs59RQx7ZC4zV21i9IDO3HjuiWl53tDHJS7UqImIiGSpzbv2c8PDs1m6djt/uPJMLjvrON8lySF0MIEAmvRZFk1mDpvyC5vyq1xrt+7h+vGzWL1lD3+9pisXdm6V1ufX90rqdDCBiIiIfOK9wp1c/9Asduwt4tGbetLrpGN8lyRlUKMmIiKSRfLXbGPIhNkAPDH8bHLaNvZckZRHjZoAMHr0aN8lxNKIESN8l5AS5Zec8gub8ku/t1Zt4uZH5tK4bk0mDe3JSS0aVNprhTQucaY5aiLlyM/P911CSnJycnyXEEvKL2zKL71eXLaBbz4+n+Ob1WPS0J60blzXd0kSKW+OmpaQEgDatGnju4RY6tu3r+8SUqL8klN+YVN+6fPMvNXcOnkenY5tyFO39K6SJi2EcQmBdn0KAOvWrfNdQiwVFhb6LiElyi855Rc25ZceE/73Pnc9t4xzOxzD2Ou706B21Xz1x31cQqFGTUREJAM55/jDi+/w5xkruej0Y/nT1V2oXaO677LkMKlREwC6du3qu4RY6tSpk+8SUqL8klN+YVN+R66kxDF62lImvfUhV3Zvx88vy6HGES6ufqTiOC4h0sEEIuXQZOawKb+wKb8js7+ohNumLmLaorXcct5J3H7xaVq3M+Z0MIFUaPjw4b5LiKW8vDzfJaRE+SWn/MKm/A7fnv3FDJ80l2mL1vKji07jx5d08takxWlcQqYtagJoqY+yaAmbsCm/sCm/w7NtzwGGTpzDvI+28IvLcrm65/Fe69H3Suq0hJSIiEgGK9ixl8HjZ/Ne4U7+ek1XLslt7bskSRM1aiIiIgH7ePNurhs/i8Id+5hwQw++2LGF75IkjdSoCQBr1qzxXUIsTZ8+3XcJKVF+ySm/sCm/iq1Yv4Prx89iX1EJk2/uRdfjm3qr5VB6X6eHDiYQAObNm+e7hFhatmyZ7xJSovySU35hU37lm//RFgaNnQnAU7f0jlWTBnpfp4sOJhBAkz7LosnMYVN+YVN+ZXvtnUJumTSPlo1qM3loL9o1q1elr58Kfa+kTgcTiIiIZIh/LV7Hd59cQIeWDXnkph60bFjHd0lSidSoiYiIBOLxWR9xx9+X0O34poy/oQeN69b0XZJUMjVqAsDYsWN9lxBLo0aN8l1CSpRfcsovbMrvU8457n/1PX793xVccGoLxlzbjbq14r1up97X6aE5aiLl0BI2YVN+YVN+Cc45fvmftxn32iou7dKG315xJjWreN1OqVxaQkoqpHXgksvNzfVdQkqUX3LKL2zKD4qKS/jRM4sZ99oqBvc+gT8M6hJMk6b3dXpo16eIiEgM7T1QzHemLOD5pRv4dr+OfO/Cjmp+spAaNRERkZjZua+I4Y/O5c33NjGqf2du+sKJvksST9SoCQD9+/f3XUIs9enTx3cJKVF+ySm/sGVrfpt37efGh2eTv3Y7vx90Jl/velxan7+q6H2dHjqYQKQcmswcNuUXtmzMb922PVw/fjYfbd7NmGu6cmHnVml7bokvHUwgFRowYIDvEmJp5MiRvktIifJLTvmFLdvyW1W4k8vvn8mGbXt59KaewTdpel+nh7aoCaClPsqiJWzCpvzClk355a/ZxpAJswF45Kae5LRtnI7SvNL3Suq0hJSIiEhMvbVqEzc/MpfGdWsyaWhPTmrRwHdJEiNq1ERERDx5adkGvvn4fI5rWpdJQ3vRpkld3yVJzKhREwBtni5DCLtdQPmVRfmFLdPze3b+av7v6cWc3qYRE2/sSbP6tdJcmV96X6eHDiYQAMaNG+e7hFiaOnWq7xJSovySU35hy+T8Jvzvfb7/1CJ6ndiMx4ednXFNGuh9nS46mEAATfosSzZNZs5Eyi9smZifc44/vPQuf57+Ll85vRV/uuos6tSM9+LqR0rfK6nTwQQiIiKelZQ48v65lEdnfsig7sfxi8tyqRHIup3ijxo1ERGRSnaguIQfPLWIaYvWMvy8k/jxxadp3U5JSaW18mY2wcwKzCy/1HXNzOxFM3s3+rtpdL2Z2Z/NbKWZLTazrqUeMyS6/7tmNqSy6s1206ZN811CLN13332+S0iJ8ktO+YUtU/Lbs7+Y4Y/OZdqitfzwolOzpknT+zo9KnOb60TgokOuux2Y7pzrCEyPLgNcDHSM/gwH7odEYweMBnoBPYHRB5s7Sa9u3br5LiGWOnfu7LuElCi/5JRf2DIhv217DjB4wixeeaeQX1yWyzfO75AVTRrofZ0uldaoOedeAzYfcvWlwCPRz48AXyt1/aMu4S2giZm1Br4CvOic2+yc2wK8yOebP0mDtm3b+i4hlvr16+e7hJQov+SUX9hCz69gx16uGvcWCz/eyn1Xn8U1vY6v4sr80vs6Pap6jlor59y66Of1wMGFzNoCH5e63+rourKu/xwzG05iaxzHH59dHwYREYmXjzfv5rrxsyjYvo+HhvSgzyktfJckgfJ2uIlLHLObtuN2nXPjnHPdnXPdW7TQB0JERPxYsX4Hlz/wJlt3H2Dyzb3UpMlRqepGbUO0S5Po74Lo+jVAu1L3Oy66rqzrJc2GDRvmu4RYGjhwoO8SUqL8klN+YQsxv/kfbWHQ2Jk4B0/d0ptuJ2TvtGq9r9OjUk94a2btgeeccznR5d8Am5xzvzKz24FmzrkfmtlXgZHAJSQOHPizc65ndDDBPODgUaDzgW7OuUPnvn2GTngr6ZKfn1/xnWIgJyfHdwmxpPzCFlp+r79byC2T5tGiYW0mD+1Fu2b1PFcmoSjvhLeVeXqOJ4CZwKlmttrMhgK/Ar5kZu8CF0aXAf4NrAJWAg8C3wCIGrK7gTnRn7sqatLkyOjonOQGDRrku4SUKL/klF/YQsrv30vWcdPEORzfrB5Tb+2tJg29r9NFS0gJoKU+ypKJS9hkE+UXtlDy633dbbS4+Ft0Pb4p44f0oHG9mr5LigV9r6ROS0iJiIhUgqeXbuOYi77Feae04P5ru1G3Vmau2yn+aJExAaB169a+S4ilUI4gVn7JKb+wxTk/5xwT5m9h4oKtFL8/m3HXd1eTdgi9r9NDjZoAsHbtWt8lxNKMGTN8l5AS5Zec8gtbXPMrLnHc99Zmnl22na+e0oAPn8ijVg19nR5K7+v00DtLAMjLy/NdQiyNGTPGdwkpUX7JKb+wxTG/A8WOe1/fyAvv7eSq3Mbc2qMZd931M99lxZLe1+mhgwkE0KTPsoQymVmT0ZNTfmGLW367D5Tw81cLWbR+L8O6NeXSTo0A5VcWfa+kTgcTiIiIHIXt+4oZPaOA9zbv53vnHEO/kxr4LkmyhBo1ERGRcmzcVcRPZxSwfscB7jivBb3a6RxpUnXUqAkA2lWc3JQpU3yXkBLll5zyC1sc8luz/QA/nb6BHftL+FnfVpxxbJ3P3Uf5JadxSQ81aiIiIkm8t3k/o6ZvwAG/vLAVHY6p7bskyUI6mEAATfosS9wmM5dFk5mTU35h85lf/oa93PVKAfVrVuPufq04rnHZqw0ov+T0vZI6HUwgIiKSotmrd/Or1zfSsn517u7Xihb19VUp/ujdJyIiEnl51U7+MHMTJzetRV7fljSuo9UGxC81agLA6NGjfZcQSyNGjPBdQkqUX3LKL2xVnd+0t7czbu4WzmhVmzvPb0m9mqmdE175JadxSQ/NURMpR35+vu8SUpKTk+O7hFhSfmGrqvycczy+eBtPLNnG2e3q8sMvtKBWdUv58cpPjlZ5c9S0hJQA0KZNG98lxFLfvn19l5AS5Zec8gtbVeRX4hxj527hiSXbuPCk+vz4i4fXpIHyK4vGJT2061MAWLdune8SYqmwsNB3CSlRfskpv7BVdn5FJY4/vrmJVz7Yxdc6NeSmrk2pZofXpIHyK4vGJT3UqImISNbZW1TCva9vZM6aPQzu0oQrTm+EHUGTJlLZ1KgJAF27dvVdQix16tTJdwkpUX7JKb+wVVZ+O/eXcNfLBSwv3Mc3ezbj4lMaHtXzKb/kNC7poYMJRMqhyehhU35hq4z8tuwpZtSMDXy87QDfP6c557Wvf9TPqfzkaOlgAqnQ8OHDfZcQS3l5eb5LSInyS075hS3d+W3YWcQPX1jP2u1F/PT8lmlp0kD5lUXjkh7aoiaAlvooi5YgCpvyC1s68/tw635+Or2AfcWOvAta0qlF+tbtVH7J6XsldVpCSkREstaKjfvIm1FAjWrGvV9qRfumtXyXJJIyNWoiIpKxFq7bwz2vFtKkTnXu6deSYxuWvbi6SBypURMA1qxZ47uEWJo+fbrvElKi/JJTfmE72vze+GgXv/nfRo5rVJO7+rakWb3K+cpTfslpXNJDBxMIAPPmzfNdQiwtW7bMdwkpUX7JKb+wHU1+z7+7g3tf30jHY2rzqy+1qrQmDZRfWTQu6aGDCQTQpM+yaDJ62JRf2I40v6eXbmPigq10a1OHH5/Xgjo1KnebhPJLTt8rqdPBBCIikvGcc0xcsJVnlm3nvBPq8b1zmlPzMNftFIkbNWoiIhK84hLHX2dv5oWVO7nklAbc0r0Z1aupSZPwqVETAMaOHeu7hFgaNWqU7xJSovySU35hSzW/A8WO37yxkTc/2s2VOY257szGVbpup/JLTuOSHpqjJlIOLUEUNuUXtlTy23OghJ+/WsjC9XsZ1q0pl3ZqVAWVfZbyk6OlJaSkQlX5v8+Q5Obm+i4hJcovOeUXtory276vmDte2sDiDXv5Xu9jvDRpoPzKonFJD+36FBGR4GzcXcSo6QWs23GAn5zXgrPb1fNdkkilUKMmIiJBWbP9AD+dvoEd+0v4Wd9WnHFsHd8liVQaNWoCQP/+/X2XEEt9+vTxXUJKlF9yyi9syfJbtXk/o2ZsoMTBLy9sRYdj0re4+pFSfslpXNJDBxOIlEOT0cOm/MJ2aH75BXu56+UC6tesxl39WtGucTzW7VR+crR0MIFUaMCAAb5LiKWRI0f6LiElyi855Re20vnNWb2bUdMLaFa3Ovd+5djYNGmg/MqicUkPbVETQEt9lEVLEIVN+YXtYH4vv7+TP7y5iZOa1uJnfVvSuE5136V9hvJLTt8rqdMSUiIiEqR/vr2dsXO3kNuqNj/t05J6tbQjSLKLGjUREYkd5xyNz72asXO3cPZxdfnhF1tQS+t2ShZSoyYA2jxdhhB2m4HyK4vyC1NJieOu55bR5AvXcuFJ9fnW2cfEet1O5ZecxiU9tA1ZABg3bpzvEmJp6tSpvktIifJLTvmF50BxCT+YuoiJb35A5+rr+XbveDdpoPzKonFJDx1MIIAmfZZFk9HDpvzCsvdAMd98bD7T3y7g/75yKiP7dlR+AdP3Sup0eg4REYm17XsPMHj8bGasKOCer+XwzQs6+C5JJBY0R01ERLwq3LGPIRNm886GHfz5qrMYcGYb3yWJxIYaNQFg2rRpvkuIpfvuu893CSlRfskpv/j7ePNurh8/iw3b9/HQkO6cf2rLT25TfmHTuKSHGjUBoFu3br5LiKXOnTv7LiElyi855Rdv727YwfXjZ7N7fxGTb+5JtxOafeZ25Rc2jUt6aI6aANC2bVvfJcRSv379fJeQEuWXnPKLr4Ufb+WKsTMpdo4nb+n9uSYNlF/oNC7poS1qIiJSpf737kaGT5pL8wa1mTS0JyccU993SSKxpUZNRESqzH/z1/HtJxZyYvP6TBrak5aN6vguSSTW1KgJAMOGDfNdQiwNHDjQdwkpUX7JKb94eXLOR/z42SV0adeEh2/oSeN6Ncu9v/ILm8YlPXTCW5Fy5Ofn+y4hJTk5Ob5LiCXlFx9jX32PX/7nbc47pQUPXNeVerUq3k6g/CRb6IS3UiEdnZPcoEGDfJeQEuWXnPLzzznHr/7zNr/8z9v0P6M1Dw3unlKTBsovdBqX9NCuTwFg/vz5vkuIpeXLl/suISXKLznl51dxiePOvy/hidkfc22v47nr0pzDWrdT+YVN45IeatRERCTt9hUV870nF/LvJesZeUEHfvDlUzCL9+LqInGkRk0AaN26te8SYqlFixa+S0iJ8ktO+fmxa18Rt06ex+vvbuTOr3bi5i+edETPo/zCpnFJDzVqAsDatWt9lxBLM2bM8F1CSpRfcsqv6m3ZtZ8bJ85h8eqt/ObyM7iie7sjfi7lFzaNS3roYAIBIC8vz3cJsTRmzBjfJaRE+SWn/KrW+m17GTR2JsvWbef+67odVZMGyi90Gpf00Ok5BAAzIxPfC0fLzFiyZInvMiqUm5ur/JJQflXn/Y27uH78LLbs2s+DQ7pzzsnNj/o5lV/Y9L2SuvJOz6FdnyIiclSWrt3GkAmzKXHwxPCzOeO4Jr5LEskYatREROSIzflgMzdNnEOD2jWYNLQXHVo28F2SSEZRoyYAaFdxclOmTPFdQkqUX3LKr3K9/HYBIx6bR5vGdZl0cy/aNqmb1udXfmHTuKSHGjURETls/1i4hh88tYjTWjfkkRt7ckyD2r5LEslIOphAAE36LIsmM4dN+VWOR2d+wOhpS+nZvhkPDelOwzrlL65+pJRf2PS9kjodTCAiIkfNOcefp6/kDy+9w4WdWvGXa86iTs3qvssSyWhq1EREpEIlJY67nlvGxDc/YGDX47h3YC41qutUnCKVTY2aADB69GjfJcTSiBEjfJeQEuWXnPJLjwPFJfzw6cX8bcEabjr3RO78aieqHcbi6kdK+YVN45IemqMmUo78/HzfJaQkJyfHdwmxpPyO3t4DxYx8fD4vLS/gti+fwjcv6FBli6srP8kW5c1R03ZrAaBNmza+S4ilvn37+i4hJcovOeV3dLbvPcDgCbOZ/nYBd38th5F9O1ZZkwbKL3Qal/TQrk8BYN26db5LiKXCwkLfJaRE+SWn/I7cxp37GDJhNivW7+CPV3bh0i5tq7wG5Rc2jUt6eNmiZmbfM7OlZpZvZk+YWR0zO9HMZpnZSjN70sxqRfetHV1eGd3e3kfNIiLZYvWW3VzxwEzeK9zJg0O6e2nSRCShyhs1M2sLfBvo7pzLAaoDVwH3An9wznUAtgBDo4cMBbZE1/8hup+kWdeuXX2XEEudOnXyXUJKlF9yyu/wvbthB5ffP5NNO/cxeWgvLji1pbdalF/YNC7pUeUHE0SN2lvAmcB24O/AfcBjwLHOuSIz6w3kOee+YmbPRz/PNLMawHqghSuncB1MIOmiycxhU36HZ+HHW7nx4dlUr1aNSUN70ql1I6/1KD/JFrE6mMA5twb4LfARsA7YBswDtjrniqK7rQYObmtvC3wcPbYouv8xhz6vmQ03s7lmNjeUeQ1xMnz4cN8lxFJeXp7vElKi/JJTfql7Y+VGrnnwLRrUqcEzI3p7b9JA+YVO45IePraoNQWeAa4EtgJTgadJbDXrEN2nHfAf51yOmeUDFznnVke3vQf0cs5tLOs1tEXt8Gmpj+S0hE3YlF9q/pu/nm8/sYATm9fn0aE9adWojrdaSlN+YdP3SupitUUNuBB43zlX6Jw7ADwLnAs0iXZtAhwHrIl+XgO0A4hubwxsqtqSRUQy01NzPuYbj83j9LaNePKWs2PTpIlIgo9G7SPgbDOrZ4kT8vQDlgEvA5dH9xkC/CP6eVp0mej2GeXNTxMRkdSMe+09fvjMYs7t0JzHbu5Fk3q1fJckIoeo8vOoOedmmdnTwHygCFgAjAP+BUwxs3ui68ZHDxkPTDKzlcBmEkeISpqtWbOm4jtloenTp/suISXKLznll5xzjl8/v4L7X3mPr57Rmj8M6kKtGvE7/7nyC5vGJT28fDKdc6Odc6c553Kcc9c75/Y551Y553o65zo4565wzu2L7rs3utwhun2Vj5oz3bx583yXEEvLli3zXUJKlF9yyu/zikscP/lbPve/8h7X9DqeP191ViybNFB+odO4pIfW+hRAkz7LosnMYVN+n7WvqJjvP7mIfy1ZxzcvOJnbvnxqlS4JdbiUX9j0vZK68g4m0BJSIiJZYNe+Im6dPI/X393IHZd0Yth5J/kuSURSoEZNRCTDbd29nxsnzmHRx1v59eVnMKh7O98liUiK1KgJAGPHjvVdQiyNGjXKdwkpUX7JKT/YsH0vg8fP5v2NuxhzbTcuyjm20l4r3ZRf2DQu6aE5aiLl0BI2Ycv2/D7YuIvrxs9iy679PDi4O+d0aF4pr1NZsj0/yR5xO+GtxFCcJxT7lJub67uElCi/5LI5v2Vrt3P5AzPZta+Ix4edHVyTBtmdXybQuKSHdn2KiGSYuR9s5saJc2hQuwZThvemQ8uGvksSkSOkRk1EJIO8/HYBIx6bR5vGdZl0cy/aNqnruyQROQpq1ASA/v37+y4hlvr06eO7hJQov+SyLb9/LFzDD55axGmtGzLxxp40b1A7Lc/rS7bll2k0LumhgwlEyqHJzGHLpvwmzfyAUdOW0rN9Mx4a0p2GdWqmoTK/sik/yW46mEAqNGDAAN8lxNLIkSN9l5AS5ZdcNuTnnOPP09/lp/9YSr/TWvLITT0zokmD7Mgvk2lc0kNb1ATQUh9l0RI2Ycv0/EpKHHf/axkPv/EBXz+rLfdefgY1q2fO/78zPb9Mp++V1GkJKRGRDHOguIQfPbOYZ+ev4cZz2/PTr3amWjWdDkEk06hRExEJzN4DxYx8fD4vLS/g+186hW/17aBzVolkKDVqAqDN02UIYbcLKL+yZGJ+2/ceYNgjc5n9wWbuvvR0ru/dvvIK8ywT88smGpf0KHcyg5ktTuHP9KoqVirPuHHjfJcQS1OnTvVdQkqUX3KZlt/Gnfu4etxbzPtwC3+8sktGN2mQefllG41LepR7MIGZLQUuKe/xwDTn3BnpLuxo6GCCw6dJn8lpMnPYMim/1Vt2M3j8bNZu28P913bjgtNaVlF1/mRSftlI3yupO5qDCW5xzn1YwZN/44grExGRCq0s2MH142ezc18Rk4b2okf7Zr5LEpEqUu6uT+fc/w69zsyamtkZ5d1HRETSY/HqrVzxwEwOFDueHN5bTZpIlknphDtm9oqZNTKzZsB84EEz+33lliZVadq0ab5LiKX77rvPdwkpUX7JhZ7fmys3cvW4t6hfuwZP39qbzm0aVXFlfoWeX7bTuKRHqmdGbOyc2w58HXjUOdcLuLDyypKq1q1bN98lxFLnzp19l5AS5ZdcyPk9v3Q9Nzw8h7ZN6/LMiHNo37y+h8r8Cjk/0bikS6qNWg0zaw0MAp6rxHrEk7Zt2/ouIZb69evnu4SUKL/kQs3vqbkfM2LyPE5v24inbulNq0Z1PFXmV6j5SYLGJT1SbdTuAp4HVjrn5pjZScC7lVeWiEh2evC1Vfzw6cWc26E5k4f2okm9Wr5LEhGPUjrhrXNuKjC11OVVwMDKKkpEJNs45/jN8ysY88p7fDW3Nb+/8kxq16juuywR8ayiE94Or+gJUrmPxN+wYcN8lxBLAweG8f8R5ZdcKPndPGwYd/w9nzGvvMfVPdvx56vPUpNGOPnp85ecxiU9Kjrh7SrgtvIeD9zlnDs93YUdDZ3wVtIlPz/fdwkpycnJ8V1CLIWQ34Fix0NLD/CvxesYcf7J/PArp2rdzkgI+YE+f3L0yjvhbUVz1F4FBpTzpz/wYvpKFV90dE5ygwYN8l1CSpRfcnHPb29RCXe/UsC/Fq/jJ5ecxo8uOk1NWilxz+8gff6S07ikR7lb1EKlLWqHT0t9JKclbMIW5/x27CvmZy8X8s6mfRT++0/sWPSC75JiJ875labPX3L6Xknd0WxRExGRNNu0u4jbX9zAys37uP2LLdi5WDsmRCS5lI76lMzXunVr3yXEUosWLXyXkBLll1wc81u34wB3Ti9g+95i8i5oSZfWdZVfGeKYXzLKLzmNS3qoURMA1q5d67uEWJoxY4bvElKi/JKLW37vb9nPqOkFFDvHzy9sxSnNawPKryxxy68syi85jUt6pLrWZyszG29m/4kudzazoZVbmlSlvLw83yXE0pgxY3yXkBLll1yc8ltWsJfbX1hPtWpw75eP/aRJA+VXljjlVx7ll5zGJT1SOpggatAeBu5wzp1pZjWABc653Mou8EjoYILDp0mfyWkyc9jikt+8tXv4xauFHFOvOvf0a0XLBp/dmaH8kotLfhVRfsnpeyV16TiYoLlz7imgBMA5VwQUp6k+EZGM9eoHu7jr5QLaNqrBr79y7OeaNBGR8qT6L8YuMzsGcABmdjawrdKqEhHJAP9+Zwf3z97M6S1r89PzW1K/lg60F5HDk2qj9n1gGnCymb0BtAAur7SqpMppV3FyU6ZM8V1CSpRfcr7yc87xZP42Ji/aRo+2dbn9i82pXaPsJk35JafPX9g0LumR6qLs882sD3AqiWWjVjjnDlRqZSIiASpxjvHztvCPt3dwwYn1+U7vY6hRTasNiMiRSfVggurAV4H2lGrunHO/r7TKjoIOJjh8mvSZnCYzh62q8ysucfz5rU1MX7WLAac2ZFj3plRLYUko5ZecPn9h0/dK6so7mCDVXZ//BPYCS4gOKBARkU/tL3bc+3ohs1bv4dozGnNVbmOt2ykiRy3VRu0459wZlVqJiEigdu8v4a5XCsgv2MetPZrR/9SGvksSkQyR6iFI/zGzL1dqJeLV6NGjfZcQSyNGjPBdQkqUX3JVkd+2vcX8+KUNLC/cx23nNj+iJk35JafPX9g0LumR6hy1y4DJJBq7AyQOKHDOuUaVW96R0Rw1SZf8/HzfJaQkJyfHdwmxVNn5FewqYtT0DRTsKubH57WgR9u6R/Q8yi85ff4kW6TjhLe/B3oD9ZxzjZxzDePapMmRadOmje8SYqlv376+S0iJ8kuuMvP7eNsBfvj8erbsKebufi2PuEkD5VcWff7CpnFJj1TnqH0M5DsdvpGx1q1b57uEWCosLPRdQkqUX3KVld+7m/YxekYB1Qx++aVjOalZraN6PuWXnD5/YdO4pEeqjdoq4JVozc99B6+M6+k5REQqy+L1e7n7lQIa1a7G3Re2ok3Dmr5LEpEMlmqj9n70p1b0RzJM165dfZcQS506dfJdQkqUX3Lpzm/mx7v59euFtG5Yk7v6taR5vfSs26n8ktPnL2wal/RI6WCC0OhgAkkXTWYOWzrze+m9nfz5rU10PKYWeRe0pGHt6ml7buWXnD5/ki2O+GACM/tL9Pc/zWzaoX8qo1jxY/jw4b5LiKW8vDzfJaRE+SWXrvz+tmw7f5y5iTOPrcM9/VqltUkD5VcWff7CpnFJj3K3qJnZdudco2idz89xzr1aaZUdBW1RO3xa6iM5LWETtqPNzznH5EVbeTJ/O+ceX4/bzm1OzerpX21A+SWnz1/Y9L2SuqNZQuo9iG9DJiJSWYpLHA/M2cx/3t3JVzo04Bs9m1Fdi6uLSBWrqFFrYWbfL+tGHfUpIpnoQLHj929u5PUPd3P56Y0Y0qWJ1u0UES8qatSqAw1IrEQgGWzNmjW+S4il6dOn+y4hJcovuSPJb29RCb94tZD56/Zy41lNGHh640qo7LOUX3L6/IVN45IeFTVq65xzd1VJJeLVvHnzdBbpJJYtW0bLli19l1Eh5Zfc4ea3c18xeS8X8s6mfXz77GZ8uUPVLK6u/JLT5y9sGpf0qOhgggXOubOqsJ600MEEh0+TPpPTZOawHU5+m3cXMWpGAau3H+CHX2jBOcfXq+TqPqX8ktPnL2z6Xknd0RxM0K8S6hERiZX1Ow5w5/QCtu4tJu+ClnRpfeTrdoqIpFO5jZpzbnNVFSIi4sMHW/bz0+kFFJU4fn5hK05tXtt3SSIin0jP+icSvLFjx/ouIZZGjRrlu4SUKL/kKspveeFe8l4upE4N494LW3F8Ez8r5Cm/5PT5C5vGJT20hJRIObSETdjKy2/e2j384tVCjqlXnbv7taJVA3//b1V+yenzJ9niiJeQkuyhc0Qll5ub67uElCi/5MrK77UPdnH3KwW0bVSDe798rNcmDZRfWfT5C5vGJT2061NEssq/39nB/bM307llbUad35L6tfT/VRGJLzVqIpIVnHNMXbqdRxdupUfbutz+xebUrqEmTUTiTY2aANC/f3/fJcRSnz59fJeQEuWX3MH8nHOMn7+Fvy/fwQUn1uc7vY+hRozW7VR+yenzFzaNS3roYAKRcmgyc9jy8/MpLnHc99YmXlq1iwGnNmRY96ZUi9ncGeWXnD5/ki10MIFUaMCAAb5LiKWRI0f6LiElyi+5b3zrO/zy9UJeWrWLa85ozPAYNmmg/Mqiz1/YNC7poS1qAmipj7JoCZtw7dxXRIcbfk2dE87klu5NGXBaI98llUn5JafPX9j0vZK6o1lCSkQkOJt37eeGh2dTu10OPzj3GC44sYHvkkREjoh2fYpIRlm7dQ9XPPAmK9bvoPDZe9SkiUjQtEVNALR5ugwh7HYB5XfQe4U7uf6hWezYW8Skob2od9UE3yWlRPklp89f2DQu6aEtagLAuHHjfJcQS1OnTvVdQkqUH+Sv2cYVD8xkf3EJU245m54nNlN+gVN+YdO4pIeXgwnMrAnwEJADOOAmYAXwJNAe+AAY5JzbYok1KP4EXALsBm5wzs0v7/l1MMHh06TP5DSZOQxvrdrEzY/MpXHdmky+uRcnNq8PKL/QKb+w6XsldXE8PcefgP86504DzgSWA7cD051zHYHp0WWAi4GO0Z/hwP1VX66IxNWLyzYweMJsWjeuwzMjzvmkSRMRyQRV3qiZWWPgPGA8gHNuv3NuK3Ap8Eh0t0eAr0U/Xwo86hLeApqYWesqLVpEYumZeau5dfI8OrVuxFO39ObYxnV8lyQiklY+tqidCBQCD5vZAjN7yMzqA62cc+ui+6wHWkU/twU+LvX41dF1n2Fmw81srpnNLSwsrMTyM9O0adN8lxBL9913n+8SUpKN+Y3/3/v8YOoizj6pGY/f3Ium9Wt97j7KL2zKL2wal/Tw0ajVALoC9zvnzgJ28eluTgBcYqf2Ye3Yds6Nc851d851b9GiRdqKzRbdunXzXUIsde7c2XcJKcmm/Jxz/O6FFdz93DIuzjmWCTf0oH7t5AewK7+wKb+waVzSw0ejthpY7ZybFV1+mkTjtuHgLs3o74Lo9jVAu1KPPy66TtKobdvPbaQUoF+/fr5LSEm25FdS4hj1j6XcN2MlV3Zvx1+u6UrtGtXLvL/yC5vyC5vGJT2qvFFzzq0HPjazU6Or+gHLgGnAkOi6IcA/op+nAYMt4WxgW6ldpCKSJfYXlfCdJxcy6a0PuaXPSfxqYC7Vq8Vv3U4RkXTydcLbbwGPmVktYBVwI4mm8SkzGwp8CAyK7vtvEqfmWEni9Bw3Vn25IuLTnv3FjHhsHq+sKOT2i0/j1j4n+y5JRKRKeGnUnHMLgWTnC/ncdu5ovto3K7umbDds2DDfJcTSwIEDfZeQkkzOb9ueAwydOIf5H23hV1/P5aqex6f8WOUXNuUXNo1Leng54W1l0wlvJV3y8/N9l5CSnJwc3yVUioIdexk8fjarCnfxx6u6cEnu4Z2ZR/mFTflJtojjCW8lZnR0TnKDBg2q+E4xkIn5fbx5N1c8MJOPNu9mwg09DrtJA+UXOuUXNo1LemhRdgFg/vxyV+XKWsuXL/ddQkoyLb8V63dw/fhZ7C8u4bGbe3HW8U2P6HmUX9iUX9g0LumhRk1EYmXeh1u4aeIc6tSsxlO39OaUVg19lyQi4o0aNQGgdWutypVMKCdPzpT8XnunkFsmzaNVo9pMGtqLds3qHdXzKb+wKb+waVzSQ42aALB27VrfJcTSjBkzfJeQkkzI77nFa/nekwvp2LIhj9zUkxYNax/1cyq/sCm/sGlc0kMHEwgAeXl5vkuIpTFjxvguISWh5/f4rI/41hML6NKuCU8MPzstTRoov9Apv7BpXNJDp+cQAMyMTHwvHC0zY8mSJb7LqFBubm6Q+TnnuP/V9/j1f1fQ97SW/PWartStVfaSUIdL+YVN+YVN3yupK+/0HNr1KSJeOOf45X/eZtxrq7i0Sxt+e8WZ1KyujfwiIqWpURORKldUXMJP/raEp+auZkjvExg94HSqad1OEZHPUaMmAGhXcXJTpkzxXUJKQspv74FivjNlAc8v3cB3+nXkuxd2xKxymjTlFzblFzaNS3qoURORKrNzXxHDH53Lm+9tYvSAztx47om+SxIRiTUdTCCAJn2WRZOZ02fzrv3c8PBslq7dzm+vOIPLzjqu0l9T+YVN+YVN3yup08EEIuLV2q17uH78LFZv2cPY67pxYedWvksSEQmCGjURqVSrCndy/fjZbN9zgEdv6kmvk47xXZKISDDUqAkAo0eP9l1CLI0YMcJ3CSmJa375a7YxZMJsAJ4YfjY5bRtX6esrv7Apv7BpXNJDc9REypGfn++7hJTk5OT4LuFz3lq1iZsfmUvjujWZNLQnJ7VoUOU1KL+wKT/JFuXNUdPZJQWANm3a+C4hlvr27eu7hJTELb+Xlm1gyITZHNu4Dk+P6O2lSQPlFzrlFzaNS3po16cAsG7dOt8lxFJhYaHvElISp/yenb+a/3t6MTltGvHwjT1pVr+Wt1qUX9iUX9g0LumhRk1E0mbC/97nrueWcW6HYxh7fXca1NY/MSIiR0P/igoAXbt29V1CLHXq1Ml3CSnxnZ9zjj+8+A5/nrGSi04/lj9d3YXaNdK3uPqRUn5hU35h07ikhw4mECmHJjNXrKTEkffPpTw680Ou7N6On1+WQ42YLK6u/MKm/CRb6GACqdDw4cN9lxBLeXl5vktIia/8DhSX8N0nF/LozA+55byT+NXA3Ng0aaD8Qqf8wqZxSQ9tURNAS32URUvYlG3P/mK+8dg8Xl5RyI8uOo0R559cpa+fCuUXNuUXNn2vpE5LSIlIWm3bc4CbH5nD3A+38Muv53J1z+N9lyQikpHUqInIYSnYsZchE+awsmAHf72mK5fktvZdkohIxlKjJgCsWbPGdwmxNH36dN8lpKSq8vt4826uGz+Lwh37mHBDD77YsUWVvO6RUn5hU35h07ikR3xm/YpX8+bN811CLC1btsx3CSmpivxWrN/BwPvfZOvuA0y+uVfsmzRQfqFTfmHTuKSHDiYQQJM+y6LJzAnzP9rCjQ/PoU7Nakwa2otTWjWstNdKJ+UXNuUXNn2vpE4HE4jIEXv93UJumTSPFg1rM3loL9o1q+e7JBGRrKFGTUTK9O8l6/jOlAV0aNmQR27qQcuGdXyXJCKSVdSoCQBjx471XUIsjRo1yncJKamM/J6Y/RE/+dsSuh3flPE39KBx3Zppf43Kls35ZQLlFzaNS3pojppIObJ1CZv7X3mPe//7Nhec2oIx13ajbi3/63YeiWzNL1MoP8kWWkJKKmRmvkuIpdzcXN8lpCRd+Tnn+OW/l3Pvf9/m0i5tGDe4e7BNGmRffplG+YVN45Ie2vUpIgAUFZfwk78t4am5qxnc+wTyBpxOtWr6h1ZExCc1aiLCvqJivvPEQv67dD3f7teR713YUf8bFhGJATVqAkD//v19lxBLffr08V1CSo4mv537irhl0lzeWLmJUf07c9MXTkxjZX5lQ36ZTPmFTeOSHjqYQKQcmT6Zecuu/dzw8Gzy127nN5efwde7HpfmyvzK9PwynfKTbKGDCaRCAwYM8F1CLI0cOdJ3CSk5kvzWbdvDFWNnsnz9DsZe1y3jmjTI7PyygfILm8YlPbRFTQAt9VGWTF3CZlXhTq4fP5vtew7w4JDunH3SMZVYnT+Zml+2UH5h0/dK6rSElIh8In/NNoZMmA3AE8PPJqdtY88ViYhIWdSoiWSRWas2cfMjc2lUtyaThvbkpBYNfJckIiLlUKMmANo8XYYQdrtAavlNX76Bbzw2n+Oa1mXyzb1o3bhuFVTmVybll42UX9g0LumhgwkEgHHjxvkuIZamTp3qu4SUVJTf3xasZvikeZx6bEOm3npOVjRpkDn5ZSvlFzaNS3roYAIBNOmzLJkwmfnhN97nZ/9cxjknH8O4wd1pUDt7NqRnQn7ZTPmFTd8rqdPBBCJZyDnHH196lz9Nf5evnN6KP111FnVqhrtup4hINlKjJpKBSkocP/vnUh6Z+SGDuh/HLy7LpUZ1zXQQEQmNGjUBYNq0ab5LiKX77rvPdwkpKZ3fgeISbpu6iH8sXMvw807ixxeflrXrdoaYn3xK+YVN45IeatQEgG7duvkuIZY6d+7su4SUHMxvz/5ivvn4fGa8XcAPLzqVEX1OztomDcLLTz5L+YVN45Ie2hciALRt29Z3CbHUr18/3yWkpG3btmzbc4DBE2bx8ooCfnFZLt84v0NWN2kQVn7yecovbBqX9NAWNZEMUK1eE64a9xYrC3Zw39Vn0f+MNr5LEhGRNFCjJhK4DTuLOPbaX/PBxl08NKQHfU5p4bskERFJEzVqAsCwYcN8lxBLAwcO9F1CuT7cup+fTi+gbpPmTL65F91OaOq7pFiJe34H6fOXnPILm8YlPXTCW5Fy5Ofn+y6hTCs27iNvRgE1qxtP3HIupx7b0HdJsRPn/ErLycnxXUIsKT/JFuWd8FYHEwigo3PKMmjQIN8lJLVg3R7ueGkDDWpV49dfacU1Xz3fd0mxFNf8DqXPX3LKL2wal/TQrk8BYP78+b5LiKXly5f7LuFz/vfhLn77xkaOa1STu/u1omnd6sqvDHHMLxnll5zyC5vGJT3UqIkE5L/v7mDM7M2c1rw2oy5oSYNa2iguIpLJ1KgJAK1bt/ZdQiy1aBGfIyifXrqNiQu20q1NHX58Xgvq1Pi0SVN+ycUpv/Iov+SUX9g0LumhRk0AWLt2re8SYmnGjBm+S8A5x8MLtvLssu30aV+P7/ZuTs3qnz2RrfJLLg75pUL5Jaf8wqZxSQ/tNxEA8vLyfJcQS2PGjPH6+sUljvve2syzy7bz1VMa8INzP9+kgfIri+/8UqX8klN+YdO4pIdOzyEAmBmZ+F44WmbGkiVLvLz2gWLHb97YyJsf7eaq3MZce0bjMpeEys3NVX5J+MzvcCi/5JRf2PS9krryTs+hXZ8iMbTnQAk/f7WQhev3MqxbUy7t1Mh3SSIi4oEaNZGY2b6vmLwZBazcvJ/vnXMM/U5q4LskERHxRI2aAKBdxclNmTKlSl9v4+4iRk0vYN2OA9xxXgt6tauX0uOUX3JVnd+RUn7JKb+waVzSQ42aSEys2X6An07fwI79JdzVrxW5rer4LklERDzTwQQCaNJnWapqMvN7m/czesYGnIOf9W1Jh2NqH9bjNZk5OU1GD5vyC5u+V1KngwlEYiy/YC93vVxA/ZrVuLtfK45rXNN3SSIiEhNq1EQ8mr16N796fSMt61fn7n6taFFfH0kREfmUtxPemll1M1tgZs9Fl080s1lmttLMnjSzWtH1taPLK6Pb2/uqOZONHj3adwmxNGLEiEp77pff38k9rxZyQuOa3PvlY4+qSVN+yVVmfumk/JJTfmHTuKSHtzlqZvZ9oDvQyDnX38yeAp51zk0xsweARc65+83sG8AZzrlbzewq4DLn3JXlPbfmqEm65OfnV8rz/vPt7Yydu4UzWtXmzvNbUq/m0f2fKScnJ02VZZbKyi/dlF9yyk+yRXlz1LxsUTOz44CvAg9Flw3oCzwd3eUR4GvRz5dGl4lu72dlnZ5djlibNm18lxBLffv2TevzOed4bNFWxs7dwtnt6pLXt9VRN2mg/MqS7vwqi/JLTvmFTeOSHr52ff4R+CFQEl0+BtjqnCuKLq8G2kY/twU+Bohu3xbd/zPMbLiZzTWzuYWFhZVYemZat26d7xJiKZ3vpRLnGDt3C08s2caFJ9fnx19sQa0k63YeCeWXXCj/Fii/5JRf2DQu6VHljZqZ9QcKnHPz0vm8zrlxzrnuzrnuLVq0SOdTixy1ohLH79/YxHMrdnBZp0Z85+xjqF5NG4ZFRKR8Pg4xOxf4f2Z2CVAHaAT8CWhiZjWirWbHAWui+68B2gGrzawG0BjYVPVlZ7auXbv6LiGWOnXqdNTPsa+ohF+9vpE5a/YwuEsTrji9UZmLqx8p5ZdcOvKrCsovOeUXNo1Leng94a2ZnQ/cFh1MMBV4ptTBBIudc2PM7JtAbqmDCb7unBtU3vPqYAJJl6OdzLxzfwl3v1LAsoJ9fKNnMy4+pWGaKvssTWZOTpPRw6b8JFvE7mCCMvwI+L6ZrSQxB218dP144Jjo+u8Dt3uqL6MNHz7cdwmxlJeXd8SP3bKnmJ+8uJ4VG/fxf19oXmlNGii/shxNflVJ+SWn/MKmcUkPLSElgJb6KMuRLmGzYWcRd07fwObdxfykTwu6talbCdV9SkvYJKcliMKm/MKm75XUaQkpkSr00db93Dm9gH3FjnsubEWnFoe3bqeIiMhBatRE0mjFxn3kzSigZnXj3i+1on3TWr5LEhGRgKlREwDWrFlT8Z2y0PTp01O+78J1e7jn1UKa1KnOPf1acmzDqltcXfkldzj5+aT8klN+YdO4pEecDiYQj+bNS+tp7TLGsmXLUrrfGx/tIu/lAo5tUINff7lVlTZpoPzKkmp+vim/5JRf2DQu6aGDCQTQpM+ypDKZ+fl3d/DX2Zs5tXltRp/fgga1q1dRdZ/SZObkNBk9bMovbPpeSZ0OJhCpJE8v3cbEBVvp1qYOPz6vBXVqaCO1iIikjxo1kSPgnGPigq08s2w7551Qj++d05yaaVq3U0RE5CA1agLA2LFjfZcQS6NGjfrcdcUljr/O3swLK3dyySkNuKV7M+/rdiq/5JLlF0fKLznlFzaNS3pojppIOQ5dwuZAseM3b2zkzY92c2VOY647s3Ha1+08ElrCJjktQRQ25SfZIpQlpMSjODQbcZSbm/vJz3sOlPCzlwt486PdDOvWlOu7NInNuMWljrgpnV+cKb/klF/YNC7poV2fIinYvq+YvBkFrNy8n+/1PoZ+JzfwXZKIiGQBNWoiFdi4u4hR0wtYt+MAd5zXgl7t6vkuSUREsoQaNQGgf//+vkuIpXO+cik/fH49O/aX8LO+rTjj2Dq+S0pK+SXXp08f3yWkRPklp/zCpnFJDx1MIFKGpWu3ce24NylxcFfflnQ4Jr6Lq2syc3KajB425SfZQgcTSIUGDBjgu4RYmf3+Zq4a+xY7tm3l3i8fG+smDZRfWUaOHOm7hJQov+SUX9g0LumhLWoCaKmP0ma8vYERk+fTtmldXvvZIBa+OcN3SRXSEjbJaQmisCm/sOl7JXXaoiaSor8vWMOwR+dxSquGTL2lN8U7Cn2XJCIiWUwHE4hEJr7xPnn/XEbvk45h3OBuNKxT03dJIiKS5dSoCUBWb552zvGn6e/yx5fe5UudW3Hf1WdRp2Z1gCB2u0B251ce5Rc25Rc2jUt6aNenADBu3DjfJXhRUuL42T+X8ceX3uXybsdx/7VdP2nSAKZOneqxutRla34VUX5hU35h07ikhw4mECA7J30eKC7hh08v5m8L1jD0CydyxyWdqHbI4uqazBw25Rc25Re2bPxeOVLlHUygXZ+SlfYeKOabj81n+tsF/N9XTuUb55+sdelERCR21KhJ1tm+9wA3T5zLnA83c8/Xcrju7BN8lyQiIpKUGjUBYNq0ab5LqBKFO/YxZMJs3tmwgz9fdRYDzmxT7v3vu+++Kqrs6GRLfodL+YVN+YVN45IeatQEgG7duvkuodJ9vHk3gyfMZv22vTw0pDvnn9qywsd07ty5Cio7etmQ35FQfmFTfmHTuKSHjvoUANq2beu7hEr17oYdXPHATDbt3Mfkm3um1KQB9OvXr5IrS49Mz+9IKb+wKb+waVzSQ1vUJOMt/HgrNzw8m5rVq/HkLb3p1LqR75JERERSokZNMtr/3t3I8Elzad6gNpOG9uSEY+r7LklERCRlatQEgGHDhvkuIe3+m7+Obz+xkBOb12fS0J60bFTnsJ9j4MCBlVBZ+mVifumg/MKm/MKmcUkPnfBWMtKTcz7ix88uoUu7Jjx8Q08a1zuydTvz8/PTXFnlyMnJ8V1CLCm/sCk/yRblnfBWBxMIkFlH54x99T1+9MwSvtCxBZNv7nXETRrAoEGD0lhZ5cmk/NJJ+YVN+YVN45Ie2vUpAMyfP993CUfNOce9/13BA6++R/8zWvP7QV2oVePo/i+yfPnyNFVXuTIhv8qg/MKm/MKmcUkPNWqSEYpLHHf+fQlPzP6Ya3sdz12X5lC9mpaEEhGRsKlREwBat27tu4Qjtq+omO89uZB/L1nPyAs68IMvn5K2dTtbtGiRluepbCHnV5mUX9iUX9g0LumhRk0AWLt2re8SjsiufUXcOnker7+7kTu/2ombv3hSWp9/xowZaX2+yhJqfpVN+YVN+YVN45IeOphAAMjLy/NdwmHbuns/1z40izdWbuQ3l5+R9iYNYMyYMWl/zsoQYn5VQfmFTfmFTeOSHjo9hwBgZoT0Xli/bS+DJ8zig027ue/qs/jK6cdWyuuYGUuWLKmU506n3NzcoPKrKsovbMovbKF9r/hU3uk5tOtTgvPBxl1cN34WW3btZ+KNPTjn5Oa+SxIREakUatQkKMvWbmfwhNmUOMcTw8/mjOOa+C5JRESk0qhREwBC2FU854PN3DRxDg1q12DS0LPp0LJBpb/mlClTKv010iGE/HxQfmFTfmHTuKSHGjUJwstvFzDisXm0aVyXSTf3om2Tur5LEhERqXQ6mECAeE/6/MfCNfzgqUWc1rohj9zYk2Ma1K6y19Zk5rApv7Apv7DF+XslbnQwgQTr0ZkfMHraUnq2b8ZDQ7rTsM6Rr9spIiISGjVqEkvOOf48fSV/eOkdLuzUir9ccxZ1alb3XZaIiEiVUqMmAIwePdp3CZ8oKXHc9dwyJr75AQO7Hse9A3OpUd3PuZlHjBjh5XUPV5zyixPlFzblFzaNS3pojprEyoHiEn709GKeXbCGm849kTu/2olqHhdXz8/P9/bahyMnJ8d3CbGk/MKm/CRblDdHTUtICQBt2rTxXQJ7DxQzYvI8nl2whtu+fAo/7e+3SQPo27ev19dPVRzyiyPlFzblFzaNS3po16cAsG7dOq+vv33vAW5+ZC5zPtjM3V/L4fqzT/Baz0GFhYW+S0iJ7/ziSvmFTfmFTeOSHmrUxLuNO/cxZMJsVqzfwR+v7MKlXdr6LklERCQW1KgJAF27dvXyuqu37Gbw+Nms3baHB4d054JTW3qpoyydOnXyXUJKfOUXd8ovbMovbBqX9NDBBOLNyoIdXD9+Nrv2FTHhhh50b9/Md0mfo8nMYVN+YVN+ki10MIFUaPjw4VX6eos+3soVD8zkQLHjyVt6x7JJA8jLy/NdQkqqOr9QKL+wKb+waVzSQ1vUBKjapT7eXLmRYY/OpVmDWkwe2osTjqlfJa97JLSETdiUX9iUX9i0hFTqtISUxMZ/89fz7ScWcGLz+jw6tCetGtXxXZKIiEhsqVGTKvPUnI+5/dnFnNmuCQ/f0IMm9Wr5LklERCTW1KgJAGvWrKnU53/wtVX8/N/L+WLH5oy9vhv1aoXx1ps+fbrvElJS2fmFSvmFTfmFTeOSHjqYQACYN29epTyvc45f//dtfv7v5Xz1jNaMH9IjmCYNYNmyZb5LSEll5Rc65Rc25Rc2jUt66GACASpn0mdxiePOv+fzxOyPuKbX8dx9aQ7VPS8Jdbg0mTlsyi9syi9sOpggdTqYQKrc/qISvvfkQv61ZB3fvOBkbvvyqZiF1aSJiIj4pkZN0m73/iJumTSP19/dyB2XdGLYeSf5LklERCRIatQEgLFjx6blebbu3s+NE+ew6OOt/PryMxjUvV1anteXUaNG+S4hJenKL9Mov7Apv7BpXNJDc9QkbTZs38vg8bN5f+Mu/nz1WVyUc6zvko6alrAJm/ILm/KTbKElpKRCRzt/7IONu7j8gTdZvWU3E2/skRFNGiQmCYdA8/+SU35hU35h07ikh3Z9ylFbvm4714+fTXFJCY8PO5sz2zXxXZKIiEhGUKMmR2XuB5u5ceIcGtSuwZThvenQsqHvkkRERDKGGjUBoH///of9mJdXFDBi8jzaNK7LpJt70bZJ3UqozK8+ffr4LiElR5JfNlB+YVN+YdO4pIcOJpAj8o+Fa/jBU4s4rXVDJt7Yk+YNavsuqVJoMnPYlF/YlJ9kCx1MIBUaMGBAyved9NaHfPfJhXQ7oSlPDDs7Y5s0gJEjR/ouISWHk182UX5hU35h07ikh7aoCZDaUh/OOf4yYyW/e/EdLuzUkr9c05U6NatXUYV+aAmbsCm/sCm/sGkJqdTFaouambUzs5fNbJmZLTWz70TXNzOzF83s3ejvptH1ZmZ/NrOVZrbYzLpWdc0CJSWOu59bzu9efIevn9WW+6/rlvFNmoiIiG8+dn0WAT9wznUGzga+aWadgduB6c65jsD06DLAxUDH6M9w4P6qLzm7FRWXcNvTi5jwxvvceG57fnvFmdSsrr3mIiIila3Kv22dc+ucc/Ojn3cAy4G2wKXAI9HdHgG+Fv18KfCoS3gLaGJmrau26sxX1ubpvQeKuXXyfJ6dv4bvf+kURvXvTLVq2XMSwxB2u0DZ+WU75Rc25Rc2jUt6eN0sYmbtgbOAWUAr59y66Kb1QKvo57bAx6Uetjq67tDnGm5mc81sbmFhYeUVnaHGjRv3uet27D3AkAmzmf72Bu6+9HS+3a9j1p1peurUqb5LSEmy/ET5hU75hU3jkh7eDiYwswbAq8DPnXPPmtlW51yTUrdvcc41NbPngF855/4XXT8d+JFzrsyjBXQwweE7dNLnpp37GPLwbN5et4PfDTqTS7t8rjfOCprMHDblFzblFzYdTJC68g4m8HLCWzOrCTwDPOaceza6eoOZtXbOrYt2bRZE168B2pV6+HHRdVJJ1mzdw/UPzWLttj08OLg7F5zW0ndJIiIiWcnHUZ8GjAeWO+d+X+qmacCQ6OchwD9KXT84OvrzbGBbqV2kkmYrC3Zy+f1vUrhzH5OG9lKTJiIi4pGPLWrnAtcDS8xsYXTdT4BfAU+Z2VDgQ2BQdNu/gUuAlcBu4MYqrTZLTJs2jcWrt3LDw3OoZsaTw3vTuU0j32V5d9999/kuISXTpk3zXUIsKb+wKb+waVzSo8obtWiuWVkz0vslub8DvlmpRQm0PIWrx71F0/q1mDy0F+2b1/ddUSx07tzZdwkp6datm+8SYkn5hU35hU3jkh46GZbwwtL1jJy6jLZN6/LMiHPUpJXSr9/n/u8QS23bZufBHhVRfmFTfmHTuKSHGrUsN3Xux9w6eR77C1bx1C29adWoju+SREREJKJGLYs99Poq/u/pxZzboTkbptxJk3q1fJckIiIipXg5PYf45Zzjty+s4K8vv8dXc1vz+yvPpO6s632XFUsDBw70XUJKhg0b5ruEWFJ+YVN+YdO4pIe3E95WJp3wtmzFJY6f/iOfx2d9xNU923HP13KpnkVLQh2u/Px83yWkJCcnx3cJsaT8wqb8JFuUd8Jb7frMIvuLSvjOlAU8PusjRpx/Mr+47NMmTUfnJDdo0KCK7xQDyi855Rc25Rc2jUt6aNdnlti9v4hbJ8/ntXcK+cklpzH8vJM/c/v8+fM9VRZvy5cv911CSpRfcsovbMovbBqX9FCjlgW27T7AjRNns/Djrfx64BkM6tGu4geJiIiId2rUMlzB9r1cP34272/cxZhru3JRTuuk92vdOvn12a5Fixa+S0iJ8ktO+YVN+YVN45IeatQy2IebdnHd+Fls3rmfh2/swbkdmpd537Vr11ZhZeGYMWOG7xJSovySU35hU35h07ikhw4myFDL123n8gdmsnNvEY8PO7vcJg0gLy+vagoLzJgxY3yXkBLll5zyC5vyC5vGJT10eo4MNO/Dzdz48Bzq1arBpKE96diqYYWPMTMy8b1wtMyMJUuW+C6jQrm5ucovCeUXNuUXNn2vpK6803No12eGeWVFAbdOnkfrxnWZNLQnxzWt57skEREROUJq1DLIPxet5ftPLaRjy4Y8OrQnzRvU9l2SiIiIHAU1ahli8lsf8tN/5NOjfTMeGtKdRnVqHtbjs3lXcXmmTJniu4SUKL/klF/YlF/YNC7poUYtcM45/vrySn77wjv0O60lf722K3VqVvddloiIiKSBDiYIWEmJ4+f/Xs74/73PZWe15deXn0HN6kd2IK8mfSanycxhU35hU35h0/dK6nQwQQYqKi7hR88s4Zn5q7nhnPaM6t+ZalpcXUREJKOoUQvQ3gPFfOuJBby4bAPfu/AUvt2vA2Zq0kRERDKNGrXA7Nh7gOGPzmPmqk387P+dzpBz2qfleUePHp2W58k0I0aM8F1CSpRfcsovbMovbBqX9NActYBs2rmPGx6ew/J12/ndoDO5tEtb3yVlvPz8fN8lpCQnJ8d3CbGk/MKm/CRblDdHTUtIBWLt1j1cMXYm72zYwbjB3dLepLVp0yatz5cp+vbt67uElCi/5JRf2JRf2DQu6aFdnwFYWbCTweNnsWNfEZNv7kWP9s3S/hrr1q1L+3NmgsLCQt8lpET5Jaf8wqb8wqZxSQ81ajG3ZPU2hjw8m2oGU4afzeltGvsuSURERKqIGrUYm/neJoY9OpfGdWsy+eZenNi8fqW9VteuXSvtuUPWqVMn3yWkRPklp/zCpvzCpnFJDx1MEFMvLF3PyCcWcEKzekwa2otjG9fxXVJW0mTmsCm/sCk/yRY6mCAwT89bzYjH5tO5dSOeuqV3lTRpw4cPr/TXCFFeXp7vElKi/JJTfmFTfmHTuKSHtqjFzEOvr+Kefy3nCx2aM/b6btSvXTV7p7XUR3JawiZsyi9syi9s+l5JnZaQCoBzjt+98A5/eXklF+ccyx+v6kLtGlpcXUREJJupUYuB4hLH6Gn5TH7rI67q0Y6fX5ZLda3bKSIikvXUqHm2v6iE7z+1kOcWr+PWPifzo4tO9bJu55o1a6r8NUMwffp03yWkRPklp/zCpvzCpnFJDx1M4NHu/UUMe3Quzy1ex+0Xn8btF5/mbXH1efPmeXnduFu2bJnvElKi/JJTfmFTfmHTuKSHDibwZNvuA9z0yBwWfLSFX1yWy1U9j/dajyZ9JqfJzGFTfmFTfmHT90rqdDBBzBRs38vgCbNZVbiLv17TlYtzW/suSURERGJIjVoV+2jTbq4bP4uNO/cx4YYefKFjc98liYiISEypUatCb6/fzuDxs9lfXMJjN/firOOb+i7pE2PHjvVdQiyNGjXKdwkpUX7JKb+wKb+waVzSQ3PUqsi8D7dw48OzqVurOpOG9uKUVg19lyQp0BI2YVN+YVN+ki20hJRnr75TyHUPzaJZ/Vo8fes5sWzSfB1tGne5ubm+S0iJ8ktO+YVN+YVN45Ie2vVZyZ5bvJbvPbmQji0b8shNPWnRsLbvkkRERCQQatQq0WOzPuTOv+fT44RmPHRDdxrVqem7JBEREQmIGrVK4JxjzCvv8ZvnV9D3tJb89Zqu1K0V73U7+/fv77uEWOrTp4/vElKi/JJTfmFTfmHTuKSHDiZIM+ccv/j3ch58/X2+1qUNv7niTGpW11TAUGkyc9iUX9iUn2QLHUxQRYqKS/jh04t58PX3ueGc9vx+UJdgmrQBAwb4LiGWRo4c6buElCi/5JRf2JRf2DQu6aEtammy90Ax335iAS8s28B3L+zId/p1DOqIFy31kZyWsAmb8gub8gubvldSpyWkKtnOfUUMe2QuM1dtIm9AZ24490TfJYmIiEgGUKN2lDbv2s8ND89m6drt/PHKLnztrLa+SxIREZEMoUbtKKzduofrx89i9ZY9jLu+G/06tfJd0hHT5unkQtjtAsqvLMovbMovbBqX9AhjpnsMvVe4k8vvf5OC7ft49KaeQTdpAOPGjfNdQixNnTrVdwkpUX7JKb+wKb+waVzSQwcTHIH8NdsYMmE2ZjDxxp7ktG1caa9VVTTpMzlNZg6b8gub8gubvldSp4MJ0uitVZu4+ZG5NK5bk8k39+LE5vV9lyQiIiIZSo3aYXhx2Qa++fh8TmhWj0eH9qR147q+SxIREZEMpkYtRc/MW80Pn1lMTtvGTLyhB03r1/JdUlpNmzbNdwmxdN999/kuISXKLznlFzblFzaNS3qoUUvBhP+9z13PLePcDscw9vruNKidecPWrVs33yXEUufOnX2XkBLll5zyC5vyC5vGJT101Gc5nHP8/oUV3PXcMi46/Vgm3NAjI5s0gLZtdf63ZPr16+e7hJQov+SUX9iUX9g0LumRmV1HGpSUOEZPW8qktz7kyu7t+PllOdQIZN1OERERyQxq1JLYX1TCbVMXMW3RWm457yRuv/i0oNbtFBERkcygRu0Qe/YXM+KxebyyopAfXXQaI84/2XdJVWLYsGG+S4ilgQMH+i4hJcovOeUXNuUXNo1LeuiEt6Vs23OAoRPnMP+jLfz8slyu7nl8JVQnIcnPz/ddQkpycnJ8lxBLyi9syk+yRXknvNWkq0jBjr1cOXYmi1Zv5S/XdM26Jk1H5yQ3aNAg3yWkRPklp/zCpvzCpnFJD+36BD7evJvrxs+icMc+JtzQgy92bOG7pCo3f/583yXE0vLly32XkBLll5zyC5vyC5vGJT2yvlFbsX4H14+fxb6iEibf3Iuuxzf1XZKIiIgIkOWN2vyPtnDjw3OoU7MaU2/tzSmtGvouyZvWrVv7LiGWWrQIY+uq8ktO+YVN+YVN45IeWduovfZOIbdMmkerRrWZNLQX7ZrV812SV2vXrvVdQizNmDHDdwkpUX7JKb+wKb+waVzSIysPJvjX4nUMfWQO7ZvXZ+qt52R9kwaQl5fnu4RYGjNmjO8SUqL8klN+YVN+YdO4pEfWnZ7j8Vkfccffl9D9hKY8NKQHjevWrOLq4snMyMT3wtEyM5YsWeK7jArl5uYqvySUX9iUX9j0vZK68k7PkTW7Pp1z3P/qe/z6vyu44NQWjLm2G3VrVfddloiIiEiZsqJRc87xy/+8zbjXVnFplzb89oozqal1O0VERCTmMr5RKyou4Sd/W8JTc1czuPcJ5A04nWrVtG7noY5kJYdsMGXKFN8lpET5Jaf8wqb8wqZxSY+MbtT2HijmO1MW8PzSDXy7X0e+d2FHLa4uIiIiwQjmYAIzuwj4E1AdeMg596uy7tu9e3f3yhtvMfzRubz53iZGD+jMjeeeWGW1hkiTPpPTZOawKb+wKb+w6XsldcEfTGBm1YG/Al8CVgNzzGyac25ZsvsXlTiuffAt8tdu5/eDzuTrXY+rynJFRERE0iKIRg3oCax0zq0CMLMpwKVA0kZtVeFOdq/fwdjrunFh51ZVWKaIiIhI+gSx69PMLgcucs7dHF2+HujlnBtZ6j7DgeHRxRwgv8oLjb/mwEbfRcSQxiU5jUtyGpfkNC7JaVyS07h81gnOuaRrpoWyRa1CzrlxwDgAM5tb1r7ebKZxSU7jkpzGJTmNS3Ial+Q0LslpXFIXysnE1gDtSl0+LrpOREREJGOF0qjNATqa2YlmVgu4CpjmuSYRERGRShXErk/nXJGZjQSeJ3F6jgnOuaXlPGRc1VQWHI1LchqX5DQuyWlcktO4JKdxSU7jkqIgDiYQERERyUah7PoUERERyTpq1ERERERiKuMaNTO7yMxWmNlKM7vddz1Vzcw+MLMlZrbQzOZG1zUzsxfN7N3o76bR9WZmf47GarGZdfVbffqY2QQzKzCz/FLXHfY4mNmQ6P7vmtkQH79LOpUxLnlmtiZ6zyw0s0tK3fbjaFxWmNlXSl2fMZ8zM2tnZi+b2TIzW2pm34muz+r3Sznjku3vlzpmNtvMFkXj8rPo+hPNbFb0Oz4ZHfiGmdWOLq+Mbm9f6rmSjleIyhmXiWb2fqn3S5fo+qz4HKWFcy5j/pA40OA94CSgFrAI6Oy7rioegw+A5odc92vg9ujn24F7o58vAf4DGHA2MMt3/Wkch/OArkD+kY4D0AxYFf3dNPq5qe/frRLGJQ+4Lcl9O0efodrAidFnq3qmfc6A1kDX6OeGwDvR757V75dyxiXb3y8GNIh+rgnMit4HTwFXRdc/AIyIfv4G8ED081XAk+WNl+/frxLGZSJweZL7Z8XnKB1/Mm2L2idLTTnn9gMHl5rKdpcCj0Q/PwJ8rdT1j7qEt4AmZtbaQ31p55x7Ddh8yNWHOw5fAV50zm12zm0BXgQuqvTiK1EZ41KWS4Epzrl9zrn3gZUkPmMZ9Tlzzq1zzs2Pft4BLAfakuXvl3LGpSzZ8n5xzrmd0cWa0R8H9AWejq4/9P1y8H30NNDPzIyyxytI5YxLWbLic5QOmdaotQU+LnV5NeX/w5KJHPCCmc2zxLJaAK2cc+uin9cDBxdAzbbxOtxxyKbxGRntfphwcBcfWTgu0W6ps0hsDdD7JXLIuECWv1/MrLqZLQQKSDQS7wFbnXNF0V1K/46f/P7R7duAY8iCcXHOHXy//Dx6v/zBzGpH12XN++VoZVqjJvAF51xX4GLgm2Z2XukbXWLbctafk0Xj8Bn3AycDXYB1wO+8VuOJmTUAngG+65zbXvq2bH6/JBmXrH+/OOeKnXNdSKyS0xM4zW9F8XDouJhZDvBjEuPTg8TuzB/5qzBMmdaoZf1SU865NdHfBcDfSPwjsuHgLs3o74Lo7tk2Xoc7DlkxPs65DdE/sCXAg3y6+yVrxsXMapJoRh5zzj0bXZ3175dk46L3y6ecc1uBl4HeJHbdHTyJfOnf8ZPfP7q9MbCJ7BiXi6Jd6M45tw94mCx+vxypTGvUsnqpKTOrb2YND/4MfBnIJzEGB4+cGQL8I/p5GjA4OvrmbGBbqV09mehwx+F54Mtm1jTavfPl6LqMcsi8xMtIvGcgMS5XRUetnQh0BGaTYZ+zaL7QeGC5c+73pW7K6vdLWeOi94u1MLMm0c91gS+RmL/3MnB5dLdD3y8H30eXAzOiLbRljVeQyhiXt0v9Z8dIzNsr/X7J+M9RWlTlkQtV8YfEkSTvkJgzcIfveqr4dz+JxFFEi4ClB39/EvMhpgPvAi8BzaLrDfhrNFZLgO6+f4c0jsUTJHbLHCAxx2HokYwDcBOJSb4rgRt9/16VNC6Tot97MYl/PFuXuv8d0bisAC4udX3GfM6AL5DYrbkYWBj9uSTb3y/ljEu2v1/OABZEv38+MCq6/iQSjdZKYCpQO7q+TnR5ZXT7SRWNV4h/yhmXGdH7JR+YzKdHhmbF5ygdf7SElIiIiEhMZdquTxEREZGMoUZNREREJKbUqImIiIjElBo1ERERkZhSoyYiIiISU2rURERERGJKjZqIBMXMis1soZktNbNFZvYDM6sW3dbdzP5czmPbm9k1VVft5157T7QW4uE87kozW2lmz1VSaSISY2rURCQ0e5xzXZxzp5M4+/nFwGgA59xc59y3y3lse8BLoxZ5zyXWQkyZc+5J4ObKKUdE4k6NmogEyyXWtB0OjIyWojn/4JYnM+sTbXlbaGYLouXVfgV8Mbrue9FWrtfNbH7055zoseeb2Stm9rSZvW1mj0VL4GBmPczszWhr3mwza2hm1c3sN2Y2x8wWm9ktFdUevfbbZjbRzN6JXuNCM3vDzN41s54VPYeIZL4aFd9FRCS+nHOrzKw60PKQm24Dvumce8PMGgB7gduB25xz/QHMrB7wJefcXjPrSGKJre7R488CTgfWAm8A55rZbOBJ4Ern3BwzawTsIbEU1zbnXA8zqw28YWYvOOfer6D8DsAVJJbMmUNia98XgP8H/ITE2ogiksXUqIlIpnoD+L2ZPQY865xbHW0UK60m8Bcz6wIUA6eUum22c241QDSvrD2wDVjnnJsD4JzbHt3+ZeAMMzu4KHdjEotsV9Sove+cWxI9x1JgunPOmdmS6PVEJMupURORoJnZSSSarAKg08HrnXO/MrN/kVgQ/A0z+0qSh38P2ACcSWIqyN5St+0r9XMx5f97acC3nHPPH2b5pV+jpNTlkgpeT0SyhOaoiUiwzKwF8ADwF+ecO+S2k51zS5xz95LYrXgasANoWOpujUlsISsBrgeqV/CSK4DWZtYjeo2GZlYDeB4YYWY1o+tPMbP6R/8biki20//YRCQ0daNdkTWBImAS8Psk9/uumV1AYuvUUuA/0c/FZrYImAiMAZ4xs8HAf4Fd5b2wc26/mV0J3GdmdUnMT7sQeIjErsr50UEHhWh+mYikgR3yn1AREakEZtYeeM45l3MEjz2fUgdBiEj20K5PEZGqUQw0PpIT3pLY8relMooSkXjTFjURERGRmNIWNREREZGYUqMmIiIiElNq1ERERERiSo2aiIiISEz9f4315cFwNiD7AAAAAElFTkSuQmCC\n",
-      "text/plain": [
-       "<Figure size 720x720 with 1 Axes>"
-      ]
-     },
-     "metadata": {
-      "needs_background": "light"
-     },
-     "output_type": "display_data"
-    }
-   ],
-   "source": [
-    "fig, ax = plt.subplots(figsize=(10, 10))\n",
-    "plt.axvline(\n",
-    "    x=calculate_distance(\n",
-    "        vessel_path_x0,\n",
-    "        (FG.nodes[\"2\"][\"geometry\"].x, FG.nodes[\"2\"][\"geometry\"].y),\n",
-    "    ),\n",
-    "    color=\"k\",\n",
-    "    linestyle=\"--\",\n",
-    "    linewidth=1.0,\n",
-    ")\n",
-    "plt.axvline(\n",
-    "    x=calculate_distance(\n",
-    "        vessel_path_x0,\n",
-    "        (FG.nodes[\"3\"][\"geometry\"].x, FG.nodes[\"3\"][\"geometry\"].y),\n",
-    "    ),\n",
-    "    color=\"k\",\n",
-    "    linestyle=\"--\",\n",
-    "    linewidth=1.0,\n",
-    ")\n",
-    "plt.axvline(\n",
-    "    x=calculate_distance(\n",
-    "        vessel_path_x0,\n",
-    "        (FG.nodes[\"4\"][\"geometry\"].x, FG.nodes[\"4\"][\"geometry\"].y),\n",
-    "    ),\n",
-    "    color=\"k\",\n",
-    "    linestyle=\"--\",\n",
-    "    linewidth=1.0,\n",
-    ")\n",
-    "plt.axvline(\n",
-    "    x=calculate_distance(\n",
-    "        vessel_path_x0,\n",
-    "        (FG.nodes[\"5\"][\"geometry\"].x, FG.nodes[\"5\"][\"geometry\"].y),\n",
-    "    ),\n",
-    "    color=\"k\",\n",
-    "    linestyle=\"--\",\n",
-    "    linewidth=1.0,\n",
-    ")\n",
-    "plt.axvline(\n",
-    "    x=calculate_distance(\n",
-    "        vessel_path_x0,\n",
-    "        (FG.nodes[\"7\"][\"geometry\"].x, FG.nodes[\"7\"][\"geometry\"].y),\n",
-    "    ),\n",
-    "    color=\"k\",\n",
-    "    linestyle=\"--\",\n",
-    "    linewidth=1.0,\n",
-    ")\n",
-    "plt.axvline(\n",
-    "    x=calculate_distance(\n",
-    "        vessel_path_x0,\n",
-    "        (FG.nodes[\"8\"][\"geometry\"].x, FG.nodes[\"8\"][\"geometry\"].y),\n",
-    "    ),\n",
-    "    color=\"k\",\n",
-    "    linestyle=\"--\",\n",
-    "    linewidth=1.0,\n",
-    ")\n",
-    "plt.axvline(\n",
-    "    x=calculate_distance(\n",
-    "        vessel_path_x0,\n",
-    "        (FG.nodes[\"9\"][\"geometry\"].x, FG.nodes[\"9\"][\"geometry\"].y),\n",
-    "    ),\n",
-    "    color=\"k\",\n",
-    "    linestyle=\"--\",\n",
-    "    linewidth=1.0,\n",
-    ")\n",
-    "plt.axvline(\n",
-    "    x=calculate_distance(\n",
-    "        vessel_path_x0,\n",
-    "        (FG.nodes[\"10\"][\"geometry\"].x, FG.nodes[\"10\"][\"geometry\"].y),\n",
-    "    ),\n",
-    "    color=\"k\",\n",
-    "    linestyle=\"--\",\n",
-    "    linewidth=1.0,\n",
-    ")\n",
-    "for v in reversed(range(0, len(vessels))):\n",
-    "    plt.plot(vessel_path_x[v], vessel_path_t[v])\n",
-    "\n",
-    "plt.text(\n",
-    "    calculate_distance(\n",
-    "        vessel_path_x0,\n",
-    "        (FG.nodes[\"6\"][\"geometry\"].x, FG.nodes[\"6\"][\"geometry\"].y),\n",
-    "    ),\n",
-    "    ax.get_ylim()[1] + 0.01 * ax.get_ylim()[1],\n",
-    "    \"lock \\n chamber\",\n",
-    "    horizontalalignment=\"center\",\n",
-    ")\n",
-    "plt.text(\n",
-    "    calculate_distance(\n",
-    "        vessel_path_x0,\n",
-    "        (FG.nodes[\"9\"][\"geometry\"].x, FG.nodes[\"9\"][\"geometry\"].y),\n",
-    "    )\n",
-    "    - 0.5\n",
-    "    * calculate_distance(\n",
-    "        (FG.nodes[\"8\"][\"geometry\"].x, FG.nodes[\"8\"][\"geometry\"].y),\n",
-    "        (FG.nodes[\"9\"][\"geometry\"].x, FG.nodes[\"9\"][\"geometry\"].y),\n",
-    "    ),\n",
-    "    ax.get_ylim()[1] + 0.01 * ax.get_ylim()[1],\n",
-    "    \"line-up \\n area\",\n",
-    "    horizontalalignment=\"center\",\n",
-    ")\n",
-    "plt.text(\n",
-    "    calculate_distance(\n",
-    "        vessel_path_x0,\n",
-    "        (FG.nodes[\"3\"][\"geometry\"].x, FG.nodes[\"3\"][\"geometry\"].y),\n",
-    "    )\n",
-    "    + 0.5\n",
-    "    * calculate_distance(\n",
-    "        (FG.nodes[\"4\"][\"geometry\"].x, FG.nodes[\"4\"][\"geometry\"].y),\n",
-    "        (FG.nodes[\"3\"][\"geometry\"].x, FG.nodes[\"3\"][\"geometry\"].y),\n",
-    "    ),\n",
-    "    ax.get_ylim()[1] + 0.01 * ax.get_ylim()[1],\n",
-    "    \"line-up \\n area\",\n",
-    "    horizontalalignment=\"center\",\n",
-    ")\n",
-    "plt.text(\n",
-    "    calculate_distance(\n",
-    "        vessel_path_x0,\n",
-    "        (FG.nodes[\"10\"][\"geometry\"].x, FG.nodes[\"10\"][\"geometry\"].y),\n",
-    "    ),\n",
-    "    ax.get_ylim()[1] + 0.01 * ax.get_ylim()[1],\n",
-    "    \"waiting \\n area\",\n",
-    "    horizontalalignment=\"center\",\n",
-    ")\n",
-    "plt.text(\n",
-    "    calculate_distance(\n",
-    "        vessel_path_x0,\n",
-    "        (FG.nodes[\"2\"][\"geometry\"].x, FG.nodes[\"2\"][\"geometry\"].y),\n",
-    "    ),\n",
-    "    ax.get_ylim()[1] + 0.01 * ax.get_ylim()[1],\n",
-    "    \"waiting \\n area\",\n",
-    "    horizontalalignment=\"center\",\n",
-    ")\n",
-    "\n",
-    "plt.title(\"Tracking diagram for vessels passing the lock\", fontweight=\"bold\", pad=32)\n",
-    "plt.xlabel(\"Distance [m]\")\n",
-    "plt.xlim([0, 0.95 * ax.get_xlim()[1]])\n",
-    "plt.ylim([0, ax.get_ylim()[1]])\n",
-    "plt.ylabel(\"Time [s]\")\n",
-    "\n",
-    "plt.fill(\n",
-    "    [\n",
-    "        calculate_distance(\n",
-    "            vessel_path_x0,\n",
-    "            (FG.nodes[\"3\"][\"geometry\"].x, FG.nodes[\"3\"][\"geometry\"].y),\n",
-    "        ),\n",
-    "        calculate_distance(\n",
-    "            vessel_path_x0,\n",
-    "            (FG.nodes[\"3\"][\"geometry\"].x, FG.nodes[\"3\"][\"geometry\"].y),\n",
-    "        ),\n",
-    "        calculate_distance(\n",
-    "            vessel_path_x0,\n",
-    "            (FG.nodes[\"4\"][\"geometry\"].x, FG.nodes[\"4\"][\"geometry\"].y),\n",
-    "        ),\n",
-    "        calculate_distance(\n",
-    "            vessel_path_x0,\n",
-    "            (FG.nodes[\"4\"][\"geometry\"].x, FG.nodes[\"4\"][\"geometry\"].y),\n",
-    "        ),\n",
-    "    ],\n",
-    "    [0, ax.get_ylim()[1], ax.get_ylim()[1], 0],\n",
-    "    \"lightgrey\",\n",
-    ")\n",
-    "\n",
-    "plt.fill(\n",
-    "    [\n",
-    "        calculate_distance(\n",
-    "            vessel_path_x0,\n",
-    "            (FG.nodes[\"5\"][\"geometry\"].x, FG.nodes[\"5\"][\"geometry\"].y),\n",
-    "        ),\n",
-    "        calculate_distance(\n",
-    "            vessel_path_x0,\n",
-    "            (FG.nodes[\"5\"][\"geometry\"].x, FG.nodes[\"5\"][\"geometry\"].y),\n",
-    "        ),\n",
-    "        calculate_distance(\n",
-    "            vessel_path_x0,\n",
-    "            (FG.nodes[\"7\"][\"geometry\"].x, FG.nodes[\"7\"][\"geometry\"].y),\n",
-    "        ),\n",
-    "        calculate_distance(\n",
-    "            vessel_path_x0,\n",
-    "            (FG.nodes[\"7\"][\"geometry\"].x, FG.nodes[\"7\"][\"geometry\"].y),\n",
-    "        ),\n",
-    "    ],\n",
-    "    [0, ax.get_ylim()[1], ax.get_ylim()[1], 0],\n",
-    "    \"lightgrey\",\n",
-    ")\n",
-    "\n",
-    "plt.fill(\n",
-    "    [\n",
-    "        calculate_distance(\n",
-    "            vessel_path_x0,\n",
-    "            (FG.nodes[\"8\"][\"geometry\"].x, FG.nodes[\"8\"][\"geometry\"].y),\n",
-    "        ),\n",
-    "        calculate_distance(\n",
-    "            vessel_path_x0,\n",
-    "            (FG.nodes[\"8\"][\"geometry\"].x, FG.nodes[\"8\"][\"geometry\"].y),\n",
-    "        ),\n",
-    "        calculate_distance(\n",
-    "            vessel_path_x0,\n",
-    "            (FG.nodes[\"9\"][\"geometry\"].x, FG.nodes[\"9\"][\"geometry\"].y),\n",
-    "        ),\n",
-    "        calculate_distance(\n",
-    "            vessel_path_x0,\n",
-    "            (FG.nodes[\"9\"][\"geometry\"].x, FG.nodes[\"9\"][\"geometry\"].y),\n",
-    "        ),\n",
-    "    ],\n",
-    "    [0, ax.get_ylim()[1], ax.get_ylim()[1], 0],\n",
-    "    \"lightgrey\",\n",
-    ")\n",
-    "\n",
-    "plt.show()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 20,
-   "id": "fa7c580b",
-   "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "text/html": [
-       "<div>\n",
-       "<style scoped>\n",
-       "    .dataframe tbody tr th:only-of-type {\n",
-       "        vertical-align: middle;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe tbody tr th {\n",
-       "        vertical-align: top;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe thead th {\n",
-       "        text-align: right;\n",
-       "    }\n",
-       "</style>\n",
-       "<table border=\"1\" class=\"dataframe\">\n",
-       "  <thead>\n",
-       "    <tr style=\"text-align: right;\">\n",
-       "      <th></th>\n",
-       "      <th>Timestamp</th>\n",
-       "      <th>ActivityID</th>\n",
-       "      <th>ActivityState</th>\n",
-       "      <th>ObjectState</th>\n",
-       "      <th>ActivityLabel</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "Empty DataFrame\n",
-       "Columns: [Timestamp, ActivityID, ActivityState, ObjectState, ActivityLabel]\n",
-       "Index: []"
-      ]
-     },
-     "execution_count": 20,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "df = pd.DataFrame.from_dict(lock_1.log)\n",
-    "df"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 21,
-   "id": "8071ecec",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "door_open_time_doors_1 = []\n",
-    "door_open_time_doors_2 = []\n",
-    "end_time = []\n",
-    "for v in range(0, len(vessels)):\n",
-    "    end_time.append(vessels[v].log[\"Timestamp\"][-1].timestamp())\n",
-    "end_of_simulation = max(end_time)\n",
-    "\n",
-    "for l in range(0, len(env.FG.nodes[\"6\"][\"Lock\"])):\n",
-    "    for t in range(0, len(env.FG.nodes[\"6\"][\"Lock\"][l].logbook) - 1):\n",
-    "        if (\n",
-    "            env.FG.nodes[\"6\"][\"Lock\"][l].logbook[t][\"Message\"]\n",
-    "            == \"Lock doors closing stop\"\n",
-    "            and env.FG.nodes[\"6\"][\"Lock\"][l].logbook[t][\"Geometry\"] == \"5\"\n",
-    "        ):\n",
-    "            for t2 in reversed(range(0, t)):\n",
-    "                if (\n",
-    "                    env.FG.nodes[\"6\"][\"Lock\"][l].logbook[t2][\"Message\"]\n",
-    "                    == \"Lock doors opening start\"\n",
-    "                ):\n",
-    "                    door_open_time_doors_1.append(\n",
-    "                        env.FG.nodes[\"6\"][\"Lock\"][l]\n",
-    "                        .logbook[t][\"Timestamp\"]\n",
-    "                        .timestamp()\n",
-    "                        / 60\n",
-    "                        - env.FG.nodes[\"6\"][\"Lock\"][l]\n",
-    "                        .logbook[t2][\"Timestamp\"]\n",
-    "                        .timestamp()\n",
-    "                        / 60\n",
-    "                    )\n",
-    "                    break\n",
-    "            else:\n",
-    "                door_open_time_doors_1.append(\n",
-    "                    env.FG.nodes[\"6\"][\"Lock\"][l].logbook[t][\"Timestamp\"].timestamp()\n",
-    "                    / 60\n",
-    "                    - simulation_start.timestamp() / 60\n",
-    "                )\n",
-    "        elif (\n",
-    "            env.FG.nodes[\"6\"][\"Lock\"][l].logbook[t][\"Message\"]\n",
-    "            == \"Lock doors closing stop\"\n",
-    "            and env.FG.nodes[\"6\"][\"Lock\"][l].logbook[t][\"Geometry\"] == \"7\"\n",
-    "        ):\n",
-    "            for t2 in reversed(range(0, t)):\n",
-    "                if (\n",
-    "                    env.FG.nodes[\"6\"][\"Lock\"][l].logbook[t2][\"Message\"]\n",
-    "                    == \"Lock doors opening start\"\n",
-    "                ):\n",
-    "                    door_open_time_doors_2.append(\n",
-    "                        env.FG.nodes[\"6\"][\"Lock\"][l]\n",
-    "                        .logbook[t][\"Timestamp\"]\n",
-    "                        .timestamp()\n",
-    "                        / 60\n",
-    "                        - env.FG.nodes[\"6\"][\"Lock\"][l]\n",
-    "                        .logbook[t2][\"Timestamp\"]\n",
-    "                        .timestamp()\n",
-    "                        / 60\n",
-    "                    )\n",
-    "                    break\n",
-    "            else:\n",
-    "                door_open_time_doors_2.append(\n",
-    "                    env.FG.nodes[\"6\"][\"Lock\"][l].logbook[t][\"Timestamp\"].timestamp()\n",
-    "                    / 60\n",
-    "                    - simulation_start.timestamp() / 60\n",
-    "                )\n",
-    "\n",
-    "for l in range(0, len(env.FG.nodes[\"6\"][\"Lock\"])):\n",
-    "    for t in reversed(range(0, len(env.FG.nodes[\"6\"][\"Lock\"][l].logbook))):\n",
-    "        if (\n",
-    "            env.FG.nodes[\"6\"][\"Lock\"][l].logbook[t][\"Message\"]\n",
-    "            == \"Lock doors opening stop\"\n",
-    "            and env.FG.nodes[\"6\"][\"Lock\"][l].logbook[t][\"Geometry\"] == \"5\"\n",
-    "        ):\n",
-    "            for t2 in range(t, len(env.FG.nodes[\"6\"][\"Lock\"][l].logbook)):\n",
-    "                if (\n",
-    "                    env.FG.nodes[\"6\"][\"Lock\"][l].logbook[t2][\"Message\"]\n",
-    "                    == \"Lock doors closing stop\"\n",
-    "                ):\n",
-    "                    pass\n",
-    "            else:\n",
-    "                door_open_time_doors_1.append(\n",
-    "                    end_of_simulation / 60\n",
-    "                    - env.FG.nodes[\"6\"][\"Lock\"][l].logbook[t][\"Timestamp\"].timestamp()\n",
-    "                    / 60\n",
-    "                )\n",
-    "                break\n",
-    "        elif (\n",
-    "            env.FG.nodes[\"6\"][\"Lock\"][l].logbook[t][\"Message\"]\n",
-    "            == \"Lock doors opening stop\"\n",
-    "            and env.FG.nodes[\"6\"][\"Lock\"][l].logbook[t][\"Geometry\"] == \"7\"\n",
-    "        ):\n",
-    "            for t2 in range(t, len(env.FG.nodes[\"6\"][\"Lock\"][l].logbook)):\n",
-    "                if (\n",
-    "                    env.FG.nodes[\"6\"][\"Lock\"][l].logbook[t2][\"Message\"]\n",
-    "                    == \"Lock doors closing stop\"\n",
-    "                ):\n",
-    "                    pass\n",
-    "            else:\n",
-    "                door_open_time_doors_2.append(\n",
-    "                    end_of_simulation / 60\n",
-    "                    - env.FG.nodes[\"6\"][\"Lock\"][l].logbook[t][\"Timestamp\"].timestamp()\n",
-    "                    / 60\n",
-    "                )\n",
-    "                break"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 22,
-   "id": "7a7204d6",
-   "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "image/png": "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\n",
-      "text/plain": [
-       "<Figure size 432x180 with 2 Axes>"
-      ]
-     },
-     "metadata": {
-      "needs_background": "light"
-     },
-     "output_type": "display_data"
-    }
-   ],
-   "source": [
-    "data = [door_open_time_doors_1, door_open_time_doors_2]\n",
-    "xaxes = \"time [min]\"\n",
-    "titles = [\"Doors 1: door open time\", \"Doors 2: door open time\"]\n",
-    "bins = [range(0, 50, 5), range(0, 50, 5)]\n",
-    "\n",
-    "f, a = plt.subplots(1, 2, figsize=(6, 2.5))\n",
-    "a = a.ravel()\n",
-    "for idx, ax in enumerate(a):\n",
-    "    if idx == 0:\n",
-    "        ax.set_ylabel(\"Frequency\")\n",
-    "    ax.hist(data[idx], bins=bins[idx])\n",
-    "    ax.set_title(titles[idx])\n",
-    "    ax.set_xlabel(xaxes)\n",
-    "    ax.set_ylim(0, max(ax.hist(data[idx], bins=bins[idx])[0]) + 1)\n",
-    "    ax.yaxis.set_major_locator(MaxNLocator(integer=True))\n",
-    "plt.tight_layout()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 23,
-   "id": "989fd2d9",
-   "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "image/png": "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\n",
-      "text/plain": [
-       "<Figure size 432x180 with 2 Axes>"
-      ]
-     },
-     "metadata": {
-      "needs_background": "light"
-     },
-     "output_type": "display_data"
-    }
-   ],
-   "source": [
-    "data = [loop_time_doors1, loop_time_doors2]\n",
-    "xaxes = \"time [min]\"\n",
-    "titles = [\"Doors 1: loop time\", \"Doors 2: loop time\"]\n",
-    "bins = [range(0, 100, 5), range(0, 100, 5)]\n",
-    "\n",
-    "f, a = plt.subplots(1, 2, figsize=(6, 2.5))\n",
-    "a = a.ravel()\n",
-    "for idx, ax in enumerate(a):\n",
-    "    if idx == 0:\n",
-    "        ax.set_ylabel(\"Frequency\")\n",
-    "    ax.hist(data[idx], bins=bins[idx])\n",
-    "    ax.set_title(titles[idx])\n",
-    "    ax.set_xlabel(xaxes)\n",
-    "    ax.set_ylim(0, max(ax.hist(data[idx], bins=bins[idx])[0]) + 1)\n",
-    "    ax.yaxis.set_major_locator(MaxNLocator(integer=True))\n",
-    "plt.tight_layout()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "ac553d4a",
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  }
- ],
- "metadata": {
-  "kernelspec": {
-   "display_name": "Python 3 (ipykernel)",
-   "language": "python",
-   "name": "python3"
-  },
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython",
-    "version": 3
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython3",
-   "version": "3.9.16"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/opentnsim/core.py b/opentnsim/core.py
index a38480b8..2b1d5002 100644
--- a/opentnsim/core.py
+++ b/opentnsim/core.py
@@ -163,8 +163,6 @@ class VesselProperties:
     - renewable_fuel_required_space: renewable fuel required storage space (consider packaging factor) on board  [m3]
     """
 
-    # TODO: add blockage factor S to vessel properties
-
     def __init__(
         self,
         type,
@@ -319,13 +317,27 @@ def __init__(self, route, complete_path=None, *args, **kwargs):
 
     @property
     def graph(self):
+        """
+        Return the graph of the underlying environment.
+
+        If it's multigraph cast to corresponding type
+        If you want the multidigraph use the HasMultiGraph mixin
+
+        """
+        graph = None
         if hasattr(self.env, "graph"):
-            return self.env.graph
+            graph = self.env.graph
         elif hasattr(self.env, "FG"):
-            return self.env.FG
+            graph = self.env.FG
         else:
             raise ValueError("Routable expects .graph to be present on env")
 
+        if isinstance(graph, nx.MultiDiGraph):
+            return nx.DiGraph(graph)
+        elif isinstance(graph, nx.MultiGraph):
+            return nx.Graph(graph)
+        return graph
+
 
 @deprecated.deprecated(reason="Use Routable instead of Routeable")
 class Routeable(Routable):
@@ -347,6 +359,7 @@ def __init__(self, v: float, *args, **kwargs):
         self.v = v
         super().__init__(*args, **kwargs)
         self.on_pass_edge_functions = []
+        self.on_pass_node_functions = []
         self.wgs84 = pyproj.Geod(ellps="WGS84")
 
     def move(self, destination: Union[Locatable, Geometry, str] = None, engine_order: float = 1.0, duration: float = None):
@@ -389,9 +402,15 @@ def move(self, destination: Union[Locatable, Geometry, str] = None, engine_order
             # name it a, b here, to avoid confusion with destination argument
             a, b = edge
 
+            node = a
+
+            yield from self.pass_node(node)
+
+            # we are now at the node
+            self.node = node
+
             # update to current position
             self.geometry = nx.get_node_attributes(self.graph, "geometry")[a]
-            self.node = a
             self.position_on_route = i
 
             # are we already at destination?
@@ -418,6 +437,11 @@ def move(self, destination: Union[Locatable, Geometry, str] = None, engine_order
         else:
             logger.debug("  current_speed:  not set")
 
+    def pass_node(self, node):
+        # call all on_pass_node_functions
+        for on_pass_node_function in self.on_pass_node_functions:
+            yield from on_pass_node_function(node)
+
     def pass_edge(self, origin, destination):
         edge = self.graph.edges[origin, destination]
         orig = nx.get_node_attributes(self.graph, "geometry")[origin]
diff --git a/opentnsim/lock.py b/opentnsim/lock.py
index dc048876..fc60cebf 100644
--- a/opentnsim/lock.py
+++ b/opentnsim/lock.py
@@ -1,217 +1,1053 @@
-import random
+"""This is the lock module as part of the OpenTNSim package. See the locking examples in the book for detailed descriptions."""
+
+# package(s) related to the simulation
+import bisect
+import datetime
 import math
+import random
+import time
+import time as timepy
 
-import numpy as np
-import simpy
-import shapely
 import networkx as nx
+import numpy as np
+import pandas as pd
+
+# spatial libraries
 import pyproj
+import pytz
+import shapely.geometry
+import simpy
+import xarray as xr
+
+from opentnsim import core, output
+
+
+class CustomLog(core.Log):
+    """this module stores log information slightly different"""
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+    def log_entry_extra(self, t, geometry_log, log, output):
+        """Log an entry with a dictionary as output"""
+        entry = {
+            "Message": log,
+            "Timestamp": datetime.datetime.fromtimestamp(t),
+            "Geometry": geometry_log,
+            "ObjectState": output,
+        }
+        self.logbook.append(entry)
+
+
+class IsDetectorNode:
+    def __init__(self, infrastructure, *args, **kwargs):
+        self.infrastructure = infrastructure
+        super().__init__(*args, **kwargs)
+
+
+class HasMultiDiGraph(core.SimpyObject):
+    """This locking module uses a MultiDiGraph to represent the network. This converts other graphs to a MultiDiGraph."""
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+    @property
+    def multidigraph(self):
+        # create a multidigraph copy of graph if it was not done before
+
+        if not hasattr(self.env, "_multidigraph"):
+            self.env._multidigraph = self.copy()
+        return self.env._multidigraph
+
+    def copy(self):
+        multidigraph = self.env.FG
+        if not isinstance(self.env.FG, nx.MultiDiGraph):
+            multidigraph = nx.MultiDiGraph(multidigraph)
+        return multidigraph
+
+
+class HasLockInformation:
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
 
-#
-from opentnsim.core import HasResource, Identifiable, Log, HasLength, SimpyObject
-import opentnsim.core
+class HasLock(core.Movable, HasMultiDiGraph):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.on_pass_node_functions.append(self.register_vessel)
+        self.on_pass_edge_functions.append(self.leave_lock_chamber)
+
+    def register_vessel(self, origin):
+        if "Detector" in self.multidigraph.nodes[origin].keys():
+            yield self.env.timeout(0)
+            lock = []
+            for node1, node2 in zip(self.route[self.route.index(origin) : -1], self.route[self.route.index(origin) + 1 :]):
+                k = sorted(self.multidigraph[node1][node2], key=lambda x: self.multidigraph[node1][node2][x]["geometry"].length)[0]
+                if "Lock" in self.multidigraph.edges[node1, node2, k].keys():
+                    lock = self.multidigraph.edges[node1, node2, k]["Lock"][0]
+                    node_doors1 = lock.node_doors1
+                    node_doors2 = lock.node_doors2
+                    doors2 = lock.doors_2[node_doors2]
+                    if node_doors1 != node1:
+                        node_doors1 = lock.node_doors2
+                        node_doors2 = lock.node_doors1
+                        doors2 = lock.doors_1[node_doors2]
+
+            if lock and "lock_information" not in dir(self):
+                self.lock_information = {}
+            if lock and lock.name not in self.lock_information.keys():
+                self.lock_information[lock.name] = HasLockInformation()
+
+            if lock:
+                self.lock_information[lock.name].in_lock = False
+                if origin == lock.detector_nodes[0]:
+                    direction = 1
+                else:
+                    direction = 0
+                if not lock.next_lockage[node_doors2].users:
+                    self.lock_information[lock.name].next_lockage = lock.next_lockage[node_doors2].request()
+                    self.lock_information[lock.name].next_lockage.obj = self
+                    self.lock_information[lock.name].next_lockage_length = lock.next_lockage_length[node_doors2].get(self.L)
+                    self.lock_information[lock.name].next_lockage_length.obj = self
+                    self.lock_information[lock.name].converting = False
+                    self.lock_information[lock.name].lock_dist = lock.length.capacity - 0.5 * self.L
+                    if not lock.next_lockage[node_doors1].users:
+                        self.lock_information[lock.name].access_lock_door2 = doors2.request(priority=-1)
+                        self.lock_information[lock.name].access_lock_door2.obj = self
+                        if lock.node_open != node_doors1:
+                            lineup_area = lock.find_previous_lineup_area(self, direction)
+                            yield from lock.request_empty_lock_conversion(lineup_area, self, node_doors1, timeout_required=False)
+                else:
+                    self.lock_information[lock.name].next_lockage = lock.next_lockage[node_doors2].request()
+                    self.lock_information[lock.name].next_lockage.obj = self
+                    self.lock_information[lock.name].next_lockage_length = lock.next_lockage_length[node_doors2].get(self.L)
+                    self.lock_information[lock.name].next_lockage_length.obj = self
+                    self.lock_information[lock.name].converting = False
+
+    def leave_lock_chamber(self, origin, destination):
+        k = sorted(
+            self.multidigraph[origin][destination], key=lambda x: self.multidigraph[origin][destination][x]["geometry"].length
+        )[0]
+        if "Lock" in self.multidigraph.edges[origin, destination, k].keys():
+            locks = self.multidigraph.edges[origin, destination, k]["Lock"]
+            if origin == locks[0].node_doors1:
+                direction = 1
+            else:
+                direction = 0
+
+            yield from PassLock.leave_lock(self, origin, destination, direction)
+            self.v = self.v_before_lock
+
+
+class HasWaitingArea(core.Movable):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.on_pass_node_functions.append(self.leave_waiting_area)
 
+    def leave_waiting_area(self, origin):
+        if "Waiting area" in self.multidigraph.nodes[origin].keys():  # if vessel is in waiting area
+            yield from PassLock.leave_waiting_area(self, origin)
 
-class IsLockWaitingArea(HasResource, Identifiable, Log):
-    """Mixin class: Something has lock object properties
 
-    - properties in meters
-    - operation in seconds
+class HasLineUpArea(core.Movable, HasMultiDiGraph):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.on_pass_edge_functions.append(self.approach_lineup_area)  # on_look_ahead_to_node
+        self.on_pass_edge_functions.append(self.leave_lineup_area)
+
+    def approach_lineup_area(self, origin, destination):
+        k1 = sorted(
+            self.multidigraph[origin][destination], key=lambda x: self.multidigraph[origin][destination][x]["geometry"].length
+        )[0]
+        if "Line-up area" in self.multidigraph.edges[origin, destination, k1].keys():  # if vessel is approaching the line-up area
+            yield from PassLock.approach_lineup_area(self, origin, destination)
+
+    def leave_lineup_area(self, origin, destination):
+        k1 = sorted(
+            self.multidigraph[origin][destination], key=lambda x: self.multidigraph[origin][destination][x]["geometry"].length
+        )[0]
+        k2 = sorted(
+            self.multidigraph[destination][origin], key=lambda x: self.multidigraph[destination][origin][x]["geometry"].length
+        )[0]
+        if "Line-up area" in self.multidigraph.edges[origin, destination, k1].keys():  # if vessel is located in the line-up
+            yield from PassLock.leave_lineup_area(self, origin, destination)
+        elif "Line-up area" in self.multidigraph.edges[destination, origin, k2].keys():
+            yield from PassLock.leave_lineup_area(self, destination, origin)
+
+
+class IsLockWaitingArea(core.HasResource, core.Identifiable, core.Log, output.HasOutput, HasMultiDiGraph):
+    """Mixin class: Something has waiting area object properties as part of the lock complex [in SI-units]:
+    creates a waiting area with a waiting_area resource which is requested when a vessels wants to enter the area with limited capacity
     """
 
-    def __init__(self, node, *args, **kwargs):
+    def __init__(
+        self, node, distance_from_node, *args, **kwargs  # a string which indicates the location of the start of the waiting area
+    ):
+        self.node = node
+        self.distance_from_node = distance_from_node
         super().__init__(*args, **kwargs)
-        """Initialization
-        """
+        """Initialization"""
 
         waiting_area_resources = 100
         self.waiting_area = {
             node: simpy.PriorityResource(self.env, capacity=waiting_area_resources),
         }
 
-        # departure_resources = 4
-        # self.departure = {
-        #    node: simpy.PriorityResource(self.env, capacity=departure_resources),
-        # }
+        # Add to the graph:
+        if "FG" in dir(self.env):
+            if "Waiting area" not in self.multidigraph.nodes[node].keys():
+                self.multidigraph.nodes[node]["Waiting area"] = [self]
+            else:
+                self.multidigraph.nodes[node]["Waiting area"].append(self)
+
+    def find_lineup_areas(self, vessel, index_node_waiting_area):
+        lineup_areas = []
+        for node1, node2 in zip(vessel.route[index_node_waiting_area:-1], vessel.route[index_node_waiting_area + 1 :]):
+            k = sorted(vessel.multidigraph[node1][node2], key=lambda x: vessel.multidigraph[node1][node2][x]["geometry"].length)[0]
+            if "Line-up area" in vessel.multidigraph.edges[node1, node2, k].keys():
+                lineup_areas = vessel.multidigraph.edges[node1, node2, k]["Line-up area"]
+            else:
+                continue
+
+        return lineup_areas
+
+    def find_locks(self, vessel, index_node_waiting_area):
+        locks = []
+        for approach_node, departure_node in zip(
+            vessel.route[index_node_waiting_area:-1], vessel.route[index_node_waiting_area + 1 :]
+        ):
+            k = sorted(
+                vessel.multidigraph[approach_node][departure_node],
+                key=lambda x: vessel.multidigraph[approach_node][departure_node][x]["geometry"].length,
+            )[0]
+            if "Lock" in vessel.multidigraph.edges[approach_node, departure_node, k].keys():
+                locks.extend(vessel.multidigraph.edges[approach_node, departure_node, k]["Lock"])
+            else:
+                continue
+
+        directions = []
+        for lock in locks:
+            if approach_node == lock.node_doors1:
+                directions.append(1)
+            else:
+                directions.append(0)
 
+        return locks, directions
 
-class IsLockLineUpArea(HasResource, HasLength, Identifiable, Log):
-    """Mixin class: Something has lock object properties
-    - properties in meters
-    - operation in seconds
-    """
 
-    def __init__(self, node, lineup_length, *args, **kwargs):
-        super().__init__(length=lineup_length, remaining_length=lineup_length, *args, **kwargs)
-        """Initialization"""
+class IsLockLineUpArea(core.HasResource, core.HasLength, core.Identifiable, core.Log, output.HasOutput, HasMultiDiGraph):
+    """Mixin class: Something has line-up area object properties as part of the lock complex [in SI-units]:
+    creates a line-up area with the following resources:
+        - enter_line_up_area: resource used when entering the line-up area (assures one-by-one entry of the line-up area by vessels)
+        - line_up_area: resource with unlimited capacity used to formally request access to the line-up area
+        - converting_while_in_line_up_area: resource used when requesting for an empty conversion of the lock chamber
+        - pass_line_up_area: resource used to pass the second encountered line-up area"""
 
-        self.lock_queue_length = 0
+    def __init__(
+        self,
+        start_node,  # a string which indicates the location of the start of the line-up area
+        end_node,  # a string which indicates the location of the start of the line-up area
+        lineup_length,  # a float which contains the length of the line-up area
+        distance_to_lock_doors,
+        effective_lineup_length=None,
+        passing_allowed=False,
+        speed_reduction_factor=0.75,
+        k_edge=0,
+        *args,
+        **kwargs
+    ):
+        self.start_node = start_node
+        self.end_node = end_node
+        self.lineup_length = self.effective_lineup_length = lineup_length
+        if effective_lineup_length:
+            self.effective_lineup_length = effective_lineup_length
+        self.distance_to_lock_doors = distance_to_lock_doors
+        self.passing_allowed = passing_allowed
+        self.speed_reduction_factor = speed_reduction_factor
+        super().__init__(length=self.effective_lineup_length, remaining_length=self.effective_lineup_length, *args, **kwargs)
 
+        """Initialization"""
         # Lay-Out
         self.enter_line_up_area = {
-            node: simpy.PriorityResource(self.env, capacity=1),
-        }
-
+            start_node: simpy.PriorityResource(self.env, capacity=1),
+        }  # used to regulate one by one entering of line-up area, so capacity must be 1
         self.line_up_area = {
-            node: simpy.PriorityResource(self.env, capacity=100),
-        }
-
+            start_node: simpy.PriorityResource(self.env, capacity=100),
+        }  # line-up area itself, infinite capacity, as this is regulated by the HasLength, so capacity = inf
         self.converting_while_in_line_up_area = {
-            node: simpy.PriorityResource(self.env, capacity=1),
-        }
-
+            start_node: simpy.PriorityResource(self.env, capacity=1),
+        }  # used to minimize the number of empty convertion requests by one by multiple waiting vessels, so capacity must be 1
         self.pass_line_up_area = {
-            node: simpy.PriorityResource(self.env, capacity=1),
-        }
+            start_node: simpy.PriorityResource(self.env, capacity=1),
+        }  # used to prevent vessel from entering the lock before all previously locked vessels have passed the line-up area one by one, so capacity must be 1
 
+        # Add to the graph:
+        if "FG" in dir(self.env):
+            if "Line-up area" not in self.multidigraph.edges[self.start_node, self.end_node, k_edge].keys():
+                self.multidigraph.edges[self.start_node, self.end_node, k_edge]["Line-up area"] = [self]
+            else:
+                self.multidigraph.edges[self.start_node, self.end_node, k_edge]["Line-up area"].append(self)
+
+    def find_lock(self, vessel, start_node, end_node, direction=0):
+        lock = None
+        if direction:
+            route = list(reversed(vessel.route))
+        else:
+            route = vessel.route
+
+        index_node_start_lineup_area = route.index(start_node)
+        index_node_end_lineup_area = route.index(end_node)
+
+        if direction:
+            loop_route_1 = route[index_node_end_lineup_area:]
+            loop_route_2 = route[index_node_start_lineup_area:-1]
+        else:
+            loop_route_1 = route[index_node_start_lineup_area:-1]
+            loop_route_2 = route[index_node_end_lineup_area:]
+
+        for approach_node, departure_node in zip(loop_route_1, loop_route_2):
+            k = sorted(
+                vessel.multidigraph[approach_node][departure_node],
+                key=lambda x: vessel.multidigraph[approach_node][departure_node][x]["geometry"].length,
+            )[0]
+            if "Lock" in vessel.multidigraph.edges[approach_node, departure_node, k].keys():
+                locks = vessel.multidigraph.edges[approach_node, departure_node, k]["Lock"]
+                if approach_node == locks[0].node_doors1:
+                    direction = 1
+                else:
+                    direction = 0
+            else:
+                continue
 
-class HasLockDoors(SimpyObject):
-    def __init__(self, node_1, node_3, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        """Initialization
-        """
+            for lock in locks:
+                if lock.name is self.name:
+                    break
+            if lock.name is self.name:
+                break
 
-        self.doors_1 = {
-            node_1: simpy.PriorityResource(self.env, capacity=1),
-        }
-        self.doors_2 = {
-            node_3: simpy.PriorityResource(self.env, capacity=1),
-        }
+        return lock, direction
 
 
-class IsLock(HasResource, HasLength, HasLockDoors, Identifiable, Log):
-    """Mixin class: Something has lock object properties
-    - properties in meters
-    - operation in seconds
-    """
+class IsLock(core.HasResource, core.HasLength, core.Identifiable, CustomLog, output.HasOutput, HasMultiDiGraph):
+    """Mixin class: Something which has lock chamber object properties as part of a lock complex [in SI-units]"""
 
     def __init__(
         self,
-        node_1,
-        node_2,
-        node_3,
-        lock_length,
-        lock_width,
-        lock_depth,
-        doors_open,
-        doors_close,
-        wlev_dif,
-        disch_coeff,
-        grav_acc,
-        opening_area,
-        opening_depth,
-        simulation_start,
-        operating_time,
+        node_doors1,  # a string which indicates the location of the first pair of lock doors
+        node_doors2,  # a string which indicates the location of the second pair of lock doors
+        lock_length,  # a float which contains the length of the lock chamber
+        lock_width,  # a float which contains the width of the lock chamber
+        lock_depth,  # a float which contains the depth of the lock chamber
+        distance_doors1_from_first_waiting_area,
+        distance_doors2_from_second_waiting_area,
+        detector_nodes=None,
+        doors_open=0,  # a float which contains the time it takes to open the doors
+        doors_close=0,  # a float which contains the time it takes to close the doors
+        disch_coeff=0,  # a float which contains the discharge coefficient of filling system
+        opening_area=0,  # a float which contains the cross-sectional area of filling system
+        opening_depth=0,  # a float which contains the depth at which filling system is located
+        speed_reduction_factor=0.3,
+        k_edge=0,
+        levelling_time=0,
+        grav_acc=9.81,  # a float which contains the gravitational acceleration
+        time_step=10,
+        node_open=None,
+        conditions=None,
+        priority_rules=None,
+        mandatory_time_gap_between_entering_vessels=None,
+        used_as_one_way_traffic_regulation=False,
         *args,
-        **kwargs,
+        **kwargs
     ):
-
         """Initialization"""
-
         # Properties
         self.lock_length = lock_length
         self.lock_width = lock_width
         self.lock_depth = lock_depth
-        self.wlev_dif = wlev_dif
         self.disch_coeff = disch_coeff
         self.grav_acc = grav_acc
         self.opening_area = opening_area
         self.opening_depth = opening_depth
-        self.simulation_start = simulation_start.timestamp()
-        self.operating_time = operating_time
+        self.levelling_time = levelling_time
+        self.speed_reduction_factor = speed_reduction_factor
+        self.node_open = node_open
+        self.conditions = conditions
+        self.time_step = time_step
+        self.priority_rules = priority_rules
+        if detector_nodes is None:
+            detector_nodes = []
+        self.detector_nodes = detector_nodes
+        self.distance_doors1_from_first_waiting_area = distance_doors1_from_first_waiting_area
+        self.distance_doors2_from_second_waiting_area = distance_doors2_from_second_waiting_area
+        self.mandatory_time_gap_between_entering_vessels = mandatory_time_gap_between_entering_vessels
+        self.used_as_one_way_traffic_regulation = used_as_one_way_traffic_regulation
+        self.converting_chamber = False
+        self.schedule = pd.DataFrame(
+            index=pd.MultiIndex(levels=[[], []], codes=[[], []], names=["Name", "VesselName"]),
+            columns=["direction", "ETA", "ETD", "Length", "Beam", "Draught", "VesselType", "Priority"],
+        )
+
+        # 100 spaces in the lock by default
+        capacity = 100
+
+        super().__init__(nr_resources=capacity, length=lock_length, remaining_length=lock_length, *args, **kwargs)
+        self.simulation_start = self.env.simulation_start.timestamp()
+        self.next_lockage = {
+            node_doors1: simpy.PriorityResource(self.env, capacity=capacity),
+            node_doors2: simpy.PriorityResource(self.env, capacity=capacity),
+        }
+        self.next_lockage_length = {
+            node_doors1: simpy.Container(self.env, capacity=lock_length, init=lock_length),
+            node_doors2: simpy.Container(self.env, capacity=lock_length, init=lock_length),
+        }
+        self.in_next_lockage = {
+            node_doors1: simpy.PriorityResource(self.env, capacity=capacity),
+            node_doors2: simpy.PriorityResource(self.env, capacity=capacity),
+        }
+        self.doors_1 = {
+            node_doors1: simpy.PriorityResource(self.env, capacity=1),
+        }  # Only one ship can pass at a time: capacity = 1, request can have priority
+        self.doors_2 = {
+            node_doors2: simpy.PriorityResource(self.env, capacity=1),
+        }  # Only one ship can pass at a time: capacity = 1, request can have priority
 
         # Operating
         self.doors_open = doors_open
         self.doors_close = doors_close
 
         # Water level
-        assert node_1 != node_3
-
-        self.node_1 = node_1
-        self.node_3 = node_3
-        self.water_level = random.choice([node_1, node_3])
-
-        super().__init__(
-            length=lock_length,
-            remaining_length=lock_length,
-            node_1=node_1,
-            node_3=node_3,
-            *args,
-            **kwargs,
+        assert node_doors1 != node_doors2
+
+        self.node_doors1 = node_doors1
+        self.node_doors2 = node_doors2
+        if not self.node_open:
+            self.node_open = random.choice([node_doors1, node_doors2])
+        index_node_open = list(self.multidigraph.nodes).index(self.node_open)
+
+        if "hydrodynamic_information" in dir(self.env.vessel_traffic_service):
+            iter_data = self.env.vessel_traffic_service.hydrodynamic_information.sel(STATIONS=index_node_open).interp(
+                TIME=np.arange(self.env.simulation_start, self.env.simulation_stop, datetime.timedelta(seconds=self.time_step))
+            )
+            self.water_level = iter_data["Water level"].copy()
+            self.salinity = iter_data["Salinity"].copy()
+            self.discharge_res = np.abs(self.water_level.rename("Residual discharge").copy()) * 0
+            self.discharge_saline = np.abs(self.water_level.rename("Saline discharge").copy()) * 0
+            self.discharge_fresh = np.abs(self.water_level.rename("Fresh discharge").copy()) * 0
+
+        for detector_node in self.detector_nodes:
+            if "Detector" not in self.multidigraph.nodes[detector_node]:
+                self.multidigraph.nodes[detector_node]["Detector"] = {}
+
+            route1 = nx.dijkstra_path(self.multidigraph, detector_node, self.node_doors1)
+            route2 = nx.dijkstra_path(self.multidigraph, detector_node, self.node_doors2)
+            for route in [route1, route2]:
+                if len(route) > 1 and (
+                    [self.node_doors1, self.node_doors2] == [route[-2], route[-1]]
+                    or [self.node_doors1, self.node_doors2] == [route[-1], route[-2]]
+                ):
+                    self.multidigraph.nodes[detector_node]["Detector"][route[-1]] = IsDetectorNode(self)
+                    break
+
+        # Add to the graph:
+        if "FG" in dir(self.env):
+            # Add the lock to the edge or append it to the existing list
+            if "Lock" not in self.multidigraph.edges[self.node_doors1, self.node_doors2, k_edge].keys():
+                self.multidigraph.edges[self.node_doors1, self.node_doors2, k_edge]["Lock"] = [self]
+                self.multidigraph.edges[self.node_doors2, self.node_doors1, k_edge]["Lock"] = [self]
+            else:
+                self.multidigraph.edges[self.node_doors1, self.node_doors2, k_edge]["Lock"].append(self)
+                self.multidigraph.edges[self.node_doors2, self.node_doors1, k_edge]["Lock"].append(self)
+
+    def check_priority(self, vessel, direction):
+        # we do not have priority
+        condition = False
+        waiting_area = self.find_previous_waiting_area(vessel, direction)
+        if self.priority_rules is not None:
+            condition = self.priority_rules.evaluate(waiting_area.node).iloc[0]
+        if condition:
+            priority = -1
+        else:
+            priority = 0
+        return priority
+
+    def request_empty_lock_conversion(self, lineup_area, vessel, approach_node, hold_request=False, timeout_required=True):
+        """Vessel will request the lock chamber to be converted without vessels to his side of the lock chamber. This
+            is programmed by requesting the converting_while_in_line_up_area resource of the line-up area the vessels is
+            currently located in. If there was already a request by another vessel waiting in the same line-up area, this
+            original request can be holded.
+
+        Input:
+            - hold_request: a boolean which defines where an earlier request for the same empty lock conversion which
+                            was made by another ship should be holded"""
+
+        vessel.lock_information[self.name].waiting_during_converting = lineup_area.converting_while_in_line_up_area[
+            lineup_area.start_node
+        ].request()
+        vessel.lock_information[self.name].waiting_during_converting.obj = vessel
+        vessel.lock_information[self.name].empty_converting = True
+        if timeout_required:
+            yield vessel.lock_information[self.name].waiting_during_converting
+        if not hold_request:
+            if timeout_required:
+                yield from self.convert_chamber(vessel.env, approach_node, 0, vessel)
+                delattr(vessel.lock_information[self.name], "empty_converting")
+            else:
+                yield from self.convert_chamber(vessel.env, approach_node, 0, vessel, timeout_required=False)
+        lineup_area.converting_while_in_line_up_area[lineup_area.start_node].release(
+            vessel.lock_information[self.name].waiting_during_converting
         )
 
-    def operation_time(self, environment):
-        if type(self.wlev_dif) == list:
-            operating_time = (
-                2
-                * self.lock_width
-                * self.lock_length
-                * abs(self.wlev_dif[1][np.abs(self.wlev_dif[0] - (environment.now - self.simulation_start)).argmin()])
-            ) / (self.disch_coeff * self.opening_area * math.sqrt(2 * self.grav_acc * self.opening_depth))
-
-        elif type(self.wlev_dif) == float or type(self.wlev_dif) == int:
-            operating_time = (2 * self.lock_width * self.lock_length * abs(self.wlev_dif)) / (
-                self.disch_coeff * self.opening_area * math.sqrt(2 * self.grav_acc * self.opening_depth)
+    def find_previous_lineup_area(self, vessel, direction):
+        lineup_area = None
+        if direction:
+            index_node_doors2 = vessel.route.index(self.node_doors2)
+        else:
+            index_node_doors2 = vessel.route.index(self.node_doors1)
+
+        for approach_node, departure_node in zip(
+            list(reversed(vessel.route[:index_node_doors2])), list(reversed(vessel.route[1 : index_node_doors2 + 1]))
+        ):
+            k = sorted(
+                vessel.multidigraph[approach_node][departure_node],
+                key=lambda x: vessel.multidigraph[approach_node][departure_node][x]["geometry"].length,
+            )[0]
+            if "Line-up area" in vessel.multidigraph.edges[approach_node, departure_node, k].keys():
+                lineup_areas = vessel.multidigraph.edges[approach_node, departure_node, k]["Line-up area"]
+            else:
+                continue
+
+            for lineup_area in lineup_areas:
+                if lineup_area.name is self.name:
+                    break
+            if lineup_area.name is self.name:
+                break
+
+        return lineup_area
+
+    def find_next_lineup_area(self, vessel, direction):
+        lineup_area = None
+        if direction:
+            index_node_doors1 = vessel.route.index(self.node_doors1)
+            index_node_doors2 = vessel.route.index(self.node_doors2)
+        else:
+            index_node_doors1 = vessel.route.index(self.node_doors2)
+            index_node_doors2 = vessel.route.index(self.node_doors1)
+
+        for departure_node, approach_node in zip(vessel.route[index_node_doors2:], vessel.route[index_node_doors1:-1]):
+            k = sorted(
+                vessel.multidigraph[departure_node][approach_node],
+                key=lambda x: vessel.multidigraph[departure_node][approach_node][x]["geometry"].length,
+            )[0]
+            if "Line-up area" in vessel.multidigraph.edges[departure_node, approach_node, k].keys():
+                lineup_areas = vessel.multidigraph.edges[departure_node, approach_node, k]["Line-up area"]
+            else:
+                continue
+
+            for lineup_area in lineup_areas:
+                if lineup_area.name is self.name:
+                    break
+            if lineup_area.name is self.name:
+                break
+
+        return lineup_area
+
+    def find_previous_waiting_area(self, vessel, direction):
+        waiting_area = None
+        if direction:
+            index_node_doors2 = vessel.route.index(self.node_doors2)
+        else:
+            index_node_doors2 = vessel.route.index(self.node_doors1)
+
+        for previous_node in list(reversed(vessel.route[:index_node_doors2])):
+            if "Waiting area" in vessel.multidigraph.nodes[previous_node].keys():
+                waiting_areas = vessel.multidigraph.nodes[previous_node]["Waiting area"]
+            else:
+                continue
+
+            for waiting_area in waiting_areas:
+                if waiting_area.name is self.name:
+                    break
+            if waiting_area.name is self.name:
+                break
+
+        return waiting_area
+
+    def find_next_waiting_area(self, vessel, direction):
+        waiting_area = None
+        if direction:
+            index_node_doors1 = vessel.route.index(self.node_doors1)
+        else:
+            index_node_doors1 = vessel.route.index(self.node_doors2)
+
+        for next_node in vessel.route[index_node_doors1:]:
+            if "Waiting area" in vessel.multidigraph.nodes[next_node].keys():
+                waiting_areas = vessel.multidigraph.nodes[next_node]["Waiting area"]
+            else:
+                continue
+
+            for waiting_area in waiting_areas:
+                if waiting_area.name is self.name:
+                    break
+            if waiting_area.name is self.name:
+                break
+
+        return waiting_area
+
+    def exchange_flux_time_series_calculator(self, T_door_open, time_index):
+        index_node_doors1 = list(self.multidigraph.nodes).index(self.node_doors1)
+        index_node_doors2 = list(self.multidigraph.nodes).index(self.node_doors2)
+        index_node_open = list(self.multidigraph.nodes).index(self.node_open)
+        time_value = self.salinity.TIME[time_index].values
+        S_lock = self.salinity[time_index]
+        S_lock_harbour = (
+            self.env.vessel_traffic_service.hydrodynamic_information["Salinity"][index_node_open].interp(TIME=time_value).values
+        )
+        S_lock_average = (
+            self.env.vessel_traffic_service.hydrodynamic_information["Salinity"][index_node_doors1].interp(TIME=time_value).values
+            + self.env.vessel_traffic_service.hydrodynamic_information["Salinity"][index_node_doors2].interp(TIME=time_value).values
+        ) / 2
+        Wlev_lock = (
+            self.env.vessel_traffic_service.hydrodynamic_information["Water level"][index_node_open].interp(TIME=time_value).values
+        )
+        V_lock = self.lock_length * self.lock_width * (Wlev_lock + self.lock_depth)
+        v_exch = 0.5 * np.sqrt(
+            self.grav_acc * (0.8 * abs(S_lock_harbour - S_lock) / (1000 + 0.8 * S_lock_average)) * (Wlev_lock + self.lock_depth)
+        )
+        if v_exch != 0:
+            T_LE = (2 * self.lock_length) / v_exch
+        else:
+            T_LE = 0
+
+        time = np.arange(0, np.min([T_door_open, 2 * 3600]), self.time_step)
+        Q = []
+        V_tot = 0
+        for t in enumerate(time):
+            if t[0] == 0:
+                continue
+            delta_t = t[1] - time[t[0] - 1]
+            if T_LE != 0:
+                delta_V = V_lock * (np.tanh(t[1] / T_LE) - np.tanh(time[t[0] - 1] / T_LE))
+            else:
+                delta_V = 0
+            V_tot += delta_V
+            V_tot += delta_V
+            Q.append(delta_V / delta_t)
+            M = (S_lock_harbour - self.salinity[time_index].values) * delta_V
+            S_lock = (S_lock * V_lock + M) / V_lock
+            self.salinity[time_index + t[0]] = S_lock.values
+            if not self.discharge_saline[time_index + t[0]].values:
+                self.discharge_saline[time_index + t[0]] += delta_V / delta_t
+            if not self.discharge_fresh[time_index + t[0]].values:
+                self.discharge_fresh[time_index + t[0]] += -delta_V / delta_t
+            if np.abs(S_lock.values - S_lock_harbour) < 0.25:
+                outer_salinity = self.env.vessel_traffic_service.hydrodynamic_information["Salinity"][index_node_open].interp(
+                    TIME=np.arange(self.env.simulation_start, self.env.simulation_stop, datetime.timedelta(seconds=self.time_step))
+                )
+                self.salinity[time_index + t[0] :] = outer_salinity[time_index + t[0] :]
+                break
+
+    def levelling_to_harbour(self, V_ship, levelling_time, side, delay=0):
+        index_node_doors1 = list(self.multidigraph.nodes).index(self.node_doors1)
+        index_node_doors2 = list(self.multidigraph.nodes).index(self.node_doors2)
+        time_index = np.absolute(
+            self.env.vessel_traffic_service.hydrodynamic_information.TIME.values
+            - np.datetime64(datetime.datetime.fromtimestamp(self.env.now + delay))
+        ).argmin()
+        time_index_start = np.absolute(
+            self.salinity.TIME.values - np.datetime64(datetime.datetime.fromtimestamp(self.env.now + delay))
+        ).argmin()
+        time_value_start = self.salinity.TIME.values[time_index_start]
+        time_index_stop = np.absolute(
+            self.salinity.TIME.values - np.datetime64(datetime.datetime.fromtimestamp(self.env.now + levelling_time + delay))
+        ).argmin()
+        time = np.arange(self.env.now + delay, self.env.now + levelling_time + delay, self.time_step)
+        S_lock_start = self.salinity.values[time_index_start]
+        WLev_lock_inner = self.env.vessel_traffic_service.hydrodynamic_information["Water level"][index_node_doors2].interp(
+            TIME=time_value_start
+        )
+        V_lock_inner = self.lock_length * self.lock_width * (WLev_lock_inner.values + self.lock_depth)
+        WLev_lock_outer = self.env.vessel_traffic_service.hydrodynamic_information["Water level"][index_node_doors1].interp(
+            TIME=time_value_start
+        )
+        V_lock_outer = self.lock_length * self.lock_width * (WLev_lock_outer.values + self.lock_depth)
+        if side == self.node_doors1:
+            WLev_to_side = WLev_lock_inner
+            WLev_from_side = WLev_lock_outer
+            V_to_side = V_lock_inner
+            V_from_side = V_lock_outer
+            if WLev_from_side < WLev_to_side:
+                filling = True
+                S_to_side = self.env.vessel_traffic_service.hydrodynamic_information["Salinity"][index_node_doors2].values[
+                    time_index
+                ]
+            else:
+                filling = False
+        else:
+            WLev_to_side = WLev_lock_outer
+            WLev_from_side = WLev_lock_inner
+            V_to_side = V_lock_outer
+            V_from_side = V_lock_inner
+            if WLev_from_side < WLev_to_side:
+                filling = True
+                S_to_side = self.env.vessel_traffic_service.hydrodynamic_information["Salinity"][index_node_doors1].values[
+                    time_index
+                ]
+            else:
+                filling = False
+
+        if filling:
+            V_levelling = self.lock_length * self.lock_width * (WLev_to_side - WLev_from_side)
+            S_lock_final = S_lock = (S_lock_start * (V_from_side - V_ship) + V_levelling * S_to_side) / (V_to_side - V_ship)
+            dt = (self.water_level.TIME.values[1] - self.water_level.TIME.values[0]) / np.timedelta64(1, "s")
+            for t in enumerate(time):
+                sum_disch = abs((self.discharge_res[time_index_start : (time_index_start + t[0])]).sum())
+                S_lock = (S_lock_start * (V_from_side - V_ship) + sum_disch * dt * S_to_side) / (
+                    (V_from_side + sum_disch * dt) - V_ship
+                )
+                self.salinity[time_index_start + t[0]] = S_lock
+            V_loss_lev = 0
+
+        else:
+            V_levelling = self.lock_length * self.lock_width * (WLev_from_side - WLev_to_side)
+            S_lock_final = S_lock = S_lock_start
+            V_loss_lev = V_levelling
+
+        self.salinity[time_index_stop:] = S_lock
+        return V_levelling, S_lock_final, V_loss_lev
+
+    def sailing_out_to_harbour(self, V_ship, time_index):
+        index_node_open = list(self.multidigraph.nodes).index(self.node_open)
+        S_lock = self.salinity[time_index]
+        time_value = self.salinity.TIME.values[time_index]
+        WLev_lock_harbour = (
+            self.env.vessel_traffic_service.hydrodynamic_information["Water level"][index_node_open].interp(TIME=time_value).values
+        )
+        S_lock_harbour = (
+            self.env.vessel_traffic_service.hydrodynamic_information["Salinity"][index_node_open].interp(TIME=time_value).values
+        )
+        V_lock_harbour = self.lock_length * self.lock_width * (WLev_lock_harbour + self.lock_depth)
+        S_lock = (S_lock * (V_lock_harbour - V_ship) + V_ship * S_lock_harbour) / V_lock_harbour
+        start_time_passing_door = self.env.now
+        end_time_passing_door = self.env.now + 0.5 * self.doors_open
+        passing_time_door = np.arange(start_time_passing_door, end_time_passing_door, self.time_step)
+        if self.node_open == self.node_doors1:
+            for t in enumerate(passing_time_door):
+                if time_index + t[0] < len(self.discharge_fresh):
+                    self.discharge_saline[time_index + t[0]] += V_ship / (end_time_passing_door - start_time_passing_door)
+                    self.discharge_res[time_index + t[0]] += V_ship / (end_time_passing_door - start_time_passing_door)
+        else:
+            for t in enumerate(passing_time_door):
+                if time_index + t[0] < len(self.discharge_fresh):
+                    self.discharge_fresh[time_index + t[0]] += -V_ship / (end_time_passing_door - start_time_passing_door)
+                    self.discharge_res[time_index + t[0]] += -V_ship / (end_time_passing_door - start_time_passing_door)
+        return S_lock
+
+    def door_open_harbour(self, T_door_open, time_index_start):
+        index_node_doors1 = list(self.multidigraph.nodes).index(self.node_doors1)
+        index_node_doors2 = list(self.multidigraph.nodes).index(self.node_doors2)
+        index_node_open = list(self.multidigraph.nodes).index(self.node_open)
+        time_value = self.salinity.TIME[time_index_start].values
+        S_lock = self.salinity[time_index_start]
+        S_lock_harbour = (
+            self.env.vessel_traffic_service.hydrodynamic_information["Salinity"][index_node_open].interp(TIME=time_value).values
+        )
+        S_lock_average = (
+            self.env.vessel_traffic_service.hydrodynamic_information["Salinity"][index_node_doors1].interp(TIME=time_value).values
+            + self.env.vessel_traffic_service.hydrodynamic_information["Salinity"][index_node_doors2].interp(TIME=time_value).values
+        ) / 2
+        WLev_lock_harbour = (
+            self.env.vessel_traffic_service.hydrodynamic_information["Water level"][index_node_open].interp(TIME=time_value).values
+        )
+        V_lock_harbour = self.lock_length * self.lock_width * (WLev_lock_harbour + self.lock_depth)
+        self.exchange_flux_time_series_calculator(T_door_open, time_index_start)
+
+        # A loop that breaks at a certain moment assigning discharges to the sluice? (discharge should be separated for positive negative (and maybe sluice gates))
+        if S_lock_harbour != S_lock:
+            T_exch = self.lock_length / (
+                0.5
+                * np.sqrt(
+                    self.grav_acc
+                    * (0.8 * abs(S_lock_harbour - S_lock) / (1000 + 0.8 * S_lock_average))
+                    * (WLev_lock_harbour + self.lock_depth)
+                )
             )
-        assert not isinstance(operating_time, complex), f"operating_time number should not be complex: {operating_time}"
+            V_exch = V_lock_harbour * np.tanh(T_door_open / (2 * T_exch))
+        else:
+            V_exch = 0
+        M_exch = (S_lock_harbour - S_lock) * V_exch
+        S_lock = (S_lock * V_lock_harbour + M_exch) / V_lock_harbour
+        return S_lock
+
+    def sailing_in_from_harbour(self, V_ship, time_index):
+        S_lock = self.salinity[time_index]
+        start_time_passing_door = self.env.now
+        end_time_passing_door = start_time_passing_door + 0.5 * self.doors_close
+        passing_time_door = np.arange(start_time_passing_door, end_time_passing_door, self.time_step)
+        if self.node_open == self.node_doors2:
+            for t in enumerate(passing_time_door):
+                if time_index + t[0] < len(self.discharge_saline) - 1:
+                    self.discharge_saline[time_index + t[0]] = V_ship / (end_time_passing_door - start_time_passing_door)
+                    self.discharge_res[time_index + t[0]] = V_ship / (end_time_passing_door - start_time_passing_door)
+        if self.node_open == self.node_doors1:
+            for t in enumerate(passing_time_door):
+                if time_index + t[0] < len(self.discharge_fresh) - 1:
+                    self.discharge_fresh[time_index + t[0]] = -V_ship / (end_time_passing_door - start_time_passing_door)
+                    self.discharge_res[time_index + t[0]] = -V_ship / (end_time_passing_door - start_time_passing_door)
+        return S_lock
+
+    def total_ship_volume_in_lock(self):
+        volume = 0
+        for user in self.resource.users:
+            volume += user.obj.B * user.obj.L * user.obj.T
+        return volume
+
+    def determine_levelling_time(self, new_level, delay=0):
+        """Function which calculates the operation time: based on the constant or nearest in the signal of the water level difference
+
+        Input:
+            - environment: see init function"""
+
+        def calculate_discharge(lock, z, to_wlev, from_wlev, time_index, time_step):
+            two_gz = np.sqrt(2 * 9.81 * z)
+            if to_wlev[time_step] <= from_wlev[time_step]:
+                lock.water_level[time_index + time_step] = z + to_wlev[time_step]
+                if z != 0:
+                    if lock.node_open == lock.node_doors2:
+                        lock.discharge_res[time_step + time_index] += -1 * lock.disch_coeff * lock.opening_area * two_gz
+                        lock.discharge_fresh[time_step + time_index] += -1 * lock.disch_coeff * lock.opening_area * two_gz
+                    else:
+                        lock.discharge_res[time_step + time_index] += lock.disch_coeff * lock.opening_area * two_gz
+                        lock.discharge_saline[time_step + time_index] += lock.disch_coeff * lock.opening_area * two_gz
+                else:
+                    lock.discharge_res[time_step + time_index] = 0
+                    lock.discharge_fresh[time_step + time_index] = 0
+            else:
+                lock.water_level[time_index + time_step] = to_wlev[time_step] - z
+                if z != 0:
+                    if lock.node_open == lock.node_doors2:
+                        lock.discharge_res[time_step + time_index] += lock.disch_coeff * lock.opening_area * two_gz
+                        lock.discharge_saline[time_step + time_index] += lock.disch_coeff * lock.opening_area * two_gz
+                    else:
+                        lock.discharge_res[time_step + time_index] += -1 * lock.disch_coeff * lock.opening_area * two_gz
+                        lock.discharge_fresh[time_step + time_index] += -1 * lock.disch_coeff * lock.opening_area * two_gz
+                else:
+                    lock.discharge_res[time_step + time_index] += 0
+                    lock.discharge_fresh[time_step + time_index] += 0
+
+        index_node_doors1 = list(self.multidigraph.nodes).index(self.node_doors1)
+        index_node_doors2 = list(self.multidigraph.nodes).index(self.node_doors2)
+        if "hydrodynamic_information" in dir(self.env.vessel_traffic_service):
+            time = np.arange(
+                0,
+                2 * 3600,
+                (
+                    self.env.vessel_traffic_service.hydrodynamic_information["TIME"].values[1]
+                    - self.env.vessel_traffic_service.hydrodynamic_information["TIME"].values[0]
+                )
+                / np.timedelta64(1, "s"),
+            )
+            time_index = np.absolute(
+                self.env.vessel_traffic_service.hydrodynamic_information.TIME.values
+                - np.datetime64(datetime.datetime.fromtimestamp(self.env.now + delay))
+            ).argmin()
+            time_value = self.env.vessel_traffic_service.hydrodynamic_information.TIME.values[time_index]
+            wlev_outer = self.env.vessel_traffic_service.hydrodynamic_information["Water level"][index_node_doors1][
+                time_index : (time_index + len(time))
+            ].interp(TIME=np.arange(time_value, time_value + np.timedelta64(2, "h"), datetime.timedelta(seconds=self.time_step)))
+            wlev_inner = self.env.vessel_traffic_service.hydrodynamic_information["Water level"][index_node_doors2][
+                time_index : (time_index + len(time))
+            ].interp(TIME=np.arange(time_value, time_value + np.timedelta64(2, "h"), datetime.timedelta(seconds=self.time_step)))
+            H = abs(wlev_outer[0] - wlev_inner[0]).values
+            z = abs(wlev_outer[0] - wlev_inner[0]).values
+            if self.node_open == self.node_doors2:
+                from_wlev = wlev_inner
+                to_wlev = wlev_outer
+                index_node_doors = index_node_doors1
+            else:
+                from_wlev = wlev_outer
+                to_wlev = wlev_inner
+                index_node_doors = index_node_doors2
+            time_index = np.absolute(
+                self.discharge_res.TIME.values
+                - self.env.vessel_traffic_service.hydrodynamic_information["Water level"][index_node_doors1].TIME[time_index].values
+            ).argmin()
+            calculate_discharge(self, z, to_wlev, from_wlev, time_index, 0)
+            time_series = np.arange(0, 2 * 3600, self.time_step)
+
+            for t in enumerate(time_series):
+                if t[0] == 0:
+                    continue
+                if t[0] + time_index >= len(self.water_level) - 1:
+                    levelling_time = t[1]
+                    break
+
+                dz = (
+                    np.sqrt(H)
+                    - ((self.disch_coeff * self.opening_area * np.sqrt(2 * 9.81)) / (2 * (self.lock_length * self.lock_width)))
+                    * (t[1] + self.time_step / 2)
+                ) ** 2 - (
+                    np.sqrt(H)
+                    - ((self.disch_coeff * self.opening_area * np.sqrt(2 * 9.81)) / (2 * (self.lock_length * self.lock_width)))
+                    * (t[1] - self.time_step / 2)
+                ) ** 2
+                z += dz
+                calculate_discharge(self, z, to_wlev, from_wlev, time_index, t[0])
+                if z <= 0.05:
+                    levelling_time = t[1]
+                    outer_water_level = self.env.vessel_traffic_service.hydrodynamic_information["Water level"][
+                        index_node_doors
+                    ].interp(
+                        TIME=np.arange(
+                            self.env.simulation_start, self.env.simulation_stop, datetime.timedelta(seconds=self.time_step)
+                        )
+                    )
+                    self.water_level[(t[0] + time_index + 1) :] = outer_water_level[(t[0] + time_index + 1) :]
+                    break
+            else:
+                levelling_time = 0
+
+        else:
+            levelling_time = self.levelling_time
+
+        # V_levelling, S_lock, V_loss_lev are not used
+        _ = self.levelling_to_harbour(self.total_ship_volume_in_lock(), levelling_time, self.node_open, delay)
 
-        return operating_time
+        return levelling_time
 
-    def convert_chamber(self, environment, new_level, number_of_vessels):
-        """Convert the water level"""
+    def convert_chamber(self, environment, new_level, number_of_vessels, vessel, timeout_required=True):
+        """Function which converts the lock chamber and logs this event.
+
+        Input:
+            - environment: see init function
+            - new_level: a string which represents the node and indicates the side at which the lock is currently levelled
+            - number_of_vessels: the total number of vessels which are levelled simultaneously"""
+
+        self.converting_chamber = True
+
+        def door_open():
+            if len(self.logbook) > 4:
+                t_doors_open = time.mktime(pd.Timestamp(self.logbook[-4]["Timestamp"]).timetuple())
+            else:
+                t_doors_open = time.mktime(self.env.simulation_start.timetuple())
+
+            if timeout_required:
+                T_door_open = self.env.now - t_doors_open - 0.5 * self.doors_open
+            else:
+                T_door_open = self.env.now + self.doors_close - t_doors_open - 0.5 * self.doors_open
+
+            if "hydrodynamic_information" in dir(self.env.vessel_traffic_service):
+                time_index_start = np.absolute(
+                    self.water_level.TIME.values - np.datetime64(datetime.datetime.fromtimestamp(self.env.now - T_door_open))
+                ).argmin()
+                time_index_stop = np.absolute(
+                    self.water_level.TIME.values
+                    - np.datetime64(datetime.datetime.fromtimestamp(self.env.now - 0.5 * self.doors_close))
+                ).argmin()
+                S_lock = self.door_open_harbour(T_door_open - 0.5 * self.doors_close, time_index_start)
+                time_series = self.env.vessel_traffic_service.hydrodynamic_information["TIME"].values
+                if time_index_stop != len(time_series) - 2:
+                    self.salinity[time_index_stop:] = S_lock
 
         # Close the doors
-        self.log_entry(
-            "Lock doors closing start",
-            environment.now,
-            number_of_vessels,
-            self.water_level,
-        )
-        yield environment.timeout(self.doors_close)
-        self.log_entry(
-            "Lock doors closing stop",
-            environment.now,
-            number_of_vessels,
-            self.water_level,
-        )
+        self.log_entry_extra(self.env.now, self.node_open, "Lock doors closing start", self.output.copy())
+        if "hydrodynamic_information" in dir(self.env.vessel_traffic_service):
+            time_index = np.max(
+                [
+                    0,
+                    np.absolute(
+                        self.salinity.TIME.values
+                        - np.datetime64(
+                            datetime.datetime.fromtimestamp(
+                                timepy.mktime(pd.Timestamp(self.logbook[-1]["Timestamp"]).timetuple()) + 0.5 * self.doors_close
+                            )
+                        )
+                    ).argmin(),
+                ]
+            )
+            if len(self.resource.users):
+                volume = 0
+                for user in self.resource.users:
+                    volume += user.obj.L * user.obj.B * user.obj.T
+                S_lock = self.sailing_in_from_harbour(volume, time_index)
+                self.salinity[time_index:] = S_lock
+
+        if timeout_required:
+            yield environment.timeout(self.doors_close)
+            self.log_entry_extra(environment.now, self.node_open, "Lock doors closing stop", self.output.copy())
+            door_open()
+            vessel.levelling_time = self.determine_levelling_time(new_level)
+        else:
+            self.log_entry_extra(environment.now + self.doors_close, self.node_open, "Lock doors closing stop", self.output.copy())
+            door_open()
+            vessel.levelling_time = self.determine_levelling_time(new_level, delay=self.doors_close)
 
         # Convert the chamber
-        self.log_entry(
-            "Lock chamber converting start",
-            environment.now,
-            number_of_vessels,
-            self.water_level,
-        )
+        if timeout_required:
+            self.log_entry_extra(self.env.now, self.node_open, "Lock chamber converting start", self.output.copy())
+        else:
+            self.log_entry_extra(
+                self.env.now + self.doors_close, self.node_open, "Lock chamber converting start", self.output.copy()
+            )
 
         # Water level will shift
         self.change_water_level(new_level)
-        yield environment.timeout(self.operation_time(environment))
-        self.log_entry(
-            "Lock chamber converting stop",
-            environment.now,
-            number_of_vessels,
-            self.water_level,
-        )
+        if timeout_required:
+            yield environment.timeout(vessel.levelling_time)
+            self.log_entry_extra(self.env.now, self.node_open, "Lock chamber converting stop", self.output.copy())
+        else:
+            self.log_entry_extra(
+                self.env.now + self.doors_close + vessel.levelling_time,
+                self.node_open,
+                "Lock chamber converting stop",
+                self.output.copy(),
+            )
+
         # Open the doors
-        self.log_entry(
-            "Lock doors opening start",
-            environment.now,
-            number_of_vessels,
-            self.water_level,
-        )
-        yield environment.timeout(self.doors_open)
-        self.log_entry(
-            "Lock doors opening stop",
-            environment.now,
-            number_of_vessels,
-            self.water_level,
-        )
+        if timeout_required:
+            self.log_entry_extra(self.env.now, self.node_open, "Lock doors opening start", self.output.copy())
+        else:
+            self.log_entry_extra(
+                self.env.now + self.doors_close + vessel.levelling_time,
+                self.node_open,
+                "Lock doors opening start",
+                self.output.copy(),
+            )
 
-    def change_water_level(self, side):
-        """Change water level and priorities in queue"""
+        if "hydrodynamic_information" in dir(vessel.env.vessel_traffic_service):
+            time_index = np.max(
+                [
+                    0,
+                    np.absolute(
+                        self.salinity.TIME.values
+                        - np.datetime64(
+                            datetime.datetime.fromtimestamp(timepy.mktime(pd.Timestamp(self.logbook[-1]["Timestamp"]).timetuple()))
+                        )
+                    ).argmin(),
+                ]
+            )
+            if len(self.resource.users):
+                volume = 0
+                for user in self.resource.users:
+                    volume += user.obj.L * user.obj.B * user.obj.T
+                S_lock = self.sailing_out_to_harbour(volume, time_index)
+                self.salinity[(time_index + int(0.5 * self.doors_open / self.time_step)) :] = S_lock
+
+        if timeout_required:
+            yield environment.timeout(self.doors_open)
+            self.log_entry_extra(self.env.now, self.node_open, "Lock doors opening stop", self.output.copy())
+        else:
+            self.log_entry_extra(
+                self.env.now + self.doors_close + vessel.levelling_time + self.doors_open,
+                self.node_open,
+                "Lock doors opening stop",
+                self.output.copy(),
+            )
+        self.converting_chamber = False
 
-        self.water_level = side
+    def change_water_level(self, side):
+        """Function which changes the water level in the lock chamber and priorities in queue"""
 
+        self.node_open = side
         for request in self.resource.queue:
             request.priority = -1 if request.priority == 0 else 0
 
@@ -221,900 +1057,1173 @@ def change_water_level(self, side):
                 self.resource.queue.insert(-1, self.resource.queue.pop(self.resource.queue.index(request)))
 
 
-####
-####
-class CanPassLock(opentnsim.core.Movable):
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        """Initialization
-        """
-        self.on_pass_edge_functions.append(self.pass_edge)
+class PassLock:
+    """Mixin class: a collection of functions which are used to pass a lock complex consisting of a waiting area, line-up areas, and lock chambers"""
+
+    @staticmethod
+    def leave_waiting_area(vessel, node_waiting_area):
+        """Processes vessels which are waiting in the waiting area of the lock complex and requesting access to preceding the line-up area:
+            if there area multiple parallel lock chambers, the chamber with the least expected total waiting time is chosen,
+            after which access is requested to enter the line-up area corresponding with the assigned lock chain series.
+
+        Input:
+            - vessel: an identity which is Identifiable, Movable, and Routable, and has VesselProperties
+            - node_waiting_area: a string which includes the name of the node the waiting area is located in the network"""
+
+        def choose_lock_chamber(vessel, lock, lock_position, series_number, lineup_areas, lock_queue_length):
+            """Assigns the lock chamber with the least expected total waiting time to the vessel in case of parallell lock chambers. The
+                expected total waiting time is calculated through quantifying the total length of the queued vessels. If a vessel does
+                not fit in a lockage, it will create a new lockage cycle by requesting the full length capacity of the line-up area. When
+                this request is granted, the vessel will immediately release the obsolete length, such that more vessels can go with the
+                next lockage.
+            This function is evaluated in the leave_waiting_area function.
+
+            Input:
+                - vessel: an identity which is Identifiable, Movable, and Routable, and has VesselProperties
+                - lock: an object within the network which is generated with the IsLock mixin class
+                - lock_position: a string which includes the name of the node the lock chamber is located in the network
+                - series_number: an integer number which indicates which lock series is evaluated (should not be greater than
+                                 the maximum number of parallel lock chambers)
+                - lineup_areas: the collection of line-up areas in the node at which the line-up areas are located (the total
+                                amount of line-up areas should not exceed the amount of lock series)
+                - lock_queue_length: an empty list at which the resistance in total queue length (~ total waiting time) is
+                                     appended."""
+
+            # Imports the properties of the evaluated line-up area
+            lineup_area = lineup_areas[series_number]
+
+            # Assesses the total queue length within this lock series
+            # - if the queue for the line-up area is empty, a name is set if the vessel fits in the lock chamber and line-up right away, otherwise the queue is calculated
+            if lineup_area.length.get_queue == []:
+                if (
+                    vessel.L <= lock.length.level
+                    and vessel.L <= lineup_area.length.level
+                    and lock.node_open == vessel.route[vessel.route.index(lock_position)]
+                ):
+                    if "lock_information" in dir(vessel) and lock.name not in vessel.lock_information.keys():
+                        vessel.lock_information[lock.name] = HasLockInformation()
+                elif vessel.L <= lineup_area.length.level:
+                    lock_queue_length.append(lineup_area.length.level)
+                else:
+                    lock_queue_length.append(lineup_area.length.capacity)
+
+            # - else, if the vessel does not fit in the line-up area, the total length of the queued is calculated added with the full length capacity of the line-up area
+            else:
+                line_up_queue_length = lineup_area.length.capacity
+                for queued_vessel in lineup_area.length.get_queue:
+                    line_up_queue_length += queued_vessel.amount
+                lock_queue_length.append(line_up_queue_length)
+
+        def access_lineup_area(vessel, lineup_area):
+            """Processes the request of vessels to access the line-up area by claiming a position (which equals the length of
+                the vessel) along its jetty.
+            This function is evaluated in the leave_waiting_area function
+
+            Input:
+                - vessel: an identity which is Identifiable, Movable, and Routable, and has VesselProperties
+                - lineup_area: an object within the network which is generated with the IsLineUpArea mixin class and
+                               assigned to the vessel as the lock series with the least expected total waiting time"""
+
+            def create_new_lock_cycle_and_request_access(vessel, lineup_area):
+                """Creates a new locking cycle (wave of vessels assigned to same lockage) by requesting the full length
+                    capacity of the line-up area, assigning the vessel to this request and returning the obsolete length
+                    when the request is granted.
+                This function is used in the access_lineup_area function within the leave_waiting_area function
+
+                No input required"""
+
+                vessel.lock_information[lineup_area.name].access_lineup_length = lineup_area.length.get(lineup_area.length.capacity)
+                vessel.lock_information[lineup_area.name].access_lineup_length.obj = vessel
+                yield vessel.lock_information[lineup_area.name].access_lineup_length
+                lineup_area.length.put(lineup_area.length.capacity - vessel.L)
+
+            def request_access_lock_cycle(vessel, lineup_area, total_length_waiting_vessels=0):
+                """Processes the request of a vessel to enter a lock cycle (wave of vessels assigned to same lockage), depending
+                    on the governing conditions regarding the current situation in the line-up area.
+                This function is used in the access_lineup_area function within the leave_waiting_area function
+
+                No input required"""
+
+                # - If the line-up area has no queue, the vessel will access the lock cycle
+                if lineup_area.length.get_queue == []:
+                    vessel.lock_information[lineup_area.name].access_lineup_length = lineup_area.length.get(vessel.L)
+                    vessel.lock_information[lineup_area.name].access_lineup_length.obj = vessel
+
+                # Else, if there are already preceding vessels waiting in a queue, the vessel will request access to a lock cycle
+                else:
+                    # Calculates the total length of vessels assigned to this lock cycle
+                    for queued_vessel in reversed(lineup_area.length.get_queue):
+                        total_length_waiting_vessels += queued_vessel.obj.L
+
+                    # If the vessels does not fit in this lock cycle, it will start a new lock cycle
+                    if vessel.L > lineup_area.length.capacity - total_length_waiting_vessels:
+                        yield from create_new_lock_cycle_and_request_access(vessel, lineup_area)
+
+                    # Else, if the vessel does fit in this last lock cycle, it will request a place in this cycle
+                    else:
+                        vessel.lock_information[lineup_area.name].access_lineup_length = lineup_area.length.get(vessel.L)
+                        vessel.lock_information[lineup_area.name].access_lineup_length.obj = vessel
+                        yield vessel.lock_information[lineup_area.name].access_lineup_length
+
+            # Requesting procedure for access to line-up area
+            # - If there area vessels in the line-up area
+            if lineup_area.line_up_area[lineup_area.start_node].users != []:
+                # - If the vessels fits in the lock cycle right away
+                if vessel.L <= (
+                    lineup_area.line_up_area[lineup_area.start_node].users[-1].obj.lock_information[lineup_area.name].lineup_dist
+                    - 0.5 * lineup_area.line_up_area[lineup_area.start_node].users[-1].obj.L
+                ):
+                    yield from request_access_lock_cycle(vessel, lineup_area)
+
+                # - Else, if the vessel does not fit in the lock cyle right away
+                else:
+                    if lineup_area.length.get_queue == []:
+                        yield from create_new_lock_cycle_and_request_access(vessel, lineup_area)
+                    else:
+                        yield from request_access_lock_cycle(vessel, lineup_area)
+
+            # - Else, if there are no vessels yet in the line-up area
+            else:
+                # - If the vessels fits in the lock cycle right away
+                if vessel.L <= lineup_area.length.level:
+                    yield from request_access_lock_cycle(vessel, lineup_area)
+
+                # - Else, if the vessel does not fit in the lock cyle right away
+                else:
+                    if lineup_area.length.get_queue == []:
+                        yield from create_new_lock_cycle_and_request_access(vessel, lineup_area)
+                    else:
+                        yield from request_access_lock_cycle(vessel, lineup_area)
+
+        # Imports the properties of the waiting area
+        for waiting_area in vessel.multidigraph.nodes[node_waiting_area]["Waiting area"]:
+            if "lock_information" not in dir(vessel):
+                vessel.lock_information = {}
+
+            vessel.waiting_area_access = waiting_area.waiting_area[node_waiting_area].request()
+            vessel.waiting_area_access.obj = vessel
+            # Identifies the index of the node of the waiting area within the route of the vessel
+            index_node_waiting_area = vessel.route.index(node_waiting_area)
+
+            # Checks whether the waiting area is the first encountered waiting area of the lock complex
+
+            lineup_areas = waiting_area.find_lineup_areas(vessel, index_node_waiting_area)
+
+            # Imports the location of the lock chamber of the lock complex
+            locks, directions = waiting_area.find_locks(vessel, index_node_waiting_area)
+
+            # Determines the current time
+            for lineup_area in lineup_areas:
+                if waiting_area.name.split("_")[0] != lineup_area.name.split("_")[0]:
+                    continue
+
+                wait_for_lineup_area = vessel.env.now
+                vessel.v_before_lock = vessel.v
+
+                # Assigning the lock chain series with least expected waiting time to the vessel
+                lock_queue_length = []
+                for count, (lock, direction) in enumerate(zip(locks, directions)):
+                    if waiting_area.name.split("_")[0] == lock.name.split("_")[0]:
+                        if direction:
+                            approach_node = lock.node_doors1
+                        else:
+                            approach_node = lock.node_doors2
+                        if lock.used_as_one_way_traffic_regulation:
+                            if "lock_information" in dir(vessel) and lock.name not in vessel.lock_information.keys():
+                                vessel.lock_information[lock.name] = HasLockInformation()
+                        else:
+                            choose_lock_chamber(vessel, lock, approach_node, count, lineup_areas, lock_queue_length)
+                        break
 
-        self.lineup_pos_lat = None
-        self.lineup_pos_lon = None
+                # If the function did not yet assign a lock chain series
+                if (
+                    lineup_area.name not in vessel.lock_information.keys()
+                    and "lock_information" in dir(vessel)
+                    and lineup_areas[lock_queue_length.index(min(lock_queue_length))].name not in vessel.lock_information.keys()
+                ):
+                    vessel.lock_information[
+                        lineup_areas[lock_queue_length.index(min(lock_queue_length))].name
+                    ] = HasLockInformation()
+
+                yield from access_lineup_area(vessel, lineup_area)
+
+                # Calculation of location in line-up area as a distance in [m] from start line-up jetty
+                # - If the line-up area is not empty
+                if len(lineup_area.line_up_area[lineup_area.start_node].users) != 0:
+                    vessel.lock_information[lineup_area.name].lineup_dist = (
+                        lineup_area.line_up_area[lineup_area.start_node]
+                        .users[-1]
+                        .obj.lock_information[lineup_area.name]
+                        .lineup_dist
+                        - 0.5 * lineup_area.line_up_area[lineup_area.start_node].users[-1].obj.L
+                        - 0.5 * vessel.L
+                    )
+                # - Else, if the line-up area is empty
+                else:
+                    vessel.lock_information[lineup_area.name].lineup_dist = lineup_area.length.capacity - 0.5 * vessel.L
+                # Calculation of the (lat,lon)-coordinates of the assigned position in the line-up area
+                if direction:
+                    k = sorted(
+                        lineup_area.multidigraph[lineup_area.start_node][lineup_area.end_node],
+                        key=lambda x: lineup_area.multidigraph[lineup_area.start_node][lineup_area.end_node][x]["geometry"].length,
+                    )[0]
+                    vessel.lock_information[lineup_area.name].lineup_position = lineup_area.multidigraph.edges[
+                        lineup_area.start_node, lineup_area.end_node, k
+                    ]["geometry"].interpolate(
+                        lock.distance_doors1_from_first_waiting_area
+                        - lineup_area.distance_to_lock_doors
+                        - (lineup_area.lineup_length - vessel.lock_information[lineup_area.name].lineup_dist)
+                    )
 
-    def get_node_idx(self, node):
-        node_idx = self.route.index(node)
-        return node_idx
+                elif not direction:
+                    k = sorted(
+                        lineup_area.multidigraph[lineup_area.start_node][lineup_area.end_node],
+                        key=lambda x: lineup_area.multidigraph[lineup_area.start_node][lineup_area.end_node][x]["geometry"].length,
+                    )[0]
+                    vessel.lock_information[lineup_area.name].lineup_position = lineup_area.multidigraph.edges[
+                        lineup_area.start_node, lineup_area.end_node, k
+                    ]["geometry"].interpolate(
+                        lock.distance_doors2_from_second_waiting_area
+                        - lineup_area.distance_to_lock_doors
+                        - (lineup_area.lineup_length - vessel.lock_information[lineup_area.name].lineup_dist)
+                    )
 
-    def pass_edge(self, origin, destination):
-        speed = self.v
-        node_idx = self.route.index(destination)
+                # Formal request of the vessel to access the line-up area assigned to the vessel (always granted)
+                vessel.lock_information[lineup_area.name].access_lineup_area = lineup_area.line_up_area[
+                    lineup_area.start_node
+                ].request()
+                vessel.lock_information[lineup_area.name].access_lineup_area.obj = vessel
+                vessel.lock_information[lineup_area.name].access_lineup_area.obj.lock_information[lineup_area.name].n = len(
+                    lineup_area.line_up_area[lineup_area.start_node].users
+                )
+
+                # Request of entering the line-up area to assure that vessels will enter the line-up area one-by-one
+                vessel.lock_information[lineup_area.name].enter_lineup_length = lineup_area.enter_line_up_area[
+                    lineup_area.start_node
+                ].request()
+                vessel.lock_information[lineup_area.name].enter_lineup_length.obj = vessel
+                yield vessel.lock_information[lineup_area.name].enter_lineup_length
+
+                # Speed reduction in the approach to the line-up area
+                vessel.lock_information[lineup_area.name].wait_for_next_cycle = False
+                vessel.lock_information[lineup_area.name].waited_in_waiting_area = False
+
+                # Calculates and reports the total waiting time in the waiting area
+                if wait_for_lineup_area != vessel.env.now:
+                    vessel.log_entry_extra(
+                        wait_for_lineup_area,
+                        nx.get_node_attributes(vessel.multidigraph, "geometry")[node_waiting_area],
+                        "Waiting in waiting area start",
+                        vessel.output.copy(),
+                    )
+                    vessel.log_entry_extra(
+                        vessel.env.now,
+                        nx.get_node_attributes(vessel.multidigraph, "geometry")[node_waiting_area],
+                        "Waiting in waiting area stop",
+                        vessel.output.copy(),
+                    )
+                    # Speed reduction in the approach to the line-up area, as the vessel had to lay still in the waiting area
+                    vessel.v = lineup_area.speed_reduction_factor * lock.speed_reduction_factor * vessel.v_before_lock
 
-        departure_lock = None
+                    # Changes boolean of the vessel which indicates that it had to wait in the waiting area
+                    for line_up_user in lineup_area.line_up_area[lineup_area.start_node].users:
+                        if line_up_user.obj.id == vessel.id:
+                            line_up_user.obj.lock_information[lineup_area.name].waited_in_waiting_area = True
+                            break
 
-        if "Lock" in self.env.FG.nodes[origin].keys():
-            # TODO: Shapely expects coordinates in x,y order
-            # https://shapely.readthedocs.io/en/stable/manual.html#Point
-            orig = shapely.geometry.Point(self.lock_pos_lat, self.lock_pos_lon)
+                vessel.update_waiting_area_status_report(waiting_area, node_waiting_area)
+                waiting_area.waiting_area[node_waiting_area].release(vessel.waiting_area_access)
+                waiting_area.waiting_area[node_waiting_area].release(vessel.waiting_area_access)
+                break
+
+    @staticmethod
+    def approach_lineup_area(vessel, start_node, end_node):
+        """Processes vessels which are approaching the line-up area of the lock complex:
+            determines whether the assigned position in the line-up area (distance in [m]) should be changed as the preceding vessel(s),
+            which was/were waiting in the line-up area, has/have of is/are already accessed/accessing the lock.
+
+        Input:
+            - vessel: an identity which is Identifiable, Movable, and Routable, and has VesselProperties
+            - node_lineup_area: a string which includes the name of the node at which the line-up area is located in the network"""
+
+        # Imports the properties of the line-up area the vessel is assigned to
+        k = sorted(
+            vessel.multidigraph[start_node][end_node], key=lambda x: vessel.multidigraph[start_node][end_node][x]["geometry"].length
+        )[0]
+        lineup_areas = vessel.multidigraph.edges[start_node, end_node, k]["Line-up area"]
+
+        for lineup_area in lineup_areas:
+            if lineup_area.name in vessel.lock_information.keys():
+                break
+
+        lock, direction = lineup_area.find_lock(vessel, start_node, end_node)
+        if isinstance(lock, type(None)):
+            return
+
+        if direction:
+            distance_to_lineup_area = (
+                lock.distance_doors1_from_first_waiting_area - lineup_area.distance_to_lock_doors - lineup_area.lineup_length
+            )
+        else:
+            distance_to_lineup_area = (
+                lock.distance_doors2_from_second_waiting_area - lineup_area.distance_to_lock_doors - lineup_area.lineup_length
+            )
+        location_of_start_lineup_area = vessel.env.vessel_traffic_service.provide_location_over_edges(
+            vessel, start_node, end_node, distance_to_lineup_area
+        )
 
-        if "Lock" in self.env.FG.nodes[destination].keys():
-            dest = shapely.geometry.Point(self.lock_pos_lat, self.lock_pos_lon)
+        if distance_to_lineup_area:
+            vessel.log_entry_extra(
+                vessel.env.now,
+                vessel.multidigraph.nodes[start_node]["geometry"],
+                "Sailing to start of line-up area start",
+                vessel.output.copy(),
+            )
+            yield vessel.env.timeout(distance_to_lineup_area / vessel.v)
+            vessel.log_entry_extra(
+                vessel.env.now, location_of_start_lineup_area, "Sailing to start of line-up area stop", vessel.output.copy()
+            )
 
-        if "Line-up area" in self.env.FG.nodes[origin].keys():
-            orig = shapely.geometry.Point(self.lineup_pos_lat, self.lineup_pos_lon)
+        def change_lineup_dist(vessel, lock, lineup_area, lineup_dist, lineup_area_user):
+            """Determines whether the assigned position in the line-up area (distance in [m]) should be changed as the preceding vessel(s),
+                which was/were waiting in the line-up area, has/have of is/are already accessed/accessing the lock.
+            This function is used in the approach_lineup_area function.
+
+            Input:
+                - vessel: an identity which is Identifiable, Movable, and Routable, and has VesselProperties
+                - lineup_area: an object within the network which is generated with the IsLineUpArea mixin class and
+                               assigned to the vessel as the lock series with the least expected total waiting time
+                - lock: an object within the network which is generated with the IsLock mixin class and
+                        assigned to the vessel as the lock series with the least expected total waiting time
+                - lineup_dist: the initial position of the vessel in the line-up area as the distance from the origin of the jetty in [m]
+                - q: an integer number which represents the assigned position of the vessel in the line-up area, only the vessel which is
+                     the new first in line (q=0) will be processed"""
+
+            if lineup_area_user[0] == 0 and lineup_area_user[1].obj.lock_information[lock.name].n != (
+                lineup_area_user[1].obj.lock_information[lock.name].n - len(lock.resource.users)
+            ):
+                lineup_dist = lineup_area.length.capacity - 0.5 * vessel.L
+            return lineup_dist
+
+        # Checks the need to change the position of the vessel within the line-up area
+        for vessel_index, lineup_area_user in enumerate(lineup_area.line_up_area[start_node].users):
+            if lineup_area_user.obj.id == vessel.id:
+                # Imports information about the current lock cycle
+                lock_door_1_user_priority = 0
+                lock_door_2_user_priority = 0
+                lock_door_1_users = lock.doors_1[lock.node_doors1].users
+                lock_door_2_users = lock.doors_2[lock.node_doors2].users
+
+                if direction and lock_door_2_users != []:
+                    lock_door_2_user_priority = lock.doors_2[lock.node_doors2].users[0].priority
+
+                elif not direction and lock_door_1_users != []:
+                    lock_door_1_user_priority = lock.doors_1[lock.node_doors1].users[0].priority
+
+                # Decision if position should be changed
+                if direction and lock_door_2_user_priority == -1:
+                    vessel.lock_information[lock.name].lineup_dist = change_lineup_dist(
+                        vessel, lock, lineup_area, vessel.lock_information[lock.name].lineup_dist, (vessel_index, lineup_area_user)
+                    )
+                    k = sorted(
+                        lineup_area.multidigraph[lineup_area.start_node][lineup_area.end_node],
+                        key=lambda x: lineup_area.multidigraph[lineup_area.start_node][lineup_area.end_node][x]["geometry"].length,
+                    )[0]
+                    vessel.lock_information[lock.name].lineup_position = lineup_area.multidigraph.edges[
+                        lineup_area.start_node, lineup_area.end_node, k
+                    ]["geometry"].interpolate(
+                        lock.distance_doors1_from_first_waiting_area
+                        - lineup_area.distance_to_lock_doors
+                        - lineup_area.lineup_length
+                        + vessel.lock_information[lock.name].lineup_dist
+                    )
 
-        if "Line-up area" in self.env.FG.nodes[destination].keys():
-            dest = shapely.geometry.Point(self.lineup_pos_lat, self.lineup_pos_lon)
+                elif not direction and lock_door_1_user_priority == -1:
+                    vessel.lock_information[lock.name].lineup_dist = change_lineup_dist(
+                        vessel, lock, lineup_area, vessel.lock_information[lock.name].lineup_dist, (vessel_index, lineup_area_user)
+                    )
+                    k = sorted(
+                        lineup_area.multidigraph[lineup_area.start_node][lineup_area.end_node],
+                        key=lambda x: lineup_area.multidigraph[lineup_area.start_node][lineup_area.end_node][x]["geometry"].length,
+                    )[0]
+                    vessel.lock_information[lock.name].lineup_position = lineup_area.multidigraph.edges[
+                        lineup_area.start_node, lineup_area.end_node, k
+                    ]["geometry"].interpolate(
+                        lock.distance_doors2_from_second_waiting_area
+                        - lineup_area.distance_to_lock_doors
+                        - lineup_area.lineup_length
+                        + vessel.lock_information[lock.name].lineup_dist
+                    )
+                break
+            break
 
-        if "Waiting area" in self.env.FG.nodes[destination].keys():
-            locks = self.env.FG.nodes[destination]["Waiting area"]
-            for lock in locks:
-                for r in self.route[node_idx:]:
-                    if "Line-up area" in self.env.FG.nodes[r].keys():
-                        wait_for_waiting_area = self.env.now
-                        access_waiting_area = lock.waiting_area[destination].request()
-                        yield access_waiting_area
-
-                        if wait_for_waiting_area != self.env.now:
-                            waiting = self.env.now - wait_for_waiting_area
-                            self.log_entry(
-                                "Waiting to enter waiting area start",
-                                wait_for_waiting_area,
-                                0,
-                                nx.get_node_attributes(self.env.FG, "geometry")[origin],
-                            )
-                            self.log_entry(
-                                "Waiting to enter waiting area stop",
-                                self.env.now,
-                                waiting,
-                                nx.get_node_attributes(self.env.FG, "geometry")[origin],
-                            )
+        if vessel.v == vessel.v_before_lock:
+            vessel.v = lineup_area.speed_reduction_factor * vessel.v_before_lock
 
-        if "Waiting area" in self.env.FG.nodes[origin].keys():
-            locks = self.env.FG.nodes[origin]["Waiting area"]
-            for lock in locks:
-                for r in self.route[node_idx]:
-                    if "Line-up area" in self.env.FG.nodes[r].keys():
-                        locks2 = self.env.FG.nodes[r]["Line-up area"]
-                        for r2 in self.route[node_idx:]:
-                            if "Lock" in self.env.FG.nodes[r2].keys():
-                                locks3 = self.env.FG.nodes[r2]["Lock"]
-                                break
-
-                        self.lock_name = []
-                        for lock3 in locks3:
-                            if lock3.water_level == self.route[self.route.index(r2) - 1]:
-                                for lock2 in locks2:
-                                    if lock2.name == lock3.name:
-                                        if lock2.lock_queue_length == 0:
-                                            self.lock_name = lock3.name
-                                    break
+        # Sail to the assigned position in the line-up area
+        if lineup_area.lineup_length:
+            vessel.log_entry_extra(
+                vessel.env.now, location_of_start_lineup_area, "Sailing to position in line-up area start", vessel.output.copy()
+            )
+            yield vessel.env.timeout(vessel.lock_information[lock.name].lineup_dist / vessel.v)
+            vessel.log_entry_extra(
+                vessel.env.now,
+                vessel.lock_information[lock.name].lineup_position,
+                "Sailing to position in line-up area stop",
+                vessel.output.copy(),
+            )
+            vessel.distance = 0
+
+    @staticmethod
+    def leave_lineup_area(vessel, start_node, end_node):
+        """Processes vessels which are waiting in the line-up area of the lock complex:
+            requesting access to the lock chamber given the governing phase in the lock cycle of the lock chamber and calculates the
+            position within the lock chamber.
+
+        Input:
+            - vessel: an identity which is Identifiable, Movable, and Routable, and has VesselProperties
+            - node_lineup_area: a string which includes the name of the node at which the line-up area is located in the network"""
+
+        # Imports the properties of the line-up area the vessel is assigned to
+        k_lineup = sorted(
+            vessel.multidigraph[start_node][end_node], key=lambda x: vessel.multidigraph[start_node][end_node][x]["geometry"].length
+        )[0]
+        lineup_areas = vessel.multidigraph.edges[start_node, end_node, k_lineup]["Line-up area"]
+        distance_to_lineup_areas = [lineup_area.distance_to_lock_doors for lineup_area in lineup_areas]
+        lineup_areas = [x for _, x in sorted(zip(distance_to_lineup_areas, lineup_areas))]
+        total_waiting_time = True
+        while total_waiting_time:
+            total_waiting_time = 0
+            for lineup_area in lineup_areas:
+                if lineup_area.name not in vessel.lock_information.keys():
+                    continue
+
+                # Determines lock
+                lock, direction = lineup_area.find_lock(vessel, start_node, end_node)
+                if isinstance(lock, type(None)):
+                    return
+
+                # If lock is used as a one-way traffic regulation
+                waiting_required = True
+                if lock.used_as_one_way_traffic_regulation:
+                    waiting_required = False
+                    vessel.lock_information[lock.name].distance_to_lock_doors1 = (
+                        lineup_area.distance_to_lock_doors + vessel.lock_information[lock.name].lineup_dist
+                    )
+                    vessel.lock_information[lock.name].distance_to_lock_doors2 = (
+                        vessel.lock_information[lock.name].distance_to_lock_doors1 + lock.lock_length
+                    )
+                    vessel.lock_information[lock.name].ETA = (
+                        vessel.env.now + vessel.lock_information[lock.name].distance_to_lock_doors1 / vessel.v
+                    )
+                    vessel.lock_information[lock.name].ETD = (
+                        vessel.env.now + vessel.lock_information[lock.name].distance_to_lock_doors2 / vessel.v
+                    )
 
-                        lock_queue_length = []
-                        if self.lock_name == []:
-                            for lock2 in locks2:
-                                lock_queue_length.append(lock2.lock_queue_length)
-
-                            self.lock_name = locks2[lock_queue_length.index(min(lock_queue_length))].name
-
-                        for lock2 in locks2:
-                            if lock2.name == self.lock_name:
-                                lock2.lock_queue_length += 1
-
-                        for lock2 in locks2:
-                            if lock2.name == self.lock_name:
-                                self.v = 0.5 * speed
-                                break
-
-                        wait_for_lineup_area = self.env.now
-                        lock.waiting_area[origin].release(access_waiting_area)
-
-                        if self.route[self.route.index(r2) - 1] == lock3.node_1:
-                            if lock3.doors_2[lock3.node_3].users != [] and lock3.doors_2[lock3.node_3].users[0].priority == -1:
-                                if self.L < lock2.length.level + lock3.length.level:
-                                    access_lineup_length = lock2.length.get(self.L)
-                                elif self.L < lock2.length.level:
-                                    if lock2.length.level == lock2.length.capacity:
-                                        access_lineup_length = lock2.length.get(self.L)
-                                    elif (
-                                        lock2.line_up_area[r].users != []
-                                        and lock3.length.level < lock2.line_up_area[r].users[0].length
+                    no_waiting_time = False
+                    if lock.conditions and "Draught" in lock.conditions.keys():
+                        no_waiting_time = vessel.T < lock.conditions["Draught"]
+
+                    if not no_waiting_time:
+                        waiting_time = True
+                        while waiting_time:
+                            waiting_time = 0
+                            mask = lock.schedule.direction != direction
+                            if lock.conditions:
+                                for condition, value in lock.conditions.items():
+                                    mask = mask & (lock.schedule[condition] >= value)
+
+                            for loc, user in lock.schedule[mask].iterrows():
+                                loc = lock.schedule[mask].index.get_loc(loc)
+                                if not loc and vessel.lock_information[lock.name].ETD < user.ETA:
+                                    break
+                                elif loc < len(lock.schedule[mask]) - 1:
+                                    if (
+                                        vessel.lock_information[lock.name].ETA > user.ETD
+                                        and vessel.lock_information[lock.name].ETD < lock.schedule[mask].iloc[loc + 1].ETA
                                     ):
-                                        access_lineup_length = lock2.length.get(self.L)
-                                    else:
-                                        if lock2.length.get_queue == []:
-                                            access_lineup_length = lock2.length.get(lock2.length.capacity)
-                                            lock2.length.get_queue[-1].length = self.L
-                                            yield access_lineup_length
-                                            correct_lineup_length = lock2.length.put(lock2.length.capacity - self.L)
-                                        else:
-                                            total_length_waiting_vessels = 0
-                                            for q in reversed(range(len(lock2.length.get_queue))):
-                                                if lock2.length.get_queue[q].amount == lock2.length.capacity:
-                                                    break
-                                            for q2 in range(q, len(lock2.length.get_queue)):
-                                                total_length_waiting_vessels += lock2.length.get_queue[q2].length
-
-                                            if self.L > lock2.length.capacity - total_length_waiting_vessels:
-                                                access_lineup_length = lock2.length.get(lock2.length.capacity)
-                                                lock2.length.get_queue[-1].length = self.L
-                                                yield access_lineup_length
-                                                correct_lineup_length = lock2.length.put(lock2.length.capacity - self.L)
-                                            else:
-                                                access_lineup_length = lock2.length.get(self.L)
-                                                lock2.length.get_queue[-1].length = self.L
-                                                yield access_lineup_length
-                                else:
-                                    if lock2.length.get_queue == []:
-                                        access_lineup_length = lock2.length.get(lock2.length.capacity)
-                                        lock2.length.get_queue[-1].length = self.L
-                                        yield access_lineup_length
-                                        correct_lineup_length = lock2.length.put(lock2.length.capacity - self.L)
-                                    else:
-                                        total_length_waiting_vessels = 0
-                                        for q in reversed(range(len(lock2.length.get_queue))):
-                                            if lock2.length.get_queue[q].amount == lock2.length.capacity:
-                                                break
-                                        for q2 in range(q, len(lock2.length.get_queue)):
-                                            total_length_waiting_vessels += lock2.length.get_queue[q2].length
-
-                                        if self.L > lock2.length.capacity - total_length_waiting_vessels:
-                                            access_lineup_length = lock2.length.get(lock2.length.capacity)
-                                            lock2.length.get_queue[-1].length = self.L
-                                            yield access_lineup_length
-                                            correct_lineup_length = lock2.length.put(lock2.length.capacity - self.L)
-                                        else:
-                                            access_lineup_length = lock2.length.get(self.L)
-                                            lock2.length.get_queue[-1].length = self.L
-                                            yield access_lineup_length
-
-                            else:
-                                if lock2.length.level == lock2.length.capacity:
-                                    access_lineup_length = lock2.length.get(self.L)
-                                elif (
-                                    lock2.line_up_area[r].users != []
-                                    and self.L
-                                    < lock2.line_up_area[r].users[-1].lineup_dist - 0.5 * lock2.line_up_area[r].users[-1].length
-                                ):
-                                    access_lineup_length = lock2.length.get(self.L)
+                                        break
+                                    elif vessel.lock_information[lock.name].ETA < user.ETD:
+                                        waiting_time = user.ETD - vessel.lock_information[lock.name].ETA
+                                        vessel.lock_information[lock.name].ETA += waiting_time
+                                        vessel.lock_information[lock.name].ETD += waiting_time
                                 else:
-                                    if lock2.length.get_queue == []:
-                                        access_lineup_length = lock2.length.get(lock2.length.capacity)
-                                        lock2.length.get_queue[-1].length = self.L
-                                        yield access_lineup_length
-                                        correct_lineup_length = lock2.length.put(lock2.length.capacity - self.L)
-                                    else:
-                                        total_length_waiting_vessels = 0
-                                        for q in reversed(range(len(lock2.length.get_queue))):
-                                            if lock2.length.get_queue[q].amount == lock2.length.capacity:
-                                                break
-                                        for q2 in range(q, len(lock2.length.get_queue)):
-                                            total_length_waiting_vessels += lock2.length.get_queue[q2].length
-
-                                        if self.L > lock2.length.capacity - total_length_waiting_vessels:
-                                            access_lineup_length = lock2.length.get(lock2.length.capacity)
-                                            lock2.length.get_queue[-1].length = self.L
-                                            yield access_lineup_length
-                                            correct_lineup_length = lock2.length.put(lock2.length.capacity - self.L)
-                                        else:
-                                            access_lineup_length = lock2.length.get(self.L)
-                                            lock2.length.get_queue[-1].length = self.L
-                                            yield access_lineup_length
-
-                        elif self.route[self.route.index(r2) - 1] == lock3.node_3:
-                            if lock3.doors_1[lock3.node_1].users != [] and lock3.doors_1[lock3.node_1].users[0].priority == -1:
-                                if self.L < lock2.length.level + lock3.length.level:
-                                    access_lineup_length = lock2.length.get(self.L)
-                                elif self.L < lock2.length.level:
-                                    if lock2.length.level == lock2.length.capacity:
-                                        access_lineup_length = lock2.length.get(self.L)
-                                    elif (
-                                        lock2.line_up_area[r].users != []
-                                        and lock3.length.level < lock2.line_up_area[r].users[0].length
+                                    if (
+                                        vessel.lock_information[lock.name].ETA > user.ETD
+                                        or vessel.lock_information[lock.name].ETD < user.ETA
                                     ):
-                                        access_lineup_length = lock2.length.get(self.L)
-                                    else:
-                                        if lock2.length.get_queue == []:
-                                            access_lineup_length = lock2.length.get(lock2.length.capacity)
-                                            yield access_lineup_length
-                                            correct_lineup_length = lock2.length.put(lock2.length.capacity - self.L)
-                                            yield correct_lineup_length
-                                        else:
-                                            total_length_waiting_vessels = 0
-                                            for q in reversed(range(len(lock2.length.get_queue))):
-                                                if lock2.length.get_queue[q].amount == lock2.length.capacity:
-                                                    break
-                                            for q2 in range(q, len(lock2.length.get_queue)):
-                                                total_length_waiting_vessels += lock2.length.get_queue[q2].length
-
-                                            if self.L > lock2.length.capacity - total_length_waiting_vessels:
-                                                access_lineup_length = lock2.length.get(lock2.length.capacity)
-                                                lock2.length.get_queue[-1].length = self.L
-                                                yield access_lineup_length
-                                                correct_lineup_length = lock2.length.put(lock2.length.capacity - self.L)
-                                            else:
-                                                access_lineup_length = lock2.length.get(self.L)
-                                                lock2.length.get_queue[-1].length = self.L
-                                                yield access_lineup_length
-                                else:
-                                    if lock2.length.get_queue == []:
-                                        access_lineup_length = lock2.length.get(lock2.length.capacity)
-                                        lock2.length.get_queue[-1].length = self.L
-                                        yield access_lineup_length
-                                        correct_lineup_length = lock2.length.put(lock2.length.capacity - self.L)
-                                    else:
-                                        total_length_waiting_vessels = 0
-                                        for q in reversed(range(len(lock2.length.get_queue))):
-                                            if lock2.length.get_queue[q].amount == lock2.length.capacity:
-                                                break
-                                        for q2 in range(q, len(lock2.length.get_queue)):
-                                            total_length_waiting_vessels += lock2.length.get_queue[q2].length
-
-                                        if self.L > lock2.length.capacity - total_length_waiting_vessels:
-                                            access_lineup_length = lock2.length.get(lock2.length.capacity)
-                                            lock2.length.get_queue[-1].length = self.L
-                                            yield access_lineup_length
-                                            correct_lineup_length = lock2.length.put(lock2.length.capacity - self.L)
-                                        else:
-                                            access_lineup_length = lock2.length.get(self.L)
-                                            lock2.length.get_queue[-1].length = self.L
-                                            yield access_lineup_length
-                            else:
-                                if lock2.length.level == lock2.length.capacity:
-                                    access_lineup_length = lock2.length.get(self.L)
-                                elif (
-                                    lock2.line_up_area[r].users != []
-                                    and self.L
-                                    < lock2.line_up_area[r].users[-1].lineup_dist - 0.5 * lock2.line_up_area[r].users[-1].length
-                                ):
-                                    access_lineup_length = lock2.length.get(self.L)
-                                else:
-                                    if lock2.length.get_queue == []:
-                                        access_lineup_length = lock2.length.get(lock2.length.capacity)
-                                        lock2.length.get_queue[-1].length = self.L
-                                        yield access_lineup_length
-                                        correct_lineup_length = lock2.length.put(lock2.length.capacity - self.L)
+                                        break
                                     else:
-                                        total_length_waiting_vessels = 0
-                                        for q in reversed(range(len(lock2.length.get_queue))):
-                                            if lock2.length.get_queue[q].amount == lock2.length.capacity:
-                                                break
-                                        for q2 in range(q, len(lock2.length.get_queue)):
-                                            total_length_waiting_vessels += lock2.length.get_queue[q2].length
-
-                                        if self.L > lock2.length.capacity - total_length_waiting_vessels:
-                                            access_lineup_length = lock2.length.get(lock2.length.capacity)
-                                            lock2.length.get_queue[-1].length = self.L
-                                            yield access_lineup_length
-                                            correct_lineup_length = lock2.length.put(lock2.length.capacity - self.L)
-                                        else:
-                                            access_lineup_length = lock2.length.get(self.L)
-                                            lock2.length.get_queue[-1].length = self.L
-                                            yield access_lineup_length
-
-                        if len(lock2.line_up_area[r].users) != 0:
-                            self.lineup_dist = (
-                                lock2.line_up_area[r].users[-1].lineup_dist
-                                - 0.5 * lock2.line_up_area[r].users[-1].length
-                                - 0.5 * self.L
+                                        waiting_time = user.ETD - vessel.lock_information[lock.name].ETA
+                                        vessel.lock_information[lock.name].ETA += waiting_time
+                                        vessel.lock_information[lock.name].ETD += waiting_time
+
+                            if waiting_time:
+                                total_waiting_time += waiting_time
+                                if vessel.logbook[-1]["Message"] != "Waiting in line-up area stop":
+                                    vessel.lock_information[lineup_area.name].vessel_waited_in_lineup_area = True
+                                    vessel.log_entry_extra(
+                                        vessel.env.now,
+                                        vessel.logbook[-1]["Geometry"],
+                                        "Waiting in line-up area start",
+                                        vessel.output.copy(),
+                                    )
+                                yield vessel.env.timeout(waiting_time)
+
+                if vessel.logbook[-1]["Message"] == "Waiting in line-up area stop":
+                    vessel.logbook[-1]["Timestamp"] = pd.Timestamp(datetime.datetime.fromtimestamp(vessel.env.now)).to_datetime64()
+                elif vessel.logbook[-1]["Message"] == "Waiting in line-up area start":
+                    vessel.log_entry_extra(
+                        vessel.env.now, vessel.logbook[-1]["Geometry"], "Waiting in line-up area stop", vessel.output.copy()
+                    )
+
+        for lineup_area in lineup_areas:
+            if lineup_area.name not in vessel.lock_information.keys():
+                continue
+
+            # Determines lock
+            lock, direction = lineup_area.find_lock(vessel, start_node, end_node)
+            if isinstance(lock, type(None)):
+                return
+
+            # Vessel releases its request to enter the line-up area, made in the waiting area
+            lineup_area.enter_line_up_area[start_node].release(vessel.lock_information[lock.name].enter_lineup_length)
+
+            def access_lock_chamber(
+                vessel,
+                direction,
+                lineup_area,
+                lock,
+                approach_node,
+                door1,
+                door2,
+                node_door1,
+                node_door2,
+                waiting_required,
+            ):
+                """Processes vessels which are waiting in the line-up area of the lock complex:
+                    determines the current phase within the lock cycle and adjusts the request of the vessel accordingly
+                This function is used in the leave_lineup_area function.
+
+                Input:
+                    - vessel: an identity which is Identifiable, Movable, and Routable, and has VesselProperties
+                    - lineup_area: an object within the network which is generated with the IsLineUpArea mixin class and
+                                   assigned to the vessel as the lock series with the least expected total waiting time
+                    - start_node: a string which includes the name of the node at which the line-up area is located in the network
+                    - lock: an object within the network which is generated with the IsLock mixin class and
+                            assigned to the vessel as the lock series with the least expected total waiting time
+                    - approach_node: a string which includes the name of the node at which the lock chamber is located in the network
+                    - door1: an object created in the IsLock class which resembles the set of lock doors which is first encountered by
+                             the vessel, which should be supscripted to, using a string which includes the name of the node at which this
+                             lock door is located in the network and was specified as input in the IsLock class
+                    - door2: an object created in the IsLock class which resembles the set of lock doors which is last encountered by
+                             the vessel, which should be supscripted to, using a string which includes the name of the node at which this
+                             lock door is located in the network and was specified as input in the IsLock class"""
+
+                def request_approach_lock_chamber(vessel, direction, lock, door1, timeout_required=True, priority=0):
+                    """Vessel will request if it can enter the lock by requesting access to the first set of lock doors. This
+                        request always has priority = 0, as vessels can only pass these doors when the doors are open (not
+                        claimed by a priority = -1 request). The capacity of the doors equals one, to prevent ships from
+                        entering simultaneously. The function yields a timeout. This can be switched off if it is assured
+                        the vessel can approach immediately.
+
+                    Input:
+                        - timeout_required: a boolean which defines whether the requesting vessel receives a timeout."""
+
+                    vessel.lock_information[lock.name].access_lock_door1 = door1.request(priority=priority)
+                    vessel.lock_information[lock.name].access_lock_door1.obj = vessel
+
+                    if timeout_required:
+                        yield vessel.lock_information[lock.name].access_lock_door1
+
+                    priority = lock.check_priority(vessel, direction)
+                    lock.schedule.loc[(lock.name, vessel.name), :] = [
+                        direction,
+                        vessel.env.now + vessel.lock_information[lock.name].distance_to_lock_doors1 / vessel.v,
+                        vessel.env.now + (vessel.lock_information[lock.name].distance_to_lock_doors1 + lock.lock_length) / vessel.v,
+                        vessel.L,
+                        vessel.B,
+                        vessel.T,
+                        vessel.type,
+                        priority,
+                    ]
+                    lock.schedule = lock.schedule.sort_values("ETA")
+
+                def secure_lock_cycle(vessel, direction, lock, door2, hold_request=False, timeout_required=True, priority=-1):
+                    """Vessel will indicate the direction of the next lock cycle by requesting access to the second pair of lock
+                        doors. Therefore, this request by default has priority = -1, which indicates the direction of the lockage
+                        as requests to access the same doors by vessels on the opposite side of the lock will be queued. A timeout
+                        is yielded. This can be switched off if it is assured the vessel can approach immediately. Furthermore, if
+                        there was already a request by another vessel waiting in the same line-up area, this original request can be
+                        holded. Lastly, the function is also used with priority = 0 in order to let vessels wait for the next lockage.
+
+                    Input:
+                        - hold_request: a boolean which defines where an earlier request for the same empty lock conversion which
+                                        was made by another ship should be holded
+                        - timeout_required: a boolean which defines whether the requesting vessel receives a timeout.
+                        - priority: an integer [-1,0] which indicates the priority of the request: either with (-1) or without (0)
+                                    priority."""
+
+                    if "access_lock_door2" not in dir(vessel.lock_information[lock.name]):
+                        vessel.lock_information[lock.name].access_lock_door2 = door2.request(priority=priority)
+                        vessel.lock_information[lock.name].access_lock_door2.obj = vessel
+                        if hold_request:
+                            door2.release(door2.users[0])
+                        if timeout_required:
+                            yield vessel.lock_information[lock.name].access_lock_door2
+
+                        priority = lock.check_priority(vessel, direction)
+                        lock.schedule.loc[(lock.name, vessel.name), :] = [
+                            direction,
+                            vessel.env.now
+                            + (vessel.lock_information[lock.name].distance_to_lock_doors2 - lock.lock_length) / vessel.v,
+                            vessel.env.now + vessel.lock_information[lock.name].distance_to_lock_doors2 / vessel.v,
+                            vessel.L,
+                            vessel.B,
+                            vessel.T,
+                            vessel.type,
+                            priority,
+                        ]
+
+                        lock.schedule = lock.schedule.sort_values("ETA")
+
+                def wait_for_next_lockage(vessel, direction, lock, door2, timeout_required=True, priority=0):
+                    """Vessels will wait for the next lockage by requesting access to the second pair of lock doors without priority. If
+                        granted, the request will immediately be released.
+
+                    No input required."""
+
+                    yield from secure_lock_cycle(
+                        vessel, direction, lock, door2, priority=priority, timeout_required=timeout_required
+                    )
+                    door2.release(vessel.lock_information[lock.name].access_lock_door2)
+                    delattr(vessel.lock_information[lock.name], "access_lock_door2")
+
+                # If the vessel was not yet assigned to the next lockage:
+                vessel.lock_information[lock.name].in_lock = False
+
+                # If vessel does not fit in the next lockage, it has to wait first
+                yield vessel.lock_information[lock.name].next_lockage_length
+                vessel.lock_information[lock.name].in_next_lockage = lock.in_next_lockage[node_door2].request()
+                vessel.lock_information[lock.name].in_next_lockage.obj = vessel
+
+                # Request procedure of the lock doors, which is dependent on the current moment within the lock cycle:
+                # If lock is at same side:
+                if approach_node == lock.node_open:
+                    # Request to start the lock cycle
+                    if lock.resource.users:
+                        if door1.users != [] and door1.users[0].priority == -1:
+                            yield from request_approach_lock_chamber(vessel, direction, lock, door1, timeout_required=True)
+                            yield from secure_lock_cycle(vessel, direction, lock, door2, timeout_required=False)
+                        else:
+                            yield from request_approach_lock_chamber(vessel, direction, lock, door1, timeout_required=True)
+                            yield from secure_lock_cycle(vessel, direction, lock, door2, hold_request=True, timeout_required=False)
+                    else:
+                        for user in lineup_area.line_up_area[lineup_area.start_node].users:
+                            if "empty_converting" in dir(user.obj.lock_information[lock.name]):
+                                if time.mktime(pd.Timestamp(lock.logbook[-1]["Timestamp"]).timetuple()) - vessel.env.now > 0:
+                                    yield vessel.env.timeout(
+                                        time.mktime(pd.Timestamp(lock.logbook[-1]["Timestamp"]).timetuple()) - vessel.env.now
+                                    )
+
+                        if (door2.users != [] and door2.users[0].priority == -1) or (
+                            door1.queue != [] and door1.queue[0].priority == 0
+                        ):
+                            yield from request_approach_lock_chamber(vessel, direction, lock, door1, timeout_required=True)
+                            yield from secure_lock_cycle(vessel, direction, lock, door2, hold_request=True, timeout_required=False)
+                        elif door1.users != [] and door1.users[0].priority == -1:
+                            yield from wait_for_next_lockage(vessel, direction, lock, door2)
+                            yield from request_approach_lock_chamber(vessel, direction, lock, door1, timeout_required=True)
+                            yield from secure_lock_cycle(vessel, direction, lock, door2, timeout_required=False)
+                        else:
+                            yield from request_approach_lock_chamber(vessel, direction, lock, door1, timeout_required=False)
+                            yield from secure_lock_cycle(vessel, direction, lock, door2, timeout_required=False)
+
+                # If lock is at other side
+                else:
+                    # If there are no lock users or vessels waiting at the other side of the lock to enter:
+                    if not lock.in_next_lockage[node_door1].users and not lock.resource.users:
+                        if door2.users and door2.users[0].priority:
+                            yield from request_approach_lock_chamber(
+                                vessel, direction, lock, door1, timeout_required=waiting_required
+                            )
+                            yield from secure_lock_cycle(
+                                vessel, direction, lock, door2, hold_request=True, timeout_required=waiting_required
+                            )
+                        else:
+                            yield from request_approach_lock_chamber(vessel, direction, lock, door1, timeout_required=False)
+                            yield from secure_lock_cycle(vessel, direction, lock, door2, timeout_required=False)
+
+                    # - Else, if no users on other side:
+                    else:
+                        yield from wait_for_next_lockage(vessel, direction, lock, door2)
+                        if approach_node != lock.node_open:
+                            yield from wait_for_next_lockage(vessel, direction, lock, door2)
+                        if door2.users != [] and door2.users[0].priority == -1:
+                            yield from request_approach_lock_chamber(
+                                vessel, direction, lock, door1, timeout_required=waiting_required
+                            )
+                            yield from secure_lock_cycle(
+                                vessel, direction, lock, door2, hold_request=True, timeout_required=waiting_required
                             )
                         else:
-                            self.lineup_dist = lock2.length.capacity - 0.5 * self.L
-
-                        self.wgs84 = pyproj.Geod(ellps="WGS84")
-                        [lineup_area_start_lat, lineup_area_start_lon, lineup_area_stop_lat, lineup_area_stop_lon] = [
-                            self.env.FG.nodes[self.route[self.route.index(r)]]["geometry"].x,
-                            self.env.FG.nodes[self.route[self.route.index(r)]]["geometry"].y,
-                            self.env.FG.nodes[self.route[self.route.index(r) + 1]]["geometry"].x,
-                            self.env.FG.nodes[self.route[self.route.index(r) + 1]]["geometry"].y,
+                            yield from request_approach_lock_chamber(
+                                vessel, direction, lock, door1, timeout_required=waiting_required
+                            )
+                            yield from secure_lock_cycle(vessel, direction, lock, door2, timeout_required=False)
+
+                if approach_node != lock.node_open:
+                    if any(
+                        [
+                            "empty_converting" in dir(user.obj.lock_information[lock.name])
+                            for user in lock.in_next_lockage[node_door2].users
                         ]
-                        fwd_azimuth, _, _ = self.wgs84.inv(
-                            lineup_area_start_lat,
-                            lineup_area_start_lon,
-                            lineup_area_stop_lat,
-                            lineup_area_stop_lon,
+                    ):
+                        yield from lock.request_empty_lock_conversion(
+                            lineup_area, vessel, approach_node, hold_request=True, timeout_required=waiting_required
                         )
-                        [self.lineup_pos_lat, self.lineup_pos_lon, _] = self.wgs84.fwd(
-                            self.env.FG.nodes[self.route[self.route.index(r)]]["geometry"].x,
-                            self.env.FG.nodes[self.route[self.route.index(r)]]["geometry"].y,
-                            fwd_azimuth,
-                            self.lineup_dist,
+                    else:
+                        yield from lock.request_empty_lock_conversion(
+                            lineup_area, vessel, approach_node, timeout_required=waiting_required
                         )
 
-                        access_lineup_area = lock2.line_up_area[r].request()
-                        lock2.line_up_area[r].users[-1].length = self.L
-                        lock2.line_up_area[r].users[-1].id = self.id
-                        lock2.line_up_area[r].users[-1].lineup_pos_lat = self.lineup_pos_lat
-                        lock2.line_up_area[r].users[-1].lineup_pos_lon = self.lineup_pos_lon
-                        lock2.line_up_area[r].users[-1].lineup_dist = self.lineup_dist
-                        lock2.line_up_area[r].users[-1].n = len(lock2.line_up_area[r].users)
-                        lock2.line_up_area[r].users[-1].v = 0.25 * speed
-                        lock2.line_up_area[r].users[-1].wait_for_next_cycle = False
-                        yield access_lineup_area
-
-                        enter_lineup_length = lock2.enter_line_up_area[r].request()
-                        yield enter_lineup_length
-                        lock2.enter_line_up_area[r].users[0].id = self.id
-
-                        if wait_for_lineup_area != self.env.now:
-                            self.v = 0.25 * speed
-                            waiting = self.env.now - wait_for_lineup_area
-                            self.log_entry(
-                                "Waiting in waiting area start",
-                                wait_for_lineup_area,
-                                0,
-                                nx.get_node_attributes(self.env.FG, "geometry")[origin],
-                            )
-                            self.log_entry(
-                                "Waiting in waiting area stop",
-                                self.env.now,
-                                waiting,
-                                nx.get_node_attributes(self.env.FG, "geometry")[origin],
-                            )
+            # Determines start time of potential waiting time
+            vessel.lock_information[lock.name].distance_to_lock_doors1 = (
+                lineup_area.distance_to_lock_doors + vessel.lock_information[lock.name].lineup_dist
+            )
+            vessel.lock_information[lock.name].distance_to_lock_doors2 = (
+                vessel.lock_information[lock.name].distance_to_lock_doors1 + lock.lock_length
+            )
+            start_waiting_time_in_lineup_area = vessel.env.now
+            priority = lock.check_priority(vessel, direction)
+            lock.schedule.loc[(lock.name, vessel.name), :] = [
+                direction,
+                vessel.env.now + (vessel.lock_information[lock.name].distance_to_lock_doors2 - lock.lock_length) / vessel.v,
+                vessel.env.now + vessel.lock_information[lock.name].distance_to_lock_doors2 / vessel.v,
+                vessel.L,
+                vessel.B,
+                vessel.T,
+                vessel.type,
+                priority,
+            ]
+
+            lock.schedule = lock.schedule.sort_values("ETA")
+            # Request access to lock chamber
+            if direction:
+                yield from access_lock_chamber(
+                    vessel,
+                    direction,
+                    lineup_area,
+                    lock,
+                    lock.node_doors1,
+                    lock.doors_1[lock.node_doors1],
+                    lock.doors_2[lock.node_doors2],
+                    lock.node_doors1,
+                    lock.node_doors2,
+                    waiting_required,
+                )
+                vessel.location_end_of_lineup_area = vessel.env.vessel_traffic_service.provide_location_over_edges(
+                    vessel,
+                    start_node,
+                    lock.node_doors2,
+                    lock.distance_doors1_from_first_waiting_area - lineup_area.distance_to_lock_doors,
+                )
+
+            if not direction:
+                yield from access_lock_chamber(
+                    vessel,
+                    direction,
+                    lineup_area,
+                    lock,
+                    lock.node_doors2,
+                    lock.doors_2[lock.node_doors2],
+                    lock.doors_1[lock.node_doors1],
+                    lock.node_doors2,
+                    lock.node_doors1,
+                    waiting_required,
+                )
+                vessel.location_end_of_lineup_area = vessel.env.vessel_traffic_service.provide_location_over_edges(
+                    vessel,
+                    start_node,
+                    lock.node_doors1,
+                    lock.distance_doors2_from_second_waiting_area - lineup_area.distance_to_lock_doors,
+                )
+
+            priority = lock.check_priority(vessel, direction)
+            lock.schedule.loc[(lock.name, vessel.name), :] = [
+                direction,
+                vessel.env.now + (vessel.lock_information[lock.name].distance_to_lock_doors2 - lock.lock_length) / vessel.v,
+                vessel.env.now + vessel.lock_information[lock.name].distance_to_lock_doors2 / vessel.v,
+                vessel.L,
+                vessel.B,
+                vessel.T,
+                vessel.type,
+                priority,
+            ]
+
+            if vessel.env.now != start_waiting_time_in_lineup_area:
+                vessel.log_entry_extra(
+                    start_waiting_time_in_lineup_area,
+                    vessel.lock_information[lock.name].lineup_position,
+                    "Waiting in line-up area start",
+                    vessel.output.copy(),
+                )
+                vessel.log_entry_extra(
+                    vessel.env.now,
+                    vessel.lock_information[lock.name].lineup_position,
+                    "Waiting in line-up area stop",
+                    vessel.output.copy(),
+                )
+
+            vessel.update_lineup_area_status_report(lineup_area, lineup_area.start_node)
+
+            # Releases the vessel's formal request to access the line-up area and releases its occupied length of the line-up area
+            lineup_area.line_up_area[lineup_area.start_node].release(vessel.lock_information[lock.name].access_lineup_area)
+            lineup_area.length.put(vessel.L)
+
+            vessel.v = vessel.v_before_lock * lock.speed_reduction_factor * lineup_area.speed_reduction_factor
+
+            # Start sailing event to end of line-up area
+            waiting_area = lock.find_previous_waiting_area(vessel, direction)
+
+            if lineup_area.lineup_length:
+                vessel.log_entry_extra(
+                    vessel.env.now,
+                    vessel.lock_information[lock.name].lineup_position,
+                    "Sailing to end of line-up area start",
+                    vessel.output.copy(),
+                )
+                yield vessel.env.timeout(
+                    np.max([0, (lineup_area.lineup_length - vessel.lock_information[lock.name].lineup_dist)]) / vessel.v
+                )
+                if direction:
+                    distance_to_end_lineup = lock.distance_doors1_from_first_waiting_area - lineup_area.distance_to_lock_doors
+                else:
+                    distance_to_end_lineup = lock.distance_doors2_from_second_waiting_area - lineup_area.distance_to_lock_doors
+                location_of_end_of_lineup_area = vessel.env.vessel_traffic_service.provide_location_over_edges(
+                    vessel, waiting_area.node, end_node, distance_to_end_lineup
+                )
+                vessel.log_entry_extra(
+                    vessel.env.now, location_of_end_of_lineup_area, "Sailing to end of line-up area stop", vessel.output.copy()
+                )
+                vessel.distance = (
+                    vessel.env.vessel_traffic_service.provide_trajectory(vessel.multidigraph, end_node, waiting_area.node).length
+                    - distance_to_end_lineup
+                )
+
+    @staticmethod
+    def leave_lock(vessel, node_doors1, node_doors2, direction):
+        """Processes vessels which are waiting in the lock chamber to be levelled and after levelling:
+            checks if vessels which have entered the lock chamber have to wait for the other vessels to enter the lock chamber and
+            requests conversion of the lock chamber.
+
+        Input:
+            - vessel: an identity which is Identifiable, Movable, and Routable, and has VesselProperties
+            - node_lock: a string which includes the name of the node at which the lock chamber is located in the network"""
+
+        # Imports the properties of the lock chamber the vessel is assigned to
+        k = sorted(
+            vessel.multidigraph[node_doors1][node_doors2],
+            key=lambda x: vessel.multidigraph[node_doors1][node_doors2][x]["geometry"].length,
+        )[0]
+        locks = vessel.multidigraph.edges[node_doors1, node_doors2, k]["Lock"]
+        for lock in locks:
+            if lock.name not in vessel.lock_information.keys():
+                continue
+
+            previous_waiting_area = lock.find_previous_waiting_area(vessel, direction)
+            lineup_area = lock.find_previous_lineup_area(vessel, direction)
+            opposing_lineup_area = lock.find_next_lineup_area(vessel, direction)
+
+            # Sail to position in the lock
+            distance_from_start_edge_to_lock_doors = 0
+            vessel.lock_information[lock.name].lock_dist = lock.length.level - 0.5 * vessel.L
+            if direction:
+                if "Line-up area" not in vessel.multidigraph.edges[node_doors1, node_doors2, k]:
+                    distance_from_start_edge_to_lock_doors = (
+                        lock.distance_doors1_from_first_waiting_area
+                        - vessel.env.vessel_traffic_service.provide_trajectory(
+                            vessel.multidigraph, previous_waiting_area.node, lock.node_doors1
+                        ).length
+                    )
+                else:
+                    distance_from_start_edge_to_lock_doors = lineup_area.distance_to_lock_doors
+
+                k = sorted(
+                    lock.multidigraph[lock.node_doors1][lock.node_doors2],
+                    key=lambda x: lock.multidigraph[lock.node_doors1][lock.node_doors2][x]["geometry"].length,
+                )[0]
+                location_first_set_of_lock_doors = vessel.env.vessel_traffic_service.provide_location_over_edges(
+                    vessel, previous_waiting_area.node, lock.node_doors2, lock.distance_doors1_from_first_waiting_area
+                )
+                vessel.lock_information[lock.name].lock_position = lock.multidigraph.edges[lock.node_doors1, lock.node_doors2, k][
+                    "geometry"
+                ].interpolate(lock.distance_doors1_from_first_waiting_area + vessel.lock_information[lock.name].lock_dist)
+
+            if not direction:
+                if "Line-up area" not in vessel.multidigraph.edges[node_doors1, node_doors2, k]:
+                    distance_from_start_edge_to_lock_doors = (
+                        lock.distance_doors2_from_second_waiting_area
+                        - vessel.env.vessel_traffic_service.provide_trajectory(
+                            vessel.multidigraph, previous_waiting_area.node, lock.node_doors2
+                        ).length
+                    )
+                else:
+                    distance_from_start_edge_to_lock_doors = lineup_area.distance_to_lock_doors
+                k = sorted(
+                    lock.multidigraph[lock.node_doors2][lock.node_doors1],
+                    key=lambda x: lock.multidigraph[lock.node_doors2][lock.node_doors1][x]["geometry"].length,
+                )[0]
+                location_first_set_of_lock_doors = vessel.env.vessel_traffic_service.provide_location_over_edges(
+                    vessel, previous_waiting_area.node, lock.node_doors1, lock.distance_doors2_from_second_waiting_area
+                )
+                vessel.lock_information[lock.name].lock_position = lock.multidigraph.edges[lock.node_doors2, lock.node_doors1, k][
+                    "geometry"
+                ].interpolate(lock.distance_doors2_from_second_waiting_area + vessel.lock_information[lock.name].lock_dist)
+
+            vessel.log_entry_extra(
+                vessel.env.now, vessel.logbook[-1]["Geometry"], "Sailing to first set of lock doors start", vessel.output.copy()
+            )
+            yield vessel.env.timeout(np.max([0, distance_from_start_edge_to_lock_doors]) / vessel.v)
+            vessel.log_entry_extra(
+                vessel.env.now, location_first_set_of_lock_doors, "Sailing to first set of lock doors stop", vessel.output.copy()
+            )
+
+            vessel.log_entry_extra(
+                vessel.env.now, location_first_set_of_lock_doors, "Sailing to assigned location in lock start", vessel.output.copy()
+            )
+            yield vessel.env.timeout(np.max([0, (vessel.lock_information[lock.name].lock_dist)]) / vessel.v)
+            vessel.log_entry_extra(
+                vessel.env.now,
+                vessel.lock_information[lock.name].lock_position,
+                "Sailing to assigned location in lock stop",
+                vessel.output.copy(),
+            )
+
+            if lock.mandatory_time_gap_between_entering_vessels is None:
+                if direction:
+                    node_doors2 = lock.node_doors2
+                    yield lock.doors_1[lock.node_doors1].release(vessel.lock_information[lock.name].access_lock_door1)
+                    vessel.distance = vessel.lock_information[lock.name].lock_dist + lock.distance_doors2_from_second_waiting_area
+
+                elif not direction:
+                    node_doors2 = lock.node_doors1
+                    yield lock.doors_2[lock.node_doors2].release(vessel.lock_information[lock.name].access_lock_door1)
+                    vessel.distance = vessel.lock_information[lock.name].lock_dist + lock.distance_doors1_from_first_waiting_area
+
+            # Determines current time and reports this to vessel's log as start time of lock passage
+            vessel.log_entry_extra(
+                vessel.env.now, vessel.lock_information[lock.name].lock_position, "Passing lock start", vessel.output.copy()
+            )
+
+            # Request access to pass the next line-up area after the lock chamber has levelled, so that vessels will leave the lock chamber one-by-one
+            if not opposing_lineup_area.passing_allowed:
+                vessel.lock_information[lock.name].departure_lock = opposing_lineup_area.pass_line_up_area[
+                    opposing_lineup_area.start_node
+                ].request(priority=-1)
+                vessel.lock_information[lock.name].departure_lock.obj = vessel
+
+            # Formal request access to lock chamber and calculate position within the lock chamber
+            for user in lock.in_next_lockage[node_doors2].users:
+                if user.obj.id != vessel.id:
+                    yield lock.length.put(lock.length.capacity - lock.length.level)
+                else:
+                    break
+
+            lock.length.get(vessel.L)
+            vessel.lock_information[lock.name].access_lock = lock.resource.request()
+            vessel.lock_information[lock.name].access_lock.obj = vessel
+            user_found = False
+            remaining_lock_capacity = lock.length.level
+            vessels_not_in_lock = []
+            for user in lock.next_lockage[node_doors2].users:
+                if user_found:
+                    if user.obj.L < remaining_lock_capacity:
+                        vessels_not_in_lock.append(user.obj)
+                        remaining_lock_capacity -= user.obj.L
+                        yield lock.length.get(lock.length.capacity)
+                        lock.length.put(lock.length.capacity)
+                        lock.length.get(vessel.L)
+                        break
+                    else:
                         break
+                if user.obj.id == vessel.id:
+                    user_found = True
+            lock.in_next_lockage[node_doors2].release(vessel.lock_information[lock.name].in_next_lockage)
+
+            if vessels_not_in_lock and not lock.used_as_one_way_traffic_regulation:
+                vessel.lock_information[lock.name].in_lock = True
+                yield lock.length.get(lock.length.capacity)
+                lock.length.put(lock.length.capacity)
+                for lock_user in lock.resource.users:
+                    if lock_user.obj.id != vessel.id:
+                        continue
+                    lock_user.obj.lock_information[lock.name].converting = True
+                    if not opposing_lineup_area.passing_allowed:
+                        yield vessel.lock_information[lock.name].departure_lock
+                if lock.resource.users[-1].obj.id == vessel.id:
+                    lock.length.put(lock.length.capacity - lock.length.level)
+
+            # Determines if the vessel explicitly has to request the conversion of the lock chamber (only the last entered vessel) or can go with a previously made request
+            else:
+                vessel.lock_information[lock.name].in_lock = True
+                vessel.lock_information[lock.name].converting = True
+                number_of_vessels = len(lock.resource.users)
+                if lock.next_lockage_length[node_doors2].level and not lock.next_lockage_length[node_doors2].get_queue:
+                    lock.next_lockage_length[node_doors2].get(lock.next_lockage_length[node_doors2].level)
+                vessel.update_lock_status_report(lock)
+                yield from lock.convert_chamber(vessel.env, node_doors2, number_of_vessels, vessel)
+                old_level = lock.length.level
+                yield lock.length.put(lock.length.capacity - lock.length.level)
+                lock.length.get(lock.length.capacity - old_level)
+                if not opposing_lineup_area.passing_allowed:
+                    yield vessel.lock_information[lock.name].departure_lock
+
+            # Calculates and reports the total locking time
+            vessel.log_entry_extra(
+                vessel.env.now, vessel.lock_information[lock.name].lock_position, "Passing lock stop", vessel.output.copy()
+            )
+            vessel.lock_information[lock.name].lineup_position = vessel.multidigraph.nodes[opposing_lineup_area.end_node][
+                "geometry"
+            ]
+
+            # Sails to the lock doors
+            distance_to_second_pair_of_lock_doors = lock.lock_length - vessel.lock_information[lock.name].lock_dist
+            if direction:
+                k = sorted(
+                    lock.multidigraph[lock.node_doors1][lock.node_doors2],
+                    key=lambda x: lock.multidigraph[lock.node_doors1][lock.node_doors2][x]["geometry"].length,
+                )[0]
+                vessel.lock_information[lock.name].position_second_pair_of_lock_doors = lock.multidigraph.edges[
+                    lock.node_doors1, lock.node_doors2, k
+                ]["geometry"].interpolate(lock.distance_doors2_from_second_waiting_area + lock.lock_length)
+
+            if not direction:
+                k = sorted(
+                    lock.multidigraph[lock.node_doors2][lock.node_doors1],
+                    key=lambda x: lock.multidigraph[lock.node_doors2][lock.node_doors1][x]["geometry"].length,
+                )[0]
+                vessel.lock_information[lock.name].position_second_pair_of_lock_doors = lock.multidigraph.edges[
+                    lock.node_doors2, lock.node_doors1, k
+                ]["geometry"].interpolate(lock.distance_doors1_from_first_waiting_area + lock.lock_length)
+
+            vessel.log_entry_extra(
+                vessel.env.now,
+                vessel.lock_information[lock.name].lock_position,
+                "Sailing to second set of lock doors start",
+                vessel.output.copy(),
+            )
+            yield vessel.env.timeout(np.max([0, (distance_to_second_pair_of_lock_doors)]) / vessel.v)
+            vessel.log_entry_extra(
+                vessel.env.now,
+                vessel.lock_information[lock.name].position_second_pair_of_lock_doors,
+                "Sailing to second set of lock doors stop",
+                vessel.output.copy(),
+            )
 
-        if "Line-up area" in self.env.FG.nodes[destination].keys():
-            locks = self.env.FG.nodes[destination]["Line-up area"]
-            for lock in locks:
-                if lock.name == self.lock_name:
-                    orig = shapely.geometry.Point(self.lineup_pos_lat, self.lineup_pos_lon)
-                    for r in self.route[node_idx:]:
-                        if "Lock" in self.env.FG.nodes[r].keys():
-                            locks = self.env.FG.nodes[r]["Lock"]
-                            for lock2 in locks:
-                                for q in range(len(lock.line_up_area[destination].users)):
-                                    if lock.line_up_area[destination].users[q].id == self.id:
-                                        if self.route[self.route.index(r) - 1] == lock2.node_1:
-                                            if (
-                                                lock2.doors_2[lock2.node_3].users != []
-                                                and lock2.doors_2[lock2.node_3].users[0].priority == -1
-                                            ):
-                                                if q <= 1 and lock.line_up_area[destination].users[q].n != lock.line_up_area[
-                                                    destination
-                                                ].users[q].n - len(lock2.resource.users):
-                                                    self.lineup_dist = lock.length.capacity - 0.5 * self.L
-                                        elif self.route[self.route.index(r) - 1] == lock2.node_3:
-                                            if (
-                                                lock2.doors_1[lock2.node_1].users != []
-                                                and lock2.doors_1[lock2.node_1].users[0].priority == -1
-                                            ):
-                                                if q <= 1 and lock.line_up_area[destination].users[q].n != lock.line_up_area[
-                                                    destination
-                                                ].users[q].n - len(lock2.resource.users):
-                                                    self.lineup_dist = lock.length.capacity - 0.5 * self.L
-                                        [self.lineup_pos_lat, self.lineup_pos_lon, _] = self.wgs84.fwd(
-                                            self.env.FG.nodes[self.route[self.route.index(destination)]]["geometry"].x,
-                                            self.env.FG.nodes[self.route[self.route.index(destination)]]["geometry"].y,
-                                            fwd_azimuth,
-                                            self.lineup_dist,
-                                        )
-                                        lock.line_up_area[destination].users[q].lineup_pos_lat = self.lineup_pos_lat
-                                        lock.line_up_area[destination].users[q].lineup_pos_lon = self.lineup_pos_lon
-                                        lock.line_up_area[destination].users[q].lineup_dist = self.lineup_dist
-                                        break
+            vessel.v = vessel.v_before_lock
+            if opposing_lineup_area.passing_allowed:
+                if direction and lock.doors_2[lock.node_doors2].users[0].obj.id == vessel.id:
+                    lock.doors_2[lock.node_doors2].release(vessel.lock_information[lock.name].access_lock_door2)
 
-        if "Line-up area" in self.env.FG.nodes[origin].keys():
-            locks = self.env.FG.nodes[origin]["Line-up area"]
-            for lock in locks:
-                if lock.name == self.lock_name:
-                    orig = shapely.geometry.Point(self.lineup_pos_lat, self.lineup_pos_lon)
-                    for r in self.route[node_idx:]:
-                        if "Lock" in self.env.FG.nodes[r].keys():
-                            locks = self.env.FG.nodes[r]["Lock"]
-                            lock.enter_line_up_area[origin].release(enter_lineup_length)
-                            for q in range(len(lock.line_up_area[origin].users)):
-                                if lock.line_up_area[origin].users[q].id == self.id:
-                                    if q > 0:
-                                        _, _, distance = self.wgs84.inv(
-                                            orig.x,
-                                            orig.y,
-                                            lock.line_up_area[origin].users[0].lineup_pos_lat,
-                                            lock.line_up_area[origin].users[0].lineup_pos_lon,
-                                        )
-                                        yield self.env.timeout(distance / self.v)
-                                        break
+                if not direction and lock.doors_1[lock.node_doors1].users[0].obj.id == vessel.id:
+                    lock.doors_1[lock.node_doors1].release(vessel.lock_information[lock.name].access_lock_door2)
 
-                            for lock2 in locks:
-                                if lock2.name == self.lock_name:
-                                    self.v = 0.25 * speed
-                                    wait_for_lock_entry = self.env.now
-
-                                    for r2 in self.route[(node_idx + 1) :]:
-                                        if "Line-up area" in self.env.FG.nodes[r2].keys():
-                                            locks = self.env.FG.nodes[r2]["Line-up area"]
-                                            for lock3 in locks:
-                                                if lock3.name == self.lock_name:
-                                                    break
-                                            break
-
-                                    if self.route[self.route.index(r) - 1] == lock2.node_1:
-                                        if len(lock2.doors_2[lock2.node_3].users) != 0:
-                                            if lock2.doors_2[lock2.node_3].users[0].priority == -1:
-                                                if (
-                                                    self.L
-                                                    > (lock2.resource.users[-1].lock_dist - 0.5 * lock2.resource.users[-1].length)
-                                                    or lock2.resource.users[-1].converting
-                                                ):
-                                                    access_lock_door2 = lock2.doors_2[lock2.node_3].request(priority=-1)
-                                                    yield access_lock_door2
-                                                    lock2.doors_2[lock2.node_3].release(access_lock_door2)
-
-                                                    wait_for_next_cycle = lock3.pass_line_up_area[r2].request()
-                                                    yield wait_for_next_cycle
-                                                    lock3.pass_line_up_area[r2].release(wait_for_next_cycle)
-
-                                                if lock.converting_while_in_line_up_area[origin].users != []:
-                                                    waiting_during_converting = lock.converting_while_in_line_up_area[
-                                                        origin
-                                                    ].request(priority=-1)
-                                                    yield waiting_during_converting
-                                                    lock.converting_while_in_line_up_area[origin].release(waiting_during_converting)
-
-                                                elif (
-                                                    len(lock2.doors_1[lock2.node_1].users) == 0
-                                                    or (
-                                                        len(lock2.doors_1[lock2.node_1].users) != 0
-                                                        and lock2.doors_1[lock2.node_1].users[0].priority != -1
-                                                    )
-                                                ) and self.route[self.route.index(r) - 1] != lock2.water_level:
-                                                    waiting_during_converting = lock.converting_while_in_line_up_area[
-                                                        origin
-                                                    ].request()
-                                                    yield waiting_during_converting
-                                                    yield from lock2.convert_chamber(
-                                                        self.env,
-                                                        self.route[self.route.index(r) - 1],
-                                                        0,
-                                                    )
-                                                    lock.converting_while_in_line_up_area[origin].release(waiting_during_converting)
-
-                                                access_lock_door1 = lock2.doors_1[lock2.node_1].request()
-                                                yield access_lock_door1
-
-                                                if (
-                                                    lock2.doors_2[lock2.node_3].users != []
-                                                    and lock2.doors_2[lock2.node_3].users[0].priority == -1
-                                                ):
-                                                    access_lock_door2 = lock2.doors_2[lock2.node_3].request(priority=-1)
-                                                    lock2.doors_2[lock2.node_3].release(lock2.doors_2[lock2.node_3].users[0])
-                                                    yield access_lock_door2
-                                                    lock2.doors_2[lock2.node_3].users[0].id = self.id
-                                                else:
-                                                    access_lock_door2 = lock2.doors_2[lock2.node_3].request(priority=-1)
-                                                    yield access_lock_door2
-                                                    lock2.doors_2[lock2.node_3].users[0].id = self.id
-
-                                            else:
-                                                if lock3.converting_while_in_line_up_area[r2].users != []:
-                                                    waiting_during_converting = lock3.converting_while_in_line_up_area[r2].request()
-                                                    yield waiting_during_converting
-                                                    lock3.converting_while_in_line_up_area[r2].release(waiting_during_converting)
-
-                                                access_lock_door1 = lock2.doors_1[lock2.node_1].request()
-                                                yield access_lock_door1
-
-                                                if (
-                                                    lock2.doors_2[lock2.node_3].users != []
-                                                    and lock2.doors_2[lock2.node_3].users[0].priority == -1
-                                                ):
-                                                    access_lock_door2 = lock2.doors_2[lock2.node_3].request(priority=-1)
-                                                    lock2.doors_2[lock2.node_3].release(lock2.doors_2[lock2.node_3].users[0])
-                                                    yield access_lock_door2
-                                                    lock2.doors_2[lock2.node_3].users[0].id = self.id
-                                                else:
-                                                    access_lock_door2 = lock2.doors_2[lock2.node_3].request(priority=-1)
-                                                    yield access_lock_door2
-                                                    lock2.doors_2[lock2.node_3].users[0].id = self.id
-                                        else:
-                                            if (
-                                                lock2.doors_2[lock2.node_3].users != []
-                                                and lock2.doors_2[lock2.node_3].users[0].priority == -1
-                                            ):
-                                                access_lock_door1 = lock2.doors_1[lock2.node_1].request()
-                                                yield access_lock_door1
-                                                access_lock_door2 = lock2.doors_2[lock2.node_3].request(priority=-1)
-                                                lock2.doors_2[lock2.node_3].release(lock2.doors_2[lock2.node_3].users[0])
-                                                yield access_lock_door2
-                                                lock2.doors_2[lock2.node_3].users[0].id = self.id
-
-                                            elif (
-                                                lock2.doors_2[lock2.node_3].users != []
-                                                and lock2.doors_2[lock2.node_3].users[0].priority == 0
-                                            ):
-                                                access_lock_door1 = lock2.doors_1[lock2.node_1].request()
-                                                yield access_lock_door1
-                                                access_lock_door2 = lock2.doors_2[lock2.node_3].request(priority=-1)
-                                                yield access_lock_door2
-                                                lock2.doors_2[lock2.node_3].users[0].id = self.id
-
-                                            else:
-                                                if lock.converting_while_in_line_up_area[origin].users != []:
-                                                    waiting_during_converting = lock.converting_while_in_line_up_area[
-                                                        origin
-                                                    ].request(priority=-1)
-                                                    yield waiting_during_converting
-                                                    lock.converting_while_in_line_up_area[origin].release(waiting_during_converting)
-                                                    access_lock_door1 = lock2.doors_1[lock2.node_1].request()
-                                                    yield access_lock_door1
-
-                                                elif (
-                                                    len(lock2.doors_1[lock2.node_1].users) == 0
-                                                    or (
-                                                        len(lock2.doors_1[lock2.node_1].users) != 0
-                                                        and lock2.doors_1[lock2.node_1].users[0].priority != -1
-                                                    )
-                                                ) and self.route[self.route.index(r) - 1] != lock2.water_level:
-                                                    access_lock_door1 = lock2.doors_1[lock2.node_1].request()
-                                                    waiting_during_converting = lock.converting_while_in_line_up_area[
-                                                        origin
-                                                    ].request()
-                                                    yield waiting_during_converting
-                                                    yield from lock2.convert_chamber(
-                                                        self.env,
-                                                        self.route[self.route.index(r) - 1],
-                                                        0,
-                                                    )
-                                                    lock.converting_while_in_line_up_area[origin].release(waiting_during_converting)
-
-                                                elif (
-                                                    len(lock2.doors_1[lock2.node_1].users) != 0
-                                                    and lock2.doors_1[lock2.node_1].users[0].priority == -1
-                                                ):
-                                                    access_lock_door1 = lock2.doors_1[lock2.node_1].request()
-                                                    yield access_lock_door1
-
-                                                else:
-                                                    access_lock_door1 = lock2.doors_1[lock2.node_1].request()
-
-                                                if (
-                                                    lock2.doors_2[lock2.node_3].users != []
-                                                    and lock2.doors_2[lock2.node_3].users[0].priority == -1
-                                                ):
-                                                    access_lock_door2 = lock2.doors_2[lock2.node_3].request(priority=-1)
-                                                    lock2.doors_2[lock2.node_3].release(lock2.doors_2[lock2.node_3].users[0])
-                                                    yield access_lock_door2
-                                                    lock2.doors_2[lock2.node_3].users[0].id = self.id
-                                                else:
-                                                    access_lock_door2 = lock2.doors_2[lock2.node_3].request(priority=-1)
-                                                    yield access_lock_door2
-                                                    lock2.doors_2[lock2.node_3].users[0].id = self.id
-
-                                    elif self.route[self.route.index(r) - 1] == lock2.node_3:
-                                        if len(lock2.doors_1[lock2.node_1].users) != 0:
-                                            if lock2.doors_1[lock2.node_1].users[0].priority == -1:
-                                                if (
-                                                    self.L
-                                                    > (lock2.resource.users[-1].lock_dist - 0.5 * lock2.resource.users[-1].length)
-                                                    or lock2.resource.users[-1].converting
-                                                ):
-                                                    access_lock_door1 = lock2.doors_1[lock2.node_1].request(priority=-1)
-                                                    yield access_lock_door1
-                                                    lock2.doors_1[lock2.node_1].release(access_lock_door1)
-
-                                                    wait_for_next_cycle = lock3.pass_line_up_area[r2].request()
-                                                    yield wait_for_next_cycle
-                                                    lock3.pass_line_up_area[r2].release(wait_for_next_cycle)
-
-                                                if lock.converting_while_in_line_up_area[origin].users != []:
-                                                    waiting_during_converting = lock.converting_while_in_line_up_area[
-                                                        origin
-                                                    ].request(priority=-1)
-                                                    yield waiting_during_converting
-                                                    lock.converting_while_in_line_up_area[origin].release(waiting_during_converting)
-
-                                                elif (
-                                                    len(lock2.doors_2[lock2.node_3].users) == 0
-                                                    or (
-                                                        len(lock2.doors_2[lock2.node_3].users) != 0
-                                                        and lock2.doors_2[lock2.node_3].users[0].priority != -1
-                                                    )
-                                                ) and self.route[self.route.index(r) - 1] != lock2.water_level:
-                                                    waiting_during_converting = lock.converting_while_in_line_up_area[
-                                                        origin
-                                                    ].request()
-                                                    yield waiting_during_converting
-                                                    yield from lock2.convert_chamber(
-                                                        self.env,
-                                                        self.route[self.route.index(r) - 1],
-                                                        0,
-                                                    )
-                                                    lock.converting_while_in_line_up_area[origin].release(waiting_during_converting)
-
-                                                access_lock_door2 = lock2.doors_2[lock2.node_3].request()
-                                                yield access_lock_door2
-
-                                                if (
-                                                    lock2.doors_1[lock2.node_1].users != []
-                                                    and lock2.doors_1[lock2.node_1].users[0].priority == -1
-                                                ):
-                                                    access_lock_door1 = lock2.doors_1[lock2.node_1].request(priority=-1)
-                                                    lock2.doors_1[lock2.node_1].release(lock2.doors_1[lock2.node_1].users[0])
-                                                    yield access_lock_door1
-                                                    lock2.doors_1[lock2.node_1].users[0].id = self.id
-                                                else:
-                                                    access_lock_door1 = lock2.doors_1[lock2.node_1].request(priority=-1)
-                                                    yield access_lock_door1
-                                                    lock2.doors_1[lock2.node_1].users[0].id = self.id
-
-                                            else:
-                                                if lock3.converting_while_in_line_up_area[r2].users != []:
-                                                    waiting_during_converting = lock3.converting_while_in_line_up_area[r2].request()
-                                                    yield waiting_during_converting
-                                                    lock3.converting_while_in_line_up_area[r2].release(waiting_during_converting)
-
-                                                access_lock_door2 = lock2.doors_2[lock2.node_3].request()
-                                                yield access_lock_door2
-
-                                                if (
-                                                    lock2.doors_1[lock2.node_1].users != []
-                                                    and lock2.doors_1[lock2.node_1].users[0].priority == -1
-                                                ):
-                                                    access_lock_door1 = lock2.doors_1[lock2.node_1].request(priority=-1)
-                                                    lock2.doors_1[lock2.node_1].release(lock2.doors_1[lock2.node_1].users[0])
-                                                    yield access_lock_door1
-                                                    lock2.doors_1[lock2.node_1].users[0].id = self.id
-                                                else:
-                                                    access_lock_door1 = lock2.doors_1[lock2.node_1].request(priority=-1)
-                                                    yield access_lock_door1
-                                                    lock2.doors_1[lock2.node_1].users[0].id = self.id
-                                        else:
-                                            if (
-                                                lock2.doors_1[lock2.node_1].users != []
-                                                and lock2.doors_1[lock2.node_1].users[0].priority == -1
-                                            ):
-                                                access_lock_door2 = lock2.doors_2[lock2.node_3].request()
-                                                yield access_lock_door2
-                                                access_lock_door1 = lock2.doors_1[lock2.node_1].request(priority=-1)
-                                                lock2.doors_1[lock2.node_1].release(lock2.doors_1[lock2.node_1].users[0])
-                                                yield access_lock_door1
-                                                lock2.doors_1[lock2.node_1].users[0].id = self.id
-
-                                            elif (
-                                                lock2.doors_1[lock2.node_1].users != []
-                                                and lock2.doors_1[lock2.node_1].users[0].priority == 0
-                                            ):
-                                                access_lock_door2 = lock2.doors_2[lock2.node_3].request()
-                                                yield access_lock_door2
-                                                access_lock_door1 = lock2.doors_1[lock2.node_1].request(priority=-1)
-                                                yield access_lock_door1
-                                                lock2.doors_1[lock2.node_1].users[0].id = self.id
-
-                                            else:
-                                                if lock.converting_while_in_line_up_area[origin].users != []:
-                                                    waiting_during_converting = lock.converting_while_in_line_up_area[
-                                                        origin
-                                                    ].request(priority=-1)
-                                                    yield waiting_during_converting
-                                                    lock.converting_while_in_line_up_area[origin].release(waiting_during_converting)
-                                                    access_lock_door2 = lock2.doors_2[lock2.node_3].request()
-                                                    yield access_lock_door2
-
-                                                elif (
-                                                    len(lock2.doors_2[lock2.node_3].users) == 0
-                                                    or (
-                                                        len(lock2.doors_2[lock2.node_3].users) != 0
-                                                        and lock2.doors_2[lock2.node_3].users[0].priority != -1
-                                                    )
-                                                ) and self.route[self.route.index(r) - 1] != lock2.water_level:
-                                                    access_lock_door2 = lock2.doors_2[lock2.node_3].request()
-                                                    waiting_during_converting = lock.converting_while_in_line_up_area[
-                                                        origin
-                                                    ].request()
-                                                    yield waiting_during_converting
-                                                    yield from lock2.convert_chamber(
-                                                        self.env,
-                                                        self.route[self.route.index(r) - 1],
-                                                        0,
-                                                    )
-                                                    lock.converting_while_in_line_up_area[origin].release(waiting_during_converting)
-
-                                                elif (
-                                                    len(lock2.doors_2[lock2.node_3].users) != 0
-                                                    and lock2.doors_2[lock2.node_3].users[0].priority == -1
-                                                ):
-                                                    access_lock_door2 = lock2.doors_2[lock2.node_3].request()
-                                                    yield access_lock_door2
-
-                                                else:
-                                                    access_lock_door2 = lock2.doors_2[lock2.node_3].request()
-
-                                                if (
-                                                    lock2.doors_1[lock2.node_1].users != []
-                                                    and lock2.doors_1[lock2.node_1].users[0].priority == -1
-                                                ):
-                                                    access_lock_door1 = lock2.doors_1[lock2.node_1].request(priority=-1)
-                                                    lock2.doors_1[lock2.node_1].release(lock2.doors_1[lock2.node_1].users[0])
-                                                    yield access_lock_door1
-                                                    lock2.doors_1[lock2.node_1].users[0].id = self.id
-                                                else:
-                                                    access_lock_door1 = lock2.doors_1[lock2.node_1].request(priority=-1)
-                                                    yield access_lock_door1
-                                                    lock2.doors_1[lock2.node_1].users[0].id = self.id
-
-                                    access_lock_length = lock2.length.get(self.L)
-                                    access_lock = lock2.resource.request()
-
-                                    access_lock_pos_length = lock2.pos_length.get(self.L)
-                                    self.lock_dist = lock2.pos_length.level + 0.5 * self.L
-                                    yield access_lock_pos_length
-
-                                    lock2.resource.users[-1].id = self.id
-                                    lock2.resource.users[-1].length = self.L
-                                    lock2.resource.users[-1].lock_dist = self.lock_dist
-                                    lock2.resource.users[-1].converting = False
-                                    if self.route[self.route.index(r) - 1] == lock2.node_1:
-                                        lock2.resource.users[-1].dir = 1.0
-                                    else:
-                                        lock2.resource.users[-1].dir = 2.0
-
-                                    if wait_for_lock_entry != self.env.now:
-                                        waiting = self.env.now - wait_for_lock_entry
-                                        self.log_entry(
-                                            "Waiting in line-up area start",
-                                            wait_for_lock_entry,
-                                            0,
-                                            orig,
-                                        )
-                                        self.log_entry(
-                                            "Waiting in line-up area stop",
-                                            self.env.now,
-                                            waiting,
-                                            orig,
-                                        )
-
-                                    self.wgs84 = pyproj.Geod(ellps="WGS84")
-                                    [doors_origin_lat, doors_origin_lon, doors_destination_lat, doors_destination_lon] = [
-                                        self.env.FG.nodes[self.route[self.route.index(r) - 1]]["geometry"].x,
-                                        self.env.FG.nodes[self.route[self.route.index(r) - 1]]["geometry"].y,
-                                        self.env.FG.nodes[self.route[self.route.index(r) + 1]]["geometry"].x,
-                                        self.env.FG.nodes[self.route[self.route.index(r) + 1]]["geometry"].y,
-                                    ]
-                                    fwd_azimuth, _, distance = self.wgs84.inv(
-                                        doors_origin_lat,
-                                        doors_origin_lon,
-                                        doors_destination_lat,
-                                        doors_destination_lon,
-                                    )
-                                    [self.lock_pos_lat, self.lock_pos_lon, _] = self.wgs84.fwd(
-                                        self.env.FG.nodes[self.route[self.route.index(r) - 1]]["geometry"].x,
-                                        self.env.FG.nodes[self.route[self.route.index(r) - 1]]["geometry"].y,
-                                        fwd_azimuth,
-                                        self.lock_dist,
-                                    )
+                lock.resource.release(vessel.lock_information[lock.name].access_lock)
+                departure_lock_length = lock.length.put(vessel.L)  # put length back in lock
+                yield departure_lock_length
 
-                                    for r4 in reversed(self.route[: (node_idx - 1)]):
-                                        if "Line-up area" in self.env.FG.nodes[r4].keys():
-                                            locks = self.env.FG.nodes[r4]["Line-up area"]
-                                            for lock4 in locks:
-                                                if lock4.name == self.lock_name:
-                                                    lock4.lock_queue_length -= 1
-                            break
+            # Releases the vessel's formal request of the lock chamber and returns its occupied length in the lock chamber
+            lock.resource.release(vessel.lock_information[lock.name].access_lock)
+            lock.length.put(vessel.L)
 
-                        elif "Waiting area" in self.env.FG.nodes[r].keys():
-                            for r2 in reversed(self.route[: (node_idx - 1)]):
-                                if "Lock" in self.env.FG.nodes[r2].keys():
-                                    locks = self.env.FG.nodes[r2]["Lock"]
-                                    for lock2 in locks:
-                                        if lock2.name == self.lock_name:
-                                            if (
-                                                self.route[self.route.index(r2) + 1] == lock2.node_3
-                                                and len(lock2.doors_2[lock2.node_3].users) != 0
-                                                and lock2.doors_2[lock2.node_3].users[0].id == self.id
-                                            ):
-                                                lock2.doors_2[lock2.node_3].release(access_lock_door2)
-                                            elif (
-                                                self.route[self.route.index(r2) + 1] == lock2.node_1
-                                                and len(lock2.doors_1[lock2.node_1].users) != 0
-                                                and lock2.doors_1[lock2.node_1].users[0].id == self.id
-                                            ):
-                                                lock2.doors_1[lock2.node_1].release(access_lock_door1)
-
-                                            if departure_lock is not None:
-                                                lock.pass_line_up_area[origin].release(departure_lock)
-                                            lock2.resource.release(access_lock)
-                                            departure_lock_length = lock2.length.put(self.L)
-                                            departure_lock_pos_length = lock2.pos_length.put(self.L)
-                                            yield departure_lock_length
-                                            yield departure_lock_pos_length
-                                    break
+            if (
+                not lock.resource.users
+                and lock.next_lockage_length[node_doors2].capacity - lock.next_lockage_length[node_doors2].level
+            ):
+                lock.next_lockage_length[node_doors2].put(
+                    lock.next_lockage_length[node_doors2].capacity - lock.next_lockage_length[node_doors2].level
+                )
 
-        node_idx = self.get_node_idx(origin)
-        if "Line-up area" in self.env.FG.nodes[self.route[node_idx - 1]].keys():
-            locks = self.env.FG.nodes[self.route[node_idx - 1]]["Line-up area"]
-            for lock in locks:
-                if lock.name == self.lock_name:
-                    for r in self.route[node_idx:]:
-                        if "Lock" in self.env.FG.nodes[r].keys():
-                            locks = self.env.FG.nodes[r]["Lock"]
-                            lock.line_up_area[self.route[node_idx - 1]].release(access_lineup_area)
-                            departure_lineup_length = lock.length.put(self.L)
-                            yield departure_lineup_length
-
-        if "Lock" in self.env.FG.nodes[origin].keys():
-            locks = self.env.FG.nodes[origin]["Lock"]
-            for lock in locks:
-                if lock.name == self.lock_name:
-                    if self.route[node_idx - 1] == lock.node_1:
-                        lock.doors_1[lock.node_1].release(access_lock_door1)
-                    elif self.route[node_idx - 1] == lock.node_3:
-                        lock.doors_2[lock.node_3].release(access_lock_door2)
-                    orig = shapely.geometry.Point(self.lock_pos_lat, self.lock_pos_lon)
-                    for r2 in reversed(self.route[node_idx:]):
-                        if "Line-up area" in self.env.FG.nodes[r2].keys():
-                            locks = self.env.FG.nodes[r2]["Line-up area"]
-                            for lock3 in locks:
-                                if lock3.name == self.lock_name:
-                                    departure_lock = lock3.pass_line_up_area[r2].request(priority=-1)
-                                    break
-                            break
+            lock.next_lockage[node_doors2].release(vessel.lock_information[lock.name].next_lockage)
 
-                    for r in reversed(self.route[: (node_idx - 1)]):
-                        if "Line-up area" in self.env.FG.nodes[r].keys():
-                            locks = self.env.FG.nodes[r]["Line-up area"]
-                            for lock2 in locks:
-                                if lock2.name == self.lock_name:
-                                    for q2 in range(0, len(lock.resource.users)):
-                                        if lock.resource.users[q2].id == self.id:
-                                            break
+            if opposing_lineup_area.start_node == node_doors2 and not opposing_lineup_area.passing_allowed:
+                yield from PassLock.leave_opposite_lineup_area(vessel, node_doors2, node_doors1, direction)
 
-                                    start_time_in_lock = self.env.now
-                                    self.log_entry("Passing lock start", self.env.now, 0, orig)
+        if direction:
+            vessel.distance = vessel.env.vessel_traffic_service.provide_distance_to_node(
+                vessel, lock.node_doors1, lock.node_doors2, vessel.logbook[-1]["Geometry"]
+            )
 
-                                    if (
-                                        len(lock2.line_up_area[r].users) != 0
-                                        and lock2.line_up_area[r].users[0].length < lock.length.level
-                                    ):
-                                        if self.route[node_idx - 1] == lock.node_1:
-                                            access_line_up_area = lock2.enter_line_up_area[r].request()
-                                            yield access_line_up_area
-                                            lock2.enter_line_up_area[r].release(access_line_up_area)
-                                            access_lock_door1 = lock.doors_1[lock.node_1].request()
-                                            yield access_lock_door1
-                                            lock.doors_1[lock.node_1].release(access_lock_door1)
-
-                                        elif self.route[node_idx - 1] == lock.node_3:
-                                            access_line_up_area = lock2.enter_line_up_area[r].request()
-                                            yield access_line_up_area
-                                            lock2.enter_line_up_area[r].release(access_line_up_area)
-                                            access_lock_door2 = lock.doors_2[lock.node_3].request()
-                                            yield access_lock_door2
-                                            lock.doors_2[lock.node_3].release(access_lock_door2)
-
-                                    if lock.resource.users[0].id == self.id:
-                                        lock.resource.users[0].converting = True
-                                        number_of_vessels = len(lock.resource.users)
-                                        yield from lock.convert_chamber(self.env, destination, number_of_vessels)
-                                    else:
-                                        for u in range(len(lock.resource.users)):
-                                            if lock.resource.users[u].id == self.id:
-                                                lock.resource.users[u].converting = True
-                                                yield self.env.timeout(
-                                                    lock.doors_close + lock.operation_time(self.env) + lock.doors_open
-                                                )
-                                                break
-
-                    yield departure_lock
-
-                    self.log_entry(
-                        "Passing lock stop",
-                        self.env.now,
-                        self.env.now - start_time_in_lock,
-                        orig,
-                    )
-                    [self.lineup_pos_lat, self.lineup_pos_lon] = [
-                        self.env.FG.nodes[self.route[self.route.index(r2)]]["geometry"].x,
-                        self.env.FG.nodes[self.route[self.route.index(r2)]]["geometry"].y,
-                    ]
-                    yield from self.pass_edge(origin, destination)
-                    self.v = speed
+        if not direction:
+            vessel.distance = vessel.env.vessel_traffic_service.provide_distance_to_node(
+                vessel, lock.node_doors2, lock.node_doors1, vessel.logbook[-1]["Geometry"]
+            )
+
+    @staticmethod
+    def leave_opposite_lineup_area(vessel, start_node, end_node, direction):
+        """Processes vessels which have left the lock chamber after levelling and are now in the next line-up area in order to leave the lock complex through the next waiting area:
+            release of their requests for accessing their second encountered line-up area and lock chamber.
+
+        Input:
+            - vessel: an identity which is Identifiable, Movable,and Routable, and has VesselProperties
+            - node_lineup_area: a string which includes the name of the node at which the line-up area is located in the network"""
+
+        # Imports the properties of the line-up area the vessel is assigned to
+        k = sorted(
+            vessel.multidigraph[start_node][end_node], key=lambda x: vessel.multidigraph[start_node][end_node][x]["geometry"].length
+        )[0]
+        lineup_areas = vessel.multidigraph.edges[start_node, end_node, k]["Line-up area"]
+
+        for lineup_area in lineup_areas:
+            if lineup_area.name not in vessel.lock_information.keys():
+                continue
+
+            lock, direction = lineup_area.find_lock(vessel, start_node, end_node, direction=direction)
+
+            if direction:
+                distance_from_lock_doors = lock.distance_doors2_from_second_waiting_area
+            else:
+                distance_from_lock_doors = lock.distance_doors1_from_first_waiting_area
+            distance_to_lineup_end = lineup_area.distance_to_lock_doors
+            location_end_lineup_area = vessel.env.vessel_traffic_service.provide_location_over_edges(
+                vessel, start_node, end_node, distance_from_lock_doors - distance_to_lineup_end
+            )
+            distance_to_lineup_start = distance_to_lineup_end + lineup_area.lineup_length
+            location_start_lineup_area = vessel.env.vessel_traffic_service.provide_location_over_edges(
+                vessel, start_node, end_node, distance_from_lock_doors - distance_to_lineup_start
+            )
+
+            vessel.lock_information[lock.name].in_lock = False
+            if distance_to_lineup_end:
+                vessel.log_entry_extra(
+                    vessel.env.now, vessel.logbook[-1]["Geometry"], "Sailing to line-up area start", vessel.output.copy()
+                )
+                yield vessel.env.timeout(np.max([0, distance_to_lineup_end]) / vessel.v)
+                vessel.log_entry_extra(
+                    vessel.env.now, location_end_lineup_area, "Sailing to line-up area stop", vessel.output.copy()
+                )
+
+            vessel.log_entry_extra(vessel.env.now, location_end_lineup_area, "Passing line-up area start", vessel.output.copy())
+            yield vessel.env.timeout(lineup_area.lineup_length / vessel.v)
+            vessel.log_entry_extra(vessel.env.now, location_start_lineup_area, "Passing line-up area stop", vessel.output.copy())
+            vessel.distance = vessel.env.vessel_traffic_service.provide_distance_to_node(
+                vessel, start_node, end_node, vessel.logbook[-1]["Geometry"]
+            )
+
+            if not lineup_area.passing_allowed:
+                # Releases the vessel's request of their second encountered set of lock doors
+                if (
+                    direction
+                    and lock.doors_2[lock.node_doors2].users
+                    and lock.doors_2[lock.node_doors2].users[0].obj.id == vessel.id
+                ):
+                    lock.doors_2[lock.node_doors2].release(vessel.lock_information[lock.name].access_lock_door2)
+                if (
+                    not direction
+                    and lock.doors_1[lock.node_doors1].users
+                    and lock.doors_1[lock.node_doors1].users[0].obj.id == vessel.id
+                ):
+                    lock.doors_1[lock.node_doors1].release(vessel.lock_information[lock.name].access_lock_door2)
+                # Releases the vessel's request to enter the second line-up area
+                lineup_area.pass_line_up_area[lineup_area.start_node].release(vessel.lock_information[lock.name].departure_lock)
diff --git a/opentnsim/model.py b/opentnsim/model.py
index 78f96503..f4a86b62 100644
--- a/opentnsim/model.py
+++ b/opentnsim/model.py
@@ -4,6 +4,7 @@
 import json
 import logging
 import uuid
+import numbers
 
 # you need these dependencies (you can get these from anaconda)
 # package(s) related to the simulation
@@ -31,7 +32,7 @@ class VesselGenerator:
     """
 
     def __init__(self, vessel_type, vessel_database, loaded=None, random_seed=3):
-        """ Initialization """
+        """Initialization"""
 
         self.vessel_type = vessel_type
         self.vessel_database = vessel_database
@@ -40,11 +41,9 @@ def __init__(self, vessel_type, vessel_database, loaded=None, random_seed=3):
         random.seed(random_seed)
 
     def generate(self, environment, vessel_name, scenario=None):
-        """ Generate a vessel """
+        """Generate a vessel"""
 
-        vessel_info = self.vessel_database.sample(
-            n=1, random_state=int(1000 * random.random())
-        )
+        vessel_info = self.vessel_database.sample(n=1, random_state=int(1000 * random.random()))
         vessel_data = {}
 
         vessel_data["env"] = environment
@@ -70,6 +69,9 @@ def generate(self, environment, vessel_name, scenario=None):
         vessel_data["route"] = None
         vessel_data["geometry"] = None
 
+        # add a unique id to the id, so that our ships are unique even when resampled
+        vessel_data["id"] = f'{vessel_data["id"]}-{uuid.uuid4()}'
+
         return self.vessel_type(**vessel_data)
 
     def arrival_process(
@@ -81,46 +83,37 @@ def arrival_process(
         scenario,
         arrival_process,
     ):
-        """ 
+        """
         Make arrival process
-        
+
         environment:            simpy environment
         arrival_distribution:   specify the distribution from which vessels are generated, int or list
         arrival_process:        process of arrivals
         """
 
         # Create an array with inter-arrival times -- average number of seconds between arrivals
-        if type(arrival_distribution) == int or type(arrival_distribution) == float:
+        if isinstance(arrival_distribution, numbers.Number):
             self.inter_arrival_times = [3600 / arrival_distribution] * 24
 
-        elif type(arrival_distribution) == list and len(arrival_distribution) == 24:
+        elif isinstance(arrival_distribution, list) and len(arrival_distribution) == 24:
             self.inter_arrival_times = [3600 / n for n in arrival_distribution]
 
-        elif type(arrival_distribution) == list:
-            raise ValueError(
-                "List should contain an average number of vessels per hour for an entire day: 24 entries."
-            )
+        elif isinstance(arrival_distribution, list):
+            raise ValueError("List should contain an average number of vessels per hour for an entire day: 24 entries.")
 
         else:
-            raise ValueError(
-                "Specify an arrival distribution: type Integer or type List."
-            )
+            raise ValueError("Specify an arrival distribution: type Integer or type List.")
 
         while True:
-
             # Check simulation time
-            inter_arrival = self.inter_arrival_times[
-                datetime.datetime.fromtimestamp(environment.now).hour
-            ]
+            inter_arrival = self.inter_arrival_times[datetime.datetime.fromtimestamp(environment.now).hour]
 
             # In the case of a Markovian arrival process
             if arrival_process == "Markovian":
-
                 # Make a timestep based on the poisson process
                 yield environment.timeout(random.expovariate(1 / inter_arrival))
 
             elif arrival_process == "Uniform":
-
                 # Make a timestep based on uniform arrivals
                 yield environment.timeout(inter_arrival)
 
@@ -133,10 +126,8 @@ def arrival_process(
             vessel = self.generate(environment, "Vessel", scenario)
             vessel.env = environment
             vessel.route = nx.dijkstra_path(environment.FG, origin, destination)
-            vessel.geometry = nx.get_node_attributes(environment.FG, "geometry")[
-                vessel.route[0]
-            ]
-            
+            vessel.geometry = nx.get_node_attributes(environment.FG, "geometry")[vessel.route[0]]
+
             environment.vessels.append(vessel)
             # Move on path
             environment.process(vessel.move())
@@ -148,14 +139,12 @@ class Simulation(core.Identifiable):
     """
 
     def __init__(self, simulation_start, graph, scenario=None):
-        """ 
-        Initialization 
-        
+        """
+        Initialization
+
         scenario: scenario with vessels - should be coupled to the database
         """
-        self.environment = simpy.Environment(
-            initial_time=time.mktime(simulation_start.timetuple())
-        )
+        self.environment = simpy.Environment(initial_time=time.mktime(simulation_start.timetuple()))
         self.environment.FG = graph
         self.environment.routes = pd.DataFrame.from_dict(
             {
@@ -175,23 +164,23 @@ def add_vessels(
         self,
         origin,
         destination,
-        vessel = None,
-        vessel_generator = None,
+        vessel=None,
+        vessel_generator=None,
         arrival_distribution=1,
         arrival_process="Markovian",
     ):
-        """ 
+        """
         Make arrival process
-        
+
         environment:            simpy environment
         arrival_distribution:   specify the distribution from which vessels are generated, int or list
         arrival_process:        process of arrivals
         """
-        
+
         if vessel_generator == None:
             self.environment.vessels.append(vessel)
             self.environment.process(vessel.move())
-            
+
         else:
             self.environment.process(
                 vessel_generator.arrival_process(
@@ -205,9 +194,9 @@ def add_vessels(
             )
 
     def run(self, duration=24 * 60 * 60):
-        """ 
-        Run the simulation 
-        
+        """
+        Run the simulation
+
         duration:               specify the duration of the simulation in seconds
         """
 
diff --git a/opentnsim/output.py b/opentnsim/output.py
new file mode 100644
index 00000000..b5bb79db
--- /dev/null
+++ b/opentnsim/output.py
@@ -0,0 +1,202 @@
+import datetime
+from copy import deepcopy
+
+import numpy as np
+import pandas as pd
+import pytz
+
+
+class HasOutput:
+    """Support extra output that doesn't fit in the logging module."""
+
+    def __init__(self, *args, **kwargs):
+        self.output = {}
+        super().__init__(*args, **kwargs)
+
+        if self.__class__.__name__ == "Vessel":
+            # Route-dependent output
+            self.output["origin"] = ""
+            self.output["destination"] = ""
+            self.output["route"] = []
+            self.output["bound"] = ""
+            self.output["anchorage"] = ""
+            self.output["turning_basin"] = ""
+            self.output["terminal"] = ""
+            self.output["berth"] = ""
+            self.output["length"] = np.NaN
+            self.output["beam"] = np.NaN
+            self.output["draught"] = np.NaN
+            self.output["sailing_distance"] = np.NaN
+            self.output["sailing_time"] = np.NaN
+            self.output["waiting_times_in_anchorages"] = []
+            self.output["waiting_times_at_terminals"] = []
+            self.output["turning_times"] = []
+            self.output["(un)loading_times"] = []
+
+            # Edge-dependent output
+            self.output["current_node"] = ""
+            self.output["next_node"] = ""
+            self.output["speed"] = np.NaN
+            self.output["heading"] = np.NaN
+            self.output["water_level"] = np.NaN
+            self.output["MBL"] = np.NaN
+            self.output["net_ukc"] = np.NaN
+            self.output["gross_ukc"] = np.NaN
+            self.output["ship_related_ukc_factors"] = {}
+            self.output["limiting current velocity"] = np.NaN
+
+            # Historic output
+            self.output["sailed_routes"] = []
+            self.output["visited_anchorages"] = []
+            self.output["visited_turning_basins"] = []
+            self.output["visited_terminals"] = []
+            self.output["visited_berths"] = []
+            self.output["visited_waiting_areas"] = []
+            self.output["visited_lineup_areas"] = []
+            self.output["visited_lock_chambers"] = []
+
+        elif self.__class__.__name__ == "IsTerminal" or self.__class__.__name__ == "IsJettyTerminal":
+            for berth in [berth.name for berth in self.resource.items]:
+                # Visit-dependent output
+                self.output[berth] = {}
+                self.output[berth]["vessel_information"] = {}
+                self.output[berth]["vessel_arrival"] = pd.NaT
+                self.output[berth]["vessel_departure"] = pd.NaT
+                self.output[berth]["vessel_berthing_times"] = np.NaN
+                self.output[berth]["vessel_(un)loading_time"] = np.NaN
+                self.output[berth]["vessel_waiting_time"] = np.NaN
+                self.output[berth]["visited_vessels"] = []
+
+        elif self.__class__.__name__ == "IsAnchorage":
+            self.output["vessel_information"] = {}
+            self.output["vessel_arrival"] = pd.NaT
+            self.output["vessel_departure"] = pd.NaT
+            self.output["vessel_waiting_time"] = np.NaN
+            self.output["visited_vessels"] = []
+
+        elif self.__class__.__name__ == "IsTurningBasin":
+            self.output["vessel_information"] = {}
+            self.output["vessel_arrival"] = pd.NaT
+            self.output["vessel_departure"] = pd.NaT
+            self.output["vessel_turning_time"] = np.NaN
+            self.output["visited_vessels"] = []
+
+        elif self.__class__.__name__ == "IsLock":
+            self.output["visiting_vessels"] = []
+
+        elif self.__class__.__name__ == "IsLockLineUpArea":
+            self.output["visiting_vessels"] = []
+
+        elif self.__class__.__name__ == "IsLockWaitingArea":
+            self.output["visiting_vessels"] = []
+
+    def update_route_status_report(self, move_stop=False):
+        if not move_stop:
+            self.output["length"] = self.L
+            self.output["beam"] = self.B
+            self.output["draught"] = self.T
+            self.output["route"] = self.route
+            self.output["bound"] = self.bound
+            self.output["sailed_routes"].append(self.route)
+            self.output["origin"] = self.route[0]
+            self.output["destination"] = self.route[-1]
+            self.output["sailing_distance"] = 0.0
+            self.output["sailing_time"] = 0.0
+            for node in self.route:
+                if "Anchorage" in self.env.FG.nodes[node]:
+                    self.output["anchorage"] = self.env.FG.nodes[node]["Anchorage"][0].name
+                if (
+                    "Turning basin" in self.env.FG.nodes[node]
+                    and self.bound == "inbound"
+                    and self.env.FG.nodes[node]["Turning Basin"][0].length <= self.L
+                ):
+                    self.output["turning_basin"] = self.env.FG.nodes[node]["Turning Basin"][0].name
+            if "terminal_of_call" in self.metadata.keys() and self.metadata["terminal_of_call"].size:
+                self.output["terminal"] = self.metadata["terminal_of_call"][0]
+            if "berth_of_call" in self.metadata.keys() and self.metadata["berth_of_call"].size:
+                self.output["berth"] = self.metadata["berth_of_call"][0]
+
+        if move_stop and self.log["Status"]:
+            correction = deepcopy(self.log["Status"])[-1]["sailing_distance"]
+            if "anchorage" in self.output["sailed_routes"][-1]:
+                correction = self.env.vessel_traffic_service.provide_sailing_distance_over_route(
+                    self, self.route_after_anchorage[:-1]
+                )["Distance"].sum()
+            for index, info in enumerate(list(reversed(self.log["Status"]))):
+                index = len(self.log["Status"]) - index - 1
+                if info["route"] != self.output["sailed_routes"][-1]:
+                    break
+                if info["bound"] == "inbound":
+                    self.log["Status"][index]["sailing_distance"] -= correction
+                else:
+                    self.log["Status"][index]["sailing_distance"] = -info["sailing_distance"]
+
+    def update_waiting_area_status_report(self, waiting_area, node_waiting_area):
+        self.output["visited_waiting_areas"].append(waiting_area.name)
+        waiting_area.output["visiting_vessels"] = [user.obj for user in waiting_area.waiting_area[node_waiting_area].users]
+
+    def update_lineup_area_status_report(self, lineup_area, node_lineup_area):
+        self.output["visited_lineup_areas"].append(lineup_area.name)
+        lineup_area.output["visiting_vessels"] = [user.obj for user in lineup_area.line_up_area[node_lineup_area].users]
+
+    def update_lock_status_report(self, lock):
+        self.output["visited_lock_chambers"].append(lock.name)
+        lock.output["visiting_vessels"] = [user.obj for user in lock.resource.users]
+
+    def update_turing_basin_status_report(self, turning_basin, turning_stop=False):
+        turning_basin.output["vessel_information"] = deepcopy(self.output)
+        if not turning_stop:
+            turning_basin.output["vessel_arrival"] = self.env.now
+            turning_basin.output["vessel_departure"] = self.env.now
+            turning_basin.output["vessel_turning_time"] = pd.Timedelta(0, "s")
+        if turning_stop:
+            self.output["visited_turning_basins"].append(turning_basin.name)
+            turning_basin.output["visited_vessels"].append(self.name)
+            turning_basin.output["vessel_departure"] = self.env.now
+            turning_basin.output["vessel_turning_time"] = (
+                deepcopy(turning_basin.output)["vessel_departure"] - deepcopy(turning_basin.output)["vessel_arrival"]
+            )
+            self.output["turning_times"].append(deepcopy(turning_basin.output)["vessel_turning_time"])
+
+    def update_waiting_status(self, priority=False, availability=False, tidal_window=False, waiting_stop=False):
+        if not waiting_stop:
+            self.output["waiting_start"] = self.env.now
+            if "waiting_time" not in self.output.keys():
+                self.output["waiting_time"] = {
+                    "Priority": pd.Timedelta(0, "s"),
+                    "Availability": pd.Timedelta(0, "s"),
+                    "Tidal window": pd.Timedelta(0, "s"),
+                }
+        else:
+            if availability:
+                self.output["waiting_time"]["Availability"] = pd.Timedelta(int(self.env.now - self.output["waiting_start"]), "s")
+            elif tidal_window:
+                self.output["waiting_time"]["Tidal window"] = pd.Timedelta(int(self.env.now - self.output["waiting_start"]), "s")
+            elif priority:
+                self.output["waiting_time"]["Priority"] = pd.Timedelta(int(self.env.now - self.output["waiting_start"]), "s")
+
+    def update_anchorage_status_report(self, anchorage, departure=False):
+        anchorage.output["vessel_information"] = deepcopy(self.output)
+        if not departure:
+            anchorage.output["vessel_arrival"] = self.env.now
+            anchorage.output["vessel_departure"] = self.env.now
+            anchorage.output["vessel_waiting_time"] = deepcopy(self.output)["waiting_time"]
+        if departure:
+            self.output["visited_anchorages"].append(anchorage.name)
+            anchorage.output["visited_vessels"].append(self.name)
+            anchorage.output["vessel_departure"] = self.env.now
+            anchorage.output["vessel_waiting_time"] = deepcopy(self.output)["waiting_time"]
+            self.output["waiting_times_in_anchorages"].append(deepcopy(self.output)["waiting_time"])
+            try:
+                del self.output["waiting_start"]
+                del self.output["waiting_time"]
+            except KeyError:
+                pass
+
+    def update_terminal_berth_status_report(self, terminal, berth):
+        terminal.output[berth.name]["vessel_information"] = deepcopy(self.output)
+
+    def update_sailing_status_report(self, current_node, next_node):
+        self.output["current_node"] = current_node
+        self.output["next_node"] = next_node
+        return deepcopy(self.output)
diff --git a/opentnsim/vessel_traffic_service.py b/opentnsim/vessel_traffic_service.py
new file mode 100644
index 00000000..70012433
--- /dev/null
+++ b/opentnsim/vessel_traffic_service.py
@@ -0,0 +1,1326 @@
+# package(s) related to the simulation
+import numpy as np
+import bisect
+import scipy as sc
+from shapely.geometry import Point, Polygon, MultiPolygon
+import shapely
+import pandas as pd
+from matplotlib import pyplot as plt, dates
+import math
+import networkx as nx
+import time as timepy
+import datetime
+import pytz
+import xarray as xr
+
+from shapely.ops import transform
+
+# spatial libraries
+import pyproj
+
+
+class VesselTrafficService:
+    """Class: a collection of functions that processes requests of vessels regarding the nautical processes on ow to enter the port safely"""
+
+    def __init__(self, hydrodynamic_data=None, vessel_speed_data=None, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        if isinstance(hydrodynamic_data, xr.Dataset):
+            self.hydrodynamic_information = hydrodynamic_data
+        if isinstance(vessel_speed_data, xr.Dataset):
+            self.vessel_speeds = vessel_speed_data
+
+    def provide_waiting_time_for_inbound_tidal_window(self, vessel, route, time_start=None, time_stop=None, delay=0, plot=False):
+        """Function: calculates the time that a vessel has to wait depending on the available tidal windows
+
+        Input:
+            - vessel: an identity which is Identifiable, Movable, and Routeable, and has VesselProperties
+            - route: a list of strings that resemble the route of the vessel (can be different than the vessel.route)
+            - delay: a delay that can be included to calculate a future situation
+            - plot: bool that specifies if a plot is requested or not
+
+        """
+
+        # Create sub-routes based on anchorage areas on the route
+        if not time_start:
+            time_start = pd.Timestamp(datetime.datetime.fromtimestamp(vessel.env.now, tz=pytz.utc)).to_datetime64()
+        if not time_stop:
+            time_stop = pd.Timestamp(
+                datetime.datetime.fromtimestamp(vessel.env.now + vessel.metadata["max_waiting_time"], tz=pytz.utc)
+            ).to_datetime64()
+        _, tidal_windows = self.provide_tidal_windows(vessel, route, time_start, time_stop, delay, plot=plot)
+        for window in tidal_windows:
+            if time_start > window[1]:
+                continue
+            if time_start >= window[0]:
+                waiting_time = pd.Timedelta(0, "s")
+            else:
+                waiting_time = window[0] - time_start
+            break
+
+        waiting_time = waiting_time.total_seconds()
+        return waiting_time
+
+    def provide_waiting_time_for_outbound_tidal_window(self, vessel, route, delay=0, plot=False):
+        vessel.bound = "outbound"
+        vessel._T -= vessel.metadata["(un)loading"][0]
+        waiting_time = self.provide_waiting_time_for_inbound_tidal_window(vessel, route=route, delay=delay, plot=plot)
+        vessel._T += vessel.metadata["(un)loading"][0]
+        vessel.bound = "inbound"
+        return waiting_time
+
+    def provide_speed(self, vessel, edge):
+        if "vessel_speeds" in dir(self) and edge in list(self.vessel_speeds.edge.values):
+            vessel_speed = float(self.vessel_speeds.sel({"edge": edge}).average_speed.values)
+        else:
+            vessel_speed = vessel.v
+        return vessel_speed
+
+    def provide_speed_over_route(self, vessel, route):
+        vessel_speed_over_route = pd.DataFrame(columns=["edge", "Speed"])
+        for idx, (u, v) in enumerate(zip(route[:-1], route[1:])):
+            vessel_speed_over_route.loc[idx, :] = [(u, v), self.provide_speed(vessel, (u, v))]
+        vessel_speed_over_route = vessel_speed_over_route.set_index("edge")
+        return vessel_speed_over_route
+
+    def provide_heading(self, vessel, edge):
+        def reverse_geometry(x, y):
+            return x[::-1], y[::-1]
+
+        distance = []
+        origin_location = vessel.multidigraph.nodes[edge[0]]["geometry"]
+        k = sorted(
+            vessel.multidigraph[edge[0]][edge[1]], key=lambda x: vessel.multidigraph[edge[0]][edge[1]][x]["geometry"].length
+        )[0]
+        edge_geometry = vessel.multidigraph.edges[edge[0], edge[1], k]["Info"]["geometry"]
+        for coord in edge_geometry.coords:
+            distance.append(origin_location.distance(Point(coord)))
+        if np.argmin(distance):
+            edge_geometry = shapely.ops.transform(reverse_geometry, edge_geometry)
+        heading = np.degrees(
+            math.atan2(
+                edge_geometry.coords[0][0] - edge_geometry.coords[-1][0], edge_geometry.coords[0][1] - edge_geometry.coords[-1][1]
+            )
+        )
+        return heading
+
+    @staticmethod
+    def provide_trajectory(multidigraph, node_1, node_2):
+        nodes = nx.dijkstra_path(multidigraph, node_1, node_2)
+        for loc, edge in enumerate(zip(nodes[:-1], nodes[1:])):
+            k = sorted(multidigraph[edge[0]][edge[1]], key=lambda x: multidigraph[edge[0]][edge[1]][x]["geometry"].length)[0]
+            geom = multidigraph.edges[edge[0], edge[1], k]["geometry"]
+            if loc:
+                multi_line = shapely.geometry.MultiLineString([final_geometry, geom])
+                final_geometry = shapely.ops.linemerge(multi_line)
+            else:
+                final_geometry = geom
+        return final_geometry
+
+    def provide_distance_to_node(self, vessel, node_1, node_2, location):
+        geometry = self.provide_trajectory(vessel.multidigraph, node_1, node_2)
+        geometries = shapely.ops.split(shapely.ops.snap(geometry, location, tolerance=100), location).geoms
+        distance = np.round(geometries[-1].length, 1)
+        return distance
+
+    def provide_location_over_edges(self, vessel, node_1, node_2, distance):
+        location = self.provide_trajectory(vessel.multidigraph, node_1, node_2).interpolate(distance)
+        return location
+
+    def provide_sailing_distance(self, vessel, edge):
+        k = sorted(
+            vessel.multidigraph[edge[0]][edge[1]], key=lambda x: vessel.multidigraph[edge[0]][edge[1]][x]["geometry"].length
+        )[0]
+        sailing_distance = [edge, vessel.multidigraph.edges[edge[0], edge[1], k]["Info"]["length"]]
+        return sailing_distance
+
+    def provide_sailing_distance_over_route(self, vessel, route):
+        sailing_distance_over_route = pd.DataFrame(columns=["edge", "Distance"])
+        for idx, (u, v) in enumerate(zip(route[:-1], route[1:])):
+            sailing_distance_over_route.loc[idx, :] = self.provide_sailing_distance(vessel, (u, v))
+        sailing_distance_over_route = sailing_distance_over_route.set_index("edge")
+        return sailing_distance_over_route
+
+    def provide_sailing_time(self, vessel, route, distance=None):
+        if distance is None:
+            sailing_distance_over_route = self.provide_sailing_distance_over_route(vessel, route)
+        else:
+            sailing_distance_over_route = pd.DataFrame(columns=["edge", "distance"])
+            sailing_distance_over_route.loc[0, "edge"] = (route[0], route[1])
+            sailing_distance_over_route.loc[0, "Distance"] = distance
+            sailing_distance_over_route = sailing_distance_over_route.set_index("edge")
+        vessel_speed_over_route = self.provide_speed_over_route(vessel, route)
+        sailing_time_over_route = pd.concat([sailing_distance_over_route, vessel_speed_over_route], axis=1)
+        sailing_time_over_route["Time"] = sailing_time_over_route["Distance"] / sailing_time_over_route["Speed"]
+        return sailing_time_over_route
+
+    def provide_nearest_anchorage_area(self, vessel, node):
+        nodes_of_anchorages = []
+        capacity_of_anchorages = []
+        users_of_anchorages = []
+        sailing_times_to_anchorages = []
+        # Loop over the nodes of the network and identify all the anchorage areas:
+        for node_anchorage in vessel.multidigraph.nodes:
+            if "Anchorage" in vessel.multidigraph.nodes[node_anchorage]:
+                # Determine if the anchorage area can be reached
+                anchorage_reachable = True
+                route_to_anchorage = nx.dijkstra_path(vessel.multidigraph, node, node_anchorage)
+                for node_on_route in route_to_anchorage:
+                    station_index = list(self.hydrodynamic_information["STATION"]).index(node_on_route)
+                    min_water_level = np.min(self.hydrodynamic_information["Water level"][station_index].values)
+                    _, _, _, required_water_depth, _, MBL = self.provide_ukc_clearance(vessel, node)
+                    if min_water_level + MBL < required_water_depth:
+                        anchorage_reachable = False
+                        break
+
+                if not anchorage_reachable:
+                    continue
+
+                # Extract information over the individual anchorage areas: capacity, users, and the sailing distance to the anchorage area from the designated terminal the vessel is planning to call
+                nodes_of_anchorages.append(node_anchorage)
+                capacity_of_anchorages.append(vessel.multidigraph.nodes[node_anchorage]["Anchorage"][0].resource.capacity)
+                users_of_anchorages.append(len(vessel.multidigraph.nodes[node_anchorage]["Anchorage"][0].resource.users))
+                route_from_anchorage = nx.dijkstra_path(vessel.multidigraph, node_anchorage, vessel.route[-1])
+                sailing_time_to_anchorage = vessel.env.vessel_traffic_service.provide_sailing_time(vessel, route_from_anchorage)[
+                    "Time"
+                ].sum()
+                sailing_times_to_anchorages.append(sailing_time_to_anchorage)
+
+        # Sort the lists based on the sailing distance to the anchorage area from the designated terminal the vessel is planning to call
+        sorted_nodes_anchorages = [nodes for (distances, nodes) in sorted(zip(sailing_times_to_anchorages, nodes_of_anchorages))]
+        sorted_users_of_anchorages = [nodes for (distances, nodes) in sorted(zip(sailing_times_to_anchorages, users_of_anchorages))]
+        sorted_capacity_of_anchorages = [
+            nodes for (distances, nodes) in sorted(zip(sailing_times_to_anchorages, capacity_of_anchorages))
+        ]
+
+        # Take the anchorage area that is closest to the designated terminal the vessel is planning to call if there is sufficient capacity:
+        node_anchorage = 0
+        for anchorage_index, node_anchorage in enumerate(sorted_nodes_anchorages):
+            if sorted_users_of_anchorages[anchorage_index] < sorted_capacity_of_anchorages[anchorage_index]:
+                # node anchorage is found
+                break
+
+        return node_anchorage
+
+    def provide_governing_current_velocity(self, vessel, node, time_start_index, time_end_index):
+        station_index = list(self.hydrodynamic_information["STATION"].values).index(node)
+        times = self.hydrodynamic_information["TIME"].values[time_start_index:time_end_index]
+        relative_layer_height = self.hydrodynamic_information["LAYER"].values
+        current_velocity = (
+            self.hydrodynamic_information["Primary current velocity"][station_index]
+            .transpose()
+            .values[time_start_index:time_end_index]
+        )
+
+        def depth_averaged_current_velocity(interpolation_depth, times, relative_layer_height, current_velocity, station_index):
+            layer_boundaries = []
+            average_current_velocity = []
+            number_of_layers = len(relative_layer_height)
+            water_depth = (
+                self.hydrodynamic_information["MBL"][station_index] + self.hydrodynamic_information["Water level"][station_index]
+            )
+            relative_water_depth = water_depth * self.hydrodynamic_information["LAYER"]
+            cumulative_water_depth = relative_water_depth.cumsum("LAYER").values
+
+            for ti in range(len(times)):
+                layer_boundaries.append(
+                    np.interp(interpolation_depth, cumulative_water_depth[ti], np.arange(0, number_of_layers, 1))
+                )
+
+            layer_boundary = np.floor(layer_boundaries)
+            relative_boundary_layer_thickness = layer_boundaries - layer_boundary
+
+            for ti in range(len(times)):
+                if int(layer_boundary[ti]) + 2 < len(relative_layer_height):
+                    rel_layer_heights = relative_layer_height[0 : int(layer_boundary[ti]) + 2].copy()
+                    rel_layer_heights[-1] = rel_layer_heights[-1] * relative_boundary_layer_thickness[ti]
+                    average_current_velocity.append(
+                        np.average(current_velocity[ti][0 : int(layer_boundary[ti]) + 2], weights=rel_layer_heights)
+                    )
+                elif int(layer_boundary[ti]) == 0:
+                    average_current_velocity = current_velocity[ti]
+                else:
+                    average_current_velocity.append(np.average(current_velocity[ti], weights=relative_layer_height))
+
+            return average_current_velocity
+
+        if "LAYER" in list(self.hydrodynamic_information["Current velocity"].dims):
+            if vessel._T <= 5:
+                current_velocity = depth_averaged_current_velocity(5, times, relative_layer_height, current_velocity, station_index)
+            elif vessel._T <= 15:
+                current_velocity = depth_averaged_current_velocity(
+                    vessel._T, times, relative_layer_height, current_velocity, station_index
+                )
+            else:
+                current_velocity = [np.average(current_velocity[t], weights=relative_layer_height) for t in range(len(times))]
+
+        if len(current_velocity) > 2:
+            current_governing_current_velocity = current_velocity[2]
+        else:
+            current_governing_current_velocity = current_velocity[-1]
+        return current_velocity, current_governing_current_velocity
+
+    # Functions used to calculate the sail-in-times for a specific vessel
+    def provide_tidal_window_restriction(self, vessel, route, specifications, node):
+        """Function: determines which tidal window restriction applies to the vessel at the specific node
+
+        Input:
+            - vessel: an identity which is Identifiable, Movable, and Routable, and has VesselProperties
+            - route: a list of strings of node names that resemble the route that the vessel is planning to sail (can be different than vessel.route)
+            - specifications: the specific data regarding the properties for which the restriction holds
+            - node: a string that defines the node of the tidal window restriction
+
+        """
+
+        # Predefined bool
+        boolean = True
+        no_tidal_window = True
+        # Determining if and which restriction applies for the vessel by looping over the restriction class
+        for restriction_class in enumerate(specifications[0]):
+            # - if restriction does not apply to vessel because it is for vessels sailing in the opposite direction: continue loop
+            if vessel.bound != specifications[2][restriction_class[0]]:
+                continue
+            if specifications[5][restriction_class[0]] != [] and (
+                specifications[5][restriction_class[0]][0] not in route or specifications[5][restriction_class[0]][1] not in route
+            ):
+                continue
+            # - else: looping over the restriction criteria
+            for restriction_type in enumerate(restriction_class[1]):
+                # - if previous condition is not met and there are no more restriction criteria and the previous condition has an 'AND' boolean statement: continue loop
+
+                if (
+                    not boolean
+                    and restriction_type[0] == len(restriction_class[1]) - 1
+                    and specifications[4][restriction_class[0]][restriction_type[0] - 1] == "and"
+                ):
+                    continue
+                # - if previous condition is not met and there are more restriction criteria and the next condition has an 'AND' boolean statement: continue loop
+                if (
+                    not boolean
+                    and restriction_type[0] != len(restriction_class[1]) - 1
+                    and specifications[4][restriction_class[0]][restriction_type[0]] == "and"
+                ):
+                    continue
+                # - if previous condition is not met and the next condition has an 'OR' boolean statement: continue loop with predefined boolean
+                if (
+                    not boolean
+                    and restriction_type[0] != len(restriction_class[1]) - 1
+                    and specifications[4][restriction_class[0]][restriction_type[0]] == "or"
+                ):
+                    boolean = True
+                    continue
+
+                # Extracting the correct vessel property for the restriction type
+                if restriction_type[1].find("Length") != -1:
+                    value = getattr(vessel, "L")
+                if restriction_type[1].find("Draught") != -1:
+                    value = getattr(vessel, "T")
+                if restriction_type[1].find("Beam") != -1:
+                    value = getattr(vessel, "B")
+                if restriction_type[1].find("UKC") != -1:
+                    value, _, _, _, _, _ = self.provide_ukc_clearance(vessel, node, delay)
+                if restriction_type[1].find("Type") != -1:
+                    value = getattr(vessel, "type")
+                # Determine if the value for the property satisfies the condition of the restriction type
+                df = pd.DataFrame({"Value": [value], "Restriction": [specifications[1][restriction_class[0]][restriction_type[0]]]})
+                boolean = df.eval("Value" + specifications[3][restriction_class[0]][restriction_type[0]] + "Restriction")[0]
+
+                # - if condition is not met: continue loop
+                if not boolean and restriction_type[0] != len(restriction_class[1]) - 1:
+                    continue
+
+                # - if one of the conditions is met and the restriction contains an 'OR' boolean statement:
+                if (
+                    boolean
+                    and restriction_type[0] != len(restriction_class[1]) - 1
+                    and specifications[-1][restriction_class[0]][restriction_type[0]] == "or"
+                ):
+                    no_tidal_window = False
+                    break
+                # - elif all the conditions are met and the restriction contains solely 'AND' boolean statements:
+                elif boolean and restriction_type[0] == len(restriction_class[1]) - 1:
+                    no_tidal_window = False
+                    break
+
+            # - if condition is met: break the loop
+            if boolean == True:
+                break
+
+            # - else: restart the loop with predefined bool
+            else:
+                boolean = True
+
+        return restriction_class[0], no_tidal_window
+
+    def provide_water_depth(self, vessel, node, delay=0):
+        node_index = list(self.hydrodynamic_information["STATION"].values).index(node)
+        time_index = np.absolute(
+            self.hydrodynamic_information.TIME.values
+            - pd.Timestamp(datetime.datetime.fromtimestamp(vessel.env.now + delay, tz=pytz.utc)).to_datetime64()
+        ).argmin()
+        water_level = self.hydrodynamic_information["Water level"][node_index].values[time_index]
+        MBL = self.hydrodynamic_information["MBL"][node_index].values[time_index]
+        available_water_depth = water_level + MBL
+        return MBL, water_level, available_water_depth
+
+    def provide_ukc_clearance(self, vessel, node, delay=0):
+        """Function: calculates the sail-in-times for a specific vssel with certain properties and a pre-determined route and provides this information to the vessel
+
+        Input:
+            - vessel: an identity which is Identifiable, Movable, and Routable, and has VesselProperties
+            - node:
+            - components_calc:
+
+        """
+        # ignore water_level
+        MBL, _, available_water_depth = self.provide_water_depth(vessel, node, delay)
+
+        ukc_s, ukc_p, ukc_r, fwa = np.zeros(4)
+        if "Vertical tidal restriction" in vessel.multidigraph.nodes[node]["Info"].keys():
+            ukcs_s, ukcs_p, ukcs_r, fwas = vessel.multidigraph.nodes[node]["Info"]["Vertical tidal restriction"]["Type"]
+            specifications = vessel.multidigraph.nodes[node]["Info"]["Vertical tidal restriction"]["Specification"]
+
+            # Determine which restriction applies to vessel
+            restriction_index, _ = self.provide_tidal_window_restriction(vessel, [node], specifications, node)
+
+            # Calculate ukc policy based on the applied restriction
+            ukc_s = ukcs_s[restriction_index]
+            ukc_p = ukcs_p[restriction_index] * vessel.T
+            ukc_r = ukcs_r[restriction_index][0] * (vessel.T - ukcs_r[restriction_index][1])
+            fwa = fwas[restriction_index] * vessel.T
+        ship_related_factors = {"ukc_s": ukc_s, "ukc_p": ukc_p, "ukc_r": ukc_r, "fwa": fwa, "extra_ukc": vessel.metadata["ukc"]}
+        required_water_depth = vessel.T + sum(ship_related_factors.values())
+        net_ukc = available_water_depth - required_water_depth
+        gross_ukc = available_water_depth - vessel.T
+        return net_ukc, gross_ukc, available_water_depth, required_water_depth, ship_related_factors, MBL
+
+    def provide_minimum_available_water_depth_along_route(self, vessel, route, time_start, time_end, delay=0):
+        """Function: calculates the minimum available water depth (predicted/modelled/measured water level minus the local maintained bed level) along the route over time,
+                  subtracted with the difference between the gross ukc and net ukc (hence: subtracted with additional safety margins consisting of vessel-related factors
+                  and water level factors). The bottom-related factors are already accounted for in the use of the MBL instead of the actual depth.
+
+        Input:
+            - vessel: an identity which is Identifiable, Movable, and Routable, and has VesselProperties
+            - route: a list of strings of node names that resemble the route that the vessel is planning to sail (can be different than vessel.route)
+            - delay:
+        """
+        time_start_index = np.max(
+            [
+                0,
+                np.absolute(self.hydrodynamic_information.TIME.values - (time_start + np.timedelta64(int(delay), "s"))).argmin()
+                - 2,
+            ]
+        )
+        time_end_index = np.absolute(
+            self.hydrodynamic_information.TIME.values - (time_end + np.timedelta64(int(delay), "s"))
+        ).argmin()
+        net_ukc = pd.DataFrame()
+        times = self.hydrodynamic_information["TIME"].values[time_start_index:time_end_index]
+        t_step = times[1] - times[0]
+
+        # Start of calculation by looping over the nodes of the route
+        for route_index, node_name in enumerate(route):
+            node_index = list(self.hydrodynamic_information["STATION"].values).index(node_name)
+            sailing_time_to_next_node = vessel.env.vessel_traffic_service.provide_sailing_time(vessel, route[: (route_index + 1)])
+            time_correction_index = int(np.round(sailing_time_to_next_node["Time"].sum() / (t_step / np.timedelta64(1, "s"))))
+            water_level = self.hydrodynamic_information["Water level"][node_index].values[time_start_index:time_end_index]
+            _, _, _, required_water_depth, _, _ = self.provide_ukc_clearance(vessel, node_name, delay)
+            MBL = self.hydrodynamic_information["MBL"][node_index].values[time_start_index:time_end_index]
+            water_depth = water_level + MBL
+            net_ukc = pd.concat(
+                [
+                    net_ukc,
+                    pd.DataFrame(
+                        [available_water_depth - required_water_depth for available_water_depth in water_depth],
+                        index=[t - time_correction_index * t_step for t in times],
+                        columns=[node_name],
+                    ),
+                ],
+                axis=1,
+            )
+
+        # Pick the minimum of the water depths for each time and each node
+        net_ukc = net_ukc.dropna(axis=0)
+        net_ukc["min_net_ukc"] = net_ukc.min(axis=1)
+        return net_ukc
+
+    def provide_vertical_tidal_windows(self, vessel, route, time_start, time_end, delay=0, plot=False):
+        """Function: calculates the windows available to sail-in and -out of the port given the vertical tidal restrictions according to the tidal window policy.
+
+        Input:
+            - vessel: an identity which is Identifiable, Movable, and Routable, and has VesselProperties
+            - route: a list of strings of node names that resemble the route that the vessel is planning to sail (can be different than vessel.route)
+            - sailing_time_correction: a bool that indicates whether the calculation should correct for sailing_speed (dynamic calculation) or not (static calculation)
+
+        """
+        vertical_tidal_accessibility = pd.DataFrame(columns=["Limit", "Accessibility"])
+        net_ukcs = self.provide_minimum_available_water_depth_along_route(vessel, route, time_start, time_end, delay)
+        time_start_index = np.max(
+            [
+                0,
+                np.absolute(self.hydrodynamic_information.TIME.values - (time_start + np.timedelta64(int(delay), "s"))).argmin()
+                - 2,
+            ]
+        )
+        time_end_index = np.absolute(
+            self.hydrodynamic_information.TIME.values - (time_end + np.timedelta64(int(delay), "s"))
+        ).argmin()
+        time_net_ukc = net_ukcs.index.to_numpy()
+        net_ukc = net_ukcs["min_net_ukc"].to_numpy()
+
+        # Determine zero crossinsg
+        zero_crossings = np.nonzero(np.diff(np.sign(net_ukc)))
+        for root in [time_net_ukc[i] for i in zero_crossings]:
+            # -if the net ukc a moment later than the root is higher than the required water depth:
+            if net_ukc[bisect.bisect_right(time_net_ukc, root)] > 0:
+                vertical_tidal_accessibility.loc[root, :] = [0, "Accessible"]
+            elif net_ukc[bisect.bisect_right(time_net_ukc, root)] < 0:
+                vertical_tidal_accessibility.loc[root, :] = [0, "Inaccessible"]
+
+        # Default values
+        if vertical_tidal_accessibility.empty:
+            vertical_tidal_accessibility.loc[np.datetime64(vessel.env.simulation_start.replace(tzinfo=None)), :] = [0, "Accessible"]
+            vertical_tidal_accessibility.loc[np.datetime64(vessel.env.simulation_stop.replace(tzinfo=None)), :] = [
+                0,
+                "Inaccessible",
+            ]
+        else:
+            if vertical_tidal_accessibility.iloc[0].Accessibility == "Inaccessible":
+                vertical_tidal_accessibility.loc[np.datetime64(vessel.env.simulation_start.replace(tzinfo=None)), :] = [
+                    0,
+                    "Accessible",
+                ]
+            else:
+                vertical_tidal_accessibility.loc[np.datetime64(vessel.env.simulation_start.replace(tzinfo=None)), :] = [
+                    0,
+                    "Inaccessible",
+                ]
+            if vertical_tidal_accessibility.iloc[-1].Accessibility == "Inaccessible":
+                vertical_tidal_accessibility.loc[np.datetime64(vessel.env.simulation_stop.replace(tzinfo=None)), :] = [
+                    0,
+                    "Accessible",
+                ]
+            else:
+                vertical_tidal_accessibility.loc[np.datetime64(vessel.env.simulation_stop.replace(tzinfo=None)), :] = [
+                    0,
+                    "Inaccessible",
+                ]
+
+        # Return the sail-in or -out-times given the vertical tidal restrictions over the route of the vessel
+        vertical_tidal_accessibility = vertical_tidal_accessibility.sort_index()
+        vertical_tidal_accessibility["Condition"] = "Water level"
+        vertical_tidal_windows = [
+            [window_start[0], window_end[0]]
+            for window_start, window_end in zip(
+                vertical_tidal_accessibility.iloc[:-1].iterrows(), vertical_tidal_accessibility.iloc[1:].iterrows()
+            )
+            if window_start[1].Accessibility == "Accessible"
+        ]
+
+        if plot:
+            # Create figure
+            fig, ax = plt.subplots(figsize=[16 * 2 / 3, 6])
+
+            # Plot vertical tidal windows
+            for window in vertical_tidal_windows:
+                (vertical_tidal_window,) = ax.fill(
+                    [window[0], window[0], window[1], window[1]],
+                    [-1.5, 1.5, 1.5, -1.5],
+                    facecolor="C0",
+                    alpha=0.25,
+                    edgecolor="none",
+                )
+
+            # Plot net UKC
+            # net_ukc = self.provide_minimum_available_water_depth_along_route(vessel,route,time_start, time_end, delay)
+            (net_UKC,) = ax.plot(net_ukcs["min_net_ukc"], color="C0", linewidth=2)
+            ax.axhline(0, color="k", linewidth=0.5)
+
+            # Figure bounds
+            ax.set_xlim(
+                self.hydrodynamic_information.TIME.values[time_start_index],
+                self.hydrodynamic_information.TIME.values[time_end_index - 36],
+            )
+            ax.set_ylim(-1.5, 1.5)
+
+            # Figure ticks
+            ax.set_xticks(ax.get_xticks())
+            ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha="right")
+            ax.xaxis.set_major_formatter(dates.DateFormatter("%Y-%m-%d %H:%M"))
+
+            # Figure axes
+            ax.set_xlabel("Date")
+            ax.set_ylabel("Net UKC [m]")
+
+            # Legend
+            ax.legend(
+                [net_UKC, vertical_tidal_window],
+                ["Net UKC", "Vertical tidal windows"],
+                frameon=False,
+                loc="upper left",
+                bbox_to_anchor=(1.0, 1.0),
+            )
+            fig.tight_layout()
+
+            plt.show()
+        return vertical_tidal_accessibility, vertical_tidal_windows, net_ukcs
+
+    def provide_horizontal_tidal_windows(self, vessel, route, time_start, time_end, delay=0, plot=False):
+        def calculate_horizontal_tidal_window(
+            vessel,
+            time_start_index,
+            time_end_index,
+            hydrodynamic_data,
+            critical_limits=[],
+            cross_current_limit_dataframe=pd.DataFrame(),
+            flood=True,
+            ebb=True,
+            decreasing=False,
+        ):
+            station = hydrodynamic_data.STATION.values
+            time_start_index = np.max(
+                [
+                    0,
+                    time_start_index
+                    - int(np.timedelta64(12, "h") / (hydrodynamic_data.TIME.values[1] - hydrodynamic_data.TIME.values[0])),
+                ]
+            )
+            currents_time = hydrodynamic_data.TIME.values[time_start_index:time_end_index]
+            currents_data, _ = self.provide_governing_current_velocity(vessel, station, time_start_index, time_end_index)
+            index_prev_root = 0
+            roots = sc.interpolate.CubicSpline(currents_time, currents_data).roots()
+
+            roots_cv = [
+                root for root in roots if root >= currents_time[0].astype(float) and root <= currents_time[-1].astype(float)
+            ]
+            times_horizontal_tidal_period = []
+            for root in roots_cv:
+                root = pd.Timestamp(root).to_datetime64()
+                index_current_root = bisect.bisect_right(currents_time, root) - 2
+                if index_current_root == -1:
+                    index_current_root = index_current_root + 1
+                if len(currents_data[index_prev_root:index_current_root]) == 0:
+                    continue
+                cvel_diff_cross = currents_data[index_current_root + 1] - currents_data[index_current_root - 1]
+                if cvel_diff_cross < 0:
+                    times_horizontal_tidal_period.append([root, "Ebb Start"])
+                    index_prev_root = index_current_root
+                elif cvel_diff_cross > 0:
+                    times_horizontal_tidal_period.append([root, "Flood Start"])
+                    index_prev_root = index_current_root
+
+            tidal_periods = [condition for condition in times_horizontal_tidal_period if condition[0] <= currents_time[-1]]
+            currents_time = np.append(
+                currents_time, np.array([tide[0] for tide in tidal_periods if tide[0] not in currents_time], dtype="datetime64[ns]")
+            )
+            currents_data = [abs(value) for value in currents_data]
+            currents_data = np.append(currents_data, -999 * np.ones(len(tidal_periods)))
+            currents_time, currents_data = [np.array(data) for data in zip(*sorted(zip(currents_time, currents_data)))]
+            # Find the intersection points with critical current velocity
+            current_intersections = []
+            if isinstance(cross_current_limit_dataframe, pd.DataFrame) and not cross_current_limit_dataframe.empty:
+                critical_limit = np.interp(
+                    currents_time.astype("float"),
+                    cross_current_limit_dataframe.index.to_numpy().astype("float"),
+                    cross_current_limit_dataframe.Limit.to_numpy(),
+                )
+                idx = np.argwhere(np.diff(np.sign(critical_limit - currents_data))).flatten()
+                roots = currents_time[idx]
+                current_intersections.extend(roots.astype(dtype="datetime64[ns]"))
+                critical_current_velocity = np.interp(
+                    np.array(current_intersections).astype("float"), currents_time.astype("float"), critical_limit
+                )
+                horizontal_tidal_accessibility = pd.DataFrame(
+                    data=critical_current_velocity, columns=["Limit"], index=current_intersections
+                )
+            elif isinstance(critical_limits, list):
+                critical_current_velocity = []
+                for critical_limit in critical_limits:
+                    idx = np.argwhere(
+                        np.diff(np.sign([current_velocity - critical_limit for current_velocity in currents_data]))
+                    ).flatten()
+                    roots = currents_time[idx]
+                    current_intersections.extend(roots.astype(dtype="datetime64[ns]"))
+                    critical_current_velocity.extend(np.ones(len(roots)) * critical_limit)
+                horizontal_tidal_accessibility = pd.DataFrame(
+                    data=critical_current_velocity, columns=["Limit"], index=current_intersections
+                )
+            horizontal_tidal_accessibility = horizontal_tidal_accessibility.sort_index()
+
+            # Determine the tidal period of the found interpolation points
+            horizontal_tidal_accessibility["Period"] = ""
+            horizontal_tidal_accessibility["Period_nr"] = -999
+            for period_nr, (tidal_period_start, tidal_period_end) in enumerate(zip(tidal_periods[:-1], tidal_periods[1:])):
+                tidal_period = tidal_period_start[1].split(" ")[0]
+                if tidal_period == "Rising":
+                    tidal_period = "Flood"
+                if tidal_period == "Falling":
+                    tidal_period = "Ebb"
+                tidal_period_start = tidal_period_start[0]
+                tidal_period_end = tidal_period_end[0]
+                horizontal_tidal_accessibility.loc[
+                    horizontal_tidal_accessibility[
+                        (horizontal_tidal_accessibility.index >= tidal_period_start)
+                        & (horizontal_tidal_accessibility.index <= tidal_period_end)
+                    ].index,
+                    "Period",
+                ] = tidal_period
+                horizontal_tidal_accessibility.loc[
+                    horizontal_tidal_accessibility[
+                        (horizontal_tidal_accessibility.index >= tidal_period_start)
+                        & (horizontal_tidal_accessibility.index <= tidal_period_end)
+                    ].index,
+                    "Period_nr",
+                ] = period_nr
+            horizontal_tidal_accessibility["Period_nr"] = horizontal_tidal_accessibility["Period_nr"].astype(int)
+
+            # Filter the found interpolation points: remove from errors (multiple interpolated numbers and flood/ebb values if not required)
+            if decreasing:
+                selected_horizontal_tidal_accessibility = pd.DataFrame(columns=horizontal_tidal_accessibility.columns)
+                if flood:
+                    selected_horizontal_tidal_accessibility = pd.concat(
+                        [
+                            selected_horizontal_tidal_accessibility,
+                            horizontal_tidal_accessibility[horizontal_tidal_accessibility.Period == "Flood"],
+                        ]
+                    )
+                if ebb:
+                    selected_horizontal_tidal_accessibility = pd.concat(
+                        [
+                            selected_horizontal_tidal_accessibility,
+                            horizontal_tidal_accessibility[horizontal_tidal_accessibility.Period == "Ebb"],
+                        ]
+                    )
+                horizontal_tidal_accessibility = selected_horizontal_tidal_accessibility.sort_index()
+                horizontal_tidal_accessibility = horizontal_tidal_accessibility.loc[
+                    horizontal_tidal_accessibility.index.drop_duplicates(keep=False)
+                ]
+
+            # Correct the found interpolation points
+            if decreasing:
+                tide_number = horizontal_tidal_accessibility.iloc[0]["Period_nr"]
+                number_of_tidal_periods = horizontal_tidal_accessibility.iloc[-1]["Period_nr"]
+                end_time_windows = []
+                for period_nr in [
+                    idx for idx, count in horizontal_tidal_accessibility.value_counts("Period_nr").items() if count == 2
+                ]:
+                    sub_df = horizontal_tidal_accessibility[horizontal_tidal_accessibility.Period_nr == period_nr]
+                    end_time_windows.append(tidal_periods[period_nr + 1][0] - sub_df.iloc[-1].name)
+                mean_end_time_window = np.mean(end_time_windows)
+                missing_tides = set(list(np.arange(tide_number, number_of_tidal_periods, 2))) - set(
+                    list(dict.fromkeys(horizontal_tidal_accessibility[horizontal_tidal_accessibility.Period == "Flood"].Period_nr))
+                )
+                for tide_index in missing_tides:
+                    starting_time = np.datetime64(tidal_periods[tide_index][0])
+                    closing_time = np.datetime64(tidal_periods[tide_index + 1][0] - mean_end_time_window)
+                    next_index = bisect.bisect_right(currents_time, closing_time)
+                    previous_index = next_index - 1
+                    current_velocity = np.interp(
+                        closing_time,
+                        [currents_time[previous_index], currents_time[next_index]],
+                        [currents_data[previous_index], currents_data[next_index]],
+                    )
+                    horizontal_tidal_accessibility.loc[starting_time, :] = [0, "Flood", tide_index]
+                    horizontal_tidal_accessibility.loc[closing_time, :] = [current_velocity, "Flood", tide_index]
+
+                for period_nr, count in [
+                    (idx, count) for idx, count in horizontal_tidal_accessibility.value_counts("Period_nr").items() if count != 2
+                ]:
+                    if count == 1:
+                        for iloc, (loc, info) in enumerate(
+                            horizontal_tidal_accessibility[horizontal_tidal_accessibility.Period_nr == period_nr].iterrows()
+                        ):
+                            if not iloc % 2 and info.Limit != np.max(critical_limits):
+                                starting_time = np.datetime64(tidal_periods[info.Period_nr][0])
+                                horizontal_tidal_accessibility.loc[starting_time] = info
+                                horizontal_tidal_accessibility.loc[starting_time, "Limit"] = np.max(critical_limits)
+                                break
+                    else:
+                        horizontal_tidal_accessibility = horizontal_tidal_accessibility.drop(
+                            horizontal_tidal_accessibility[
+                                (horizontal_tidal_accessibility.Period_nr == period_nr)
+                                & (horizontal_tidal_accessibility.Limit == np.max(critical_limits))
+                            ].index[:-1]
+                        )
+                        horizontal_tidal_accessibility = horizontal_tidal_accessibility.drop(
+                            horizontal_tidal_accessibility[
+                                (horizontal_tidal_accessibility.Period_nr == period_nr)
+                                & (horizontal_tidal_accessibility.Limit == np.min(critical_limits))
+                            ].index[:-1]
+                        )
+                        if len(horizontal_tidal_accessibility[horizontal_tidal_accessibility.Period_nr == period_nr]) < 2:
+                            for iloc, (loc, info) in enumerate(
+                                horizontal_tidal_accessibility[horizontal_tidal_accessibility.Period_nr == period_nr].iterrows()
+                            ):
+                                if not iloc % 2 and info.Limit != np.max(critical_limits):
+                                    starting_time = np.datetime64(tidal_periods[info.Period_nr][0])
+                                    horizontal_tidal_accessibility.loc[starting_time] = info
+                                    horizontal_tidal_accessibility.loc[starting_time, "Limit"] = np.max(critical_limits)
+                                    break
+
+            else:
+                for period_nr in [
+                    idx for idx, count in horizontal_tidal_accessibility.value_counts("Period_nr").items() if count % 2
+                ]:
+                    for loc, info in horizontal_tidal_accessibility[
+                        horizontal_tidal_accessibility.Period_nr == period_nr
+                    ].iterrows():
+                        if (
+                            currents_data[list(currents_time).index(loc) - 1] < info.Limit
+                            and currents_data[list(currents_time).index(loc) + 1] < info.Limit
+                        ):
+                            horizontal_tidal_accessibility[loc + np.timedelta64(1, "ns")] = info
+                            break
+
+            horizontal_tidal_accessibility = horizontal_tidal_accessibility.sort_index()
+            horizontal_tidal_accessibility = horizontal_tidal_accessibility[horizontal_tidal_accessibility.Period_nr != -999]
+            # Add accessibility information to intersection points
+            if not horizontal_tidal_accessibility.empty:
+                if not decreasing:
+                    horizontal_tidal_accessibility.loc[
+                        list(horizontal_tidal_accessibility.index)[1::2], "Accessibility"
+                    ] = "Accessible"
+                    horizontal_tidal_accessibility.loc[
+                        list(horizontal_tidal_accessibility.index)[::2], "Accessibility"
+                    ] = "Inaccessible"
+                    limiting_currents = np.interp(
+                        horizontal_tidal_accessibility.index.to_numpy().astype(float),
+                        cross_current_limit_dataframe.index.to_numpy().astype(float),
+                        cross_current_limit_dataframe.Limit.to_numpy(),
+                    )
+                    horizontal_tidal_accessibility["Accessibility"] = [
+                        "Accessible" if interpcur < limit else accessibility
+                        for accessibility, interpcur, limit in zip(
+                            horizontal_tidal_accessibility.Accessibility.to_numpy(),
+                            horizontal_tidal_accessibility.Limit.to_numpy(),
+                            limiting_currents,
+                        )
+                    ]
+                    horizontal_tidal_accessibility["Limit"] = [
+                        0 if interpcur < limit else limit
+                        for interpcur, limit in zip(horizontal_tidal_accessibility.Limit.to_numpy(), limiting_currents)
+                    ]
+                    horizontal_tidal_accessibility = horizontal_tidal_accessibility[horizontal_tidal_accessibility.Limit != -1.0]
+                else:
+                    horizontal_tidal_accessibility.loc[
+                        list(horizontal_tidal_accessibility.index)[1::2], "Accessibility"
+                    ] = "Inaccessible"
+                    horizontal_tidal_accessibility.loc[
+                        list(horizontal_tidal_accessibility.index)[::2], "Accessibility"
+                    ] = "Accessible"
+                horizontal_tidal_accessibility["Condition"] = "Current velocity"
+                horizontal_tidal_accessibility = horizontal_tidal_accessibility[["Limit", "Condition", "Accessibility"]]
+            return horizontal_tidal_accessibility, station
+
+        # Start calculation
+        horizontal_tidal_restriction_nodes = []
+        horizontal_tidal_restriction_stations = []
+        window_specifications = []
+        horizontal_tidal_accessibility = pd.DataFrame(columns=["Limit", "Condition", "Accessibility"])
+        horizontal_tidal_window = False
+        for route_index, node_name in enumerate(route):
+            if "Horizontal tidal restriction" in vessel.multidigraph.nodes[node_name]["Info"].keys():
+                horizontal_tidal_window = True
+                sailing_time_to_next_node = self.provide_sailing_time(vessel, route[: (route_index + 1)])
+                specifications = vessel.multidigraph.nodes[node_name]["Info"]["Horizontal tidal restriction"]["Specification"]
+                restriction_index, no_tidal_window = self.provide_tidal_window_restriction(vessel, route, specifications, node_name)
+                if no_tidal_window:
+                    continue
+                hydrodynamic_data = vessel.multidigraph.nodes[node_name]["Info"]["Horizontal tidal restriction"]["Data"][
+                    restriction_index
+                ]
+                cross_current_limit = vessel.multidigraph.nodes[node_name]["Info"]["Horizontal tidal restriction"]["Limit"][
+                    restriction_index
+                ]
+                window_specifications = vessel.multidigraph.nodes[node_name]["Info"]["Horizontal tidal restriction"]["Type"][
+                    restriction_index
+                ]
+                time_start_index = np.max(
+                    [
+                        0,
+                        np.absolute(
+                            self.hydrodynamic_information.TIME.values - (time_start + np.timedelta64(int(delay), "s"))
+                        ).argmin()
+                        - 2,
+                    ]
+                )
+                time_end_index = np.absolute(hydrodynamic_data.TIME.values - (time_end + np.timedelta64(int(delay), "s"))).argmin()
+                if window_specifications.window_method == "Maximum":
+                    next_horizontal_tidal_accessibility, station = calculate_horizontal_tidal_window(
+                        vessel,
+                        time_start_index,
+                        time_end_index,
+                        hydrodynamic_data,
+                        cross_current_limit_dataframe=cross_current_limit,
+                    )
+                if window_specifications.window_method == "Point-based":
+                    if isinstance(window_specifications.current_velocity_values["Flood"], list) and not isinstance(
+                        window_specifications.current_velocity_values["Ebb"], list
+                    ):
+                        next_horizontal_tidal_accessibility, station = calculate_horizontal_tidal_window(
+                            vessel,
+                            time_start_index,
+                            time_end_index,
+                            hydrodynamic_data,
+                            critical_limits=cross_current_limit,
+                            ebb=False,
+                            decreasing=True,
+                        )
+                    elif isinstance(window_specifications.current_velocity_values["Ebb"], list) and not isinstance(
+                        window_specifications.current_velocity_values["Flood"], list
+                    ):
+                        next_horizontal_tidal_accessibility, station = calculate_horizontal_tidal_window(
+                            vessel,
+                            time_start_index,
+                            time_end_index,
+                            hydrodynamic_data,
+                            critical_limits=cross_current_limit,
+                            flood=False,
+                            decreasing=True,
+                        )
+                    else:
+                        next_horizontal_tidal_accessibility, station = calculate_horizontal_tidal_window(
+                            vessel,
+                            time_start_index,
+                            time_end_index,
+                            hydrodynamic_data,
+                            critical_limits=cross_current_limit,
+                            decreasing=True,
+                        )
+
+                horizontal_tidal_restriction_nodes.append(node_name)
+                horizontal_tidal_restriction_stations.append(station)
+                next_horizontal_tidal_accessibility_time_correction = np.timedelta64(
+                    int(sailing_time_to_next_node["Time"].sum()), "s"
+                )
+                next_horizontal_tidal_accessibility.index -= next_horizontal_tidal_accessibility_time_correction
+                if horizontal_tidal_accessibility.empty:
+                    horizontal_tidal_accessibility = next_horizontal_tidal_accessibility
+                else:
+                    horizontal_tidal_accessibility = self.combine_tidal_windows(
+                        horizontal_tidal_accessibility, next_horizontal_tidal_accessibility
+                    )
+
+        if horizontal_tidal_accessibility.empty or not horizontal_tidal_window:
+            horizontal_tidal_accessibility = pd.DataFrame(columns=["Limit", "Condition", "Accessibility"])
+            horizontal_tidal_accessibility.loc[np.datetime64(vessel.env.simulation_start.replace(tzinfo=None)), :] = [
+                0,
+                "Current velocity",
+                "Accessible",
+            ]
+            horizontal_tidal_accessibility.loc[np.datetime64(vessel.env.simulation_stop.replace(tzinfo=None)), :] = [
+                0,
+                "Current velocity",
+                "Inaccessible",
+            ]
+        else:
+            if horizontal_tidal_accessibility.iloc[0].Accessibility == "Inaccessible":
+                horizontal_tidal_accessibility.loc[np.datetime64(vessel.env.simulation_start.replace(tzinfo=None)), :] = [
+                    0,
+                    "Current velocity",
+                    "Accessible",
+                ]
+            else:
+                horizontal_tidal_accessibility.loc[np.datetime64(vessel.env.simulation_start.replace(tzinfo=None)), :] = [
+                    0,
+                    "Current velocity",
+                    "Inaccessible",
+                ]
+            if horizontal_tidal_accessibility.iloc[-1].Accessibility == "Inaccessible":
+                horizontal_tidal_accessibility.loc[np.datetime64(vessel.env.simulation_stop.replace(tzinfo=None)), :] = [
+                    0,
+                    "Current velocity",
+                    "Accessible",
+                ]
+            else:
+                horizontal_tidal_accessibility.loc[np.datetime64(vessel.env.simulation_stop.replace(tzinfo=None)), :] = [
+                    0,
+                    "Current velocity",
+                    "Inaccessible",
+                ]
+
+        horizontal_tidal_accessibility = horizontal_tidal_accessibility.sort_index()
+        horizontal_tidal_windows = [
+            [window_start[0], window_end[0]]
+            for window_start, window_end in zip(
+                horizontal_tidal_accessibility.iloc[:-1].iterrows(), horizontal_tidal_accessibility.iloc[1:].iterrows()
+            )
+            if window_start[1].Accessibility == "Accessible"
+        ]
+
+        if plot:
+            # Create figure
+            fig, ax = plt.subplots(figsize=[16 * 2 / 3, 6])
+
+            # Plot vertical tidal windows
+            for window in horizontal_tidal_windows:
+                (horizontal_tidal_window,) = ax.fill(
+                    [window[0], window[0], window[1], window[1]],
+                    [-1.5, 1.5, 1.5, -1.5],
+                    facecolor="firebrick",
+                    alpha=0.25,
+                    edgecolor="none",
+                )
+
+            # Plot governing current velocity
+            for node, station in zip(horizontal_tidal_restriction_nodes, horizontal_tidal_restriction_stations):
+                governing_current_velocity, _ = self.provide_governing_current_velocity(
+                    vessel, station, time_start_index, time_end_index
+                )
+                horizontal_tidal_accessibility_time_correction = np.timedelta64(
+                    int(self.provide_sailing_time(vessel, route[: (route.index(node) + 1)])["Time"].sum() + delay), "s"
+                )
+                (current_velocity,) = ax.plot(
+                    [
+                        time - horizontal_tidal_accessibility_time_correction
+                        for time in self.hydrodynamic_information.TIME.values[time_start_index:time_end_index]
+                    ],
+                    governing_current_velocity,
+                    color="firebrick",
+                    linewidth=2,
+                )
+            ax.axhline(0, color="k", linewidth=1)
+
+            # Figure bounds
+            ax.set_xlim(
+                self.hydrodynamic_information.TIME.values[time_start_index],
+                self.hydrodynamic_information.TIME.values[time_end_index - 36],
+            )
+            ax.set_ylim(-1.5, 1.5)
+
+            # Figure ticks
+            ax.set_xticks(ax.get_xticks())
+            ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha="right")
+            ax.xaxis.set_major_formatter(dates.DateFormatter("%Y-%m-%d %H:%M"))
+
+            # Figure axes
+            ax.set_xlabel("Date")
+            ax.set_ylabel("Current velocity [m/s]")
+
+            # Legend
+            ax.legend(
+                [current_velocity, horizontal_tidal_window],
+                ["Current velocity", "Horizontal tidal windows"],
+                frameon=False,
+                loc="upper left",
+                bbox_to_anchor=(1.0, 1.0),
+            )
+            fig.tight_layout()
+            plt.show()
+
+        return (
+            horizontal_tidal_accessibility,
+            horizontal_tidal_windows,
+            horizontal_tidal_restriction_nodes,
+            horizontal_tidal_restriction_stations,
+            window_specifications,
+        )
+
+    def combine_tidal_windows(self, tidal_window_1, tidal_window_2):
+        accessibility_1 = tidal_window_1.iloc[
+            [bisect.bisect_right(tidal_window_1.index, index) - 1 for index in tidal_window_2.index]
+        ].Accessibility.to_numpy()
+        accessibility_2 = tidal_window_2.iloc[
+            [bisect.bisect_right(tidal_window_2.index, index) - 1 for index in tidal_window_1.index]
+        ].Accessibility.to_numpy()
+        accessibility_1_interpolated = tidal_window_1.copy()
+        accessibility_2_interpolated = tidal_window_2.copy()
+        for key, value in zip(tidal_window_2.index, accessibility_1):
+            accessibility_1_interpolated.loc[key, "Accessibility"] = value
+        for key, value in zip(tidal_window_1.index, accessibility_2):
+            accessibility_2_interpolated.loc[key, "Accessibility"] = value
+        accessibility_1_interpolated = accessibility_1_interpolated.sort_index()
+        accessibility_2_interpolated = accessibility_2_interpolated.sort_index()
+        tidal_accessibility = pd.concat([accessibility_1_interpolated, accessibility_2_interpolated], axis=1)
+        tidal_accessibility = tidal_accessibility.sort_index()
+        tidal_accessibility_limit = [
+            limit_1 if not math.isnan(limit_1) else limit_2 for limit_1, limit_2 in tidal_accessibility.Limit.to_numpy()
+        ]
+        tidal_accessibility_condition = [
+            condition_1 if isinstance(condition_1, str) else condition_2
+            for condition_1, condition_2 in tidal_accessibility.Condition.to_numpy()
+        ]
+        tidal_accessibility_accessibility = [
+            "Accessible" if accessibility_1 == accessibility_2 and accessibility_1 == "Accessible" else "Inaccessible"
+            for accessibility_1, accessibility_2 in tidal_accessibility.Accessibility.to_numpy()
+        ]
+        tidal_accessibility = tidal_accessibility.drop(["Limit", "Condition", "Accessibility"], axis=1)
+        tidal_accessibility["Limit"] = tidal_accessibility_limit
+        tidal_accessibility["Condition"] = tidal_accessibility_condition
+        tidal_accessibility["Accessibility"] = tidal_accessibility_accessibility
+        accessible_indexes = [
+            idx for idx, accessibility in enumerate((tidal_accessibility.Accessibility == "Accessible").to_numpy()) if accessibility
+        ]
+        inaccessible_indexes = [
+            idx
+            for idx, inaccessibility in enumerate((tidal_accessibility.Accessibility == "Inaccessible").to_numpy())
+            if inaccessibility
+        ]
+        accessible_indexes = np.array(
+            [indexes[-1] for indexes in np.split(accessible_indexes, np.nonzero(np.diff(accessible_indexes) != 1) + 1)], dtype=int
+        )
+        inaccessible_indexes = np.array(
+            [indexes[0] for indexes in np.split(inaccessible_indexes, np.nonzero(np.diff(inaccessible_indexes) != 1) + 1)],
+            dtype=int,
+        )
+        tidal_window_indexes = np.sort(np.append(accessible_indexes, inaccessible_indexes))
+        tidal_accessibility = tidal_accessibility.iloc[tidal_window_indexes]
+        return tidal_accessibility
+
+    def provide_tidal_windows(self, vessel, route, time_start, time_end, ax_left=None, ax_right=None, delay=0, plot=False):
+        time_start_index = np.max(
+            [
+                0,
+                np.absolute(self.hydrodynamic_information.TIME.values - (time_start + np.timedelta64(int(delay), "s"))).argmin()
+                - 2,
+            ]
+        )
+        time_end_index = np.absolute(
+            self.hydrodynamic_information.TIME.values - (time_end + np.timedelta64(int(delay), "s"))
+        ).argmin()
+        vertical_tidal_accessibility, vertical_tidal_windows, net_ukcs = self.provide_vertical_tidal_windows(
+            vessel, route, time_start, time_end, delay
+        )
+        (
+            horizontal_tidal_accessibility,
+            horizontal_tidal_windows,
+            horizontal_tidal_restriction_nodes,
+            horizontal_tidal_restriction_stations,
+            window_specifications,
+        ) = self.provide_horizontal_tidal_windows(vessel, route, time_start, time_end, delay)
+        tidal_accessibility = self.combine_tidal_windows(vertical_tidal_accessibility, horizontal_tidal_accessibility)
+        tidal_windows = [
+            [window_start[0], window_end[0]]
+            for window_start, window_end in zip(tidal_accessibility.iloc[:-1].iterrows(), tidal_accessibility.iloc[1:].iterrows())
+            if window_start[1].Accessibility == "Accessible"
+        ]
+        # Plot
+        if plot:
+            # Create figure
+            if not ax_left:
+                _, ax_left = plt.subplots(figsize=[16 * 2 / 3, 6])
+                ax_right = ax_left.twinx()
+
+            # Plot net UKC
+            # net_ukc = self.provide_minimum_available_water_depth_along_route(vessel, route, time_start, time_end, delay)
+            (net_UKC,) = ax_left.plot(net_ukcs["min_net_ukc"], color="C0", linewidth=2, zorder=1)
+            minimum_required_net_ukc = ax_left.axhline(0, color="C0", linestyle="--", linewidth=2)
+
+            # Plot governing current velocity
+            for node, station in zip(horizontal_tidal_restriction_nodes, horizontal_tidal_restriction_stations):
+                governing_current_velocity, _ = self.provide_governing_current_velocity(
+                    vessel, station, time_start_index, time_end_index
+                )
+                horizontal_tidal_accessibility_time_correction = np.timedelta64(
+                    int(self.provide_sailing_time(vessel, route[: (route.index(node) + 1)])["Time"].sum()), "s"
+                ) + np.timedelta64(15, "m")
+                (current_velocity,) = ax_right.plot(
+                    [
+                        time - horizontal_tidal_accessibility_time_correction
+                        for time in self.hydrodynamic_information.TIME.values[time_start_index:time_end_index]
+                    ],
+                    governing_current_velocity,
+                    color="firebrick",
+                    linewidth=2,
+                    zorder=1,
+                )
+                if window_specifications.window_method == "Maximum":
+                    critical_current_velocity = ax_right.axhline(
+                        window_specifications.current_velocity_values["Flood"], color="firebrick", linestyle="--", linewidth=2
+                    )
+                    ax_right.axhline(
+                        -1 * window_specifications.current_velocity_values["Ebb"], color="firebrick", linestyle="--", linewidth=2
+                    )
+                ax_right.set_ylim(np.floor(np.min(governing_current_velocity)), np.ceil(np.max(governing_current_velocity)))
+
+            # Figure bounds
+            ax_left.set_xlim(
+                self.hydrodynamic_information.TIME.values[time_start_index],
+                self.hydrodynamic_information.TIME.values[time_end_index - 36],
+            )
+            ax_left.set_ylim(
+                np.min([np.floor(np.min(net_ukcs["min_net_ukc"].to_numpy())), -1.0]),
+                np.max([np.ceil(np.max(net_ukcs["min_net_ukc"])), 1.0]),
+            )
+
+            # Calculate vertical and horizontal tidal windows
+            vertical_tidal_window_polygons = []
+            for window in vertical_tidal_windows:
+                vertical_tidal_window_polygons.append(
+                    Polygon(
+                        [
+                            Point((window[0] - np.datetime64("1970-01-01")) / np.timedelta64(1, "s"), ax_left.get_ylim()[0]),
+                            Point((window[0] - np.datetime64("1970-01-01")) / np.timedelta64(1, "s"), ax_left.get_ylim()[1]),
+                            Point((window[1] - np.datetime64("1970-01-01")) / np.timedelta64(1, "s"), ax_left.get_ylim()[1]),
+                            Point((window[1] - np.datetime64("1970-01-01")) / np.timedelta64(1, "s"), ax_left.get_ylim()[0]),
+                        ]
+                    )
+                )
+            horizontal_tidal_window_polygons = []
+            for window in horizontal_tidal_windows:
+                horizontal_tidal_window_polygons.append(
+                    Polygon(
+                        [
+                            Point((window[0] - np.datetime64("1970-01-01")) / np.timedelta64(1, "s"), ax_left.get_ylim()[0]),
+                            Point((window[0] - np.datetime64("1970-01-01")) / np.timedelta64(1, "s"), ax_left.get_ylim()[1]),
+                            Point((window[1] - np.datetime64("1970-01-01")) / np.timedelta64(1, "s"), ax_left.get_ylim()[1]),
+                            Point((window[1] - np.datetime64("1970-01-01")) / np.timedelta64(1, "s"), ax_left.get_ylim()[0]),
+                        ]
+                    )
+                )
+            tidal_window_polygons = []
+            for window in tidal_windows:
+                tidal_window_polygons.append(
+                    Polygon(
+                        [
+                            Point((window[0] - np.datetime64("1970-01-01")) / np.timedelta64(1, "s"), ax_left.get_ylim()[0]),
+                            Point((window[0] - np.datetime64("1970-01-01")) / np.timedelta64(1, "s"), ax_left.get_ylim()[1]),
+                            Point((window[1] - np.datetime64("1970-01-01")) / np.timedelta64(1, "s"), ax_left.get_ylim()[1]),
+                            Point((window[1] - np.datetime64("1970-01-01")) / np.timedelta64(1, "s"), ax_left.get_ylim()[0]),
+                        ]
+                    )
+                )
+
+            if not isinstance(horizontal_tidal_window_polygons, Polygon):
+                horizontal_tidal_window_polygons = MultiPolygon(horizontal_tidal_window_polygons)
+            vertical_tidal_window_polygons = MultiPolygon(vertical_tidal_window_polygons).difference(
+                horizontal_tidal_window_polygons
+            )
+            vertical_tidal_window_polygons = vertical_tidal_window_polygons.difference(MultiPolygon(tidal_window_polygons))
+            if not isinstance(vertical_tidal_window_polygons, Polygon):
+                vertical_tidal_window_polygons = MultiPolygon(vertical_tidal_window_polygons)
+            horizontal_tidal_window_polygons = MultiPolygon(horizontal_tidal_window_polygons).difference(
+                vertical_tidal_window_polygons
+            )
+            horizontal_tidal_window_polygons = horizontal_tidal_window_polygons.difference(MultiPolygon(tidal_window_polygons))
+
+            # Plot vertical tidal windows
+            if not isinstance(vertical_tidal_window_polygons, Polygon):
+                for polygon in vertical_tidal_window_polygons.geoms:
+                    polygon_x = []
+                    for timestamp in polygon.exterior.xy[0]:
+                        polygon_x.append(pd.Timestamp(datetime.datetime.fromtimestamp(timestamp, tz=pytz.utc)))
+                    polygon_y = list(polygon.exterior.xy[1])
+                    (vertical_tidal_window,) = ax_left.fill(
+                        polygon_x, polygon_y, facecolor="C0", alpha=0.25, edgecolor="none", zorder=0
+                    )
+            elif isinstance(vertical_tidal_window_polygons, Polygon):
+                polygon = vertical_tidal_window_polygons
+                polygon_x = []
+                for timestamp in polygon.exterior.xy[0]:
+                    polygon_x.append(pd.Timestamp(datetime.datetime.fromtimestamp(timestamp, tz=pytz.utc)))
+                polygon_y = list(polygon.exterior.xy[1])
+                (vertical_tidal_window,) = ax_left.fill(
+                    polygon_x, polygon_y, facecolor="C0", alpha=0.25, edgecolor="none", zorder=0
+                )
+
+            # Plot horizontal tidal windows
+            if not isinstance(horizontal_tidal_window_polygons, Polygon):
+                for polygon in horizontal_tidal_window_polygons.geoms:
+                    polygon_x = []
+                    for timestamp in polygon.exterior.xy[0]:
+                        polygon_x.append(pd.Timestamp(datetime.datetime.fromtimestamp(timestamp, tz=pytz.utc)))
+                    polygon_y = list(polygon.exterior.xy[1])
+                    (horizontal_tidal_window,) = ax_left.fill(
+                        polygon_x, polygon_y, facecolor="firebrick", alpha=0.25, edgecolor="none", zorder=0
+                    )
+            elif isinstance(horizontal_tidal_window_polygons, Polygon):
+                polygon = horizontal_tidal_window_polygons
+                polygon_x = []
+                for timestamp in polygon.exterior.xy[0]:
+                    polygon_x.append(pd.Timestamp(datetime.datetime.fromtimestamp(timestamp, tz=pytz.utc)))
+                polygon_y = list(polygon.exterior.xy[1])
+                (horizontal_tidal_window,) = ax_left.fill(
+                    polygon_x, polygon_y, facecolor="firebrick", alpha=0.25, edgecolor="none", zorder=0
+                )
+
+            # Plot tidal windows
+            for window in tidal_windows:
+                (tidal_window,) = ax_left.fill(
+                    [window[0], window[0], window[1], window[1]],
+                    [ax_left.get_ylim()[0], ax_left.get_ylim()[1], ax_left.get_ylim()[1], ax_left.get_ylim()[0]],
+                    facecolor="limegreen",
+                    alpha=0.25,
+                    edgecolor="none",
+                    zorder=0,
+                )
+            if not tidal_windows:
+                tidal_window = ax_left.fill(
+                    [0, 0, 0, 0],
+                    [ax_left.get_ylim()[0], ax_left.get_ylim()[1], ax.get_ylim()[1], ax_left.get_ylim()[0]],
+                    facecolor="limegreen",
+                    alpha=0.25,
+                    edgecolor="none",
+                    zorder=0,
+                )
+
+            # Figure ticks
+            ax_left.set_xticks(ax_left.get_xticks())
+            ax_left.set_xticklabels(ax_left.get_xticklabels(), rotation=45, ha="right")
+            ax_left.xaxis.set_major_formatter(dates.DateFormatter("%Y-%m-%d %H:%M"))
+
+            # Figure axes
+            ax_left.set_xlabel("Date")
+            ax_left.set_ylabel("Net UKC [m]")
+            ax_right.set_ylabel("Current velocity [m/s]")
+            # Legend and title
+            if window_specifications:
+                if window_specifications.window_method == "Maximum":
+                    ax_left.legend(
+                        [
+                            net_UKC,
+                            minimum_required_net_ukc,
+                            current_velocity,
+                            critical_current_velocity,
+                            vertical_tidal_window,
+                            horizontal_tidal_window,
+                            tidal_window,
+                        ],
+                        [
+                            "Net UKC",
+                            "Required net UKC",
+                            "Current velocity",
+                            "Vertical tidal windows",
+                            "Horizontal tidal windows",
+                            "Resulting tidal windows",
+                        ],
+                        frameon=False,
+                        loc="upper left",
+                        bbox_to_anchor=(1.05, 1.0),
+                    )
+                else:
+                    ax_left.legend(
+                        [
+                            net_UKC,
+                            minimum_required_net_ukc,
+                            current_velocity,
+                            vertical_tidal_window,
+                            horizontal_tidal_window,
+                            tidal_window,
+                        ],
+                        [
+                            "Net UKC",
+                            "Required net UKC",
+                            "Current velocity",
+                            "Vertical tidal windows",
+                            "Horizontal tidal windows",
+                            "Resulting tidal windows",
+                        ],
+                        frameon=False,
+                        loc="upper left",
+                        bbox_to_anchor=(1.05, 1.0),
+                    )
+            else:
+                ax_left.legend(
+                    [net_UKC, minimum_required_net_ukc, vertical_tidal_window, tidal_window],
+                    ["Net UKC", "Required net UKC", "Vertical tidal windows", "Resulting tidal windows"],
+                    frameon=False,
+                    loc="upper left",
+                    bbox_to_anchor=(1.05, 1.0),
+                )
+            ax_left.set_title(
+                f"Accessibility of {vessel.type}-class vessel with name {vessel.name} and {np.round(vessel.T,2)}m draught and a length of {np.round(vessel.L)}m, sailing {vessel.bound}"
+            )
+            plt.show()
+            return tidal_accessibility, tidal_windows, ax_left, ax_right
+
+        return tidal_accessibility, tidal_windows
diff --git a/setup.cfg b/setup.cfg
index dc0fe43f..4b5cabe0 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -51,6 +51,7 @@ testing =
     pytest
     pytest-cov
     pytest-timeout
+    sphinx
 
 [options.entry_points]
 # Add here console scripts like:
diff --git a/setup.py b/setup.py
index abeb786c..23daf609 100755
--- a/setup.py
+++ b/setup.py
@@ -48,6 +48,7 @@
     # deprecate old functions
     "Deprecated",
     "tqdm",
+    "xarray",
 ]
 
 setup_requirements = [
@@ -75,10 +76,11 @@
     "notebook==6.4",
     "nbconvert==6.4",
     "jupyter",
-    "jupyter-book==0.13.1",
+    "jupyter-book",
     "ipywidgets==7.7",
     "jsonschema==3.0",
     "jupyterlab_widgets==3",
+    "sphinx",
 ]
 
 with open("README.md", "r") as des:


Test patch:
diff --git a/tests/test_lock.py b/tests/test_lock.py
new file mode 100644
index 00000000..92f227cd
--- /dev/null
+++ b/tests/test_lock.py
@@ -0,0 +1,162 @@
+import datetime
+import pytest
+import shapely
+import simpy
+import numpy as np
+import xarray as xr
+
+import opentnsim.core as core
+import opentnsim.output as output
+import opentnsim.vessel_traffic_service
+
+import opentnsim.lock
+import networkx as nx
+
+
+@pytest.fixture
+def graph():
+    FG = nx.DiGraph()
+    Node = type("Site", (core.Identifiable, core.Log, core.Locatable, core.HasResource), {})
+    coordinates = [(0, 0), (1200, 0)]
+    for index, coord in enumerate(coordinates):
+        x, y = coord
+        geometry = shapely.geometry.Point(x, y)
+        node = Node(**{"env": [], "name": index, "geometry": geometry})
+        FG.add_node(node.name, geometry=node.geometry, name=index, Info={"geometry": geometry})
+
+    edges = [(FG.nodes[0], FG.nodes[1]), (FG.nodes[1], FG.nodes[0])]
+    for node_start, node_stop in edges:
+        geometry = shapely.LineString([node_start["geometry"], node_stop["geometry"]])
+
+        FG.add_edge(
+            node_start["name"],
+            node_stop["name"],
+            weight=1,
+            geometry=geometry,
+            Info={"geometry": geometry, "length": geometry.length},
+        )
+
+    # run for a day
+    return FG
+
+
+@pytest.fixture
+def env(graph):
+    t_start = datetime.datetime(2024, 1, 1, 0, 0, 0)
+    t_stop = datetime.datetime(2024, 2, 1, 0, 0, 0)
+    env = simpy.Environment(initial_time=t_start.timestamp())
+
+    env.epoch = t_start
+    env.simulation_start = t_start
+    env.simulation_stop = t_stop
+
+    env.FG = graph
+    # run for a day
+    return env
+
+
+@pytest.fixture
+def hydrodynamics_env(env):
+    t_end = env.simulation_stop
+    stations = list(env.FG.nodes)
+    times = np.arange(env.epoch, t_end, datetime.timedelta(seconds=600))
+    water_depth = [np.linspace(10, 10, len(times)), np.linspace(10, 10, len(times))]
+    water_level = [np.linspace(0, 0, len(times)), np.linspace(1, 1, len(times))]
+    salinity = [np.linspace(0, 0, len(times)), np.linspace(0, 0, len(times))]
+    station_data = xr.DataArray(data=stations, dims=["STATIONS"])
+    time_data = xr.DataArray(data=times, dims=["TIME"])
+    depth_data = xr.DataArray(data=water_depth, dims=["STATIONS", "TIME"])
+    water_level_data = xr.DataArray(data=[wlev for wlev in water_level], dims=["STATIONS", "TIME"])
+    salinity_data = xr.DataArray(data=[sal for sal in salinity], dims=["STATIONS", "TIME"])
+    hydrodynamic_data = xr.Dataset(
+        {"TIME": time_data, "Stations": station_data, "MBL": depth_data, "Water level": water_level_data, "Salinity": salinity_data}
+    )
+    env.vessel_traffic_service = opentnsim.vessel_traffic_service.VesselTrafficService(hydrodynamic_data)
+    return env
+
+
+def create_vessel(env, name, origin, destination, vessel_type, L, B, T, v, arrival_time):
+    Vessel = type(
+        "Vessel",
+        (
+            opentnsim.lock.HasLock,
+            opentnsim.lock.HasLineUpArea,
+            opentnsim.lock.HasWaitingArea,
+            core.Movable,
+            core.VesselProperties,
+            output.HasOutput,
+            opentnsim.lock.CustomLog,
+            core.Identifiable,
+            core.SimpyObject,
+            core.ExtraMetadata,
+        ),
+        {},
+    )
+
+    node = env.FG.nodes[origin]
+    geometry = node["geometry"]
+    vessel = Vessel(
+        **{
+            "env": env,
+            "name": name,
+            "origin": origin,
+            "destination": destination,
+            "geometry": geometry,
+            "node": origin,
+            "route": nx.dijkstra_path(env.FG, origin, destination),
+            "type": vessel_type,
+            "L": L,
+            "B": B,
+            "T": T,
+            "v": v,
+            "arrival_time": arrival_time,
+        }
+    )
+
+    env.process(vessel.move())
+    return vessel
+
+
+@pytest.fixture
+def lock_env(hydrodynamics_env):
+    env = hydrodynamics_env
+    lock = opentnsim.lock.IsLock(
+        env=env,
+        name="Lock",
+        lock_length=400,
+        lock_width=40,
+        lock_depth=10,
+        node_doors1=0,
+        node_doors2=1,
+        distance_doors1_from_first_waiting_area=400,
+        distance_doors2_from_second_waiting_area=400,
+        speed_reduction_factor=1,
+        detector_nodes=[0, 1],
+    )
+    opentnsim.lock.IsLockLineUpArea(
+        env=env, name="Lock", distance_to_lock_doors=0, start_node=0, end_node=1, lineup_length=400, speed_reduction_factor=1
+    )
+    opentnsim.lock.IsLockLineUpArea(
+        env=env, name="Lock", distance_to_lock_doors=0, start_node=1, end_node=0, lineup_length=400, speed_reduction_factor=1
+    )
+    opentnsim.lock.IsLockWaitingArea(env=env, name="Lock", distance_from_node=0, node=0)
+    opentnsim.lock.IsLockWaitingArea(env=env, name="Lock", distance_from_node=0, node=1)
+
+    env.lock = lock
+    return env
+
+
+def test_is_lock(lock_env):
+    env = lock_env
+    lock = env.lock
+    assert hasattr(lock, "next_lockage_length"), "lock should have next_lockage_length"
+
+
+def test_sail_through_lock(lock_env):
+    env = lock_env
+
+    vessel = create_vessel(env, 0, 0, 1, None, 200, 30, 8, v=4, arrival_time=datetime.datetime(2024, 1, 1, 0, 0, 0))
+
+    env.run()
+
+    assert len(vessel.logbook) > 2
diff --git a/tests/test_output.py b/tests/test_output.py
new file mode 100644
index 00000000..c4782156
--- /dev/null
+++ b/tests/test_output.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python3
+
+import pytest
+import simpy
+import opentnsim.output
+
+
+def test_output():
+    Vessel = type("Vessel", (opentnsim.output.HasOutput,), {})
+    vessel = Vessel()
+
+    assert hasattr(vessel, "output"), "Vessel should have an output attribute"