Spaces:
Sleeping
Sleeping
import faicons as fa | |
from ipyleaflet import Map, Marker, LayerGroup, Circle, Icon, AwesomeIcon, DivIcon, basemaps, GeoJSON | |
import matplotlib.pyplot as plt | |
from pandas.core.frame import functools | |
# Load data and compute static values | |
from borgarlina3_leaflet import load_and_preprocess_data | |
from shared import app_dir | |
from shinywidgets import render_widget | |
from shiny import reactive, render | |
from shiny.express import input, ui | |
# Import from backend | |
from data_processing.data_provider import Data_provider | |
initBackend = Data_provider() | |
def generateStops(year): | |
geojson_file = f"given_data/cityline_geojson/cityline_{year}.geojson" | |
pop_file = "given_data/ibuafjoldi.csv" | |
smallarea_file = "given_data/smasvaedi_2021.json" | |
dwellings_file = "given_data/ibudir.csv" | |
gpdStops, _, all_small_areas = load_and_preprocess_data(geojson_file, pop_file, smallarea_file, dwellings_file) | |
points = [] | |
# Assuming your GeoDataFrame is named 'gdf' | |
for _, row in gpdStops.iterrows(): | |
point = row["geometry"] | |
color = row["line"] | |
if color not in ["red", "yellow", "blue", "green", "purple", "orange"]: | |
color = color.split("/") | |
points.append(((point.y, point.x), color)) | |
return points, all_small_areas | |
def show_important_message(): | |
m = ui.modal( | |
ui.div( | |
ui.div( | |
ui.p( | |
"Our team’s solution for evaluating the Borgarlína bus stop placements was awarded the best solution at the " | |
"Gagnavist 2024 conference. Learn more about the event here: ", | |
ui.a( | |
"Datathon 2024", | |
href="https://www.ihpc.is/events/radstefna-um-islenska-gagnavistkerfid---gagnathon-2024", | |
target="_blank" | |
) | |
), | |
ui.div( | |
ui.p("Highlights:"), | |
ui.div("- Integrated datasets: Income deciles, population density, and age distribution."), | |
ui.div("- Developed the solution over a single weekend!"), | |
), | |
ui.p(""), | |
ui.div( | |
ui.p("Special thanks to:"), | |
ui.div("- Statistics Iceland"), | |
ui.div("- The Ministry of Finance and Economic Affairs"), | |
ui.div("- The Ministry of Higher Education, Innovation, and Industry"), | |
), | |
ui.p(""), | |
ui.p("Looking forward to participating again next year!"), | |
ui.p("Alexander, Birgir, Elvar, Thomas, and Jan") | |
), | |
), | |
size="xl", | |
easy_close=True, | |
footer=None, | |
title="Datathon 2024: A Winning Experience!" | |
) | |
ui.modal_show(m) | |
# Add page title and sidebar | |
ui.page_opts(title="Borgarlínan", fillable=True) | |
with ui.sidebar(open="open"): | |
ui.input_select("year", "Year:", {2025: "2025", 2029: "2029", 2030: "2030"}) | |
ui.input_slider("rad", "Stop reach radius:", min=200, max=1000, value=400), | |
# station_coord, w_density=1, w_income=1, w_age=1): | |
ui.input_numeric("w_density", "Density Weight", "1") | |
ui.input_numeric("w_income", "Income Weight", "1") | |
ui.input_numeric("w_age", "Age Weight", "1") | |
ui.input_action_button("reset", "Reset zoom") | |
ui.input_action_button("show", "About Us") | |
with ui.layout_columns(col_widths=[8, 4]): | |
with ui.card(full_screen=True): | |
ui.card_header("Capital area") | |
def map(): | |
return Map( | |
basemap=basemaps.CartoDB.Positron) | |
with ui.layout_column_wrap(width="450px"): | |
with ui.layout_columns(col_widths=(6, 6), min_height="450px"): | |
with ui.value_box(theme="text-red", showcase=fa.icon_svg("bus", width="50px"),): | |
"Average score" | |
def render_line_score(): | |
score = lineScore().get("red", 0) | |
if score: | |
return str(int(score)) | |
else: | |
return str("Pending") | |
with ui.value_box(theme="text-blue",showcase=fa.icon_svg("bus", width="50px"),): | |
"Average score" | |
def render_line_score1(): | |
score = lineScore().get("blue", 0) | |
if score: | |
return str(int(score)) | |
else: | |
return str("Pending") | |
with ui.value_box(theme="text-yellow" ,showcase=fa.icon_svg("bus", width="50px"),): | |
"Average score" | |
def render_line_score2(): | |
score = lineScore().get("orange", 0) | |
if score: | |
return str(int(score)) | |
else: | |
return str("Pending") | |
with ui.value_box(theme="text-green",showcase=fa.icon_svg("bus", width="50px"),): | |
"Average score" | |
def render_line_score4(): | |
score = lineScore().get("green", 0) | |
if score: | |
return str(int(score)) | |
else: | |
return str("Pending") | |
with ui.value_box(theme="text-purple",showcase=fa.icon_svg("bus", width="50px"),): | |
"Average score" | |
def render_line_score3(): | |
score = lineScore().get("purple", 0) | |
if score: | |
return str(int(score)) | |
else: | |
return str("Pending") | |
with ui.value_box(theme="text-black",showcase=fa.icon_svg("route", width="50px")): | |
"Total average score" | |
def render_line_score5(): | |
return str(int(sum(lineScore().get(color, 0) for color in ["red", "blue", "orange", "purple", "green"]))) | |
with ui.card(min_height="450px"): | |
with ui.navset_pill(id="tab"): | |
with ui.nav_panel("Score"): | |
def totalScore(): | |
score = scores() | |
return "Total score: " + str(int(score["total_score"])) | |
def contribution_pie_chart(): | |
print("Generating pie chart of contributions") | |
# Get score components | |
score = scores() | |
age_contribution = score["age_score"] | |
income_contribution = score["income_score"] | |
density_contribution = score["density_score"] | |
# Data for the pie chart | |
contributions = [age_contribution, income_contribution, density_contribution] | |
labels = ["Age", "Income", "Density"] | |
colors = ["#FD4D86", "#36DEC2", "#704CB0"] # Custom colors for the segments | |
# Create a Matplotlib figure | |
fig, ax = plt.subplots(figsize=(5, 5)) | |
# Create the pie chart | |
ax.pie( | |
contributions, | |
labels=labels, | |
autopct='%1.1f%%', | |
startangle=90, | |
colors=colors, | |
textprops={'fontsize': 12} | |
) | |
# Add a title | |
ax.set_title("Score Contributions", fontsize=16) | |
# Return the figure for rendering in Shiny | |
return fig | |
with ui.nav_panel("Income"): | |
"Income Score" | |
def incomeScore(): | |
score = scores() | |
return score["income_score"] | |
def income_plot(): | |
print("Generating income distribution bar chart") | |
# Get selected stop coordinates | |
x, y = stop.get() | |
station_coord = (y, x) | |
# Fetch income distribution data from the Data_provider instance | |
income_data = initBackend.get_station_score(station_coord, radius=input.rad())['income_data'] # Assume this returns a dictionary | |
# Example structure: {1: 150, 2: 200, 3: 180, ...} | |
income_brackets = list(income_data.keys()) | |
populations = list(income_data.values()) | |
# Create a Matplotlib figure | |
fig, ax = plt.subplots(figsize=(6, 3)) | |
# Create the bar chart | |
ax.bar(income_brackets, populations, color='#36DEC2') | |
# Customize the plot | |
ax.set_title("Population by Income Bracket") | |
ax.set_xlabel("Income Bracket") | |
ax.set_ylabel("Population") | |
ax.set_xticks(income_brackets) | |
ax.set_xticklabels(income_brackets, rotation=45, ha="right") | |
# Return the figure for rendering in Shiny | |
return fig | |
with ui.nav_panel("Age"): | |
"Age Score" | |
def ageScore(): | |
score = scores() | |
return score["age_score"] | |
def age_plot(): | |
print("Generating age distribution bar chart") | |
# Get selected stop coordinates | |
x, y = stop.get() | |
station_coord = (y, x) | |
# Fetch age distribution data from the Data_provider instance | |
age_data = initBackend.get_station_score(station_coord, radius=input.rad())['age_data'] # Assume this returns a dictionary | |
# Example structure: {'0-4 ára': 120, '5-9 ára': 140, ...} | |
age_brackets = list(age_data.keys()) | |
populations = list(age_data.values()) | |
# Create a Matplotlib figure | |
fig, ax = plt.subplots(figsize=(6, 3)) | |
# Create the bar chart with custom colors | |
ax.bar(age_brackets, populations, color='#FD4D86') | |
# Customize the plot | |
ax.set_title("Population by Age Bracket", fontsize=14) | |
ax.set_xlabel("Age Bracket", fontsize=12) | |
ax.set_ylabel("Population", fontsize=12) | |
ax.set_xticks(range(len(age_brackets))) | |
ax.set_xticklabels(age_brackets, rotation=45, ha="right", fontsize=10) | |
# Return the figure for rendering in Shiny | |
return fig | |
with ui.nav_panel("Density"): | |
"Density" | |
def sensityScoer(): | |
score = scores() | |
return float(score["density_score"] * 1000000) | |
def density_plot(): | |
print("Generating density score bar chart") | |
# Get selected stop coordinates | |
x, y = stop.get() | |
station_coord = (y, x) | |
# Fetch small area contributions from the Data_provider instance | |
small_area_contributions = initBackend.get_station_score( | |
station_coord, | |
radius=input.rad(), | |
w_density=input.w_density(), | |
w_income=input.w_income(), | |
w_age=input.w_age() | |
)['small_area_contributions'] | |
# Extract density scores for each small area | |
area_ids = [area_id for area_id in small_area_contributions.keys()] | |
density_scores = [area_data['density_score'] for area_data in small_area_contributions.values()] | |
# Create a Matplotlib figure | |
fig, ax = plt.subplots(figsize=(6, 3)) | |
# Create the bar chart | |
ax.bar(area_ids, density_scores, color='#704CB0') | |
# Customize the plot | |
ax.set_title("Density Scores of Small Areas", fontsize=14) | |
ax.set_xlabel("Small Area ID", fontsize=12) | |
ax.set_ylabel("Density Score", fontsize=12) | |
ax.set_xticks(range(len(area_ids))) | |
ax.set_xticklabels(area_ids, rotation=45, ha="right", fontsize=10) | |
# Return the figure for rendering in Shiny | |
return fig | |
ui.include_css(app_dir / "styles.css") | |
# -------------------------------------------------------- | |
# Reactive calculations and effects | |
# -------------------------------------------------------- | |
def _(): | |
year = input.year() | |
stops, small_areas = generateStops(year) | |
rad = input.rad() | |
markers = [] | |
circles = [] | |
for layer in map.widget.layers[:]: | |
if layer.name in ["stops", "radius", "polygons", "heatmap"]: | |
map.widget.remove_layer(layer) | |
# Add polygons from small_areas | |
polygons_layer = [] | |
for _, area in small_areas.iterrows(): | |
geojson_data = area["geometry"].__geo_interface__ | |
geojson_dict = { | |
"type": "Feature", | |
"properties": {}, | |
"geometry": geojson_data | |
} | |
geojson = GeoJSON( | |
data=geojson_dict, # Pass the dictionary here | |
style={ | |
"color": "#005485", # Border color | |
"fillColor": "white", # Fill color | |
"opacity": 0.5, # Border opacity | |
"weight": 1.0, # Border thickness | |
"dashArray": "5, 5", # Optional dashed border | |
"fillOpacity": 0.3 # Fill opacity | |
}, | |
hover_style={"color": "#005485", "weight": 1}, # Highlight on hover | |
name="polygons" | |
) | |
polygons_layer.append(geojson) | |
i = 0 | |
for stop, color in stops: | |
if type(color) == list: | |
smallerRad = 0 | |
for c in color: | |
circle = Circle() | |
circle.location = stop | |
circle.radius = rad - smallerRad | |
circle.color = c | |
circle.fill_opacity = 0.1 | |
circle.name = str(i) | |
circles.append(circle) | |
smallerRad =+ 50 | |
else: | |
circle = Circle() | |
circle.location = stop | |
circle.radius = rad | |
circle.color = color | |
circle.fill_color = color | |
circle.fill_opacity = 0.1 | |
circle.name = str(i) | |
circles.append(circle) | |
icon = AwesomeIcon(name="bus", marker_color="black", icon_color="white") | |
# icon1 = DivIcon(html = '<div style="border-radius:50%;background-color: black; width: 10px; height: 10px;"></div>') | |
# icon2 = Icon(icon_url="marker.png") | |
marker = Marker(location=stop, | |
icon=icon, | |
icon_anchor=(10,10), | |
icon_size=(0,0), | |
draggable=True) | |
marker.name = str(i) | |
marker.on_click(functools.partial(create_marker_callback, id=stop)) | |
marker.on_move(functools.partial(reset_marker, index=i)) | |
markers.append(marker) | |
i += 1 | |
layerGroup = LayerGroup(layers=markers, name="stops") | |
layerGroup2 = LayerGroup(layers=circles, name="radius") | |
map.widget.add(layerGroup) | |
map.widget.add(layerGroup2) | |
# Add polygon layers to the map | |
polygon_group = LayerGroup(layers=polygons_layer, name="polygons") | |
map.widget.add(polygon_group) | |
stop = reactive.value() | |
def create_marker_callback(id, **kwargs): | |
# We can also get coordinates of the marker here | |
rad = input.rad() | |
zoom = 15.0 | |
if rad > 500: | |
zoom = 14.8 | |
map.widget.zoom = zoom | |
map.widget.center = kwargs["coordinates"] | |
stop.set(id) | |
def reset_marker(index, **kwargs): | |
cord = kwargs["location"] | |
x = cord[0] | |
y = cord[1] | |
for layer in map.widget.layers: | |
if layer.name == "radius": # Check for the correct LayerGroup | |
for circle in layer.layers: | |
if circle.name == str(index): # Match the Circle by name | |
circle.location = [x, y] # Update the Circle's location | |
stop.set((x,y)) | |
def centerMap(): | |
_ = input.reset() | |
map.widget.zoom = 11.8 | |
map.widget.center = (64.11,-21.90) | |
def scores(): | |
x, y = stop.get() | |
score = initBackend.get_station_score(station_coord=(y, x), w_density=input.w_density(), w_income=input.w_income(), w_age=input. w_age(), radius=input.rad()) | |
return score | |
def lineScore(): | |
listOfStops, _ = generateStops(input.year()) | |
listOflines = {} | |
# Handle stops with single and multiple colors | |
for stop, color in listOfStops: | |
x, y = stop | |
# If the color is a list (multiple colors), iterate through it | |
if isinstance(color, list): | |
for single_color in color: | |
if single_color not in listOflines: | |
listOflines[single_color] = [] | |
listOflines[single_color].append((y, x)) | |
else: | |
# If it's a single color, process it normally | |
if color not in listOflines: | |
listOflines[color] = [] | |
listOflines[color].append((y, x)) | |
# Calculate scores for each line | |
lines = {} | |
for key, val in listOflines.items(): | |
score = initBackend.line_score( | |
val, | |
w_density=input.w_density(), | |
w_income=input.w_income(), | |
w_age=input.w_age(), | |
radius=input.rad() | |
) | |
lines[key] = score["final_score"] | |
return lines | |