def repnd_by_rstval(ref_rst, val_rst, out_rst): """ Replace NoData Values with values from another raster """ import numpy as np from osgeo import gdal from glass.g.wt.rst import obj_to_rst # TODO check if shape of two rasters are the same # Open Rasters and Get data as array refsrc = gdal.Open(ref_rst, gdal.GA_ReadOnly) popsrc = gdal.Open(val_rst, gdal.GA_ReadOnly) nd_val = refsrc.GetRasterBand(1).GetNoDataValue() nd_pop = refsrc.GetRasterBand(1).GetNoDataValue() refnum = refsrc.GetRasterBand(1).ReadAsArray() popnum = popsrc.GetRasterBand(1).ReadAsArray() # Replace NoData Values np.copyto(refnum, popnum, where=refnum==nd_val) # Export to file obj_to_rst(refnum, out_rst, refsrc, noData=nd_pop) return out_rst
def bands_to_rst(inRst, outFolder): """ Export all bands of a raster to a new dataset TODO: this could be done using gdal_translate """ import numpy import os from osgeo import gdal from glass.g.wt.rst import obj_to_rst from glass.g.prop.rst import get_nodata rst = gdal.Open(inRst) if rst.RasterCount == 1: return nodata = get_nodata(inRst) for band in range(rst.RasterCount): band += 1 src_band = rst.GetRasterBand(band) if src_band is None: continue else: # Convert to array array = numpy.array(src_band.ReadAsArray()) obj_to_rst(array, os.path.join( outFolder, '{r}_{b}.tif'.format(r=os.path.basename( os.path.splitext(inRst)[0]), b=str(band))), inRst, noData=nodata)
def gdal_mapcalc(expression, exp_val_paths, outRaster, template_rst, outNodata=-99999): """ GDAL Raster Calculator TODO: Check if rasters dimensions are equal """ import numpy as np from osgeo import gdal from py_expression_eval import Parser from glass.g.prop.img import get_nd from glass.g.wt.rst import obj_to_rst parser = Parser() EXPRESSION = parser.parse(expression) evalValue = {} noDatas = {} for x in EXPRESSION.variables(): img = gdal.Open(exp_val_paths[x]) arr = img.ReadAsArray().astype(float) evalValue[x] = arr noDatas[x] = get_nd(img) result = EXPRESSION.evaluate(evalValue) for v in noDatas: np.place(result, evalValue[v]==noDatas[v], outNodata) # Write output and return return obj_to_rst(result, outRaster, template_rst, noData=outNodata)
def nbr(nir, swir, outrst): """ Normalized Burn Ratio EXPRESSION Sentinel-2A: (9-12) / (9+12) """ import numpy as np from osgeo import gdal, gdal_array from glass.g.wt.rst import obj_to_rst # Open Images srcNir = gdal.Open(nir, gdal.GA_ReadOnly) srcSwir = gdal.Open(swir, gdal.GA_ReadOnly) # To Array numNir = srcNir.GetRasterBand(1).ReadAsArray().astype(float) numSwir = srcSwir.GetRasterBand(1).ReadAsArray().astype(float) # Do Calculation nbr = (numNir - numSwir) / (numNir + numSwir) # Place NoData Value nirNdVal = srcNir.GetRasterBand(1).GetNoDataValue() swirNdVal = srcSwir.GetRasterBand(1).GetNoDataValue() nd = np.amin(nbr) - 1 np.place(nbr, numNir == nirNdVal, nd) np.place(nbr, numSwir == swirNdVal, nd) # Export Result return obj_to_rst(nbr, outrst, nir, noData=nd)
def rst_mean(rsts, out_rst): """ Return rasters mean """ import numpy as np from osgeo import gdal from glass.g.wt.rst import obj_to_rst # Open images src_rst = [gdal.Open(i, gdal.GA_ReadOnly) for i in rsts] # To Array num_rst = [s.GetRasterBand(1).ReadAsArray() for s in src_rst] # Sum all rasters num_out = num_rst[0] for i in range(1, len(num_rst)): num_out = num_out + num_rst[i] # Get Mean num_out = num_out / len(rsts) # Place NoDatas nd_out = np.amin(num_out) - 1 for r in range(len(src_rst)): nd_val = src_rst[r].GetRasterBand(1).GetNoDataValue() np.place(num_out, num_rst[r] == nd_val, nd_out) # Export result return obj_to_rst(num_out, out_rst, src_rst[0], noData=nd_out)
def resample_by_majority(refrst, valrst, out_rst): """ Resample valrst based on refrst: Get Majority value of valrst for each cell in refrst Useful when ref raster has cellsize greater than value raster. TODO: Valrst must be of int type """ import numpy as np from osgeo import gdal from glass.g.prop.img import get_cell_size, get_nd from glass.g.wt.rst import obj_to_rst # Data to Array if type(refrst) == gdal.Dataset: refsrc = refrst else: refsrc = gdal.Open(refrst) if type(valrst) == gdal.Dataset: valsrc = valrst else: valsrc = gdal.Open(valrst) refnum = refsrc.ReadAsArray() valnum = valsrc.ReadAsArray() # Get Ref shape ref_shape = refnum.shape # in a row, how many cells valnum are for each refnum cell refcs = int(get_cell_size(refsrc)[0]) valcs = int(get_cell_size(valsrc)[0]) dcell = int(refcs / valcs) # Valnum must be of int type # Create generalized/resampled raster resnum = np.zeros(ref_shape, dtype=valnum.dtype) for row in range(ref_shape[0]): for col in range(ref_shape[1]): resnum[row, col] = np.bincount( valnum[row*dcell:row*dcell+dcell, col*dcell : col*dcell+dcell].reshape(dcell*dcell) ).argmax() # Export out raster return obj_to_rst(resnum, out_rst, refsrc, noData=get_nd(valsrc))
def rst_rotation(inFolder, template, outFolder, img_format='.tif'): """ Invert raster data """ import os from osgeo import gdal from glass.pys.oss import lst_ff from glass.g.rd.rst import rst_to_array from glass.g.prop.rst import get_nodata from glass.g.wt.rst import obj_to_rst rasters = lst_ff(inFolder, file_format=img_format) for rst in rasters: a = rst_to_array(rst) nd = get_nodata(rst) obj_to_rst(a[::-1], os.path.join(outFolder, os.path.basename(rst)), template, noData=nd) return outFolder
def rstval_to_binrst(rst, outfld, fileformat=None): """ Export all values in a raster to new binary raster """ import os import numpy as np from osgeo import gdal from glass.g.wt.rst import obj_to_rst from glass.pys.oss import fprop fileformat = fileformat if fileformat else '.tif' rst_src = gdal.Open(rst, gdal.GA_ReadOnly) # Get Nodata nd = rst_src.GetRasterBand(1).GetNoDataValue() # Data To Array rst_num = rst_src.GetRasterBand(1).ReadAsArray() # Get Unique values in Raster val = np.unique(rst_num) val = list(val[val != nd]) fn = fprop(rst, 'fn') for v in val: # Create new binary array val_a = np.zeros(rst_num.shape, dtype=np.uint8) np.place(val_a, rst_num == v, 1) # Export to new raster obj_to_rst(val_a, os.path.join(outfld, fn + '_val' + str(v) + fileformat), rst_src, noData=0)
def ndvi(nir, red, outRst): """ Apply Normalized Difference NIR/Red Normalized Difference Vegetation Index, Calibrated NDVI - CDVI https://www.indexdatabase.de/db/i-single.php?id=58 EXPRESSION: (nir - red) / (nir + red) """ import numpy as np from osgeo import gdal, gdal_array from glass.g.wt.rst import obj_to_rst # Open Images src_nir = gdal.Open(nir, gdal.GA_ReadOnly) src_red = gdal.Open(red, gdal.GA_ReadOnly) # To Array num_nir = src_nir.GetRasterBand(1).ReadAsArray().astype(float) num_red = src_red.GetRasterBand(1).ReadAsArray().astype(float) # Do Calculation ndvi = (num_nir - num_red) / (num_nir + num_red) # Place NoData Value nirNdVal = src_nir.GetRasterBand(1).GetNoDataValue() redNdVal = src_red.GetRasterBand(1).GetNoDataValue() ndNdvi = np.amin(ndvi) - 1 np.place(ndvi, num_nir==nirNdVal, ndNdvi) np.place(ndvi, num_red==redNdVal, ndNdvi) # Export Result return obj_to_rst(ndvi, outRst, nir, noData=ndNdvi)
def rst_to_tiles(rst, n_tiles_x, n_tiles_y, out_folder): """ Raster file to tiles """ import os from glass.pys.oss import fprop from osgeo import gdal from glass.g.wt.rst import obj_to_rst rstprop = fprop(rst, ['fn', 'ff']) rstn, rstf = rstprop['filename'], rstprop['fileformat'] # Open Raster img = gdal.Open(rst, gdal.GA_ReadOnly) # Get raster Geo Properties geotrans = img.GetGeoTransform() # Get rows and columns number of original raster nrows, ncols = img.RasterYSize, img.RasterXSize # Get rows and columns number for the tiles tile_rows = int(nrows / n_tiles_y) tile_cols = int(ncols / n_tiles_x) if tile_rows == nrows / n_tiles_y: remain_rows = 0 else: remain_rows = nrows - (tile_rows * n_tiles_y) if tile_cols == ncols / n_tiles_x: remain_cols = 0 else: remain_cols = ncols - (tile_cols * n_tiles_x) # Create news raster rst_num = img.GetRasterBand(1).ReadAsArray() nd = img.GetRasterBand(1).GetNoDataValue() for tr in range(n_tiles_y): if tr + 1 == n_tiles_y: __tile_rows = tile_rows + remain_rows else: __tile_rows = tile_rows top = geotrans[3] + (geotrans[5] * (tr * tile_rows)) for tc in range(n_tiles_x): if tc + 1 == n_tiles_x: __tile_cols = tile_cols + remain_cols else: __tile_cols = tile_cols left = geotrans[0] + (geotrans[1] * (tc * tile_cols)) nr = rst_num[tr * tile_rows:tr * tile_rows + __tile_rows, tc * tile_cols:tc * tile_cols + __tile_cols] # New array to file obj_to_rst(nr, os.path.join( out_folder, rstn + '_' + str(tr) + '_' + str(tc) + rstf), img, noData=nd, geotrans=(left, geotrans[1], geotrans[2], top, geotrans[4], geotrans[5])) return out_folder
def k_means(imgFiles, out, n_cls=8): """ K-Means implementation """ import os from osgeo import gdal, gdal_array import numpy as np from sklearn import cluster from glass.g.wt.rst import obj_to_rst gdal.UseExceptions() gdal.AllRegister() singleBand = None if type(imgFiles) != list: # Check if img is a valid file if not os.path.exists(imgFiles): raise ValueError("{} is not a valid file".format(imgFiles)) img_src = gdal.Open(imgFiles, gdal.GA_ReadOnly) n_bnd = img_src.RasterCount ndVal = img_src.GetRasterBand(1).GetNoDataValue() if n_bnd == 1: band = img_src.GetRasterBand(1) img = band.ReadAsArray() singleBand = 1 #X = img.reshape((-1, 1)) else: img = np.zeros((img_src.RasterYSize, img_src.RasterXSize, n_bnd), gdal_array.GDALTypeCodeToNumericTypeCode( img_src.GetRasterBand(1).DataType)) for b in range(img.shape[2]): img[:, :, b] = img_src.GetRasterBand(b + 1).ReadAsArray() else: img_src = [gdal.Open(i, gdal.GA_ReadOnly) for i in imgFiles] ndVal = img_src[0].GetRasterBand(1).GetNoDataValue() n_bnd = len(img_src) img = np.zeros( (img_src[0].RasterYSize, img_src[0].RasterXSize, len(img_src)), gdal_array.GDALTypeCodeToNumericTypeCode( img_src[0].GetRasterBand(1).DataType)) for b in range(img.shape[2]): img[:, :, b] = img_src[b].GetRasterBand(1).ReadAsArray() # Reshape arrays for classification if not singleBand: new_shape = (img.shape[0] * img.shape[1], img.shape[2]) X = img[:, :, :n_bnd].reshape(new_shape) else: X = img.reshape((-1, 1)) kmeans = cluster.KMeans(n_clusters=n_cls) kmeans.fit(X) X_cluster = kmeans.labels_ if not singleBand: X_cluster = X_cluster.reshape(img[:, :, 0].shape) else: X_cluster = X_cluster.reshape(img.shape) # Place nodata values if type(imgFiles) != list: tmp = img_src.GetRasterBand(1).ReadAsArray() else: tmp = img_src[0].GetRasterBand(1).ReadAsArray() np.place(X_cluster, tmp == ndVal, 255) return obj_to_rst(X_cluster, out, imgFiles if type(imgFiles) != list else imgFiles[0], noData=255)
def update_globe_land_cover(original_globe_raster, osm_urban_atlas_raster, osm_globe_raster, epsg, updated_globe_raster, detailed_globe_raster): """ Update the original Glob Land 30 with the result of the conversion of OSM DATA to the Globe Land Cover nomenclature; Also updates he previous updated Glob Land 30 with the result of the conversion of osm data to the Urban Atlas Nomenclature """ import os import numpy as np from glass.g.rd.rst import rst_to_array from glass.g.prop.rst import get_cellsize, get_nodata from glass.g.wt.rst import obj_to_rst # ############################# # # Convert images to numpy array # # ############################# # np_globe_original = rst_to_array(original_globe_raster) np_globe_osm = rst_to_array(osm_globe_raster) np_ua_osm = rst_to_array(osm_urban_atlas_raster) # ################################## # # Check the dimension of both images # # ################################## # if np_globe_original.shape != np_globe_osm.shape: return ( 'The Globe Land 30 raster (original) do not have the same number' ' of columns/lines comparing with the Globe Land 30 derived ' 'from OSM data') elif np_globe_original.shape != np_ua_osm.shape: return ( 'The Globe Land 30 raster (original) do not have the same ' 'number of columns/lines comparing with the Urban Atlas raster ' 'derived from OSM data') elif np_globe_osm.shape != np_ua_osm.shape: return ( 'The Globe Land 30 derived from OSM data do not have the same ' 'number of columns/lines comparing with the Urban Atlas raster ' 'derived from OSM data') # ############## # # Check Cellsize # # ############## # cell_of_rsts = get_cellsize( [original_globe_raster, osm_globe_raster, osm_urban_atlas_raster], xy=True, gisApi='gdal') cell_globe_original = cell_of_rsts[original_globe_raster] cell_globe_osm = cell_of_rsts[osm_globe_raster] cell_ua_osm = cell_of_rsts[osm_urban_atlas_raster] if cell_globe_original != cell_globe_osm: return ( 'The cellsize of the Globe Land 30 raster (original) is not the ' 'same comparing with the Globe Land 30 derived from OSM data') elif cell_globe_original != cell_ua_osm: return ( 'The cellsize of the Globe Land 30 raster (original) is not the ' 'same comparing with the Urban Atlas raster derived from OSM data') elif cell_ua_osm != cell_globe_osm: return ( 'The cellsize of the Globe Land 30 derived from OSM data is not ' 'the same comparing with the Urban Atlas raster derived from ' 'OSM data') # ############################# # # Get the Value of Nodata Cells # # ############################# # nodata_glob_original = get_nodata(original_globe_raster, gisApi='gdal') nodata_glob_osm = get_nodata(osm_globe_raster, gisApi='gdal') nodata_ua_osm = get_nodata(osm_urban_atlas_raster, gisApi='gdal') # ######################################## # # Create a new map - Globe Land 30 Updated # # ######################################## # """ Create a new array with zeros... 1) The zeros will be replaced by the values in the Globe Land derived from OSM. 2) The zeros will be replaced by the values in the Original Globe Land at the cells with NULL data in the Globe Land derived from OSM. The meta array will identify values origins in the updated raster: 1 - Orinal Raster 2 - OSM Derived Raster """ update_array = np.zeros( (np_globe_original.shape[0], np_globe_original.shape[1])) update_meta_array = np.zeros( (np_globe_original.shape[0], np_globe_original.shape[1])) # 1) np.copyto(update_array, np_globe_osm, 'no', np_globe_osm != nodata_glob_osm) # 1) meta np.place(update_meta_array, update_array != 0, 2) # 2) meta np.place(update_meta_array, update_array == 0, 1) # 2) np.copyto(update_array, np_globe_original, 'no', update_array == 0) # 2) meta np.place(update_meta_array, update_array == nodata_glob_original, int(nodata_glob_original)) # noData to int np.place(update_array, update_array == nodata_glob_original, int(nodata_glob_original)) updated_meta = os.path.join( os.path.dirname(updated_globe_raster), '{n}_meta{e}'.format( n=os.path.splitext(os.path.basename(updated_globe_raster))[0], e=os.path.splitext(os.path.basename(updated_globe_raster))[1])) # Create Updated Globe Cover 30 obj_to_rst(update_array, updated_globe_raster, original_globe_raster, noData=int(nodata_glob_original)) # Create Updated Globe Cover 30 meta obj_to_rst(update_meta_array, updated_meta, original_globe_raster, noData=int(nodata_glob_original)) # ################################################# # # Create a new map - Globe Land 30 Detailed with UA # # ################################################# # np_update = rst_to_array(updated_globe_raster) detailed_array = np.zeros((np_update.shape[0], np_update.shape[1])) detailed_meta_array = np.zeros((np_update.shape[0], np_update.shape[1])) """ Replace 80 Globe Land for 11, 12, 13, 14 of Urban Atlas The meta array will identify values origins in the detailed raster: 1 - Updated Raster 2 - UA Derived Raster from OSM """ # Globe - Mantain some classes np.place(detailed_array, np_update == 30, 8) np.place(detailed_array, np_update == 30, 1) np.place(detailed_array, np_update == 40, 9) np.place(detailed_array, np_update == 40, 1) np.place(detailed_array, np_update == 50, 10) np.place(detailed_array, np_update == 50, 1) np.place(detailed_array, np_update == 10, 5) np.place(detailed_array, np_update == 10, 1) # Water bodies np.place(detailed_array, np_ua_osm == 50 or np_update == 60, 7) np.place(detailed_meta_array, np_ua_osm == 50 or np_update == 60, 1) # Urban - Where Urban Atlas IS NOT NULL np.place(detailed_array, np_ua_osm == 11, 1) np.place(detailed_meta_array, np_ua_osm == 11, 2) np.place(detailed_array, np_ua_osm == 12, 2) np.place(detailed_meta_array, np_ua_osm == 12, 2) np.place(detailed_array, np_ua_osm == 13, 3) np.place(detailed_meta_array, np_ua_osm == 13, 2) np.place(detailed_array, np_ua_osm == 14, 4) np.place(detailed_meta_array, np_ua_osm == 14, 2) # Urban Atlas - Class 30 to 6 np.place(detailed_array, np_ua_osm == 30, 6) np.place(detailed_meta_array, np_ua_osm == 30, 2) # Create Detailed Globe Cover 30 obj_to_rst(detailed_array, detailed_globe_raster, original_globe_raster, noData=0) # Create Detailed Globe Cover 30 meta detailed_meta = os.path.join( os.path.dirname(detailed_globe_raster), '{n}_meta{e}'.format( n=os.path.splitext(os.path.basename(detailed_meta))[0], e=os.path.splitext(os.path.basename(detailed_meta))[1])) obj_to_rst(detailed_meta_array, detailed_meta, original_globe_raster, noData=0)
def floatrst_to_intrst(in_rst, out_rst): """ Raster with float data to Raster with Integer Values """ import numpy as np from osgeo import gdal from glass.g.prop.img import get_nd from glass.g.wt.rst import obj_to_rst nds = { 'int8': -128, 'int16': -32768, 'int32': -2147483648, 'uint8': 255, 'uint16': 65535, 'uint32': 4294967295 } # Open Raster img = gdal.Open(in_rst) # Raster to Array rstnum = img.ReadAsArray() # Round data rstint = np.around(rstnum, decimals=0) # Get min and max tstmin = rstint.min() tstmax = rstint.max() try: nd = int(round(get_nd(img), 0)) except: nd = None if tstmin == nd: np.place(rstint, rstint == nd, np.nan) rstmin = rstint.min() rstmax = tstmax else: rstmin = tstmin if tstmax == nd: np.place(rstint, rstint == nd, np.nan) rstmax = rstint.max() else: rstmax = tstmax # Get dtype for output raster if rstmin < 0: if rstmin <= -128: if rstmin <= -32768: tmin = 'int32' else: tmin = 'int16' else: tmin = 'int8' else: tmin = 'u' if tmin == 'u': if rstmax >= 255: if rstmax >= 65535: tmax = 'uint32' else: tmax = 'uint16' else: tmax = 'uint8' else: if tmin == 'int8': if rstmax >= 127: if rstmax >= 32767: tmax = 'int32' else: tmax = 'int16' else: tmax = 'int8' elif tmin == 'int16': if rstmax >= 32767: tmax = 'int32' else: tmax = 'int16' else: tmax = 'int32' if tmax == 'int8': nt = np.int8 elif tmax == 'int16': nt = np.int16 elif tmax == 'int32': nt = np.int32 elif tmax == 'uint8': nt = np.uint8 elif tmax == 'uint16': nt = np.uint16 else: nt = np.uint32 # Get nodata for new raster new_nd = nds[tmax] # Place NoData value np.nan_to_num(rstint, copy=False, nan=new_nd) # Convert array type to integer rstint = rstint.astype(nt) # Export result to file and return return obj_to_rst(rstint, out_rst, img, noData=new_nd)
def conv_rst_dtype(rst, out, odtype): """ Change Raster Dtype odtype options: * byte * int8 * uint8 * int16 * uint16 * int32 * uint32 * float32 """ import numpy as np from osgeo import gdal from glass.g.wt.rst import obj_to_rst # Open Raster src = gdal.Open(rst, gdal.GA_ReadOnly) imgnum = src.ReadAsArray() # Get nodata value ndval = src.GetRasterBand(1).GetNoDataValue() # Get new data type if odtype == 'int8': dt = np.int8 ndval = int(ndval) elif odtype == 'uint8': dt = np.uint8 ndval = int(ndval) elif odtype == 'int16': dt = np.int16 ndval = int(ndval) elif odtype == 'uint16': dt = np.uint16 ndval = int(ndval) elif odtype == 'int32': dt = np.int32 ndval = int(ndval) elif odtype == 'uint32': dt = np.uint32 ndval = int(ndval) elif odtype == 'float32': dt = np.float32 ndval = float(ndval) elif odtype == 'byte': dt = np.byte ndval = int(ndval) else: dt = np.float32 ndval = float(ndval) # Data type conversion newnum = imgnum.astype(dt) # Export new raster obj_to_rst(newnum, out, src, noData=ndval) return out
def imgcls_from_mdl(mdl, imgvar, outrst, fileformat='.tif'): """ Classification from Model File """ import os from joblib import load from osgeo import gdal, gdal_array import numpy as np from glass.g.wt.rst import obj_to_rst if type(imgvar) != list: # Check if it is a folder if os.path.isdir(imgvar): from glass.pys.oss import lst_ff imgvar = lst_ff(imgvar, file_format=fileformat if fileformat else '.tif') else: imgvar = [imgvar] # Read model file rf = load(mdl) # Open feature images img_var = [gdal.Open(i, gdal.GA_ReadOnly) for i in imgvar] # Get NoData Value nd_val = img_var[0].GetRasterBand(1).GetNoDataValue() # Get band number of each raster img_bnd = [i.RasterCount for i in img_var] # Check images shape ref_shp = (img_var[0].RasterYSize, img_var[0].RasterXSize) if len(img_var) > 1: for r in range(1, len(img_var)): rst_shp = (img_var[r].RasterYSize, img_var[r].RasterXSize) if ref_shp != rst_shp: raise ValueError( 'There are at least two raster files with different shape') # Get features number nvar = sum(img_bnd) # Convert feature images to array X = np.zeros((ref_shp[0], ref_shp[1], nvar), gdal_array.GDALTypeCodeToNumericTypeCode( img_var[0].GetRasterBand(1).DataType)) f = 0 for r in range(len(img_var)): for b in range(img_bnd[r]): X[:, :, f] = img_var[r].GetRasterBand(b + 1).ReadAsArray() f += 1 # Reshape nshp = (X.shape[0] * X.shape[1], X.shape[2]) n_x = X[:, :, :nvar].reshape(nshp) # Predict y_cls = rf.predict(n_x) # Reshape result res = y_cls.reshape(X[:, :, 0].shape) # Place nodata values tmp = img_var[0].GetRasterBand(1).ReadAsArray() np.place(res, tmp == nd_val, 255) # Export result obj_to_rst(res, outrst, imgvar[0], noData=255) return outrst
def num_roads(osmdata, nom, lineTbl, polyTbl, folder, cellsize, srs, rstTemplate): """ Select Roads and convert To Raster """ import datetime as dt import os import numpy as np from threading import Thread from glass.g.rd.rst import rst_to_array from glass.g.tbl.filter import sel_by_attr from glass.g.gp.prox.bfing.sql import splite_buffer from glass.g.dp.torst import shp_to_rst from glass.g.wt.rst import obj_to_rst from glass.ng.prop.sql import row_num time_a = dt.datetime.now().replace(microsecond=0) NR = row_num(osmdata, lineTbl, where="roads IS NOT NULL", api='sqlite') time_b = dt.datetime.now().replace(microsecond=0) if not NR: return None, {0: ('count_rows_roads', time_b - time_a)} timeGasto = {0: ('count_rows_roads', time_b - time_a)} # Get Roads Buffer LULC_CLS = '1221' if nom != "GLOBE_LAND_30" else '801' bfShps = [] def exportAndBuffer(): time_cc = dt.datetime.now().replace(microsecond=0) roadFile = splite_buffer(osmdata, lineTbl, "bf_roads", "geometry", os.path.join(folder, 'bf_roads.gml'), whrClause="roads IS NOT NULL", outTblIsFile=True, dissolve=None) time_c = dt.datetime.now().replace(microsecond=0) distRst = shp_to_rst(roadFile, None, cellsize, -1, os.path.join(folder, 'rst_roads.tif'), epsg=srs, rst_template=rstTemplate, api="gdal") time_d = dt.datetime.now().replace(microsecond=0) bfShps.append(distRst) timeGasto[1] = ('buffer_roads', time_c - time_cc) timeGasto[2] = ('to_rst_roads', time_d - time_c) BUILDINGS = [] def exportBuild(): time_ee = dt.datetime.now().replace(microsecond=0) NB = row_num(osmdata, polyTbl, where="building IS NOT NULL", api='sqlite') time_e = dt.datetime.now().replace(microsecond=0) timeGasto[3] = ('check_builds', time_e - time_ee) if not NB: return bShp = sel_by_attr( osmdata, "SELECT geometry FROM {} WHERE building IS NOT NULL".format( polyTbl), os.path.join(folder, 'road_builds.shp'), api_gis='ogr') time_f = dt.datetime.now().replace(microsecond=0) bRst = shp_to_rst(bShp, None, cellsize, -1, os.path.join(folder, 'road_builds.tif'), epsg=srs, rst_template=rstTemplate, api='gdal') time_g = dt.datetime.now().replace(microsecond=0) BUILDINGS.append(bRst) timeGasto[4] = ('export_builds', time_f - time_e) timeGasto[5] = ('builds_to_rst', time_g - time_f) thrds = [ Thread(name="build-th", target=exportBuild), Thread(name='roads-th', target=exportAndBuffer) ] for t in thrds: t.start() for t in thrds: t.join() if not len(BUILDINGS): return {LULC_CLS: bfShps[0]} time_x = dt.datetime.now().replace(microsecond=0) BUILD_ARRAY = rst_to_array(BUILDINGS[0], with_nodata=True) rst_array = rst_to_array(bfShps[0], with_nodata=True) np.place(rst_array, BUILD_ARRAY == 1, 0) newRaster = obj_to_rst(rst_array, os.path.join(folder, 'fin_roads.tif'), rstTemplate, noData=-1) time_z = dt.datetime.now().replace(microsecond=0) timeGasto[6] = ('sanitize_roads', time_z - time_x) return {int(LULC_CLS): newRaster}, timeGasto
def gdal_slope(dem, srs, slope, unit='DEGREES'): """ Create Slope Raster TODO: Test and see if is running correctly """ import numpy; import math from osgeo import gdal from scipy.ndimage import convolve from glass.g.rd.rst import rst_to_array from glass.g.wt.rst import obj_to_rst from glass.g.prop.rst import get_cellsize, get_nodata # ################ # # Global Variables # # ################ # cellsize = get_cellsize(dem, gisApi='gdal') # Get Nodata Value NoData = get_nodata(dem) # #################### # # Produce Slope Raster # # #################### # # Get Elevation array arr_dem = rst_to_array(dem) # We have to get a array with the number of nearst cells with values with_data = numpy.zeros((arr_dem.shape[0], arr_dem.shape[1])) numpy.place(with_data, arr_dem!=NoData, 1.0) mask = numpy.array([[1,1,1], [1,0,1], [1,1,1]]) arr_neigh = convolve(with_data, mask, mode='constant') numpy.place(arr_dem, arr_dem==NoData, 0.0) # The rate of change in the x direction for the center cell e is: kernel_dz_dx_left = numpy.array([[0,0,1], [0,0,2], [0,0,1]]) kernel_dz_dx_right = numpy.array([[1,0,0], [2,0,0], [1,0,0]]) dz_dx = (convolve(arr_dem, kernel_dz_dx_left, mode='constant')-convolve(arr_dem, kernel_dz_dx_right, mode='constant')) / (arr_neigh * cellsize) # The rate of change in the y direction for cell e is: kernel_dz_dy_left = numpy.array([[0,0,0], [0,0,0], [1,2,1]]) kernel_dz_dy_right = numpy.array([[1,2,1], [0,0,0], [0,0,0]]) dz_dy = (convolve(arr_dem, kernel_dz_dy_left, mode='constant')-convolve(arr_dem, kernel_dz_dy_right, mode='constant')) / (arr_neigh * cellsize) # Taking the rate of change in the x and y direction, the slope for the center cell e is calculated using rise_run = ((dz_dx)**2 + (dz_dy)**2)**0.5 if unit=='DEGREES': arr_slope = numpy.arctan(rise_run) * 57.29578 elif unit =='PERCENT_RISE': arr_slope = numpy.tan(numpy.arctan(rise_run)) * 100.0 # Estimate the slope for the cells with less than 8 neigh aux_dem = rst_to_array(dem) index_vizinhos = numpy.where(arr_neigh<8) for idx in range(len(index_vizinhos[0])): # Get Value of the cell lnh = index_vizinhos[0][idx] col = index_vizinhos[1][idx] e = aux_dem[lnh][col] a = aux_dem[lnh-1][col-1] if a == NoData: a = e if lnh==0 or col==0: a=e b = aux_dem[lnh-1][col] if b == NoData: b = e if lnh==0: b=e try: c = aux_dem[lnh-1][col+1] if c == NoData: c=e if lnh==0: c=e except: c = e d = aux_dem[lnh][col-1] if d == NoData: d = e if col==0: d=e try: f = aux_dem[lnh][col+1] if f == NoData: f=e except: f=e try: g = aux_dem[lnh+1][col-1] if g == NoData: g=e if col==0: g=e except: g=e try: h = aux_dem[lnh+1][col] if h ==NoData: h = e except: h=e try: i = aux_dem[lnh+1][col+1] if i == NoData: i = e except: i=e dz_dx = ((c + 2*f + i) - (a + 2*d + g)) / (8 * cellsize) dz_dy = ((g + 2*h + i) - (a + 2*b + c)) / (8 * cellsize) rise_sun = ((dz_dx)**2 + (dz_dy)**2)**0.5 if unit == 'DEGREES': arr_slope[lnh][col] = math.atan(rise_sun) * 57.29578 elif unit == 'PERCENT_RISE': arr_slope[lnh][col] = math.tan(math.atan(rise_sun)) * 100.0 # Del value originally nodata numpy.place(arr_slope, aux_dem==NoData, numpy.nan) #arr_slope[lnh][col] = slope_degres obj_to_rst(arr_slope, slope, dem)
def rcls_rst(inrst, rclsRules, outrst, api='gdal', maintain_ext=True): """ Reclassify a raster (categorical and floating points) if api == 'gdal rclsRules = { 1 : 99, 2 : 100 ... } or rclsRules = { (0, 8) : 1 (8, 16) : 2 '*' : 'NoData' } elif api == grass: rclsRules should be a path to a text file """ if api == 'gdal': import numpy as np import os from osgeo import gdal from glass.g.wt.rst import obj_to_rst from glass.g.rd.rsrc import imgsrc_to_num from glass.g.prop.img import get_nd if not os.path.exists(inrst): raise ValueError('File {} does not exist!'.format(inrst)) # Open Raster img = gdal.Open(inrst) # Raster to Array rst_num = imgsrc_to_num(img) nodataVal = get_nd(img) rcls_num = np.full(rst_num.shape, 255, dtype=np.uint8) # Change values for k in rclsRules: if rclsRules[k] == 'NoData': continue if type(k) == str: continue elif type(k) == tuple: q = (rst_num > k[0]) & (rst_num <= k[1]) else: q = rst_num == k np.place(rcls_num, q, rclsRules[k]) if '*' in rclsRules and rclsRules['*'] != 'NoData': np.place(rcls_num, rcls_num == 255, rclsRules['*']) if 'NoData' in rclsRules and rclsRules['NoData'] != 'NoData': np.place(rcls_num, rst_num == nodataVal, rclsRules['NoData']) if not maintain_ext: from glass.g.rst.rshp import rshp_to_data left, cellx, z, top, c, celly = img.GetGeoTransform() clip_rcls, n_left, n_top = rshp_to_data(rcls_num, 255, left, cellx, top, celly) return obj_to_rst(clip_rcls, outrst, img, noData=255, geotrans=(n_left, cellx, z, n_top, c, celly)) else: return obj_to_rst(rcls_num, outrst, img, noData=255) elif api == "pygrass": from grass.pygrass.modules import Module r = Module('r.reclass', input=inrst, output=outrst, rules=rclsRules, overwrite=True, run_=False, quiet=True) r() else: raise ValueError(f"API {api} is not available") return outrst
def proprndcells_to_rst(inrst, class_proportion, out_rst, sample_dim, cls_sample_min=None): """ Extract some cells from one raster and save them into a new raster The cells are extracted in a random way for each class in inrst. The number of cells extracted for each class are based on the values in class_proportion object """ from osgeo import gdal, gdal_array import numpy as np from glass.g.wt.rst import obj_to_rst img = gdal.Open(inrst, gdal.GA_ReadOnly) nd_val = img.GetRasterBand(1).GetNoDataValue() # Image to array num_ref = img.GetRasterBand(1).ReadAsArray() # Reshape array oned_ref = num_ref.reshape(num_ref.shape[0] * num_ref.shape[1]) # Get classes in inrst array id_cls = list(np.unique(oned_ref)) # Remove nodata value from id_cls if nd_val in id_cls: id_cls.remove(nd_val) # All classes in class_proportion must be in id_cls ks = list(class_proportion.keys()) for k in ks: if k not in id_cls: del class_proportion[k] # Exclude values not in ks ks = list(class_proportion.keys()) __id_cls = id_cls.copy() for c in __id_cls: if c not in ks: id_cls.remove(c) # Get absolute frequencies of inrst # Exclude no data values ref_sem_nd = oned_ref[oned_ref != nd_val] freq = np.bincount(ref_sem_nd) freq = freq[freq != 0] # Get number of cells for each class based on class_proportions class_cells = { c: int(round(class_proportion[c] * sample_dim / 100, 0)) for c in class_proportion } # The n_cells for each class could not be lesser than # cls_sample_min for c in class_cells: if cls_sample_min: if class_cells[c] < cls_sample_min: class_cells[c] = cls_sample_min # n_cells for each class could not be larger than the class frequency for e in range(len(id_cls)): if id_cls[e] == c: if freq[e] < class_cells[c]: class_cells[c] = freq[e] break # Get index array idx_ref = np.arange(oned_ref.size) # Get indexes for cells of each class idx_cls = [idx_ref[oned_ref == c] for c in id_cls] # Get indexes to be selected for each class sel_cls = [ np.random.choice(idx_cls[e], size=class_cells[id_cls[e]], replace=False) for e in range(len(id_cls)) ] # Create result res = np.zeros(oned_ref.shape, dtype=oned_ref.dtype) # Place selected cells in result array for c in range(len(id_cls)): res[sel_cls[c]] = id_cls[c] # Place nodata np.place(res, oned_ref == nd_val, nd_val) np.place(res, res == 0, nd_val) # Reshape res = res.reshape(num_ref.shape) # Save result obj_to_rst(res, out_rst, img, noData=nd_val) return out_rst
def lnd8_dn_to_ref(folder, img_format, meta_json, outWorkspace, srs): """ Landsat8 digital numbers to surface reflectance """ import math import json import os from glass.pys.oss import lst_ff from glass.g.rd.rst import rst_to_array from glass.g.prop.rst import get_cellsize, rst_stats from glass.g.wt.rst import obj_to_rst def Get_RTA(Ml, Qcalc, Al): """ Obtem Radiancia no Topo da Atmosfera Ml - relacao da radiancia multibanda Qcalc - imagem de satelite original Al - radiancia add band """ Llambda = Ml * Qcalc + Al return Llambda def GetIrraSolar(d, Lmax, Pmax): """ d - distancia da terra ao sol (com base no dia do ano em que a imagem foi recolhida) ESUN - irradiancia solar media exoatmosferica Lmax - radiancia maxima Pmax - reflectancia maxima """ return (math.pi * d**2) * (Lmax / Pmax) def GetRefAparente(d, esun, rta, Z): """ Reflectancia aparente Z - angulo zenital do sol """ pp = math.pi * rta * d**2 / esun * math.cos(Z) return pp def GetRefSuperfice(DNmin, Ml, Al, IrrSolar, Z, d, RefAparente): """Reflectancia a superficie""" Lp = (Ml * DNmin + Al - 0.01 * IrrSolar) * (math.cos(Z) / math.pi * d**2) p = math.pi * (RefAparente - Lp) * d**2 / IrrSolar * math.cos(Z) return p lst_bands = lst_ff(folder, file_format=img_format) json_file = open(meta_json, 'r') json_data = json.load(json_file) cellsize = get_cellsize(lst_bands[0], gisApi='gdal') # Estimate Surface Reflectance for each band for bnd in lst_bands: # Convert images to numpy array img = rst_to_array(bnd) # Calculations of each pixel; store results on a new numpy array rta_array = Get_RTA( json_data[u"RADIANCE_MULT_BAND"][os.path.basename(bnd)], img, json_data[u"RADIANCE_ADD_BAND"][os.path.basename(bnd)]) solar_irradiation = GetIrraSolar( json_data[u"EARTH_SUN_DISTANCE"], json_data[u"RADIANCE_MAXIMUM_BAND"][os.path.basename(bnd)], json_data[u"REFLECTANCE_MAXIMUM_BAND"][os.path.basename(bnd)]) ref_aparente_array = GetRefAparente(json_data[u"EARTH_SUN_DISTANCE"], solar_irradiation, rta_array, 90 - json_data[u"SUN_ELEVATION"]) new_map = GetRefSuperfice( rst_stats(bnd, api='gdal')['MIN'], json_data[u"RADIANCE_MULT_BAND"][os.path.basename(bnd)], json_data[u"RADIANCE_ADD_BAND"][os.path.basename(bnd)], solar_irradiation, 90 - json_data[u"SUN_ELEVATION"], json_data[u"EARTH_SUN_DISTANCE"], ref_aparente_array) obj_to_rst(new_map, os.path.join(outWorkspace, os.path.basename(bnd)), bnd)
def osm2lulc(osmdata, nomenclature, refRaster, lulcRst, overwrite=None, dataStore=None, roadsAPI='POSTGIS'): """ Convert OSM data into Land Use/Land Cover Information A matrix based approach roadsAPI Options: * SQLITE * POSTGIS """ # ************************************************************************ # # Python Modules from Reference Packages # # ************************************************************************ # import os import numpy import datetime from threading import Thread from osgeo import gdal # ************************************************************************ # # Dependencies # # ************************************************************************ # from glass.g.rd.rst import rst_to_array from glass.g.prop import check_isRaster from glass.g.prop.rst import get_cellsize from glass.pys.oss import mkdir, copy_file from glass.pys.oss import fprop if roadsAPI == 'POSTGIS': from glass.ng.sql.db import create_db from glass.g.it.db import osm_to_psql from glass.ete.osm2lulc.mod2 import pg_num_roads from glass.ng.sql.bkup import dump_db from glass.ng.sql.db import drop_db else: from glass.g.it.osm import osm_to_sqdb from glass.ete.osm2lulc.mod2 import num_roads from glass.ete.osm2lulc.utils import osm_project, add_lulc_to_osmfeat from glass.ete.osm2lulc.utils import osmlulc_rsttbl from glass.ete.osm2lulc.utils import get_ref_raster from glass.ete.osm2lulc.mod1 import num_selection from glass.ete.osm2lulc.m3_4 import num_selbyarea from glass.ete.osm2lulc.mod5 import num_base_buffer from glass.ete.osm2lulc.mod6 import num_assign_builds from glass.g.wt.rst import obj_to_rst # ************************************************************************ # # Global Settings # # ************************************************************************ # # Check if input parameters exists! if not os.path.exists(os.path.dirname(lulcRst)): raise ValueError('{} does not exist!'.format(os.path.dirname(lulcRst))) if not os.path.exists(osmdata): raise ValueError( 'File with OSM DATA ({}) does not exist!'.format(osmdata)) if not os.path.exists(refRaster): raise ValueError( 'File with reference area ({}) does not exist!'.format(refRaster)) # Check if Nomenclature is valid nomenclature = "URBAN_ATLAS" if nomenclature != "URBAN_ATLAS" and \ nomenclature != "CORINE_LAND_COVER" and \ nomenclature == "GLOBE_LAND_30" else nomenclature time_a = datetime.datetime.now().replace(microsecond=0) workspace = os.path.join(os.path.dirname(lulcRst), 'num_osmto') if not dataStore else dataStore # Check if workspace exists: if os.path.exists(workspace): if overwrite: mkdir(workspace, overwrite=True) else: raise ValueError('Path {} already exists'.format(workspace)) else: mkdir(workspace, overwrite=None) # Get Ref Raster and EPSG refRaster, epsg = get_ref_raster(refRaster, workspace, cellsize=2) CELLSIZE = get_cellsize(refRaster, gisApi='gdal') from glass.ete.osm2lulc import osmTableData, PRIORITIES time_b = datetime.datetime.now().replace(microsecond=0) # ************************************************************************ # # Convert OSM file to SQLITE DB or to POSTGIS DB # # ************************************************************************ # if roadsAPI == 'POSTGIS': osm_db = create_db(fprop(osmdata, 'fn', forceLower=True), overwrite=True) osm_db = osm_to_psql(osmdata, osm_db) else: osm_db = osm_to_sqdb(osmdata, os.path.join(workspace, 'osm.sqlite')) time_c = datetime.datetime.now().replace(microsecond=0) # ************************************************************************ # # Add Lulc Classes to OSM_FEATURES by rule # # ************************************************************************ # add_lulc_to_osmfeat(osm_db, osmTableData, nomenclature, api=roadsAPI) time_d = datetime.datetime.now().replace(microsecond=0) # ************************************************************************ # # Transform SRS of OSM Data # # ************************************************************************ # osmTableData = osm_project( osm_db, epsg, api=roadsAPI, isGlobeLand=None if nomenclature != "GLOBE_LAND_30" else True) time_e = datetime.datetime.now().replace(microsecond=0) # ************************************************************************ # # MapResults # # ************************************************************************ # mergeOut = {} timeCheck = {} RULES = [1, 2, 3, 4, 5, 7] def run_rule(ruleID): time_start = datetime.datetime.now().replace(microsecond=0) _osmdb = copy_file( osm_db, os.path.splitext(osm_db)[0] + '_r{}.sqlite'.format(ruleID)) if roadsAPI == 'SQLITE' else None # ******************************************************************** # # 1 - Selection Rule # # ******************************************************************** # if ruleID == 1: res, tm = num_selection(_osmdb if _osmdb else osm_db, osmTableData['polygons'], workspace, CELLSIZE, epsg, refRaster, api=roadsAPI) # ******************************************************************** # # 2 - Get Information About Roads Location # # ******************************************************************** # elif ruleID == 2: res, tm = num_roads( _osmdb, nomenclature, osmTableData['lines'], osmTableData['polygons'], workspace, CELLSIZE, epsg, refRaster) if _osmdb else pg_num_roads( osm_db, nomenclature, osmTableData['lines'], osmTableData['polygons'], workspace, CELLSIZE, epsg, refRaster) # ******************************************************************** # # 3 - Area Upper than # # ******************************************************************** # elif ruleID == 3: if nomenclature != "GLOBE_LAND_30": res, tm = num_selbyarea(osm_db if not _osmdb else _osmdb, osmTableData['polygons'], workspace, CELLSIZE, epsg, refRaster, UPPER=True, api=roadsAPI) else: return # ******************************************************************** # # 4 - Area Lower than # # ******************************************************************** # elif ruleID == 4: if nomenclature != "GLOBE_LAND_30": res, tm = num_selbyarea(osm_db if not _osmdb else _osmdb, osmTableData['polygons'], workspace, CELLSIZE, epsg, refRaster, UPPER=False, api=roadsAPI) else: return # ******************************************************************** # # 5 - Get data from lines table (railway | waterway) # # ******************************************************************** # elif ruleID == 5: res, tm = num_base_buffer(osm_db if not _osmdb else _osmdb, osmTableData['lines'], workspace, CELLSIZE, epsg, refRaster, api=roadsAPI) # ******************************************************************** # # 7 - Assign untagged Buildings to tags # # ******************************************************************** # elif ruleID == 7: if nomenclature != "GLOBE_LAND_30": res, tm = num_assign_builds(osm_db if not _osmdb else _osmdb, osmTableData['points'], osmTableData['polygons'], workspace, CELLSIZE, epsg, refRaster, apidb=roadsAPI) else: return time_end = datetime.datetime.now().replace(microsecond=0) mergeOut[ruleID] = res timeCheck[ruleID] = {'total': time_end - time_start, 'detailed': tm} thrds = [] for r in RULES: thrds.append( Thread(name="to_{}".format(str(r)), target=run_rule, args=(r, ))) for t in thrds: t.start() for t in thrds: t.join() # Merge all results into one Raster compileResults = {} for rule in mergeOut: for cls in mergeOut[rule]: if cls not in compileResults: if type(mergeOut[rule][cls]) == list: compileResults[cls] = mergeOut[rule][cls] else: compileResults[cls] = [mergeOut[rule][cls]] else: if type(mergeOut[rule][cls]) == list: compileResults[cls] += mergeOut[rule][cls] else: compileResults[cls].append(mergeOut[rule][cls]) time_m = datetime.datetime.now().replace(microsecond=0) # All Rasters to Array arrayRst = {} for cls in compileResults: for raster in compileResults[cls]: if not raster: continue array = rst_to_array(raster) if cls not in arrayRst: arrayRst[cls] = [array.astype(numpy.uint8)] else: arrayRst[cls].append(array.astype(numpy.uint8)) time_n = datetime.datetime.now().replace(microsecond=0) # Sum Rasters of each class for cls in arrayRst: if len(arrayRst[cls]) == 1: sumArray = arrayRst[cls][0] else: sumArray = arrayRst[cls][0] for i in range(1, len(arrayRst[cls])): sumArray = sumArray + arrayRst[cls][i] arrayRst[cls] = sumArray time_o = datetime.datetime.now().replace(microsecond=0) # Apply priority rule __priorities = PRIORITIES[nomenclature + "_NUMPY"] for lulcCls in __priorities: __lulcCls = rstcls_map(lulcCls) if __lulcCls not in arrayRst: continue else: numpy.place(arrayRst[__lulcCls], arrayRst[__lulcCls] > 0, lulcCls) for i in range(len(__priorities)): lulc_i = rstcls_map(__priorities[i]) if lulc_i not in arrayRst: continue else: for e in range(i + 1, len(__priorities)): lulc_e = rstcls_map(__priorities[e]) if lulc_e not in arrayRst: continue else: numpy.place(arrayRst[lulc_e], arrayRst[lulc_i] == __priorities[i], 0) time_p = datetime.datetime.now().replace(microsecond=0) # Merge all rasters startCls = 'None' for i in range(len(__priorities)): lulc_i = rstcls_map(__priorities[i]) if lulc_i in arrayRst: resultSum = arrayRst[lulc_i] startCls = i break if startCls == 'None': return 'NoResults' for i in range(startCls + 1, len(__priorities)): lulc_i = rstcls_map(__priorities[i]) if lulc_i not in arrayRst: continue resultSum = resultSum + arrayRst[lulc_i] # Save Result outIsRst = check_isRaster(lulcRst) if not outIsRst: from glass.pys.oss import fprop lulcRst = os.path.join(os.path.dirname(lulcRst), fprop(lulcRst, 'fn') + '.tif') numpy.place(resultSum, resultSum == 0, 1) obj_to_rst(resultSum, lulcRst, refRaster, noData=1) osmlulc_rsttbl( nomenclature + "_NUMPY", os.path.join(os.path.dirname(lulcRst), os.path.basename(lulcRst) + '.vat.dbf')) time_q = datetime.datetime.now().replace(microsecond=0) # Dump Database if PostGIS was used # Drop Database if PostGIS was used if roadsAPI == 'POSTGIS': dump_db(osm_db, os.path.join(workspace, osm_db + '.sql'), api='psql') drop_db(osm_db) return lulcRst, { 0: ('set_settings', time_b - time_a), 1: ('osm_to_sqdb', time_c - time_b), 2: ('cls_in_sqdb', time_d - time_c), 3: ('proj_data', time_e - time_d), 4: ('rule_1', timeCheck[1]['total'], timeCheck[1]['detailed']), 5: ('rule_2', timeCheck[2]['total'], timeCheck[2]['detailed']), 6: None if 3 not in timeCheck else ('rule_3', timeCheck[3]['total'], timeCheck[3]['detailed']), 7: None if 4 not in timeCheck else ('rule_4', timeCheck[4]['total'], timeCheck[4]['detailed']), 8: ('rule_5', timeCheck[5]['total'], timeCheck[5]['detailed']), 9: None if 7 not in timeCheck else ('rule_7', timeCheck[7]['total'], timeCheck[7]['detailed']), 10: ('rst_to_array', time_n - time_m), 11: ('sum_cls', time_o - time_n), 12: ('priority_rule', time_p - time_o), 13: ('merge_rst', time_q - time_p) }