Mercurial > repos > ecology > landcover_subindicator
diff land_cover.py @ 0:828c02027180 draft default tip
planemo upload for repository https://github.com/galaxyecology/tools-ecology/tree/master/tools/earth commit 4cbace8c3a71b7a1638cfd44a21f5d4b84d2fd15
author | ecology |
---|---|
date | Mon, 21 Oct 2024 16:47:10 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/land_cover.py Mon Oct 21 16:47:10 2024 +0000 @@ -0,0 +1,268 @@ +# Land Cover TE Algorithm Without GEE + +# How to Execute: +# - Load the two images (.TIFF) of the rasters +# to be processed in the "/data/land_cover/input" folder +# - Setting the two filenames in the specific cell of this script (0) +# - Run all cells of the script +# - Check the script results in the "/data/land_cover/output" folder +# - Land Cover Degradation "/data/land_cover/output/lc_dg.tiff" +# - Land Cover Transition "/data/land_cover/output/lc_tr.tiff" + +# Librairies import +import argparse +import json +import os +import sys + +import matplotlib.pyplot as plt + +import numpy as np + +import rasterio +from rasterio.plot import show + +from te_schemas.land_cover import LCLegendNesting +from te_schemas.land_cover import LCTransitionDefinitionDeg + +# Methods to manage Rasters + + +def remap(raster, problem_numbers, alternative_numbers): + n_min, n_max = raster.min(), raster.max() + replacer = np.arange(n_min, n_max + 1) + mask = (problem_numbers >= n_min) & (problem_numbers <= n_max) + replacer[problem_numbers[mask] - n_min] = alternative_numbers[mask] + raster_replaced = replacer[raster - n_min] + return raster_replaced + + +def saveRaster(dataset, datasetPath, cols, rows, projection, namedataset=None): + if namedataset: + rasterSet = rasterio.open(datasetPath, + 'w', + driver='GTiff', + height=rows, + width=cols, + count=1, + dtype=np.int8, + crs=projection, + transform=transform, ) + rasterSet.write(dataset, 1) + rasterSet.close() + else: + rasterSet = rasterio.open(datasetPath, + 'w', + driver='GTiff', + height=rows, + width=cols, + count=1, + dtype=np.int8, + crs=projection, + transform=transform, ) + rasterSet.write(dataset, 1) + rasterSet.set_band_description(1, namedataset) + rasterSet.close() + + +def plot(ndviImage, cmap): + src = rasterio.open(ndviImage, 'r') + fig, ax = plt.subplots(1, figsize=(12, 12)) + show(src, cmap=cmap, ax=ax) + ax.set_xlabel('Est') + ax.set_ylabel('Nord') + plt.show() + + +def plotContour(ndviImage, cmap): + src = rasterio.open(ndviImage, 'r') + fig, ax = plt.subplots(1, figsize=(12, 12)) + show(src, cmap=cmap, contour=True, ax=ax) + ax.set_xlabel('Est') + ax.set_ylabel('Nord') + plt.show() + + +# Setting inputs +command_line_args = sys.argv[1:] + + +parser = argparse.ArgumentParser(description="landcover inputs and outputs") +# Add arguments +parser.add_argument("--raster_1", help="raster 1") +parser.add_argument("--raster_2", help="raster 2") +parser.add_argument("--json", help="json") + +args = parser.parse_args(command_line_args) + +# Parse the command line arguments + +# Import data + +path_raster_yi = args.raster_1 +path_raster_yf = args.raster_2 +fjson = args.json +# Input Rasters + +# Outputs +out_dir = os.path.join(os.getcwd(), "data/land_cover/output/") +if not os.path.exists(out_dir): + os.makedirs(out_dir) + +# Output Rasters +path_lc_tr = os.path.join(out_dir, 'lc_tr.tiff') +path_lc_bl = os.path.join(out_dir, 'lc_bl.tiff') +path_lc_dg = os.path.join(out_dir, 'lc_dg.tiff') +path_lc_tg = os.path.join(out_dir, 'lc_tg.tiff') +path_change_yf_yi = os.path.join(out_dir, 'change_yf_yi.tiff') +path_lc_baseline_esa = os.path.join(out_dir, 'lc_baseline_esa.tiff') +path_lc_target_esa = os.path.join(out_dir, 'lc_target_esa.tiff') +path_out_img = os.path.join(out_dir, 'stack.tiff') + +# Contours +contours_change_yf_yi = os.path.join(out_dir, '/change_yf_yi0.shp') + + +# Parsing Inputs +# Load static inputs +# Transition Matrix, ESA Legend, IPCC Legend +# Input Raster and Vector Paths +params = json.load(open(fjson)) + +crs = params.get("crs") + +trans_matrix_val = params.get("trans_matrix") +trans_matrix = LCTransitionDefinitionDeg.Schema().load(trans_matrix_val) + +esa_to_custom_nesting = LCLegendNesting.Schema().load( + params.get("legend_nesting_esa_to_custom") +) + +ipcc_nesting = LCLegendNesting.Schema().load( + params.get("legend_nesting_custom_to_ipcc") +) + +class_codes = sorted([c.code for c in esa_to_custom_nesting.parent.key]) +class_positions = [*range(1, len(class_codes) + 1)] + +# Load dynamic inputs +# Baseline ESA Raster +raster_yi = rasterio.open(path_raster_yi) +lc_baseline_esa = raster_yi.read(1) +yi_dict_profile = dict(raster_yi.profile) + +for k in yi_dict_profile: + print(k.upper(), yi_dict_profile[k]) + +# Target ESA Raster +raster_yf = rasterio.open(path_raster_yf) +lc_target_esa = raster_yf.read(1) +yf_dict_profile = dict(raster_yf.profile) + +for k in yf_dict_profile: + print(k.upper(), yf_dict_profile[k]) + +cols = raster_yi.width +rows = raster_yf.height +transform = raster_yi.transform +projection = raster_yi.crs + +# Setting up output +saveRaster(lc_baseline_esa.astype('int8'), + path_lc_baseline_esa, + cols, + rows, + projection, + "Land_cover_yi") + +saveRaster(lc_target_esa.astype('int8'), + path_lc_target_esa, + cols, + rows, + projection, + "Land_cover_yf") + +# Algorithm execution +# Transition codes are based on the class code indices +# (i.e. their order when sorted by class code) - +# not the class codes themselves. +# So need to reclass the land cover used for the transition calculations +# from the raw class codes to the positional indices of those class codes. +# And before doing that, need to reclassified initial and +# final layers to the IPCC (or custom) classes. + +# Processing baseline raster +bl_remap_1 = remap(lc_baseline_esa, + np.asarray(esa_to_custom_nesting.get_list()[0]), + np.asarray(esa_to_custom_nesting.get_list()[1])) +lc_bl = remap(bl_remap_1, np.asarray(class_codes), np.asarray(class_positions)) + +saveRaster(lc_bl.astype('int8'), + path_lc_bl, + cols, + rows, + projection, + "Land_cover_yi") + +# Processing Target Raster +tg_remap_1 = remap(lc_target_esa, + np.asarray(esa_to_custom_nesting.get_list()[0]), + np.asarray(esa_to_custom_nesting.get_list()[1])) +lc_tg = remap(tg_remap_1, np.asarray(class_codes), np.asarray(class_positions)) + +saveRaster(lc_tg.astype('int8'), + path_lc_tg, + cols, + rows, + projection, + "Land_cover_yf") + +# Processing Transition Map +# Compute transition map (first digit for baseline land cover, +# and second digit for target year land cover) +lc_tr = (lc_bl * esa_to_custom_nesting.parent.get_multiplier()) + lc_tg + +# Compute land cover transition +# Remap persistence classes so they are sequential. +# This makes it easier to assign a clear color ramp in QGIS. +lc_tr_pre_remap = remap(lc_tr, + np.asarray(trans_matrix.get_persistence_list()[0]), + np.asarray(trans_matrix.get_persistence_list()[1])) + +saveRaster(lc_tr_pre_remap.astype('int8'), + path_lc_tr, + cols, + rows, + projection, + "Land_cover_transitions_yi-yf") + +# Compute land cover degradation +# definition of land cover transitions as degradation (-1), +# improvement (1), or no relevant change (0) +lc_dg = remap(lc_tr, + np.asarray(trans_matrix.get_list()[0]), + np.asarray(trans_matrix.get_list()[1])) + +saveRaster(lc_dg.astype('int8'), + path_lc_dg, + cols, + rows, + projection, + "Land_cover_degradation") + +# Compute Mutlibands stack +# Land Cover Degradation + Baseline ESA +# + Target ESA + Land Cover Transition +dg_raster = rasterio.open(path_lc_dg, "r") +dg = dg_raster.read(1, masked=True) +baseline_esa = rasterio.open(path_lc_baseline_esa, "r").read(1, masked=True) +target_esa = rasterio.open(path_lc_target_esa, "r").read(1, masked=True) +tr = rasterio.open(path_lc_tr, "r").read(1, masked=True) + +band_list = [dg, lc_baseline_esa, lc_target_esa, tr] +out_meta = dg_raster.meta.copy() +out_meta.update({"count": 4}) + +with rasterio.open(path_out_img, 'w', **out_meta) as dest: + for band_nr, src in enumerate(band_list, start=1): + dest.write(src, band_nr)