diff --git a/data_combining.ipynb b/data_combining.ipynb index d4f89d9..4504045 100644 --- a/data_combining.ipynb +++ b/data_combining.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 11, + "execution_count": 60, "id": "133bc69e", "metadata": {}, "outputs": [ @@ -10,43 +10,81 @@ "name": "stdout", "output_type": "stream", "text": [ - "0 52.372634, 4.892071\n", - "1 52.374616, 4.899830\n", - "2 52.373860, 4.898690\n", - "3 52.372439, 4.897689\n", - "4 52.373538, 4.898166\n", - "5 52.372916, 4.898207\n", - "6 52.372628, 4.898233\n", - "7 52.372782, 4.896649\n", - "8 52.373587, 4.899815\n", - "9 52.375350, 4.897480\n", - "10 52.371930, 4.895600\n", - "11 52.372764, 4.899829\n", - "12 52.377027, 4.898059\n", - "13 52.380686, 4.899041\n", - "14 52.380566,4.899360\n", - "15 52.380516,4.899524\n", - "16 52.378526, 4.905811\n", - "17 52.378526, 4.905811\n", - "18 52.378101,4.937281\n", - "19 52.392143,4.885861\n", - "20 52.392029,4.886005\n", - "21 52.374414, 4.949579\n", - "22 52.401060, 4.891368\n", - "23 52.382352, 4.903016\n", - "24 52.382169, 4.903385\n", - "25 52.378020, 4.937150\n", - "26 52.380450, 4.900310\n", - "27 52.380420, 4.900280\n", - "28 52.380340, 4.899920\n", - "29 52.380280, 4.899850\n", - "30 52.378640, 4.904720\n", - "31 52.378580, 4.904660\n", - "32 52.377620, 4.911010\n", - "33 52.377020, 4.903520\n", - "34 52.388980, 4.919630\n", - "35 52.389260, 4.918930\n", - "Name: Lat/Long, dtype: object\n" + " Objectummer Locatienaam Lat/Long \\\n", + "0 CMSA-GAKH-01 Kalverstraat t.h.v. 1 52.372634, 4.892071 \n", + "1 CMSA-GAWW-11 Korte Niezel 52.374616, 4.899830 \n", + "2 CMSA-GAWW-12 Oudekennissteeg 52.373860, 4.898690 \n", + "3 CMSA-GAWW-13 Stoofsteeg 52.372439, 4.897689 \n", + "4 CMSA-GAWW-14 Oudezijds Voorburgwal t.h.v. 91 52.373538, 4.898166 \n", + "5 CMSA-GAWW-15 Oudezijds Achterburgwal t.h.v. 86 52.372916, 4.898207 \n", + "6 CMSA-GAWW-16 Oudezijds Achterburgwal t.h.v. 91 52.372628, 4.898233 \n", + "7 CMSA-GAWW-17 Oudezijds Voorburgwal t.h.v. 206 52.372782, 4.896649 \n", + "8 CMSA-GAWW-19 Molensteeg 52.373587, 4.899815 \n", + "9 CMSA-GAWW-20 Oudebrugsteeg 52.375350, 4.897480 \n", + "10 CMSA-GAWW-21 Damstraat 52.371930, 4.895600 \n", + "11 CMSA-GAWW-23 Bloedstraat 52.372764, 4.899829 \n", + "12 GACM-04 Damrak 1-5 52.377027, 4.898059 \n", + "13 GVCV-01 CS - ri. NDSM 52.380686, 4.899041 \n", + "14 GVCV-03 CS - ri. Buiksloterweg (West) 52.380566,4.899360 \n", + "15 GVCV-04 CS - ri. Buiksloterweg (Oost) 52.380516,4.899524 \n", + "16 GVCV-05-A CS - ri. IJplein (West) 52.378526, 4.905811 \n", + "17 GVCV-05-B CS - ri. IJplein (Oost) 52.378526, 4.905811 \n", + "18 GVCV-06 Azartplein 52.378101,4.937281 \n", + "19 GVCV-07 Pontsteiger N (ri. NDSM) 52.392143,4.885861 \n", + "20 GVCV-08 Pontsteiger Z (ri. Distelweg) 52.392029,4.886005 \n", + "21 GVCV-09 Zeeburg 52.374414, 4.949579 \n", + "22 GVCV-11 NDSMkade 52.401060, 4.891368 \n", + "23 GVCV-13 Buiksloterweg 52.382352, 4.903016 \n", + "24 GVCV-14 Buiksloterweg 52.382169, 4.903385 \n", + "25 GASA-06 Azartplein B 52.378020, 4.937150 \n", + "26 GASA-01-A1 IJBoulevardA1 52.380450, 4.900310 \n", + "27 GASA-01-A2 IJBoulevardA2 52.380420, 4.900280 \n", + "28 GASA-01-B IJBoulevardB 52.380340, 4.899920 \n", + "29 GASA-01-C IJBoulevardC 52.380280, 4.899850 \n", + "30 GASA-02-01 BrugOostertoegang1 52.378640, 4.904720 \n", + "31 GASA-02-02 BrugOostertoegang2 52.378580, 4.904660 \n", + "32 GASA-03 Oosterdoksbrug 52.377620, 4.911010 \n", + "33 GASA-04 CSriOosterdokskade 52.377020, 4.903520 \n", + "34 GASA-05-O NoorderparkOost 52.388980, 4.919630 \n", + "35 GASA-05-W NoorderparkWest 52.389260, 4.918930 \n", + "\n", + " Breedte Effectieve breedte \n", + "0 8 6,7 \n", + "1 3,8 3,4 \n", + "2 3 2,6 \n", + "3 2,6 2,2 \n", + "4 4 3,6 \n", + "5 3,2 2,8 \n", + "6 3,1 2,7 \n", + "7 5,1 4,7 \n", + "8 2,9 2,5 \n", + "9 5,7 5,3 \n", + "10 9,7 9,3 \n", + "11 5,7 5,3 \n", + "12 7 6,2 \n", + "13 3,5 3,1 \n", + "14 3,5 3,1 \n", + "15 3,5 3,1 \n", + "16 3,5 3,1 \n", + "17 3,5 3,1 \n", + "18 3,5 3,1 \n", + "19 3,5 3,1 \n", + "20 3,5 3,1 \n", + "21 3,5 3,1 \n", + "22 3,5 3,1 \n", + "23 3,5 3,1 \n", + "24 3,5 3,1 \n", + "25 NaN 3,1 \n", + "26 NaN 4 \n", + "27 NaN 4 \n", + "28 NaN 6 \n", + "29 NaN 6 \n", + "30 NaN 6 \n", + "31 NaN 6 \n", + "32 NaN 8 \n", + "33 NaN 7 \n", + "34 NaN 4 \n", + "35 NaN 4 \n" ] } ], @@ -73,12 +111,12 @@ " related_cols = [c for c in df_data.columns if c.startswith(base + \"_\")]\n", " if len(related_cols) >1: \n", " df_combined[base] = df_data[related_cols].sum(axis=1) \n", - "print(df_sensors['Lat/Long'])\n" + "print(df_sensors)\n" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 56, "id": "521729d1", "metadata": {}, "outputs": [], @@ -128,7 +166,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 62, "id": "3491b2ec", "metadata": {}, "outputs": [], @@ -145,22 +183,86 @@ "for sensor in sensors:\n", " df_plot = df_long_sorted[df_long_sorted[\"Objectummer\"] == sensor]\n", "\n", - " # plt.figure(figsize=(12,5))\n", - " # sns.lineplot(\n", - " # data=df_plot,\n", - " # x=\"timestamp\",\n", - " # y=\"flow\",\n", - " # hue=\"sensor_direction\", # each direction gets its own line\n", - " # marker=\"o\"\n", - " # )\n", - " # plt.title(f\"Directional flow over time for {sensor}\")\n", - " # plt.xlabel(\"Time\")\n", - " # plt.ylabel(\"Flow (person/min/meter)\")\n", - " # plt.xticks(rotation=45)\n", - " # plt.grid(True)\n", - " # plt.tight_layout()\n", + " plt.figure(figsize=(12,5))\n", + " sns.lineplot(\n", + " data=df_plot,\n", + " x=\"timestamp\",\n", + " y=\"flow\",\n", + " hue=\"sensor_direction\", # each direction gets its own line\n", + " marker=\"o\"\n", + " )\n", + " plt.title(f\"Directional flow over time for {sensor}\")\n", + " plt.xlabel(\"Time\")\n", + " plt.ylabel(\"Flow (person/min/meter)\")\n", + " plt.xticks(rotation=45)\n", + " plt.grid(True)\n", + " plt.tight_layout()\n", " # plt.show()\n", - "\n" + " # os.makedirs(\"plots\", exist_ok=True)\n", + " plt.savefig(f\"plots/{sensor}.png\")\n", + " plt.close() \n" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "6be7ce3c", + "metadata": {}, + "outputs": [], + "source": [ + "import base64\n", + "import folium\n", + "from folium import IFrame\n", + "\n", + "\n", + "# Center map roughly at sensor locations\n", + "df_sensors[['latitude', 'longitude']] = df_sensors['Lat/Long'].str.split(',', expand=True)\n", + "df_sensors['latitude'] = pd.to_numeric(df_sensors['latitude'])\n", + "df_sensors['longitude'] = pd.to_numeric(df_sensors['longitude'])\n", + "\n", + "\n", + "map_center = [df_sensors['latitude'].mean(), df_sensors['longitude'].mean()]\n", + "m = folium.Map(location=map_center, zoom_start=15)\n", + "\n", + "# Add zones as colored polygons\n", + "# print(gdf_zones)\n", + "gdf_zones = gdf_zones.to_crs(epsg=4326) # ensure lat/lon\n", + "# print(gdf_zones)\n", + "folium.GeoJson(\n", + " gdf_zones,\n", + " style_function=lambda feature: {\n", + " 'fillColor': feature['properties']['color'],\n", + " 'color': 'black',\n", + " 'weight': 1,\n", + " 'fillOpacity': 0.5\n", + " }\n", + ").add_to(m)\n", + "\n", + "# Add sensor markers with popups showing the plots\n", + "for idx, row in df_sensors.iterrows():\n", + " sensor = row['Objectummer']\n", + " plot_path = f\"plots/{sensor}.png\"\n", + " \n", + " if os.path.exists(plot_path):\n", + " encoded = base64.b64encode(open(plot_path, 'rb').read()).decode()\n", + " html = f''\n", + " iframe = IFrame(html, width=800, height=330)\n", + " popup = folium.Popup(iframe, max_width=1000)\n", + " else:\n", + " popup = None\n", + " \n", + " folium.CircleMarker(\n", + " location=[row['latitude'], row['longitude']],\n", + " radius=7,\n", + " color='red',\n", + " fill=True,\n", + " fill_color='red',\n", + " popup=popup\n", + " ).add_to(m)\n", + "\n", + "# Save map to HTML\n", + "# m\n", + "m.save(\"interactive_sensor_map.html\")" ] }, { diff --git a/project.ipynb b/project.ipynb index 22a5d9a..1bf1264 100644 --- a/project.ipynb +++ b/project.ipynb @@ -45,7 +45,7 @@ "\n", "1. What are the most important crowd-related indicators for crowd managers during SAIL 2025? \n", "2. How can different data sources (historical, event specific, mobility, geospatial) be integrated to unify to one single monitoring dashboard? \n", - "3. How can real-time (and historical?) data be leveraged to anticipate crowding issues before they can escalate by forecasting potential bottlenecks? \n", + "3. How can real-time and historical data be leveraged to anticipate crowding issues before they can escalate by forecasting potential bottlenecks? \n", "4. In what way can the UI and UX of the monitoring dashboard be designed to maximize clear and usable real-time decision-making?" ] }, @@ -92,27 +92,25 @@ "source": [ "**Dataset 1: Pedestrian Sensor Data**\n", "\n", - "1.\tDataset Description\n", - "The pedestrian sensors placed around the SAIL 2025 event area record how many people pass by within each one-minute interval. Each sensor measures movement in two opposite directions (approximately 180 degrees from each other). This means the dataset includes two separate count values per sensor: one for individuals moving in direction A and one for individuals moving in direction B. Over time, these values create a continuous flow-based representation of pedestrian movements across the monitored zone.\n", + "1.\tDataset Description
\n", + "The pedestrian sensors placed around the SAIL 2025 event area record how many people pass by within each three-minute interval. Each sensor measures movement in two opposite directions (180 degrees from each other). This means the dataset includes two separate count values per sensor: one for individuals moving in direction A and one for individuals moving in direction B. Over time, these values create a continuous flow-based representation of pedestrian movements across the monitored zone.\n", "\n", - "2.\tProcessing and Filtering into Useful Information\n", + "2.\tProcessing and Filtering into Useful Information
\n", "To make this data meaningful, it is processed in two ways:\n", "- Directional Flow Analysis: \n", - "\n", " By keeping the two count directions separate, we can determine the dominant walking direction, identify flow patterns (e.g., crowd moving toward an event stage or exiting the area), and detect potential one-way or congested movement.\n", "- Total Crowd Estimate per Sensor Location:\n", - "\n", " The two directional counts can also be combined to estimate the total number of people passing through that location in a given time window. This helps determine general crowd intensity in specific zones.\n", "\n", - "- Additional processing steps may include:\n", + "\n", "\n", - "3.\tUse in the Dashboard\n", - "In the dashboard, this processed data will be visualized to provide both high-level and detailed crowd insights. For example:\n", - "- Heatmaps or density indicators will show which areas are experiencing high pedestrian volume.\n", - "- Directional flow arrows or movement paths will help crowd managers understand where people are moving and whether circulation is happening as intended.\n", + "3.\tUse in the Dashboard
\n", + "In the dashboard, this processed data will be visualized to provide both high-level and detailed crowd insights. \n", + "- Heatmaps and density indicators will show which areas are experiencing high pedestrian volume.\n", + "- Directional flow will help crowd managers understand where people are moving and whether circulation is happening as intended.\n", "- Automated alerts can be triggered when thresholds are exceeded, signaling that crowd pressure is increasing in a particular zone.\n", "\n", "This allows decision-makers to respond proactively, redirect flows, adjust entry/exit routes, deploy additional staff, or coordinate with traffic and mobility teams to maintain a safe and smooth movement of visitors during the event.\n" @@ -166,7 +164,7 @@ "source": [ "# Functionalities of the dashboard\n", "\n", - "The dashboard can be divided into to 5 segments. A live map per minute, a zone status panel, a alert panel and a overview panel that works as a Dashboard header. \n" + "The dashboard can be divided into to 5 segments. A live map per minute, a zone status panel, an alert panel and an overview panel that works as a Dashboard header. \n" ] }, { @@ -175,7 +173,7 @@ "source": [ "**Part 1: Area Overview**\n", "\n", - "Nog doen!" + "The initial design objective for the area overview was to create a clear and interactive visual representation of pedestrian activity across the monitored area. This overview combines spatial and temporal data to show how pedestrian density changes over time at different sensor locations. Directional pedestrian counts were normalized by effective walkway width to calculate flow in persons per minute per meter, providing a standardized measure of crowd intensity. Each sensor’s time series was plotted individually, and these plots were integrated into an interactive map that displays sensor locations along with surrounding zones. By selecting a sensor on the map, users can view how pedestrian flow varied during the analyzed period. Unlike the real-time heat map, which visualizes live movement patterns, this overview presents historical and periodic trends—offering insights into when and where pedestrian activity tends to be highest. This enables better understanding of temporal crowd dynamics and supports planning or design decisions based on recurring movement patterns." ] }, { @@ -332,20 +330,15 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "base", "language": "python", "name": "python3" }, "language_info": { "name": "python", - "version": "3.8.2" + "version": "3.13.9" }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" - } - } + "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2