Py.Cafe

asafnb/

folium-od-map-visualization

Folium-Based OD Map Visualization

DocsPricing
  • 20240301_DE2_TAZ_LAYER.json
  • aggregated_od_33.csv
  • app.py
  • requirements.txt
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import pandas as pd
import geopandas as gpd
import folium
import solara
from branca.colormap import LinearColormap

# Load data
od_data = pd.read_csv("aggregated_od_33.csv")
zones = gpd.read_file("20240301_DE2_TAZ_LAYER.json")

# Ensure zones is in WGS84 (EPSG:4326) for web mapping
zones = zones.to_crs(epsg=4326)

# Calculate centroids
zones['centroid'] = zones.geometry.centroid

# Prepare data
od_data['total_flow'] = od_data.groupby(['From_TAZ_33', 'To_TAZ_33'])['Count'].transform('sum')

# Create a color scale
color_scale = LinearColormap(colors=['blue', 'green', 'yellow', 'red'], vmin=od_data['total_flow'].min(), vmax=od_data['total_flow'].max())

@solara.component
def ODMap():
    # State variables
    vehicle_type, set_vehicle_type = solara.use_state("All")
    min_count, set_min_count = solara.use_state(0)

    # Filter data based on user input
    def filter_data():
        filtered = od_data
        if vehicle_type != "All":
            filtered = filtered[filtered['VehicleType'] == vehicle_type]
        filtered = filtered[filtered['Count'] >= min_count]
        return filtered

    # Create map
    def create_map():
        # Calculate the center of all centroids
        center_lat = zones['centroid'].y.mean()
        center_lon = zones['centroid'].x.mean()
        m = folium.Map(location=[center_lat, center_lon], zoom_start=8)

        # Add OD lines
        filtered_data = filter_data()
        for _, row in filtered_data.iterrows():
            origin = zones[zones['taz_33'] == str(row['From_TAZ_33'])]['centroid'].iloc[0]
            destination = zones[zones['taz_33'] == str(row['To_TAZ_33'])]['centroid'].iloc[0]
            
            folium.PolyLine(
                locations=[[origin.y, origin.x], [destination.y, destination.x]],
                color=color_scale(row['total_flow']),
                weight=1 + (row['Count'] / filtered_data['Count'].max()) * 5,
                opacity=0.8,
                popup=f"From: {row['From_TAZ_33']}, To: {row['To_TAZ_33']}<br>Vehicle Type: {row['VehicleType']}<br>Count: {row['Count']}<br>Total Flow: {row['total_flow']}"
            ).add_to(m)

        # Add color scale legend
        color_scale.add_to(m)

        return m

    # Solara components for user input
    vehicle_type_select = solara.Select(
        label="Vehicle Type",
        value=vehicle_type,
        values=["All"] + list(od_data['VehicleType'].unique()),
        on_value=set_vehicle_type
    )

    min_count_slider = solara.SliderFloat(
        label="Minimum Count",
        value=min_count,
        min=0,
        max=od_data['Count'].max(),
        step=10,
        on_value=set_min_count
    )

    # Create Folium map
    map_figure = create_map()

    # Layout
    return solara.Column([
        solara.Row([
            solara.Column([vehicle_type_select, min_count_slider]),
            solara.FoliumMap(map_figure, style={"width": "800px", "height": "600px"})
        ])
    ])

# Main app
@solara.component
def Page():
    return ODMap()