nascetti-a commited on
Commit
9983e4d
·
verified ·
1 Parent(s): 1cfd239

Test StreamGEE

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +202 -21
src/streamlit_app.py CHANGED
@@ -1,33 +1,214 @@
1
- import os
 
 
2
  import streamlit as st
3
  import ee
4
- from pathlib import Path
 
 
 
 
 
 
 
5
 
6
- # Create a .streamlit/secrets.toml file manually at runtime
7
- def ensure_streamlit_secrets():
8
- secrets_path = Path("/root/.streamlit/secrets.toml")
9
- secrets_path.parent.mkdir(parents=True, exist_ok=True)
10
 
11
- service_account = os.getenv("GEE_SERVICE_ACCOUNT")
12
- private_key = os.getenv("GEE_PRIVATE_KEY")
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
- if not service_account or not private_key:
15
- st.error("❌ Missing GEE credentials in environment variables.")
16
- st.stop()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
- # Create a TOML file Streamlit can read
19
- secrets_content = f'''GEE_SERVICE_ACCOUNT = "{service_account}"
 
 
 
20
 
21
- GEE_PRIVATE_KEY = """{private_key}"""
22
- '''
23
- secrets_path.write_text(secrets_content)
 
24
 
25
- # Ensure the file exists before Streamlit tries to access it
26
- ensure_streamlit_secrets()
 
 
27
 
28
- st.title("Secret Check")
 
 
29
 
30
  try:
31
- st.write("Secrets keys:", list(st.secrets.keys()))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  except Exception as e:
33
- st.error(f"Error loading secrets: {e}")
 
 
 
 
 
 
1
+ #service_account = os.getenv("service_account")
2
+ #private_key = os.getenv("private_key")
3
+
4
  import streamlit as st
5
  import ee
6
+ import geemap.foliumap as geemap
7
+ import base64
8
+ import json
9
+ import tempfile
10
+ import os
11
+ import datetime
12
+ import pandas as pd # Added for data manipulation and plotting
13
+ import altair as alt # Added for custom chart coloring
14
 
15
+ # --- Configuration ---
16
+ st.set_page_config(layout="wide")
17
+ st.title("🇪🇺 European Capitals Satellite Viewer")
 
18
 
19
+ # Define a list of major European capitals and their coordinates (Lon, Lat)
20
+ EUROPEAN_CAPITALS = {
21
+ "Rome, Italy": (12.4964, 41.9028),
22
+ "Stockholm, Sweden": (18.0656, 59.3327),
23
+ "Paris, France": (2.3522, 48.8566),
24
+ "Berlin, Germany": (13.4050, 52.5200),
25
+ "London, UK": (-0.1278, 51.5074),
26
+ "Madrid, Spain": (-3.7038, 40.4168),
27
+ "Vienna, Austria": (16.3738, 48.2082),
28
+ "Athens, Greece": (23.7275, 37.9838),
29
+ "Warsaw, Poland": (21.0118, 52.2297),
30
+ "Amsterdam, Netherlands": (4.8952, 52.3702),
31
+ "Oslo, Norway": (10.7522, 59.9139),
32
+ "Lisbon, Portugal": (-9.1393, 38.7223),
33
+ }
34
 
35
+ # --- Initialize EE (using the temporary file method) ---
36
+
37
+
38
+ @st.cache_resource
39
+ def initialize_ee_session():
40
+ """Initializes the Earth Engine session and caches the result."""
41
+ try:
42
+ # Ensure secrets are available
43
+ SERVICE_ACCOUNT = os.getenv("service_account")
44
+ PRIVATE_KEY_B64 = os.getenv("private_key")
45
+
46
+ # Decode the private key and write it to a temporary file for ee.Initialize
47
+ decoded = base64.b64decode(PRIVATE_KEY_B64).decode("utf-8")
48
+ with tempfile.NamedTemporaryFile(mode="w+", suffix=".json", delete=False) as f:
49
+ f.write(decoded)
50
+ temp_path = f.name
51
+
52
+ credentials = ee.ServiceAccountCredentials(SERVICE_ACCOUNT, temp_path)
53
+ ee.Initialize(credentials)
54
+ os.remove(temp_path)
55
+
56
+ return True
57
+ except Exception as e:
58
+ st.error(f"❌ Error initializing Earth Engine. Check your Streamlit secrets configuration. Error: {e}")
59
+ return False
60
+
61
+
62
+ # Run the initialization only once
63
+ if initialize_ee_session():
64
+ st.success(f"✅ Earth Engine initialized successfully (Cached).")
65
+ else:
66
+ st.stop()
67
+
68
+
69
+ # --- User Inputs ---
70
+ st.sidebar.image("image/logo.png")
71
+
72
+ st.sidebar.header("Controls")
73
+
74
+ selected_city = st.sidebar.selectbox(
75
+ "1. Select a European Capital:",
76
+ options=list(EUROPEAN_CAPITALS.keys())
77
+ )
78
+
79
+ col1, col2 = st.sidebar.columns(2)
80
+ with col1:
81
+ # Fixed: Use datetime.date for Streamlit compatibility
82
+ start_date = st.date_input("2. Start Date:", value=datetime.date(2023, 9, 1))
83
+ with col2:
84
+ # Fixed: Use datetime.date for Streamlit compatibility
85
+ end_date = st.date_input("3. End Date:", value=datetime.date(2024, 3, 1))
86
+
87
+ cloud_filter = st.sidebar.slider(
88
+ "4. Max Cloud Filter (%):",
89
+ min_value=1,
90
+ max_value=100,
91
+ value=15
92
+ )
93
+
94
+
95
+ # --- Processing Logic ---
96
 
97
+ # Get selected city coordinates
98
+ lon, lat = EUROPEAN_CAPITALS[selected_city]
99
+ # Define a buffer around the city point (e.g., 25km radius)
100
+ city_point = ee.Geometry.Point([lon, lat])
101
+ aoi = city_point.buffer(25000)
102
 
103
+ # 1. Collection filtered ONLY by date and bounds (used for comprehensive plotting)
104
+ s2_unfiltered_collection = ee.ImageCollection("COPERNICUS/S2_HARMONIZED") \
105
+ .filterDate(start_date.isoformat(), end_date.isoformat()) \
106
+ .filterBounds(aoi)
107
 
108
+ # 2. Collection filtered by date, bounds, AND cloud percentage (used for composite)
109
+ # This ensures only images under the cloud_filter threshold are used for the median composite.
110
+ s2_composite_collection = s2_unfiltered_collection \
111
+ .filterMetadata('CLOUDY_PIXEL_PERCENTAGE', 'less_than', cloud_filter)
112
 
113
+ # Calculate the size of the *composite* collection (blocking call)
114
+ collection_size = s2_composite_collection.size().getInfo()
115
+ unfiltered_collection_size = s2_unfiltered_collection.size().getInfo()
116
 
117
  try:
118
+ if collection_size == 0:
119
+ st.warning(
120
+ f"⚠️ No Sentinel-2 images found for **{selected_city}** that meet the **{cloud_filter}%** max cloudiness filter. Try expanding the date range or increasing the cloud filter.")
121
+ # Create a map centered on the city even if no image is found
122
+ Map = geemap.Map(center=[lat, lon], zoom=11, plugin_Draw=False)
123
+ Map.to_streamlit(width=800, height=500)
124
+ st.stop()
125
+ else:
126
+ # Calculate the median composite image
127
+ s2_composite = s2_composite_collection.median()
128
+
129
+ # --- Data Extraction for Plotting (Using UNFILTERED Collection) ---
130
+ # Get list of properties for each image in the UNFILTERED collection
131
+ feature_list = s2_unfiltered_collection.toList(unfiltered_collection_size).getInfo()
132
+ data_for_df = []
133
+ for feature in feature_list:
134
+ props = feature['properties']
135
+ data_for_df.append({
136
+ 'Acquisition Date': props['system:time_start'],
137
+ 'Cloudiness (%)': props['CLOUDY_PIXEL_PERCENTAGE']
138
+ })
139
+
140
+ # Convert to Pandas DataFrame and format the date
141
+ df = pd.DataFrame(data_for_df)
142
+ # Convert Earth Engine Unix timestamp (milliseconds) to datetime objects
143
+ df['Acquisition Date'] = pd.to_datetime(df['Acquisition Date'], unit='ms')
144
+ df = df.set_index('Acquisition Date').sort_index()
145
+
146
+ # Add color column based on the user's filter threshold (Blue <= threshold, Red > threshold)
147
+ df['Color'] = df['Cloudiness (%)'].apply(
148
+ lambda x: 'blue' if x <= cloud_filter else 'red'
149
+ )
150
+
151
+ # Calculate the average cloudiness of the source images (from the UNFILTERED set for proper reporting)
152
+ mean_cloud_percentage = s2_unfiltered_collection.aggregate_mean('CLOUDY_PIXEL_PERCENTAGE').getInfo()
153
+
154
+ # Display analysis results
155
+ st.subheader(f"Data Analysis for {selected_city}")
156
+ st.info(f"📸 Total available images (date/bounds filtered): **{unfiltered_collection_size}**")
157
+ st.info(f"✅ Images used for composite (under {cloud_filter}% cloudiness): **{collection_size}**")
158
+ st.info(f"☁️ Average Cloudiness of all available images: **{mean_cloud_percentage:.2f}%**")
159
+
160
+ # --- PLOT CLOUDINESS OVER TIME with Conditional Colors using Altair ---
161
+ st.subheader("Cloudiness Over Time vs. Filter Threshold")
162
+ st.markdown(
163
+ f"Bars are colored **blue** if cloudiness is below the **{cloud_filter}%** threshold (used for composite) and **red** if above.")
164
+
165
+ # Define custom color scale to ensure blue and red are used
166
+ color_scale = alt.Scale(domain=['blue', 'red'], range=['blue', 'red'])
167
+
168
+ # Create the Altair chart
169
+ chart = alt.Chart(df.reset_index()).mark_bar().encode(
170
+ x=alt.X('Acquisition Date', title='Acquisition Date'),
171
+ y=alt.Y('Cloudiness (%)', title='Cloudiness (%)'),
172
+ color=alt.Color('Color', scale=color_scale), # Use the pre-calculated color column
173
+ tooltip=['Acquisition Date', 'Cloudiness (%)']
174
+ ).properties(
175
+ height=300
176
+ ).interactive() # Make the chart zoomable/pannable
177
+
178
+ # Add a horizontal line to represent the user's cloud filter threshold
179
+ rule = alt.Chart(pd.DataFrame({'y': [cloud_filter]})).mark_rule(color='green', strokeDash=[5, 5]).encode(
180
+ y='y'
181
+ )
182
+
183
+ st.altair_chart(chart + rule, use_container_width=True)
184
+ # --- END PLOT ---
185
+
186
+ # Visualization parameters (Natural Color RGB)
187
+ vis_params = {
188
+ "bands": ["B4", "B3", "B2"],
189
+ "min": 0,
190
+ "max": 3000,
191
+ "gamma": 1.4
192
+ }
193
+
194
+ # Create a map centered on the selected city
195
+ Map = geemap.Map(center=[lat, lon], zoom=10)
196
+
197
+ # Add the composite layer to the map
198
+ Map.addLayer(s2_composite, vis_params, f"Sentinel-2 Composite: {selected_city}")
199
+
200
+ # Add a marker for the capital city center
201
+ Map.add_marker([lat, lon], tooltip=selected_city)
202
+ #Map.add_ee_layer(aoi.bounds(), {'color': 'red'}, 'Area of Interest')
203
+
204
+ # Display the map in Streamlit
205
+ st.subheader("Satellite Composite Visualization")
206
+ Map.to_streamlit(width=900, height=600)
207
+
208
  except Exception as e:
209
+ st.error(f"An Earth Engine error occurred during processing: {e}")
210
+
211
+ st.markdown("""
212
+ ---
213
+ *Data Source: ESA Copernicus Sentinel-2 Level 2A data via Google Earth Engine.*
214
+ """)