def atsat_bright_temp_8(meta_path, outdir = False): """ Converts Landsat 8 TIRS bands to at satellite brightnes temperature in Kelvins To be performed on raw Landsat 8 level 1 data. See link below for details see here http://landsat.usgs.gov/Landsat8_Using_Product.php :param band_nums: A list of desired band numbers, which should be [10,11] :param meta_path: The full filepath to the metadata file for those bands :param outdir: Output directory to save converted files. If left False it will save ouput files in the same directory as input files. :return output_filelist: A list of all files created by this function """ #enforce the list of band numbers and grab metadata from the MTL file band_nums = ["10", "11"] meta_path = os.path.abspath(meta_path) meta = landsat_metadata(meta_path) output_filelist = [] #cycle through each band in the list for calculation, ensuring each is in the list of TIRS bands for band_num in band_nums: #scrape data from the given file path and attributes in the MTL file band_path = meta_path.replace("MTL.txt","B{0}.tif".format(band_num)) Qcal = arcpy.Raster(band_path) #get rid of the zero values that show as the black background to avoid skewing values null_raster = arcpy.sa.SetNull(Qcal, Qcal, "VALUE = 0") #requires first converting to radiance Ml = getattr(meta,"RADIANCE_MULT_BAND_{0}".format(band_num)) # multiplicative scaling factor Al = getattr(meta,"RADIANCE_ADD_BAND_{0}".format(band_num)) # additive rescaling factor TOA_rad = (null_raster * Ml) + Al #now convert to at-sattelite brightness temperature K1 = getattr(meta,"K1_CONSTANT_BAND_{0}".format(band_num)) # thermal conversion constant 1 K2 = getattr(meta,"K2_CONSTANT_BAND_{0}".format(band_num)) # thermal conversion constant 2 #calculate brightness temperature at the satellite Bright_Temp = K2/(arcpy.sa.Ln((K1/TOA_rad) + 1)) #save the data to the automated name if outdir is given or in the parent folder if not if outdir: outdir = os.path.abspath(outdir) outname = core.create_outname(outdir, band_path, "ASBTemp", "tif") else: folder = os.path.split(meta_path)[0] outname = core.create_outname(folder, band_path, "ASBTemp", "tif") Bright_Temp.save(outname) output_filelist.append(outname) print("Saved output at {0}".format(outname)) del TOA_rad, null_raster return output_filelist
def toa_reflectance_8(band_nums, meta_path, outdir = None): """ Converts Landsat 8 bands to Top-of-Atmosphere reflectance. To be performed on raw Landsat 8 level 1 data. See link below for details see here [http://landsat.usgs.gov/Landsat8_Using_Product.php] :param band_nums: A list of desired band numbers such as [3,4,5] :param meta_path: The full filepath to the metadata file for those bands :param outdir: Output directory to save converted files. If left False it will save ouput files in the same directory as input files. :return output_filelist: List of files created by this function """ output_filelist = [] # enforce the list of band numbers and grab metadata from the MTL file band_nums = core.enf_list(band_nums) band_nums = map(str, band_nums) OLI_bands = ['1','2','3','4','5','6','7','8','9'] meta_path = os.path.abspath(meta_path) meta = landsat_metadata(meta_path) # cycle through each band in the list for calculation, ensuring each is in the list of OLI bands for band_num in band_nums: if band_num in OLI_bands: # scrape data from the given file path and attributes in the MTL file band_path = meta_path.replace("MTL.txt","B{0}.tif".format(band_num)) Qcal = arcpy.Raster(band_path) Mp = getattr(meta,"REFLECTANCE_MULT_BAND_{0}".format(band_num)) # multiplicative scaling factor Ap = getattr(meta,"REFLECTANCE_ADD_BAND_{0}".format(band_num)) # additive rescaling factor SEA = getattr(meta,"SUN_ELEVATION")*(math.pi/180) # sun elevation angle theta_se # get rid of the zero values that show as the black background to avoid skewing values null_raster = arcpy.sa.SetNull(Qcal, Qcal, "VALUE = 0") # calculate top-of-atmosphere reflectance TOA_ref = (((null_raster * Mp) + Ap)/(math.sin(SEA))) # save the data to the automated name if outdir is given or in the parent folder if not if outdir is not None: outdir = os.path.abspath(outdir) outname = core.create_outname(outdir, band_path, "TOA_Ref", "tif") else: folder = os.path.split(meta_path)[0] outname = core.create_outname(folder, band_path, "TOA_Ref", "tif") TOA_ref.save(outname) output_filelist.append(outname) print("Saved output at {0}".format(outname)) # if listed band is not an OLI sensor band, skip it and print message else: print("Can only perform reflectance conversion on OLI sensor bands") print("Skipping band {0}".format(band_num)) return output_filelist
def grab_meta(filename): """ Legacy metadata function simply wraps the newer landsat_metadata class. You should use ``landsat_metadata`` instead of this function, and can refer to that class for further explanation. :param filename: filepath to an MTL file :return landsat_metadata: Metadata object with MTL attributes. """ return landsat_metadata(filename)
def __init__(self, MTL_path, tif_dir = None): """ builds the scene. In some cases, users may have their MTL file located somewhere other than their landsat data. In this instance, users should input the path to the landsat images as tif_dir. """ self.mtl_dir = os.path.dirname(MTL_path) # directory of MTL file self.meta = landsat_metadata(MTL_path) # dnppy landsat_metadata object self.in_paths = {} # dict of filepaths to tifs self.rasts = {} # dict of arcpy raster objects if not tif_dir: self.tif_dir = self.mtl_dir self._find_bands() return
def toa_radiance_457(band_nums, meta_path, outdir=None): """ Top of Atmosphere radiance (in Watts/(square meter x steradians x micrometers)) conversion for Landsat 4, 5, and 7 data. To be performed on raw Landsat 4, 5, or 7 level 1 data. :param band_nums: A list of desired band numbers such as [3, 4, 5] :param meta_path: The full filepath to the metadata file for those bands :param outdir: Output directory to save converted files. :return output_filelist: List of filepaths created by this function. """ output_filelist = [] meta_path = os.path.abspath(meta_path) band_nums = core.enf_list(band_nums) band_nums = map(str, band_nums) #metadata format was changed August 29, 2012. This tool can process either the new or old format f = open(meta_path) MText = f.read() metadata = landsat_metadata(meta_path) #the presence of a PRODUCT_CREATION_TIME category is used to identify old metadata #if this is not present, the meta data is considered new. #Band6length refers to the length of the Band 6 name string. In the new metadata this string is longer if "PRODUCT_CREATION_TIME" in MText: Meta = "oldMeta" Band6length = 2 else: Meta = "newMeta" Band6length = 8 #The tilename is located using the newMeta/oldMeta indixes and the date of capture is recorded if Meta == "newMeta": TileName = getattr(metadata, "LANDSAT_SCENE_ID") year = TileName[9:13] jday = TileName[13:16] date = getattr(metadata, "DATE_ACQUIRED") elif Meta == "oldMeta": TileName = getattr(metadata, "BAND1_FILE_NAME") year = TileName[13:17] jday = TileName[17:20] date = getattr(metadata, "ACQUISITION_DATE") #the spacecraft from which the imagery was capture is identified #this info determines the solar exoatmospheric irradiance (ESun) for each band spacecraft = getattr(metadata, "SPACECRAFT_ID") if "7" in spacecraft: ESun = (1969.0, 1840.0, 1551.0, 1044.0, 255.700, 0., 82.07, 1368.00) TM_ETM_bands = ['1', '2', '3', '4', '5', '7', '8'] elif "5" in spacecraft: ESun = (1957.0, 1826.0, 1554.0, 1036.0, 215.0, 0., 80.67) TM_ETM_bands = ['1', '2', '3', '4', '5', '7'] elif "4" in spacecraft: ESun = (1957.0, 1825.0, 1557.0, 1033.0, 214.9, 0., 80.72) TM_ETM_bands = ['1', '2', '3', '4', '5', '7'] else: arcpy.AddError("This tool only works for Landsat 4, 5, or 7") raise arcpy.ExecuteError() #Calculating values for each band for band_num in band_nums: if band_num in TM_ETM_bands: print("Processing Band {0}".format(band_num)) pathname = meta_path.replace("MTL.txt", "B{0}.tif".format(band_num)) Oraster = arcpy.Raster(pathname) null_raster = arcpy.sa.SetNull(Oraster, Oraster, "VALUE = 0") #using the oldMeta/newMeta indixes to pull the min/max for radiance/Digital numbers if Meta == "newMeta": LMax = getattr(metadata, "RADIANCE_MAXIMUM_BAND_{0}".format(band_num)) LMin = getattr(metadata, "RADIANCE_MINIMUM_BAND_{0}".format(band_num)) QCalMax = getattr(metadata, "QUANTIZE_CAL_MAX_BAND_{0}".format(band_num)) QCalMin = getattr(metadata, "QUANTIZE_CAL_MIN_BAND_{0}".format(band_num)) elif Meta == "oldMeta": LMax = getattr(metadata, "LMAX_BAND{0}".format(band_num)) LMin = getattr(metadata, "LMIN_BAND{0}".format(band_num)) QCalMax = getattr(metadata, "QCALMAX_BAND{0}".format(band_num)) QCalMin = getattr(metadata, "QCALMIN_BAND{0}".format(band_num)) Radraster = (((LMax - LMin) / (QCalMax - QCalMin)) * (null_raster - QCalMin)) + LMin Oraster = 0 del null_raster band_rad = "{0}_B{1}".format(TileName, band_num) #create the output name and save the TOA radiance tiff if outdir is not None: outdir = os.path.abspath(outdir) outname = core.create_outname(outdir, band_rad, "TOA_Rad", "tif") else: folder = os.path.split(meta_path)[0] outname = core.create_outname(folder, band_rad, "TOA_Rad", "tif") Radraster.save(outname) output_filelist.append(outname) del Radraster print("toa radiance saved for Band {0}".format(band_num)) #if listed band is not a TM/ETM+ sensor band, skip it and print message else: print( "Can only perform reflectance conversion on TM/ETM+ sensor bands" ) print("Skipping band {0}".format(band_num)) f.close() return output_filelist
def toa_radiance_8(band_nums, meta_path, outdir=None): """ Top of Atmosphere radiance (in Watts/(square meter x steradians x micrometers)) conversion for landsat 8 data. To be performed on raw Landsat 8 level 1 data. See link below for details: see here http://landsat.usgs.gov/Landsat8_Using_Product.php :param band_nums: A list of desired band numbers such as [3, 4, 5] :param meta_path: The full filepath to the metadata file for those bands :param outdir: Output directory to save converted files. :return output_filelist: List of filepaths created by this function. """ meta_path = os.path.abspath(meta_path) output_filelist = [] #enforce list of band numbers and grab the metadata from the MTL file band_nums = core.enf_list(band_nums) band_nums = map(str, band_nums) meta = landsat_metadata(meta_path) OLI_bands = ['1', '2', '3', '4', '5', '6', '7', '8', '9'] #loop through each band for band_num in band_nums: if band_num in OLI_bands: #create the band name band_path = meta_path.replace("MTL.txt", "B{0}.tif".format(band_num)) Qcal = arcpy.Raster(band_path) null_raster = arcpy.sa.SetNull(Qcal, Qcal, "VALUE = 0") #scrape the attribute data Ml = getattr(meta, "RADIANCE_MULT_BAND_{0}".format( band_num)) # multiplicative scaling factor Al = getattr(meta, "RADIANCE_ADD_BAND_{0}".format( band_num)) # additive rescaling factor #calculate Top-of-Atmosphere radiance TOA_rad = (null_raster * Ml) + Al del null_raster # create the output name and save the TOA radiance tiff if "\\" in meta_path: name = meta_path.split("\\")[-1] elif "//" in meta_path: name = meta_path.split("//")[-1] rad_name = name.replace("_MTL.txt", "_B{0}".format(band_num)) if outdir is not None: outdir = os.path.abspath(outdir) outname = core.create_outname(outdir, rad_name, "TOA_Rad", "tif") else: folder = os.path.split(meta_path)[0] outname = core.create_outname(folder, rad_name, "TOA_Rad", "tif") TOA_rad.save(outname) output_filelist.append(outname) print("Saved toa_radiance at {0}".format(outname)) #if listed band is not a OLI sensor band, skip it and print message else: print( "Can only perform reflectance conversion on OLI sensor bands") print("Skipping band {0}".format(band_num)) return output_filelist
def surface_reflectance(meta_path, toa_folder, dem_path, dew_point, outdir = False, kt = 1.0): """ This function will estimate surface reflectance for Landsat 4 TM, 5 TM, 7 ETM+, or 8 OLI data. Note this function will calculate surface reflectance for Landsat 4, 5, and 7 bands 1, 2, 3, 4, 5, and 7 or Landsat 8 bands 2, 3, 4, 5, 6, and 7. All 6 bands should be in the toa_folder and have "TOA_Ref" in their filenames as per the landsat.toa_reflectance convention. The Landsat 8 Coastal Aerosol band (1) and Cirrus band (9) will not be calculated as they do not have a corresponding band in TM or ETM+. To be performed on Top-of-Atmosphere Reflectance data processed with the landsat.toa_reflectance_457 or the landsat.toa_reflectance_8 function. Resources - DEM - NASA Shuttle Radar Topography Mission (SRTM) up to 30m resolution. [http://www2.jpl.nasa.gov/srtm/cbanddataproducts.html] - NASA Advanced Spaceborne Thermal Emission and Reflection Radiometer (ASTER) Global Digital Elevation Model (GDEM) [http://asterweb.jpl.nasa.gov/gdem.asp] - USGS National Elevation Dataset (NED) [http://nationalmap.gov/elevation.html] downloadable in 30 meter resolution through The National Map Viewer - Dew Point - NOAA Daily Observational Data: Global Summary of the Day (GSOD) - GIS Data Locator [http://gis.ncdc.noaa.gov/map/viewer/#app=clim&cfg=cdo&theme=daily&layers=0001&node=gis]. Select a weather station within the Landsat scene's extent, follow the link, and enter the scene's acquisition date. This will output a text file, with dew point under DEWP. More NOAA climatic data available at [https://www.climate.gov/data/maps-and-data] :param meta_path: The full filepath to the metadata file (ending in MTL.txt) for the dataset :param toa_folder: The filepath to the folder containing the TOA_Ref tiffs to be processed :param dem_path: The full filepath to a DEM tif that covers the desired Landsat scene Note that the DEM should be resampled to the Landsat bands' resolution (30m) and pixel alignment. Also ensure there are no gaps in the dataset. :param dew_point: The number (e.g. 57.7) for the dew point at the time and place of scene acquisition :param outdir: Output directory to save converted files. If left False it will save output files in the toa_folder directory. :param kt: Unitless turbidity coefficient. Default set at 1.0 for clean air. Set at 0.5 for extremely turbid, dusty, or polluted air. :return output_filelist: A list of all files created by this function """ meta_path = os.path.abspath(meta_path) toa_folder = os.path.abspath(toa_folder) dem_path = os.path.abspath(dem_path) output_filelist = [] #define the list of constants for effective narrowband transmissivity for incoming solar radiation constants_enbt1 = [[0.987, -0.00071, 0.000036, 0.0880, 0.0789], [2.319, -0.00016, 0.000105, 0.0437, -1.2697], [0.951, -0.00033, 0.000280, 0.0875, 0.1014], [0.375, -0.00048, 0.005018, 0.1355, 0.6621], [0.234, -0.00101, 0.004336, 0.0560, 0.7757], [0.365, -0.00097, 0.004296, 0.0155, 0.6390]] #define the list of constants for effective narrowband transmissivity for shortwave radiation #reflected from the surface constants_enbt2 = [[0.987, -0.00071, 0.000036, 0.0880, 0.0789], [2.319, -0.00016, 0.000105, 0.0437, -1.2697], [0.951, -0.00033, 0.000280, 0.0875, 0.1014], [0.375, -0.00048, 0.005018, 0.1355, 0.6621], [0.234, -0.00101, 0.004336, 0.0560, 0.7757], [0.365, -0.00097, 0.004296, 0.0155, 0.6390]] #enforce the list of band numbers, grab metadata from the MTL file, and define the band numbers needed from each sensor meta = landsat_metadata(meta_path) OLI_bands = ['2','3','4','5','6','7'] TM_ETM_bands = ['1','2','3','4','5','7'] #define the tile name for the landsat scene based on the metadata file's name #Open the metadata text file and read to set the scene's tilename f = open(meta_path) MText = f.read() if "PRODUCT_CREATION_TIME" in MText: tilename = getattr(meta, "BAND1_FILE_NAME") else: tilename = getattr(meta, "LANDSAT_SCENE_ID") #construct the list of TOA reflectance band tiffs and populate it based on the above definitions toa_list = [] out_list = [] n = 0 for file in os.listdir(toa_folder): if ("TOA_Ref" in file) and (file[-4:] == ".tif" or file[-4:] == ".TIF"): if "LC8" in meta_path: tile = "{0}_B{1}".format(tilename, OLI_bands[n]) if tile in file: path = "{0}\\{1}".format(toa_folder, file) out_file = file.replace("TOA", "Surf") toa_list.append(path) out_list.append(out_file) n = n + 1 if n > 5: break elif ("LE7" in file) or ("LT5" in file) or ("LT4" in file): tile = "{0}_B{1}".format(tilename, TM_ETM_bands[n]) if tile in file: path = "{0}\\{1}".format(toa_folder, file) out_file = file.replace("TOA", "Surf") toa_list.append(path) out_list.append(out_file) n = n + 1 if n > 5: break #grab the corner lat/lon coordinates to calculate the approximate scene center lat/lon ul_lat = getattr(meta, "CORNER_UL_LAT_PRODUCT") ul_lon = getattr(meta, "CORNER_UL_LON_PRODUCT") ur_lat = getattr(meta, "CORNER_UR_LAT_PRODUCT") ur_lon = getattr(meta, "CORNER_UR_LON_PRODUCT") ll_lat = getattr(meta, "CORNER_LL_LAT_PRODUCT") ll_lon = getattr(meta, "CORNER_LL_LON_PRODUCT") lr_lat = getattr(meta, "CORNER_LR_LAT_PRODUCT") lr_lon = getattr(meta, "CORNER_LR_LON_PRODUCT") u_lon_avg = np.mean([ul_lon, ur_lon]) l_lon_avg = np.mean([ll_lon, lr_lon]) l_lat_avg = np.mean([ul_lat, ll_lat]) r_lat_avg = np.mean([ur_lat, lr_lat]) center_lat = np.mean([l_lat_avg, r_lat_avg]) center_lon = np.mean([u_lon_avg, l_lon_avg]) #construct the datetime object from the date acquired and scene center time attributes date = getattr(meta, "DATE_ACQUIRED") dl = date.split("-") time = getattr(meta, "SCENE_CENTER_TIME") tl = time.split(":") dt = datetime.datetime(int(dl[0]), int(dl[1]), int(dl[2]), int(tl[0]), int(tl[1]), int(tl[2][0:2])) #use the dnppy.solar module to calculate the solar characteristics at the scene center at the time of acquisition sc = solar.solar(center_lat, center_lon, dt, 0) sc.compute_all() #Cosine of Solar Zenith over horizontal surface declination = math.degrees(sc.get_declination()) hour_angle = math.degrees(sc.get_hour_angle()) lat = math.degrees(center_lat) cth = (math.sin(declination) * math.sin(lat)) + (math.cos(declination) * math.cos(center_lat) * math.cos(hour_angle)) #Saturation Vapor Pressure svp = 0.6108 * math.exp((17.27 * dew_point) / (dew_point + 237.3)) #Atmospheric Pressure DEM = arcpy.sa.Raster(dem_path) ap = 101.3 * ((( 293 - (0.0065 * DEM))/ 293) ** 5.26) #Water in Atmosphere wia = (0.14 * svp * ap) + 2.1 #Effective Narrowband Transmittance for incoming solar radiation entisr_bands = [] for i in xrange(6): c1 = constants_enbt1[i][0] c2 = constants_enbt1[i][1] c3 = constants_enbt1[i][2] c4 = constants_enbt1[i][3] c5 = constants_enbt1[i][4] enbt1 = c1 * ((arcpy.sa.Exp((c2 * ap)/(kt * cth))) - (((c3 * wia) + c4)/cth)) + c5 entisr_bands.append(enbt1) #Effective Narrowband Transmittance for shortwave radiation reflected from surface entsrrs_bands = [] #cos_n always 1 for sensor pointing straight nadir cos_n = 1 for i in xrange(6): c1 = constants_enbt2[i][0] c2 = constants_enbt2[i][1] c3 = constants_enbt2[i][2] c4 = constants_enbt2[i][3] c5 = constants_enbt2[i][4] enbt2 = c1 * ((arcpy.sa.Exp((c2 * ap)/(kt * cos_n))) - (((c3 * wia) + c4))) + c5 entsrrs_bands.append(enbt2) #Per Band Path Reflectance pr_bands = [] pr_constants = [0.254, 0.149, 0.147, 0.311, 0.103, 0.036] for j in xrange(6): pr = pr_constants[j] * (1 - entisr_bands[j]) pr_bands.append(pr) #Calculate and save At-Surface Reflectance band tiffs for k in xrange(6): if outdir: outdir = os.path.abspath(outdir) asr_path = "{0}\\{1}".format(outdir, out_list[k]) else: asr_path = "{0}\\{1}".format(toa_folder, out_list[k]) refl_surf = (toa_list[k] - pr_bands[k])/(entisr_bands[k] * entsrrs_bands[k]) refl_surf.save(asr_path) output_filelist.append(asr_path) return output_filelist
def surface_temp_8(band4_toa, meta_path, path_rad, nbt, sky_rad, outdir = False, L = 0.5): """ Calculates surface temperature from Landsat 8 OLI and TIRS data. Requires band 4 and 5 Top-of-Atmosphere Reflectance tiffs and the unprocessed band 10 and 11 tiffs. Note: if the default values of 0, 1, and 0 are used for the Path Radiance, Narrowband \ Transmissivity, and Sky Radiance constants, atmospheric conditions will not be accounted for and the surface values may be off. Values are attainable using MODTRAN. :param band4_toa: Filepath to the Band 4 Top-of-Atmosphere Reflectance tiff. use landsat.toa_reflectance_8 :param meta_path: Filepath to the metadata file (ending in _MTL.txt) :param path_rad: Path Radiance constant (default 0) :param nbt: Narrowband Transmissivity constant (default 1) :param sky_rad: Sky Radiance constant (default 0) :param outdir: Path to the desired output folder. If left False the output tiff will be place in band4_toa's folder :param L: Soil brightness correction factor, between 0 and 1. used to calculate Soil Adjusted Vegetation Index. Default L = 0.5 works well in most situations. when L = 0, SAVI = NDVI. :return surface_temp_8: Full filepath of tif created by this function """ band4_toa = os.path.abspath(band4_toa) meta_path = os.path.abspath(meta_path) # Grab metadata from the MTL file and set the pathnames for Band 5 TOA Reflectance and the raw Band 11 tiffs meta = landsat_metadata(meta_path) band5_toa = band4_toa.replace("_B4_", "_B5_") band10 = meta_path.replace("_MTL.txt", "_B10.tif") band11 = band10.replace("_B10.tif", "_B11.tif") # Soil Adjusted Vegetation Index red = arcpy.sa.Float(band4_toa) nir = arcpy.sa.Float(band5_toa) savi = ((1 + L) * (nir - red))/(L + (nir + red)) # Leaf Area Index # assigns LAI for 0.1 <= SAVI <= 0.687 lai_1 = ((arcpy.sa.Ln((0.69 - savi)/0.59))/(-0.91)) # assigns LAI for SAVI >= 0.687 lai_2 = arcpy.sa.Con(savi, lai_1, 6, "VALUE < 0.687") # assigns LAI for SAVI <= 0.1 lai = arcpy.sa.Con(savi, lai_2, 0, "VALUE >= 0.1") # Narrow Band Emissivity remap = 0.97 + (0.0033 * lai) nbe = arcpy.sa.Con(lai, remap, 0.98, "VALUE <= 3") # Get the radiance mult/add bands for bands 10 and 11 Ml_10 = getattr(meta, "RADIANCE_MULT_BAND_10") Al_10 = getattr(meta, "RADIANCE_ADD_BAND_10") Ml_11 = getattr(meta, "RADIANCE_MULT_BAND_11") Al_11 = getattr(meta, "RADIANCE_ADD_BAND_11") # Set values in the TIRS band tiffs to null null_10 = arcpy.sa.SetNull(band10, band10, "VALUE <= 1") null_11 = arcpy.sa.SetNull(band11, band11, "VALUE <= 1") # Initial Thermal Radiances itr_10 = (null_10 * Ml_10) + Al_10 itr_11 = (null_11 * Ml_11) + Al_11 # Corrected Thermal Radiances ctr_10 = ((itr_10 - path_rad)/nbt) - ((1 - nbe) * sky_rad) ctr_11 = ((itr_11 - path_rad)/nbt) - ((1 - nbe) * sky_rad) # Get the K1 and K2 constants for bands 10 and 11 K1_10 = getattr(meta, "K1_CONSTANT_BAND_10") K2_10 = getattr(meta, "K2_CONSTANT_BAND_10") K1_11 = getattr(meta, "K1_CONSTANT_BAND_11") K2_11 = getattr(meta, "K2_CONSTANT_BAND_11") # Calculate surface temperature based on bands 10 and 11 and average them for final output st_10 = (K2_10/(arcpy.sa.Ln(((nbe * K1_10)/ctr_10) + 1))) st_11 = (K2_11/(arcpy.sa.Ln(((nbe * K1_11)/ctr_10) + 1))) st = (st_10 + st_11)/2 # Create output name and save the Surface Temperature tiff tilename = getattr(meta, "LANDSAT_SCENE_ID") if outdir: outdir = os.path.abspath(outdir) surface_temp_8 = core.create_outname(outdir, tilename, "Surf_Temp", "tif") else: folder = os.path.split(band4_toa)[0] surface_temp_8 = core.create_outname(folder, tilename, "Surf_Temp", "tif") st.save(surface_temp_8) return surface_temp_8
def surface_temp_457(band3_toa, meta_path, path_rad, nbt, sky_rad, outdir = False, L = 0.5): """ Calculates surface temperature from Landsat 4/5 TM or 7 ETM+ data. Requires band 3 and 4 Top-of-Atmosphere Reflectance tiffs and the unprocessed band 6 (or 6_VCID_1 for Landsat 7) tiff. Note: if the default values of 0, 1, and 0 are used for the Path Radiance, Narrowband Transmissivity, and Sky Radiance constants, atmospheric conditions will not be accounted for and the surface values may be off. Values are attainable using MODTRAN. :param band3_toa: Filepath to the Band 3 Top-of-Atmosphere Reflectance tiff. use landsat.toa_reflectance_457 :param meta_path: Filepath to the metadata file (ending in _MTL.txt) :param path_rad: Path Radiance constant (default 0) :param nbt: Narrowband Transmissivity constant (default 1) :param sky_rad: Sky Radiance constant (default 0) :param outdir: Path to the desired output folder. If left False the output tiff will be place in band4_toa's folder :param L: Soil brightness correction factor, between 0 and 1. used to calculate Soil Adjusted Vegetation Index. Default L = 0.5 works well in most situations. when L = 0, SAVI = NDVI. :return surface_temp_457: Full filepath of tif created by this function """ band3_toa = os.path.abspath(band3_toa) meta_path = os.path.abspath(meta_path) # Set the pathname for band 4 band4_toa = band3_toa.replace("_B3_", "_B4_") # Grab metadata from the MTL file and identify the spacecraft ID meta = landsat_metadata(meta_path) spacecraft = getattr(meta, "SPACECRAFT_ID") # Set the band 6 number, K1 and K2 thermal constants, and band 6 pathname based on spacecraft ID if "4" in spacecraft or "5" in spacecraft: band_num = "6" K1 = 607.76 K2 = 1260.56 band6 = meta_path.replace("_MTL.txt", "_B6.tif") elif "7" in spacecraft: band_num = "6_VCID_1" K1 = 666.09 K2 = 1282.71 band6 = meta_path.replace("_MTL.txt", "_B6_VCID_1.tif") else: print("Enter the MTL file corresponding to a Landsat 4, 5, or 7 dataset") # Open the metadata text file and read to set the scene's tilename f = open(meta_path) MText = f.read() if "PRODUCT_CREATION_TIME" in MText: tilename = getattr(meta, "BAND1_FILE_NAME") else: tilename = getattr(meta, "LANDSAT_SCENE_ID") #Soil Adjusted Vegetation Index red = arcpy.sa.Float(band3_toa) nir = arcpy.sa.Float(band4_toa) savi = ((1 + L) * (nir - red))/(L + (nir - red)) #Leaf Area Index #assigns LAI for 0.1 <= SAVI <= 0.687 lai_1 = ((arcpy.sa.Ln((0.69 - savi)/0.59))/(-0.91)) #assigns LAI for SAVI >= 0.687 lai_2 = arcpy.sa.Con(savi, lai_1, 6, "VALUE < 0.687") #assigns LAI for SAVI <= 0.1 lai = arcpy.sa.Con(savi, lai_2, 0, "VALUE >= 0.1") #Narrow Band Emissivity remap = 0.97 + (0.0033 * lai) nbe = arcpy.sa.Con(lai, remap, 0.98, "VALUE <= 3") #Get the radiance mult/add bands for bands 10 and 11 Ml = getattr(meta, "RADIANCE_MULT_BAND_{0}".format(band_num)) Al = getattr(meta, "RADIANCE_ADD_BAND_{0}".format(band_num)) #Set values in the TIRS band tiffs to null null = arcpy.sa.SetNull(band6, band6, "VALUE <= 1") # Initial Thermal Radiances itr = (null * Ml) + Al # Corrected Thermal Radiances ctr = ((itr - path_rad)/nbt) - ((1 - nbe) * sky_rad) # Calculate surface temperature st = (K2/(arcpy.sa.Ln(((nbe * K1)/ctr) + 1))) #Create output name and save the surface temperature tiff if outdir: outdir = os.path.abspath(outdir) surface_temp_457 = core.create_outname(outdir, tilename, "Surf_Temp", "tif") else: folder = os.path.split(band3_toa)[0] surface_temp_457 = core.create_outname(folder, tilename, "Surf_Temp", "tif") st.save(surface_temp_457) return surface_temp_457
def toa_reflectance_457(band_nums, meta_path, outdir = None): """ This function is used to convert Landsat 4, 5, or 7 pixel values from digital numbers to Top-of-Atmosphere Reflectance. To be performed on raw Landsat 4, 5, or 7 data. :param band_nums: A list of desired band numbers such as [3,4,5] :param meta_path: The full filepath to the metadata file for those bands :param outdir: Output directory to save converted files. If left False it will save ouput files in the same directory as input files. :return output_filelist: List of files created by this function """ output_filelist = [] band_nums = core.enf_list(band_nums) band_nums = map(str, band_nums) # metadata format was changed August 29, 2012. This tool can process either the new or old format f = open(meta_path) MText = f.read() meta_path = os.path.abspath(meta_path) metadata = landsat_metadata(meta_path) # the presence of a PRODUCT_CREATION_TIME category is used to identify old metadata # if this is not present, the meta data is considered new. # Band6length refers to the length of the Band 6 name string. In the new metadata this string is longer if "PRODUCT_CREATION_TIME" in MText: Meta = "oldMeta" Band6length = 2 else: Meta = "newMeta" Band6length = 8 # The tilename is located using the newMeta/oldMeta indixes and the date of capture is recorded if Meta == "newMeta": TileName = getattr(metadata, "LANDSAT_SCENE_ID") year = TileName[9:13] jday = TileName[13:16] date = getattr(metadata, "DATE_ACQUIRED") elif Meta == "oldMeta": TileName = getattr(metadata, "BAND1_FILE_NAME") year = TileName[13:17] jday = TileName[17:20] date = getattr(metadata, "ACQUISITION_DATE") # the spacecraft from which the imagery was capture is identified # this info determines the solar exoatmospheric irradiance (ESun) for each band spacecraft = getattr(metadata, "SPACECRAFT_ID") if "7" in spacecraft: ESun = (1969.0, 1840.0, 1551.0, 1044.0, 255.700, 0., 82.07, 1368.00) TM_ETM_bands = ['1','2','3','4','5','7','8'] elif "5" in spacecraft: ESun = (1957.0, 1826.0, 1554.0, 1036.0, 215.0, 0. ,80.67) TM_ETM_bands = ['1','2','3','4','5','7'] elif "4" in spacecraft: ESun = (1957.0, 1825.0, 1557.0, 1033.0, 214.9, 0. ,80.72) TM_ETM_bands = ['1','2','3','4','5','7'] else: arcpy.AddError("This tool only works for Landsat 4, 5, or 7") raise arcpy.ExecuteError() # determing if year is leap year and setting the Days in year accordingly if float(year) % 4 == 0: DIY = 366. else: DIY=365. # using the date to determining the distance from the sun theta = 2 * math.pi * float(jday)/DIY dSun2 = (1.00011 + 0.034221 * math.cos(theta) + 0.001280 * math.sin(theta) + 0.000719 * math.cos(2*theta)+ 0.000077 * math.sin(2 * theta)) SZA = 90. - float(getattr(metadata, "SUN_ELEVATION")) # Calculating values for each band for band_num in band_nums: if band_num in TM_ETM_bands: print("Processing Band {0}".format(band_num)) pathname = meta_path.replace("MTL.txt", "B{0}.tif".format(band_num)) Oraster = arcpy.Raster(pathname) null_raster = arcpy.sa.SetNull(Oraster, Oraster, "VALUE = 0") # using the oldMeta/newMeta indices to pull the min/max for radiance/Digital numbers if Meta == "newMeta": LMax = getattr(metadata, "RADIANCE_MAXIMUM_BAND_{0}".format(band_num)) LMin = getattr(metadata, "RADIANCE_MINIMUM_BAND_{0}".format(band_num)) QCalMax = getattr(metadata, "QUANTIZE_CAL_MAX_BAND_{0}".format(band_num)) QCalMin = getattr(metadata, "QUANTIZE_CAL_MIN_BAND_{0}".format(band_num)) elif Meta == "oldMeta": LMax = getattr(metadata, "LMAX_BAND{0}".format(band_num)) LMin = getattr(metadata, "LMIN_BAND{0}".format(band_num)) QCalMax = getattr(metadata, "QCALMAX_BAND{0}".format(band_num)) QCalMin = getattr(metadata, "QCALMIN_BAND{0}".format(band_num)) Radraster = (((LMax - LMin)/(QCalMax-QCalMin)) * (null_raster - QCalMin)) + LMin Oraster = 0 del null_raster # Calculating temperature for band 6 if present Refraster = (math.pi * Radraster * dSun2) / (ESun[int(band_num[0])-1] * math.cos(SZA*(math.pi/180))) # construc output names for each band based on whether outdir is set (default is False) if outdir is not None: outdir = os.path.abspath(outdir) BandPath = core.create_outname(outdir, pathname, "TOA_Ref", "tif") else: folder = os.path.split(meta_path)[0] BandPath = core.create_outname(folder, pathname, "TOA_Ref", "tif") Refraster.save(BandPath) output_filelist.append(BandPath) del Refraster, Radraster print("Reflectance Calculated for Band {0}".format(band_num)) # if listed band is not a TM/ETM+ sensor band, skip it and print message else: print("Can only perform reflectance conversion on TM/ETM+ sensor bands") print("Skipping band {0}".format(band_num)) f.close() return output_filelist
def toa_radiance_457(band_nums, meta_path, outdir=None): """ Top of Atmosphere radiance (in Watts/(square meter x steradians x micrometers)) conversion for Landsat 4, 5, and 7 data. To be performed on raw Landsat 4, 5, or 7 level 1 data. :param band_nums: A list of desired band numbers such as [3, 4, 5] :param meta_path: The full filepath to the metadata file for those bands :param outdir: Output directory to save converted files. :return output_filelist: List of filepaths created by this function. """ output_filelist = [] meta_path = os.path.abspath(meta_path) band_nums = core.enf_list(band_nums) band_nums = map(str, band_nums) # metadata format was changed August 29, 2012. This tool can process either the new or old format f = open(meta_path) MText = f.read() metadata = landsat_metadata(meta_path) # the presence of a PRODUCT_CREATION_TIME category is used to identify old metadata # if this is not present, the meta data is considered new. # Band6length refers to the length of the Band 6 name string. In the new metadata this string is longer if "PRODUCT_CREATION_TIME" in MText: Meta = "oldMeta" Band6length = 2 else: Meta = "newMeta" Band6length = 8 # The tilename is located using the newMeta/oldMeta indixes and the date of capture is recorded if Meta == "newMeta": TileName = getattr(metadata, "LANDSAT_SCENE_ID") year = TileName[9:13] jday = TileName[13:16] date = getattr(metadata, "DATE_ACQUIRED") elif Meta == "oldMeta": TileName = getattr(metadata, "BAND1_FILE_NAME") year = TileName[13:17] jday = TileName[17:20] date = getattr(metadata, "ACQUISITION_DATE") # the spacecraft from which the imagery was capture is identified # this info determines the solar exoatmospheric irradiance (ESun) for each band spacecraft = getattr(metadata, "SPACECRAFT_ID") if "7" in spacecraft: ESun = (1969.0, 1840.0, 1551.0, 1044.0, 255.700, 0.0, 82.07, 1368.00) TM_ETM_bands = ["1", "2", "3", "4", "5", "7", "8"] elif "5" in spacecraft: ESun = (1957.0, 1826.0, 1554.0, 1036.0, 215.0, 0.0, 80.67) TM_ETM_bands = ["1", "2", "3", "4", "5", "7"] elif "4" in spacecraft: ESun = (1957.0, 1825.0, 1557.0, 1033.0, 214.9, 0.0, 80.72) TM_ETM_bands = ["1", "2", "3", "4", "5", "7"] else: arcpy.AddError("This tool only works for Landsat 4, 5, or 7") raise arcpy.ExecuteError() # Calculating values for each band for band_num in band_nums: if band_num in TM_ETM_bands: print("Processing Band {0}".format(band_num)) pathname = meta_path.replace("MTL.txt", "B{0}.tif".format(band_num)) Oraster = arcpy.Raster(pathname) null_raster = arcpy.sa.SetNull(Oraster, Oraster, "VALUE = 0") # using the oldMeta/newMeta indixes to pull the min/max for radiance/Digital numbers if Meta == "newMeta": LMax = getattr(metadata, "RADIANCE_MAXIMUM_BAND_{0}".format(band_num)) LMin = getattr(metadata, "RADIANCE_MINIMUM_BAND_{0}".format(band_num)) QCalMax = getattr(metadata, "QUANTIZE_CAL_MAX_BAND_{0}".format(band_num)) QCalMin = getattr(metadata, "QUANTIZE_CAL_MIN_BAND_{0}".format(band_num)) elif Meta == "oldMeta": LMax = getattr(metadata, "LMAX_BAND{0}".format(band_num)) LMin = getattr(metadata, "LMIN_BAND{0}".format(band_num)) QCalMax = getattr(metadata, "QCALMAX_BAND{0}".format(band_num)) QCalMin = getattr(metadata, "QCALMIN_BAND{0}".format(band_num)) Radraster = (((LMax - LMin) / (QCalMax - QCalMin)) * (null_raster - QCalMin)) + LMin Oraster = 0 del null_raster band_rad = "{0}_B{1}".format(TileName, band_num) # create the output name and save the TOA radiance tiff if outdir is not None: outdir = os.path.abspath(outdir) outname = core.create_outname(outdir, band_rad, "TOA_Rad", "tif") else: folder = os.path.split(meta_path)[0] outname = core.create_outname(folder, band_rad, "TOA_Rad", "tif") Radraster.save(outname) output_filelist.append(outname) del Radraster print("toa radiance saved for Band {0}".format(band_num)) # if listed band is not a TM/ETM+ sensor band, skip it and print message else: print("Can only perform reflectance conversion on TM/ETM+ sensor bands") print("Skipping band {0}".format(band_num)) f.close() return output_filelist
def toa_radiance_8(band_nums, meta_path, outdir=None): """ Top of Atmosphere radiance (in Watts/(square meter x steradians x micrometers)) conversion for landsat 8 data. To be performed on raw Landsat 8 level 1 data. See link below for details: see here http://landsat.usgs.gov/Landsat8_Using_Product.php :param band_nums: A list of desired band numbers such as [3, 4, 5] :param meta_path: The full filepath to the metadata file for those bands :param outdir: Output directory to save converted files. :return output_filelist: List of filepaths created by this function. """ meta_path = os.path.abspath(meta_path) output_filelist = [] # enforce list of band numbers and grab the metadata from the MTL file band_nums = core.enf_list(band_nums) band_nums = map(str, band_nums) meta = landsat_metadata(meta_path) OLI_bands = ["1", "2", "3", "4", "5", "6", "7", "8", "9"] # loop through each band for band_num in band_nums: if band_num in OLI_bands: # create the band name band_path = meta_path.replace("MTL.txt", "B{0}.tif".format(band_num)) Qcal = arcpy.Raster(band_path) null_raster = arcpy.sa.SetNull(Qcal, Qcal, "VALUE = 0") # scrape the attribute data Ml = getattr(meta, "RADIANCE_MULT_BAND_{0}".format(band_num)) # multiplicative scaling factor Al = getattr(meta, "RADIANCE_ADD_BAND_{0}".format(band_num)) # additive rescaling factor # calculate Top-of-Atmosphere radiance TOA_rad = (null_raster * Ml) + Al del null_raster # create the output name and save the TOA radiance tiff if "\\" in meta_path: name = meta_path.split("\\")[-1] elif "//" in meta_path: name = meta_path.split("//")[-1] rad_name = name.replace("_MTL.txt", "_B{0}".format(band_num)) if outdir is not None: outdir = os.path.abspath(outdir) outname = core.create_outname(outdir, rad_name, "TOA_Rad", "tif") else: folder = os.path.split(meta_path)[0] outname = core.create_outname(folder, rad_name, "TOA_Rad", "tif") TOA_rad.save(outname) output_filelist.append(outname) print("Saved toa_radiance at {0}".format(outname)) # if listed band is not a OLI sensor band, skip it and print message else: print("Can only perform reflectance conversion on OLI sensor bands") print("Skipping band {0}".format(band_num)) return output_filelist
def toa_reflectance_457(band_nums, meta_path, outdir=None): """ This function is used to convert Landsat 4, 5, or 7 pixel values from digital numbers to Top-of-Atmosphere Reflectance. To be performed on raw Landsat 4, 5, or 7 data. :param band_nums: A list of desired band numbers such as [3,4,5] :param meta_path: The full filepath to the metadata file for those bands :param outdir: Output directory to save converted files. If left False it will save ouput files in the same directory as input files. :return output_filelist: List of files created by this function """ output_filelist = [] band_nums = core.enf_list(band_nums) band_nums = map(str, band_nums) # metadata format was changed August 29, 2012. This tool can process either the new or old format f = open(meta_path) MText = f.read() meta_path = os.path.abspath(meta_path) metadata = landsat_metadata(meta_path) # the presence of a PRODUCT_CREATION_TIME category is used to identify old metadata # if this is not present, the meta data is considered new. # Band6length refers to the length of the Band 6 name string. In the new metadata this string is longer if "PRODUCT_CREATION_TIME" in MText: Meta = "oldMeta" Band6length = 2 else: Meta = "newMeta" Band6length = 8 # The tilename is located using the newMeta/oldMeta indixes and the date of capture is recorded if Meta == "newMeta": TileName = getattr(metadata, "LANDSAT_SCENE_ID") year = TileName[9:13] jday = TileName[13:16] date = getattr(metadata, "DATE_ACQUIRED") elif Meta == "oldMeta": TileName = getattr(metadata, "BAND1_FILE_NAME") year = TileName[13:17] jday = TileName[17:20] date = getattr(metadata, "ACQUISITION_DATE") # the spacecraft from which the imagery was capture is identified # this info determines the solar exoatmospheric irradiance (ESun) for each band spacecraft = getattr(metadata, "SPACECRAFT_ID") if "7" in spacecraft: ESun = (1969.0, 1840.0, 1551.0, 1044.0, 255.700, 0., 82.07, 1368.00) TM_ETM_bands = ['1', '2', '3', '4', '5', '7', '8'] elif "5" in spacecraft: ESun = (1957.0, 1826.0, 1554.0, 1036.0, 215.0, 0., 80.67) TM_ETM_bands = ['1', '2', '3', '4', '5', '7'] elif "4" in spacecraft: ESun = (1957.0, 1825.0, 1557.0, 1033.0, 214.9, 0., 80.72) TM_ETM_bands = ['1', '2', '3', '4', '5', '7'] else: arcpy.AddError("This tool only works for Landsat 4, 5, or 7") raise arcpy.ExecuteError() # determing if year is leap year and setting the Days in year accordingly if float(year) % 4 == 0: DIY = 366. else: DIY = 365. # using the date to determining the distance from the sun theta = 2 * math.pi * float(jday) / DIY dSun2 = (1.00011 + 0.034221 * math.cos(theta) + 0.001280 * math.sin(theta) + 0.000719 * math.cos(2 * theta) + 0.000077 * math.sin(2 * theta)) SZA = 90. - float(getattr(metadata, "SUN_ELEVATION")) # Calculating values for each band for band_num in band_nums: if band_num in TM_ETM_bands: print("Processing Band {0}".format(band_num)) pathname = meta_path.replace("MTL.txt", "B{0}.tif".format(band_num)) Oraster = arcpy.Raster(pathname) null_raster = arcpy.sa.SetNull(Oraster, Oraster, "VALUE = 0") # using the oldMeta/newMeta indices to pull the min/max for radiance/Digital numbers if Meta == "newMeta": LMax = getattr(metadata, "RADIANCE_MAXIMUM_BAND_{0}".format(band_num)) LMin = getattr(metadata, "RADIANCE_MINIMUM_BAND_{0}".format(band_num)) QCalMax = getattr(metadata, "QUANTIZE_CAL_MAX_BAND_{0}".format(band_num)) QCalMin = getattr(metadata, "QUANTIZE_CAL_MIN_BAND_{0}".format(band_num)) elif Meta == "oldMeta": LMax = getattr(metadata, "LMAX_BAND{0}".format(band_num)) LMin = getattr(metadata, "LMIN_BAND{0}".format(band_num)) QCalMax = getattr(metadata, "QCALMAX_BAND{0}".format(band_num)) QCalMin = getattr(metadata, "QCALMIN_BAND{0}".format(band_num)) Radraster = (((LMax - LMin) / (QCalMax - QCalMin)) * (null_raster - QCalMin)) + LMin Oraster = 0 del null_raster # Calculating temperature for band 6 if present Refraster = (math.pi * Radraster * dSun2) / ( ESun[int(band_num[0]) - 1] * math.cos(SZA * (math.pi / 180))) # construc output names for each band based on whether outdir is set (default is False) if outdir is not None: outdir = os.path.abspath(outdir) BandPath = core.create_outname(outdir, pathname, "TOA_Ref", "tif") else: folder = os.path.split(meta_path)[0] BandPath = core.create_outname(folder, pathname, "TOA_Ref", "tif") Refraster.save(BandPath) output_filelist.append(BandPath) del Refraster, Radraster print("Reflectance Calculated for Band {0}".format(band_num)) # if listed band is not a TM/ETM+ sensor band, skip it and print message else: print( "Can only perform reflectance conversion on TM/ETM+ sensor bands" ) print("Skipping band {0}".format(band_num)) f.close() return output_filelist
def toa_reflectance_8(band_nums, meta_path, outdir=None): """ Converts Landsat 8 bands to Top-of-Atmosphere reflectance. To be performed on raw Landsat 8 level 1 data. See link below for details see here [http://landsat.usgs.gov/Landsat8_Using_Product.php] :param band_nums: A list of desired band numbers such as [3,4,5] :param meta_path: The full filepath to the metadata file for those bands :param outdir: Output directory to save converted files. If left False it will save ouput files in the same directory as input files. :return output_filelist: List of files created by this function """ output_filelist = [] # enforce the list of band numbers and grab metadata from the MTL file band_nums = core.enf_list(band_nums) band_nums = map(str, band_nums) OLI_bands = ['1', '2', '3', '4', '5', '6', '7', '8', '9'] meta_path = os.path.abspath(meta_path) meta = landsat_metadata(meta_path) # cycle through each band in the list for calculation, ensuring each is in the list of OLI bands for band_num in band_nums: if band_num in OLI_bands: # scrape data from the given file path and attributes in the MTL file band_path = meta_path.replace("MTL.txt", "B{0}.tif".format(band_num)) Qcal = arcpy.Raster(band_path) Mp = getattr(meta, "REFLECTANCE_MULT_BAND_{0}".format( band_num)) # multiplicative scaling factor Ap = getattr(meta, "REFLECTANCE_ADD_BAND_{0}".format( band_num)) # additive rescaling factor SEA = getattr(meta, "SUN_ELEVATION") * ( math.pi / 180) # sun elevation angle theta_se # get rid of the zero values that show as the black background to avoid skewing values null_raster = arcpy.sa.SetNull(Qcal, Qcal, "VALUE = 0") # calculate top-of-atmosphere reflectance TOA_ref = (((null_raster * Mp) + Ap) / (math.sin(SEA))) # save the data to the automated name if outdir is given or in the parent folder if not if outdir is not None: outdir = os.path.abspath(outdir) outname = core.create_outname(outdir, band_path, "TOA_Ref", "tif") else: folder = os.path.split(meta_path)[0] outname = core.create_outname(folder, band_path, "TOA_Ref", "tif") TOA_ref.save(outname) output_filelist.append(outname) print("Saved output at {0}".format(outname)) # if listed band is not an OLI sensor band, skip it and print message else: print( "Can only perform reflectance conversion on OLI sensor bands") print("Skipping band {0}".format(band_num)) return output_filelist
def surface_reflectance(meta_path, toa_folder, dem_path, dew_point, outdir=False, kt=1.0): """ This function will estimate surface reflectance for Landsat 4 TM, 5 TM, 7 ETM+, or 8 OLI data. Note this function will calculate surface reflectance for Landsat 4, 5, and 7 bands 1, 2, 3, 4, 5, and 7 or Landsat 8 bands 2, 3, 4, 5, 6, and 7. All 6 bands should be in the toa_folder and have "TOA_Ref" in their filenames as per the landsat.toa_reflectance convention. The Landsat 8 Coastal Aerosol band (1) and Cirrus band (9) will not be calculated as they do not have a corresponding band in TM or ETM+. To be performed on Top-of-Atmosphere Reflectance data processed with the landsat.toa_reflectance_457 or the landsat.toa_reflectance_8 function. Resources - DEM - NASA Shuttle Radar Topography Mission (SRTM) up to 30m resolution. [http://www2.jpl.nasa.gov/srtm/cbanddataproducts.html] - NASA Advanced Spaceborne Thermal Emission and Reflection Radiometer (ASTER) Global Digital Elevation Model (GDEM) [http://asterweb.jpl.nasa.gov/gdem.asp] - USGS National Elevation Dataset (NED) [http://nationalmap.gov/elevation.html] downloadable in 30 meter resolution through The National Map Viewer - Dew Point - NOAA Daily Observational Data: Global Summary of the Day (GSOD) - GIS Data Locator [http://gis.ncdc.noaa.gov/map/viewer/#app=clim&cfg=cdo&theme=daily&layers=0001&node=gis]. Select a weather station within the Landsat scene's extent, follow the link, and enter the scene's acquisition date. This will output a text file, with dew point under DEWP. More NOAA climatic data available at [https://www.climate.gov/data/maps-and-data] :param meta_path: The full filepath to the metadata file (ending in MTL.txt) for the dataset :param toa_folder: The filepath to the folder containing the TOA_Ref tiffs to be processed :param dem_path: The full filepath to a DEM tif that covers the desired Landsat scene Note that the DEM should be resampled to the Landsat bands' resolution (30m) and pixel alignment. Also ensure there are no gaps in the dataset. :param dew_point: The number (e.g. 57.7) for the dew point at the time and place of scene acquisition :param outdir: Output directory to save converted files. If left False it will save output files in the toa_folder directory. :param kt: Unitless turbidity coefficient. Default set at 1.0 for clean air. Set at 0.5 for extremely turbid, dusty, or polluted air. :return output_filelist: A list of all files created by this function """ meta_path = os.path.abspath(meta_path) toa_folder = os.path.abspath(toa_folder) dem_path = os.path.abspath(dem_path) output_filelist = [] #define the list of constants for effective narrowband transmissivity for incoming solar radiation constants_enbt1 = [[0.987, -0.00071, 0.000036, 0.0880, 0.0789], [2.319, -0.00016, 0.000105, 0.0437, -1.2697], [0.951, -0.00033, 0.000280, 0.0875, 0.1014], [0.375, -0.00048, 0.005018, 0.1355, 0.6621], [0.234, -0.00101, 0.004336, 0.0560, 0.7757], [0.365, -0.00097, 0.004296, 0.0155, 0.6390]] #define the list of constants for effective narrowband transmissivity for shortwave radiation #reflected from the surface constants_enbt2 = [[0.987, -0.00071, 0.000036, 0.0880, 0.0789], [2.319, -0.00016, 0.000105, 0.0437, -1.2697], [0.951, -0.00033, 0.000280, 0.0875, 0.1014], [0.375, -0.00048, 0.005018, 0.1355, 0.6621], [0.234, -0.00101, 0.004336, 0.0560, 0.7757], [0.365, -0.00097, 0.004296, 0.0155, 0.6390]] #enforce the list of band numbers, grab metadata from the MTL file, and define the band numbers needed from each sensor meta = landsat_metadata(meta_path) OLI_bands = ['2', '3', '4', '5', '6', '7'] TM_ETM_bands = ['1', '2', '3', '4', '5', '7'] #define the tile name for the landsat scene based on the metadata file's name #Open the metadata text file and read to set the scene's tilename f = open(meta_path) MText = f.read() if "PRODUCT_CREATION_TIME" in MText: tilename = getattr(meta, "BAND1_FILE_NAME") else: tilename = getattr(meta, "LANDSAT_SCENE_ID") #construct the list of TOA reflectance band tiffs and populate it based on the above definitions toa_list = [] out_list = [] n = 0 for file in os.listdir(toa_folder): if ("TOA_Ref" in file) and (file[-4:] == ".tif" or file[-4:] == ".TIF"): if "LC8" in meta_path: tile = "{0}_B{1}".format(tilename, OLI_bands[n]) if tile in file: path = "{0}\\{1}".format(toa_folder, file) out_file = file.replace("TOA", "Surf") toa_list.append(path) out_list.append(out_file) n = n + 1 if n > 5: break elif ("LE7" in file) or ("LT5" in file) or ("LT4" in file): tile = "{0}_B{1}".format(tilename, TM_ETM_bands[n]) if tile in file: path = "{0}\\{1}".format(toa_folder, file) out_file = file.replace("TOA", "Surf") toa_list.append(path) out_list.append(out_file) n = n + 1 if n > 5: break #grab the corner lat/lon coordinates to calculate the approximate scene center lat/lon ul_lat = getattr(meta, "CORNER_UL_LAT_PRODUCT") ul_lon = getattr(meta, "CORNER_UL_LON_PRODUCT") ur_lat = getattr(meta, "CORNER_UR_LAT_PRODUCT") ur_lon = getattr(meta, "CORNER_UR_LON_PRODUCT") ll_lat = getattr(meta, "CORNER_LL_LAT_PRODUCT") ll_lon = getattr(meta, "CORNER_LL_LON_PRODUCT") lr_lat = getattr(meta, "CORNER_LR_LAT_PRODUCT") lr_lon = getattr(meta, "CORNER_LR_LON_PRODUCT") u_lon_avg = np.mean([ul_lon, ur_lon]) l_lon_avg = np.mean([ll_lon, lr_lon]) l_lat_avg = np.mean([ul_lat, ll_lat]) r_lat_avg = np.mean([ur_lat, lr_lat]) center_lat = np.mean([l_lat_avg, r_lat_avg]) center_lon = np.mean([u_lon_avg, l_lon_avg]) #construct the datetime object from the date acquired and scene center time attributes date = getattr(meta, "DATE_ACQUIRED") dl = date.split("-") time = getattr(meta, "SCENE_CENTER_TIME") tl = time.split(":") dt = datetime.datetime(int(dl[0]), int(dl[1]), int(dl[2]), int(tl[0]), int(tl[1]), int(tl[2][0:2])) #use the dnppy.solar module to calculate the solar characteristics at the scene center at the time of acquisition sc = solar.solar(center_lat, center_lon, dt, 0) sc.compute_all() #Cosine of Solar Zenith over horizontal surface declination = math.degrees(sc.get_declination()) hour_angle = math.degrees(sc.get_hour_angle()) lat = math.degrees(center_lat) cth = (math.sin(declination) * math.sin(lat)) + ( math.cos(declination) * math.cos(center_lat) * math.cos(hour_angle)) #Saturation Vapor Pressure svp = 0.6108 * math.exp((17.27 * dew_point) / (dew_point + 237.3)) #Atmospheric Pressure DEM = arcpy.sa.Raster(dem_path) ap = 101.3 * (((293 - (0.0065 * DEM)) / 293)**5.26) #Water in Atmosphere wia = (0.14 * svp * ap) + 2.1 #Effective Narrowband Transmittance for incoming solar radiation entisr_bands = [] for i in xrange(6): c1 = constants_enbt1[i][0] c2 = constants_enbt1[i][1] c3 = constants_enbt1[i][2] c4 = constants_enbt1[i][3] c5 = constants_enbt1[i][4] enbt1 = c1 * ((arcpy.sa.Exp( (c2 * ap) / (kt * cth))) - (((c3 * wia) + c4) / cth)) + c5 entisr_bands.append(enbt1) #Effective Narrowband Transmittance for shortwave radiation reflected from surface entsrrs_bands = [] #cos_n always 1 for sensor pointing straight nadir cos_n = 1 for i in xrange(6): c1 = constants_enbt2[i][0] c2 = constants_enbt2[i][1] c3 = constants_enbt2[i][2] c4 = constants_enbt2[i][3] c5 = constants_enbt2[i][4] enbt2 = c1 * ((arcpy.sa.Exp( (c2 * ap) / (kt * cos_n))) - (((c3 * wia) + c4))) + c5 entsrrs_bands.append(enbt2) #Per Band Path Reflectance pr_bands = [] pr_constants = [0.254, 0.149, 0.147, 0.311, 0.103, 0.036] for j in xrange(6): pr = pr_constants[j] * (1 - entisr_bands[j]) pr_bands.append(pr) #Calculate and save At-Surface Reflectance band tiffs for k in xrange(6): if outdir: outdir = os.path.abspath(outdir) asr_path = "{0}\\{1}".format(outdir, out_list[k]) else: asr_path = "{0}\\{1}".format(toa_folder, out_list[k]) refl_surf = (toa_list[k] - pr_bands[k]) / (entisr_bands[k] * entsrrs_bands[k]) refl_surf.save(asr_path) output_filelist.append(asr_path) return output_filelist
def atsat_bright_temp_8(meta_path, outdir=False): """ Converts Landsat 8 TIRS bands to at satellite brightnes temperature in Kelvins To be performed on raw Landsat 8 level 1 data. See link below for details see here http://landsat.usgs.gov/Landsat8_Using_Product.php :param band_nums: A list of desired band numbers, which should be [10,11] :param meta_path: The full filepath to the metadata file for those bands :param outdir: Output directory to save converted files. If left False it will save ouput files in the same directory as input files. :return output_filelist: A list of all files created by this function """ #enforce the list of band numbers and grab metadata from the MTL file band_nums = ["10", "11"] meta_path = os.path.abspath(meta_path) meta = landsat_metadata(meta_path) output_filelist = [] #cycle through each band in the list for calculation, ensuring each is in the list of TIRS bands for band_num in band_nums: #scrape data from the given file path and attributes in the MTL file band_path = meta_path.replace("MTL.txt", "B{0}.tif".format(band_num)) Qcal = arcpy.Raster(band_path) #get rid of the zero values that show as the black background to avoid skewing values null_raster = arcpy.sa.SetNull(Qcal, Qcal, "VALUE = 0") #requires first converting to radiance Ml = getattr(meta, "RADIANCE_MULT_BAND_{0}".format( band_num)) # multiplicative scaling factor Al = getattr(meta, "RADIANCE_ADD_BAND_{0}".format( band_num)) # additive rescaling factor TOA_rad = (null_raster * Ml) + Al #now convert to at-sattelite brightness temperature K1 = getattr(meta, "K1_CONSTANT_BAND_{0}".format( band_num)) # thermal conversion constant 1 K2 = getattr(meta, "K2_CONSTANT_BAND_{0}".format( band_num)) # thermal conversion constant 2 #calculate brightness temperature at the satellite Bright_Temp = K2 / (arcpy.sa.Ln((K1 / TOA_rad) + 1)) #save the data to the automated name if outdir is given or in the parent folder if not if outdir: outdir = os.path.abspath(outdir) outname = core.create_outname(outdir, band_path, "ASBTemp", "tif") else: folder = os.path.split(meta_path)[0] outname = core.create_outname(folder, band_path, "ASBTemp", "tif") Bright_Temp.save(outname) output_filelist.append(outname) print("Saved output at {0}".format(outname)) del TOA_rad, null_raster return output_filelist
def atsat_bright_temp_457(meta_path, outdir=None): """ Converts band 6 from Landsat 4 and 5 or bands 6 VCID 1 and 2 from Landsat 7 to at satellite brightness temperature in Kelvins To be performed on raw Landsat 4, 5, or 7 level 1 data. :param meta_path: The full filepath to the metadata file, labeled '_MTL.txt', which must be in the same folder as band 6 or 6_VCID_1 and 6_VCID_2 :param outdir: Output directory to save converted files. If left False it will save ouput files in the same directory as input files. :return output_filelist: A list of all files created by this function """ output_filelist = [] meta_path = os.path.abspath(meta_path) metadata = landsat_metadata(meta_path) spacecraft = getattr(metadata, "SPACECRAFT_ID") if "4" in spacecraft or "5" in spacecraft: band_nums = ["6"] elif "7" in spacecraft: band_nums = ["6_VCID_1", "6_VCID_2"] else: print( "Enter the MTL file corresponding to a Landsat 4, 5, or 7 dataset") # These lists will be used to parse the meta data text file and locate relevant information # metadata format was changed August 29, 2012. This tool can process either the new or old format f = open(meta_path) MText = f.read() # the presence of a PRODUCT_CREATION_TIME category is used to identify old metadata # if this is not present, the meta data is considered new. # Band6length refers to the length of the Band 6 name string. In the new metadata this string is longer if "PRODUCT_CREATION_TIME" in MText: Meta = "oldMeta" else: Meta = "newMeta" # The tile name is located using the newMeta/oldMeta indixes and the date of capture is recorded if Meta == "newMeta": TileName = getattr(metadata, "LANDSAT_SCENE_ID") year = TileName[9:13] jday = TileName[13:16] date = getattr(metadata, "DATE_ACQUIRED") elif Meta == "oldMeta": TileName = getattr(metadata, "BAND1_FILE_NAME") year = TileName[13:17] jday = TileName[17:20] date = getattr(metadata, "ACQUISITION_DATE") # the spacecraft from which the imagery was capture is identified # this info determines the solar exoatmospheric irradiance (ESun) for each band # Calculating values for each band for band_num in band_nums: print("Processing Band {0}".format(band_num)) pathname = meta_path.replace("MTL.txt", "B{0}.tif".format(band_num)) Oraster = arcpy.Raster(pathname) # get rid of the zero values that show as the black background to avoid skewing values null_raster = arcpy.sa.SetNull(Oraster, Oraster, "VALUE = 0") # using the oldMeta/newMeta indixes to pull the min/max for radiance/Digital numbers if Meta == "newMeta": LMax = getattr(metadata, "RADIANCE_MAXIMUM_BAND_{0}".format(band_num)) LMin = getattr(metadata, "RADIANCE_MINIMUM_BAND_{0}".format(band_num)) QCalMax = getattr(metadata, "QUANTIZE_CAL_MAX_BAND_{0}".format(band_num)) QCalMin = getattr(metadata, "QUANTIZE_CAL_MIN_BAND_{0}".format(band_num)) elif Meta == "oldMeta": LMax = getattr(metadata, "LMAX_BAND{0}".format(band_num)) LMin = getattr(metadata, "LMIN_BAND{0}".format(band_num)) QCalMax = getattr(metadata, "QCALMAX_BAND{0}".format(band_num)) QCalMin = getattr(metadata, "QCALMIN_BAND{0}".format(band_num)) Radraster = (((LMax - LMin) / (QCalMax - QCalMin)) * (null_raster - QCalMin)) + LMin Oraster = 0 # Calculating temperature for band 6 if present if "4" in spacecraft or "5" in spacecraft: Refraster = 1260.56 / (arcpy.sa.Ln((607.76 / Radraster) + 1.0)) if "7" in spacecraft: Refraster = 1282.71 / (arcpy.sa.Ln((666.09 / Radraster) + 1.0)) band_temp = "{0}_B{1}".format(TileName, band_num) # save the data to the automated name if outdir is given or in the parent folder if not if outdir: outdir = os.path.abspath(outdir) BandPath = core.create_outname(outdir, band_temp, "ASBTemp", "tif") else: folder = os.path.split(meta_path)[0] BandPath = core.create_outname(folder, band_temp, "ASBTemp", "tif") Refraster.save(BandPath) output_filelist.append(BandPath) del Refraster, Radraster, null_raster print("Temperature Calculated for Band {0}".format(band_num)) f.close() return output_filelist
def atsat_bright_temp_457(meta_path, outdir = None): """ Converts band 6 from Landsat 4 and 5 or bands 6 VCID 1 and 2 from Landsat 7 to at satellite brightness temperature in Kelvins To be performed on raw Landsat 4, 5, or 7 level 1 data. :param meta_path: The full filepath to the metadata file, labeled '_MTL.txt', which must be in the same folder as band 6 or 6_VCID_1 and 6_VCID_2 :param outdir: Output directory to save converted files. If left False it will save ouput files in the same directory as input files. :return output_filelist: A list of all files created by this function """ output_filelist = [] meta_path = os.path.abspath(meta_path) metadata = landsat_metadata(meta_path) spacecraft = getattr(metadata, "SPACECRAFT_ID") if "4" in spacecraft or "5" in spacecraft: band_nums = ["6"] elif "7" in spacecraft: band_nums = ["6_VCID_1", "6_VCID_2"] else: print("Enter the MTL file corresponding to a Landsat 4, 5, or 7 dataset") # These lists will be used to parse the meta data text file and locate relevant information # metadata format was changed August 29, 2012. This tool can process either the new or old format f = open(meta_path) MText = f.read() # the presence of a PRODUCT_CREATION_TIME category is used to identify old metadata # if this is not present, the meta data is considered new. # Band6length refers to the length of the Band 6 name string. In the new metadata this string is longer if "PRODUCT_CREATION_TIME" in MText: Meta = "oldMeta" else: Meta = "newMeta" # The tile name is located using the newMeta/oldMeta indixes and the date of capture is recorded if Meta == "newMeta": TileName = getattr(metadata, "LANDSAT_SCENE_ID") year = TileName[9:13] jday = TileName[13:16] date = getattr(metadata, "DATE_ACQUIRED") elif Meta == "oldMeta": TileName = getattr(metadata, "BAND1_FILE_NAME") year = TileName[13:17] jday = TileName[17:20] date = getattr(metadata, "ACQUISITION_DATE") # the spacecraft from which the imagery was capture is identified # this info determines the solar exoatmospheric irradiance (ESun) for each band # Calculating values for each band for band_num in band_nums: print("Processing Band {0}".format(band_num)) pathname = meta_path.replace("MTL.txt", "B{0}.tif".format(band_num)) Oraster = arcpy.Raster(pathname) # get rid of the zero values that show as the black background to avoid skewing values null_raster = arcpy.sa.SetNull(Oraster, Oraster, "VALUE = 0") # using the oldMeta/newMeta indixes to pull the min/max for radiance/Digital numbers if Meta == "newMeta": LMax = getattr(metadata, "RADIANCE_MAXIMUM_BAND_{0}".format(band_num)) LMin = getattr(metadata, "RADIANCE_MINIMUM_BAND_{0}".format(band_num)) QCalMax = getattr(metadata, "QUANTIZE_CAL_MAX_BAND_{0}".format(band_num)) QCalMin = getattr(metadata, "QUANTIZE_CAL_MIN_BAND_{0}".format(band_num)) elif Meta == "oldMeta": LMax = getattr(metadata, "LMAX_BAND{0}".format(band_num)) LMin = getattr(metadata, "LMIN_BAND{0}".format(band_num)) QCalMax = getattr(metadata, "QCALMAX_BAND{0}".format(band_num)) QCalMin = getattr(metadata, "QCALMIN_BAND{0}".format(band_num)) Radraster = (((LMax - LMin)/(QCalMax-QCalMin)) * (null_raster - QCalMin)) + LMin Oraster = 0 # Calculating temperature for band 6 if present if "4" in spacecraft or "5" in spacecraft: Refraster = 1260.56/(arcpy.sa.Ln((607.76/Radraster) + 1.0)) if "7" in spacecraft: Refraster = 1282.71/(arcpy.sa.Ln((666.09/Radraster) + 1.0)) band_temp = "{0}_B{1}".format(TileName, band_num) # save the data to the automated name if outdir is given or in the parent folder if not if outdir: outdir = os.path.abspath(outdir) BandPath = core.create_outname(outdir, band_temp, "ASBTemp", "tif") else: folder = os.path.split(meta_path)[0] BandPath = core.create_outname(folder, band_temp, "ASBTemp", "tif") Refraster.save(BandPath) output_filelist.append(BandPath) del Refraster, Radraster, null_raster print("Temperature Calculated for Band {0}".format(band_num)) f.close() return output_filelist