Update src/streamlit_app.py
Browse files- src/streamlit_app.py +107 -88
src/streamlit_app.py
CHANGED
@@ -87,50 +87,49 @@ df = load_and_process_data()
|
|
87 |
# This will be updated based on spatial and attribute filters
|
88 |
filtered_df = df.copy()
|
89 |
|
90 |
-
# --- 2.
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
"
|
104 |
-
|
105 |
-
|
|
|
|
|
106 |
},
|
107 |
-
|
108 |
-
|
109 |
-
)
|
110 |
-
m.add_child(draw)
|
111 |
|
112 |
-
with st.expander("Draw a polygon on the map to spatially filter properties"):
|
113 |
-
st.subheader("Draw a Polygon on the Map")
|
114 |
st.info("Draw a polygon on the map to spatially filter properties. The filtered results will appear below.")
|
115 |
output = st_folium(m, width=1000, height=600, returned_objects=["all_draw_features"])
|
116 |
-
|
117 |
polygon_drawn = False
|
118 |
shapely_polygon = None
|
119 |
polygon_coords = None
|
120 |
-
|
121 |
if output and output["all_draw_features"]:
|
122 |
polygons = [
|
123 |
feature["geometry"]["coordinates"]
|
124 |
for feature in output["all_draw_features"]
|
125 |
if feature["geometry"]["type"] == "Polygon"
|
126 |
]
|
127 |
-
|
128 |
if polygons:
|
129 |
polygon_coords = polygons[-1][0] # Get the coordinates of the last drawn polygon
|
130 |
# Shapely Polygon expects (lon, lat) tuples, Folium provides (lat, lon)
|
131 |
shapely_polygon = Polygon([(lon, lat) for lat, lon in polygon_coords])
|
132 |
polygon_drawn = True
|
133 |
-
|
134 |
# Apply spatial filter to the full dataframe based on centroid containment
|
135 |
filtered_df = df[
|
136 |
df.apply(
|
@@ -203,74 +202,94 @@ with st.form("attribute_filters"):
|
|
203 |
st.info("Adjust filters and click 'Apply Attribute Filters'.")
|
204 |
|
205 |
|
206 |
-
# --- 4. Display Filtered Data on a New Map and as a Table ---
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
|
|
212 |
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
236 |
).add_to(filtered_m)
|
237 |
|
238 |
-
|
239 |
-
filtered_gdf = gpd.GeoDataFrame(filtered_df, geometry='geometry')
|
240 |
-
|
241 |
-
# Add filtered polygons to the map as GeoJSON layer
|
242 |
-
folium.GeoJson(
|
243 |
-
filtered_gdf.to_json(),
|
244 |
-
style_function=lambda x: {
|
245 |
-
'fillColor': 'green',
|
246 |
-
'color': 'darkgreen',
|
247 |
-
'weight': 1,
|
248 |
-
'fillOpacity': 0.7
|
249 |
-
},
|
250 |
-
tooltip=folium.GeoJsonTooltip(
|
251 |
-
fields=['PARCELID', 'zn_type', 'zn_area', 'fsi_total', 'prcnt_cver', 'height_metres', 'stories', 'ADDRESS_NUMBER', 'LINEAR_NAME_FULL'],
|
252 |
-
aliases=['Parcel ID:', 'Zoning Type:', 'Lot Area (m²):', 'FSI:', 'Coverage (%):', 'Height (m):', 'Stories:', 'Address Num:', 'Street:'],
|
253 |
-
localize=True
|
254 |
-
)
|
255 |
-
).add_to(filtered_m)
|
256 |
-
|
257 |
-
st_folium(filtered_m, width=1000, height=500)
|
258 |
|
259 |
-
|
260 |
-
|
261 |
-
|
|
|
262 |
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
271 |
|
272 |
-
else:
|
273 |
-
|
274 |
|
|
|
275 |
st.markdown("---")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
276 |
st.markdown("This app demonstrates spatial and attribute filtering on the ProjectMultiplexCoop/PropertyBoundaries dataset from Hugging Face. FSI, Building Coverage, Height, and Stories are synthetic for demonstration.")
|
|
|
87 |
# This will be updated based on spatial and attribute filters
|
88 |
filtered_df = df.copy()
|
89 |
|
90 |
+
# --- 2. Map for Drawing (now in an expander) ---
|
91 |
+
with st.expander("Draw a Polygon on the Map", expanded=False):
|
92 |
+
# Center the map around the mean of the actual data's centroids
|
93 |
+
m = folium.Map(location=[df['latitude'].mean(), df['longitude'].mean()], zoom_start=12)
|
94 |
+
|
95 |
+
# Add drawing tools to the map
|
96 |
+
draw = Draw(
|
97 |
+
export=True,
|
98 |
+
filename="drawn_polygon.geojson",
|
99 |
+
position="topleft",
|
100 |
+
draw_options={
|
101 |
+
"polyline": False, "rectangle": False, "circlemarker": False,
|
102 |
+
"circle": False, "marker": False,
|
103 |
+
"polygon": {
|
104 |
+
"allowIntersection": False,
|
105 |
+
"drawError": {"color": "#e0115f", "message": "Oups!"},
|
106 |
+
"shapeOptions": {"color": "#ef233c", "fillOpacity": 0.5},
|
107 |
+
},
|
108 |
},
|
109 |
+
edit_options={"edit": False, "remove": True},
|
110 |
+
)
|
111 |
+
m.add_child(draw)
|
|
|
112 |
|
|
|
|
|
113 |
st.info("Draw a polygon on the map to spatially filter properties. The filtered results will appear below.")
|
114 |
output = st_folium(m, width=1000, height=600, returned_objects=["all_draw_features"])
|
115 |
+
|
116 |
polygon_drawn = False
|
117 |
shapely_polygon = None
|
118 |
polygon_coords = None
|
119 |
+
|
120 |
if output and output["all_draw_features"]:
|
121 |
polygons = [
|
122 |
feature["geometry"]["coordinates"]
|
123 |
for feature in output["all_draw_features"]
|
124 |
if feature["geometry"]["type"] == "Polygon"
|
125 |
]
|
126 |
+
|
127 |
if polygons:
|
128 |
polygon_coords = polygons[-1][0] # Get the coordinates of the last drawn polygon
|
129 |
# Shapely Polygon expects (lon, lat) tuples, Folium provides (lat, lon)
|
130 |
shapely_polygon = Polygon([(lon, lat) for lat, lon in polygon_coords])
|
131 |
polygon_drawn = True
|
132 |
+
|
133 |
# Apply spatial filter to the full dataframe based on centroid containment
|
134 |
filtered_df = df[
|
135 |
df.apply(
|
|
|
202 |
st.info("Adjust filters and click 'Apply Attribute Filters'.")
|
203 |
|
204 |
|
205 |
+
# --- 4. Display Filtered Data on a New Map and as a Table (now in an expander) ---
|
206 |
+
# The expander is expanded by default to show results immediately after filtering
|
207 |
+
with st.expander("Filtered Properties Display", expanded=True):
|
208 |
+
if not filtered_df.empty:
|
209 |
+
# Calculate bounds for filtered data to set appropriate zoom
|
210 |
+
min_lat, max_lat = filtered_df['latitude'].min(), filtered_df['latitude'].max()
|
211 |
+
min_lon, max_lon = filtered_df['longitude'].min(), filtered_df['longitude'].max()
|
212 |
|
213 |
+
if min_lat == max_lat and min_lon == max_lon: # Single point case
|
214 |
+
filtered_map_center = [min_lat, min_lon]
|
215 |
+
filtered_map_zoom = 18
|
216 |
+
else:
|
217 |
+
filtered_map_center = [filtered_df['latitude'].mean(), filtered_df['longitude'].mean()]
|
218 |
+
lat_diff = max_lat - min_lat
|
219 |
+
lon_diff = max_lon - min_lon
|
220 |
+
# Heuristic for zoom level
|
221 |
+
if max(lat_diff, lon_diff) < 0.001: filtered_map_zoom = 18
|
222 |
+
elif max(lat_diff, lon_diff) < 0.01: filtered_map_zoom = 16
|
223 |
+
elif max(lat_diff, lon_diff) < 0.1: filtered_map_zoom = 14
|
224 |
+
else: filtered_map_zoom = 12
|
225 |
+
|
226 |
+
filtered_m = folium.Map(location=filtered_map_center, zoom_start=filtered_map_zoom)
|
227 |
+
|
228 |
+
# Add the drawn polygon to the new map if it exists
|
229 |
+
if polygon_drawn and polygon_coords:
|
230 |
+
folium.Polygon(
|
231 |
+
locations=polygon_coords,
|
232 |
+
color="#ef233c",
|
233 |
+
fill=True,
|
234 |
+
fill_color="#ef233c",
|
235 |
+
fill_opacity=0.5
|
236 |
+
).add_to(filtered_m)
|
237 |
+
|
238 |
+
# Convert filtered_df back to GeoDataFrame for direct plotting of geometries
|
239 |
+
filtered_gdf = gpd.GeoDataFrame(filtered_df, geometry='geometry')
|
240 |
+
|
241 |
+
# Add filtered polygons to the map as GeoJSON layer
|
242 |
+
folium.GeoJson(
|
243 |
+
filtered_gdf.to_json(),
|
244 |
+
style_function=lambda x: {
|
245 |
+
'fillColor': 'green',
|
246 |
+
'color': 'darkgreen',
|
247 |
+
'weight': 1,
|
248 |
+
'fillOpacity': 0.7
|
249 |
+
},
|
250 |
+
tooltip=folium.GeoJsonTooltip(
|
251 |
+
fields=['PARCELID', 'zn_type', 'zn_area', 'fsi_total', 'prcnt_cver', 'height_metres', 'stories', 'ADDRESS_NUMBER', 'LINEAR_NAME_FULL'],
|
252 |
+
aliases=['Parcel ID:', 'Zoning Type:', 'Lot Area (m²):', 'FSI:', 'Coverage (%):', 'Height (m):', 'Stories:', 'Address Num:', 'Street:'],
|
253 |
+
localize=True
|
254 |
+
)
|
255 |
).add_to(filtered_m)
|
256 |
|
257 |
+
st_folium(filtered_m, width=1000, height=500)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
258 |
|
259 |
+
st.subheader("Filtered Properties Table")
|
260 |
+
# Limit rows displayed in the dataframe to prevent MessageSizeError
|
261 |
+
MAX_ROWS_DISPLAY = 1000 # Define a reasonable limit
|
262 |
+
display_cols = ['PARCELID', 'zn_type', 'zn_area', 'fsi_total', 'prcnt_cver', 'height_metres', 'stories', 'ADDRESS_NUMBER', 'LINEAR_NAME_FULL']
|
263 |
|
264 |
+
if len(filtered_df) > MAX_ROWS_DISPLAY:
|
265 |
+
st.warning(f"Displaying only the first {MAX_ROWS_DISPLAY} rows of the filtered data ({len(filtered_df)} total properties). Download the full dataset below.")
|
266 |
+
st.dataframe(filtered_df[display_cols].head(MAX_ROWS_DISPLAY))
|
267 |
+
else:
|
268 |
+
st.dataframe(filtered_df[display_cols])
|
269 |
+
|
270 |
+
# --- 5. Export Data Button ---
|
271 |
+
csv = filtered_df.to_csv(index=False).encode('utf-8')
|
272 |
+
st.download_button(
|
273 |
+
label="Export Full Filtered Data to CSV",
|
274 |
+
data=csv,
|
275 |
+
file_name="multiplex_coop_filtered_properties.csv",
|
276 |
+
mime="text/csv",
|
277 |
+
)
|
278 |
|
279 |
+
else:
|
280 |
+
st.warning("No properties match the current filters. Adjust your criteria or draw a polygon on the map.")
|
281 |
|
282 |
+
# Add a note about the MessageSizeError and config option
|
283 |
st.markdown("---")
|
284 |
+
st.markdown(
|
285 |
+
"""
|
286 |
+
**Troubleshooting Large Data:**
|
287 |
+
If you encounter a `MessageSizeError` when displaying a large number of filtered properties,
|
288 |
+
it means the data size exceeds Streamlit's default limit.
|
289 |
+
You can increase this limit by adding `server.maxMessageSize = <size_in_mb>`
|
290 |
+
(e.g., `server.maxMessageSize = 500`) to your Streamlit `config.toml` file.
|
291 |
+
However, be aware that increasing this limit can lead to longer loading times and higher
|
292 |
+
memory consumption in your browser and on the Streamlit server.
|
293 |
+
"""
|
294 |
+
)
|
295 |
st.markdown("This app demonstrates spatial and attribute filtering on the ProjectMultiplexCoop/PropertyBoundaries dataset from Hugging Face. FSI, Building Coverage, Height, and Stories are synthetic for demonstration.")
|