-
Notifications
You must be signed in to change notification settings - Fork 0
/
streamlit_app.py
293 lines (231 loc) · 9.86 KB
/
streamlit_app.py
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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# __IMPORTS__
# sys
from sys import platform
import os
# app
import streamlit as st
from streamlit_js_eval import streamlit_js_eval
# map
import folium
from streamlit_folium import st_folium
from folium.plugins import Fullscreen
from folium.plugins import Draw
from shapely.geometry import Polygon
import geopandas as gpd
# 3d
import pyvista as pv
from stpyvista import stpyvista
from stpyvista.utils import start_xvfb
# own
from src.web.data import *
from src.web.build import *
from src.web.coords import *
from src.web.filter import *
from src.web.map import *
# plot
import plotly.express as px
# __PAGE CONFIGS__
st.set_page_config(
page_title="3D - Mountains",
page_icon="🇨🇭",
layout="wide",
initial_sidebar_state="expanded",
menu_items={
"Get help": "https://github.com/SchabiDesigns/swiss3d/discussions",
"Report a bug": "https://github.com/SchabiDesigns/swiss3d/issues",
"About": """
Developed by [SchabiDesigns](https://schabidesigns.ch).
Data from [swisstopo](https://www.swisstopo.admin.ch).
"""
}
)
st.sidebar.image("media/swiss3d.png")
# __INIT STATES__
if "created" not in st.session_state:
st.session_state["created"] = False
delete_stl()
if "downloaded" not in st.session_state:
st.session_state["downloaded"] = False
if "given_feedback" not in st.session_state:
st.session_state["given_feedback"] = False
if "sponsor" not in st.session_state:
st.session_state["sponsor"] = False
if "points" not in st.session_state:
st.session_state["points"] = []
if platform == "linux" or platform == "linux2":
if "IS_XVFB_RUNNING" not in st.session_state:
start_xvfb()
st.session_state.IS_XVFB_RUNNING = True
# __VARIABLES__
window_height = 1000 # how to set to vh????
DEV = False
# __FUNCTIONS__
@st.cache_data
def read_data(sponsor):
if sponsor:
with st.spinner("loading 25m"):
df, meta = check_cache(file="dhm25")
else:
with st.spinner("loading 200m"):
df, meta = check_cache(file='dhm200')
return df, meta
@st.cache_data
def get_border():
return get_switzerland()
def build_stl(area_3d):
return create_stl(object=area_3d)
def reset_page():
delete_stl()
st.session_state["created"] = False
st.session_state["downloaded"] = False
streamlit_js_eval(js_expressions="parent.window.location.reload()")
# __MAIN PAGE__
if st.session_state["downloaded"] == False:
if st.session_state["created"] == False:
st.sidebar.markdown("""
# Instruction
1. Draw your area in Switzerland
2. Inspect the 3D-model
3. Download STL
""")
button_container = st.sidebar.container(height=100, border=False)
m = folium.Map(attr="swisstopo")
# add drawing tools
Draw(export = False,
draw_options={
"marker" : False,
"circlemarker" : False,
"polyline" : False,
"circle" : False
}).add_to(m)
# add country borders
folium.GeoJson(get_border(),
smooth_factor=2,
style_function=lambda feature: {
"fillColor": "red",
"fillOpacity": 0,
"color": "red",
"weight": 2
}).add_to(m)
# set level of zoom automatically to border
m.fit_bounds(m.get_bounds(), padding=(10, 10))
Fullscreen(position="topleft").add_to(m)
output = st_folium(m, use_container_width=True, height=window_height)
st.session_state["output"] = output
# if something has been drawn
if output["last_active_drawing"] and output["all_drawings"]:
geo = []
# check if drawing is within border
st.session_state["geometry"] = output["last_active_drawing"]["geometry"]
st.session_state["type"] = output["last_active_drawing"]["geometry"]["type"]
if st.session_state["type"]=="Polygon":
# store this information for cutting 3D object
st.session_state["points"] = st.session_state["geometry"]["coordinates"][0]
geo = gpd.GeoSeries(Polygon(st.session_state["points"]), crs="epsg:4326")
else:
st.error("Drawing type not implemented yet... use other drawing object.")
not_in_swiss = not all(geo.covered_by(get_border()))
if not_in_swiss:
button_container.error("The area must be in switzerland")
else:
if button_container.button("Build 3D-object", key="button_build_3d", disabled=not_in_swiss, use_container_width=True, type="primary"):
st.session_state["created"] = True
st.rerun()
else:
button_container.info("No drawings on the map")
if st.session_state["created"] == True:
if st.session_state["type"]=="Polygon":
st.sidebar.markdown("""
# Instruction
1. Draw your area in Switzerland ✔️
2. Inspect the 3D-model ⏳
3. Download STL
""")
button_container = st.sidebar.container(height=100, border=False)
col1, col2 = button_container.columns([0.3, 0.7], gap="small")
if col1.button("Back", use_container_width=True, key="button_inspect_back"):
reset_page()
df, meta = read_data(st.session_state["sponsor"])
with st.spinner("creating your custom 3D model..."):
area = filter_dataframe(df, st.session_state["points"], **meta)
height = area.max().max()
zero = area.min().min()
#add fundament of 10%
zero -= (height-zero)*0.1
mesh = create_surface(area)
model = create_model(mesh,zero)
border = create_border_from_points(st.session_state["points"], zero, height)
area_3d = cut_model(model, border)
pl = pv.Plotter()
pl.add_mesh(area_3d.triangulate())
## Final touches
pl.camera_position = "xz"
pl.camera.azimuth = 0
pl.camera.elevation = 45
pl.reset_camera(bounds=mesh.bounds)
stpyvista(pl, use_container_width=True)
if col2.download_button("Download your STL file", data=build_stl(area_3d), file_name="your_custom_model.stl", type="primary", use_container_width=True, ):
st.session_state["downloaded"] = True
st.rerun()
if st.session_state["downloaded"] == True:
st.sidebar.markdown("""
# Instruction
1. Draw your area in Switzerland ✔️
2. Inspect the 3D-model ✔️
3. Download STL ✔️
""")
button_container = st.sidebar.container(height=100, border=False)
"""
## Done!
You will find your custom model in the **download folder**.
"""
st.text("")
st.divider()
st.subheader("Mini survey")
cols = st.columns((.6,.4), gap="large")
if os.path.isfile("survey_results.pkl"):
survey_results = pd.read_pickle("survey_results.pkl")
df_plot = survey_results.melt(var_name="Feature", value_name="Rating").sort_values("Feature")
fig = px.bar_polar(df_plot, r="Rating", theta="Feature", color="Feature")
fig = px.box(df_plot, color="Feature", orientation="h", y="Feature", x="Rating", range_x=(0,10), title=f"Collected feedback<br><sub>from {survey_results.shape[0]} users")
fig.add_vline(5, line_color="lightgrey", line_dash="dash")
cols[0].plotly_chart(fig, use_container_width=True)
else:
cols[0].info("no survey results available yet...")
if not st.session_state["given_feedback"]:
with cols[1].form("feedback"):
st.markdown("**If you have a minute to share your experience...**")
features = {}
for feature in np.sort(["Usability", "Simplicity", "Design", "Appreciation"]):
features[feature] = st.slider(feature, 0, 10, value=5, help="from 0 ~ horrible to 10 ~ awesome")
# Every form must have a submit button.
submitted = st.form_submit_button("Add your feedback", type="primary", use_container_width=True)
if submitted:
if os.path.isfile("survey_results.pkl"):
survey_result = pd.DataFrame(features, index=[survey_results.shape[0]])
survey_results = pd.concat([survey_results, survey_result])
survey_results.to_pickle("survey_results.pkl")
else:
survey_result = pd.DataFrame(features, index=[0])
survey_result.to_pickle("survey_results.pkl")
st.session_state["given_feedback"] = True
st.rerun()
else:
cols[1].info("Thanks for your feedback!")
if button_container.button("Create another model", use_container_width=True, type="primary", key="button_survey_new"):
reset_page()
# __INFORMATION__
with st.sidebar: # GRID
st.markdown(
"""
## Grid Resolutions
Higher grid resolution means more resources, which results in higher hosting-costs.
🌍 Deployed App is fixed to **200m**.
🖥️ Local up to **25m** is supported.
SPOILER *Grid resolutions up to **0.5m** are possible!*
⚙️ **Contribute**
https://github.com/SchabiDesigns/swiss3d
🍻 **Support**
https://buymeacoffee.com/schabi
"""
)