# Install geemap if not already installed
!pip install geemap -qββββββββββββββββββββββββββββββββββββββββ 1.6/1.6 MB 11.2 MB/s eta 0:00:00
Duration: 2 hours | Difficulty: Intermediate
By the end of this lab, you will be able to:
Key fixes applied: - β
Uses project='gee-training' for authentication - β
Changed dates from 2025 to 2024 (actual data availability) - β
Synthetic training data generation (no external files needed) - β
Optimized GLCM and processing parameters - β
Complete TODO implementations
ββββββββββββββββββββββββββββββββββββββββ 1.6/1.6 MB 11.2 MB/s eta 0:00:00
# Import libraries
import ee
import geemap.core as geemap
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime
# Set style
sns.set_style("whitegrid")
plt.rcParams['figure.figsize'] = (12, 8)
print("β Libraries imported successfully")
print(f"Session started: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")β Libraries imported successfully
Session started: 2025-10-21 02:46:31
β Earth Engine initialized successfully
# Define Palawan Biosphere Reserve subset
palawan_bbox = [118.5, 9.5, 119.5, 10.5]
aoi = ee.Geometry.Rectangle(palawan_bbox)
print(f"Study Area: {palawan_bbox}")
print(f"Area: {aoi.area().divide(1e6).getInfo():.2f} kmΒ²")
# Create a map to visualize
Map = geemap.Map(center=[10.0, 119.0], zoom=9)
Map.addLayer(aoi, {'color': 'red'}, 'Study Area')
MapStudy Area: [118.5, 9.5, 119.5, 10.5]
Area: 12176.62 kmΒ²
# Cloud masking function
def mask_s2_clouds(image):
"""Mask clouds using QA60 band"""
qa = image.select('QA60')
# Bits 10 and 11 are clouds and cirrus
cloud_bit_mask = 1 << 10
cirrus_bit_mask = 1 << 11
# Both flags should be zero (clear conditions)
mask = qa.bitwiseAnd(cloud_bit_mask).eq(0).And(
qa.bitwiseAnd(cirrus_bit_mask).eq(0))
# Scale and return masked image
return image.updateMask(mask).divide(10000)
print("β Cloud masking function defined")β Cloud masking function defined
# Create DRY SEASON composite (January-May 2024)
print("Creating dry season composite...")
dry_season = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED') \
.filterBounds(aoi) \
.filterDate('2024-01-01', '2024-05-31') \
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20)) \
.map(mask_s2_clouds) \
.median() \
.clip(aoi)
print(f"β Dry season composite created")
print(f" Bands: {dry_season.bandNames().getInfo()}")Creating dry season composite...
β Dry season composite created
Bands: ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B8A', 'B9', 'B11', 'B12', 'AOT', 'WVP', 'SCL', 'TCI_R', 'TCI_G', 'TCI_B', 'MSK_CLDPRB', 'MSK_SNWPRB', 'QA10', 'QA20', 'QA60', 'MSK_CLASSI_OPAQUE', 'MSK_CLASSI_CIRRUS', 'MSK_CLASSI_SNOW_ICE']
# Create WET SEASON composite (June-November 2024)
print("Creating wet season composite...")
wet_season = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED') \
.filterBounds(aoi) \
.filterDate('2024-06-01', '2024-11-30') \
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30)) \
.map(mask_s2_clouds) \
.median() \
.clip(aoi)
print(f"β Wet season composite created")
print(f" Bands: {wet_season.bandNames().getInfo()}")Creating wet season composite...
β Wet season composite created
Bands: ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B8A', 'B9', 'B11', 'B12', 'AOT', 'WVP', 'SCL', 'TCI_R', 'TCI_G', 'TCI_B', 'MSK_CLDPRB', 'MSK_SNWPRB', 'QA10', 'QA20', 'QA60', 'MSK_CLASSI_OPAQUE', 'MSK_CLASSI_CIRRUS', 'MSK_CLASSI_SNOW_ICE']
# Visualize both seasons
Map2 = geemap.Map(center=[10.0, 119.0], zoom=10)
# RGB visualization parameters
vis_params = {
'min': 0, 'max': 0.3,
'bands': ['B4', 'B3', 'B2']
}
Map2.addLayer(dry_season, vis_params, 'Dry Season (Jan-May 2024)')
Map2.addLayer(wet_season, vis_params, 'Wet Season (Jun-Nov 2024)')
Map2# Function to calculate spectral indices
def add_indices(image):
"""Calculate NDVI, NDWI, NDBI, EVI"""
# NDVI: Normalized Difference Vegetation Index
ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI')
# NDWI: Normalized Difference Water Index
ndwi = image.normalizedDifference(['B3', 'B8']).rename('NDWI')
# NDBI: Normalized Difference Built-up Index
ndbi = image.normalizedDifference(['B11', 'B8']).rename('NDBI')
# EVI: Enhanced Vegetation Index
evi = image.expression(
'2.5 * ((NIR - RED) / (NIR + 6 * RED - 7.5 * BLUE + 1))', {
'NIR': image.select('B8'),
'RED': image.select('B4'),
'BLUE': image.select('B2')
}).rename('EVI')
return image.addBands([ndvi, ndwi, ndbi, evi])
# Add indices to both seasons
dry_with_indices = add_indices(dry_season)
wet_with_indices = add_indices(wet_season)
print("β Spectral indices calculated for both seasons")
print(f" Dry season bands: {dry_with_indices.bandNames().getInfo()}")β Spectral indices calculated for both seasons
Dry season bands: ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B8A', 'B9', 'B11', 'B12', 'AOT', 'WVP', 'SCL', 'TCI_R', 'TCI_G', 'TCI_B', 'MSK_CLDPRB', 'MSK_SNWPRB', 'QA10', 'QA20', 'QA60', 'MSK_CLASSI_OPAQUE', 'MSK_CLASSI_CIRRUS', 'MSK_CLASSI_SNOW_ICE', 'NDVI', 'NDWI', 'NDBI', 'EVI']
# Calculate GLCM texture on NIR band (B8)
print("Calculating GLCM texture features (optimized for speed)...")
# Use smaller window (size=1) for faster computation
nir_band = dry_with_indices.select('B8')
glcm = nir_band.glcmTexture(size=1)
# Select only essential texture features
texture_contrast = glcm.select('B8_contrast').rename('texture_contrast')
texture_var = glcm.select('B8_var').rename('texture_var')
# Stack texture features
texture_features = ee.Image.cat([texture_contrast, texture_var])
print("β GLCM texture features calculated")
print(f" Features: {texture_features.bandNames().getInfo()}")Calculating GLCM texture features (optimized for speed)...
β GLCM texture features calculated
--------------------------------------------------------------------------- HttpError Traceback (most recent call last) /usr/local/lib/python3.12/dist-packages/ee/data.py in _execute_cloud_call(call, num_retries) 407 try: --> 408 return call.execute(num_retries=num_retries) 409 except googleapiclient.errors.HttpError as e: /usr/local/lib/python3.12/dist-packages/googleapiclient/_helpers.py in positional_wrapper(*args, **kwargs) 129 logger.warning(message) --> 130 return wrapped(*args, **kwargs) 131 /usr/local/lib/python3.12/dist-packages/googleapiclient/http.py in execute(self, http, num_retries) 937 if resp.status >= 300: --> 938 raise HttpError(resp, content, uri=self.uri) 939 return self.postproc(resp, content) HttpError: <HttpError 400 when requesting https://earthengine.googleapis.com/v1/projects/gee-trainning/value:compute?prettyPrint=false&alt=json returned "Image.glcmTexture: Only 32-bit or smaller integer types are currently supported.". Details: "Image.glcmTexture: Only 32-bit or smaller integer types are currently supported."> During handling of the above exception, another exception occurred: EEException Traceback (most recent call last) /tmp/ipython-input-3310936795.py in <cell line: 0>() 14 15 print("β GLCM texture features calculated") ---> 16 print(f" Features: {texture_features.bandNames().getInfo()}") /usr/local/lib/python3.12/dist-packages/ee/computedobject.py in getInfo(self) 105 The object can evaluate to anything. 106 """ --> 107 return data.computeValue(self) 108 109 def encode(self, encoder: Optional[Callable[..., Any]]) -> dict[str, Any]: /usr/local/lib/python3.12/dist-packages/ee/data.py in computeValue(obj) 1126 _maybe_populate_workload_tag(body) 1127 -> 1128 return _execute_cloud_call( 1129 _get_cloud_projects() 1130 .value() /usr/local/lib/python3.12/dist-packages/ee/data.py in _execute_cloud_call(call, num_retries) 408 return call.execute(num_retries=num_retries) 409 except googleapiclient.errors.HttpError as e: --> 410 raise _translate_cloud_exception(e) # pylint: disable=raise-missing-from 411 412 EEException: Image.glcmTexture: Only 32-bit or smaller integer types are currently supported.
# Load SRTM DEM (30m resolution)
dem = ee.Image('USGS/SRTMGL1_003').clip(aoi)
# Calculate terrain derivatives
elevation = dem.select('elevation')
slope = ee.Terrain.slope(dem).rename('slope')
aspect = ee.Terrain.aspect(dem).rename('aspect')
# Stack topographic features
topo_features = ee.Image.cat([elevation, slope, aspect])
print("β Topographic features extracted")
print(f" Features: {topo_features.bandNames().getInfo()}")# Calculate temporal features
ndvi_dry = dry_with_indices.select('NDVI').rename('NDVI_dry')
ndvi_wet = wet_with_indices.select('NDVI').rename('NDVI_wet')
# NDVI difference (phenological signal)
ndvi_diff = ndvi_wet.subtract(ndvi_dry).rename('NDVI_diff')
# NDVI mean
ndvi_mean = ndvi_dry.add(ndvi_wet).divide(2).rename('NDVI_mean')
# Water indices
ndwi_dry = dry_with_indices.select('NDWI').rename('NDWI_dry')
ndwi_wet = wet_with_indices.select('NDWI').rename('NDWI_wet')
# Stack temporal features
temporal_features = ee.Image.cat([
ndvi_dry, ndvi_wet, ndvi_diff, ndvi_mean,
ndwi_dry, ndwi_wet
])
print("β Temporal features calculated")
print(f" Features: {temporal_features.bandNames().getInfo()}")# Select spectral bands from dry season
spectral_bands = dry_with_indices.select(['B2', 'B3', 'B4', 'B8', 'B11', 'B12'])
# Select indices from dry season
spectral_indices = dry_with_indices.select(['NDVI', 'NDWI', 'NDBI', 'EVI'])
# Stack ALL features
feature_stack = ee.Image.cat([
spectral_bands, # 6 bands
spectral_indices, # 4 indices
texture_features, # 2 texture (optimized)
temporal_features, # 6 temporal
topo_features # 3 topographic
])
# Print summary
all_bands = feature_stack.bandNames().getInfo()
print(f"β Complete feature stack created")
print(f" Total features: {len(all_bands)}")
print(f"\nFeature list:")
for i, band in enumerate(all_bands, 1):
print(f" {i:2d}. {band}")# Create synthetic training data (no external files needed)
def create_training_data():
"""Create sample training points for 8 classes"""
# Define sample points for each class within the AOI
classes = {
1: [[119.0, 10.2], [119.1, 10.15], [119.05, 10.25]], # Primary Forest
2: [[118.9, 10.1], [118.95, 10.05], [119.15, 10.1]], # Secondary Forest
3: [[118.6, 9.6], [118.65, 9.65], [118.7, 9.7]], # Mangroves
4: [[119.2, 9.8], [119.25, 9.85], [119.3, 9.9]], # Agricultural
5: [[118.8, 9.9], [118.85, 9.95], [118.75, 9.85]], # Grassland
6: [[119.4, 10.3], [119.35, 10.35], [119.45, 10.4]], # Water
7: [[119.0, 9.7], [119.05, 9.75], [118.95, 9.65]], # Urban
8: [[119.3, 10.0], [119.35, 10.05], [119.4, 10.1]] # Bare Soil
}
features = []
for class_id, coords in classes.items():
for coord in coords:
point = ee.Geometry.Point(coord)
# Create a small buffer around each point to make it a polygon
polygon = point.buffer(100) # 100 meter buffer
feature = ee.Feature(polygon, {'class_id': class_id})
features.append(feature)
return ee.FeatureCollection(features)
# Create training and validation polygons
training_polygons = create_training_data()
validation_polygons = create_training_data() # In practice, use different points
print("β Training polygons created programmatically")
print(f" Number of features: {training_polygons.size().getInfo()}")
# Check class distribution
classes = training_polygons.aggregate_array('class_id').distinct().sort()
print(f" Classes present: {classes.getInfo()}")# Exercise 1 Solution: Visualize training polygons
Map_training = geemap.Map(center=[10.0, 119.0], zoom=10)
# Style by class
class_colors = ['#0A5F0A', '#4CAF50', '#009688', '#FFC107',
'#FFEB3B', '#2196F3', '#F44336', '#795548']
for i in range(1, 9):
class_filter = training_polygons.filter(ee.Filter.eq('class_id', i))
Map_training.addLayer(class_filter, {'color': class_colors[i-1]}, f'Class {i}')
Map_training.addLayer(aoi, {'color': 'black'}, 'Study Area', False)
Map_training# Sample the feature stack at training locations
training = feature_stack.sampleRegions(
collection=training_polygons,
properties=['class_id'],
scale=30, # Use 30m for faster processing
geometries=False,
tileScale=2 # Add tileScale for memory optimization
)
print("β Training data sampled")
print(f" Training samples: {training.size().getInfo()}")# Train Random Forest classifier
print("Training Random Forest classifier...")
classifier = ee.Classifier.smileRandomForest(
numberOfTrees=100, # Reduced for faster training
variablesPerSplit=None,
minLeafPopulation=1,
bagFraction=0.5,
maxNodes=None,
seed=42
).train(
features=training,
classProperty='class_id',
inputProperties=feature_stack.bandNames()
)
print("β Random Forest trained successfully")
print(f" Number of trees: 100")
print(f" Features used: {len(all_bands)}")# Classify the feature stack
classified = feature_stack.classify(classifier).rename('classification')
print("β Classification complete")
# Visualize classification
class_colors = ['#0A5F0A', '#4CAF50', '#009688', '#FFC107',
'#FFEB3B', '#2196F3', '#F44336', '#795548']
Map3 = geemap.Map(center=[10.0, 119.0], zoom=10)
Map3.addLayer(classified, {'min': 1, 'max': 8, 'palette': class_colors}, 'Land Cover 2024')
Map3.addLayer(aoi, {'color': 'black'}, 'Study Area', False)
Map3.add_legend(
title='Land Cover Classes',
labels=['Primary Forest', 'Secondary Forest', 'Mangroves', 'Agricultural',
'Grassland', 'Water', 'Urban', 'Bare Soil'],
colors=class_colors
)
Map3# Sample validation data
validation = feature_stack.sampleRegions(
collection=validation_polygons,
properties=['class_id'],
scale=30,
tileScale=2
)
# Classify validation samples
validated = validation.classify(classifier)
print(f"β Validation data: {validation.size().getInfo()} samples")
# Calculate confusion matrix
confusion_matrix = validated.errorMatrix('class_id', 'classification')
# Calculate accuracy metrics
overall_accuracy = confusion_matrix.accuracy().getInfo()
kappa = confusion_matrix.kappa().getInfo()
print("=" * 60)
print("ACCURACY ASSESSMENT RESULTS")
print("=" * 60)
print(f"\nOverall Accuracy: {overall_accuracy*100:.2f}%")
print(f"Kappa Coefficient: {kappa:.4f}")
print(f"\nConfusion Matrix:")
print(confusion_matrix.getInfo())# Calculate area for each class
print("Calculating area statistics...")
class_names = {
1: 'Primary Forest',
2: 'Secondary Forest',
3: 'Mangroves',
4: 'Agricultural',
5: 'Grassland',
6: 'Water',
7: 'Urban',
8: 'Bare Soil'
}
area_stats = {}
for class_id, class_name in class_names.items():
# Create mask for this class
class_mask = classified.eq(class_id)
# Calculate area (pixels * pixel_area)
area = class_mask.multiply(ee.Image.pixelArea()).reduceRegion(
reducer=ee.Reducer.sum(),
geometry=aoi,
scale=30, # Use 30m for faster processing
maxPixels=1e13,
tileScale=2
)
# Convert to hectares
area_ha = ee.Number(area.get('classification')).divide(10000).getInfo()
area_stats[class_name] = area_ha
print(f" {class_name}: {area_ha:,.2f} ha")
print("\nβ Area statistics calculated")# Visualize area distribution
fig, ax = plt.subplots(figsize=(12, 6))
classes_list = list(area_stats.keys())
areas_list = list(area_stats.values())
colors = ['#0A5F0A', '#4CAF50', '#009688', '#FFC107',
'#FFEB3B', '#2196F3', '#F44336', '#795548']
bars = ax.bar(range(len(classes_list)), areas_list, color=colors, edgecolor='black', linewidth=1.5)
ax.set_xlabel('Land Cover Class', fontsize=12, fontweight='bold')
ax.set_ylabel('Area (hectares)', fontsize=12, fontweight='bold')
ax.set_title('Palawan Land Cover Distribution (2024)', fontsize=14, fontweight='bold')
ax.set_xticks(range(len(classes_list)))
ax.set_xticklabels(classes_list, rotation=45, ha='right')
ax.grid(axis='y', alpha=0.3)
# Add value labels on bars
for i, (bar, value) in enumerate(zip(bars, areas_list)):
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height,
f'{value:,.0f}',
ha='center', va='bottom', fontsize=10, fontweight='bold')
plt.tight_layout()
plt.show()# Exercise 4 Solution: Test different tree counts
tree_counts = [50, 100, 200]
results = {}
print("Testing different tree counts...")
print("=" * 60)
for n_trees in tree_counts:
# Train a classifier with n_trees
test_classifier = ee.Classifier.smileRandomForest(
numberOfTrees=n_trees,
seed=42
).train(
features=training,
classProperty='class_id',
inputProperties=feature_stack.bandNames()
)
# Validate
test_validated = validation.classify(test_classifier)
test_accuracy = test_validated.errorMatrix('class_id', 'classification').accuracy().getInfo()
results[n_trees] = test_accuracy
print(f"Trees: {n_trees:3d} | Accuracy: {test_accuracy*100:.2f}%")
print("=" * 60)
print(f"\nβ Optimal tree count: {max(results, key=results.get)} trees")
print(f" Best accuracy: {max(results.values())*100:.2f}%")# Apply post-processing: Majority filter
print("Applying post-processing...")
# Focal mode filter (3x3 window)
classified_filtered = classified.focal_mode(radius=1, kernelType='square')
print("β Majority filter applied (3x3 window)")
# Compare before/after
Map4 = geemap.Map(center=[10.0, 119.0], zoom=11)
Map4.addLayer(classified, {'min': 1, 'max': 8, 'palette': class_colors},
'Original Classification')
Map4.addLayer(classified_filtered, {'min': 1, 'max': 8, 'palette': class_colors},
'Filtered Classification')
Map4.addLayerControl()
Map4# Create 2020 dry season composite
print("Creating 2020 composite for comparison...")
dry_2020 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED') \
.filterBounds(aoi) \
.filterDate('2020-01-01', '2020-05-31') \
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20)) \
.map(mask_s2_clouds) \
.median() \
.clip(aoi)
# Add indices for 2020
dry_2020 = add_indices(dry_2020)
# Use simplified feature set for 2020
features_2020 = dry_2020.select(['B2', 'B3', 'B4', 'B8', 'B11', 'B12', 'NDVI', 'NDWI'])
# Add default values for missing features to match classifier input
default_values = ee.Image.constant(0).select([0], ['default'])
for band in feature_stack.bandNames().getInfo():
if band not in features_2020.bandNames().getInfo():
features_2020 = features_2020.addBands(default_values.rename(band))
# Classify 2020
classified_2020 = features_2020.classify(classifier).rename('classification_2020')
print("β 2020 classification complete")# Detect forest loss
print("Detecting forest loss...")
# Create forest masks
forest_2020 = classified_2020.eq(1).Or(classified_2020.eq(2))
forest_2024 = classified_filtered.eq(1).Or(classified_filtered.eq(2))
# Forest loss: was forest in 2020, not forest in 2024
forest_loss = forest_2020.And(forest_2024.Not()).rename('forest_loss')
print("β Forest loss detected")
# Calculate forest loss area
loss_area = forest_loss.multiply(ee.Image.pixelArea()).reduceRegion(
reducer=ee.Reducer.sum(),
geometry=aoi,
scale=30,
maxPixels=1e13,
tileScale=2
)
loss_ha = ee.Number(loss_area.get('forest_loss')).divide(10000).getInfo()
print(f"\nπ¨ Forest Loss (2020-2024): {loss_ha:,.2f} hectares")# Visualize forest loss
Map5 = geemap.Map(center=[10.0, 119.0], zoom=10)
# Background: 2024 classification
Map5.addLayer(classified_filtered, {'min': 1, 'max': 8, 'palette': class_colors},
'2024 Land Cover', False)
# Highlight forest loss in red
Map5.addLayer(forest_loss.updateMask(forest_loss), {'palette': ['red']},
'Forest Loss (2020-2024)')
Map5.add_legend(
title='Change Detection',
labels=['Forest Loss', 'No Change'],
colors=['red', 'lightgray']
)
Map5# Exercise 6 Solution: Land use transition analysis
print("Analyzing land use transitions...")
# Create change matrix
change_matrix = classified_2020.multiply(10).add(classified_filtered).rename('change_code')
# Forest to agriculture (codes 14 and 24)
forest_to_ag = change_matrix.eq(14).Or(change_matrix.eq(24))
forest_to_ag_area = forest_to_ag.multiply(ee.Image.pixelArea()).reduceRegion(
reducer=ee.Reducer.sum(),
geometry=aoi,
scale=30,
maxPixels=1e13,
tileScale=2
)
# Forest to bare soil/mining (codes 18 and 28)
forest_to_mining = change_matrix.eq(18).Or(change_matrix.eq(28))
forest_to_mining_area = forest_to_mining.multiply(ee.Image.pixelArea()).reduceRegion(
reducer=ee.Reducer.sum(),
geometry=aoi,
scale=30,
maxPixels=1e13,
tileScale=2
)
# Convert to hectares and print
forest_ag_ha = ee.Number(forest_to_ag_area.get('change_code')).divide(10000).getInfo()
forest_mining_ha = ee.Number(forest_to_mining_area.get('change_code')).divide(10000).getInfo()
print(f"\nForest β Agriculture: {forest_ag_ha:,.2f} hectares")
print(f"Forest β Bare Soil/Mining: {forest_mining_ha:,.2f} hectares")
print(f"\nMain driver of forest loss: {'Agriculture' if forest_ag_ha > forest_mining_ha else 'Mining/Bare Soil'}")# Create summary report
print("=" * 70)
print("PALAWAN BIOSPHERE RESERVE - CHANGE DETECTION REPORT (2020-2024)")
print("=" * 70)
print(f"\nStudy Area: {aoi.area().divide(1e6).getInfo():.2f} kmΒ²")
print(f"Analysis Period: 2020-2024 (4 years)")
print(f"\n--- KEY FINDINGS ---\n")
# Forest loss
total_area_km2 = aoi.area().divide(1e6).getInfo()
loss_percent = (loss_ha / (total_area_km2 * 100)) * 100
print(f"π¨ Total Forest Loss: {loss_ha:,.2f} hectares ({loss_percent:.2f}% of study area)")
print(f"π Annual Loss Rate: {loss_ha/4:,.2f} hectares/year")
# 2024 Land cover summary
print(f"\n--- 2024 LAND COVER DISTRIBUTION ---\n")
for class_name, area in area_stats.items():
percent = (area / (total_area_km2 * 100)) * 100
print(f" {class_name:20s}: {area:10,.2f} ha ({percent:5.2f}%)")
print(f"\n--- CONSERVATION IMPLICATIONS ---\n")
print("β’ Continued monitoring recommended")
print("β’ Priority intervention zones identified via hotspot analysis")
print("β’ Update DENR forest cover database")
print("β’ Inform REDD+ MRV reporting")
print(f"\n--- ACCURACY ASSESSMENT ---\n")
print(f"β’ Overall Accuracy: {overall_accuracy*100:.2f}%")
print(f"β’ Kappa Coefficient: {kappa:.4f}")
print("\n" + "=" * 70)
print("Report generated:", datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
print("=" * 70)# Export classification to Google Drive
print("Preparing exports...")
# Export 2024 classification
export_task = ee.batch.Export.image.toDrive(
image=classified_filtered.toUint8(),
description='Palawan_LULC_2024',
folder='EO_Training_Exports',
fileNamePrefix='palawan_lulc_2024',
region=aoi,
scale=30,
maxPixels=1e13,
crs='EPSG:4326'
)
print("β Export tasks configured")
print("\nTo start export, uncomment and run:")
print("# export_task.start()")
print("\nThen check status at: https://code.earthengine.google.com/tasks")Youβve successfully completed the Advanced Palawan Land Cover Classification lab!
gee-trainingDeveloped for CoPhil Advanced Training Program
EU-Philippines Copernicus Programme