File size: 10,844 Bytes
a80532f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
import dash_bootstrap_components as dbc
import dash_leaflet as dl
from dash import dcc, html

from config import (
    BASEMAP_URL, BASEMAP_ATTRIBUTION,
    MAP_CENTER, MAP_ZOOM,
    COLOR_POLYGON, COLOR_MMH,
    ANALYSIS_LAYERS, CONTEXT_LAYERS,
    SIDEBAR_BG, CARD_BG, TEXT_PRIMARY, TEXT_MUTED,
)
from queries import get_geography_options


def make_layout() -> html.Div:
    geo_options = get_geography_options()
    
    map_children = [
        dl.TileLayer(
            url=BASEMAP_URL,
            attribution=BASEMAP_ATTRIBUTION,
            maxZoom=20,
        ),

        dl.GeoJSON(id="ctx-villages",   data=None,
                   options={"style": {"color": "#7B8CDE", "weight": 1.5,
                                      "fillColor": "#7B8CDE", "fillOpacity": 0.03}}),
        dl.GeoJSON(id="ctx-zoning",     data=None,
                   options={"style": {"color": "#FF6B6B", "weight": 0.8,
                                      "fillOpacity": 0.0}}),
        dl.GeoJSON(id="ctx-citylimits", data=None,
                   options={"style": {"color": "#FFFFFF", "weight": 2,
                                      "fillOpacity": 0.0, "dashArray": "8 4"}}),
        dl.GeoJSON(id="ctx-dpi",        data=None,
                   options={"style": {"color": "#69F0AE", "weight": 2,
                                      "fillOpacity": 0.0, "dashArray": "4 4"}}),
        dl.GeoJSON(id="ctx-lightrail",  data=None,
                   options={"style": {"color": "#FFD700", "weight": 3,
                                      "fillOpacity": 0.0}}),
        dl.GeoJSON(id="ctx-arterials",  data=None,
                   options={"style": {"color": "#FF9800", "weight": 1.5,
                                      "fillOpacity": 0.0}}),
        dl.GeoJSON(id="ctx-parks",      data=None,
                   options={"style": {"color": "#4CAF50", "weight": 1,
                                      "fillColor": "#4CAF50", "fillOpacity": 0.2}}),

        dl.GeoJSON(
            id="choropleth-layer",
            data=None,
            style=dict(weight=0.8, fillOpacity=0.65,
                       color=COLOR_MMH, fillColor=COLOR_MMH),
            clickData=True,
            zoomToBoundsOnClick=False,
        ),

        dl.GeoJSON(
            id="active-polygon-layer",
            data=None,
            options={"style": {
                "color":       COLOR_POLYGON,
                "weight":      2.5,
                "fillColor":   COLOR_POLYGON,
                "fillOpacity": 0.08,
                "dashArray":   "6 4",
            }},
        ),

        dl.FeatureGroup(children=[
            dl.EditControl(
                id="edit-control",
                position="topleft",
                draw={
                    "polygon":      {"allowIntersection": False,
                                     "shapeOptions": {"color": COLOR_POLYGON,
                                                      "weight": 2}},
                    "rectangle":    {"shapeOptions": {"color": COLOR_POLYGON,
                                                      "weight": 2}},
                    "circle":       False,
                    "circlemarker": False,
                    "marker":       False,
                    "polyline":     False,
                },
                edit={"edit": True, "remove": True},
            ),
        ]),
    ]

    map_component = dl.Map(
        id="main-map",
        center=MAP_CENTER,
        zoom=MAP_ZOOM,
        style={"height": "100vh", "width": "100%"},
        children=map_children,
    )

    layer_panel = html.Div(
        style={
            "position": "absolute", "top": "10px", "right": "10px",
            "zIndex": 1000, "width": "220px",
        },
        children=[
            dbc.Button(
                "⊞  Layers",
                id="layer-panel-toggle",
                size="sm",
                style={
                    "background":    "rgba(22,33,62,0.95)",
                    "border":        "1px solid #2A2A4A",
                    "color":         TEXT_PRIMARY,
                    "width":         "100%",
                    "textAlign":     "left",
                    "marginBottom":  "2px",
                    "fontSize":      "0.8rem",
                },
                n_clicks=0,
            ),
            dbc.Collapse(
                id="layer-panel-collapse",
                is_open=True,
                children=html.Div(
                    style={
                        "background":    "rgba(22,33,62,0.95)",
                        "border":        "1px solid #2A2A4A",
                        "borderRadius":  "4px",
                        "padding":       "10px 12px",
                    },
                    children=[
                        html.P("ANALYSIS LAYER",
                               style={"color": "#7B8CDE", "fontSize": "0.62rem",
                                      "fontWeight": 700, "letterSpacing": "0.1em",
                                      "margin": "0 0 6px"}),
                        dcc.RadioItems(
                            id="analysis-layer-radio",
                            options=[
                                {
                                    "label": html.Span([
                                        html.Span("● ", style={"color": l["color"],
                                                                "fontSize": "1.1em"}),
                                        l["label"],
                                    ], style={"fontSize": "0.8rem",
                                              "color": TEXT_PRIMARY}),
                                    "value": l["value"],
                                }
                                for l in ANALYSIS_LAYERS
                            ],
                            value="mmh",
                            inputStyle={"marginRight": "6px"},
                            labelStyle={"display": "flex", "alignItems": "center",
                                        "marginBottom": "4px"},
                        ),
                        html.Hr(style={"borderColor": "#2A2A4A", "margin": "8px 0"}),
                        html.P("CONTEXT LAYERS",
                               style={"color": "#7B8CDE", "fontSize": "0.62rem",
                                      "fontWeight": 700, "letterSpacing": "0.1em",
                                      "margin": "0 0 6px"}),
                        dcc.Checklist(
                            id="context-layers-checklist",
                            options=[
                                {
                                    "label": html.Span([
                                        html.Span("— ", style={"color": l["color"],
                                                                "fontWeight": 700}),
                                        l["label"],
                                    ], style={"fontSize": "0.8rem",
                                              "color": TEXT_PRIMARY}),
                                    "value": l["value"],
                                }
                                for l in CONTEXT_LAYERS
                            ],
                            value=["villages"],
                            inputStyle={"marginRight": "6px"},
                            labelStyle={"display": "flex", "alignItems": "center",
                                        "marginBottom": "4px"},
                        ),
                    ],
                ),
            ),
        ],
    )

    sidebar = html.Div(
        id="sidebar",
        style={
            "background": SIDEBAR_BG,
            "height":     "100vh",
            "overflowY":  "auto",
            "padding":    "16px",
        },
        children=[
            html.Div([
                html.H5("Phoenix Upzoning Scanner",
                        style={"color": TEXT_PRIMARY, "margin": 0,
                               "fontWeight": 700}),
                html.P("Parcel-level MMH feasibility analysis",
                       style={"color": TEXT_MUTED, "fontSize": "0.8rem",
                               "margin": 0}),
            ], className="mb-3"),

            html.Hr(style={"borderColor": "#333", "margin": "8px 0 16px"}),

            html.P("ANALYZE AREA", className="section-label"),
            dcc.Dropdown(
                id="geography-dropdown",
                options=geo_options,
                value="",
                placeholder="Select Urban Village or TOD district…",
                clearable=True,
                className="dark-dropdown mb-2",
            ),
            html.P(
                "— or draw a polygon on the map —",
                style={"color": TEXT_MUTED, "fontSize": "0.75rem",
                       "textAlign": "center", "margin": "4px 0 12px"},
            ),
            dbc.Button(
                "✕  Clear polygon",
                id="clear-btn",
                color="secondary",
                outline=True,
                size="sm",
                className="w-100 mb-3",
                style={"fontSize": "0.75rem"},
            ),

            html.Hr(style={"borderColor": "#333", "margin": "8px 0 16px"}),

            html.P("FILTER BY ZONE", className="section-label"),
            dcc.Dropdown(
                id="zone-filter-dropdown",
                options=[],
                value=None,
                placeholder="All zones…",
                clearable=True,
                multi=True,
                className="dark-dropdown mb-3",
            ),

            html.Hr(style={"borderColor": "#333", "margin": "8px 0 16px"}),

            html.Div(id="scorecard-area"),
        ],
    )

    parcel_modal = dbc.Modal([
        dbc.ModalHeader(dbc.ModalTitle(id="parcel-modal-title")),
        dbc.ModalBody(id="parcel-modal-body"),
        dbc.ModalFooter(
            dbc.Button("Close", id="parcel-modal-close",
                       className="ms-auto", n_clicks=0)
        ),
    ], id="parcel-modal", size="lg", is_open=False, scrollable=True)

    stores = html.Div([
        dcc.Store(id="active-polygon-store"),
        dcc.Store(id="active-layer-store",  data="mmh"),
        dcc.Store(id="zone-filter-store",   data=None),
    ])

    return html.Div([
        stores,
        parcel_modal,
        html.Div(
            style={"position": "relative", "height": "100vh"},
            children=[
                dbc.Row(
                    [
                        dbc.Col(map_component, width=8, className="p-0"),
                        dbc.Col(sidebar,       width=4, className="p-0"),
                    ],
                    className="g-0",
                    style={"height": "100vh", "overflow": "hidden"},
                ),
                layer_panel,
            ],
        ),
    ])