Análisis Dinámico de Cobertura de Suelo para Evaluación del Cambio Climático con Python y Rasterio

La imágenes satelitales nos han dado la capacidad de observar la superficie terrestre en los últimos años, pero no hemos tenido tanto éxito en comprender las dinámicas de la cobertura del suelo y su interacción con factores económicos, sociológicos y políticos. Se han encontrado algunas deficiencias para este tipo de análisis al usar software comercial de SIG, pero también existen limitaciones en la manera en que aplicamos procesos lógicos y matemáticos a un conjunto de imágenes satelitales. Trabajar con datos geoespaciales en Python nos da la capacidad de filtrar, calcular, recortar, iterar y exportar conjuntos de datos ráster o vectoriales haciendo un uso eficiente del poder computacional, lo que permite un mayor alcance en el análisis de datos.

Este es un análisis aplicado de la dinámica de la cobertura del suelo en algunas regiones de Turquía para la evaluación de la aridificación del suelo y el abandono de tierras debido al cambio climático en la última década. El caso cubre todos los pasos en Python con las bibliotecas geoespaciales relacionadas para procesar imágenes de Sentinel 2 en un área de estudio, considerando condiciones hidrológicas y la reducción de valores atípicos.

Tutorial

Datos de ingreso

https://owncloud.hatarilabs.com/s/N2rZop3wc60TEGL

Password to descargar los datos: Hatarilabs.

Código

Part 1

import os
import rasterio
from rasterio import plot
from rasterio.crs import CRS
import matplotlib.pyplot as plt
import numpy as np
from rasterio.mask import mask
from rasterio.warp import calculate_default_transform, reproject, Resampling
import geopandas as gpd
from affine import Affine
aoiPath = '../shp/dagecitAreaBoundWgs37N.shp'

ndviDateList = [x for x in os.listdir('../rst') if '-' in x]
ndviDateList
def createTotalNdvi(ndviDate):
    totalPath = os.path.join('../rst',ndviDate)
    band4List = [x for x in os.listdir(totalPath) if x.endswith('B04.tif')]
    band5List = [x for x in os.listdir(totalPath) if x.endswith('B05.tif')]
    band4 = rasterio.open(os.path.join(totalPath,band4List[0]))
    band5 = rasterio.open(os.path.join(totalPath,band5List[0]))
    #generate nir and red objects as arrays in float64 format
    red = band4.read(1).astype('float64')
    nir = band5.read(1).astype('float64')

    #ndvi calculation, empty cells or nodata cells are reported as 0
    ndvi=np.where(
        (nir+red)==0., 
        0, 
        (nir-red)/(nir+red))
    #export ndvi image
    ndviImage = rasterio.open('../rst/ndviTotal/%s.tif'%ndviDate,'w',driver='Gtiff',
                            width=band4.width, 
                            height = band4.height, 
                            count=1, crs=band4.crs, 
                            transform=band4.transform, 
                            dtype='float32')
    ndviImage.write(ndvi,1)
    ndviImage.close()
for ndviDate in ndviDateList:
    createTotalNdvi(ndviDate)
def reprojectRaster(ndvi):
    inNdvi = os.path.join('../rst','ndviTotal',ndvi+'.tif')
    outNdvi = os.path.join('../rst','ndviReproj',ndvi+'.tif')

    with rasterio.open(inNdvi) as src:
        dst_crs = "EPSG:32637"        
        transform, width, height = calculate_default_transform(
            src.crs, dst_crs, src.width, src.height, *src.bounds)
        kwargs = src.meta.copy()
        kwargs.update({
            "crs": dst_crs,
            "transform": transform,
            "width": width,
            "height": height
        })
        with rasterio.open(outNdvi, "w", **kwargs) as dst:
            for i in range(1, src.count + 1):
                reproject(
                    source=rasterio.band(src, i),
                    destination=rasterio.band(dst, i),
                    src_transform=src.transform,
                    src_crs=src.crs,
                    dst_transform=transform,
                    dst_crs=dst_crs,
                    resampling=Resampling.nearest  # or bilinear, cubic, etc.
                )
for ndviDate in ndviDateList:
    reprojectRaster(ndviDate)
outRes = 30
boundDf = gpd.read_file(aoiPath)
xDim = boundDf.total_bounds[2] - boundDf.total_bounds[0]
yDim = boundDf.total_bounds[3] - boundDf.total_bounds[1]
print(xDim,yDim)
ncol = int(xDim // outRes)
nrow = int(yDim // outRes)
print(ncol,nrow)
print(boundDf.total_bounds)
def clipRasterByMask(ndvi,boundDf):
    inNdvi = os.path.join('../rst','ndviReproj',ndvi+'.tif')
    outNdvi = os.path.join('../rst','ndviClip',ndvi+'.tif')

    # Open the raster and apply the mask
    with rasterio.open(inNdvi) as src:
        out_image, out_transform = mask(src, boundDf.geometry.values, crop=True)
        out_meta = src.meta.copy()
    out_meta.update({
        "driver": "GTiff",
        "height": out_image.shape[1],
        "width": out_image.shape[2],
        "transform": out_transform
    })

    # Write the clipped raster
    with rasterio.open(outNdvi, "w", **out_meta) as dest:
        dest.write(out_image)
for ndviDate in ndviDateList:
    clipRasterByMask(ndviDate, boundDf)
def resampleRasterByParams(ndvi,boundDf, ncol,nrow,outRes):
    inNdvi = os.path.join('../rst','ndviClip',ndvi+'.tif')
    outNdvi = os.path.join('../rst','ndviClipResampled',ndvi+'.tif')
    with rasterio.open(inNdvi) as src:
        transform = Affine(
            outRes, 0, boundDf.total_bounds[0],
            0, -outRes, boundDf.total_bounds[3]
        )
        kwargs = src.meta.copy()
        kwargs.update({
            'height': nrow,
            'width': ncol,
            'transform': transform
        })

        with rasterio.open(outNdvi, 'w', **kwargs) as dst:
            reproject(
                source=rasterio.band(src, 1),
                destination=rasterio.band(dst, 1),
                src_transform=src.transform,
                src_crs=src.crs,
                dst_transform=transform,
                dst_crs=src.crs,
                resampling=Resampling.bilinear
            )
for ndviDate in ndviDateList:
    resampleRasterByParams(ndviDate,boundDf,ncol,nrow,outRes)

Part 2

import os
import rasterio
from rasterio import plot
from rasterio.crs import CRS
import matplotlib.pyplot as plt
import numpy as np
from rasterio.mask import mask
from rasterio.warp import calculate_default_transform, reproject, Resampling
import fiona
ndviDateList = [x for x in os.listdir('../rst/ndviReproj') if x.endswith('.tif')]
ndviDateList
def calculateMeanNdvi(raster1, raster2, rasterName):
    raster1Path = os.path.join('../rst/ndviClipResampled',raster1)
    raster2Path = os.path.join('../rst/ndviClipResampled',raster2)
    rst1 = rasterio.open(raster1Path)
    rst2 = rasterio.open(raster2Path)
    rst1Band = rst1.read(1).astype('float64')
    rst2Band = rst2.read(1).astype('float64')
    meanNdvi=np.where(
        (rst1Band+rst2Band)==0., 
        0, 
        (rst1Band+rst2Band)/2)
    ndviImage = rasterio.open('../rst/ndviMean/%s.tif'%rasterName,'w',driver='Gtiff',
                        width=rst1.width, 
                        height=rst1.height, 
                        count=1, crs=rst1.crs, 
                        transform=rst1.transform, 
                        dtype='float32')
    ndviImage.write(meanNdvi,1)
    ndviImage.close()
calculateMeanNdvi(ndviDateList[0], ndviDateList[1], 'ndvi_2013_2014')
calculateMeanNdvi(ndviDateList[2], ndviDateList[3], 'ndvi_2023_2024')
def calculateNdviDifference(raster1,raster2,rasterName,threshold):
    raster1Path = os.path.join('../rst/ndviMean',raster1)
    raster2Path = os.path.join('../rst/ndviMean',raster2)
    rst1 = rasterio.open(raster1Path)
    rst2 = rasterio.open(raster2Path)
    rst1Band = rst1.read(1).astype('float64')
    rst2Band = rst2.read(1).astype('float64')
    diffNdvi=np.where(
        (rst1Band-rst2Band)>=threshold, 
        rst1Band-rst2Band, 
        -9999)
    ndviImage = rasterio.open('../rst/ndviMean/%s.tif'%rasterName,'w',driver='Gtiff',
                        width=rst1.width, 
                        height=rst1.height, 
                        count=1, crs=rst1.crs, 
                        transform=rst1.transform, 
                        dtype='float32',
                        nodata=-9999)
    ndviImage.write(diffNdvi,1)
    ndviImage.close()
calculateNdviDifference('ndvi_2013_2014.tif','ndvi_2023_2024.tif','dif_2013_2023',0.03)

 

Suscríbete a nuestro boletín electrónico

Suscríbase a nuestro boletín gratuito para recibir noticias, datos interesantes y fechas de nuestros cursos en recursos hídricos.

 

Posted on May 29, 2025 .