diff --git a/notebooks/corner.ipynb b/notebooks/corner.ipynb
index a4d4397afe..a19e74cfb2 100644
--- a/notebooks/corner.ipynb
+++ b/notebooks/corner.ipynb
@@ -9,9 +9,9 @@
}
},
"source": [
- "# Simple Corner Simulation\n",
+ "# Movement around Corners\n",
"\n",
- "In the following we'll investigate the movement of pedestrians around corners. When pedestrians walk around corners they are expected to slow down and take a path that is close to the corner. According to RiMEA Test 6 **[TODO REF]** a scenario is configured where **20 agents** move towards a **corner** at which they should turn to the left.\n",
+ "In the following we'll investigate the movement of pedestrians around corners. When pedestrians walk around corners they are expected to slow down and take a path that is close to the corner. According to RiMEA Test 6 [1] a scenario is configured where **20 agents** move towards a **corner** at which they should turn to the left.\n",
"\n",
"Let's begin by importing the required packages for our simulation:"
]
@@ -38,15 +38,17 @@
"from shapely import GeometryCollection, Polygon, to_wkt\n",
"import pathlib\n",
"import jupedsim as jps\n",
+ "import jupedsim.distributions # for spawning positions\n",
+ "from numpy.random import normal # normal distribution of free movement speed\n",
"import pedpy\n",
"import pandas as pd\n",
"import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
"import plotly # visualise trajectories\n",
"import plotly.express as px\n",
"import plotly.graph_objects as go\n",
"from plotly.graph_objs import Figure\n",
- "import sqlite3\n",
- "from numpy.random import normal # normal distribution of free movement speed"
+ "import sqlite3"
]
},
{
@@ -60,7 +62,7 @@
"source": [
"## Setting up the Geometry\n",
"\n",
- "We define a corridor with a width of 2 meters and a corner on halfway:"
+ "According to the RiMEA Test we define a corridor with a width of 2 meters and a corner on halfway:"
]
},
{
@@ -82,7 +84,8 @@
"outputs": [],
"source": [
"area = Polygon([(0, 0), (12, 0), (12, 12), (10, 12), (10, 2), (0, 2)])\n",
- "area"
+ "walkable_area = pedpy.WalkableArea(area)\n",
+ "pedpy.plot_walkable_area(walkable_area=walkable_area).set_aspect('equal')"
]
},
{
@@ -112,10 +115,10 @@
"source": [
"spawning_area = Polygon([(0, 0), (6, 0), (6, 2), (0, 2)])\n",
"num_agents = 20\n",
- "positions = jps.distributions.distribute_by_number(\n",
+ "pos_in_spawning_area = jps.distributions.distribute_by_number(\n",
" polygon=spawning_area,\n",
" number_of_agents=num_agents,\n",
- " distance_to_agents=0.4,\n",
+ " distance_to_agents=0.3,\n",
" distance_to_polygon=0.15,\n",
" seed=1,\n",
")\n",
@@ -124,14 +127,50 @@
},
{
"cell_type": "markdown",
- "id": "40bee00b-7b0d-46d7-914d-d88bea7bc39d",
+ "id": "badcdfb6-ffd5-4a78-a3cf-fedc137f5732",
+ "metadata": {},
+ "source": [
+ "Let's have a look at the basic simulation setup. The spawning area is shown in light grey, the agents in blue and the exit area in dark grey:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "073c2dab-542e-4ad7-9a94-7988ffa1b15f",
"metadata": {
- "pycharm": {
- "name": "#%% md\n"
- }
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": [
+ "hide-input"
+ ]
},
+ "outputs": [],
+ "source": [
+ "def plot_simulation_configuration(walkable_area, spawning_area, starting_positions, exit_area):\n",
+ " axes = plt.gca()\n",
+ " axes.plot(\n",
+ " *walkable_area.polygon.exterior.xy,\n",
+ " color='black',\n",
+ " )\n",
+ " axes.fill(*spawning_area.exterior.xy,\n",
+ " color='lightgrey')\n",
+ " axes.fill(*exit_area.exterior.xy, color='darkgrey')\n",
+ " axes.scatter(*zip(*starting_positions))\n",
+ " axes.set_xlabel('x/m')\n",
+ " axes.set_ylabel('y/m')\n",
+ " axes.set_aspect('equal') "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a840a3be-8759-4058-9e37-3b2cc483f0c3",
+ "metadata": {},
+ "outputs": [],
"source": [
- "**TODO: plot config setup - geo + start positions + exit**"
+ "plot_simulation_configuration(walkable_area, spawning_area, pos_in_spawning_area, exit_area)"
]
},
{
@@ -161,7 +200,7 @@
"source": [
"trajectory_file = \"corner.sqlite\" # output file\n",
"simulation = jps.Simulation(\n",
- " model=jps.CollisionFreeSpeedModel(),\n",
+ " model=jps.VelocityModelParameters(),\n",
" geometry=area,\n",
" trajectory_writer=jps.SqliteTrajectoryWriter(\n",
" output_file=pathlib.Path(trajectory_file)\n",
@@ -210,6 +249,13 @@
},
"outputs": [],
"source": [
+ "agent_parameters = jps.VelocityModelAgentParameters()\n",
+ "agent_parameters.journey_id = journey_id\n",
+ "agent_parameters.stage_id = exit_id\n",
+ "agent_parameters.radius = 0.15\n",
+ "agent_parameters.time_gap = 1\n",
+ "agent_parameters.tau = 0.5\n",
+ "\n",
"v_distribution = normal(1.34, 0.2, num_agents)"
]
},
@@ -238,12 +284,10 @@
},
"outputs": [],
"source": [
- "for position, v0 in zip(positions, v_distribution):\n",
- " simulation.add_agent(\n",
- " jps.CollisionFreeSpeedModelAgentParameters(\n",
- " journey_id=journey_id, stage_id=exit_id, position=position, v0=v0\n",
- " )\n",
- " )\n",
+ "for i in range(num_agents):\n",
+ " agent_parameters.position = pos_in_spawning_area[i]\n",
+ " agent_parameters.v0 = v_distribution[i]\n",
+ " simulation.add_agent(agent_parameters)\n",
"\n",
"while simulation.agent_count() > 0:\n",
" simulation.iterate()"
@@ -261,6 +305,286 @@
"## Visualizing the Trajectories\n"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5379718a-4af8-470b-8cf0-b4a6dd6f7193",
+ "metadata": {
+ "pycharm": {
+ "name": "#%%\n"
+ },
+ "tags": [
+ "hide-input"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "def read_sqlite_file(\n",
+ " trajectory_file: str,\n",
+ ") -> (pedpy.TrajectoryData, pedpy.WalkableArea):\n",
+ " with sqlite3.connect(trajectory_file) as con:\n",
+ " data = pd.read_sql_query(\n",
+ " \"select frame, id, pos_x as x, pos_y as y, ori_x as ox, ori_y as oy from trajectory_data\",\n",
+ " con,\n",
+ " )\n",
+ " fps = float(\n",
+ " con.cursor()\n",
+ " .execute(\"select value from metadata where key = 'fps'\")\n",
+ " .fetchone()[0]\n",
+ " )\n",
+ " walkable_area = (\n",
+ " con.cursor().execute(\"select wkt from geometry\").fetchone()[0]\n",
+ " )\n",
+ " return (\n",
+ " pedpy.TrajectoryData(data=data, frame_rate=fps),\n",
+ " pedpy.WalkableArea(walkable_area),\n",
+ " )\n",
+ "\n",
+ "\n",
+ "def speed_to_color(speed, min_speed, max_speed, midpoint):\n",
+ " colorscale = px.colors.diverging.RdBu_r[::-1]\n",
+ "\n",
+ " # Normalize speed based on the midpoint\n",
+ " if speed >= midpoint:\n",
+ " normalized_speed = 0.5 + 0.5 * (speed - midpoint) / (\n",
+ " max_speed - midpoint\n",
+ " )\n",
+ " else:\n",
+ " normalized_speed = 0.5 * (speed - min_speed) / (midpoint - min_speed)\n",
+ "\n",
+ " # Clip to ensure the value is between 0 and 1\n",
+ " normalized_speed = np.clip(normalized_speed, 0, 1)\n",
+ "\n",
+ " # Find the corresponding color in the colorscale\n",
+ " color_idx = int(normalized_speed * (len(colorscale) - 1))\n",
+ " return colorscale[color_idx]\n",
+ "\n",
+ "\n",
+ "def get_geometry_traces(area):\n",
+ " geometry_traces = []\n",
+ " x, y = area.exterior.xy\n",
+ " geometry_traces.append(\n",
+ " go.Scatter(\n",
+ " x=np.array(x),\n",
+ " y=np.array(y),\n",
+ " mode=\"lines\",\n",
+ " line={\"color\": \"grey\"},\n",
+ " showlegend=False,\n",
+ " name=\"Exterior\",\n",
+ " hoverinfo=\"name\",\n",
+ " )\n",
+ " )\n",
+ " for inner in area.interiors:\n",
+ " xi, yi = zip(*inner.coords[:])\n",
+ " geometry_traces.append(\n",
+ " go.Scatter(\n",
+ " x=np.array(xi),\n",
+ " y=np.array(yi),\n",
+ " mode=\"lines\",\n",
+ " line={\"color\": \"grey\"},\n",
+ " showlegend=False,\n",
+ " name=\"Obstacle\",\n",
+ " hoverinfo=\"name\",\n",
+ " )\n",
+ " )\n",
+ " return geometry_traces\n",
+ "\n",
+ "\n",
+ "def get_shapes_for_frame(frame_data, min_speed, max_speed, midpoint):\n",
+ " def create_shape(row):\n",
+ " hover_trace = go.Scatter(\n",
+ " x=[row[\"x\"]],\n",
+ " y=[row[\"y\"]],\n",
+ " text=[f\"ID: {row['id']}, Pos({row['x']:.2f},{row['y']:.2f})\"],\n",
+ " mode=\"markers\",\n",
+ " marker=dict(size=1, opacity=1),\n",
+ " hoverinfo=\"text\",\n",
+ " showlegend=False,\n",
+ " )\n",
+ " if row[\"speed\"] == -1000: # Check for dummy speed\n",
+ " return (\n",
+ " go.layout.Shape(\n",
+ " type=\"circle\",\n",
+ " xref=\"x\",\n",
+ " yref=\"y\",\n",
+ " x0=row[\"x\"] - row[\"radius\"],\n",
+ " y0=row[\"y\"] - row[\"radius\"],\n",
+ " x1=row[\"x\"] + row[\"radius\"],\n",
+ " y1=row[\"y\"] + row[\"radius\"],\n",
+ " line=dict(width=0),\n",
+ " fillcolor=\"rgba(255,255,255,0)\", # Transparent fill\n",
+ " ),\n",
+ " hover_trace,\n",
+ " )\n",
+ " color = speed_to_color(row[\"speed\"], min_speed, max_speed, midpoint)\n",
+ " return (\n",
+ " go.layout.Shape(\n",
+ " type=\"circle\",\n",
+ " xref=\"x\",\n",
+ " yref=\"y\",\n",
+ " x0=row[\"x\"] - row[\"radius\"],\n",
+ " y0=row[\"y\"] - row[\"radius\"],\n",
+ " x1=row[\"x\"] + row[\"radius\"],\n",
+ " y1=row[\"y\"] + row[\"radius\"],\n",
+ " line_color=color,\n",
+ " fillcolor=color,\n",
+ " ),\n",
+ " hover_trace,\n",
+ " )\n",
+ "\n",
+ " results = frame_data.apply(create_shape, axis=1).tolist()\n",
+ " shapes = [res[0] for res in results]\n",
+ " hover_traces = [res[1] for res in results]\n",
+ " return shapes, hover_traces\n",
+ "\n",
+ "\n",
+ "def create_fig(\n",
+ " initial_agent_count,\n",
+ " initial_shapes,\n",
+ " initial_hover_trace,\n",
+ " geometry_traces,\n",
+ " hover_traces,\n",
+ " frames,\n",
+ " steps,\n",
+ " area_bounds,\n",
+ " width=800,\n",
+ " height=800,\n",
+ "):\n",
+ " minx, miny, maxx, maxy = area_bounds\n",
+ " fig = go.Figure(\n",
+ " data=geometry_traces + hover_traces + initial_hover_trace,\n",
+ " frames=frames,\n",
+ " layout=go.Layout(\n",
+ " shapes=initial_shapes,\n",
+ " title=f\"Number of Agents: {initial_agent_count}\",\n",
+ " title_x=0.5,\n",
+ " ),\n",
+ " )\n",
+ " fig.update_layout(\n",
+ " updatemenus=[\n",
+ " {\n",
+ " \"buttons\": [\n",
+ " {\n",
+ " \"args\": [\n",
+ " None,\n",
+ " {\n",
+ " \"frame\": {\"duration\": 100, \"redraw\": True},\n",
+ " \"fromcurrent\": True,\n",
+ " },\n",
+ " ],\n",
+ " \"label\": \"Play\",\n",
+ " \"method\": \"animate\",\n",
+ " }\n",
+ " ],\n",
+ " \"direction\": \"left\",\n",
+ " \"pad\": {\"r\": 10, \"t\": 87},\n",
+ " \"showactive\": False,\n",
+ " \"type\": \"buttons\",\n",
+ " \"x\": 0.1,\n",
+ " \"xanchor\": \"right\",\n",
+ " \"y\": 0,\n",
+ " \"yanchor\": \"top\",\n",
+ " }\n",
+ " ],\n",
+ " sliders=[\n",
+ " {\n",
+ " \"active\": 0,\n",
+ " \"yanchor\": \"top\",\n",
+ " \"xanchor\": \"left\",\n",
+ " \"currentvalue\": {\n",
+ " \"font\": {\"size\": 20},\n",
+ " \"prefix\": \"Frame:\",\n",
+ " \"visible\": True,\n",
+ " \"xanchor\": \"right\",\n",
+ " },\n",
+ " \"transition\": {\"duration\": 100, \"easing\": \"cubic-in-out\"},\n",
+ " \"pad\": {\"b\": 10, \"t\": 50},\n",
+ " \"len\": 0.9,\n",
+ " \"x\": 0.1,\n",
+ " \"y\": 0,\n",
+ " \"steps\": steps,\n",
+ " }\n",
+ " ],\n",
+ " autosize=False,\n",
+ " width=width,\n",
+ " height=height,\n",
+ " xaxis=dict(range=[minx - 0.5, maxx + 0.5]),\n",
+ " yaxis=dict(\n",
+ " scaleanchor=\"x\", scaleratio=1, range=[miny - 0.5, maxy + 0.5]\n",
+ " ),\n",
+ " )\n",
+ " return fig\n",
+ "\n",
+ "\n",
+ "def animate(\n",
+ " data: pedpy.TrajectoryData, area: pedpy.WalkableArea, *, every_nth_frame=5\n",
+ "):\n",
+ " data_df = pedpy.compute_individual_speed(traj_data=data, frame_step=5)\n",
+ " data_df = data_df.merge(data.data, on=[\"id\", \"frame\"], how=\"left\")\n",
+ " data_df[\"radius\"] = 0.2\n",
+ " min_speed = data_df[\"speed\"].min()\n",
+ " max_speed = data_df[\"speed\"].max()\n",
+ " midpoint = np.mean(data_df[\"speed\"])\n",
+ " max_agents = data_df.groupby(\"frame\").size().max()\n",
+ " dummy_agent_data = {\"x\": 0, \"y\": 0, \"radius\": 0, \"speed\": -1000}\n",
+ " frames = []\n",
+ " steps = []\n",
+ " unique_frames = data_df[\"frame\"].unique()\n",
+ " selected_frames = unique_frames[::every_nth_frame]\n",
+ " geometry_traces = get_geometry_traces(area.polygon)\n",
+ " initial_frame_data = data_df[data_df[\"frame\"] == data_df[\"frame\"].min()]\n",
+ " initial_agent_count = len(initial_frame_data)\n",
+ " initial_shapes, initial_hovers = get_shapes_for_frame(\n",
+ " initial_frame_data, min_speed, max_speed, midpoint\n",
+ " )\n",
+ " for frame_num in selected_frames[1:]:\n",
+ " frame_data = data_df[data_df[\"frame\"] == frame_num]\n",
+ " agent_count = len(frame_data)\n",
+ " while len(frame_data) < max_agents:\n",
+ " dummy_df = pd.DataFrame([dummy_agent_data])\n",
+ " frame_data = pd.concat([frame_data, dummy_df], ignore_index=True)\n",
+ "\n",
+ " shapes, hover_traces = get_shapes_for_frame(\n",
+ " frame_data, min_speed, max_speed, midpoint\n",
+ " )\n",
+ " frame = go.Frame(\n",
+ " data=geometry_traces + hover_traces,\n",
+ " name=str(frame_num),\n",
+ " layout=go.Layout(\n",
+ " shapes=shapes,\n",
+ " title=f\"Number of Agents: {agent_count}\",\n",
+ " title_x=0.5,\n",
+ " ),\n",
+ " )\n",
+ " frames.append(frame)\n",
+ " step = {\n",
+ " \"args\": [\n",
+ " [str(frame_num)],\n",
+ " {\n",
+ " \"frame\": {\"duration\": 100, \"redraw\": True},\n",
+ " \"mode\": \"immediate\",\n",
+ " \"transition\": {\"duration\": 500},\n",
+ " },\n",
+ " ],\n",
+ " \"label\": str(frame_num),\n",
+ " \"method\": \"animate\",\n",
+ " }\n",
+ " steps.append(step)\n",
+ "\n",
+ " return create_fig(\n",
+ " initial_agent_count,\n",
+ " initial_shapes,\n",
+ " initial_hovers,\n",
+ " geometry_traces,\n",
+ " hover_traces,\n",
+ " frames,\n",
+ " steps,\n",
+ " area.bounds,\n",
+ " width=800,\n",
+ " height=800,\n",
+ " )"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -272,8 +596,6 @@
},
"outputs": [],
"source": [
- "from jupedsim.internal.notebook_utils import animate, read_sqlite_file\n",
- "\n",
"trajectory_data, walkable_area = read_sqlite_file(trajectory_file)\n",
"animate(trajectory_data, walkable_area)"
]
@@ -303,9 +625,9 @@
"source": [
"## References & Further Exploration\n",
"\n",
- "**TODO RiMEA reference**\n",
+ "[1] RiMEA, 'Guideline for Microscopic Evacuation Analysis'(2016), URL: https://rimea.de/ \n",
"\n",
- "The chosen model here is based on the collision-free speed model. JuPedSim also incorporates another model known as GCFM. For more details on GCFM, refer to another notebook **(TODO: Link to the GCFM notebook)**.\n",
+ "This examples shows the basic behaviour of agents when moving around corners. A more advanced simulation and analysis can be found in **TODO ref to Mohcine's notebook**.\n",
"\n",
"The demonstration employed a straightforward journey with a singular exit. For a more intricate journey featuring multiple intermediate stops and waiting zones, see the upcoming section **(TODO: Link to the advanced journey section)**."
]