File size: 9,542 Bytes
10d0bac
 
 
be85e86
2619083
be85e86
 
2619083
be85e86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2619083
be85e86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
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
237
238
239
240
241
242
import streamlit as st
from streamlit_folium import st_folium
import folium
from folium.plugins import Draw
import pandas as pd
from shapely.geometry import Polygon, Point
import numpy as np

st.set_page_config(layout="wide", page_title="Multiplex Coop Map Filter")

st.title("🗺️ Multiplex Coop Housing Filter")
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.")

# --- 1. Create a Sample DataFrame with more attributes ---
@st.cache_data
def load_sample_data():
    num_points = 100
    data = {
        'id': range(1, num_points + 1),
        'name': [f'Property {i}' for i in range(1, num_points + 1)],
        'latitude': np.random.uniform(34.03, 34.07, num_points),
        'longitude': np.random.uniform(-118.28, -118.21, num_points),
        'zn_type': np.random.choice(['Residential (0)', 'Residential Apartment (101)', 'Commercial Residential (6)'], num_points),
        'zn_area': np.random.randint(200, 2000, num_points), # Lot Area in Sq Metres
        'fsi_total': np.round(np.random.uniform(0.5, 3.0, num_points), 2), # Floor Space Index
        'prcnt_cver': np.random.randint(20, 70, num_points), # Building Percent Coverage
        'height_metres': np.round(np.random.uniform(5, 30, num_points), 1), # Height in Metres
        'stories': np.random.randint(2, 10, num_points) # Number of Stories
    }
    df = pd.DataFrame(data)
    return df

df = load_sample_data()

# Initialize filtered_df with the full dataframe
filtered_df = df.copy()

# --- 2. Initialize the Folium Map with Drawing Tools ---
# Center the map around the sample data (e.g., Los Angeles area)
m = folium.Map(location=[df['latitude'].mean(), df['longitude'].mean()], zoom_start=12)

# Add drawing tools
draw = Draw(
    export=True,
    filename="drawn_polygon.geojson",
    position="topleft",
    draw_options={
        "polyline": False,
        "rectangle": False,
        "circlemarker": False,
        "circle": False,
        "marker": False,
        "polygon": {
            "allowIntersection": False,
            "drawError": {
                "color": "#e0115f",
                "message": "Oups!",
            },
            "shapeOptions": {
                "color": "#ef233c",
                "fillOpacity": 0.5,
            },
        },
    },
    edit_options={"edit": False, "remove": True},
)
m.add_child(draw)

# Add all data points to the map initially (these will be updated after filtering)
for idx, row in df.iterrows():
    folium.CircleMarker(
        location=[row['latitude'], row['longitude']],
        radius=5,
        color='blue',
        fill=True,
        fill_color='blue',
        fill_opacity=0.7,
        tooltip=(
            f"ID: {row['id']}<br>Name: {row['name']}<br>Zoning: {row['zn_type']}<br>"
            f"Area: {row['zn_area']} m²<br>FSI: {row['fsi_total']}<br>"
            f"Coverage: {row['prcnt_cver']}%<br>Height: {row['height_metres']}m<br>"
            f"Stories: {row['stories']}"
        )
    ).add_to(m)

st.subheader("Draw a Polygon on the Map")
output = st_folium(m, width=1000, height=600, returned_objects=["all_draw_features"])

polygon_drawn = False
shapely_polygon = None
polygon_coords = None

if output and output["all_draw_features"]:
    polygons = [
        feature["geometry"]["coordinates"]
        for feature in output["all_draw_features"]
        if feature["geometry"]["type"] == "Polygon"
    ]

    if polygons:
        polygon_coords = polygons[-1][0] # Get the last drawn polygon's coordinates
        # Shapely Polygon expects (lon, lat) tuples, Folium gives (lat, lon)
        shapely_polygon = Polygon([(lon, lat) for lat, lon in polygon_coords])
        polygon_drawn = True

        # Apply spatial filter
        filtered_df = df[
            df.apply(
                lambda row: shapely_polygon.contains(Point(row['longitude'], row['latitude'])),
                axis=1
            )
        ].copy() # Use .copy() to avoid SettingWithCopyWarning
        st.success(f"Initially filtered {len(filtered_df)} points within the drawn polygon.")
    else:
        st.info("Draw a polygon on the map to spatially filter points.")
else:
    st.info("Draw a polygon on the map to spatially filter points.")

# --- 3. Attribute Filtering Form ---
st.subheader("Filter Property Attributes")

with st.form("attribute_filters"):
    col1, col2 = st.columns(2)

    with col1:
        # Zoning Type
        all_zoning_types = ['All Resdidential Zoning (0, 101, 6)'] + sorted(df['zn_type'].unique().tolist())
        selected_zn_type = st.selectbox("Zoning Type", all_zoning_types, key="zn_type_select")

        # Lot Area in Sq Metres
        min_zn_area = st.number_input("Minimum Lot Area in Sq Metres", min_value=0, value=0, step=10, key="zn_area_input")

        # Floor Space Index (FSI)
        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")

    with col2:
        # Building Percent Coverage
        max_prcnt_cver = st.number_input("Maximum Building Percent Coverage (%)", min_value=0, value=100, step=1, key="prcnt_cver_input")

        # Height or Stories selection
        height_stories_option = st.radio(
            "Filter by",
            ("Height", "Stories"),
            index=0, # Default to Height
            key="height_stories_radio"
        )

        # Single input field for height/stories, label changes dynamically
        if height_stories_option == "Height":
            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")
        else: # Stories
            min_stories_value = st.number_input("Minimum Stories", min_value=0, value=0, step=1, key="stories_input")

    submitted = st.form_submit_button("Apply Attribute Filters")

    if submitted:
        # Apply attribute filters to the already spatially filtered_df
        if selected_zn_type != 'All Resdidential Zoning (0, 101, 6)':
            filtered_df = filtered_df[filtered_df['zn_type'] == selected_zn_type]

        if min_zn_area > 0:
            filtered_df = filtered_df[filtered_df['zn_area'] >= min_zn_area]

        if min_fsi_total > 0:
            filtered_df = filtered_df[filtered_df['fsi_total'] >= min_fsi_total]

        if max_prcnt_cver < 100: # Assuming 100% means no upper limit applied
            filtered_df = filtered_df[filtered_df['prcnt_cver'] <= max_prcnt_cver]

        if height_stories_option == "Height" and min_height_value > 0:
            filtered_df = filtered_df[filtered_df['height_metres'] >= min_height_value]
        elif height_stories_option == "Stories" and min_stories_value > 0:
            filtered_df = filtered_df[filtered_df['stories'] >= min_stories_value]

        st.success(f"Applied attribute filters. Total points after all filters: {len(filtered_df)}")
    else:
        # If form not submitted, the filtered_df remains as it was after spatial filtering
        st.info("Adjust filters and click 'Apply Attribute Filters'.")


# --- 4. Display Filtered Data on a New Map and as a Table ---
st.subheader("Filtered Data Points")

if not filtered_df.empty:
    # Create a new map to show only the filtered points
    # Adjust map center and zoom if filtered_df is very small or empty,
    # otherwise use the original map's center or the filtered_df's center.
    if len(filtered_df) > 0:
        filtered_map_center = [filtered_df['latitude'].mean(), filtered_df['longitude'].mean()]
        filtered_map_zoom = 14 if len(filtered_df) < 5 else 12
    else:
        filtered_map_center = [df['latitude'].mean(), df['longitude'].mean()]
        filtered_map_zoom = 12

    filtered_m = folium.Map(location=filtered_map_center, zoom_start=filtered_map_zoom)

    # Add the drawn polygon to the new map if it exists
    if polygon_drawn and polygon_coords:
        folium.Polygon(
            locations=polygon_coords, # Use original (lat,lon) for folium
            color="#ef233c",
            fill=True,
            fill_color="#ef233c",
            fill_opacity=0.5
        ).add_to(filtered_m)

    # Add filtered points to the new map
    for idx, row in filtered_df.iterrows():
        folium.CircleMarker(
            location=[row['latitude'], row['longitude']],
            radius=7,
            color='green',
            fill=True,
            fill_color='green',
            fill_opacity=0.8,
            tooltip=(
                f"ID: {row['id']}<br>Name: {row['name']}<br>Zoning: {row['zn_type']}<br>"
                f"Area: {row['zn_area']} m²<br>FSI: {row['fsi_total']}<br>"
                f"Coverage: {row['prcnt_cver']}%<br>Height: {row['height_metres']}m<br>"
                f"Stories: {row['stories']}"
            )
        ).add_to(filtered_m)

    st_folium(filtered_m, width=1000, height=500)

    st.subheader("Filtered Data Table")
    st.dataframe(filtered_df)

    # --- 5. Export Data Button ---
    csv = filtered_df.to_csv(index=False).encode('utf-8')
    st.download_button(
        label="Export Filtered Data to CSV",
        data=csv,
        file_name="multiplex_coop_filtered_data.csv",
        mime="text/csv",
    )

else:
    st.warning("No data points match the current filters. Try adjusting your criteria or drawing a different polygon.")

st.markdown("---")
st.markdown("This app demonstrates spatial filtering using a drawn polygon and attribute filtering based on the provided HTML structure.")