Zeel commited on
Commit
4072fc2
1 Parent(s): 79b3dbd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -150
app.py CHANGED
@@ -5,51 +5,23 @@ import json
5
  import geopandas as gpd
6
  import streamlit as st
7
  import pandas as pd
8
- from fastkml import kml
9
  import geojson
10
  from shapely.geometry import Polygon, MultiPolygon, shape, Point
 
11
 
 
 
 
 
 
 
12
  ee_credentials = os.environ.get("EE")
13
  os.makedirs(os.path.expanduser("~/.config/earthengine/"), exist_ok=True)
14
  with open(os.path.expanduser("~/.config/earthengine/credentials"), "w") as f:
15
  f.write(ee_credentials)
16
-
17
  ee.Initialize()
18
 
19
- def convert_3d_to_2d(geometry):
20
- """
21
- Recursively convert any 3D coordinates in a geometry to 2D.
22
- """
23
- if geometry.is_empty:
24
- return geometry
25
-
26
- if geometry.geom_type == 'Polygon':
27
- return geojson.Polygon([[(x, y) for x, y, *_ in ring] for ring in geometry.coordinates])
28
-
29
- elif geometry.geom_type == 'MultiPolygon':
30
- return geojson.MultiPolygon([
31
- [[(x, y) for x, y, *_ in ring] for ring in poly]
32
- for poly in geometry.coordinates
33
- ])
34
-
35
- elif geometry.geom_type == 'LineString':
36
- return geojson.LineString([(x, y) for x, y, *_ in geometry.coordinates])
37
-
38
- elif geometry.geom_type == 'MultiLineString':
39
- return geojson.MultiLineString([
40
- [(x, y) for x, y, *_ in line]
41
- for line in geometry.coordinates
42
- ])
43
-
44
- elif geometry.geom_type == 'Point':
45
- x, y, *_ = geometry.coordinates
46
- return geojson.Point((x, y))
47
-
48
- elif geometry.geom_type == 'MultiPoint':
49
- return geojson.MultiPoint([(x, y) for x, y, *_ in geometry.coordinates])
50
-
51
- return geometry # Return unchanged if not a supported geometry type
52
-
53
  def convert_to_2d_geometry(geom): #Handles Polygon Only
54
  if geom is None:
55
  return None
@@ -69,38 +41,11 @@ def convert_to_2d_geometry(geom): #Handles Polygon Only
69
  else:
70
  return geom
71
 
72
- def kml_to_geojson(kml_string):
73
- k = kml.KML()
74
- k.from_string(kml_string.encode('utf-8')) # Convert the string to bytes
75
- features = list(k.features())
76
-
77
- geojson_features = []
78
- for feature in features:
79
- geometry_2d = convert_3d_to_2d(feature.geometry)
80
- geojson_features.append(geojson.Feature(geometry=geometry_2d))
81
-
82
- geojson_data = geojson.FeatureCollection(geojson_features)
83
- return geojson_data
84
-
85
- # Calculate NDVI as Normalized Index
86
- def reduce_zonal_ndvi(image, ee_object):
87
- ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI')
88
- image = image.addBands(ndvi)
89
- image = image.select('NDVI')
90
- reduced = image.reduceRegion(
91
- reducer=ee.Reducer.mean(),
92
- geometry=ee_object.geometry(),
93
- scale=10,
94
- maxPixels=1e12
95
- )
96
- return image.set(reduced)
97
-
98
- # Validate KML File for Single Polygon and return polygon information
99
- def validate_KML_file(kml_file):
100
- try:
101
- gdf = gpd.read_file(kml_file)
102
- except Exception as e:
103
- ValueError("Input must be a valid KML file.")
104
 
105
  if gdf.empty:
106
  return {
@@ -110,12 +55,12 @@ def validate_KML_file(kml_file):
110
  'is_single_polygon': False}
111
 
112
  polygon_info = {}
113
-
114
  # Check if it's a single polygon or multipolygon
115
  if isinstance(gdf.iloc[0].geometry, Polygon):
116
  polygon_info['is_single_polygon'] = True
117
-
118
- polygon = gdf.geometry.iloc[0]
119
 
120
  # Calculate corner points in GCS projection
121
  polygon_info['corner_points'] = [
@@ -128,8 +73,8 @@ def validate_KML_file(kml_file):
128
  # Calculate Centroids in GCS projection
129
  polygon_info['centroid'] = polygon.centroid.coords[0]
130
 
131
- # Calculate area and perimeter in EPSG:7761 projection
132
- # It is a local projection defined for Gujarat as per NNRMS
133
  polygon = gdf.to_crs(epsg=7761).geometry.iloc[0]
134
  polygon_info['area'] = polygon.area
135
  polygon_info['perimeter'] = polygon.length
@@ -144,6 +89,19 @@ def validate_KML_file(kml_file):
144
 
145
  return polygon_info
146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  # Get Zonal NDVI
148
  def get_zonal_ndvi(collection, geom_ee_object):
149
  reduced_collection = collection.map(lambda image: reduce_zonal_ndvi(image, ee_object=geom_ee_object))
@@ -153,23 +111,6 @@ def get_zonal_ndvi(collection, geom_ee_object):
153
  df = pd.DataFrame({'NDVI': stats_list, 'Date': dates, 'Imagery': filenames})
154
  return df
155
 
156
- def geojson_to_ee(geojson_data):
157
- ee_object = ee.FeatureCollection(geojson_data)
158
- return ee_object
159
-
160
- def kml_to_gdf(kml_file):
161
- try:
162
- gdf = gpd.read_file(kml_file)
163
- for i in range(len(gdf)):
164
- geom = gdf.iloc[i].geometry
165
- new_geom = convert_to_2d_geometry(geom)
166
- gdf.loc[i, 'geometry'] = new_geom
167
- print(gdf.iloc[i].geometry)
168
- print(f"KML file '{kml_file}' successfully read")
169
- except Exception as e:
170
- print(f"Error: {e}")
171
- return gdf
172
-
173
  # put title in center
174
  st.markdown("""
175
  <style>
@@ -193,65 +134,58 @@ max_cloud_cover = st.number_input("Max Cloud Cover", value=20)
193
  # Get the geojson file from the user
194
  uploaded_file = st.file_uploader("Upload KML/GeoJSON file", type=["geojson", "kml"])
195
 
196
- # Read the KML file
197
- if uploaded_file is None:
198
- file_name = "Bhankhara_Df_11_he_5_2020-21.geojson"
199
- st.write(f"Using default file: {file_name}")
200
- data = gpd.read_file(file_name)
201
- with open(file_name) as f:
202
- str_data = f.read()
203
- else:
204
- st.write(f"Using uploaded file: {uploaded_file.name}")
205
- file_name = uploaded_file.name
206
- bytes_data = uploaded_file.getvalue()
207
- str_data = bytes_data.decode("utf-8")
208
-
209
-
210
- if file_name.endswith(".geojson"):
211
- geojson_data = json.loads(str_data)
212
- elif file_name.endswith(".kml"):
213
- geojson_data = json.loads(kml_to_gdf(str_data).to_json())
214
 
215
- # Read Geojson File
216
- ee_object = geojson_to_ee(geojson_data)
217
 
218
- # Filter data based on the date, bounds, cloud coverage and select NIR and Red Band
219
- collection = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED").filterBounds(ee_object).filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', max_cloud_cover)).filter(ee.Filter.date(start_date, end_date)).select(['B4', 'B8'])
220
-
221
- polygon_info = validate_KML_file(str_data)
222
-
223
- if polygon_info['is_single_polygon']:
224
- # Read KML file
225
- geom_ee_object = ee.FeatureCollection(geojson_data)
226
-
227
- # Add buffer of 100m to ee_object
228
- buffered_ee_object = geom_ee_object.map(lambda feature: feature.buffer(100))
229
-
230
- # Filter data based on the date, bounds, cloud coverage and select NIR and Red Band
231
- collection = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED").filterBounds(geom_ee_object).filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20)).filter(ee.Filter.date('2022-01-01', '2023-01-01')).select(['B4', 'B8'])
232
-
233
- # Get Zonal NDVI based on collection and geometries (Original KML and Buffered KML)
234
- df_geom = get_zonal_ndvi(collection, geom_ee_object)
235
- df_buffered_geom = get_zonal_ndvi(collection, buffered_ee_object)
236
-
237
- # Merge both Zonalstats and create resultant dataframe
238
- resultant_df = pd.merge(df_geom, df_buffered_geom, on='Date', how='inner')
239
- resultant_df = resultant_df.rename(columns={'NDVI_x': 'AvgNDVI_Inside', 'NDVI_y': 'Avg_NDVI_Buffer'})
240
- resultant_df['Ratio'] = resultant_df['AvgNDVI_Inside'] / resultant_df['Avg_NDVI_Buffer']
241
- resultant_df.drop(columns=['Imagery_y'], inplace=True)
242
-
243
- # Re-order the columns of the resultant dataframe
244
- resultant_df = resultant_df[['Date', 'Imagery_x', 'AvgNDVI_Inside', 'Avg_NDVI_Buffer', 'Ratio']]
245
-
246
- # Map = geemap.Map(center=(polygon_info['centroid'][1],polygon_info['centroid'][0]) , zoom=12)
247
- # Map.addLayer(geom_ee_object, {}, 'Layer1')
248
- # Map.addLayer(buffered_ee_object, {}, 'Layer2')
249
-
250
- # plot the time series
251
- st.write("Time Series Plot")
252
- st.line_chart(resultant_df.set_index('Date'))
253
-
254
- #st.write(f"Overall Mean NDVI: {resultant_df['Mean NDVI'].mean():.2f}")
255
-
256
- else:
257
- print("Input must be a single Polygon.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  import geopandas as gpd
6
  import streamlit as st
7
  import pandas as pd
 
8
  import geojson
9
  from shapely.geometry import Polygon, MultiPolygon, shape, Point
10
+ from io import BytesIO
11
 
12
+
13
+ # Enable fiona driver
14
+ gpd.io.file.fiona.drvsupport.supported_drivers['KML'] = 'rw'
15
+
16
+ #Intialize EE library
17
+ # Error in EE Authentication
18
  ee_credentials = os.environ.get("EE")
19
  os.makedirs(os.path.expanduser("~/.config/earthengine/"), exist_ok=True)
20
  with open(os.path.expanduser("~/.config/earthengine/credentials"), "w") as f:
21
  f.write(ee_credentials)
 
22
  ee.Initialize()
23
 
24
+ # Functions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  def convert_to_2d_geometry(geom): #Handles Polygon Only
26
  if geom is None:
27
  return None
 
41
  else:
42
  return geom
43
 
44
+ def validate_KML_file(gdf):
45
+ # try:
46
+ # gdf = gpd.read_file(BytesIO(uploaded_file.read()), driver='KML')
47
+ # except Exception as e:
48
+ # ValueError("Input must be a valid KML file.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
  if gdf.empty:
51
  return {
 
55
  'is_single_polygon': False}
56
 
57
  polygon_info = {}
58
+
59
  # Check if it's a single polygon or multipolygon
60
  if isinstance(gdf.iloc[0].geometry, Polygon):
61
  polygon_info['is_single_polygon'] = True
62
+
63
+ polygon = convert_to_2d_geometry(gdf.geometry.iloc[0])
64
 
65
  # Calculate corner points in GCS projection
66
  polygon_info['corner_points'] = [
 
73
  # Calculate Centroids in GCS projection
74
  polygon_info['centroid'] = polygon.centroid.coords[0]
75
 
76
+ # Calculate area and perimeter in EPSG:7761 projection
77
+ # It is a local projection defined for Gujarat as per NNRMS
78
  polygon = gdf.to_crs(epsg=7761).geometry.iloc[0]
79
  polygon_info['area'] = polygon.area
80
  polygon_info['perimeter'] = polygon.length
 
89
 
90
  return polygon_info
91
 
92
+ # Calculate NDVI as Normalized Index
93
+ def reduce_zonal_ndvi(image, ee_object):
94
+ ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI')
95
+ image = image.addBands(ndvi)
96
+ image = image.select('NDVI')
97
+ reduced = image.reduceRegion(
98
+ reducer=ee.Reducer.mean(),
99
+ geometry=ee_object.geometry(),
100
+ scale=10,
101
+ maxPixels=1e12
102
+ )
103
+ return image.set(reduced)
104
+
105
  # Get Zonal NDVI
106
  def get_zonal_ndvi(collection, geom_ee_object):
107
  reduced_collection = collection.map(lambda image: reduce_zonal_ndvi(image, ee_object=geom_ee_object))
 
111
  df = pd.DataFrame({'NDVI': stats_list, 'Date': dates, 'Imagery': filenames})
112
  return df
113
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  # put title in center
115
  st.markdown("""
116
  <style>
 
134
  # Get the geojson file from the user
135
  uploaded_file = st.file_uploader("Upload KML/GeoJSON file", type=["geojson", "kml"])
136
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
 
 
138
 
139
+ if uploaded_file is not None:
140
+ try:
141
+ if uploaded_file.name.endswith("kml"):
142
+ gdf = gpd.read_file(BytesIO(uploaded_file.read()), driver='KML')
143
+ elif uploaded_file.name.endswith("geojson"):
144
+ gdf = gpd.read_file(uploaded_file)
145
+ except Exception as e:
146
+ st.write('ValueError: "Input must be a valid KML file."')
147
+ st.stop()
148
+
149
+ # Validate KML File
150
+ polygon_info = validate_KML_file(gdf)
151
+
152
+ if polygon_info["is_single_polygon"]==True:
153
+ st.write("Uploaded KML file has single geometry.")
154
+ st.write("It has bounds as {0:.6f}, {1:.6f}, {2:.6f}, and {3:.6f}.".format(
155
+ polygon_info['corner_points'][0][0],
156
+ polygon_info['corner_points'][0][1],
157
+ polygon_info['corner_points'][2][0],
158
+ polygon_info['corner_points'][2][1]
159
+ ))
160
+ st.write("It has centroid at ({0:.6f}, {1:.6f}).".format(polygon_info['centroid'][0], polygon_info['centroid'][1]))
161
+ st.write("It has area of {:.2f} meter squared.".format(polygon_info['area']))
162
+ st.write("It has perimeter of {:.2f} meters.".format(polygon_info['perimeter']))
163
+
164
+ # # Read KML file
165
+ # geom_ee_object = ee.FeatureCollection(json.loads(gdf.to_json()))
166
+
167
+ # # Add buffer of 100m to ee_object
168
+ # buffered_ee_object = geom_ee_object.map(lambda feature: feature.buffer(100))
169
+
170
+ # # Filter data based on the date, bounds, cloud coverage and select NIR and Red Band
171
+ # collection = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED").filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', max_cloud_cover)).filter(ee.Filter.date(start_date, end_date)).select(['B4', 'B8'])
172
+
173
+ # # Get Zonal NDVI based on collection and geometries (Original KML and Buffered KML)
174
+ # df_geom = get_zonal_ndvi(collection, geom_ee_object)
175
+ # df_buffered_geom = get_zonal_ndvi(collection, buffered_ee_object)
176
+
177
+ # # Merge both Zonalstats and create resultant dataframe
178
+ # resultant_df = pd.merge(df_geom, df_buffered_geom, on='Date', how='inner')
179
+ # resultant_df = resultant_df.rename(columns={'NDVI_x': 'AvgNDVI_Inside', 'NDVI_y': 'Avg_NDVI_Buffer', 'Imagery_x': 'Imagery'})
180
+ # resultant_df['Ratio'] = resultant_df['AvgNDVI_Inside'] / resultant_df['Avg_NDVI_Buffer']
181
+ # resultant_df.drop(columns=['Imagery_y'], inplace=True)
182
+
183
+ # # Re-order the columns of the resultant dataframe
184
+ # resultant_df = resultant_df[['Date', 'Imagery', 'AvgNDVI_Inside', 'Avg_NDVI_Buffer', 'Ratio']]
185
+
186
+ # st.write(resultant_df)
187
+
188
+ else:
189
+ st.write('ValueError: "Input must have single polygon geometry"')
190
+ st.write(gdf)
191
+ st.stop()