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 | |