UjjwalKGupta commited on
Commit
d6afcca
·
verified ·
1 Parent(s): d128cb1

Add Zonalstats for Buffer

Browse files
Files changed (1) hide show
  1. app.py +257 -196
app.py CHANGED
@@ -1,196 +1,257 @@
1
- import os
2
- import ee
3
- import geemap
4
- 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
-
11
- ee_credentials = os.environ.get("EE")
12
- os.makedirs(os.path.expanduser("~/.config/earthengine/"), exist_ok=True)
13
- with open(os.path.expanduser("~/.config/earthengine/credentials"), "w") as f:
14
- f.write(ee_credentials)
15
-
16
- ee.Initialize()
17
-
18
- def convert_3d_to_2d(geometry):
19
- """
20
- Recursively convert any 3D coordinates in a geometry to 2D.
21
- """
22
- if geometry.is_empty:
23
- return geometry
24
-
25
- if geometry.geom_type == 'Polygon':
26
- return geojson.Polygon([[(x, y) for x, y, *_ in ring] for ring in geometry.coordinates])
27
-
28
- elif geometry.geom_type == 'MultiPolygon':
29
- return geojson.MultiPolygon([
30
- [[(x, y) for x, y, *_ in ring] for ring in poly]
31
- for poly in geometry.coordinates
32
- ])
33
-
34
- elif geometry.geom_type == 'LineString':
35
- return geojson.LineString([(x, y) for x, y, *_ in geometry.coordinates])
36
-
37
- elif geometry.geom_type == 'MultiLineString':
38
- return geojson.MultiLineString([
39
- [(x, y) for x, y, *_ in line]
40
- for line in geometry.coordinates
41
- ])
42
-
43
- elif geometry.geom_type == 'Point':
44
- x, y, *_ = geometry.coordinates
45
- return geojson.Point((x, y))
46
-
47
- elif geometry.geom_type == 'MultiPoint':
48
- return geojson.MultiPoint([(x, y) for x, y, *_ in geometry.coordinates])
49
-
50
- return geometry # Return unchanged if not a supported geometry type
51
-
52
- def convert_to_2d_geometry(geom): #Handles Polygon Only
53
- if geom is None:
54
- return None
55
- elif geom.has_z:
56
- # Extract exterior coordinates and convert to 2D
57
- exterior_coords = geom.exterior.coords[:] # Get all coordinates of the exterior ring
58
- exterior_coords_2d = [(x, y) for x, y, *_ in exterior_coords] # Keep only the x and y coordinates, ignoring z
59
-
60
- # Handle interior rings (holes) if any
61
- interior_coords_2d = []
62
- for interior in geom.interiors:
63
- interior_coords = interior.coords[:]
64
- interior_coords_2d.append([(x, y) for x, y, *_ in interior_coords])
65
-
66
- # Create a new Polygon with 2D coordinates
67
- return type(geom)(exterior_coords_2d, interior_coords_2d)
68
- else:
69
- return geom
70
-
71
- def kml_to_gdf(kml_file):
72
- try:
73
- gdf = gpd.read_file(kml_file)
74
- for i in range(len(gdf)):
75
- geom = gdf.iloc[i].geometry
76
- new_geom = convert_to_2d_geometry(geom)
77
- gdf.loc[i, 'geometry'] = new_geom
78
- print(gdf.iloc[i].geometry)
79
- print(f"KML file '{kml_file}' successfully read")
80
- except Exception as e:
81
- print(f"Error: {e}")
82
- return gdf
83
-
84
- def kml_to_geojson(kml_string):
85
- k = kml.KML()
86
- k.from_string(kml_string.encode('utf-8')) # Convert the string to bytes
87
- features = list(k.features())
88
-
89
- geojson_features = []
90
- for feature in features:
91
- geometry_2d = convert_3d_to_2d(feature.geometry)
92
- geojson_features.append(geojson.Feature(geometry=geometry_2d))
93
-
94
- geojson_data = geojson.FeatureCollection(geojson_features)
95
- return geojson_data
96
-
97
- def geojson_to_ee(geojson_data):
98
- ee_object = ee.FeatureCollection(geojson_data)
99
- return ee_object
100
-
101
- def kml_to_gdf(kml_file):
102
- try:
103
- gdf = gpd.read_file(kml_file)
104
- for i in range(len(gdf)):
105
- geom = gdf.iloc[i].geometry
106
- new_geom = convert_to_2d_geometry(geom)
107
- gdf.loc[i, 'geometry'] = new_geom
108
- print(gdf.iloc[i].geometry)
109
- print(f"KML file '{kml_file}' successfully read")
110
- except Exception as e:
111
- print(f"Error: {e}")
112
- return gdf
113
-
114
- # put title in center
115
- st.markdown("""
116
- <style>
117
- h1 {
118
- text-align: center;
119
- }
120
- </style>
121
- """, unsafe_allow_html=True)
122
-
123
- st.title("Mean NDVI Calculator")
124
-
125
- # get the start and end date from the user
126
- col = st.columns(2)
127
- start_date = col[0].date_input("Start Date", value=pd.to_datetime('2021-01-01'))
128
- end_date = col[1].date_input("End Date", value=pd.to_datetime('2021-01-30'))
129
- start_date = start_date.strftime("%Y-%m-%d")
130
- end_date = end_date.strftime("%Y-%m-%d")
131
-
132
- max_cloud_cover = st.number_input("Max Cloud Cover", value=20)
133
-
134
- # Get the geojson file from the user
135
- uploaded_file = st.file_uploader("Upload KML/GeoJSON file", type=["geojson", "kml"])
136
-
137
- # Read the KML file
138
- if uploaded_file is None:
139
- file_name = "Bhankhara_Df_11_he_5_2020-21.geojson"
140
- st.write(f"Using default file: {file_name}")
141
- data = gpd.read_file(file_name)
142
- with open(file_name) as f:
143
- str_data = f.read()
144
- else:
145
- st.write(f"Using uploaded file: {uploaded_file.name}")
146
- file_name = uploaded_file.name
147
- bytes_data = uploaded_file.getvalue()
148
- str_data = bytes_data.decode("utf-8")
149
-
150
-
151
- if file_name.endswith(".geojson"):
152
- geojson_data = json.loads(str_data)
153
- elif file_name.endswith(".kml"):
154
- geojson_data = json.loads(kml_to_gdf(str_data).to_json())
155
- print(geojson_data)
156
-
157
- # Read Geojson File
158
- ee_object = geojson_to_ee(geojson_data)
159
-
160
- # Filter data based on the date, bounds, cloud coverage and select NIR and Red Band
161
- 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'])
162
-
163
- # Print Number of Images in collection
164
- # print("Number of images", collection.size().getInfo())
165
- st.write(f"Number of images: {collection.size().getInfo()}")
166
-
167
- # Calculate NDVI as Normalized Index
168
- def calculate_ndvi(image):
169
- ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI')
170
- return image.addBands(ndvi)
171
-
172
- collection = collection.map(calculate_ndvi)
173
-
174
- # Write Zonalstats into csv file
175
- # out_dir = os.path.join("Output")
176
- # out_NDVI_stats = os.path.join(out_dir, "tmp.csv")
177
-
178
- # if not os.path.exists(out_dir):
179
- # os.makedirs(out_dir)
180
-
181
- geemap.zonal_stats(collection.select(["NDVI"]), ee_object, "tmp.csv", stat_type="mean", scale=10)
182
-
183
- # Show the table
184
- df = pd.read_csv("tmp.csv")
185
- df = df.T
186
- df = df.reset_index()
187
- df = df.iloc[:-2]
188
- df['index'] = pd.to_datetime(df['index'].apply(lambda x: x.split('_')[1].split('T')[0])).dt.strftime('%Y-%m-%d')
189
- df.rename(columns={'index': 'Date', 0: 'Mean NDVI'}, inplace=True)
190
- st.write(df)
191
-
192
- # plot the time series
193
- st.write("Time Series Plot")
194
- st.line_chart(df.set_index('Date'))
195
-
196
- st.write(f"Overall Mean NDVI: {df['Mean NDVI'].mean():.2f}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import ee
3
+ import geemap
4
+ 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
56
+ elif geom.has_z:
57
+ # Extract exterior coordinates and convert to 2D
58
+ exterior_coords = geom.exterior.coords[:] # Get all coordinates of the exterior ring
59
+ exterior_coords_2d = [(x, y) for x, y, *_ in exterior_coords] # Keep only the x and y coordinates, ignoring z
60
+
61
+ # Handle interior rings (holes) if any
62
+ interior_coords_2d = []
63
+ for interior in geom.interiors:
64
+ interior_coords = interior.coords[:]
65
+ interior_coords_2d.append([(x, y) for x, y, *_ in interior_coords])
66
+
67
+ # Create a new Polygon with 2D coordinates
68
+ return type(geom)(exterior_coords_2d, interior_coords_2d)
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 {
107
+ 'corner_points': None,
108
+ 'area': None,
109
+ 'perimeter': None,
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'] = [
122
+ (polygon.bounds[0], polygon.bounds[1]),
123
+ (polygon.bounds[2], polygon.bounds[1]),
124
+ (polygon.bounds[2], polygon.bounds[3]),
125
+ (polygon.bounds[0], polygon.bounds[3])
126
+ ]
127
+
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
136
+
137
+ else:
138
+ polygon_info['is_single_polygon'] = False
139
+ polygon_info['corner_points'] = None
140
+ polygon_info['area'] = None
141
+ polygon_info['perimeter'] = None
142
+ polygon_info['centroid'] = None
143
+ ValueError("Input must be a single Polygon.")
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))
150
+ stats_list = reduced_collection.aggregate_array('NDVI').getInfo()
151
+ filenames = reduced_collection.aggregate_array('system:index').getInfo()
152
+ dates = [f.split("_")[0].split('T')[0] for f in reduced_collection.aggregate_array('system:index').getInfo()]
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>
176
+ h1 {
177
+ text-align: center;
178
+ }
179
+ </style>
180
+ """, unsafe_allow_html=True)
181
+
182
+ st.title("Mean NDVI Calculator")
183
+
184
+ # get the start and end date from the user
185
+ col = st.columns(2)
186
+ start_date = col[0].date_input("Start Date", value=pd.to_datetime('2021-01-01'))
187
+ end_date = col[1].date_input("End Date", value=pd.to_datetime('2021-01-30'))
188
+ start_date = start_date.strftime("%Y-%m-%d")
189
+ end_date = end_date.strftime("%Y-%m-%d")
190
+
191
+ max_cloud_cover = st.number_input("Max Cloud Cover", value=20)
192
+
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.")