File size: 7,101 Bytes
79b3dbd
 
 
 
 
 
 
 
 
4072fc2
79b3dbd
4072fc2
 
 
 
 
 
79b3dbd
 
 
 
 
 
4072fc2
79b3dbd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4072fc2
 
 
 
 
79b3dbd
 
 
 
 
 
 
 
 
4072fc2
79b3dbd
 
 
4072fc2
 
79b3dbd
 
 
 
 
 
 
 
 
 
 
 
4072fc2
 
79b3dbd
 
 
 
 
 
 
 
 
 
 
 
 
 
4072fc2
 
 
 
 
 
 
 
 
 
 
 
 
79b3dbd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4072fc2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import os
import ee
import geemap
import json
import geopandas as gpd
import streamlit as st
import pandas as pd
import geojson
from shapely.geometry import Polygon, MultiPolygon, shape, Point
from io import BytesIO


# Enable fiona driver
gpd.io.file.fiona.drvsupport.supported_drivers['KML'] = 'rw'

#Intialize EE library 
# Error in EE Authentication 
ee_credentials = os.environ.get("EE")
os.makedirs(os.path.expanduser("~/.config/earthengine/"), exist_ok=True)
with open(os.path.expanduser("~/.config/earthengine/credentials"), "w") as f:
    f.write(ee_credentials)
ee.Initialize()

# Functions
def convert_to_2d_geometry(geom): #Handles Polygon Only
  if geom is None:
    return None
  elif geom.has_z:
    # Extract exterior coordinates and convert to 2D
    exterior_coords = geom.exterior.coords[:]  # Get all coordinates of the exterior ring
    exterior_coords_2d = [(x, y) for x, y, *_ in exterior_coords] # Keep only the x and y coordinates, ignoring z

    # Handle interior rings (holes) if any
    interior_coords_2d = []
    for interior in geom.interiors:
        interior_coords = interior.coords[:]
        interior_coords_2d.append([(x, y) for x, y, *_ in interior_coords])

    # Create a new Polygon with 2D coordinates
    return type(geom)(exterior_coords_2d, interior_coords_2d)
  else:
    return geom
  
def validate_KML_file(gdf):
    # try:
    #   gdf = gpd.read_file(BytesIO(uploaded_file.read()), driver='KML')
    # except Exception as e:
    #   ValueError("Input must be a valid KML file.")

    if gdf.empty:
      return {
        'corner_points': None,
        'area': None,
        'perimeter': None,
        'is_single_polygon': False}

    polygon_info = {}

    # Check if it's a single polygon or multipolygon
    if isinstance(gdf.iloc[0].geometry, Polygon):
      polygon_info['is_single_polygon'] = True

      polygon = convert_to_2d_geometry(gdf.geometry.iloc[0])

      # Calculate corner points in GCS projection
      polygon_info['corner_points'] = [
          (polygon.bounds[0], polygon.bounds[1]),
          (polygon.bounds[2], polygon.bounds[1]),
          (polygon.bounds[2], polygon.bounds[3]),
          (polygon.bounds[0], polygon.bounds[3])
      ]

      # Calculate Centroids in GCS projection
      polygon_info['centroid'] = polygon.centroid.coords[0]

      # Calculate area and perimeter in EPSG:7761 projection
      # It is a local projection defined for Gujarat as per NNRMS
      polygon = gdf.to_crs(epsg=7761).geometry.iloc[0]
      polygon_info['area'] = polygon.area
      polygon_info['perimeter'] = polygon.length

    else:
      polygon_info['is_single_polygon'] = False
      polygon_info['corner_points'] = None
      polygon_info['area'] = None
      polygon_info['perimeter'] = None
      polygon_info['centroid'] = None
      ValueError("Input must be a single Polygon.")

    return polygon_info

# Calculate NDVI as Normalized Index
def reduce_zonal_ndvi(image, ee_object):
  ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI')
  image = image.addBands(ndvi)
  image = image.select('NDVI')
  reduced = image.reduceRegion(
    reducer=ee.Reducer.mean(),
    geometry=ee_object.geometry(),
    scale=10,
    maxPixels=1e12
  )
  return image.set(reduced)

# Get Zonal NDVI
def get_zonal_ndvi(collection, geom_ee_object):
  reduced_collection = collection.map(lambda image: reduce_zonal_ndvi(image, ee_object=geom_ee_object))
  stats_list = reduced_collection.aggregate_array('NDVI').getInfo()
  filenames = reduced_collection.aggregate_array('system:index').getInfo()
  dates = [f.split("_")[0].split('T')[0] for f in reduced_collection.aggregate_array('system:index').getInfo()]
  df = pd.DataFrame({'NDVI': stats_list, 'Date': dates, 'Imagery': filenames})
  return df

# put title in center
st.markdown("""
<style>
h1 {
    text-align: center;
}
</style>
""", unsafe_allow_html=True)

st.title("Mean NDVI Calculator")

# get the start and end date from the user
col = st.columns(2)
start_date = col[0].date_input("Start Date", value=pd.to_datetime('2021-01-01'))
end_date = col[1].date_input("End Date", value=pd.to_datetime('2021-01-30'))
start_date = start_date.strftime("%Y-%m-%d")
end_date = end_date.strftime("%Y-%m-%d")

max_cloud_cover = st.number_input("Max Cloud Cover", value=20)

# Get the geojson file from the user
uploaded_file = st.file_uploader("Upload KML/GeoJSON file", type=["geojson", "kml"])



if uploaded_file is not None:
    try:
        if uploaded_file.name.endswith("kml"):
            gdf = gpd.read_file(BytesIO(uploaded_file.read()), driver='KML')
        elif uploaded_file.name.endswith("geojson"):
            gdf = gpd.read_file(uploaded_file)
    except Exception as e:
      st.write('ValueError: "Input must be a valid KML file."')
      st.stop()
    
    # Validate KML File
    polygon_info = validate_KML_file(gdf)

    if polygon_info["is_single_polygon"]==True:
        st.write("Uploaded KML file has single geometry.")
        st.write("It has bounds as {0:.6f}, {1:.6f}, {2:.6f}, and {3:.6f}.".format(
            polygon_info['corner_points'][0][0],
            polygon_info['corner_points'][0][1],
            polygon_info['corner_points'][2][0],
            polygon_info['corner_points'][2][1]
        ))
        st.write("It has centroid at ({0:.6f}, {1:.6f}).".format(polygon_info['centroid'][0], polygon_info['centroid'][1]))
        st.write("It has area of {:.2f} meter squared.".format(polygon_info['area']))
        st.write("It has perimeter of {:.2f} meters.".format(polygon_info['perimeter']))

        # # Read KML file
        # geom_ee_object = ee.FeatureCollection(json.loads(gdf.to_json()))

        # # Add buffer of 100m to ee_object
        # buffered_ee_object = geom_ee_object.map(lambda feature: feature.buffer(100))

        # # Filter data based on the date, bounds, cloud coverage and select NIR and Red Band
        # 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'])

        # # Get Zonal NDVI based on collection and geometries (Original KML and Buffered KML)
        # df_geom = get_zonal_ndvi(collection, geom_ee_object)
        # df_buffered_geom = get_zonal_ndvi(collection, buffered_ee_object)

        # # Merge both Zonalstats and create resultant dataframe
        # resultant_df = pd.merge(df_geom, df_buffered_geom, on='Date', how='inner')
        # resultant_df = resultant_df.rename(columns={'NDVI_x': 'AvgNDVI_Inside', 'NDVI_y': 'Avg_NDVI_Buffer', 'Imagery_x': 'Imagery'})
        # resultant_df['Ratio'] = resultant_df['AvgNDVI_Inside'] / resultant_df['Avg_NDVI_Buffer']
        # resultant_df.drop(columns=['Imagery_y'], inplace=True)

        # # Re-order the columns of the resultant dataframe
        # resultant_df = resultant_df[['Date', 'Imagery', 'AvgNDVI_Inside', 'Avg_NDVI_Buffer', 'Ratio']]

        # st.write(resultant_df)
    
    else:
        st.write('ValueError: "Input must have single polygon geometry"')
        st.write(gdf)
        st.stop()