Simple Calculations

Learn to perform basic mathematical operations and calculate spectral indices in Earth Engine.

What You’ll Learn

  • Basic arithmetic operations on images

  • Calculating common spectral indices

  • Working with image bands and band math

  • Understanding Earth Engine expressions

  • Conditional operations and masking

Prerequisites

  • Completed first script and image display examples

  • Understanding of remote sensing concepts

  • Basic knowledge of spectral indices

  • Familiarity with Python programming

Basic Mathematical Operations

Earth Engine supports standard mathematical operations:

import ee

# Initialize Earth Engine
ee.Initialize(project='your-project-id')

# Load a Landsat 8 image
image = ee.Image('LANDSAT/LC08/C02/T1_L2/LC08_044034_20140318')

# Apply scaling factors
image = image.select('SR_B.').multiply(0.0000275).add(-0.2)

# Basic arithmetic
red = image.select('SR_B4')
nir = image.select('SR_B5')

# Addition
sum_bands = red.add(nir)

# Subtraction
difference = nir.subtract(red)

# Multiplication
product = red.multiply(nir)

# Division
ratio = nir.divide(red)

Common Spectral Indices

NDVI (Normalized Difference Vegetation Index):

# NDVI = (NIR - Red) / (NIR + Red)
ndvi = image.normalizedDifference(['SR_B5', 'SR_B4']).rename('NDVI')

print("NDVI range: -1 to 1")
print("Values > 0.3 typically indicate vegetation")

NDWI (Normalized Difference Water Index):

# NDWI = (Green - NIR) / (Green + NIR)
ndwi = image.normalizedDifference(['SR_B3', 'SR_B5']).rename('NDWI')

print("NDWI > 0 typically indicates water")

EVI (Enhanced Vegetation Index):

# EVI = 2.5 * ((NIR - Red) / (NIR + 6 * Red - 7.5 * Blue + 1))
evi = image.expression(
    '2.5 * ((NIR - RED) / (NIR + 6 * RED - 7.5 * BLUE + 1))',
    {
        'NIR': image.select('SR_B5'),
        'RED': image.select('SR_B4'),
        'BLUE': image.select('SR_B2')
    }
).rename('EVI')

Using ee.Image.expression()

For complex calculations, use expressions:

# Custom vegetation index
custom_vi = image.expression(
    '(NIR * 2.5 - RED * 1.5) / (NIR + RED + 1)',
    {
        'NIR': image.select('SR_B5'),
        'RED': image.select('SR_B4')
    }
).rename('Custom_VI')

# Atmospheric visibility
visibility = image.expression(
    'BLUE / RED',
    {
        'BLUE': image.select('SR_B2'),
        'RED': image.select('SR_B4')
    }
).rename('Visibility')

Conditional Operations

Create masks and conditional values:

# Create vegetation mask
vegetation_mask = ndvi.gt(0.3)

# Create water mask
water_mask = ndwi.gt(0.2)

# Conditional assignment
land_cover = ee.Image(0)  # Start with zeros
land_cover = land_cover.where(water_mask, 1)      # Water = 1
land_cover = land_cover.where(vegetation_mask, 2) # Vegetation = 2

# Apply mask to preserve only high vegetation
high_veg_only = ndvi.updateMask(ndvi.gt(0.5))

Statistical Analysis

Calculate statistics over regions:

# Define area of interest
point = ee.Geometry.Point([-122.4, 37.8])
region = point.buffer(1000)  # 1km buffer

# Calculate statistics
stats = ndvi.reduceRegion(
    reducer=ee.Reducer.mean().combine(
        reducer2=ee.Reducer.minMax(),
        sharedInputs=True
    ).combine(
        reducer2=ee.Reducer.stdDev(),
        sharedInputs=True
    ),
    geometry=region,
    scale=30,
    maxPixels=1e9
)

# Print results
ndvi_mean = stats.get('NDVI_mean').getInfo()
ndvi_min = stats.get('NDVI_min').getInfo()
ndvi_max = stats.get('NDVI_max').getInfo()
ndvi_std = stats.get('NDVI_stdDev').getInfo()

print(f"NDVI Statistics:")
print(f"  Mean: {ndvi_mean:.3f}")
print(f"  Range: {ndvi_min:.3f} to {ndvi_max:.3f}")
print(f"  Std Dev: {ndvi_std:.3f}")

Complete Example

Here’s a complete script combining all concepts:

  1"""
  2Basic Example 3: Simple Calculations and Spectral Indices
  3=========================================================
  4
  5This example demonstrates:
  6- Basic mathematical operations on images
  7- Calculating common spectral indices (NDVI, NDWI, EVI)
  8- Working with image bands and band math
  9- Understanding Earth Engine data types
 10- Creating custom calculations and functions
 11
 12Prerequisites:
 13- Authenticated Earth Engine environment
 14- Basic understanding of remote sensing concepts
 15- Familiarity with spectral indices
 16"""
 17
 18import ee
 19import matplotlib.pyplot as plt
 20import pandas as pd
 21import numpy as np
 22
 23def demonstrate_basic_math():
 24    """
 25    Demonstrate basic mathematical operations on Earth Engine images.
 26    """
 27    print("🧮 Basic Mathematical Operations")
 28    print("-" * 40)
 29    
 30    # Load a Landsat 8 image
 31    image = (ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
 32             .filterDate('2023-06-01', '2023-09-01')
 33             .filterBounds(ee.Geometry.Point([-122.4, 37.8]))  # San Francisco
 34             .sort('CLOUD_COVER')
 35             .first())
 36    
 37    print(f"Working with image: {image.get('LANDSAT_PRODUCT_ID').getInfo()}")
 38    
 39    # Scale factors for Landsat Collection 2
 40    def apply_scale_factors(image):
 41        """Apply scale factors to Landsat Collection 2 data."""
 42        optical_bands = image.select('SR_B.').multiply(0.0000275).add(-0.2)
 43        thermal_bands = image.select('ST_B.*').multiply(0.00341802).add(149.0)
 44        return image.addBands(optical_bands, None, True).addBands(thermal_bands, None, True)
 45    
 46    # Apply scaling
 47    image = apply_scale_factors(image)
 48    
 49    # Basic arithmetic operations
 50    print("\n📊 Basic Arithmetic Operations:")
 51    
 52    # Addition
 53    red_plus_nir = image.select('SR_B4').add(image.select('SR_B5'))
 54    print("✓ Addition: Red + NIR")
 55    
 56    # Subtraction  
 57    nir_minus_red = image.select('SR_B5').subtract(image.select('SR_B4'))
 58    print("✓ Subtraction: NIR - Red")
 59    
 60    # Multiplication
 61    red_times_nir = image.select('SR_B4').multiply(image.select('SR_B5'))
 62    print("✓ Multiplication: Red × NIR")
 63    
 64    # Division
 65    nir_div_red = image.select('SR_B5').divide(image.select('SR_B4'))
 66    print("✓ Division: NIR ÷ Red")
 67    
 68    # Constants
 69    red_plus_constant = image.select('SR_B4').add(0.1)
 70    red_times_constant = image.select('SR_B4').multiply(2.0)
 71    print("✓ Operations with constants")
 72    
 73    # Power operations
 74    red_squared = image.select('SR_B4').pow(2)
 75    red_sqrt = image.select('SR_B4').sqrt()
 76    print("✓ Power operations: square and square root")
 77    
 78    return image
 79
 80def calculate_spectral_indices(image):
 81    """
 82    Calculate common spectral indices for vegetation and water analysis.
 83    
 84    Args:
 85        image: Landsat 8 image (scaled)
 86    
 87    Returns:
 88        ee.Image: Image with spectral indices added as bands
 89    """
 90    print("\n🌱 Calculating Spectral Indices")
 91    print("-" * 40)
 92    
 93    # NDVI (Normalized Difference Vegetation Index)
 94    # NDVI = (NIR - Red) / (NIR + Red)
 95    ndvi = image.normalizedDifference(['SR_B5', 'SR_B4']).rename('NDVI')
 96    print("✓ NDVI calculated")
 97    
 98    # NDWI (Normalized Difference Water Index)
 99    # NDWI = (Green - NIR) / (Green + NIR)
100    ndwi = image.normalizedDifference(['SR_B3', 'SR_B5']).rename('NDWI')
101    print("✓ NDWI calculated")
102    
103    # NDBI (Normalized Difference Built-up Index)
104    # NDBI = (SWIR1 - NIR) / (SWIR1 + NIR)
105    ndbi = image.normalizedDifference(['SR_B6', 'SR_B5']).rename('NDBI')
106    print("✓ NDBI calculated")
107    
108    # EVI (Enhanced Vegetation Index)
109    # EVI = 2.5 * ((NIR - Red) / (NIR + 6 * Red - 7.5 * Blue + 1))
110    evi = image.expression(
111        '2.5 * ((NIR - RED) / (NIR + 6 * RED - 7.5 * BLUE + 1))',
112        {
113            'NIR': image.select('SR_B5'),
114            'RED': image.select('SR_B4'),
115            'BLUE': image.select('SR_B2')
116        }
117    ).rename('EVI')
118    print("✓ EVI calculated")
119    
120    # SAVI (Soil Adjusted Vegetation Index)
121    # SAVI = ((NIR - Red) / (NIR + Red + L)) * (1 + L), where L = 0.5
122    savi = image.expression(
123        '((NIR - RED) / (NIR + RED + 0.5)) * (1.5)',
124        {
125            'NIR': image.select('SR_B5'),
126            'RED': image.select('SR_B4')
127        }
128    ).rename('SAVI')
129    print("✓ SAVI calculated")
130    
131    # MNDWI (Modified Normalized Difference Water Index)
132    # MNDWI = (Green - SWIR1) / (Green + SWIR1)
133    mndwi = image.normalizedDifference(['SR_B3', 'SR_B6']).rename('MNDWI')
134    print("✓ MNDWI calculated")
135    
136    # NBR (Normalized Burn Ratio)
137    # NBR = (NIR - SWIR2) / (NIR + SWIR2)
138    nbr = image.normalizedDifference(['SR_B5', 'SR_B7']).rename('NBR')
139    print("✓ NBR calculated")
140    
141    # Add all indices to the image
142    indices_image = image.addBands([ndvi, ndwi, ndbi, evi, savi, mndwi, nbr])
143    
144    return indices_image
145
146def create_custom_calculations(image):
147    """
148    Demonstrate custom calculations using ee.Image.expression().
149    
150    Args:
151        image: Input Earth Engine image
152    
153    Returns:
154        ee.Image: Image with custom calculated bands
155    """
156    print("\n🔬 Custom Calculations with ee.Image.expression()")
157    print("-" * 50)
158    
159    # Custom vegetation index combining multiple bands
160    custom_vi = image.expression(
161        '(NIR * 2.5 - RED * 1.5 - BLUE * 0.8) / (NIR + RED + BLUE)',
162        {
163            'NIR': image.select('SR_B5'),
164            'RED': image.select('SR_B4'),
165            'BLUE': image.select('SR_B2')
166        }
167    ).rename('Custom_VI')
168    print("✓ Custom vegetation index")
169    
170    # Atmospheric visibility index
171    # Uses the ratio of blue to red for atmospheric clarity
172    visibility_index = image.expression(
173        'BLUE / RED',
174        {
175            'BLUE': image.select('SR_B2'),
176            'RED': image.select('SR_B4')
177        }
178    ).rename('Visibility_Index')
179    print("✓ Atmospheric visibility index")
180    
181    # Soil brightness index
182    # Average of visible bands
183    soil_brightness = image.expression(
184        '(BLUE + GREEN + RED) / 3',
185        {
186            'BLUE': image.select('SR_B2'),
187            'GREEN': image.select('SR_B3'),
188            'RED': image.select('SR_B4')
189        }
190    ).rename('Soil_Brightness')
191    print("✓ Soil brightness index")
192    
193    # Shadow index (using multiple bands)
194    shadow_index = image.expression(
195        '(BLUE + GREEN) / (NIR + SWIR1)',
196        {
197            'BLUE': image.select('SR_B2'),
198            'GREEN': image.select('SR_B3'),
199            'NIR': image.select('SR_B5'),
200            'SWIR1': image.select('SR_B6')
201        }
202    ).rename('Shadow_Index')
203    print("✓ Shadow index")
204    
205    # Add custom calculations
206    custom_image = image.addBands([custom_vi, visibility_index, soil_brightness, shadow_index])
207    
208    return custom_image
209
210def analyze_image_statistics(image, region=None):
211    """
212    Calculate comprehensive statistics for image bands.
213    
214    Args:
215        image: Earth Engine image
216        region: Optional region for analysis (uses image bounds if None)
217    
218    Returns:
219        dict: Dictionary containing statistics
220    """
221    print("\n📈 Statistical Analysis")
222    print("-" * 30)
223    
224    if region is None:
225        region = image.geometry()
226    
227    # Define bands to analyze
228    spectral_bands = ['SR_B2', 'SR_B3', 'SR_B4', 'SR_B5', 'SR_B6', 'SR_B7']
229    index_bands = ['NDVI', 'NDWI', 'NDBI', 'EVI', 'SAVI']
230    
231    all_bands = spectral_bands + index_bands
232    
233    # Calculate comprehensive statistics
234    stats = image.select(all_bands).reduceRegion(
235        reducer=ee.Reducer.minMax().combine(
236            reducer2=ee.Reducer.mean(),
237            sharedInputs=True
238        ).combine(
239            reducer2=ee.Reducer.stdDev(),
240            sharedInputs=True
241        ).combine(
242            reducer2=ee.Reducer.percentile([25, 75]),
243            sharedInputs=True
244        ),
245        geometry=region,
246        scale=30,
247        maxPixels=1e9
248    )
249    
250    stats_dict = stats.getInfo()
251    
252    # Format and display statistics
253    print("Band Statistics Summary:")
254    print("-" * 60)
255    
256    for band in all_bands:
257        if f'{band}_mean' in stats_dict:
258            print(f"\n{band}:")
259            print(f"  Mean: {stats_dict[f'{band}_mean']:.4f}")
260            print(f"  StdDev: {stats_dict[f'{band}_stdDev']:.4f}")
261            print(f"  Min: {stats_dict[f'{band}_min']:.4f}")
262            print(f"  Max: {stats_dict[f'{band}_max']:.4f}")
263            
264            if f'{band}_p25' in stats_dict:
265                print(f"  Q1 (25%): {stats_dict[f'{band}_p25']:.4f}")
266                print(f"  Q3 (75%): {stats_dict[f'{band}_p75']:.4f}")
267    
268    return stats_dict
269
270def demonstrate_conditional_operations(image):
271    """
272    Demonstrate conditional operations and masking.
273    
274    Args:
275        image: Input Earth Engine image
276    
277    Returns:
278        ee.Image: Image with conditional results
279    """
280    print("\n🎯 Conditional Operations and Masking")
281    print("-" * 40)
282    
283    # Create vegetation mask (NDVI > 0.3)
284    vegetation_mask = image.select('NDVI').gt(0.3)
285    print("✓ Vegetation mask created (NDVI > 0.3)")
286    
287    # Create water mask (NDWI > 0.2)
288    water_mask = image.select('NDWI').gt(0.2)
289    print("✓ Water mask created (NDWI > 0.2)")
290    
291    # Create urban mask (NDBI > 0.1)
292    urban_mask = image.select('NDBI').gt(0.1)
293    print("✓ Urban mask created (NDBI > 0.1)")
294    
295    # Combined land cover classification using conditions
296    land_cover = (ee.Image(0)  # Start with zeros
297                  .where(water_mask, 1)      # Water = 1
298                  .where(vegetation_mask, 2)  # Vegetation = 2
299                  .where(urban_mask, 3))     # Urban = 3
300    
301    land_cover = land_cover.rename('Land_Cover')
302    print("✓ Simple land cover classification")
303    
304    # Conditional value assignment
305    # High vegetation areas get NDVI value, others get 0
306    high_vegetation = image.select('NDVI').where(
307        image.select('NDVI').lt(0.5), 0
308    ).rename('High_Vegetation_Only')
309    print("✓ Conditional value assignment")
310    
311    # Multiple conditions using ee.Image.expression()
312    complex_condition = image.expression(
313        '(NDVI > 0.4) && (NDWI < 0.1) && (EVI > 0.3) ? 1 : 0',
314        {
315            'NDVI': image.select('NDVI'),
316            'NDWI': image.select('NDWI'),
317            'EVI': image.select('EVI')
318        }
319    ).rename('Healthy_Vegetation')
320    print("✓ Complex conditional expression")
321    
322    # Add conditional results
323    conditional_image = image.addBands([
324        land_cover, high_vegetation, complex_condition,
325        vegetation_mask.rename('Vegetation_Mask'),
326        water_mask.rename('Water_Mask'),
327        urban_mask.rename('Urban_Mask')
328    ])
329    
330    return conditional_image
331
332def create_visualization_parameters():
333    """
334    Create visualization parameters for different types of data.
335    
336    Returns:
337        dict: Dictionary of visualization parameters
338    """
339    vis_params = {
340        'true_color': {
341            'bands': ['SR_B4', 'SR_B3', 'SR_B2'],
342            'min': 0.0,
343            'max': 0.3,
344            'gamma': 1.2
345        },
346        'false_color': {
347            'bands': ['SR_B5', 'SR_B4', 'SR_B3'],
348            'min': 0.0,
349            'max': 0.3,
350            'gamma': 1.2
351        },
352        'ndvi': {
353            'bands': ['NDVI'],
354            'min': -0.2,
355            'max': 0.8,
356            'palette': ['blue', 'white', 'green']
357        },
358        'ndwi': {
359            'bands': ['NDWI'],
360            'min': -0.3,
361            'max': 0.5,
362            'palette': ['white', 'blue']
363        },
364        'land_cover': {
365            'bands': ['Land_Cover'],
366            'min': 0,
367            'max': 3,
368            'palette': ['gray', 'blue', 'green', 'red']
369        }
370    }
371    
372    return vis_params
373
374def main():
375    """
376    Main function demonstrating simple calculations and spectral indices.
377    """
378    # Initialize Earth Engine
379    try:
380        ee.Initialize(project='your-project-id')
381        print("✓ Earth Engine initialized successfully!")
382    except Exception as e:
383        print(f"✗ Error initializing Earth Engine: {e}")
384        return
385    
386    print("\n" + "="*70)
387    print("🧮 EARTH ENGINE SIMPLE CALCULATIONS AND SPECTRAL INDICES")
388    print("="*70)
389    
390    # Step 1: Basic mathematical operations
391    image = demonstrate_basic_math()
392    
393    # Step 2: Calculate spectral indices
394    indices_image = calculate_spectral_indices(image)
395    
396    # Step 3: Custom calculations
397    custom_image = create_custom_calculations(indices_image)
398    
399    # Step 4: Statistical analysis
400    san_francisco_region = ee.Geometry.Rectangle([-122.5, 37.7, -122.3, 37.9])
401    stats = analyze_image_statistics(custom_image, san_francisco_region)
402    
403    # Step 5: Conditional operations
404    final_image = demonstrate_conditional_operations(custom_image)
405    
406    # Step 6: Visualization setup
407    print("\n🎨 Visualization Parameters")
408    print("-" * 30)
409    vis_params = create_visualization_parameters()
410    
411    for vis_name, params in vis_params.items():
412        print(f"✓ {vis_name}: {params['bands']}")
413    
414    # Step 7: Export example
415    print("\n📤 Export Configuration Example")
416    print("-" * 35)
417    
418    # Select key bands for export
419    export_image = final_image.select([
420        'SR_B4', 'SR_B3', 'SR_B2',  # RGB bands
421        'NDVI', 'NDWI', 'EVI',      # Vegetation indices
422        'Land_Cover'                 # Classification
423    ])
424    
425    export_config = {
426        'image': export_image,
427        'description': 'landsat_analysis_with_indices',
428        'folder': 'EarthEngine_Exports',
429        'region': san_francisco_region,
430        'scale': 30,
431        'crs': 'EPSG:4326',
432        'maxPixels': 1e13
433    }
434    
435    print("Export parameters configured:")
436    for key, value in export_config.items():
437        if key != 'image':
438            print(f"  {key}: {value}")
439    
440    # Step 8: Summary of calculations performed
441    print("\n📋 Summary of Calculations Performed")
442    print("-" * 40)
443    
444    all_bands = final_image.bandNames().getInfo()
445    
446    spectral_bands = [b for b in all_bands if b.startswith('SR_B')]
447    index_bands = [b for b in all_bands if b in ['NDVI', 'NDWI', 'NDBI', 'EVI', 'SAVI', 'MNDWI', 'NBR']]
448    custom_bands = [b for b in all_bands if 'Custom' in b or 'Visibility' in b or 'Soil' in b or 'Shadow' in b]
449    mask_bands = [b for b in all_bands if 'Mask' in b or 'Cover' in b or 'Vegetation' in b]
450    
451    print(f"Original spectral bands ({len(spectral_bands)}): {spectral_bands}")
452    print(f"Standard indices ({len(index_bands)}): {index_bands}")
453    print(f"Custom calculations ({len(custom_bands)}): {custom_bands}")
454    print(f"Masks and classifications ({len(mask_bands)}): {mask_bands}")
455    print(f"Total bands: {len(all_bands)}")
456    
457    print("\n✅ Simple Calculations Example Completed!")
458    print("\n🎓 Key Concepts Demonstrated:")
459    print("• Basic arithmetic operations on images")
460    print("• Standard spectral index calculations")
461    print("• Custom expressions and formulas")
462    print("• Statistical analysis and summaries")
463    print("• Conditional operations and masking")
464    print("• Land cover classification basics")
465    print("• Visualization parameter setup")
466    
467    print("\n📚 Next Steps:")
468    print("• Experiment with different spectral indices")
469    print("• Try custom mathematical expressions")
470    print("• Apply calculations to different sensors")
471    print("• Explore time series calculations")
472    print("• Learn about image collection processing")
473
474if __name__ == "__main__":
475    main()

Visualization Parameters

Visualize your calculated indices:

# NDVI visualization
ndvi_vis = {
    'bands': ['NDVI'],
    'min': -0.2,
    'max': 0.8,
    'palette': ['blue', 'white', 'green']
}

# Water index visualization
ndwi_vis = {
    'bands': ['NDWI'],
    'min': -0.3,
    'max': 0.5,
    'palette': ['white', 'blue']
}

# Land cover visualization
landcover_vis = {
    'bands': ['classification'],
    'min': 0,
    'max': 2,
    'palette': ['gray', 'blue', 'green']
}

Common Applications

Agriculture Monitoring: * NDVI for crop health assessment * EVI for biomass estimation * SAVI for soil-adjusted vegetation monitoring

Water Resources: * NDWI for water body mapping * MNDWI for water extent monitoring * Turbidity indices for water quality

Urban Planning: * NDBI for built-up area detection * Urban heat island analysis * Green space monitoring

Best Practices

Scale Considerations: * Use appropriate scale for your analysis * Consider sensor resolution limits * Match scale to feature size

Data Quality: * Apply cloud masking before calculations * Check for valid data ranges * Handle no-data values appropriately

Computational Efficiency: * Calculate indices only when needed * Use appropriate data types * Optimize expressions for performance

Common Issues

Scale Factor Problems:

# Wrong - using raw DN values
ndvi_wrong = image.normalizedDifference(['SR_B5', 'SR_B4'])

# Correct - apply scale factors first
scaled = image.select('SR_B.').multiply(0.0000275).add(-0.2)
ndvi_correct = scaled.normalizedDifference(['SR_B5', 'SR_B4'])

Invalid Data Handling:

# Mask invalid values
valid_ndvi = ndvi.updateMask(ndvi.gte(-1).And(ndvi.lte(1)))

Memory Issues:

# For large areas, use appropriate scale
stats = ndvi.reduceRegion(
    reducer=ee.Reducer.mean(),
    geometry=large_region,
    scale=100,  # Use coarser resolution for large areas
    maxPixels=1e9
)

Next Steps

  • Try calculating different spectral indices

  • Experiment with custom expressions

  • Learn about time series calculations

  • Explore classification applications

Next: Intermediate Examples