npc0 commited on
Commit
be85e86
·
verified ·
1 Parent(s): 8654ba6

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +236 -71
src/streamlit_app.py CHANGED
@@ -1,77 +1,242 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
- import streamlit as st
5
-
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
  import streamlit as st
17
  from streamlit_folium import st_folium
18
- from folium.plugins import Draw
19
  import folium
20
-
21
- m = folium.Map(location=[40.7128, -74.0060], zoom_start=12)
22
- Draw(export=True).add_to(m)
23
- output = st_folium(m, width=700, height=500)
24
-
25
  import pandas as pd
 
 
26
 
27
- # Login using e.g. `huggingface-cli login` to access this dataset
28
- df = pd.read_parquet("hf://datasets/ProjectMultiplexCoop/PropertyBoundaries/Property_Boundaries_4326.parquet")
29
-
30
- features = [
31
- {"type": row["FEATURE_TYPE"], "geometry": {"type": "Polygon", "coordinates": row["geometry"]}}
32
- for row in df.iterrows()
33
- ]
34
-
35
- color_map = {
36
- "RESERVE": "#00FF00", # Green
37
- "COMMON": "#FF0000", # Red
38
- "CORRIDOR": "#0000FF", # Blue
39
- }
40
-
41
- import geopandas as gpd
42
-
43
- gdf = gpd.GeoDataFrame.from_features(features)
44
-
45
- import folium
46
-
47
- m = folium.Map(location=[43.6534817, -79.3839347], zoom_start=12)
48
-
49
- def style_function(feature):
50
- return {
51
- 'fillColor': color_map.get(feature['properties']['type'], "#888888"),
52
- 'color': color_map.get(feature['properties']['type'], "#888888"),
53
- 'weight': 2,
54
- 'fillOpacity': 0.5
55
  }
56
-
57
- # For GeoJSON features in a list:
58
- geojson = {"type": "FeatureCollection", "features": features}
59
- folium.GeoJson(
60
- geojson,
61
- style_function=style_function
62
- ).add_to(m)
63
-
64
- # Or, for a GeoDataFrame:
65
- # folium.GeoJson(
66
- # gdf.to_json(),
67
- # style_function=style_function
68
- # ).add_to(m)
69
-
70
- m
71
-
72
-
73
-
74
-
75
- # if output and 'last_active_drawing' in output:
76
- # geojson_data = output['last_active_drawing']
77
- # st.write("Geofence drawn:", geojson_data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
  from streamlit_folium import st_folium
 
3
  import folium
4
+ from folium.plugins import Draw
 
 
 
 
5
  import pandas as pd
6
+ from shapely.geometry import Polygon, Point
7
+ import numpy as np
8
 
9
+ st.set_page_config(layout="wide", page_title="Multiplex Coop Map Filter")
10
+
11
+ st.title("🗺️ Multiplex Coop Housing Filter")
12
+ st.write("Draw a polygon on the map to filter the data points within it. Use the form below to apply additional filters based on property attributes.")
13
+
14
+ # --- 1. Create a Sample DataFrame with more attributes ---
15
+ @st.cache_data
16
+ def load_sample_data():
17
+ num_points = 100
18
+ data = {
19
+ 'id': range(1, num_points + 1),
20
+ 'name': [f'Property {i}' for i in range(1, num_points + 1)],
21
+ 'latitude': np.random.uniform(34.03, 34.07, num_points),
22
+ 'longitude': np.random.uniform(-118.28, -118.21, num_points),
23
+ 'zn_type': np.random.choice(['Residential (0)', 'Residential Apartment (101)', 'Commercial Residential (6)'], num_points),
24
+ 'zn_area': np.random.randint(200, 2000, num_points), # Lot Area in Sq Metres
25
+ 'fsi_total': np.round(np.random.uniform(0.5, 3.0, num_points), 2), # Floor Space Index
26
+ 'prcnt_cver': np.random.randint(20, 70, num_points), # Building Percent Coverage
27
+ 'height_metres': np.round(np.random.uniform(5, 30, num_points), 1), # Height in Metres
28
+ 'stories': np.random.randint(2, 10, num_points) # Number of Stories
 
 
 
 
 
 
 
 
29
  }
30
+ df = pd.DataFrame(data)
31
+ return df
32
+
33
+ df = load_sample_data()
34
+
35
+ # Initialize filtered_df with the full dataframe
36
+ filtered_df = df.copy()
37
+
38
+ # --- 2. Initialize the Folium Map with Drawing Tools ---
39
+ # Center the map around the sample data (e.g., Los Angeles area)
40
+ m = folium.Map(location=[df['latitude'].mean(), df['longitude'].mean()], zoom_start=12)
41
+
42
+ # Add drawing tools
43
+ draw = Draw(
44
+ export=True,
45
+ filename="drawn_polygon.geojson",
46
+ position="topleft",
47
+ draw_options={
48
+ "polyline": False,
49
+ "rectangle": False,
50
+ "circlemarker": False,
51
+ "circle": False,
52
+ "marker": False,
53
+ "polygon": {
54
+ "allowIntersection": False,
55
+ "drawError": {
56
+ "color": "#e0115f",
57
+ "message": "Oups!",
58
+ },
59
+ "shapeOptions": {
60
+ "color": "#ef233c",
61
+ "fillOpacity": 0.5,
62
+ },
63
+ },
64
+ },
65
+ edit_options={"edit": False, "remove": True},
66
+ )
67
+ m.add_child(draw)
68
+
69
+ # Add all data points to the map initially (these will be updated after filtering)
70
+ for idx, row in df.iterrows():
71
+ folium.CircleMarker(
72
+ location=[row['latitude'], row['longitude']],
73
+ radius=5,
74
+ color='blue',
75
+ fill=True,
76
+ fill_color='blue',
77
+ fill_opacity=0.7,
78
+ tooltip=(
79
+ f"ID: {row['id']}<br>Name: {row['name']}<br>Zoning: {row['zn_type']}<br>"
80
+ f"Area: {row['zn_area']} m²<br>FSI: {row['fsi_total']}<br>"
81
+ f"Coverage: {row['prcnt_cver']}%<br>Height: {row['height_metres']}m<br>"
82
+ f"Stories: {row['stories']}"
83
+ )
84
+ ).add_to(m)
85
+
86
+ st.subheader("Draw a Polygon on the Map")
87
+ output = st_folium(m, width=1000, height=600, returned_objects=["all_draw_features"])
88
+
89
+ polygon_drawn = False
90
+ shapely_polygon = None
91
+ polygon_coords = None
92
+
93
+ if output and output["all_draw_features"]:
94
+ polygons = [
95
+ feature["geometry"]["coordinates"]
96
+ for feature in output["all_draw_features"]
97
+ if feature["geometry"]["type"] == "Polygon"
98
+ ]
99
+
100
+ if polygons:
101
+ polygon_coords = polygons[-1][0] # Get the last drawn polygon's coordinates
102
+ # Shapely Polygon expects (lon, lat) tuples, Folium gives (lat, lon)
103
+ shapely_polygon = Polygon([(lon, lat) for lat, lon in polygon_coords])
104
+ polygon_drawn = True
105
+
106
+ # Apply spatial filter
107
+ filtered_df = df[
108
+ df.apply(
109
+ lambda row: shapely_polygon.contains(Point(row['longitude'], row['latitude'])),
110
+ axis=1
111
+ )
112
+ ].copy() # Use .copy() to avoid SettingWithCopyWarning
113
+ st.success(f"Initially filtered {len(filtered_df)} points within the drawn polygon.")
114
+ else:
115
+ st.info("Draw a polygon on the map to spatially filter points.")
116
+ else:
117
+ st.info("Draw a polygon on the map to spatially filter points.")
118
+
119
+ # --- 3. Attribute Filtering Form ---
120
+ st.subheader("Filter Property Attributes")
121
+
122
+ with st.form("attribute_filters"):
123
+ col1, col2 = st.columns(2)
124
+
125
+ with col1:
126
+ # Zoning Type
127
+ all_zoning_types = ['All Resdidential Zoning (0, 101, 6)'] + sorted(df['zn_type'].unique().tolist())
128
+ selected_zn_type = st.selectbox("Zoning Type", all_zoning_types, key="zn_type_select")
129
+
130
+ # Lot Area in Sq Metres
131
+ min_zn_area = st.number_input("Minimum Lot Area in Sq Metres", min_value=0, value=0, step=10, key="zn_area_input")
132
+
133
+ # Floor Space Index (FSI)
134
+ min_fsi_total = st.number_input("Minimum Floor Space Index (FSI)", min_value=0.0, value=0.0, step=0.1, format="%.2f", key="fsi_total_input")
135
+
136
+ with col2:
137
+ # Building Percent Coverage
138
+ max_prcnt_cver = st.number_input("Maximum Building Percent Coverage (%)", min_value=0, value=100, step=1, key="prcnt_cver_input")
139
+
140
+ # Height or Stories selection
141
+ height_stories_option = st.radio(
142
+ "Filter by",
143
+ ("Height", "Stories"),
144
+ index=0, # Default to Height
145
+ key="height_stories_radio"
146
+ )
147
+
148
+ # Single input field for height/stories, label changes dynamically
149
+ if height_stories_option == "Height":
150
+ min_height_value = st.number_input("Minimum Height in Metres", min_value=0.0, value=0.0, step=0.1, format="%.1f", key="height_input")
151
+ else: # Stories
152
+ min_stories_value = st.number_input("Minimum Stories", min_value=0, value=0, step=1, key="stories_input")
153
+
154
+ submitted = st.form_submit_button("Apply Attribute Filters")
155
+
156
+ if submitted:
157
+ # Apply attribute filters to the already spatially filtered_df
158
+ if selected_zn_type != 'All Resdidential Zoning (0, 101, 6)':
159
+ filtered_df = filtered_df[filtered_df['zn_type'] == selected_zn_type]
160
+
161
+ if min_zn_area > 0:
162
+ filtered_df = filtered_df[filtered_df['zn_area'] >= min_zn_area]
163
+
164
+ if min_fsi_total > 0:
165
+ filtered_df = filtered_df[filtered_df['fsi_total'] >= min_fsi_total]
166
+
167
+ if max_prcnt_cver < 100: # Assuming 100% means no upper limit applied
168
+ filtered_df = filtered_df[filtered_df['prcnt_cver'] <= max_prcnt_cver]
169
+
170
+ if height_stories_option == "Height" and min_height_value > 0:
171
+ filtered_df = filtered_df[filtered_df['height_metres'] >= min_height_value]
172
+ elif height_stories_option == "Stories" and min_stories_value > 0:
173
+ filtered_df = filtered_df[filtered_df['stories'] >= min_stories_value]
174
+
175
+ st.success(f"Applied attribute filters. Total points after all filters: {len(filtered_df)}")
176
+ else:
177
+ # If form not submitted, the filtered_df remains as it was after spatial filtering
178
+ st.info("Adjust filters and click 'Apply Attribute Filters'.")
179
+
180
+
181
+ # --- 4. Display Filtered Data on a New Map and as a Table ---
182
+ st.subheader("Filtered Data Points")
183
+
184
+ if not filtered_df.empty:
185
+ # Create a new map to show only the filtered points
186
+ # Adjust map center and zoom if filtered_df is very small or empty,
187
+ # otherwise use the original map's center or the filtered_df's center.
188
+ if len(filtered_df) > 0:
189
+ filtered_map_center = [filtered_df['latitude'].mean(), filtered_df['longitude'].mean()]
190
+ filtered_map_zoom = 14 if len(filtered_df) < 5 else 12
191
+ else:
192
+ filtered_map_center = [df['latitude'].mean(), df['longitude'].mean()]
193
+ filtered_map_zoom = 12
194
+
195
+ filtered_m = folium.Map(location=filtered_map_center, zoom_start=filtered_map_zoom)
196
+
197
+ # Add the drawn polygon to the new map if it exists
198
+ if polygon_drawn and polygon_coords:
199
+ folium.Polygon(
200
+ locations=polygon_coords, # Use original (lat,lon) for folium
201
+ color="#ef233c",
202
+ fill=True,
203
+ fill_color="#ef233c",
204
+ fill_opacity=0.5
205
+ ).add_to(filtered_m)
206
+
207
+ # Add filtered points to the new map
208
+ for idx, row in filtered_df.iterrows():
209
+ folium.CircleMarker(
210
+ location=[row['latitude'], row['longitude']],
211
+ radius=7,
212
+ color='green',
213
+ fill=True,
214
+ fill_color='green',
215
+ fill_opacity=0.8,
216
+ tooltip=(
217
+ f"ID: {row['id']}<br>Name: {row['name']}<br>Zoning: {row['zn_type']}<br>"
218
+ f"Area: {row['zn_area']} m²<br>FSI: {row['fsi_total']}<br>"
219
+ f"Coverage: {row['prcnt_cver']}%<br>Height: {row['height_metres']}m<br>"
220
+ f"Stories: {row['stories']}"
221
+ )
222
+ ).add_to(filtered_m)
223
+
224
+ st_folium(filtered_m, width=1000, height=500)
225
+
226
+ st.subheader("Filtered Data Table")
227
+ st.dataframe(filtered_df)
228
+
229
+ # --- 5. Export Data Button ---
230
+ csv = filtered_df.to_csv(index=False).encode('utf-8')
231
+ st.download_button(
232
+ label="Export Filtered Data to CSV",
233
+ data=csv,
234
+ file_name="multiplex_coop_filtered_data.csv",
235
+ mime="text/csv",
236
+ )
237
+
238
+ else:
239
+ st.warning("No data points match the current filters. Try adjusting your criteria or drawing a different polygon.")
240
+
241
+ st.markdown("---")
242
+ st.markdown("This app demonstrates spatial filtering using a drawn polygon and attribute filtering based on the provided HTML structure.")