{ "cells": [ { "cell_type": "markdown", "id": "0", "metadata": {}, "source": [ "# Fundamental Diagrams at Measurement Lines\n", "\n", "This notebook demonstrates the calculation of density, speed, and flow for pedestrian trajectories at a measurement line, based on the method described in Adrian et al., 2024: [Continuity Equation and Fundamental Diagram of Pedestrians](https://arxiv.org/abs/2409.11857).\n", "\n", "The calculations rely on a Voronoi decomposition of the available space, adapted to determine each pedestrian's contribution to the measurement line. Specifically, the proportion of the line intersected by a pedestrian's Voronoi cell defines their contribution.\n", "\n", "The speed at the measurement line ($v_l$) is calculated as:\n", "\n", "$$v_{l} = \\sum_i v_{i,n_{l}} \\cdot w_{i,l},$$\n", "\n", "where $v_{i,n_{l}}$ is the speed of pedestrian $i$ orthogonal to the measurement line, and $w_{i,l}$ is their proportional contribution to the line. Density and flow are computed using similar principles.\n", "\n", "This method measures flow perpendicular to a line, aligning with the classical physical definition. It is particularly effective for analyzing directional flows (e.g., uni- or bidirectional). However, for non-directional flows over wide areas, alternative measurement methods may be more appropriate.\n", "\n", "## Analysis Set-up\n", "\n", "The first step is to load the trajectory data. This guide analyzes data from four corridor experiments: two with unidirectional flows and two with bidirectional flows. The measurement line is aligned orthogonally to the main movement direction in all experiments." ] }, { "cell_type": "code", "execution_count": null, "id": "1", "metadata": {}, "outputs": [], "source": [ "from pedpy import TrajectoryUnit\n", "\n", "files = {\n", " \"unidirectional 1\": (\n", " \"demo-data/uni-directional/traj_UNI_CORR_500_08.txt\",\n", " TrajectoryUnit.METER,\n", " ),\n", " \"unidirectional 2\": (\n", " \"demo-data/uni-directional/traj_UNI_CORR_500_03.txt\",\n", " TrajectoryUnit.METER,\n", " ),\n", " \"bidirectional 1\": (\n", " \"demo-data/bi-directional/bi_corr_400_b_08.txt\",\n", " TrajectoryUnit.CENTIMETER,\n", " ),\n", " \"bidirectional 2\": (\n", " \"demo-data/bi-directional/bi_corr_400_b_03.txt\",\n", " TrajectoryUnit.CENTIMETER,\n", " ),\n", "}" ] }, { "cell_type": "code", "execution_count": null, "id": "2", "metadata": {}, "outputs": [], "source": [ "import pathlib\n", "\n", "from pedpy import load_trajectory\n", "\n", "trajectories = {}\n", "for name, (path, unit) in files.items():\n", " trajectories[name] = load_trajectory(trajectory_file=pathlib.Path(path), default_unit=unit)" ] }, { "cell_type": "code", "execution_count": null, "id": "3", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "from pedpy import plot_measurement_setup\n", "\n", "\n", "def format_axis(ax, xlabel, ylabel, title, legend=True):\n", " \"\"\"Format axis.\"\"\"\n", " label_size = 14\n", " title_size = 16\n", " legend_size = 12\n", " tick_size = 12\n", "\n", " ax.set_xlabel(xlabel, fontsize=label_size)\n", " ax.set_ylabel(ylabel, fontsize=label_size)\n", " ax.tick_params(axis=\"both\", which=\"major\", labelsize=tick_size)\n", " ax.set_title(title, fontsize=title_size)\n", " if legend:\n", " ax.legend(fontsize=legend_size)\n", "\n", "\n", "fig, axs = plt.subplots(2, int(len(trajectories) / 2), figsize=(15, 8))\n", "\n", "for (name, trajectory), ax in zip(trajectories.items(), axs.ravel(), strict=False):\n", " plot_measurement_setup(\n", " traj=trajectory,\n", " axes=ax,\n", " traj_width=0.2,\n", " traj_end_marker=\"x\",\n", " )\n", " format_axis(ax, \"x / m\", \"y / m\", name, legend=False)\n", "\n", "fig.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "4", "metadata": {}, "source": [ "The plot above shows the trajectories of the experimental data analyzed in this Notebook, including both unidirectional and bidirectional experiments. Each red line represents a pedestrian's trajectory, with their ending positions marked by an \"x.\"\n", "\n", "Next the {class}`walkable area ` of the experiment is defined.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "5", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "from pedpy import WalkableArea\n", "\n", "walkable_areas = {\n", " \"unidirectional 1\": WalkableArea(\n", " # complete area\n", " [(-6.5, 6.5), (-6.5, -1.5), (6.5, -1.5), (6.5, 6.5)],\n", " obstacles=[\n", " # lower Wall:\n", " [(-6.0, 0.0), (5.0, 0.0), (5.0, -1.0), (-6.0, -1.0)],\n", " # upper Wall:\n", " [(-6.0, 5.0), (5.0, 5.0), (5.0, 6.0), (-6.0, 6.0)],\n", " ],\n", " ),\n", " \"unidirectional 2\": WalkableArea(\n", " # complete area\n", " [(-6.5, 6.5), (-6.5, -1.5), (6.5, -1.5), (6.5, 6.5)],\n", " obstacles=[\n", " # lower Wall:\n", " [(-6.0, 0.0), (5.0, 0.0), (5.0, -1.0), (-6.0, -1.0)],\n", " # upper Wall:\n", " [(-6.0, 5.0), (5.0, 5.0), (5.0, 6.0), (-6.0, 6.0)],\n", " ],\n", " ),\n", " \"bidirectional 1\": WalkableArea(\n", " # complete area\n", " [(-6.5, 6.5), (-6.5, -1.5), (5.5, -1.5), (5.5, 6.5)],\n", " obstacles=[\n", " # lower Wall:\n", " [(-6.0, 0.0), (5.0, 0.0), (5.0, -1.0), (-6.0, -1.0)],\n", " # upper Wall:\n", " [(-6.0, 4.0), (5.0, 4.0), (5.0, 5.0), (-6.0, 5.0)],\n", " ],\n", " ),\n", " \"bidirectional 2\": WalkableArea(\n", " # complete area\n", " [(-6.5, 6.5), (-6.5, -1.5), (5.5, -1.5), (5.5, 6.5)],\n", " obstacles=[\n", " # lower Wall:\n", " [(-5.0, 0.0), (5.0, 0.0), (5.0, -1.0), (-5.0, -1.0)],\n", " # upper Wall:\n", " [(-5.0, 4.1), (5.0, 4.1), (5.0, 5.0), (-5.0, 5.0)],\n", " ],\n", " ),\n", "}" ] }, { "cell_type": "markdown", "id": "6", "metadata": {}, "source": [ "To obtain correct results, it is important that the measurement line is orthogonal to the main direction of movement.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "7", "metadata": {}, "outputs": [], "source": [ "from pedpy import MeasurementLine\n", "\n", "measurement_lines = {\n", " \"unidirectional 1\": MeasurementLine([(0.0, 0.0), (0.0, 5.0)]),\n", " \"unidirectional 2\": MeasurementLine([(0.0, 0.0), (0.0, 5.0)]),\n", " \"bidirectional 1\": MeasurementLine([(0.0, 0.0), (0.0, 4.0)]),\n", " \"bidirectional 2\": MeasurementLine([(0.0, 0.0), (0.0, 4.0)]),\n", "}" ] }, { "cell_type": "markdown", "id": "8", "metadata": {}, "source": [ "The entire setup, including trajectories, geometry, and measurement lines looks like this:\n" ] }, { "cell_type": "code", "execution_count": null, "id": "9", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "fig, axs = plt.subplots(2, int(len(trajectories) / 2), figsize=(15, 11))\n", "\n", "for (name, trajectory), ax in zip(trajectories.items(), axs.ravel(), strict=False):\n", " plot_measurement_setup(\n", " traj=trajectory,\n", " walkable_area=walkable_areas[name],\n", " measurement_lines=[measurement_lines[name]],\n", " axes=ax,\n", " traj_width=0.2,\n", " traj_end_marker=\"x\",\n", " ma_color=\"g\",\n", " ma_line_color=\"g\",\n", " ma_alpha=0.2,\n", " ml_color=\"b\",\n", " )\n", " format_axis(ax, \"x / m\", \"y / m\", name, legend=False)\n", "fig.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "10", "metadata": {}, "source": [ "## Calculate Voronoi Polygons\n", "\n", "The Voronoi decomposition is used to assign cells to each pedestrian and to calculate density, speed, and flow at a measurement line. To ensure accuracy, it is highly recommended to apply a cutoff when calculating Voronoi polygons. Without a cutoff, pedestrians at the edges of a group may influence disproportionately large areas, leading to inaccuracies.\n", "\n", "Please note that computing Voronoi polygons may take some time.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "11", "metadata": {}, "outputs": [], "source": [ "from pedpy.methods.method_utils import (\n", " Cutoff,\n", " compute_individual_voronoi_polygons,\n", ")\n", "\n", "individual_cutoffs = {}" ] }, { "cell_type": "code", "execution_count": null, "id": "12", "metadata": {}, "outputs": [], "source": [ "individual_cutoffs[\"unidirectional 1\"] = compute_individual_voronoi_polygons(\n", " traj_data=trajectories[\"unidirectional 1\"],\n", " walkable_area=walkable_areas[\"unidirectional 1\"],\n", " cut_off=Cutoff(radius=0.8, quad_segments=3),\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "13", "metadata": {}, "outputs": [], "source": [ "individual_cutoffs[\"unidirectional 2\"] = compute_individual_voronoi_polygons(\n", " traj_data=trajectories[\"unidirectional 2\"],\n", " walkable_area=walkable_areas[\"unidirectional 2\"],\n", " cut_off=Cutoff(radius=0.8, quad_segments=3),\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "14", "metadata": {}, "outputs": [], "source": [ "individual_cutoffs[\"bidirectional 1\"] = compute_individual_voronoi_polygons(\n", " traj_data=trajectories[\"bidirectional 1\"],\n", " walkable_area=walkable_areas[\"bidirectional 1\"],\n", " cut_off=Cutoff(radius=0.8, quad_segments=3),\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "15", "metadata": {}, "outputs": [], "source": [ "individual_cutoffs[\"bidirectional 2\"] = compute_individual_voronoi_polygons(\n", " traj_data=trajectories[\"bidirectional 2\"],\n", " walkable_area=walkable_areas[\"bidirectional 2\"],\n", " cut_off=Cutoff(radius=0.8, quad_segments=3),\n", ")" ] }, { "cell_type": "markdown", "id": "16", "metadata": {}, "source": [ "## Determine the Species\n", "\n", "A species represents a set of pedestrians that encouters the measurement line frome the same side.\n", "Since there are two possible main directions of movement in which the line can be approached orthogonally, pedestrians are divided into two species, referred to as Species 1 and Species 2.\n", "\n", "To determine which species a pedestrian belongs to, the direction of their velocity at the first frame $t_{i,l}$​, where their Voronoi cell intersects the line, is analyzed.\n", "\n", "A movement factor $m_i$​ is defined for each pedestrian $i$ as the sign of the orthogonal velocity component $n \\cdot v(t_{i,l})$:\n", "\n", "$$m_i = sign \\Big( n \\cdot v(t_{i,l}) \\Big).$$\n", "\n", "Here:\n", "\n", "- $n$ is the normal vector of the measurement line\n", "- $v(t_{i,l})$ is the velocity of pedestrian $i$ when their Voronoi cell intersects with line $l$.\n", "\n", "Pedestrians with $m_i=1$ have the same main movement direction and belong to Species 1.\n", "Pedestrian with $m_i=-1$ move in the opposite direction and belong to Species 2.\n", "\n", "```{eval-rst}\n", ".. figure:: images/species_determination.svg\n", " :align: center\n", "```\n", "\n", "This figure shows the frame where the species determination is made.\n", "This decision is based on the first frame where a pedestrian's Voronoi cell intersects the measurement line. The direction of the current velocity at that moment determines the species assignment.\n", "\n", "It is important to note that the decision is independent of whether the pedestrian crosses the line afterward." ] }, { "cell_type": "code", "execution_count": null, "id": "17", "metadata": {}, "outputs": [], "source": [ "from pedpy.methods.speed_calculator import compute_species\n", "\n", "species = {}\n", "\n", "for name in trajectories:\n", " species[name] = compute_species(\n", " individual_voronoi_polygons=individual_cutoffs[name],\n", " measurement_line=measurement_lines[name],\n", " trajectory_data=trajectories[name],\n", " frame_step=25,\n", " )" ] }, { "cell_type": "markdown", "id": "18", "metadata": {}, "source": [ "## Calculating Individual Velocity\n", "\n", "We will calculate the individual velocity using the `compute_individual_speed` function.\n", "Keep the following considerations in mind:\n", "\n", "1. **Enable Velocity Calculation**:\n", " Ensure that the `compute_velocity` option is enabled to calculate the individual velocity.\n", "\n", "2. **Frame Step Selection**:\n", "\n", " - The frame step determines how many frames before and after the current position are used to calculate movement.\n", " - **Choosing the frame step**:\n", " - A small frame step may result in noisy calculations.\n", " - A large frame step might overlook smaller movements, reducing accuracy.\n", "\n", "3. **Handling Border Cases**:\n", "\n", " - The default speed calculation method is `BORDER_EXCLUDE`, which excludes trajectory values at the edges.\n", " - If these excluded values are required for subsequent calculations, this may lead to errors. Possible solutions include:\n", " - Cutting a few frames from the trajectory data used for Voronoi polygon calculations to ensure alignment.\n", " - Switching to a different speed calculation method.\n", "\n", "4. **Alternative Speed Calculation Methods**:\n", " - Consider other speed calculation options if `BORDER_EXCLUDE` is unsuitable.\n", " - Refer to the [User Guide](user_guide) for details on available methods and their implications.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "19", "metadata": {}, "outputs": [], "source": [ "from pedpy.methods.speed_calculator import (\n", " SpeedCalculation,\n", " compute_individual_speed,\n", ")\n", "\n", "calculation_methods = {\n", " \"unidirectional 1\": SpeedCalculation.BORDER_SINGLE_SIDED,\n", " \"unidirectional 2\": SpeedCalculation.BORDER_EXCLUDE,\n", " \"bidirectional 1\": SpeedCalculation.BORDER_EXCLUDE,\n", " \"bidirectional 2\": SpeedCalculation.BORDER_EXCLUDE,\n", "}\n", "\n", "individual_speeds = {}\n", "\n", "for name in trajectories:\n", " individual_speeds[name] = compute_individual_speed(\n", " traj_data=trajectories[name],\n", " frame_step=1,\n", " compute_velocity=True,\n", " speed_calculation=calculation_methods[name],\n", " )" ] }, { "cell_type": "markdown", "id": "20", "metadata": {}, "source": [ "## Calculating Speed Perpendicular to the Measurement Line\n", "\n", "To calculate the total speed perpendicular to the measurement line in a specific frame, all pedestrians whose Voronoi polygons intersect with the measurement line are considered.\n", "\n", "```{eval-rst}\n", ".. figure:: images/voronoi_at_line.svg\n", " :align: center\n", "```\n", "\n", "The speed of an individual pedestrian is determined by multiplying their orthogonal speed with their proportion of the measurement line:\n", "\n", "$$ v^\\text{line}_i(t) = v_{i}(t) \\cdot n_l \\cdot \\frac{w_i(t)}{w}.$$\n", "\n", "Here:\n", "\n", "- $w_i(t)$ is the length of the measurement line segment intersected by the pedestrian’s Voronoi cell.\n", "- $w$ is the total length of the measurement line.\n", "- $n_l$ is the unit normal vector to the measurement line.\n", "\n", "```{eval-rst}\n", ".. figure:: images/line_intersection_voronoi.svg\n", " :align: center\n", "```\n", "\n", "## Handling Opposite Movement Directions\n", "\n", "Pedestrians in Species 2 move in the opposite main direction, which causes their velocities to have opposite signs. Adding these directly would result in the contributions of the two species canceling each other out.\n", "\n", "To address this, the velocity of each pedestrian is adjusted by multiplying it with the movement factor $m$, which ensures consistent directionality in the calculations.\n", "\n", "$$ v^S(t) = \\sum_{i \\in S} m \\cdot v^\\text{line}_i(t).$$\n", "\n", "The total speed across the measurement line combines the contributions from both species:\n", "\n", "$$ v^\\text{line}(t) = v^{\\mathrm{I}}(t) + v^{\\mathrm{II}}(t).$$\n", "\n", "## Allowing for Negative Velocities\n", "\n", "This approach accommodates negative velocities within each species. Negative velocities may occur when a pedestrian:\n", "\n", "- Stands still but wobbles their head, resulting in small velocity fluctuations.\n", "- Crosses the measurement line multiple times, moving in both directions.\n", "\n", "By accounting for such cases, this method ensures comprehensive and robust measurements of pedestrian movement.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "21", "metadata": {}, "outputs": [], "source": [ "from pedpy.methods.speed_calculator import compute_line_speed\n", "\n", "speeds = {}\n", "for name in trajectories:\n", " speeds[name] = compute_line_speed(\n", " individual_voronoi_polygons=individual_cutoffs[name],\n", " measurement_line=measurement_lines[name],\n", " individual_speed=individual_speeds[name],\n", " species=species[name],\n", " )" ] }, { "cell_type": "code", "execution_count": null, "id": "22", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "from pedpy.plotting.plotting import plot_speed_at_line\n", "\n", "fig, axs = plt.subplots(2, int(len(speeds) / 2), figsize=(15, 11))\n", "\n", "for (name, speed_on_line), ax in zip(speeds.items(), axs.ravel(), strict=False):\n", " plot_speed_at_line(\n", " speed_at_line=speed_on_line,\n", " axes=ax,\n", " line_width=0.5,\n", " )\n", " format_axis(ax, \"Frame\", \"Speed / m/s\", f\"Speed on line for {name}\")\n", "\n", "fig.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "23", "metadata": {}, "source": [ "## Compute Line Density\n", "\n", "The density along the measurement line is calculated based on the densities of the Voronoi cells of pedestrians, weighted by their proportional contribution to the line. The density for each species considers only the pedestrians belonging to that species.\n", "\n", "$$ \\rho^S(t) = \\sum_{i \\in S} \\frac{1}{A_i(t)} \\cdot \\frac{w_i(t)}{w}.$$\n", "\n", "Here:\n", "\n", "- $A_i(t)$: The area of pedestrian $i$'s Voronoi cell.\n", "- $\\frac{w_i(t)}{w}$​: The fraction of the measurement line intersected by the Voronoi cell of pedestrian $i$.\n", "\n", "The total density is obtained by combining the contributions from both Species 1 and 2:\n", "\n", "$$ \\rho^\\text{line}(t) = \\rho^{\\mathrm{I}}(t) + \\rho^{\\mathrm{II}}(t)$$\n" ] }, { "cell_type": "code", "execution_count": null, "id": "24", "metadata": {}, "outputs": [], "source": [ "from pedpy.methods.density_calculator import compute_line_density\n", "\n", "densities = {}\n", "for name in trajectories:\n", " densities[name] = compute_line_density(\n", " individual_voronoi_polygons=individual_cutoffs[name],\n", " measurement_line=measurement_lines[name],\n", " species=species[name],\n", " )" ] }, { "cell_type": "code", "execution_count": null, "id": "25", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "from pedpy.plotting.plotting import plot_density_at_line\n", "\n", "fig, axs = plt.subplots(2, int(len(densities) / 2), figsize=(15, 11))\n", "\n", "for (name, density_on_line), ax in zip(densities.items(), axs.ravel(), strict=False):\n", " plot_density_at_line(\n", " density_at_line=density_on_line,\n", " axes=ax,\n", " line_width=0.75,\n", " title=f\"density on line for {name}\",\n", " )\n", " format_axis(ax, \"Frame\", \"Density / $m^{-2}$\", f\"Density on line for {name}\")\n", "\n", "fig.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "26", "metadata": {}, "source": [ "## Compute Line Flow\n", "\n", "The calculation of flow is based on the flow equation:\n", "\n", "$$\n", "J = \\rho \\cdot v \\cdot w\n", "$$\n", "\n", "To compute the flow, both the velocity orthogonal to the measurement line and the density are considered. Each species only accounts for the pedestrians representing that species. To handle the negative velocities of Species 2, the movement factor $m$ is applied.\n", "\n", "The flow for a species $S$ is calculated as:\n", "\n", "$$\n", "j^S(t) = \\sum_{i \\in S} m_i \\cdot v_i(t) \\cdot n_{l} \\cdot \\frac{1}{A_i(t)} \\cdot \\frac{w_i(t)}{w}\n", "$$\n", "\n", "### Explanation of Symbols\n", "\n", "- $j$: The flow\n", "- $S$: The species for which the flow is calculated\n", "- $i$: A pedestrian belonging to species $S$\n", "- $m_i$: The movement factor for pedestrian $i$, determined during species assignment\n", "- $v_i(t)$: The velocity of pedestrian $i$ at time $t$\n", "- $n_{l}$: The orthogonal vector of the measurement line $l$\n", "- $A_i(t)$: The area of the Voronoi cell for pedestrian $i$\n", "- $w_i(t)$: The length of the measurement line intersected by the Voronoi cell of pedestrian $i$\n", "- $w$: The total length of the measurement line\n", "\n", "The total flow across the measurement line is obtained by combining the contributions from both species:\n", "\n", "$$\n", "j^\\text{line}(t) = j^{\\mathrm{I}}(t) + j^{\\mathrm{II}}(t)\n", "$$\n" ] }, { "cell_type": "code", "execution_count": null, "id": "27", "metadata": {}, "outputs": [], "source": [ "from pedpy.methods.flow_calculator import compute_line_flow\n", "\n", "flows = {}\n", "for name in trajectories:\n", " flows[name] = compute_line_flow(\n", " individual_voronoi_polygons=individual_cutoffs[name],\n", " measurement_line=measurement_lines[name],\n", " species=species[name],\n", " individual_speed=individual_speeds[name],\n", " )" ] }, { "cell_type": "code", "execution_count": null, "id": "28", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "from pedpy.plotting.plotting import plot_flow_at_line\n", "\n", "fig, axs = plt.subplots(2, int(len(flows) / 2), figsize=(15, 11))\n", "\n", "for (name, flow_on_line), ax in zip(flows.items(), axs.ravel(), strict=False):\n", " plot_flow_at_line(\n", " flow_at_line=flow_on_line,\n", " axes=ax,\n", " line_width=0.75,\n", " title=f\"flow on line for {name}\",\n", " )\n", " format_axis(ax, \"Frame\", \"Flow / 1/s\", f\"Flow on line for {name}\")\n", "\n", "fig.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "29", "metadata": {}, "source": [ "## Excluding Data from the Start and End Phases\n", "\n", "Upon closer examination of the trajectory data from the first unidirectional experiment, it becomes evident that not all the data collected are meaningful for analysis. This is due to the way the experiment was conducted.\n", "\n", "Toward the end of the experiment, participants were instructed to remain stationary. Consequently, although the data are technically usable, it may not contribute meaningfully to further analysis.\n", "\n", "When analyzing on your own it is recommended to exclude data that lack substantive relevance.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "30", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "fig, axs = plt.subplots(2, 2, figsize=(15, 11))\n", "\n", "name = \"unidirectional 1\"\n", "\n", "interval = [500, 2075]\n", "plot_measurement_setup(\n", " axes=axs[0, 0],\n", " traj=trajectories[name],\n", " walkable_area=walkable_areas[name],\n", " measurement_lines=[measurement_lines[name]],\n", ")\n", "format_axis(axs[0, 0], \"x / m\", \"y / m\", f\"Trajectories {name}\", legend=False)\n", "plot_speed_at_line(axes=axs[0, 1], speed_at_line=speeds[name], title=f\"line speed for {name}\")\n", "axs[0, 1].plot([interval[0], interval[0]], [-0.1, 1.2], c=\"r\")\n", "axs[0, 1].plot([interval[1], interval[1]], [-0.1, 1.2], c=\"r\")\n", "format_axis(axs[0, 1], \"Frame\", \"v / m/s\", f\"Line speed for {name}\")\n", "\n", "plot_density_at_line(\n", " axes=axs[1, 0],\n", " density_at_line=densities[name],\n", " title=f\"line density for {name}\",\n", ")\n", "axs[1, 0].plot([interval[0], interval[0]], [0, 4], c=\"r\")\n", "axs[1, 0].plot([interval[1], interval[1]], [0, 4], c=\"r\")\n", "format_axis(axs[1, 0], \"Frame\", \"Density / $m^{-2}$\", f\"Density for {name}\")\n", "\n", "plot_flow_at_line(axes=axs[1, 1], flow_at_line=flows[name], title=f\"line flow for {name}\")\n", "axs[1, 1].plot([interval[0], interval[0]], [0, 2.2], c=\"r\")\n", "axs[1, 1].plot([interval[1], interval[1]], [0, 2.2], c=\"r\")\n", "format_axis(axs[1, 1], \"Frame\", \"J / 1/s\", f\"Flow for {name}\")\n", "\n", "fig.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "31", "metadata": {}, "source": [ "In the plots above you can see the trajectories of the first unidirectional experiment as well as the line speed, line density and line flow for said experiment. In between the red lines the measured values will be used for analysis. The additional data outside the red lines is removed for further analysis.\n" ] }, { "cell_type": "markdown", "id": "32", "metadata": {}, "source": [ "## Creating a Fundamental Diagram\n", "\n", "With the calculations complete, we can now generate a fundamental diagram. For this, we will use precomputed data from 10 unidirectional and 10 bidirectional experiments, all conducted under the same experimental setup as described earlier.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "33", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "style_options = {\n", " \"traj_UNI_CORR_500_01\": {\"color\": \"red\", \"marker\": \"+\"},\n", " \"traj_UNI_CORR_500_02\": {\"color\": \"green\", \"marker\": \"x\"},\n", " \"traj_UNI_CORR_500_03\": {\"color\": \"blue\", \"marker\": \"x\"},\n", " \"traj_UNI_CORR_500_04\": {\n", " \"color\": \"white\",\n", " \"marker\": \"s\",\n", " \"edgecolors\": \"pink\",\n", " },\n", " \"traj_UNI_CORR_500_05\": {\"color\": \"cyan\", \"marker\": \"s\"},\n", " \"traj_UNI_CORR_500_06\": {\"color\": \"grey\", \"marker\": \"^\"},\n", " \"traj_UNI_CORR_500_07\": {\n", " \"color\": \"white\",\n", " \"marker\": \"^\",\n", " \"edgecolors\": \"orange\",\n", " },\n", " \"traj_UNI_CORR_500_08\": {\"color\": \"black\", \"marker\": \"o\"},\n", " \"traj_UNI_CORR_500_09\": {\n", " \"color\": \"white\",\n", " \"marker\": \"o\",\n", " \"edgecolors\": \"purple\",\n", " },\n", " \"traj_UNI_CORR_500_10\": {\"color\": \"orange\", \"marker\": \"+\"},\n", " \"bi_corr_400_b_01\": {\"color\": \"red\", \"marker\": \"+\"},\n", " \"bi_corr_400_b_02\": {\"color\": \"green\", \"marker\": \"x\"},\n", " \"bi_corr_400_b_03\": {\"color\": \"blue\", \"marker\": \"x\"},\n", " \"bi_corr_400_b_04\": {\"color\": \"white\", \"marker\": \"s\", \"edgecolors\": \"pink\"},\n", " \"bi_corr_400_b_05\": {\"color\": \"cyan\", \"marker\": \"s\"},\n", " \"bi_corr_400_b_06\": {\"color\": \"grey\", \"marker\": \"^\"},\n", " \"bi_corr_400_b_07\": {\n", " \"color\": \"white\",\n", " \"marker\": \"^\",\n", " \"edgecolors\": \"orange\",\n", " },\n", " \"bi_corr_400_b_08\": {\"color\": \"black\", \"marker\": \"o\"},\n", " \"bi_corr_400_b_09\": {\n", " \"color\": \"white\",\n", " \"marker\": \"o\",\n", " \"edgecolors\": \"purple\",\n", " },\n", " \"bi_corr_400_b_10\": {\"color\": \"orange\", \"marker\": \"+\"},\n", "}" ] }, { "cell_type": "code", "execution_count": null, "id": "34", "metadata": {}, "outputs": [], "source": [ "import pickle\n", "\n", "with open(\"demo-data/uni-directional/unidirectional_data.pkl\", \"rb\") as file:\n", " loaded_dictionaries = pickle.load(file)\n", "\n", "line_densities_uni = loaded_dictionaries[\"line_densities_uni\"]\n", "line_speeds_uni = loaded_dictionaries[\"line_speeds_uni\"]\n", "\n", "with open(\"demo-data/bi-directional/bidirectional_data.pkl\", \"rb\") as file:\n", " loaded_dictionaries = pickle.load(file)\n", "\n", "line_densities_bi = loaded_dictionaries[\"line_densities_bi\"]\n", "line_speeds_bi = loaded_dictionaries[\"line_speeds_bi\"]" ] }, { "cell_type": "markdown", "id": "35", "metadata": {}, "source": [ "The fundamental diagram can now be created using the line speed and line density.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "36", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "from pedpy.column_identifier import DENSITY_COL, SPEED_COL\n", "\n", "f, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))\n", "\n", "for name in sorted(line_densities_uni.keys()):\n", " ax1.scatter(\n", " line_densities_uni[name][DENSITY_COL],\n", " line_speeds_uni[name][SPEED_COL],\n", " alpha=1,\n", " label=name,\n", " **style_options[name],\n", " )\n", "\n", "for name in sorted(line_densities_bi.keys()):\n", " ax2.scatter(\n", " line_densities_bi[name][DENSITY_COL],\n", " line_speeds_bi[name][SPEED_COL],\n", " alpha=1,\n", " label=name,\n", " **style_options[name],\n", " )\n", "\n", "format_axis(\n", " ax1,\n", " \"Density / $m^{-2}$\",\n", " \"Speed / m/s\",\n", " \"Fundamental Diagram: unidirectional\",\n", ")\n", "format_axis(\n", " ax2,\n", " \"Density / $m^{-2}$\",\n", " \"Speed / m/s\",\n", " \"Fundamental Diagram: bidirectional\",\n", ")\n", "ax1.grid(True, alpha=0.3)\n", "ax2.grid(True, alpha=0.3)\n", "ax1.legend()\n", "ax2.legend()\n", "plt.show()" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "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.12.7" } }, "nbformat": 4, "nbformat_minor": 5 }