notebook + environment + stack

This commit is contained in:
Pierre Guillod 2024-09-07 18:39:47 +02:00
parent b2868a66ad
commit c018512d4d
3 changed files with 354 additions and 0 deletions

10
Dockerfile Normal file
View file

@ -0,0 +1,10 @@
FROM continuumio/miniconda3 AS base-deps
WORKDIR /root/home/
ADD . /root/home/remplis-ta-ville
RUN apt update
RUN cd remplis-ta-ville && conda env create && conda init
FROM base-deps
EXPOSE 8866
ENTRYPOINT cd remplis-ta-ville && conda run -n gis voila remplis-ta-ville.ipynb --no-browser --Voila.ip=0.0.0.0

14
environment.yml Normal file
View file

@ -0,0 +1,14 @@
name: gis
channels:
- conda-forge
- defaults
dependencies:
- osmnx
- geopandas
- networkx
- pandas
- numpy
- jupyter
- ipython
- ipywidgets
- voila

330
remplis-ta-ville.ipynb Normal file
View file

@ -0,0 +1,330 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "4af40662-b782-4904-ba32-224960d16d38",
"metadata": {
"editable": true,
"slideshow": {
"slide_type": ""
},
"tags": []
},
"source": [
"# Remplis ta ville"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "e2abc871-bf82-4cf6-993e-cd904c7e7468",
"metadata": {
"editable": true,
"slideshow": {
"slide_type": ""
},
"tags": []
},
"outputs": [],
"source": [
"import ipywidgets as widgets\n",
"from IPython import display\n",
"import os"
]
},
{
"cell_type": "markdown",
"id": "caf2f2f9-05de-4ea7-8efb-55e5eeaffc59",
"metadata": {},
"source": [
"Trouves-tu que ta région n'est pas assez encombrée par le trafic de transit, qu'elle n'est pas assez bruyante, que l'air et les eaux n'y sont pas assez pollués ? Eh bien, tu te trouves sur la bonne page ! Choisis ta région parmi la liste et laisse-moi te montrer ce que représente la quantité de trafic automobile supplémentaire estimée par l'Office Fédéral des Routes (OFROU) engendrée par l'extension de la largeur des autoroutes."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "529de80c-dd7f-45eb-b3c1-753b33d2bdb7",
"metadata": {
"editable": true,
"slideshow": {
"slide_type": ""
},
"tags": []
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "a65109866b884b0c8bde3165b2fb39fb",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HBox(children=(VBox(children=(Dropdown(description='Région', options=(('Ville de Genève', 'geneva'), ('Région …"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"places = {\n",
" \"geneva\": {\"shorthand\": \"geneva\", \"friendly\": \"Ville de Genève\", \"label\": \"Geneva, Switzerland\", \"cars\": 44_000, \"node\": 2809410},\n",
" \"coppet\": {\"shorthand\": \"coppet\", \"friendly\": \"Région de Coppet\", \"label\": [\"Commugny, Switzerland\",\"Coppet, Switzerland\",\"Founex, Switzerland\"], \"cars\": 8_800, \"node\": 6514705484},\n",
" \"nyon\": {\"shorthand\": \"nyon\", \"friendly\": \"Ville de Nyon\", \"label\": \"Nyon, Switzerland\", \"cars\": 7_000, \"node\": 311240719},\n",
"}\n",
"\n",
"mots = {\n",
" \"car\": {\"shorthand\": \"car\", \"friendly\": \"Voiture\", \"ppv\": 1.5, \"length\": 7},\n",
" \"bus\": {\"shorthand\": \"bus\", \"friendly\": \"Bus\", \"ppv\": 132, \"length\": 21},\n",
" \"bike\": {\"shorthand\": \"bike\", \"friendly\": \"Vélo\",\"ppv\": 1, \"length\": 0.5},\n",
"}\n",
"\n",
"w_place = widgets.Dropdown(\n",
" options=[(places[place][\"friendly\"],places[place][\"shorthand\"]) for place in places.keys()],\n",
" description='Région',\n",
")\n",
"\n",
"w_mot = widgets.Dropdown(\n",
" options=[(mots[mot][\"friendly\"],mots[mot][\"shorthand\"]) for mot in mots.keys()],\n",
" description='Véhicule',\n",
")\n",
"\n",
"w_submit = widgets.Button(\n",
" description='Remplir !',\n",
" disabled=False\n",
")\n",
"\n",
"w_progress = widgets.IntProgress(\n",
" min=0,\n",
" description=' ',\n",
" orientation='horizontal'\n",
")\n",
"\n",
"def simulate(*ignore):\n",
" \n",
" import geopandas as gpd\n",
" import pandas as pd\n",
" import networkx as nx\n",
" import osmnx as ox\n",
" import numpy as np\n",
" \n",
" place = places[w_place.value]\n",
" mot = mots[w_mot.value]\n",
" \n",
" people = place[\"cars\"] * mots[\"car\"][\"ppv\"]\n",
" \n",
" vehicles = np.ceil(people / mot[\"ppv\"])\n",
"\n",
" # Download regional roads graph and convert it to GeoDataFrame\n",
" \n",
" utw = [\"name\",\"highway\",\"access\",\"length\",\"lanes\",\"lanes:forward\",\"lanes:backward\",\"lanes:psv\",\"lanes:psv:forward\",\"lanes:psv:backward\",\"lanes:bus\",\"lanes:bus:forward\",\"lanes:bus:backward\",\"motor_vehicle\"]\n",
" ox.settings.useful_tags_way=utw\n",
"\n",
" w_progress.description = \"Chargement du graphe\"\n",
" w_progress.value = 0\n",
" G = ox.graph_from_place(place[\"label\"], network_type=\"drive\")\n",
" \n",
" # Estimate and clean:\n",
" # \n",
" # * Lanes dedicated to motorized individual transport\n",
" # * Lanes length\n",
" # * Lanes cars capacity\n",
"\n",
" w_progress.description = \"Annotation du graphe\"\n",
" edges = ox.graph_to_gdfs(G, nodes=False, edges=True)\n",
"\n",
" w_progress.description = \"Estimation des capacités\"\n",
" edges[\"lanes:forward\"] = edges[\"lanes:forward\"].fillna(0) if \"lanes:forward\" in edges.columns else 0\n",
" edges[\"lanes:forward\"] = edges.apply(lambda row: np.min(np.array(row[\"lanes:forward\"]).astype(int)), axis=1)\n",
" edges[\"lanes:backward\"] = edges[\"lanes:backward\"].fillna(0) if \"lanes:backward\" in edges.columns else 0\n",
" edges[\"lanes:backward\"] = edges.apply(lambda row: np.min(np.array(row[\"lanes:backward\"]).astype(int)), axis=1)\n",
" edges[\"lanes\"] = edges[\"lanes\"].fillna(0) if \"lanes\" in edges.columns else 0\n",
" edges[\"lanes\"] = edges.apply(lambda row: np.min(np.array(row[\"lanes\"]).astype(int)), axis=1)\n",
"\n",
" edges[\"lanes:psv\"] = edges[\"lanes:psv\"].fillna(0) if \"lanes:psv\" in edges.columns else 0\n",
" edges[\"lanes:psv\"] = edges.apply(lambda row: np.min(np.array(row[\"lanes:psv\"]).astype(int)), axis=1)\n",
" edges[\"lanes:psv:forward\"] = edges[\"lanes:psv:forward\"].fillna(0) if \"lanes:psv:forward\" in edges.columns else 0\n",
" edges[\"lanes:psv:forward\"] = edges.apply(lambda row: np.min(np.array(row[\"lanes:psv:forward\"]).astype(int)), axis=1)\n",
" edges[\"lanes:psv:backward\"] = edges[\"lanes:psv:backward\"].fillna(0) if \"lanes:psv:backward\" in edges.columns else 0\n",
" edges[\"lanes:psv:backward\"] = edges.apply(lambda row: np.min(np.array(row[\"lanes:psv:backward\"]).astype(int)), axis=1)\n",
"\n",
" edges[\"lanes:bus\"] = edges[\"lanes:bus\"].fillna(0) if \"lanes:bus\" in edges.columns else 0\n",
" edges[\"lanes:bus\"] = edges.apply(lambda row: np.min(np.array(row[\"lanes:bus\"]).astype(int)), axis=1)\n",
" edges[\"lanes:bus:forward\"] = edges[\"lanes:bus:forward\"].fillna(0) if \"lanes:bus:forward\" in edges.columns else 0\n",
" edges[\"lanes:bus:forward\"] = edges.apply(lambda row: np.min(np.array(row[\"lanes:bus:forward\"]).astype(int)), axis=1)\n",
" edges[\"lanes:bus:backward\"] = edges[\"lanes:bus:backward\"].fillna(0) if \"lanes:bus:backward\" in edges.columns else 0\n",
" edges[\"lanes:bus:backward\"] = edges.apply(lambda row: np.min(np.array(row[\"lanes:bus:backward\"]).astype(int)), axis=1)\n",
"\n",
" edges[\"lanes:mit\"] = edges.apply(lambda row: np.max([row[\"lanes\"],row[\"lanes:forward\"]+row[\"lanes:backward\"],2]) - np.max([row[\"lanes:psv\"],row[\"lanes:psv:forward\"]+row[\"lanes:psv:backward\"],row[\"lanes:bus:backward\"]]), axis=1)\n",
"\n",
" edges[\"highway\"] = edges[\"highway\"] if \"highway\" in edges.columns else \"\"\n",
" edges[\"motor_vehicle\"] = edges[\"motor_vehicle\"] if \"motor_vehicle\" in edges.columns else \"\"\n",
" edges[\"access\"] = edges[\"access\"] if \"access\" in edges.columns else \"\"\n",
" \n",
" edges[\"lanes:mit\"] = edges.apply(lambda row: (0 if np.isin(row[\"highway\"],[\"residential\",\"living_street\"]).any() else row[\"lanes:mit\"]), axis=1)\n",
" edges[\"lanes:mit\"] = edges.apply(lambda row: (0 if np.isin(row[\"motor_vehicle\"],[\"no\",\"private\",\"destination\",\"agricultural\",\"forestry\",\"delivery\"]).any() else row[\"lanes:mit\"]), axis=1)\n",
" edges[\"lanes:mit\"] = edges.apply(lambda row: (0 if np.isin(row[\"access\"], [\"no\",\"destination\"]).any() else row[\"lanes:mit\"]), axis=1)\n",
"\n",
" edges[\"length\"] = edges[\"length\"].fillna(0)\n",
" \n",
" edges[\"capacity\"] = edges.apply(lambda row: np.floor(row[\"length\"] * row[\"lanes:mit\"] / mot[\"length\"]), axis=1)\n",
" \n",
" edges_reset = edges.copy()\n",
" \n",
" # print(f\"Occupancy : {vehicles / sum(edges[\"capacity\"])}\")\n",
"\n",
" # Colorize the plotted roads graph:\n",
"\n",
" w_progress.description = \"Remplissage des rues\"\n",
" \n",
" counter = 0\n",
" nodes = [place[\"node\"]];\n",
" shoteach = vehicles / 10;\n",
" \n",
" edges = edges_reset.copy()\n",
" \n",
" edges_copy = pd.DataFrame(index=edges.index)\n",
" edges_copy[\"occupancy\"] = 0\n",
" edges_copy[\"capacity\"] = edges[\"capacity\"]\n",
" lastshot = 0\n",
"\n",
" w_progress.max = vehicles\n",
" \n",
" while counter < vehicles and nodes:\n",
" \n",
" idx = nodes.pop(0)\n",
" if idx in edges.index.get_level_values(0).values:\n",
" nodedges = edges.loc[idx]\n",
" nodedges_copy = edges_copy.loc[idx]\n",
" for i,r in nodedges.iterrows():\n",
" #print(nodes,counter,r[\"capacity\"],nodedges_copy.loc[i,\"occupancy\"],edges.shape[0])\n",
" if counter >= vehicles:\n",
" break\n",
" \n",
" if not nodedges_copy.loc[i,\"occupancy\"]:\n",
" nodedges_copy.loc[i,\"occupancy\"] = r[\"capacity\"]\n",
" counter += r[\"capacity\"]\n",
" w_progress.value = counter\n",
" nodes.append(i[0])\n",
" edges.drop(idx, inplace=True)\n",
" if counter // shoteach > lastshot:\n",
" lastshot += 1\n",
" \n",
" edges_copy[\"color\"] = edges_copy.apply(lambda row: 'darkred' if row[\"occupancy\"] else 'darkseagreen', axis=1)\n",
" edges_copy[\"color\"] = edges_copy.apply(lambda row: 'darkgray' if not row[\"capacity\"] else row[\"color\"], axis=1)\n",
" \n",
" colors = edges_copy.loc[:,\"color\"].to_numpy()\n",
" \n",
" fig, ax = ox.plot_graph(G, edge_color=colors, node_size=15, edge_linewidth=5, filepath=f\"./outputs/{place[\"shorthand\"]}/plot_{mot[\"shorthand\"]}_{lastshot}.svg\", save=True, show=False, close=True)\n",
" \n",
" w_progress.description = \"Terminé\"\n",
"\n",
"w_submit.on_click(simulate)\n",
"\n",
"if os.environ.get('SERVER_SOFTWARE','jupyter').startswith('voila'):\n",
" toolbar = widgets.HBox([widgets.VBox([w_place, w_mot])],layout=widgets.Layout(justify_content=\"center\"))\n",
"else:\n",
" toolbar = widgets.HBox([widgets.VBox([w_place, w_mot, w_submit, w_progress])],layout=widgets.Layout(justify_content=\"center\"))\n",
"\n",
"toolbar"
]
},
{
"cell_type": "markdown",
"id": "c16c39c7-c442-4460-97c6-318b01c357db",
"metadata": {},
"source": [
"Pour te donner une idée concrète de la quantité que cela représente, je vais les placer dans ta région. Imagine-toi ta région vide de voitures. Maintenant, remplis les routes de ta région une par une jusqu'à atteindre l'augmentation de la quantité de voitures prédite par l'OFROU en cas d'extension des autoroutes. Les routes complètement remplies sont représentées en rouge, celles encore vides en vert. Les routes représentées en gris sont des routes essentiellement résidentielles, elles ne sont donc pas prises en compte dans le calcul."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "f4440649-731e-421e-b43d-17576bb8ad3f",
"metadata": {
"editable": true,
"slideshow": {
"slide_type": ""
},
"tags": []
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "d7f46e8330ab475481525e195b871224",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HBox(children=(VBox(children=(HBox(children=(IntSlider(value=1, max=10, min=1),), layout=Layout(justify_conten…"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"slider = widgets.IntSlider(min=1, max=10, step=1)\n",
"\n",
"image = widgets.Image(format='svg+xml')\n",
"\n",
"def compute(*ignore):\n",
" file = open(f\"./outputs/{places[w_place.value][\"shorthand\"]}/plot_{w_mot.value}_{slider.value}.svg\", \"rb\")\n",
" read = file.read()\n",
" image.value = read\n",
"\n",
"slider.observe(compute, 'value')\n",
"\n",
"widgets.HBox([widgets.VBox([widgets.HBox([slider], layout=widgets.Layout(justify_content=\"center\")), image], layout=widgets.Layout(max_width=\"700px\"))], layout=widgets.Layout(justify_content=\"center\"))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "adf24613-a4ab-433f-8a1d-6e1d168163aa",
"metadata": {},
"outputs": [],
"source": [
"from IPython.core.display import HTML\n",
"HTML(\"\"\"\n",
"<style>\n",
"h1, p {\n",
" display: block !important;\n",
" max-width: 700px !important;\n",
" margin: 0 auto !important;\n",
"}\n",
"</style>\n",
"\"\"\")"
]
}
],
"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.12.5"
},
"voila": {
"template": "lab",
"theme": "dark"
}
},
"nbformat": 4,
"nbformat_minor": 5
}