Before we can compute indices, classify land cover, or detect change, we need to understand the building blocks. Think of this module as learning your tools before building the house. We will cover how a single image differs from a collection, how to pick the bands you need, clip to a study area, and attach computed layers. Let's jump in.
Learning objectives
- Distinguish
ee.Imagefromee.ImageCollectionand know when to use each. - Use
.clip(),.mosaic(),.select(), and.addBands()for core image manipulation. - Read image metadata and properties using
.get(),.propertyNames(), and.bandNames(). - Understand band structure, projections, and how images store multi-layer data.
Why it matters
Here is one of the most common beginner mistakes: confusing an Image with an ImageCollection. It leads to cryptic errors, and it will happen to you at least once. Understanding the difference (along with core operations like band selection and clipping) gives you a reliable foundation for everything we build in this course.
Key vocabulary
- ee.Image
- A single raster image composed of one or more bands. Think of it as a single photograph with multiple color channels.
- ee.ImageCollection
- An ordered stack of images, typically organized by acquisition date. Think of it as a photo album.
- Band
- One layer of data within an image. Each band stores values for a specific wavelength, index, or measurement.
- Metadata
- Properties attached to an image (acquisition date, cloud cover, sensor ID) that describe the data without being pixel values.
Your first win: load, select, clip, and add NDVI
Let's get a quick win. This script loads a single Landsat 8 image over San Francisco, selects the visible bands, clips it to a county boundary, and adds a computed NDVI band. All four core operations in under 20 lines.
// Load a specific Landsat 8 image by ID
var image = ee.Image('LANDSAT/LC08/C02/T1_L2/LC08_044034_20230715');
// Apply scale factors for surface reflectance
var scaled = image.select('SR_B.*').multiply(0.0000275).add(-0.2);
// Select RGB bands for display
var rgb = scaled.select(['SR_B4', 'SR_B3', 'SR_B2']);
// Clip to San Francisco County
var sfCounty = ee.FeatureCollection('TIGER/2018/Counties')
.filter(ee.Filter.eq('NAME', 'San Francisco')).first().geometry();
var clipped = rgb.clip(sfCounty);
// Compute and add NDVI
var ndvi = scaled.normalizedDifference(['SR_B5', 'SR_B4']).rename('NDVI');
var withNDVI = clipped.addBands(ndvi.clip(sfCounty));
// Display results
Map.centerObject(sfCounty, 12);
Map.addLayer(clipped, {min: 0, max: 0.3}, 'RGB');
Map.addLayer(withNDVI.select('NDVI'), {min: 0, max: 0.8, palette: ['brown', 'yellow', 'green']}, 'NDVI');
What you should see
A true-color image of San Francisco clipped to the county boundary, plus an NDVI layer showing green areas (parks, Presidio) in bright green and urban areas in brown/yellow.
What's the difference? Image vs. ImageCollection
Think of an ee.Image as a single photograph: one acquisition, one date, one
set of bands. An ee.ImageCollection is the whole photo album: many images
grouped together, usually by sensor and time. This analogy is simple, but it is critical.
Functions that work on one type often do not work on the other.
| Feature | ee.Image |
ee.ImageCollection |
|---|---|---|
| What it holds | One raster (one date, one scene) | Many rasters (many dates or scenes) |
| How to load | ee.Image('ID') |
ee.ImageCollection('ID') |
| Common operations | .select(), .clip(), .addBands() |
.filterDate(), .map(), .median() |
| Extract one image | Already is one | .first(), .sort().first() |
| Combine into one | N/A | .median(), .mosaic(), .mean() |
Key rule: If you loaded with ee.ImageCollection(), you must
reduce or extract before you can use single-image operations like .clip() or
.normalizedDifference().
Peeking inside an image
Before we process anything, let's peek inside. GEE images carry rich metadata and band information that tells us exactly what data is available.
var image = ee.Image('LANDSAT/LC08/C02/T1_L2/LC08_044034_20230715');
// What bands does this image have?
print('Band names:', image.bandNames());
// What properties (metadata) are available?
print('Property names:', image.propertyNames());
// Read specific properties
print('Cloud cover:', image.get('CLOUD_COVER'));
print('Acquisition date:', image.date().format('YYYY-MM-dd'));
print('Sensor ID:', image.get('SENSOR_ID'));
// Check the projection and scale of a band
print('B4 projection:', image.select('SR_B4').projection());
print('B4 scale (meters):', image.select('SR_B4').projection().nominalScale());
What you should see
The console shows a list of band names (SR_B1 through SR_B7, plus thermal and QA bands), a long list of metadata properties, the cloud cover percentage, the date (2023-07-15), and the native scale of 30 meters for the visible bands.
Picking just the bands you need
Most images contain way more bands than we need. The .select() method pulls out
only the ones we want, keeping scripts clean and fast.
var image = ee.Image('LANDSAT/LC08/C02/T1_L2/LC08_044034_20230715');
// Select by exact name (most common)
var rgb = image.select(['SR_B4', 'SR_B3', 'SR_B2']);
print('RGB band count:', rgb.bandNames().size()); // 3
// Select by pattern (regular expression)
var allSR = image.select('SR_B.*');
print('All SR bands:', allSR.bandNames()); // SR_B1 through SR_B7
// Select by index (0-based)
var firstTwo = image.select([0, 1]);
print('First two bands:', firstTwo.bandNames());
// Select and rename in one step
var renamed = image.select(['SR_B5', 'SR_B4'], ['NIR', 'RED']);
print('Renamed bands:', renamed.bandNames()); // NIR, RED
Best practice: Select by name, not by index. Band order can change between datasets, but names are consistent within a collection.
Cutting to your study area
The .clip() method masks an image to a geometry or feature boundary. Pixels
outside become transparent. It is great for clean map displays and focusing on our area
of interest.
// Clip to a simple geometry
var roi = ee.Geometry.Rectangle([-122.5, 37.6, -122.3, 37.8]);
var clipped = image.clip(roi);
// Clip to a feature from a FeatureCollection
var state = ee.FeatureCollection('TIGER/2018/States')
.filter(ee.Filter.eq('NAME', 'California')).first();
var clippedToState = image.clip(state.geometry());
Map.addLayer(clippedToState.select(['SR_B4', 'SR_B3', 'SR_B2'])
.multiply(0.0000275).add(-0.2),
{min: 0, max: 0.3}, 'California');
Performance note: Clipping is computationally expensive on large or complex
geometries. For analysis (reducers, exports), prefer .filterBounds() on the
collection and specify the geometry in reduceRegion() instead of clipping first.
Save .clip() for display purposes.
Stitching scenes together with mosaic
When our study area spans multiple satellite scenes, .mosaic() stitches them
into a single seamless image. Here is the catch: the last image in the collection "wins"
where scenes overlap, so sort order matters.
// Create a mosaic from multiple Landsat scenes
var collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
.filterDate('2023-07-01', '2023-07-31')
.filterBounds(ee.Geometry.Point([-122.4, 37.7]))
.filter(ee.Filter.lt('CLOUD_COVER', 15));
// Sort by cloud cover so cleanest image is on top
var mosaic = collection.sort('CLOUD_COVER').mosaic();
// Apply scale factors
var scaled = mosaic.select('SR_B.*').multiply(0.0000275).add(-0.2);
Map.setCenter(-122.4, 37.7, 8);
Map.addLayer(scaled, {bands: ['SR_B4', 'SR_B3', 'SR_B2'], min: 0, max: 0.3}, 'Mosaic');
For a statistically robust composite (better cloud removal), use .median() or
.mean() instead of .mosaic(). Mosaicking is best when you want speed
or need to preserve the original pixel values from a specific scene.
Stacking on new layers with addBands
The .addBands() method attaches new layers to an existing image. This is how
we include computed indices (NDVI, NDWI), elevation data, or any derived layer alongside
the original bands. Think of it like adding a new sheet to a stack of transparencies.
var image = ee.Image('LANDSAT/LC08/C02/T1_L2/LC08_044034_20230715');
var scaled = image.select('SR_B.*').multiply(0.0000275).add(-0.2);
// Compute NDVI and NDWI
var ndvi = scaled.normalizedDifference(['SR_B5', 'SR_B4']).rename('NDVI');
var ndwi = scaled.normalizedDifference(['SR_B3', 'SR_B5']).rename('NDWI');
// Add both to the original image
var enriched = scaled.addBands(ndvi).addBands(ndwi);
print('Enriched band names:', enriched.bandNames());
// SR_B1, SR_B2, ..., SR_B7, NDVI, NDWI
// Add elevation from a different source
var dem = ee.Image('USGS/SRTMGL1_003').rename('elevation');
var withElevation = enriched.addBands(dem);
print('With elevation:', withElevation.bandNames());
By default, .addBands() keeps all bands. If the new band has the same name as an
existing one, you can use .addBands(newBand, null, true) to overwrite the existing
band.
Reading the fine print: image metadata
Metadata properties tell us when, where, and how an image was captured. Think of it as the EXIF data on your phone photos, but for satellites. This information is essential for quality control, filtering, and documenting our analyses.
var image = ee.Image('LANDSAT/LC08/C02/T1_L2/LC08_044034_20230715');
// Key metadata fields for Landsat 8
print('Date acquired:', image.get('DATE_ACQUIRED'));
print('Cloud cover (%):', image.get('CLOUD_COVER'));
print('Cloud cover (land):', image.get('CLOUD_COVER_LAND'));
print('Sun elevation:', image.get('SUN_ELEVATION'));
print('WRS Path:', image.get('WRS_PATH'));
print('WRS Row:', image.get('WRS_ROW'));
// The system timestamp (milliseconds since epoch)
print('System time:', image.get('system:time_start'));
print('Formatted date:', image.date().format('YYYY-MM-dd'));
// Image footprint geometry
var footprint = image.geometry();
Map.addLayer(footprint, {color: 'blue'}, 'Image footprint');
Map.centerObject(footprint, 7);
Use .getInfo() sparingly. It forces a synchronous server call, which blocks your
script. For display purposes, print() is asynchronous and much safer.
Pro tips
- Always check band names first. Different collections use different naming conventions. Landsat 8 SR uses
SR_B4, while Sentinel-2 usesB4. - Select early. Drop unneeded bands at the start of your script. Fewer bands means less memory and faster processing.
- Use
print()instead of.getInfo()for inspection. The.getInfo()method blocks execution and can time out on large objects. - Clip only for display. For analysis, pass the geometry to
reduceRegion()orExportinstead of clipping the image.
Try it: build a multi-index image
Load a Sentinel-2 image, apply scale factors (.divide(10000)), compute NDVI and
EVI, and add both as new bands. Print the final band list to verify.
// Starter code
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
.filterBounds(ee.Geometry.Point([-122.4, 37.7]))
.filterDate('2023-07-01', '2023-08-01')
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 10))
.first();
var scaled = s2.select(['B2', 'B3', 'B4', 'B8']).divide(10000);
// YOUR CODE HERE:
// 1. Compute NDVI: (B8 - B4) / (B8 + B4)
// 2. Compute EVI: 2.5 * (B8 - B4) / (B8 + 6*B4 - 7.5*B2 + 1)
// 3. Add both bands to the scaled image
// 4. Print the final band names
Common mistakes
- Calling
.clip()on an ImageCollection. Collections do not have a.clip()method. Either extract a single image first, or use.map()to clip each image in the collection. - Forgetting scale factors. Landsat 8/9 SR values are stored as integers. Always apply
.multiply(0.0000275).add(-0.2)before analysis or display. - Selecting bands by index. Band order can differ across images in a collection. Always select by name.
- Using
.mosaic()without sorting. The last image wins in overlap zones. If you do not sort by quality, a cloudy image could end up on top.
Quick self-check
- What is the key difference between
ee.Imageandee.ImageCollection? - How do you extract a single image from a collection?
- Why should you avoid clipping for analysis workflows?
- What does
.addBands()return: a new image, or does it modify the original? - How would you find the native resolution of a Landsat band?
Going deeper
This module covers the essential image operations you will use in every GEE script. For a deeper exploration of image structure, visualization, and manipulation, see:
- EEFA Book - Chapter F1.1: Exploring Images
Next Steps
- Band Arithmetic - perform mathematical operations between bands.
- NDVI - compute and interpret the Normalized Difference Vegetation Index.
- Cloud Masking - remove cloud-contaminated pixels before analysis.
- Image Collections - filter, sort, and composite multi-image stacks.