UjjwalKGupta commited on
Commit
46719a3
·
1 Parent(s): 1676df6

Add Zonal Statistics

Browse files
Files changed (2) hide show
  1. app.py +197 -0
  2. requirement.txt +0 -0
app.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import geojson
9
+ from shapely.geometry import Polygon, MultiPolygon, shape, Point
10
+ from io import BytesIO
11
+ import fiona
12
+
13
+
14
+ # Enable fiona driver
15
+ fiona.drvsupport.supported_drivers['LIBKML'] = 'rw'
16
+
17
+ #Intialize EE library
18
+ # Error in EE Authentication
19
+ # ee_credentials = os.environ.get("EE")
20
+ # os.makedirs(os.path.expanduser("~/.config/earthengine/"), exist_ok=True)
21
+ # with open(os.path.expanduser("~/.config/earthengine/credentials"), "w") as f:
22
+ # f.write(ee_credentials)
23
+ # ee.Initialize()
24
+
25
+ # Functions
26
+ def convert_to_2d_geometry(geom): #Handles Polygon Only
27
+ if geom is None:
28
+ return None
29
+ elif geom.has_z:
30
+ # Extract exterior coordinates and convert to 2D
31
+ exterior_coords = geom.exterior.coords[:] # Get all coordinates of the exterior ring
32
+ exterior_coords_2d = [(x, y) for x, y, *_ in exterior_coords] # Keep only the x and y coordinates, ignoring z
33
+
34
+ # Handle interior rings (holes) if any
35
+ interior_coords_2d = []
36
+ for interior in geom.interiors:
37
+ interior_coords = interior.coords[:]
38
+ interior_coords_2d.append([(x, y) for x, y, *_ in interior_coords])
39
+
40
+ # Create a new Polygon with 2D coordinates
41
+ return type(geom)(exterior_coords_2d, interior_coords_2d)
42
+ else:
43
+ return geom
44
+
45
+ def validate_KML_file(gdf):
46
+ # try:
47
+ # gdf = gpd.read_file(BytesIO(uploaded_file.read()), driver='KML')
48
+ # except Exception as e:
49
+ # ValueError("Input must be a valid KML file.")
50
+
51
+ if gdf.empty:
52
+ return {
53
+ 'corner_points': None,
54
+ 'area': None,
55
+ 'perimeter': None,
56
+ 'is_single_polygon': False}
57
+
58
+ polygon_info = {}
59
+
60
+ # Check if it's a single polygon or multipolygon
61
+ if isinstance(gdf.iloc[0].geometry, Polygon):
62
+ polygon_info['is_single_polygon'] = True
63
+
64
+ polygon = convert_to_2d_geometry(gdf.geometry.iloc[0])
65
+
66
+ # Calculate corner points in GCS projection
67
+ polygon_info['corner_points'] = [
68
+ (polygon.bounds[0], polygon.bounds[1]),
69
+ (polygon.bounds[2], polygon.bounds[1]),
70
+ (polygon.bounds[2], polygon.bounds[3]),
71
+ (polygon.bounds[0], polygon.bounds[3])
72
+ ]
73
+
74
+ # Calculate Centroids in GCS projection
75
+ polygon_info['centroid'] = polygon.centroid.coords[0]
76
+
77
+ # Calculate area and perimeter in EPSG:7761 projection
78
+ # It is a local projection defined for Gujarat as per NNRMS
79
+ polygon = gdf.to_crs(epsg=7761).geometry.iloc[0]
80
+ polygon_info['area'] = polygon.area
81
+ polygon_info['perimeter'] = polygon.length
82
+
83
+ else:
84
+ polygon_info['is_single_polygon'] = False
85
+ polygon_info['corner_points'] = None
86
+ polygon_info['area'] = None
87
+ polygon_info['perimeter'] = None
88
+ polygon_info['centroid'] = None
89
+ ValueError("Input must be a single Polygon.")
90
+
91
+ return polygon_info
92
+
93
+ # Calculate NDVI as Normalized Index
94
+ def reduce_zonal_ndvi(image, ee_object):
95
+ ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI')
96
+ image = image.addBands(ndvi)
97
+ image = image.select('NDVI')
98
+ reduced = image.reduceRegion(
99
+ reducer=ee.Reducer.mean(),
100
+ geometry=ee_object.geometry(),
101
+ scale=10,
102
+ maxPixels=1e12
103
+ )
104
+ return image.set(reduced)
105
+
106
+ # Get Zonal NDVI
107
+ def get_zonal_ndvi(collection, geom_ee_object):
108
+ reduced_collection = collection.map(lambda image: reduce_zonal_ndvi(image, ee_object=geom_ee_object))
109
+ stats_list = reduced_collection.aggregate_array('NDVI').getInfo()
110
+ filenames = reduced_collection.aggregate_array('system:index').getInfo()
111
+ dates = [f.split("_")[0].split('T')[0] for f in reduced_collection.aggregate_array('system:index').getInfo()]
112
+ df = pd.DataFrame({'NDVI': stats_list, 'Date': dates, 'Imagery': filenames})
113
+ return df
114
+
115
+ # put title in center
116
+ st.markdown("""
117
+ <style>
118
+ h1 {
119
+ text-align: center;
120
+ }
121
+ </style>
122
+ """, unsafe_allow_html=True)
123
+
124
+ st.title("Mean NDVI Calculator")
125
+
126
+ # get the start and end date from the user
127
+ col = st.columns(2)
128
+ start_date = col[0].date_input("Start Date", value=pd.to_datetime('2021-01-01'))
129
+ end_date = col[1].date_input("End Date", value=pd.to_datetime('2021-01-30'))
130
+ start_date = start_date.strftime("%Y-%m-%d")
131
+ end_date = end_date.strftime("%Y-%m-%d")
132
+
133
+ max_cloud_cover = st.number_input("Max Cloud Cover", value=20)
134
+
135
+ # Get the geojson file from the user
136
+ uploaded_file = st.file_uploader("Upload KML/GeoJSON file", type=["geojson", "kml"])
137
+
138
+
139
+
140
+ if uploaded_file is not None:
141
+ try:
142
+ if uploaded_file.name.endswith("kml"):
143
+ gdf = gpd.read_file(BytesIO(uploaded_file.read()), driver='LIBKML')
144
+ elif uploaded_file.name.endswith("geojson"):
145
+ gdf = gpd.read_file(uploaded_file)
146
+ except Exception as e:
147
+ st.write('ValueError: "Input must be a valid KML file."')
148
+ st.stop()
149
+
150
+ # Validate KML File
151
+ polygon_info = validate_KML_file(gdf)
152
+
153
+ if polygon_info["is_single_polygon"]==True:
154
+ st.write("Uploaded KML file has single geometry.")
155
+ st.write("It has bounds as {0:.6f}, {1:.6f}, {2:.6f}, and {3:.6f}.".format(
156
+ polygon_info['corner_points'][0][0],
157
+ polygon_info['corner_points'][0][1],
158
+ polygon_info['corner_points'][2][0],
159
+ polygon_info['corner_points'][2][1]
160
+ ))
161
+ st.write("It has centroid at ({0:.6f}, {1:.6f}).".format(polygon_info['centroid'][0], polygon_info['centroid'][1]))
162
+ st.write("It has area of {:.2f} meter squared.".format(polygon_info['area']))
163
+ st.write("It has perimeter of {:.2f} meters.".format(polygon_info['perimeter']))
164
+
165
+ # Read KML file
166
+ # geom_ee_object = ee.FeatureCollection(json.loads(gdf.to_json()))
167
+
168
+ # # Add buffer of 100m to ee_object
169
+ # buffered_ee_object = geom_ee_object.map(lambda feature: feature.buffer(100))
170
+
171
+ # # Filter data based on the date, bounds, cloud coverage and select NIR and Red Band
172
+ # 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'])
173
+
174
+ # # Get Zonal NDVI based on collection and geometries (Original KML and Buffered KML)
175
+ # df_geom = get_zonal_ndvi(collection, geom_ee_object)
176
+ # df_buffered_geom = get_zonal_ndvi(collection, buffered_ee_object)
177
+
178
+ # # Merge both Zonalstats and create resultant dataframe
179
+ # resultant_df = pd.merge(df_geom, df_buffered_geom, on='Date', how='inner')
180
+ # resultant_df = resultant_df.rename(columns={'NDVI_x': 'AvgNDVI_Inside', 'NDVI_y': 'Avg_NDVI_Buffer', 'Imagery_x': 'Imagery'})
181
+ # resultant_df['Ratio'] = resultant_df['AvgNDVI_Inside'] / resultant_df['Avg_NDVI_Buffer']
182
+ # resultant_df.drop(columns=['Imagery_y'], inplace=True)
183
+
184
+ # # Re-order the columns of the resultant dataframe
185
+ # resultant_df = resultant_df[['Date', 'Imagery', 'AvgNDVI_Inside', 'Avg_NDVI_Buffer', 'Ratio']]
186
+
187
+ # st.write(resultant_df)
188
+
189
+ # # plot the time series
190
+ # st.write("Time Series Plot")
191
+ # st.line_chart(resultant_df.set_index('Date'))
192
+
193
+ else:
194
+ st.write('ValueError: "Input must have single polygon geometry"')
195
+ st.write(gdf)
196
+ st.stop()
197
+
requirement.txt ADDED
File without changes