Skip to content

Commit 6464f9a

Browse files
author
Adriano Sanges
committed
Enhance main.py with interactive map features and data handling improvements
- Added support for displaying an interactive map using Folium and GeoPandas. - Refactored region and municipality retrieval functions for clarity and consistency. - Introduced a new function to load geospatial data from a file. - Updated the UI to include tabs for displaying data in table and map formats. - Enhanced error handling to provide user feedback in case of exceptions. - Updated dependencies in pyproject.toml to include GeoPandas and Streamlit-Folium.
1 parent cdbbb5b commit 6464f9a

File tree

8 files changed

+493
-48
lines changed

8 files changed

+493
-48
lines changed

__pycache__/maptab.cpython-311.pyc

6.09 KB
Binary file not shown.

data/geojsons/limits_IT_municipalities.geojson

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

data/geojsons/limits_IT_provinces.geojson

Lines changed: 109 additions & 0 deletions
Large diffs are not rendered by default.

data/geojsons/limits_IT_regions.geojson

Lines changed: 22 additions & 0 deletions
Large diffs are not rendered by default.

main.py

Lines changed: 89 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,68 +2,109 @@
22

33
import duckdb
44
import streamlit as st
5+
import geopandas as gpd
6+
import folium
7+
import streamlit_folium as st_folium
8+
import os
9+
10+
# Set page configuration as the first Streamlit command
11+
st.set_page_config(layout="wide", page_title="Interactive Real Estate Map", page_icon="🌍")
12+
513
from init_db import init_db
14+
from maptab import map_tab
15+
16+
617

718
@st.cache_data(hash_funcs={duckdb.DuckDBPyConnection: id})
8-
def get_regioni(conn):
9-
return conn.execute("SELECT DISTINCT Regione FROM luoghi order by Regione").df()
19+
def get_regions(conn):
20+
return conn.execute("SELECT DISTINCT Regione FROM luoghi ORDER BY Regione").df()
1021

1122
@st.cache_data(hash_funcs={duckdb.DuckDBPyConnection: id})
12-
def get_comuni(conn, regione):
13-
return conn.execute(f"SELECT DISTINCT Comune_descrizione FROM luoghi WHERE Regione = '{regione}' order by Comune_descrizione").df()
23+
def get_municipalities(conn, region):
24+
return conn.execute(f"SELECT DISTINCT Comune_descrizione FROM luoghi WHERE Regione = '{region}' ORDER BY Comune_descrizione").df()
25+
26+
@st.cache_data
27+
def load_geodataframe(file_path):
28+
return gpd.read_file(file_path)
1429

30+
def create_map(data_gdf, highlight_column):
31+
m = folium.Map(location=[42.5, 12.5], zoom_start=6, tiles="cartodbpositron")
32+
33+
for _, row in data_gdf.iterrows():
34+
feature = folium.GeoJson(
35+
row["geometry"],
36+
name=row[highlight_column],
37+
tooltip=row[highlight_column],
38+
style_function=lambda x: {
39+
"fillColor": "blue",
40+
"color": "black",
41+
"weight": 1,
42+
"fillOpacity": 0.4,
43+
},
44+
highlight_function=lambda x: {"weight": 3, "fillOpacity": 0.7},
45+
popup=folium.Popup(f"<b>{row[highlight_column]}</b>", parse_html=True)
46+
)
47+
feature.add_to(m)
48+
49+
return m
1550

1651
def main():
1752
"""
1853
Main function that serves as the entry point for the application.
1954
"""
2055
try:
21-
conn = duckdb.connect(database = "dati-immobiliari.duckdb")
56+
conn = duckdb.connect(database="dati-immobiliari.duckdb")
2257
init_db(conn)
23-
24-
st.set_page_config(layout="wide")
25-
26-
st.title("Dati immobiliari Agenzia delle Entrate")
27-
st.caption("Dati del 2024")
28-
29-
30-
show_all_columns = st.checkbox("Mostra tutte le colonne", value=False)
31-
32-
default_columns = {
33-
"Comune_descrizione" : "Comune",
34-
"Descr_Tipologia" : "Tipologia",
35-
"Zona_Descr" : "Zona",
36-
"Compr_min" : "Prezzo acquisto minimo al mq (€)",
37-
"Compr_max" : "Prezzo acquisto massimo al mq (€)",
38-
"Loc_min" : "Prezzo locazione minimo al mq (€)",
39-
"Loc_max" : "Prezzo locazione massimo al mq (€)"
40-
}
41-
42-
regioni_dataset = get_regioni(conn)["Regione"]
43-
selected_regione =st.selectbox("Seleziona la regione", regioni_dataset)
44-
45-
if(selected_regione != None):
46-
comuni_dataset = get_comuni(conn, selected_regione)["Comune_descrizione"]
47-
selected_comune = st.selectbox("Seleziona il comune", comuni_dataset)
48-
49-
if(selected_comune != None):
50-
51-
selected_tipologia = st.selectbox("Seleziona la tipologia", ("Abitazioni civili", "Uffici", "Negozi"))
52-
53-
if(selected_tipologia != None):
54-
result = conn.execute(f"SELECT * FROM joined_data WHERE Regione = '{selected_regione}' and Comune_descrizione = '{selected_comune}' and Descr_Tipologia = '{selected_tipologia}'").df()
55-
readable_columns = {**default_columns, **{col: col for col in result.columns if col not in default_columns}}
56-
57-
if(show_all_columns):
58-
st.dataframe(result)
59-
60-
else:
61-
62-
display_df = result[list(default_columns.keys())].rename(columns=default_columns)
63-
st.dataframe(display_df)
64-
58+
59+
tableTab, mapTab = st.tabs(["Table", "Map"])
60+
61+
with tableTab:
62+
st.title("Italian Real Estate Prices")
63+
st.caption("Downloaded from Agenzia delle Entrate - current data is from 1st semester 2024")
64+
65+
show_all_columns = st.checkbox("Show all columns", value=False)
66+
67+
default_columns = {
68+
"Comune_descrizione": "Municipality",
69+
"Descr_Tipologia": "Type",
70+
"Zona_Descr": "Zones",
71+
"Compr_min": "Minimum purchase price per sqm (€)",
72+
"Compr_max": "Maximum purchase price per sqm (€)",
73+
"Loc_min": "Minimum rental price per sqm (€)",
74+
"Loc_max": "Maximum rental price per sqm (€)"
75+
}
76+
77+
regions_dataset = get_regions(conn)["Regione"]
78+
selected_region = st.selectbox("Select a region", regions_dataset)
79+
80+
if selected_region is not None:
81+
municipalities_dataset = get_municipalities(conn, selected_region)["Comune_descrizione"]
82+
selected_municipality = st.selectbox("Select a municipality", municipalities_dataset)
83+
84+
if selected_municipality is not None:
85+
selected_building_type = st.selectbox("Select the type of building", ("Residential buildings", "Offices", "Shops"))
86+
87+
building_types = {
88+
"Residential buildings": "Abitazioni civili",
89+
"Offices": "Uffici",
90+
"Shops": "Negozi"
91+
}
92+
93+
if selected_building_type is not None:
94+
result = conn.execute(f"SELECT * FROM joined_data WHERE Regione = '{selected_region}' and Comune_descrizione = '{selected_municipality}' and Descr_Tipologia = '{building_types[selected_building_type]}'").df()
95+
readable_columns = {**default_columns, **{col: col for col in result.columns if col not in default_columns}}
96+
97+
if show_all_columns:
98+
st.dataframe(result)
99+
else:
100+
display_df = result[list(default_columns.keys())].rename(columns=default_columns)
101+
st.dataframe(display_df)
102+
103+
with mapTab:
104+
pass
105+
65106
except Exception as e:
66-
print(f"An error occurred: {e}")
107+
st.error(f"An error occurred: {e}")
67108
return 1
68109

69110
return 0

maptab.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#!/usr/bin/env python3
2+
import os
3+
import streamlit as st
4+
import json
5+
import streamlit_folium as st_folium
6+
import folium
7+
import geopandas as gpd
8+
9+
@st.cache_data
10+
def load_geodataframe(file_path):
11+
return gpd.read_file(file_path)
12+
13+
dirname = os.path.dirname(os.path.abspath(__file__))
14+
regions_filename = os.path.join(dirname, 'data', 'geojsons', 'limits_IT_regions.geojson')
15+
provinces_filename = os.path.join(dirname, 'data', 'geojsons', 'limits_IT_provinces.geojson')
16+
municipalities_filename = os.path.join(dirname, 'data', 'geojsons', 'limits_IT_municipalities.geojson')
17+
18+
def create_map(data_gdf, highlight_column, callback_column, callback_key):
19+
m = folium.Map(location=[42.5, 12.5], zoom_start=6, tiles="cartodbpositron")
20+
21+
for _, row in data_gdf.iterrows():
22+
feature = folium.GeoJson(
23+
row["geometry"],
24+
name=row[highlight_column],
25+
tooltip=row[highlight_column],
26+
style_function=lambda x: {
27+
"fillColor": "blue",
28+
"color": "black",
29+
"weight": 1,
30+
"fillOpacity": 0.4,
31+
},
32+
highlight_function=lambda x: {"weight": 3, "fillOpacity": 0.7},
33+
popup=folium.Popup(f"<b>{row[highlight_column]}</b>", parse_html=True)
34+
)
35+
feature.add_to(m)
36+
37+
# Optionally, add a marker at the center of the geometry
38+
folium.Marker(
39+
location=[row["geometry"].centroid.y, row["geometry"].centroid.x],
40+
popup=f"<b>{row[highlight_column]}</b>"
41+
).add_to(m)
42+
43+
return m
44+
45+
def map_tab(conn):
46+
st.title("Italian Real Estate Prices")
47+
st.caption("Downloaded from Agenzia delle Entrate - current data is from 1st semester 2024")
48+
49+
if "level" not in st.session_state:
50+
st.session_state.level = "region"
51+
if "selected_region" not in st.session_state:
52+
st.session_state.selected_region = None
53+
if "selected_province" not in st.session_state:
54+
st.session_state.selected_province = None
55+
56+
regions_gdf = load_geodataframe(regions_filename)
57+
provinces_gdf = load_geodataframe(provinces_filename)
58+
municipalities_gdf = load_geodataframe(municipalities_filename)
59+
60+
if st.session_state.level == "region":
61+
st.write("### Select a Region")
62+
m = create_map(regions_gdf, "reg_name", "reg_istat_code", "selected_region")
63+
output = st_folium.folium_static(m, height=600)
64+
65+
# Debugging: Check the entire output structure
66+
st.write("Map Output:", output)
67+
68+
# Check if the map interaction has updated the session state
69+
if output and "last_clicked" in output:
70+
clicked_properties = output["last_clicked"].get("properties", {})
71+
st.write("Clicked Properties:", clicked_properties) # Debugging: Check clicked properties
72+
73+
if "reg_istat_code" in clicked_properties:
74+
st.session_state.selected_region = clicked_properties["reg_istat_code"]
75+
st.session_state.level = "province"
76+
st.write(f"Transitioning to province level with region: {st.session_state.selected_region}")
77+
else:
78+
st.write("No region code found in clicked properties.")
79+
80+
elif st.session_state.level == "province":
81+
st.write(f"### Selected Region: {st.session_state.selected_region}")
82+
selected_provinces = provinces_gdf[
83+
provinces_gdf["reg_istat_code"] == st.session_state.selected_region
84+
]
85+
m = create_map(selected_provinces, "prov_name", "prov_istat_code", "selected_province")
86+
st_folium.folium_static(m, height=600)
87+
88+
# Debugging: Check if a province is selected
89+
st.write(f"Selected Province: {st.session_state.selected_province}")
90+
91+
if st.session_state.selected_province:
92+
st.session_state.level = "municipality"
93+
st.session_state.selected_province = st.session_state.selected_province
94+
95+
elif st.session_state.level == "municipality":
96+
st.write(f"### Selected Province: {st.session_state.selected_province}")
97+
selected_municipalities = municipalities_gdf[
98+
municipalities_gdf["prov_istat_code"] == st.session_state.selected_province
99+
]
100+
m = create_map(
101+
selected_municipalities, "name", "op_id", None
102+
)
103+
st_folium.folium_static(m, height=600)
104+
105+
if st.button("Reset"):
106+
st.session_state.level = "region"
107+
st.session_state.selected_region = None
108+
st.session_state.selected_province = None

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ readme = "README.md"
66
requires-python = ">=3.11"
77
dependencies = [
88
"duckdb>=1.1.3",
9+
"streamlit-folium>=0.23.2",
910
"streamlit>=1.40.2",
11+
"geopandas>=1.0.1",
1012
]

0 commit comments

Comments
 (0)