Hi, this is Alex from DEV STORIES DOT EU. Rasterio:
Numpy for Geospatial Data, episode 5 of 5. Sometimes you have beautiful, high-resolution satellite imagery, but your climate model only accepts coarse, low-resolution inputs. You cannot just slice the array, because the resulting pixels still need to represent the exact same physical area, just in a lower-density grid. To do that correctly, you need Resampling and Writing.
This is not about reprojection. The coordinate reference system stays exactly the same. We are only changing the resolution. Let us say you need to downsample a raster by half. In Rasterio, you perform the resampling at the exact moment you read the data into memory. When you call the read method on your dataset, you pass a parameter called out shape. Instead of reading the full array, Rasterio reads it and immediately scales it to fit the shape you requested. For a single-band raster, your out shape would be a tuple specifying one band, then half the original height, and half the original width.
You also need to tell Rasterio how to calculate the values of these new, larger pixels. You do this by passing a resampling parameter. Rasterio provides a Resampling enum for this. If you are dealing with continuous data like temperature, you might pass Resampling dot bilinear, which calculates a weighted average of the surrounding pixels. If you are dealing with categorical data like land cover, you would use Resampling dot nearest to preserve the exact original class values.
Here is the key insight. You now have a new, smaller Numpy array, but it still represents the exact same geographic area on the Earth. Because the array has fewer pixels, each individual pixel covers more physical space. This means your original Affine transform is now completely wrong. If you write the new array to disk using the old transform, your image will shrink on the map to cover only a quarter of its original area.
You must scale the transform. You do this by taking the original dataset transform and multiplying it by an Affine scaling matrix. You find the scaling ratios by dividing the original width by your new width, and the original height by your new height. Since we halved the dimensions, both of these ratios are exactly two. When you multiply the original transform by these scaling factors, you get a new transform where the pixel dimensions are doubled, but the top-left origin point remains perfectly anchored.
Now you have your resampled array and your scaled transform. The final step is writing a valid GeoTIFF to disk. To do this, you need metadata. The safest approach is to copy the profile property from your source dataset. This gives you a dictionary containing the file driver, the coordinate reference system, the data type, and the nodata value. You then update this profile dictionary with your new height, your new width, and your newly calculated transform.
With the updated profile ready, you call rasterio dot open. You provide the new filename, set the mode to write, and unpack your profile dictionary as keyword arguments. This creates an empty dataset on disk formatted to your exact specifications. Finally, you call the write method on this new dataset object and pass in your downsampled Numpy array. The array is written to the file, and your new lower-resolution GeoTIFF is ready for the climate model.
Remember, modifying raster data is always a two-part contract: if you change the shape of the Numpy array, you must explicitly change the Affine transform to match, or your spatial footprint is broken. That brings us to the end of our series. I encourage you to read the official Rasterio documentation to explore other resampling algorithms and try building these pipelines hands-on. If you want to suggest topics for a future series, visit devstories dot eu. I would like to take a moment to thank you for listening, it helps us a lot. Have a great one!