File size: 4,732 Bytes
80e070e | 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 | import ee
import geemap
import geopandas as gpd
import os
import glob
from shapely.geometry import box
# ==========================================
# Authentication & Initialization
# ==========================================
try:
ee.Initialize(project='aerobic-mile-484705-s8')
except Exception:
print("\n--- Google Earth Engine Authentication Required ---")
ee.Authenticate()
ee.Initialize(project='aerobic-mile-484705-s8')
print("Authentication successful!\n")
INPUT_DIR = r'D:\RockGlacier_Project\EDA\individual_polygons'
OUTPUT_DIR = r'D:\RockGlacier_Project\features'
os.makedirs(OUTPUT_DIR, exist_ok=True)
TARGET_CRS_UTM = 'EPSG:32645' # UTM Zone 45N for meter calculations
def get_s2_image(roi):
# Expanded date range for the Himalayas (Monsoon clouds often block strict filters)
# We rely on the pixel-level QA60 mask instead of scene-level filtering.
collection = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED') \
.filterBounds(roi) \
.filterDate('2021-01-01', '2024-01-01')
# Cloud masking function
def mask_s2_clouds(image):
qa = image.select('QA60')
cloudBitMask = 1 << 10
cirrusBitMask = 1 << 11
mask = qa.bitwiseAnd(cloudBitMask).eq(0).And(qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask).divide(10000)
# Apply cloud mask and get median
s2_median = collection.map(mask_s2_clouds).median().clip(roi)
# Calculate indices
ndvi = s2_median.normalizedDifference(['B8', 'B4']).rename('NDVI')
ndwi = s2_median.normalizedDifference(['B3', 'B8']).rename('NDWI')
ndsi = s2_median.normalizedDifference(['B3', 'B11']).rename('NDSI')
# Return selected bands + indices
return s2_median.select(['B2', 'B3', 'B4', 'B8', 'B11', 'B12']).addBands([ndvi, ndwi, ndsi])
def get_terrain_image(roi):
# COPERNICUS DEM is a collection of tiles. We must calculate slope/aspect BEFORE mosaicking
# so that the native 30m projection is preserved for the terrain algorithms.
def add_terrain(img):
slope = ee.Terrain.slope(img).rename('slope')
aspect = ee.Terrain.aspect(img).rename('aspect')
return img.addBands([slope, aspect])
terrain = ee.ImageCollection('COPERNICUS/DEM/GLO30').select('DEM').map(add_terrain).mosaic().clip(roi)
elevation = terrain.select('DEM').rename('elevation')
slope = terrain.select('slope')
aspect = terrain.select('aspect')
return elevation.addBands([slope, aspect])
def main():
geojson_files = sorted(glob.glob(os.path.join(INPUT_DIR, '*.geojson')))
print(f"Found {len(geojson_files)} polygons to process.")
# Process all 626 polygons
for filepath in geojson_files:
filename = os.path.basename(filepath)
idx_str = filename.split('_')[1].split('.')[0]
idx = int(idx_str)
out_tif = os.path.join(OUTPUT_DIR, f"feature_{idx:04d}.tif")
if os.path.exists(out_tif):
print(f"Skipping {idx}, already exists.")
continue
# 1. Read polygon
gdf = gpd.read_file(filepath)
if gdf.empty or gdf.geometry.is_empty.all() or gdf.geometry.isna().all():
print(f"Skipping invalid/empty geometry for polygon {idx}")
continue
# 2. Add 200m Buffer in UTM
gdf_utm = gdf.to_crs(TARGET_CRS_UTM)
bounds = gdf_utm.total_bounds
minx, miny, maxx, maxy = bounds
minx -= 200
miny -= 200
maxx += 200
maxy += 200
# 3. Convert padded bounds back to EPSG:4326 for Earth Engine Bounding Box
padded_box_utm = gpd.GeoDataFrame(geometry=[box(minx, miny, maxx, maxy)], crs=TARGET_CRS_UTM)
padded_box_4326 = padded_box_utm.to_crs('EPSG:4326')
bounds_4326 = padded_box_4326.total_bounds
# Earth Engine ROI
roi = ee.Geometry.BBox(bounds_4326[0], bounds_4326[1], bounds_4326[2], bounds_4326[3])
# 4. Generate Feature Stacks
try:
s2_img = get_s2_image(roi)
terrain_img = get_terrain_image(roi)
# Combine into single 12-band image
final_img = s2_img.addBands(terrain_img)
# 5. Download Image
print(f"Downloading feature stack {idx:04d}...")
geemap.ee_export_image(
final_img,
filename=out_tif,
scale=10,
region=roi,
crs=TARGET_CRS_UTM,
file_per_band=False
)
except Exception as e:
print(f"Failed to process {idx:04d}: {e}")
if __name__ == '__main__':
main()
|