From db9f01f262e69b5b4efc9fdbc65182a3bec72587 Mon Sep 17 00:00:00 2001 From: Shi Qiu Date: Thu, 16 May 2019 12:48:04 -0400 Subject: [PATCH] Add files via upload --- BufferMasks.m | 66 +++ CDI.m | 36 ++ CheckImagesPath.m | 224 +++++++++ DetectAbsSnow.m | 97 ++++ DetectPotentialCloud.m | 152 ++++++ DetectPotentialCloudShadow.m | 114 +++++ DetectPotentialFalsePositivePixels.m | 62 +++ DetectPotentialPixels.m | 56 +++ DetectSnow.m | 13 + DetectWater.m | 76 +++ EnhanceLine.m | 85 ++++ ErodeCommissons.m | 105 ++++ FmaskParameters.m | 45 ++ GRIDobj.m | 437 +++++++++++++++++ GRIDobj2geotiff.m | 120 +++++ LoadAuxiData.m | 535 +++++++++++++++++++++ LoadData.m | 152 ++++++ LoadSensorType.m | 73 +++ MatchCloudShadow.m | 582 ++++++++++++++++++++++ NDBI.m | 34 ++ NDSI.m | 41 ++ NDVI.m | 36 ++ NormalizaCirrusDEM.m | 54 +++ NormalizeBT.m | 96 ++++ ObjMeta.m | 22 + ObjTOABT.m | 53 ++ ObservMask.m | 51 ++ ProjectDEM2Plane.m | 108 +++++ ReadMetadataMSI.m | 118 +++++ ReadS2InspireXML.m | 16 + ReadSunViewGeometryMSI.m | 322 +++++++++++++ Readopentopo.m | 121 +++++ Saturate.m | 7 + arcslope.m | 90 ++++ aspect.m | 99 ++++ autoFmask.m | 236 +++++++++ autoFmaskBatch.m | 13 + findMatdetecFootprint.m | 173 +++++++ geoimread.m | 399 ++++++++++++++++ getRealCloudPosition.m | 15 + getRealCloudPositionS2.m | 22 + getSensorViewGeo.m | 23 + getcoordinates.m | 32 ++ imcircle.m | 99 ++++ inpaint_nans.m | 1 + lndhdrread.m | 371 ++++++++++++++ nd2toarbt.m | 691 +++++++++++++++++++++++++++ nd2toarbt_msi.m | 227 +++++++++ pixel2pixv.m | 59 +++ probThin.m | 8 + problBrightness.m | 18 + problSpectralVaribility.m | 11 + problTemperature.m | 26 + probwBrightness.m | 10 + probwTemperature.m | 14 + readme.txt | 13 + refmat2XY.m | 29 ++ reproject2utm.m | 188 ++++++++ stratiedSampleHanlder.m | 137 ++++++ xml2struct.m | 183 +++++++ 60 files changed, 7296 insertions(+) create mode 100644 BufferMasks.m create mode 100644 CDI.m create mode 100644 CheckImagesPath.m create mode 100644 DetectAbsSnow.m create mode 100644 DetectPotentialCloud.m create mode 100644 DetectPotentialCloudShadow.m create mode 100644 DetectPotentialFalsePositivePixels.m create mode 100644 DetectPotentialPixels.m create mode 100644 DetectSnow.m create mode 100644 DetectWater.m create mode 100644 EnhanceLine.m create mode 100644 ErodeCommissons.m create mode 100644 FmaskParameters.m create mode 100644 GRIDobj.m create mode 100644 GRIDobj2geotiff.m create mode 100644 LoadAuxiData.m create mode 100644 LoadData.m create mode 100644 LoadSensorType.m create mode 100644 MatchCloudShadow.m create mode 100644 NDBI.m create mode 100644 NDSI.m create mode 100644 NDVI.m create mode 100644 NormalizaCirrusDEM.m create mode 100644 NormalizeBT.m create mode 100644 ObjMeta.m create mode 100644 ObjTOABT.m create mode 100644 ObservMask.m create mode 100644 ProjectDEM2Plane.m create mode 100644 ReadMetadataMSI.m create mode 100644 ReadS2InspireXML.m create mode 100644 ReadSunViewGeometryMSI.m create mode 100644 Readopentopo.m create mode 100644 Saturate.m create mode 100644 arcslope.m create mode 100644 aspect.m create mode 100644 autoFmask.m create mode 100644 autoFmaskBatch.m create mode 100644 findMatdetecFootprint.m create mode 100644 geoimread.m create mode 100644 getRealCloudPosition.m create mode 100644 getRealCloudPositionS2.m create mode 100644 getSensorViewGeo.m create mode 100644 getcoordinates.m create mode 100644 imcircle.m create mode 100644 inpaint_nans.m create mode 100644 lndhdrread.m create mode 100644 nd2toarbt.m create mode 100644 nd2toarbt_msi.m create mode 100644 pixel2pixv.m create mode 100644 probThin.m create mode 100644 problBrightness.m create mode 100644 problSpectralVaribility.m create mode 100644 problTemperature.m create mode 100644 probwBrightness.m create mode 100644 probwTemperature.m create mode 100644 readme.txt create mode 100644 refmat2XY.m create mode 100644 reproject2utm.m create mode 100644 stratiedSampleHanlder.m create mode 100644 xml2struct.m diff --git a/BufferMasks.m b/BufferMasks.m new file mode 100644 index 0000000..3a65bd5 --- /dev/null +++ b/BufferMasks.m @@ -0,0 +1,66 @@ +function [cloud,shadow,snow] = BufferMasks(pcloud,cdpix,pshadow,csdpix,psnow,sdpix) +% BUFFERMASKS Dilate cloud/cloud shadow/snow with cdpix/csdpix/sdpix with +% default value of 3/3/3. +% +% Syntax +% +% [cloud,shadow,snow] = +% BufferMasks(pcloud,cdpix,pshadow,csdpix,psnow,sdpix) +% +% Description +% +% Dilate cloud/cloud shadow/snow with cdpix/csdpix/sdpix with +% default value of 3/3/3. +% +% Input arguments +% pcloud Cloud mask. +% cldpix Dilated number of pixels for cloud with default value of 3. +% pshadow Cloud shadow mask. +% sdpix Dilated number of pixels for cloud shadow value of 3. +% psnow Snow mask. +% snpix Dilated number of pixels for snow value of 3. +% +% Output arguments +% +% cloud Final dilated cloud mask. +% shadow Final dilated cloud shadow mask. +% snow Final dilated snow mask. +% +% Example +% +% [cloud,shadow,snow] = +% BufferMasks(pcloud,3,pshadow,3,psnow,3); +% +% +% Author: Shi Qiu (shi.qiu@ttu.edu) +% Date: 2. November, 2017 + + % buffer cloud + if cdpix>0 + CEs=strel('square',2*cdpix+1); + cloud=imdilate(pcloud,CEs); + clear pcloud CEs cdpix; + else + cloud=pcloud; + clear pcloud + end + % buffer cloud shadow + if csdpix>0 + CSEs=strel('square',2*csdpix+1); + shadow=imdilate(pshadow,CSEs); + clear pshadow CSEs csdpix; + else + shadow=pshadow; + clear pshadow + end + % buffer snow + if sdpix>0 + SEs=strel('square',2*sdpix+1); + snow=imdilate(psnow,SEs); + clear psnow SEs sdpix; + else + snow=psnow; + clear psnow + end +end + diff --git a/CDI.m b/CDI.m new file mode 100644 index 0000000..aa1c43d --- /dev/null +++ b/CDI.m @@ -0,0 +1,36 @@ +function cdi = CDI(S2band7,S2band8,S2band8A) +%CDI This is used seprate bright surface from cloud by following David, +%2018 RSE + if ~isempty(S2band7)&&~isempty(S2band8) + ratio_8A_8 = S2band8./S2band8A; + clear S2band8; + ratio_8A_7 = S2band7./S2band8A; + clear S2band7 S2band8A; + + std_ratio_8A_8 =stdfilt(ratio_8A_8, true(7)); + clear ratio_8A_8; + var_ratio_8A_8 = std_ratio_8A_8 .^2; + clear std_ratio_8A_8; + + std_ratio_8A_7 = stdfilt(ratio_8A_7, true(7)); + clear ratio_8A_7; + var_ratio_8A_7 = std_ratio_8A_7 .^2; + clear std_ratio_8A_7; + + cdi = (var_ratio_8A_7-var_ratio_8A_8)./(var_ratio_8A_8+var_ratio_8A_7); + +% dim = size(cdi); +% cdi = imresize(cdi,1/7,'box'); +% cdi = ordfilt2(cdi,1,ones(3,3)); +% cdi = imresize(cdi,dim,'nearest'); +% clear dim; + + +% h = fspecial('average', 21); +% cdi = filter2(h, cdi); + else + cdi = []; + end +end + + diff --git a/CheckImagesPath.m b/CheckImagesPath.m new file mode 100644 index 0000000..6b69ed7 --- /dev/null +++ b/CheckImagesPath.m @@ -0,0 +1,224 @@ +function [num_all_images, sensors, paths, info_count_text] = CheckImagesPath(path_data) +%CHECKIMAGEPATH Ensure the input path is right to find a Landsats 4-8, and +%Sentinel 2 image(s). +% path_data - the input path + + % searching deeps. 0 is default. + [image_types_paths] = CheckImagePath(path_data,0); + + % all count info foe searched images at current folder. + num_all_images = size(image_types_paths,1); + num_L4_tm = 0; + num_L5_tm = 0; + num_L6_tm = 0; + num_L7_tm_plus = 0; + num_L8_oli_tirs = 0; + num_S2A_msi = 0; + num_S2B_msi = 0; + + % key info: the senor and path are needed as outputs, the first one is + % to determine the default parameters for Fmask, and the second one is + % to determine the path loading all data. + sensors = []; + paths = []; + info_count_text = []; + + for i_all = 1:num_all_images + % c: current. + cimage_sensor = image_types_paths{i_all,1}; + cimage_num = image_types_paths{i_all,2}; + cimage_type = Convert2ImageType(cimage_sensor,cimage_num); + clear cimage_num; + cimage_path = image_types_paths{i_all,3}; + switch cimage_type + case {'Landsat 4 TM'} + num_L4_tm=num_L4_tm+1; + case {'Landsat 5 TM'} + num_L5_tm=num_L5_tm+1; + case {'Landsat 6 TM'} + num_L6_tm=num_L6_tm+1; + case {'Landsat 7 ETM+'} + num_L7_tm_plus=num_L7_tm_plus+1; + case {'Landsat 8 OLI/TIRS'} + num_L8_oli_tirs=num_L8_oli_tirs+1; + case {'Sentinel 2A MSI'} + % further check Sentinel 2 image folder, see there is + % .SAFE. + if contains(cimage_path,'.SAFE') + num_S2A_msi=num_S2A_msi+1; + else + cimage_sensor = [];% no available Sentinel 2 data. + end + case {'Sentinel 2B MSI'} + % see there is .SAFE. + if contains(cimage_path,'.SAFE') + num_S2B_msi=num_S2B_msi+1; + else + cimage_sensor = [];% no available Sentinel 2 data. + end + end + if ~isempty(cimage_sensor) + sensors = [sensors;{cimage_sensor}]; + paths = [paths;{cimage_path}]; + end + clear cimage_sensor; + end + % renew num_all_images + num_all_images = length(sensors); + % used to notice user. + text_line = 0; + % multide + if isequal(num_all_images,num_L4_tm)||... + isequal(num_all_images,num_L5_tm)||... + isequal(num_all_images,num_L6_tm)||... + isequal(num_all_images,num_L7_tm_plus)||... + isequal(num_all_images,num_L8_oli_tirs)||... + isequal(num_all_images,num_S2A_msi)||... + isequal(num_all_images,num_S2B_msi) + % only for 1 type image + if num_L4_tm > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 4 TM images are found at ''%s''\n',... + num2str(num_L4_tm),path_data); + end + if num_L5_tm > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 5 TM images are found at ''%s''\n',... + num2str(num_L5_tm),path_data); + end + if num_L6_tm > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 6 TM images are found at ''%s''\n',... + num2str(num_L6_tm),path_data); + end + if num_L7_tm_plus > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 7 ETM+ images are found at ''%s''\n',... + num2str(num_L7_tm_plus),path_data); + end + if num_L8_oli_tirs > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 8 OLI/TIRS images are found at ''%s''\n',... + num2str(num_L8_oli_tirs),path_data); + end + if num_S2A_msi > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Sentinel 2A MSI images are found at ''%s''\n',... + num2str(num_S2A_msi),path_data); + end + if num_S2B_msi > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Sentinel 2B MSI images are found at ''%s''\n',... + num2str(num_S2B_msi),path_data); + end + else + text_line = text_line+1; + info_count_text{text_line} = sprintf(... + 'A total of %s images (as follows) are found at ''%s''\n',... + num2str(num_all_images), path_data); + if num_L4_tm > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 4 TM images\n',... + num2str(num_L4_tm)); + end + if num_L5_tm > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 5 TM images\n',... + num2str(num_L5_tm)); + end + if num_L6_tm > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 6 TM images\n',... + num2str(num_L6_tm)); + end + if num_L7_tm_plus > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 7 ETM+ images\n',... + num2str(num_L7_tm_plus)); + end + if num_L8_oli_tirs > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 8 OLI/TIRS images\n',... + num2str(num_L8_oli_tirs)); + end + if num_S2A_msi > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Sentinel 2A MSI images\n',... + num2str(num_S2A_msi)); + end + if num_S2B_msi > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Sentinel 2B MSI images\n',... + num2str(num_S2B_msi)); + end + end + + % if no data is found, give mention info. + if isempty(sensors) + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s available images are found at ''%s''\n',... + '0',path_data); + text_line = text_line+1; + info_count_text{text_line} = sprintf('Please ensure the path is correct\n'); + end +% fprintf(info_count); +end + +function [image_types_paths] = CheckImagePath(path_data,subfolder_level) +%CHECKIMAGEPATH Ensure the input path is right to find a Landsats 4-8, and +%Sentinel 2 image(s). +% path_data - the input path +% subfolder_level - the level of subfolders that can be used to limit +% searching deeps. 0 is default. + + % If the searching deeps are more than 5, stop and return; + if subfolder_level > 5 + image_types_paths = []; + return; + end + image_types_paths = []; + % first search the image(s) at current folder.image_types_paths + [sensor,num_Lst,~,~] = LoadSensorType(path_data); + if isempty(sensor) + % if no available image at current folder, + % and search its subfolders. + subfolders = dir(path_data); + for i_sub=1:length(subfolders) + % filter out the file names starting with '.', that is not + % right folder (system crashes). + if strcmp(subfolders(i_sub).name(1),'.') + continue; + end + % go to search the images at each subfolder + path_subfoler = fullfile(subfolders(i_sub).folder,... + subfolders(i_sub).name); + [image_types_paths_sub] = CheckImagePath(path_subfoler,subfolder_level+1); + if ~isempty(image_types_paths_sub) + image_types_paths =[image_types_paths;image_types_paths_sub]; + end + end + + else + % successfully searched a supported image. + % and, return the sensor and the image path + image_types_paths = [image_types_paths;{sensor,num_Lst,path_data}]; + end +end +function image_type = Convert2ImageType(sensor,num_Lst) +% CONVERT@IMAGETYPE Contruct image type from the sensor and num + + switch sensor + case 'L_TM' + image_type = ['Landsat ',num_Lst,' TM']; + case 'L_ETM_PLUS' + image_type = ['Landsat ',num_Lst,' ETM+']; + case 'L_OLI_TIRS' + image_type = ['Landsat ',num_Lst,' OLI/TIRS']; + case 'S_MSI' + image_type = ['Sentinel ',num_Lst,' MSI']; + otherwise + image_type=[]; +% error(['Errors occur when searching images at ''', path_data],'''.'); + end +end + diff --git a/DetectAbsSnow.m b/DetectAbsSnow.m new file mode 100644 index 0000000..857ae27 --- /dev/null +++ b/DetectAbsSnow.m @@ -0,0 +1,97 @@ +function abs_snow = DetectAbsSnow(band_green,band_green_statu,ndsi,psnow,resolution) +%DETECTABSSNOW Select absolute snow/ice pixels using spectral-contextual +% features. +% +% Syntax +% +% abs_snow = +% DetectAbsSnow(band_green,band_green_statu,ndsi,psnow,resolution) +% +% Description +% +% A spectral-contextual snow index (SCSI) is used to select 100% +% snow/ice pixels. SCSI10 is computed here, of which 10 indicates 10 +% kilometers-by-10 kilometers window size, that 333-by-333 pixels for +% Landsat 30 meters image and 501-by-501 pixels for Sentinel-2 20 +% meters image (Fmask runs at this 20 meters). That window size is +% large enough to capture the various contexts for clouds, but still +% give us soomth variations for pure snow/ice. +% +% Input arguments +% +% band_green Green band. +% band_green_statu Statured pixels at green band. +% psnow Potential snow/ice. +% resolution Spatial resolution of input image. +% +% Output arguments +% +% abs_snow Absolute snow/ice. +% +% Example +% +% abs_snow = +% DetectAbsSnow(band_green,band_green_statu,ndsi,psnow,resolution) +% +% +% Author: Shi Qiu (shi.qiu@uconn.edu) +% Date: 21. January, 2018 + + % which is equal to 51*51 % when have snow/ice pixels. + if sum(psnow(:))> 110889 + % sometimes Nan will appear in this green band. + band_green_filled = fillmissing(band_green,'constant',0); + clear band_green; + % window size is 10 km. + radius = fix(5000/resolution); + clear resolution; + win_size=2*radius+1; + % compute SCSI. + % clip to small arrays (16 smalls); + num_clips = 5; % 5 cippes + [size_row, size_colum] =size(band_green_filled); + num_per_row = round((size_row)/num_clips); + + row_nums = zeros([1,num_clips],'double'); + row_nums(1:num_clips-1)=num_per_row; + % the last one + row_nums(num_clips) = size_row - num_per_row*(num_clips-1); + + scsi = zeros([size_row, size_colum],'double'); + % the first clip + + + for i_row = 1:num_clips + row_ed = sum(row_nums(1:i_row)); + row_st = row_ed - row_nums(i_row) + 1; + + row_st_exp = row_st - radius; + row_ed_exp = row_ed + radius; + row_st_small = radius + 1; + row_ed_small = radius + row_nums(i_row); + % the first clip + if row_st_exp < 1 + row_st_exp =1; + row_st_small =1; + row_ed_small = row_nums(i_row);% no the first exp + end + % the last clip + if row_ed_exp > size_row + row_ed_exp = size_row; +% row_ed_small = radius + row_nums(i_row); + end + scsi_small= stdfilt(band_green_filled(row_st_exp:row_ed_exp,:),... + true(win_size)).*(1-ndsi(row_st_exp:row_ed_exp,:)); + scsi(row_st:row_ed,:) = scsi_small(row_st_small:row_ed_small,:); + clear scsi_small; + end + % scsi=stdfilt(band_green_filled,true(win_size)).*(1-ndsi); + clear band_green_filled ndsi win_size; + % only get snow/ice pixels from all potential snow/ice pixels, and + % do not select the saturated pixels which may be cloud! + abs_snow=scsi<9&psnow&(~band_green_statu); + clear scsi psnow band_green_statu; + else + abs_snow=[]; + end +end diff --git a/DetectPotentialCloud.m b/DetectPotentialCloud.m new file mode 100644 index 0000000..1bd8a6b --- /dev/null +++ b/DetectPotentialCloud.m @@ -0,0 +1,152 @@ +function [sum_clr,cloud,idused,t_templ,t_temph]=DetectPotentialCloud(... + data_meta,mask,water,data_toabt, dem, ndvi,ndsi,ndbi,idplcd,... + whiteness,HOT,wpt,cldprob) +%DETECTPOTENTIALCLOUD Detect potential clouds using scene-based method. +% +% Syntax +% +% [sum_clr,cloud,idlnd,t_templ,t_temph]= +% DetectPotentialCloud(data_meta,mask,water,data_toabt, dem, ndvi,ndsi, +% ndbi,idplcd,whiteness,HOT,wpt,cldprob) +% +% Description +% +% Several cloud probabilities are combinated together to capture cloud +% features of white, bright, cold, and/or high. +% +% Input arguments +% +% data_meta Metadata including [row size, column size]. +% mask Observation mask (outside). +% water Water mask. +% data_toabt TOA reflectance and BT. +% dem DEM data. +% ndvi NDVI. +% ndsi NDSI. +% ndbi NDBI. +% idplcd Absolute clear sky pixels. +% whiteness Whitness. +% HOT Data derived from the HOT transform. +% wpt Weight of thin probability (0.3 for Landsat 8 and +% 0.5 for Sentinel 2). +% cldprob Cloud probability threshold to segment clouds from +% surface. +% +% Output arguments +% +% sum_clr The total number of clear sky pixels. +% cloud Potential clouds. +% idlnd Clear sky land pixels. +% t_templ Low level temperature (78.5 percentile). +% t_temph High level temperature (81.5 percentile). +% +% +% +% Author: Shi Qiu (shi.qiu@uconn.edu) +% Date: 20. January, 2018 + + % inputs: BandCirrus BandBT BandSWIR1 SatuGreen SatuRed + cloud = zeros(data_meta.Dim,'uint8'); % cloud mask + %% Constants Parameters + l_pt=0.175; % low percent + h_pt=1-l_pt; % high percent + + %% Step 2: calcualte cloud probability + % select clear sky pixels for land and water, respectively. + idclr=idplcd==false&mask==1; + sum_clr=sum(idclr(:)); + idlnd = idclr&water==false; + idwt = idclr&water==true;%&data(:,:,6)<=300; + + t_templ = 0; + t_temph = 0; + idused = []; + % 99.9% TO 99.99% + if sum_clr <= 40000 % when potential cloud cover less than 0.1%, directly screen all PCPs out. + cloud(idplcd==true)=1; % all cld + cloud(mask==0)=0; + else + %%%%%%%%%%% thin cloud prob for both water and land%%%%%%%%%%% + prob_thin = 0; % there is no contribution from the new bands + if ~isempty(data_toabt.BandCirrus) % Landsat 4~7 + prob_thin = probThin(data_toabt.BandCirrus); + data_toabt.BandCirrus = []; + end + + %%%%%%%%%%%%%%%%%%%%%%cloud prob over water%%%%%%%%%%%%%%%%%%% + wprob_temp=1; + if ~isempty(data_toabt.BandBT) + wprob_temp = probwTemperature(data_toabt.BandBT,idwt,h_pt); + end + + wprob_brightness = probwBrightness(data_toabt.BandSWIR1); + data_toabt.BandSWIR1 = []; + + %%%%%%%%%%%%%%%%%%%%%%cloud prob over land%%%%%%%%%%%%%%%%%%%% + lndptm=100*sum(idlnd(:))/sum(mask(:)); + if lndptm >= 0.1 + idused=idlnd; + else % when having no enough clear land pixels, we used all PCPs to calculate clear land basics. + idused=idclr; + end + clear lndptm; + + lprob_temp=1; + lprob_brightness=1; + + + if ~isempty(data_toabt.BandBT) % if have BT. normalize it using DEMs and use it to calcualte temperature probability. + data_toabt.BandBT = NormalizeBT( data_meta.Dim,dem,mask,data_toabt.BandBT,idused,l_pt,h_pt,data_meta.Resolution(1)); + [lprob_temp, t_templ,t_temph]=problTemperature(data_toabt.BandBT,idused,l_pt,h_pt); + else % if have no BT, use HOT probability instead of temperature probability. + lprob_brightness = problBrightness(HOT,idused,l_pt,h_pt); + clear HOT l_pt; + end +% clear idused; + + lprob_vari = problSpectralVaribility(ndvi,ndsi,ndbi,whiteness,data_toabt.SatuGreen,data_toabt.SatuRed); + clear ndvi ndsi whiteness; + + %%%%%%%%%%%%%%%%%%%%%%%%%%final clouds%%%%%%%%%%%%%%%%%%%%%%% + % [Final prob mask (water)] + wprob_final=wprob_temp.*wprob_brightness + wpt.*prob_thin; % cloud over water probability + clear wprob_temp wprob_brightness; + wprob_final=100.*wprob_final; % convert percentage + wclr_h=prctile(wprob_final(idwt),100*h_pt); + clear idwt; + % Final prob mask (land) + lprob_final=lprob_temp.*lprob_vari.*lprob_brightness + wpt.*prob_thin; % cloud over land probability +% clear lprob_temp lprob_vari lprob_brightness prob_thin wpt; + clear lprob_temp lprob_vari prob_thin wpt; + lprob_final=100.*lprob_final; % convert percentage + clr_h=prctile(lprob_final(idlnd),100*h_pt); + clear h_pt; + + wclr_max=wclr_h+cldprob;% dynamic threshold (water) + clr_max=clr_h+cldprob;% dynamic threshold (land) + clear cldprob; + clear idclr; + + % all potential clouds +% % cloud(idclr==true)=-1; + + id_final_cld = SegementClouds(idplcd,water,data_toabt.BandBT,t_templ,lprob_final,wprob_final,clr_max,wclr_max); + clear clr_max wclr_max; + cloud(id_final_cld)=1; + clear id_final_cld; + + cloud(mask==0)=0; + clear mask; + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%final clouds%%%%%%%%%%%%%%%%%%%%%%% +function id_final_cld = SegementClouds(idplcd,water,bt,t_templ,lprob,wprob,clr_max,wclr_max) + % final clouds + id_final_cld=idplcd==true&((lprob>clr_max&water==0)|...% cloud over land + (wprob>wclr_max&water==1));% thin cloud over water + if ~isempty(bt) % if have BT. + id_final_cld=id_final_cld|(bt200)=1; + masker_shadow(masker_observation==0)=255; +end + +% CSC+C stratied on cos i with 0.1 increasement. a total of 50,000 pixels. +function [data_nir,data_swir] = getDataTopoCorrected(data_nir_ori,data_swir_ori,index_exclude_cloud_water,sun_zenith_deg,sun_azimuth_deg, slope_data,aspect_data,dim,resl ) +% History: +% 1. Create this function. (1. January, 2017) +% 2. A total of samples are revised as 40,000 from 50,000. (8. March, 2018) +% 3. When c is calculated as Nan, this function will not make the topo +% correction. (8. March, 2018) + sun_zenith_rad=deg2rad(sun_zenith_deg); + sun_zenith_cos=cos(sun_zenith_rad); + sun_zenith_sin=sin(sun_zenith_rad); + clear sun_zenith_deg sun_zenith_rad sun_zenith_rad; + cos_sita=sun_zenith_cos.*cos(deg2rad(slope_data))+sun_zenith_sin.*sin(deg2rad(slope_data)).*cos(deg2rad(sun_azimuth_deg-aspect_data)); + clear aspect_data; + cos_sita_exclude_cloud=cos_sita(index_exclude_cloud_water); + % random stratied sampling + cos_sita_min=min(cos_sita_exclude_cloud); + cos_sita_max=max(cos_sita_exclude_cloud); +% total_sample=50000; + total_sample=40000; + cos_sita_interval=0.1; + samples_ids= stratiedSampleHanlder(cos_sita_exclude_cloud,cos_sita_min,cos_sita_max,dim,total_sample,cos_sita_interval,0,resl); + cos_sita_samples=cos_sita_exclude_cloud(samples_ids); + clear cos_sita_exclude_cloud cos_sita_min cos_sita_max total_sample cos_sita_interval; + % for NIR + data_nir_ori_tmp=data_nir_ori(index_exclude_cloud_water); + data_samples_nir=data_nir_ori_tmp(samples_ids); + clear data_nir_ori_tmp; + c_fitted=polyfit(double(cos_sita_samples),double(data_samples_nir),1); +% figure;plot(double(cos_sita_samples),double(data_samples_nir),'r.'); + c=c_fitted(1,2)/c_fitted(1,1); + + clear c_fitted; + if isnan(c) + data_nir=data_nir_ori; + clear data_nir_ori; + else + data_nir=double(data_nir_ori).*(cos(deg2rad(slope_data)).*sun_zenith_cos+c)./(cos_sita+c); + clear data_nir_ori; + end + % for SWIR + data_swir_ori_tmp=data_swir_ori(index_exclude_cloud_water); + data_samples_swir=data_swir_ori_tmp(samples_ids); + clear data_swir_ori_tmp; + c_fitted=polyfit(double(cos_sita_samples),double(data_samples_swir),1); + c=c_fitted(1,2)/c_fitted(1,1); + clear c_fitted samples_ids; + if isnan(c) + data_swir=data_swir_ori; + clear data_swir_ori; + else + data_swir=double(data_swir_ori).*(cos(deg2rad(slope_data)).*sun_zenith_cos+c)./(cos_sita+c); + clear data_swir_ori; + end +end diff --git a/DetectPotentialFalsePositivePixels.m b/DetectPotentialFalsePositivePixels.m new file mode 100644 index 0000000..e2cc5a8 --- /dev/null +++ b/DetectPotentialFalsePositivePixels.m @@ -0,0 +1,62 @@ +function pfpl = DetectPotentialFalsePositivePixels(mask, psnow,slope, ndbi,ndvi,bt,cdi,water,resolution) +%DETECTPOTENTIALFALSEPOSITIVEPIXELS +% remove the water pixels at the final layer. by Shi Mar.27, 2018. + %% urban + ndbi = EnhanceLine(ndbi); % enhance the ndbi to high urban/built-up area. + pfpl = (ndbi > 0)&(ndbi > ndvi)&mask&water==0;% urban + + %% remove cloudy pixels using ostu method for thermal band. + if sum(pfpl(:))>0 % have urban pixels + if ~isempty(bt) + % ostu's method to determine the temperature therhold to + % seperate clouds from urban/built-up area. + bt_pfpl = bt(pfpl==1); + t = graythresh(bt_pfpl); + % get the thershold + BW = imbinarize(bt_pfpl,t); + clear t; + cold_tmp = min(bt_pfpl(BW)); + clear bt_pfpl BW; + if ~isempty(cold_tmp) + clouds_tmp = bt < cold_tmp(1); + pfpl(clouds_tmp==1)=0; + end + clear bt cold_tmp +% figure; imshow(pfpl); + elseif ~isempty(cdi) +% pfpl(cdi < -1)=0; % by following David, 2018 RSE + pfpl(cdi < -0.8)=0; % by following David, 2018 RSE + end + end + + % remove the isolate urban/built-up pixels +% % pfpl = bwareaopen(pfpl,2); + + %% add poential snow/ice pixels in mountain. + if ~isempty(slope) + psnow_mountain = psnow==1& slope > 20; + % 20 is from Burbank D W, Leland J, Fielding E, et al. Bedrock incision, rock uplift and threshold hillslopes in the northwestern Himalayas[J]. Nature, 1996, 379(6565): 505. + pfpl = pfpl|psnow_mountain; + end + + %% buffer urban pixels with 1 kilometer window. + witdh=250; + width_pixels=fix(witdh/resolution);% 1 km 33 Landsat pixel 500 meters 17 Landsat pixels. 200 meters 7 pixels. + SEw=strel('square',2*width_pixels+1); + pfpl=imdilate(pfpl,SEw); + + %% must over land this will be runned in commission process function. + % some coastline may be classified as water, that is easily detected as + % cloud when it is grouped into water layer. +% pfpl = pfpl&mask&water==0; % remove the water pixels at the final. also remove the absolute cloud pixels. + +% % % dilate user-defined buffers for erosion more commission errors. +% % if pfbpix > 0 +% % SEw=strel('square',2*pfbpix+1); +% % pfpl=imdilate(pfpl,SEw); +% % end + + pfpl = pfpl|psnow;%% add the snow/ice pixels located in normal areas. + + pfpl = pfpl&mask; % remove the water pixels at the final. also remove the absolute cloud pixels. +end \ No newline at end of file diff --git a/DetectPotentialPixels.m b/DetectPotentialPixels.m new file mode 100644 index 0000000..b40db8b --- /dev/null +++ b/DetectPotentialPixels.m @@ -0,0 +1,56 @@ +function [idplcd,BandCirrusNormal,whiteness,HOT] = DetectPotentialPixels(mask,data_toabt,dem,ndvi,ndsi,satu_Bv) +% DETECTPOTENTIALCLOUD detect potential cloud pixels (PCPs) + + % Cirrus Probability This is unavailable here because some high + % mountianus have high cirrus values. + + % inputs: BandSWIR2 BandBT BandBlue BandGreen BandRed BandNIR BandSWIR1 + % BandCirrus + %% Step 1: detect possible cloud pixels (PCPs) + + % [Basic cloud test] + idplcd=ndsi<0.8&ndvi<0.8&data_toabt.BandSWIR2>300; + clear ndsi ndvi; data_toabt.BandSWIR2 = []; % memory. + % when have BT data. + if ~isempty(data_toabt.BandBT) + idplcd=idplcd==true&data_toabt.BandBT<2700; + data_toabt.BandBT = []; + end + + % [Whiteness test] + % visible bands flatness (sum(abs)/mean < 0.6 => brigt and dark cloud ) + visimean=(data_toabt.BandBlue+data_toabt.BandGreen+data_toabt.BandRed)/3; + whiteness=(abs(data_toabt.BandBlue-visimean)+abs(data_toabt.BandGreen-visimean)+... + abs(data_toabt.BandRed-visimean))./visimean; + data_toabt.BandGreen = []; + clear visimean; + % update idplcd + whiteness(satu_Bv==1)=0;% If one visible is saturated whiteness == 0 + idplcd=idplcd==true&whiteness<0.7; + + % [Haze test] + HOT=data_toabt.BandBlue-0.5.*data_toabt.BandRed-800;% Haze test + data_toabt.BandBlue = []; + data_toabt.BandRed = []; + + idplcd=idplcd==true&(HOT>0|satu_Bv==1); + clear satu_Bv; % need to find thick warm cloud + + % [Ratio4/5>0.75 cloud test] + Ratio4_5=data_toabt.BandNIR./data_toabt.BandSWIR1; + data_toabt.BandNIR = []; + data_toabt.BandSWIR1 = []; + idplcd=idplcd==true&Ratio4_5>0.75; + clear Ratio4_5; + + BandCirrusNormal=[]; + % normalize Cirrus band [Cirrus test] from Landsat 8 and Sentinel 2 images + if ~isempty(data_toabt.BandCirrus) + BandCirrusNormal=NormalizaCirrusDEM( mask, idplcd, data_toabt.BandCirrus, dem ); +% BandCirrusNormal= data_toabt.BandCirrus; + clear data_toabt mask dem; + idplcd=idplcd==true|BandCirrusNormal > 100; % When TOA at Cirrus band is more than 0.01, it may be cloudy. + end + +end + diff --git a/DetectSnow.m b/DetectSnow.m new file mode 100644 index 0000000..b107891 --- /dev/null +++ b/DetectSnow.m @@ -0,0 +1,13 @@ +function snow = DetectSnow(dim,band_green,band_nir,band_bt,ndsi) +%DETECSNOW Summary of this function goes here +% Detailed explanation goes here + +% % snow=zeros(dim,'uint8'); % Snow mask + % It takes every snow pixels including snow pixel under thin clouds or icy clouds + snow=ndsi>0.15&band_nir>1100&band_green>1000; + if ~isempty(band_bt) + snow=snow&band_bt<1000; + end +% snow(ids_snow)=1; +end + diff --git a/DetectWater.m b/DetectWater.m new file mode 100644 index 0000000..d9085bc --- /dev/null +++ b/DetectWater.m @@ -0,0 +1,76 @@ +function water = DetectWater( dim, mask, nir, NDVI, psnow, slope, gswater) +%DETECTWATER Detect water by combining spectral-derived water and +%GSWO-derived water togeter. +% +% Syntax +% +% water = DetectWater( dim, mask, nir, NDVI, psnow, slope, gswater) +% +% Description +% +% History: +% 1. Create this function. (1. January, 2018) +% 2. The sepctral-derived water may be incorrect, resulting in a 100% +% absolutely wrong low level GSWO (equal to 0). The GWSO will be used +% only when the low level GSWO is larger than 0. (9. March, 2018) +% 3. Remove the coastline because of its frequent changes. (6. May, 2018) +% +% +% Input arguments +% +% dim Dim for data. +% mask Mask for observations. +% nir NIR. +% NDVI NDVI. +% psnow Potential snow. +% slope Slope. +% gswater GSWO. +% +% Output arguments +% +% water Water map. +% +% +% Author: Shi Qiu (shi.qiu@uconn.edu) +% Date: 9. March, 2018 + + water=zeros(dim,'uint8'); % Water msk + %% Zhe's water test (works over thin cloud) + water((NDVI<0.01&nir<1100)|(NDVI<0.1&NDVI>0&nir<500))=1; + clear resolution; + + % within observation. + water(~mask)=0; + + %% the GSWO data to enhance water map. + if sum(water(:))>0&&~isempty(gswater) + if sum(gswater(:))>0 % have water occurences. + % assume the water occurances are same in each whole scene. + % global surface water occurance (GSWO) + % low level to exclude the commssion errors as water. + % 5% tolerances. + gswater_occur=prctile(gswater(water==1),17.5)-5; + + if gswater_occur>0 % must be more than 0. + water_gs = gswater>gswater_occur; + clear gswater gswater_occur; + % sometimes ice may be over water. Snow covered sea ice is determined + % using the potential snow/ice. + water_gs(psnow == 1)=0; % remove out ice water. + clear psnow; + + water(water_gs==1)=1;% extend water regions based on GSWO. + % equal to the theshold because it may be 100%. + + % water(psnow)=0; % remove out ice water. I think this snow + % cannot be removed because there are sometimes ice clouds over + % water. + water(~mask)=0; + end + end + % note that 255 indicates no data in GSWO, that is ocean pixels or + % permenate snow/ice pixels (which can be identified as land pixels). + end + +end + diff --git a/EnhanceLine.m b/EnhanceLine.m new file mode 100644 index 0000000..e90b342 --- /dev/null +++ b/EnhanceLine.m @@ -0,0 +1,85 @@ +function line_enhanced = EnhanceLine(band) +%ENHANCELINE enhance NDBI to light urban/built-up areas and dark other +%bright surface, such as desert, rock. ref Guindon et. RSE 2004 + +% also can see the details at +% https://homepages.inf.ed.ac.uk/rbf/HIPR2/linedet.htm. +% band=data_toabt.BandGreen; +% +% band=ndbi; + +%% line with a length of three pixels +% template1=[-1 1 0; +% -1 1 0; +% -1 1 0;]; +% template2=[0 1 -1; +% 0 1 -1; +% 0 1 -1;]; +% line_enhanced1 = imfilter(band,template1); +% line_enhanced2 = imfilter(band,template2); +% line_enhanced=max(line_enhanced1,line_enhanced2); +% +% template1=[-1 -1 -1; +% 1 1 1; +% 0 0 0;]; +% template2=[0 0 0; +% 1 1 1; +% -1 -1 -1;]; +% line_enhanced1 = imfilter(band,template1); +% line_enhanced2 = imfilter(band,template2); +% line_enhanced=max(line_enhanced1,line_enhanced); +% line_enhanced=max(line_enhanced2,line_enhanced); +% +% template1=[1 -1 -1; +% 0 1 -1; +% 0 0 1;]; +% template2=[1 0 0; +% -1 1 0; +% -1 -1 1;]; +% line_enhanced1 = imfilter(band,template1); +% line_enhanced2 = imfilter(band,template2); +% line_enhanced=max(line_enhanced1,line_enhanced); +% line_enhanced=max(line_enhanced2,line_enhanced); +% +% template1=[-1 -1 1; +% -1 1 0; +% 1 0 0;]; +% template2=[0 0 1; +% 0 1 -1; +% 1 -1 -1;]; +% +% line_enhanced1 = imfilter(band,template1); +% line_enhanced2 = imfilter(band,template2); +% line_enhanced=max(line_enhanced1,line_enhanced); +% line_enhanced=max(line_enhanced2,line_enhanced); +% line_enhanced = line_enhanced./3; + + + template=[-1 2 -1; + -1 2 -1; + -1 2 -1;]; + template = template./6; + line_enhanced = imfilter(band,template); + + template=[-1 -1 -1; + 2 2 2; + -1 -1 -1;]; + template = template./6; + line_enhanced_new = imfilter(band,template); + line_enhanced=max(line_enhanced_new,line_enhanced); + + template =[2 -1 -1; + -1 2 -1; + -1 -1 2;]; + template = template./6; + line_enhanced_new = imfilter(band,template); + line_enhanced=max(line_enhanced_new,line_enhanced); + + template =[-1 -1 2; + -1 2 -1; + 2 -1 -1;]; + template = template./6; + line_enhanced_new = imfilter(band,template); + line_enhanced=max(line_enhanced_new,line_enhanced); +end + diff --git a/ErodeCommissons.m b/ErodeCommissons.m new file mode 100644 index 0000000..cf67aa6 --- /dev/null +++ b/ErodeCommissons.m @@ -0,0 +1,105 @@ + +% output: cloud 1:cloud; 2: potential cloud can be eroded, which will be +% identified using cloud shadow match method. +function cloud = ErodeCommissons(data_meta,pcloud,pfpl,water,cdi,erdpix) +%REMOVECOMMISSONS remove most of commission errors from bright and low +%tempareture features. +% +% use the optimal erosion size for Landsat and Sentinel 2 images. Shi +% 4/21/2018. +% remove the cloud objects with less than 3 pixels after eroding 1 pixel. +% Shi 4/10/2018 + % pcloud potential cloud layer + % pccl potential commissions as cloud layer + + % 2-by-2 pixels window for Landsat 8 + % 3-by-3 pixels window for Landsats 4-7 and Sentinel 2 + cipix = erdpix; + + cloud = zeros(data_meta.Dim,'uint8'); % the clouds needed to be eroded. + cloud(pcloud>0)=1; + clear pcloud; + + + %% erode and dilate back. +% CEs = strel('square',2*cipix+1); + CEs = strel('disk',cipix); + % erode to remove the potential false pixels + cloud_eroded = imerode(cloud,CEs); + clear CEs; + pixels_eroded = ~cloud_eroded & pfpl; + clear cloud_eroded; + % only remove the potential false positive pixels + cloud_eroded = cloud; + cloud_eroded(pixels_eroded) = 0; + clear pixels_eroded; + + %% dilate back to orginal cloud shape of which size is dual to the erosion. + CEs = strel('disk',2*cipix); + cloud_dilated = imdilate(cloud_eroded,CEs); + + %% remover the clouds gone forever. + % Segmentate each cloud to remove the small objs. + cloud_objs=bwlabeln(cloud,8); + clouds_remaining = cloud_objs; + clouds_remaining(~cloud_eroded)=0; % remove the clouds gone. + clear cloud_eroded; + idx_clouds_remaining = unique(clouds_remaining(:)); + clear clouds_remaining; + + cloud_remaining = zeros(data_meta.Dim,'uint8'); % the clouds needed to be eroded. + if ~isempty(idx_clouds_remaining) + if idx_clouds_remaining(1)==0 + idx_clouds_remaining(1) = []; + end + if ~isempty(idx_clouds_remaining) + cloud_remaining = ismember(cloud_objs,idx_clouds_remaining); + end + end + clear cloud_objs idx_clouds_remaining; + + % only for land + + clear cloud_eroded; + cloud = (cloud_dilated&cloud_remaining)|(water&cloud); % add clouds over water. + clear cloud_dilate cipix CEs water; + + %% remove small object with minum CDI < 0.5 only for Sentinel 2 + if ~isempty(cdi) + % exclude small objects of which minimum CDI is still larger than + % -0.5. + % get small clouds + large_obj = bwareaopen(cloud,10000); + small_obj = cloud==1&large_obj==0; + clear large_obj; + % segment small clouds + small_obj_init=bwlabeln(small_obj,8); + + % produce all non confident cloud pixels using CDI + confident_cloud = cdi < -0.5; + + clear cdi; + + % remove the non confident cloud pixels again + small_obj_exd_urban = small_obj_init; + small_obj_exd_urban(confident_cloud==0) = 0; + clear confident_cloud; + + % a true cloud can be determined when any confident pixels are + % remaining. + idx = unique(small_obj_exd_urban); + clear small_obj_exd_urban; + + true_cloud = ismember(small_obj_init,idx); + clear small_obj_init idx; + + % remove bright surfaces + bright_surfaces = true_cloud==0&small_obj==1; + clear small_obj true_cloud; + cloud(bright_surfaces) =0; + end + + %% remove very small object. + cloud = bwareaopen(cloud,3); + +end \ No newline at end of file diff --git a/FmaskParameters.m b/FmaskParameters.m new file mode 100644 index 0000000..0e66d55 --- /dev/null +++ b/FmaskParameters.m @@ -0,0 +1,45 @@ +classdef FmaskParameters + %FMASKPARAMETERS Save all fmask parameters here. + + properties + CloudBuffer % pixels + CloudShadowBuffer % pixels + SnowBuffer % pixels + ThinWeight + CloudProbabilityThershold + PFPCErosionRadius % meters + OutputResolution % meters + end + + methods + function obj = FmaskParameters(sensor) + %FMASKPARAMETERS Construct an instance of this class according + %to input image. + + % public and constant paras. + obj.CloudBuffer=3; + obj.CloudShadowBuffer=3; + obj.SnowBuffer=0; + + % different paras for different sensors. + switch sensor + case 'S_MSI' + obj.ThinWeight=0.5; + obj.CloudProbabilityThershold=20.00; + obj.OutputResolution=20; + obj.PFPCErosionRadius=90;% mirrored from Landsat 8. + case 'L_OLI_TIRS' + obj.ThinWeight=0.3; + obj.CloudProbabilityThershold=17.50; + obj.OutputResolution=30; + obj.PFPCErosionRadius=90; + case {'L_TM','L_ETM_PLUS'} + obj.ThinWeight=0.0; + obj.CloudProbabilityThershold=10.00; + obj.OutputResolution=30; + obj.PFPCErosionRadius=150; + end + end + end +end + diff --git a/GRIDobj.m b/GRIDobj.m new file mode 100644 index 0000000..c296347 --- /dev/null +++ b/GRIDobj.m @@ -0,0 +1,437 @@ +classdef GRIDobj + +%GRIDobj Create instance of a GRIDobj +% +% Syntax +% +% DEM = GRIDobj(X,Y,dem) +% DEM = GRIDobj('ESRIasciiGrid.txt') +% DEM = GRIDobj('GeoTiff.tif') +% DEM = GRIDobj(); +% DEM = GRIDobj([]); +% DEM = GRIDobj(FLOWobj or GRIDobj or STREAMobj,class) +% +% +% Description +% +% GRIDobj creates an instance of the grid class, which contains a +% numerical or logical matrix and information on georeferencing. When a +% GRIDobj is created from a file, the number format of the data in +% GRIDobj is either single or double. Unsigned and signed integers are +% converted to single. For unsigned integers, missing values are +% assumed to be denoted as intmax(class(input)). For signed integers, +% missing values are assumed to be intmin(class(input)). Please check, +% that missing values in your data have been identified correctly +% before further analysis. +% +% Note that while throughout this help text GRIDobj is associated with +% gridded digital elevation models, instances of GRIDobj can contain +% other gridded, single band, datasets such as flow accumulation grids, +% gradient grids etc. +% +% DEM = GRIDobj(X,Y,dem) creates a DEM object from the coordnate +% matrices or vectors X and Y and the matrix dem. The elements of dem +% refer to the elevation of each pixel. +% +% DEM = GRIDobj('ESRIasciiGrid.txt') creates a DEM object from an ESRI +% Ascii grid exported from other GI systems. +% +% DEM = GRIDobj('GeoTiff.tif') creates a DEM object from a Geotiff. +% +% DEM = GRIDobj() opens a dialog box to read either an ESRI Ascii Grid +% or a Geotiff. +% +% DEM = GRIDobj([]) creates an empty instance of GRIDobj +% +% DEM = GRIDobj(FLOWobj or GRIDobj or STREAMobj,class) creates an +% instance of GRIDobj with all common properties (e.g., spatial +% referencing) inherited from another instance of a FLOWobj, GRIDobj +% or STREAMobj class. DEM.Z is set to all zeros where class can be +% integer classes or double or single. By default, class is double. +% +% Example +% +% % Load DEM +% DEM = GRIDobj('srtm_bigtujunga30m_utm11.tif'); +% % Display DEM +% imageschs(DEM) +% +% See also: FLOWobj, STREAMobj, GRIDobj/info +% +% Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) +% Date: 17. August, 2017 + + + properties + %Public properties + +%Z matrix with elevation values +% The Z property contains the elevation values in a 2D matrix. +% +% See also GRIDobj + Z + +%CELLSIZE cellsize of the grid (scalar) +% The cellsize property specifies the spacing of the grid in x and y +% directions. Note that TopoToolbox requires grids to have square cells, +% e.g., dx and dy are the same. +% +% See also GRIDobj + cellsize + +%REFMAT 3-by-2 affine transformation matrix +% The refmat property specifies a 3-by-2 affine transformation matrix as +% used by the mapping toolbox. +% +% See also GRIDobj, makerefmat + refmat + +%SIZE size of the grid (two element vector) +% The cellsize property is a two element vector that contains the number +% of rows and columns of the Z matrix. +% +% See also GRIDobj, size + size + +%NAME optional name (string) +% The name property allows to specify a name of the grid. By default and +% if the constructor is called with a filename, the name property is set +% to the name of the file. +% +% See also GRIDobj + name + +%ZUNIT unit of grid values (string) +% The zunit is optional and is used to store the physical unit (e.g. m) +% of an instance of GRIDobj. This property is currently not fully +% supported and TopoToolbox functions usually assume that the unit is in +% meters and equals the xyunit property. +% +% See also GRIDobj + zunit + +%XYUNIT unit of the coordinates (string) +% The xyunit is optional and is used to store the physical unit (e.g. m) +% of the coordinates. This property is currently not fully +% supported and TopoToolbox functions usually assume that the unit is in +% meters and equals the zunit property. +% +% See also GRIDobj + xyunit + +%GEOREF additional information on spatial referencing (structure array) +% The georef property stores an instance of map.rasterref.MapCellsReference, +% a structure array GeoKeyDirectoryTag, and a mapping structure +% (mstruct). +% +% See also GRIDobj, geotiffinfo + georef + + end + + methods + function DEM = GRIDobj(varargin) + % GRIDobj constructor + + if nargin == 3 + %% GRIDobj is created from three matrices + % GRIDobj(X,Y,dem) + X = varargin{1}; + Y = varargin{2}; + + if min(size(X)) > 1 + X = X(1,:); + end + if min(size(Y)) > 1 + Y = Y(:,1); + end + + DEM.Z = varargin{3}; + DEM.size = size(DEM.Z); + + if numel(X) ~= DEM.size(2) || numel(Y) ~= DEM.size(1) + error('TopoToolbox:GRIDobj',... + ['Coordinate matrices/vectors don''t fit the size of the \n'... + 'the grid']); + end + + if (Y(2)-Y(1)) > 0 + % the y coordinate vector must be monotonically + % decreasing, so that the left upper edge of the DEM is + % north-west (on the northern hemisphere). + DEM.Z = flipud(DEM.Z); + Y = Y(end:-1:1); + end + + dy = Y(2)-Y(1); + dx = X(2)-X(1); + + if abs(abs(dx)-abs(dy))>1e-9 + error('TopoToolbox:GRIDobj',... + 'The resolution in x- and y-direction must be the same'); + end + + + DEM.refmat = double([0 dy;... + dx 0;... + X(1)-dx Y(1)-dy]); + + DEM.cellsize = dx; + DEM.georef = []; + DEM.name = []; + + + elseif nargin <= 2 + + + if nargin == 0 + %% No input arguments. File dialog box will open and ask + % for a txt or tiff file as input + FilterSpec = {'*.txt;*.asc;*.tif;*.tiff','supported file types (*.txt,*.asc,*.tif,*.tiff)';... + '*.txt', 'ESRI ASCII grid (*.txt)';... + '*.asc', 'ESRI ASCII grid (*.asc)';... + '*.tif', 'GeoTiff (*.tif)';... + '*.tiff', 'GeoTiff (*.tiff)';... + '*.*', 'all files (*.*)'}; + + DialogTitle = 'Select ESRI ASCII grid or GeoTiff'; + [FileName,PathName] = uigetfile(FilterSpec,DialogTitle); + + if FileName == 0 + error('TopoToolbox:incorrectinput',... + 'no file was selected') + end + + filename = fullfile(PathName, FileName); + + elseif nargin > 0 + % One input argument + if isempty(varargin{1}) + % if empty array than return empty GRIDobj + return + elseif isa(varargin{1},'GRIDobj') || ... + isa(varargin{1},'FLOWobj') || ... + isa(varargin{1},'STREAMobj') + % empty GRIDobj + DEM = GRIDobj([]); + % find common properties of F and G and from F to G + pg = properties(DEM); + pf = properties(varargin{1}); + p = intersect(pg,pf); + for r = 1:numel(p) + DEM.(p{r}) = varargin{1}.(p{r}); + end + if nargin == 1 + cl = 'double'; + else + cl = varargin{2}; + end + + if strcmp(cl,'logical') + DEM.Z = false(DEM.size); + else + DEM.Z = zeros(DEM.size,cl); + end + DEM.name = ''; + + return + end + + % GRIDobj is created from a file + filename = varargin{1}; + end + + % check if file exists + if exist(filename,'file')~=2 + error('File doesn''t exist') + end + + + % separate filename into path, name and extension + [pathstr,DEM.name,ext] = fileparts(filename); + + + if any(strcmpi(ext,{'.tif', '.tiff'})) + % it is a GeoTiff + try + % try to read using geotiffread (requires mapping + % toolbox) + [DEM.Z, DEM.refmat, ~] = geotiffread(filename); + gtiffinfo = geotiffinfo(filename); + DEM.georef.SpatialRef = gtiffinfo.SpatialRef; + DEM.georef.GeoKeyDirectoryTag = gtiffinfo.GeoTIFFTags.GeoKeyDirectoryTag; + georef_enabled = true; + + catch ME + + % mapping toolbox is not available. Will try to + % read the tif file together with the tfw file + georef_enabled = false; + + % the tfw file has the same filename but a tfw + % extension + tfwfile = fullfile(pathstr,[DEM.name '.tfw']); + + % check whether file exists. If it exists then read + % it using worldfileread or own function + tfwfile_exists = exist(tfwfile,'file'); + if tfwfile_exists + try + % prefer builtin worldfileread, if + % available + DEM.refmat = worldfileread(tfwfile); + catch ME + W = dlmread(tfwfile); + + DEM.refmat(2,1) = W(1,1); + DEM.refmat(1,2) = W(4,1); + DEM.refmat(3,1) = W(5,1)-W(1); + DEM.refmat(3,2) = W(6,1)-W(4); + end + + DEM.Z = imread(filename); + else + if ~tfwfile_exists + error('TopoToolbox:GRIDobj:read',... + 'GRIDobj cannot read the TIF-file because it does not have a tfw-file.'); + else + throw(ME) + end + + end + end + + + % Unless any error occurred, we now attempt to generate + % an mapping projection structure. This will not work + % if the DEM is in a geographic coordinate system or if + % the projection is not supported by mstruct. + if georef_enabled + try + DEM.georef.mstruct = geotiff2mstruct(gtiffinfo); + catch + DEM.georef.mstruct = []; + warning('TopoToolbox:GRIDobj:projection',... + ['GRIDobj cannot derive a map projection structure. This is either\n' ... + 'because the grid is in a geographic coordinate system or because\n' ... + 'geotiff2mstruct cannot identify the projected coordinate system used.\n' ... + 'TopoToolbox assumes that horizontal and vertical units of DEMs are \n'... + 'the same. It is recommended to use a projected coordinate system,\n' ... + 'preferably UTM WGS84. Use the function GRIDobj/reproject2utm\n' ... + 'to reproject your grid.']) + end + end + + % Finally, check whether no_data tag is available. This tag is + % not accessible using geotiffinfo (nice hack by Simon + % Riedl) + tiffinfo = imfinfo(filename); + if isfield(tiffinfo,'GDAL_NODATA') + nodata_val = str2double(tiffinfo.GDAL_NODATA); + end + + else + [DEM.Z,R] = rasterread(filename); + DEM.refmat = R; + DEM.georef = []; + end + + DEM.size = size(DEM.Z); + DEM.cellsize = abs(DEM.refmat(2)); + + % remove nans + demclass = class(DEM.Z); + nodata_val_exists = exist('nodata_val','var'); + + switch demclass + case {'uint8','uint16','uint32'} + % unsigned integer + DEM.Z = single(DEM.Z); + + if nodata_val_exists + nodata_val = single(nodata_val); + DEM.Z(DEM.Z == nodata_val) = nan; + else + DEM.Z(DEM.Z==intmax(demclass)) = nan; + end + + case {'int8','int16','int32'} + % signed integer + DEM.Z = single(DEM.Z); + if nodata_val_exists + nodata_val = single(nodata_val); + DEM.Z(DEM.Z == nodata_val) = nan; + else + DEM.Z(DEM.Z==intmin(demclass)) = nan; + end + + case {'double','single'} + if nodata_val_exists + DEM.Z(DEM.Z == cast(nodata_val,class(DEM.Z))) = nan; + end + case 'logical' + otherwise + error('TopoToolbox:GRIDobj','unrecognized class') + end + + + end + end + + end + +end + + + + +% Subfunction for ASCII GRID import +function [Z,refmat] = rasterread(file) + +fid=fopen(file,'r'); +% loop through header + +header = struct('ncols',[],... + 'nrows',[],... + 'xllcorner',[],... + 'yllcorner',[],... + 'cellsize',[],... + 'nodata',[]); + +names = fieldnames(header); +nrnames = numel(names); + +try + fseek(fid,0,'bof'); + for r = 1:nrnames ; + headertext = fgetl(fid); + [headertext, headernum] = strtok(headertext,' '); + I = cellfun(@(x,y) strcmpi(x(1:4),y(1:4)),names,repmat({headertext},nrnames,1)); + header.(names{I}) = str2double(headernum); + end +catch ME1 + error('header can not be read') +end + + +% read raster data +Z = fscanf(fid,'%lg',[header.ncols header.nrows]); +fclose(fid); +Z(Z==header.nodata) = NaN; +Z = Z'; +% create X and Y using meshgrid +refmat = [0 -header.cellsize;... + header.cellsize 0;... + header.xllcorner+(0.5*header.cellsize) - header.cellsize ... + (header.yllcorner+(0.5*header.cellsize))+((header.nrows)*header.cellsize)]; + +end + + + + + + + + + \ No newline at end of file diff --git a/GRIDobj2geotiff.m b/GRIDobj2geotiff.m new file mode 100644 index 0000000..0087e99 --- /dev/null +++ b/GRIDobj2geotiff.m @@ -0,0 +1,120 @@ +function GRIDobj2geotiff(A,file) + +%GRIDobj2geotiff Exports an instance of GRIDobj to a geotiff file +% +% Syntax +% +% GRIDobj2geotiff(DEM) +% GRIDobj2geotiff(DEM,filename) +% +% Description +% +% GeoTIFF is a common image file format that stores coordinates and +% projection information to be read by most GIS software. +% GRIDobj2geotiff writes an instance of GRIDobj to a GeoTIFF file. +% +% GRIDobj2geotiff requires the function geotiffwrite available with +% the Mapping Toolbox. If geotiffwrite does not exist on the search +% path, the function will write a standard tif together with a +% '.tfw'-file (worldfile, http://en.wikipedia.org/wiki/World_file ) to +% the disk. +% +% GRIDobj2geotiff(DEM) opens a dialogue box to save the GeoTIFF +% +% GRIDobj2geotiff(DEM,filename) saves the DEM to the specified +% filename +% +% Input arguments +% +% DEM instance of GRIDobj +% filename absolute or relative path and filename +% +% See also: GRIDobj +% +% Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) +% Date: 17. August, 2017 + +narginchk(1,2) + +% if only 1 argument, open file dialog box +if nargin == 1 + [FileName,PathName] = uiputfile({'*.tif'}); + if FileName == 0 + disp(' no output written to disk') + return + end + file = [PathName FileName]; +end + +% try to use geotiffwrite, which comes with the Mapping Toolbox +try + if isempty(A.georef); + geotiffwrite(file,A.Z,A.refmat); + else + geotiffwrite(file,A.Z,A.georef.SpatialRef,... + 'GeoKeyDirectoryTag',A.georef.GeoKeyDirectoryTag); + end +catch ME + warning('TopoToolbox:GRIDobj',... + ['GRIDobj2geotiff is unable to write a geotiff. Either you don''t \n'... + 'have the mapping toolbox, or there was another issue with geotiffwrite. \n'... + 'GRIDobj2geotiff instead writes a tif-image together with a world \n'... + 'file (*.tfw) which contains data on spatial referencing of the \n' ... + 'image, yet which lacks information on the type of projection used.']); + + + % if geotiffwrite is not available or any other error occurs + % a tif file will be written to the disk together with a worldfile + % .tfw-file. + [pathstr, name, ~] = fileparts(file); + k = refmat2worldfile(A.refmat); + dlmwrite(fullfile(pathstr,[name '.tfw']),k,'precision', '%.10f'); + A = A.Z; + + + siz = size(A); + cla = class(A); + + switch cla; + case 'double' + BpS = 64; + TSF = Tiff.SampleFormat.IEEEFP; + case 'single' + BpS = 32; + TSF = Tiff.SampleFormat.IEEEFP; + otherwise + if islogical(A); + A = uint32(A); + cla = 'uint32'; + end + BpS = round(log2(double(intmax(cla)))); + TSF = Tiff.SampleFormat.UInt; + + end + + t = Tiff(file,'w'); + tagstruct.ImageLength = siz(1); + tagstruct.ImageWidth = siz(2); + tagstruct.BitsPerSample = BpS; + tagstruct.SampleFormat = TSF; + tagstruct.SamplesPerPixel = 1; + tagstruct.RowsPerStrip = 16; + tagstruct.PlanarConfiguration = Tiff.PlanarConfiguration.Chunky; + tagstruct.Software = 'MATLAB'; + tagstruct.Photometric = 0; + + t.setTag(tagstruct); + t.write(A); + t.close; +end + +end + +function k = refmat2worldfile(r) +% does not support rotation + +k(1,1) = r(2,1); +k(4,1) = r(1,2); +k(5,1) = r(3,1)+k(1); +k(6,1) = r(3,2)+k(4); +end \ No newline at end of file diff --git a/LoadAuxiData.m b/LoadAuxiData.m new file mode 100644 index 0000000..cb299de --- /dev/null +++ b/LoadAuxiData.m @@ -0,0 +1,535 @@ +function [dem_out,slope_out,aspect_out,water_occur] = LoadAuxiData(doc_path,fname,bbox,trgt,saveas_local,varargin) +%LOADAXUIDATA Read DEM, GSWO, slope, and aspect. +% +% Syntax +% +% [dem_out,slope_out,aspect_out,water_occur] = +% LoadAuxiData(doc_path,fname,bbox,trgt,saveas_local) +% +% Description +% +% Read DEM and GSWO, and calculate Slope and Aspect data. +% It will spend about 1 min to load, mosaic DEM and GSWO, and calculate +% Slope and Aspect. If users use the online DEM, the time will depend +% on the Internet speed. +% Create a chache folder at scene directory, that will be affected by +% each others (especailly in parelel) +% User can define a DEM path. (by Shi. Jun 14, 2018) +% +% Input arguments +% +% doc_path Path of images. +% Landsat: the directory where you save the Landsat scene. +% Sentinel 2: the directory reaching to +% '~/S2*/GRANULE/S2*/IMG_DATA/'. +% fname Image name. +% bbox Boundary of image. [north, south, west, east] +% trgt The GRIDobj used as a targart, which is useful when +% projecting and mosaicing the auxi data. +% saveas_local Locally save as the auxi data or not. +% true: saveas false:do not saveas. +% +% Output arguments +% +% dem_out DEM responding to the scene. +% slope_out Slope, which will be generated if unvailable. +% aspect_out Aspect, which will be generated if unvailable. +% water_occur GSWO responding to the scene. +% +% +% +% Author: Shi Qiu (shi.qiu@ttu.edu) +% Date: 22. September, 2017 + + + % instant paras +% dem_path=strcat(pwd,'/AuxiData/GTOPO3010degZIP/'); % cliped by each +% 10 degrees -by- 10 degrees. + p = inputParser; + p.FunctionName = 'AuxiParameters'; + addParameter(p,'parallel',0); + addParameter(p,'userdem',''); + % request user's input + parse(p,varargin{:}); + isParallel=p.Results.parallel; + + userdem = p.Results.userdem; % dem path + + if isdeployed % Stand-alone mode. +% m_path = ctfroot; + if ismac||isunix + % Code to run on Mac plaform + % Code to run on Linux plaform + [~, result] = system('echo $PATH'); + m_path = char(regexpi(result, '(.*?):', 'tokens', 'once')); + elseif ispc + % Code to run on PC plaform + [~, result] = system('path'); + m_path = char(regexpi(result, 'Path=(.*?);', 'tokens', 'once')); + clear result; + end + else % MATLAB mode. + m_path=mfilename('fullpath'); + m_path(end-12:end)=[]; % remove the '/LoadAuxiData' + end + water_path=fullfile(m_path,'AuxiData','GSWO150ZIP'); + dem_path=fullfile(m_path,'AuxiData','GTOPO30ZIP'); + clear m_path; + +% water_path=fullfile('.','AuxiData','GSWO150ZIP'); +% dem_path=fullfile('.','AuxiData','GTOPO30ZIP'); +% cache_path=fullfile(m_path,'Cache'); + cache_path=fullfile(doc_path,'Cache'); + + % see the Cache is available + if ~exist(cache_path,'dir') + %have no the directory, create it here. + mkdir(cache_path); +% % cannot make it, give the results here directly. +% if ~status_mkdir +% cache_path = doc_path; +% end + end + + % online + + %% DEM + % Step 1. Read DEM data at the directory of scene. + % filename_dem=dir([doc_path,fname,'_DEM.tif']); + % userdem + dem_gridobj=[]; + if ~isempty(userdem)&&~isempty(trgt) + % read user's DEM + userdem_gridobj = GRIDobj(userdem); + % resample to same extent. + dem_gridobj = reproject2utm(userdem_gridobj,trgt); + clear userdem_gridobj userdem; + end + % local _DEM.tif + if isempty (dem_gridobj) + filename_dem=dir(fullfile(doc_path,'L*_DEM.tif')); % upper + if ~isempty(filename_dem) + dem_gridobj = GRIDobj(fullfile(doc_path,filename_dem(1).name)); + else + filename_dem=dir(fullfile(doc_path,'L*_dem.tif')); % lower + if ~isempty(filename_dem) + dem_gridobj = GRIDobj(fullfile(doc_path,filename_dem(1).name)); + else + filename_dem=dir(fullfile(doc_path,'S*_DEM.tif')); % upper + if ~isempty(filename_dem) + dem_gridobj = GRIDobj(fullfile(doc_path,filename_dem(1).name)); + else + filename_dem=dir(fullfile(doc_path,'S*_dem.tif')); % lower + if ~isempty(filename_dem) + dem_gridobj = GRIDobj(fullfile(doc_path,filename_dem(1).name)); + else + dem_gridobj=[]; + end + end + end + end + end +% dem_gridobj=[]; + clear filename_dem; + % Step 2. Locally load GTOPO30 DEM (1km) or Download SRTM DEM data according to users' requirements. + if isempty (dem_gridobj)&&~isempty(trgt) + islocal=true; + if islocal + dem_data=MosaicLocalDEM(dem_path,cache_path,trgt,bbox); +% dem_data = MosaicLocalAuxiData('DEM',dem_path,trgt,bbox); + if ~isempty(dem_data)&&sum(sum(dem_data(:)))>0 % all 0 will be empty. + % sometimes there may be over water totally. + dem_gridobj = GRIDobj(trgt); + dem_gridobj.name=[fname,'_DEM']; + dem_gridobj.Z = dem_data; + +% tic +% while sum(sum(isnan(dem_gridobj.Z )))>0 +% dem_gridobj = inpaintnans(dem_gridobj,'fill');% imfill nan +% end +% toc + if saveas_local + GRIDobj2geotiff(dem_gridobj,fullfile(doc_path,[dem_gridobj.name,'.tif'])); + end +% fprintf('GTOPO30 (1km DEM), '); + else +% fprintf('non-DEM, '); + end + clear dem_data; +% fprintf('Local GTOPO30 (1km DEM) will be used.\n'); + else + demtype = 'SRTMGL3'; + dem_gridobj = LoadOnlineDEM(fname,demtype,bbox,trgt); + if ~isempty(dem_gridobj) + if saveas_local + dem_gridobj.name=[fname,'_DEM']; + GRIDobj2geotiff(dem_gridobj,fullfile(doc_path,[dem_gridobj.name,'.tif'])); + end +% fprintf('Online DEM (',demtype,'),\n'); + else +% fprintf('non-DEM (',demtype,'), '); + end + + end + elseif ~isempty (dem_gridobj) +% fprintf('DEM in the scene directory,\n'); +% fprintf('The DEM in the directory of scene will be used.\n'); + end + + if ~isempty(dem_gridobj) + dem_out=single(dem_gridobj.Z); + else + dem_out=[]; + end + + % Step 3. Calculate Slope. + slope_out = LoadAuxiItem(doc_path,fname,'SLOPE'); + if isempty (slope_out) + if ~isempty (dem_gridobj) + slope_gridobj=arcslope(dem_gridobj,'deg'); + slope_out=single(slope_gridobj.Z); + if saveas_local + slope_gridobj.name=[fname,'_SLOPE']; + GRIDobj2geotiff(slope_gridobj,fullfile(doc_path,[slope_gridobj.name,'.tif'])); + end + clear slope_gridobj; +% fprintf('SLOPE, '); +% clear slope_gridobj; + else +% fprintf('non-SLOPE, '); + end + else +% fprintf('SLOPE in the scene directory,\n'); + end + % Step 4. Calculate Aspect. + aspect_out = LoadAuxiItem(doc_path,fname,'ASPECT'); + if isempty (aspect_out) + if ~isempty (dem_gridobj) + aspect_gridobj = aspect(dem_gridobj); + aspect_out = single(aspect_gridobj.Z); + if saveas_local + aspect_gridobj.name=[fname,'_ASPECT']; + GRIDobj2geotiff(aspect_gridobj,fullfile(doc_path,[aspect_gridobj.name,'.tif'])); + end +% fprintf('ASPECT, '); + clear aspect_gridobj; + else +% fprintf('non-ASPECT, '); + end + else +% fprintf('ASPECT in the scene directory,\n'); + end + clear dem_gridobj; + + %% WATER + water_occur = LoadAuxiItem(doc_path,fname,'WATER'); + + if isempty (water_occur)&& ~isempty(trgt) + water_occur = MosaicLocalAuxiData('WATER',water_path,cache_path,trgt,bbox); + if ~isempty(water_occur) +% fprintf('and GSWO (150m water map).\n'); +% fprintf('Local GSWO (150m water map) will be used.\n'); + if saveas_local + wt_gridobj = GRIDobj(trgt); + wt_gridobj.name=[fname,'_WATER']; + wt_gridobj.Z = water_occur; + GRIDobj2geotiff(wt_gridobj,fullfile(doc_path,[wt_gridobj.name,'.tif'])); + clear wt_gridobj; + end + else +% fprintf('and non-GSWO.\n'); + end + else +% fprintf('and GSWO in the scene directory.\n'); + end + clear trgt; + if ~isempty (water_occur) + water_occur(water_occur==255)=100;% 255 is 100% ocean. + end + if ~isParallel + % remove the cache folder in the scene directory. + if exist(cache_path,'dir') + rmdir(cache_path,'s'); + end + end +end + + +function auxi_data = LoadAuxiItem(doc_path,fname,name_match) +% LoadAuxiItem read local auxi data first. + +% filename=dir([doc_path,'x*_',name_match,'.tif']); + filename=dir(fullfile(doc_path,[fname,'_',name_match,'.tif'])); + if ~isempty(filename) + auxi_data=imread(fullfile(doc_path,filename(1).name)); +% fprintf('Successfully load %s at %s.\n',name_match,fname); + else + auxi_data=[]; +% fprintf('Fail to locally find out %s at %s.\n',name_match,fname); + end +end + +function dem = LoadOnlineDEM(fname,demtype,bbox,trgt) + try +% fprintf('DEM is downloading.\n'); +% cache_path=fullfile(pwd,'Cache'); + tempfile_path=fullfile(cache_path,[fname, '_DEM.tif']); + dem_temp=Readopentopo('filename',tempfile_path,... + 'north',bbox(1),... + 'south',bbox(2),... + 'west',bbox(3),... + 'east',bbox(4),... + 'demtype',demtype,... + 'deletefile',true); + if ~isequal(dem_temp.size,trgt.size) % when DEM is NOT same as Landsat, CLIP it. + dem = reproject2utm(dem_temp,trgt,'method','nearest'); + else + dem = dem_temp; + end +% fprintf('DEM was successfully downloaded at %s.\n',fname); + catch + dem=[]; +% fprintf('DEM was unsuccessfully downloaded at %s.\n',fname); + end +end + +function auxi_data=MosaicLocalDEM(auxi_path,cache_path,target_gridobj,bbox) + latlim=[bbox(2) bbox(1)]; + lonlim=[bbox(3) bbox(4)]; + clear bbox; +% bbox=[north,south,west,east]; +% [southern_limit northern_limit] +% [western_limit eastern_limit] + + tileNames = gtopo30s(latlim,lonlim); + clear latlim lonlim; + if isempty(tileNames) % no tiles, return empty. + auxi_data=[]; % set empty layer. + return; + end +% auxi_data = zeros(target_gridobj.size,'double'); % dem mask + auxi_data = NaN(target_gridobj.size,'double'); % dem mask +% mask_dem = createmask(target_gridobj); + + for i=1:length(tileNames) + tile_name=['gt30',lower(char(tileNames(i)))]; + if ~isempty(tile_name) + % unzip files. + filename = fullfile(auxi_path,[tile_name,'.zip']); + if exist(filename,'file') + unzip(filename,cache_path); + filename = fullfile(cache_path,[tile_name,'.tif']); + % reproject to UTM same with target image. + auxi_data_part_tmp = GRIDobj(filename); + clear filename; + + % In the gtopo30 DEM, ocean areas have been masked as "no data" +% ids_nan=auxi_data_part_tmp.Z==-9999; +% auxi_data_part_tmp.Z(ids_nan)=0;% give 0 to ocean. + % further give 0 to nan. + auxi_data_part_tmp.Z=fillmissing(auxi_data_part_tmp.Z,'constant',0); + +% % % recontruct the refmat to 3-by-2 matrix because of the orginal RGB. +% % LON11=min(auxi_data_part_tmp.georef.SpatialRef.LongitudeLimits); +% % LAT11=max(auxi_data_part_tmp.georef.SpatialRef.LatitudeLimits); +% % % Convert to a geographic raster reference object: +% % DLON=auxi_data_part_tmp.cellsize; +% % DLAT=0-auxi_data_part_tmp.cellsize; +% % auxi_data_part_tmp.refmat = makerefmat(LON11, LAT11, DLON, DLAT); + auxi_data_tmp = reproject2utm(auxi_data_part_tmp,target_gridobj); + clear auxi_data_part_tmp; + auxi_data=max(auxi_data,auxi_data_tmp.Z); + clear auxi_data_tmp; +% % delete(filename); % delete the files + end + clear tile_name; + end + end + clear tileNames; + % if using more than 2 tiles, the mosaic method maybe result in some + % Nan values at lines. fast fill then. + if sum(sum(isnan(auxi_data)))>0 + % see along-row or along-col + [row_nan,col_nan]=find(isnan(auxi_data)); + row_diff=max(row_nan)-min(row_nan); + col_diff=max(col_nan)-min(col_nan); + % along row and col. + if row_diff>col_diff + auxi_data=fillmissing(auxi_data,'linear',2);% by row. + else + auxi_data=fillmissing(auxi_data,'linear',1);% by column. + end + % https://www.mathworks.com/help/matlab/ref/fillmissing.html?searchHighlight=fillmissing&s_tid=doc_srchtitle + end + % when have nan values in DEM,this will be ocean. all as 0. + auxi_data=fillmissing(auxi_data,'constant',0); +end + +function auxi_data = MosaicLocalAuxiData(auxi_type,auxi_path,cache_path,target_gridobj,bbox) + + switch auxi_type + case 'DEM' + pname_1th='gt30'; + case 'WATER' + pname_1th='occurrence'; + otherwise + error('The input of auxiliary type is incorrect, which should be ''DEM'' or ''WATER''.'); + end + + % target_image + % get the up-left cornner's name for water title + [tiles_used, ul_name, ur_name, ll_name, lr_name] = ConvertBBox2TileName(bbox,pname_1th); + clear bbox pname_1th; + if sum(tiles_used(:))==0 % no tiles, return empty. + auxi_data=[]; % set empty water layer. + return; + end + + auxi_data = zeros(target_gridobj.size,'double'); % water mask + % loop all water titles covering the Landsat or Sentinel image. + for i=1:4 + tile_name=''; + switch i + case 1 + if tiles_used(i) + tile_name=ul_name; + end + case 2 + if tiles_used(i) + tile_name=ur_name; + end + case 3 + if tiles_used(i) + tile_name=ll_name; + end + case 4 + if tiles_used(i) + tile_name=lr_name; + end + end + if ~isempty(tile_name) + % unzip files. + filename = fullfile(auxi_path,[tile_name,'.zip']); +% cache_path=fullfile(pwd,'Cache'); + if exist(filename,'file') + unzip(filename,cache_path); + filename = fullfile(cache_path,[tile_name,'.tif']); +% filename = [water_path,tile_name,'.tif']; + % reproject to UTM same with target image. + auxi_data_part_tmp = GRIDobj(filename); + clear filename; + % recontruct the refmat to 3-by-2 matrix because of the orginal RGB. + +% LON11=min(auxi_data_part_tmp.georef.SpatialRef.LongitudeLimits); +% LAT11=max(auxi_data_part_tmp.georef.SpatialRef.LatitudeLimits); +% % Convert to a geographic raster reference object: +% % rasterSize = auxi_data_part_tmp.size; +% DLON=auxi_data_part_tmp.cellsize; +% DLAT=0-auxi_data_part_tmp.cellsize; +% % cell_resol=10/rasterSize(1); +% % DLON=cell_resol; +% % DLAT=0-cell_resol; +% auxi_data_part_tmp.refmat = makerefmat(LON11, LAT11, DLON, DLAT); + + auxi_data_part_tmp.Z=fillmissing(auxi_data_part_tmp.Z,'constant',255);% 255 or No data indicates ocean area. +% auxi_data_part_tmp.Z(isnan(auxi_data_part_tmp.Z))=255;% 255 or No data indicates ocean area. + auxi_data_tmp = reproject2utm(auxi_data_part_tmp,target_gridobj); + clear auxi_data_part_tmp; +% auxi_data_part_tmp.Z=fillmissing(auxi_data_part_tmp.Z,'constant',0);% no data indicates no sure whether there is water. + auxi_data=max(auxi_data,auxi_data_tmp.Z); + clear auxi_data_tmp; +% delete(filename); % delete the files + else + tiles_used(i)=0;% no data file! + end + end + end + % see it has or not again + if sum(tiles_used(:))==0 % no tiles, return empty. + auxi_data=[]; % set empty. + return; + end + + if sum(tiles_used(:))>1% masic will lead to none data. + auxi_data = medfilt2(auxi_data); % filter it. + end + + clear tiles_used; + + switch auxi_type + case 'DEM' + auxi_data=int16(auxi_data); % convert to intergr with 16 bit for DEM, can be negative and positive + case 'WATER' + auxi_data=uint8(auxi_data); % convert to intergr with 8 bit for water, only be positive. + end + clear auxi_type; +end + +function [tiles_used, ul_name, ur_name, ll_name, lr_name] = ConvertBBox2TileName(bbox,pname_1th) + tiles_used=zeros(1,4); +% bbox=[north,south,west,east]; +% e.g., 21.2993 19.1468 77.7836 80.0166 +% 30N 19N 70E 90E + size_tile=10;% unit: degree. +% bbox=[21.2993 19.1468 77.7836 80.0166]; + % to negative infinity + bbox_floor=size_tile.*floor(bbox./size_tile); %e.g., floor: floor(-1.3)=-2; floor(1.3)=1; + % 20 10 70 80 + % to positive infinity + bbox_ceil=size_tile.*ceil(bbox./size_tile); %e.g., ceil: ceil(-1.3)=-1; ceil(1.3)=2; + clear size_tile bbox; + % 30 20 80 90 + +% % % four corners for each image. +% % ul_loc=[bbox(1) bbox(3)]; +% % ur_loc=[bbox(1) bbox(4)]; +% % ll_loc=[bbox(2) bbox(3)]; +% % lr_loc=[bbox(2) bbox(4)]; + % find the Granule's top-left corner. + ul_loc=[bbox_ceil(1) bbox_floor(3)]; + ur_loc=[bbox_ceil(1) bbox_floor(4)]; + ll_loc=[bbox_ceil(2) bbox_floor(3)]; + lr_loc=[bbox_ceil(2) bbox_floor(4)]; + clear bbox_ceil bbox_floor; +% pname_1th='occurrence'; + ul_pname_2th=convertNumel2Char(ul_loc); + ur_pname_2th=convertNumel2Char(ur_loc); + ll_pname_2th=convertNumel2Char(ll_loc); + lr_pname_2th=convertNumel2Char(lr_loc); + clear ul_loc ur_loc ll_loc lr_loc; + + + ul_name=[pname_1th,ul_pname_2th]; + ur_name=''; + ll_name=''; + lr_name=''; + % remove repeat tiles. + tiles_used(1)=1; % always have 1 or more than 1 tile. + + if ~isequal(ul_pname_2th,ur_pname_2th) + ur_name=[pname_1th,ur_pname_2th]; + tiles_used(2)=1; + end + if ~isequal(ul_pname_2th,ll_pname_2th)&&~isequal(ur_pname_2th,ll_pname_2th) + ll_name=[pname_1th,ll_pname_2th]; + tiles_used(3)=1; + end + if ~isequal(ul_pname_2th,lr_pname_2th)&&~isequal(ur_pname_2th,lr_pname_2th)&&~isequal(ll_pname_2th,lr_pname_2th) + lr_name=[pname_1th,lr_pname_2th]; + tiles_used(4)=1; + end +end + +function pname_2th=convertNumel2Char(corner) + pname_2th='_'; + if corner(2)>=0 + pname_2th=[pname_2th,num2str(abs(corner(2))),'E_']; + else + pname_2th=[pname_2th,num2str(abs(corner(2))),'W_']; + end + if corner(1)>=0 + pname_2th=[pname_2th,num2str(abs(corner(1))),'N']; + else + pname_2th=[pname_2th,num2str(abs(corner(1))),'S']; + end +end diff --git a/LoadData.m b/LoadData.m new file mode 100644 index 0000000..e84f11c --- /dev/null +++ b/LoadData.m @@ -0,0 +1,152 @@ +function [data_meta,data_toabt,angles_view,trgt] = LoadData(path_data,sensor,InputFile,main_meta) +%LOADDATA Read all used bands and metadata for Landsats 4-8 and Sentinel 2. +% +% Syntax +% +% [data_meta,data_toabt,trgt] = LoadData(path_data) +% +% Description +% +% For Landsat Level-1 images, Digital Number (DN) values are converted +% to TOA reflectances and BT (Celsius degree) by using the LEDAPS +% atmosphere correction tool (Masek et al., 2006). This function is +% derived from Fmask 3.3 for Landsat. +% For Sentinel 2 Level-1C images, TOA reflectances are provided with a +% spatial resolution of 10, 20 and 60 meters of the different spectral +% bands, that will be resampled into 20 meters at Fmask 4.0 rountine. +% This read function is derived from Fmask 3.3 for Sentinel 2. +% +% History +% +% 1. Create this function. (17. May, 2017) +% +% Input arguments +% +% path_data Path of images. +% Landsat: the directory where you save the Landsat scene. +% Sentinel 2: the directory reaching to '~/S2*/GRANULE/S2*/IMG_DATA/' +% +% Output arguments +% +% data_meta Metadata +% data_toabt TOA reflectances (x10000) and BT(x100 Celsius degree) data. +% trgt The GRIDobj used as a targart, which is useful when +% projecting and mosaicing the auxi data. +% +% See also: nd2toarbt nd2toarbt_msi +% +% +% Author: Shi Qiu (shi.qiu@ttu.edu) +% Date: 17. May, 2017 + + trgt=[]; + angles_view=[]; + if strcmp(sensor , 'S_MSI' ) + +% fprintf('Load TOA reflectances from the Level-1C product.\n'); + % Load data for Sentinel 2 + [~,data,trgt,dim,bbox,ul,zen,azi,zc,Angles,satu_B1,satu_B2,satu_B3,resolu]=nd2toarbt_msi(InputFile); + %% Input data class defination + data_meta=ObjMeta; + data_toabt=ObjTOABT; + %% Parameters + + norln=strread(main_meta.name,'%s','delimiter','.'); + n_name=char(norln(1)); + data_meta.Name=n_name; + data_meta.Sensor=sensor; + data_meta.Dim=dim; + data_meta.UL=ul; + data_meta.Zen=zen; + data_meta.Azi=azi; + data_meta.ZC=zc; + data_meta.Resolution=resolu; + data_meta.BBox=bbox; + + fmask_dir='FMASK_DATA'; + % see the FMASK_DATA is available + if ~exist(fullfile(InputFile.pathh,fmask_dir),'dir') + %have no the Fmask directory, create it here. + status_mkdir = mkdir(fullfile(InputFile.pathh,fmask_dir)); + % cannot make it, give the results here directly. + if ~status_mkdir + fmask_dir=''; + end + end + data_meta.Output=fmask_dir; + clear fmask_dir; + + %% TOA + data_toabt.BandBlue=data(:,:,1); + data_toabt.BandGreen=data(:,:,2); + data_toabt.BandRed=data(:,:,3); + data_toabt.BandNIR=data(:,:,4); % band 8A + data_toabt.BandSWIR1=data(:,:,5); + data_toabt.BandSWIR2=data(:,:,6); + data_toabt.BandCirrus=data(:,:,7); + + data_toabt.BandVRE3 = data(:,:,8); % band 07 + data_toabt.BandNIR8 = data(:,:,9); % band 08 + + %% View angles + angles_view=Angles; + + %% Saturated at visible bands + data_toabt.SatuBlue=satu_B1; + data_toabt.SatuGreen=satu_B2; + data_toabt.SatuRed=satu_B3; + else +% fprintf('Calculate TOA reflectances and BT from the Collection 1 product.\n'); + % Load data for Landsat 4-8 + [Temp,data,trgt,dim,ul,bbox,zen,azi,zc,satu_B1,satu_B2,satu_B3,resolu]=nd2toarbt(path_data,main_meta.name); + + %% Input data class defination + data_meta=ObjMeta; + data_toabt=ObjTOABT; + %% Parameters + + % reedit dir_im + norln=strread(main_meta.name,'%s','delimiter','.'); + n_name=char(norln(1)); + data_meta.Name=n_name(1:end-4); + data_meta.Sensor=sensor; + data_meta.Dim=dim; + data_meta.UL=ul; + data_meta.Zen=zen; + data_meta.Azi=azi; + data_meta.ZC=zc; + data_meta.Resolution=resolu; + data_meta.BBox=bbox; + data_meta.Output=''; + + %% TOA + if strcmp(sensor,'L_ETM_PLUS')||strcmp(sensor,'L_TM') + data_toabt.BandBlue=data(:,:,1); + data_toabt.BandGreen=data(:,:,2); + data_toabt.BandRed=data(:,:,3); + data_toabt.BandNIR=data(:,:,4); + data_toabt.BandSWIR1=data(:,:,5); + data_toabt.BandSWIR2=data(:,:,6); + else + if strcmp(sensor,'L_OLI_TIRS') + data_toabt.BandBlue=data(:,:,1); + data_toabt.BandGreen=data(:,:,2); + data_toabt.BandRed=data(:,:,3); + data_toabt.BandNIR=data(:,:,4); + data_toabt.BandSWIR1=data(:,:,5); + data_toabt.BandSWIR2=data(:,:,6); + data_toabt.BandCirrus=data(:,:,7); + end + end + + %% BT + +% fprintf('BT, '); + data_toabt.BandBT=Temp; + %% Saturated at visible bands + data_toabt.SatuBlue=satu_B1; + data_toabt.SatuGreen=satu_B2; + data_toabt.SatuRed=satu_B3; + end +end + diff --git a/LoadSensorType.m b/LoadSensorType.m new file mode 100644 index 0000000..276b6f7 --- /dev/null +++ b/LoadSensorType.m @@ -0,0 +1,73 @@ +function [sensor,num_Lst,InputFile,main_meta] = LoadSensorType(path_data) +%LOADSENSORTYPE Basic metadata should be loaded first to see which sensor +%here. + +%% Search metadate file for Landsat 4-8 + main_meta=dir(fullfile(path_data,'L*MTL.txt')); + existMTL=size(main_meta); + InputFile=[]; + if existMTL(1)==0 + main_meta=dir(fullfile(path_data, 'S*MTD*TL*.xml')); + if isempty(main_meta) + main_meta=dir(fullfile(path_data, 'MTD*TL*.xml')); + end + if ~isempty(main_meta) + txtstart = strfind(path_data,'S2A') ; + num_Lst='2A'; + if isempty(txtstart) + txtstart = strfind(path_data,'S2B') ; % S2A or S2B by Shi 10/18/2017 + num_Lst='2B'; + end + txtend = strfind(path_data,'.SAFE')-1 ; + InputFile.DataStrip = path_data(txtstart:txtend) ; + + InputFile.Dmain = path_data(1:txtstart-1) ; + + txtstart = strfind(path_data,'GRANULE')+8 ; + + InputFile.InspireXML=path_data(1:txtstart-10); % extra add to read inspire xml. + + txtend = length(path_data); + InputFile.Granule = path_data(txtstart:txtend) ; + InputFile.pathh = path_data ; + main_meta.name=InputFile.Granule;% return file name fro Fmask results. + else +% fprintf('No available data in the current folder!\n'); + sensor=[]; % no supported image. + num_Lst=[]; + InputFile=[]; + main_meta=[]; + return; + end + else + % determine sensor type + % open and read hdr file + fid_in=fopen(fullfile(path_data,main_meta.name),'r'); + geo_char=fscanf(fid_in,'%c',inf); + fclose(fid_in); + geo_char=geo_char'; + geo_str=strread(geo_char,'%s'); + + % Identify Landsat Number (Lnum = 4, 5, 7, or 8) + LID=char(geo_str(strmatch('SPACECRAFT_ID',geo_str)+2)); +% num_Lst=str2double(LID(end-1)); + num_Lst=(LID(end-1)); + end + % define Landsat sensor. + sensor=''; + if strcmp(num_Lst,'8') + sensor='L_OLI_TIRS'; + else + if strcmp(num_Lst,'4')||strcmp(num_Lst,'5')||strcmp(num_Lst,'6') + sensor='L_TM'; + else + if strcmp(num_Lst,'7') + sensor='L_ETM_PLUS'; + end + end + end + if strcmp(num_Lst,'2A')||strcmp(num_Lst,'2B') + sensor='S_MSI'; + end +end + diff --git a/MatchCloudShadow.m b/MatchCloudShadow.m new file mode 100644 index 0000000..cc9c624 --- /dev/null +++ b/MatchCloudShadow.m @@ -0,0 +1,582 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% This functin is used to match cloud shadow with cloud. +% The similarity defineing cloud shadow is larger than 0.3 only for those +% clouds, of which all pixels are included in the Landsat observations. +% This minor modification was made because the match similarity may be +% wrong when some parts of cloud are out of the observations. +% +% +% fix the bug that cloud shadow would be projected on the other side in Sentinel-2 imagery when the azimuth angle > 180. By Shi, at 19, Jan., 2019 +% use new match similarity becasue we do not know the potential clouds +% excluding self cloud and outsides are shadow or not. by Shi, at 21, April, 2018 +% remove the overlap between final matched cloud shadow and the potential +% cloud shadow. by Shi, at 26, Mar., 2018. +% speed up the match of cloud shadow with cloud for large clouds using +% sampling projections. by Zhe and Shi. at 24, Mar., 2018. +% do not revisit for the big clouds (more than 10,000,000). by Shi. at 22, Mar., 2018 +% match cloud shadow by following the sort from the center because the +% clouds loacted boundary will be easily affected due to the unkown of the +% pixels out of obervations. by Shi. at 15, Mar., 2018 +% cloud's temperature may be warmer than surface when we wrongly give some +% surface to the cloud. This will result in no cloud shadow. by Shi. at 3, Mar., 2018 +% fix the bugger that revisit clouds when less than 14 cloud objects. by Shi. at 11, Dec., 2017 +% still improve the prediction of cloud shadow location when no DEMs. by Shi. at 13, Sept., 2017 +% revisit the first 14 cloud objects. by Shi. at 21,Feb.,2017 +% fix the bugger, struct2table for lt. struct2table. at 21,Feb.,2017 +% search neighboring clouds by distance rule. by Shi. at 13,Feb.,2017 +% fix the bugger that bias for the location of real cloud object. at 13,Oct.,2016 +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function [ similar_num,data_cloud_matched, data_shadow_matched] = MatchCloudShadow(... + mask,plcim,plsim,pfpl,water,data_dem,data_bt_c,t_templ,t_temph,data_meta,ptm,num_near,angles_view) + + dim=data_meta.Dim; + % get potential mask values + data_shadow_potential=zeros(dim,'uint8'); + data_cloud_potential=(plcim>0)&mask==1; + data_shadow_potential(plsim==1)=1;% plshadow layer + clear plsim; % empty memory + % matched cloud & shadow layer + data_cloud_matched=zeros(dim,'uint8'); + data_shadow_matched=zeros(dim,'double'); + % revised percent of cloud on the scene after plcloud + revised_ptm=sum(data_cloud_potential(:))/sum(mask(:)); + % When having too many clouds, we will not match cloud shadow. + if ptm <=40000||revised_ptm>=0.90 % 0.1% was changed to 40,000 pixels. + fprintf('Skip cloud shadow detection because high cloud cover\n'); + data_cloud_matched(data_cloud_potential==true)=1; + data_shadow_matched(data_shadow_potential==false)=1; + data_shadow_matched=uint8(data_shadow_matched); + similar_num=-1; + else + + clear pfpl; + %% parameters + clear plcim; % empty memory + % max similarity threshold + max_similar = 0.95; + % number of inward pixes (240m) for cloud base temperature + num_pix=3; + + % enviromental lapse rate 6.5 degrees/km + rate_elapse=6.5; + % dry adiabatic lapse rate 9.8 degrees/km + rate_dlapse=9.8; + + % max match pixels number + max_match_num =1000000; %more than 1 million pixels will result in ~2 mins runtime. + + % sun angles + sun_zenith_deg=data_meta.Zen; + sun_azimuth_deg=data_meta.Azi; + % sun angle geometry + sun_elevation_deg=90-sun_zenith_deg; + sun_elevation_rad=deg2rad(sun_elevation_deg); + % solar azimuth anngle + Sun_tazi=sun_azimuth_deg-90; + sun_tazi_rad=deg2rad(Sun_tazi); + clear sun_elevation_deg sun_elevation_deg; + + % view angles for Sentinel 2 images, which will be used compute the + % average values for each cloud. Note that the Landsat's view angles + % can be estimated by the obersations of the entire scene. + if strcmp(data_meta.Sensor,'S_MSI') + VAA = angles_view.VAA; + VZA = angles_view.VZA ; + clear angles_view; + % mini matched similarity + Tsimilar=0.425; +% Tsimilar=0.4; + % threshold for matching buffering + Tbuffer=0.90; + elseif strcmp(data_meta.Sensor,'L_OLI_TIRS')||... + strcmp(data_meta.Sensor,'L_ETM_PLUS')||... + strcmp(data_meta.Sensor,'L_TM') + % mini matched similarity +% Tsimilar=0.35; + Tsimilar=0.3; + % threshold for matching buffering + Tbuffer=0.95; + + % view angle geometry for Landsat + % get track derection + [rows,cols]=find(mask==1); + [cor_ul_y,num]=min(rows);cor_ul_x=cols(num); + [cor_lr_y,num]=max(rows);cor_lr_x=cols(num); + [cor_ll_x,num]=min(cols);cor_ll_y=rows(num); + [cor_ur_x,num]=max(cols);cor_ur_y=rows(num); + % get view angle geometry + [A,B,C,omiga_par,omiga_per]=getSensorViewGeo(cor_ul_x,cor_ul_y,cor_ur_x,cor_ur_y,cor_ll_x,cor_ll_y,cor_lr_x,cor_lr_y); + clear cor_ul_x cor_ul_y cor_ur_x cor_ur_y cor_ll_x cor_ll_y cor_lr_x cor_lr_y; + else + error('Only Landsats 4-7, Landsat 8 and Sentinel 2 data can be supported./n'); + end + + % the lowest elevation. + if ~isempty(data_dem) + dem_base_heigh=double(prctile(data_dem(mask),0.001)); + else + dem_base_heigh=0; + end + % expand 1,000 pixels for the potential cloud shadow layer. + dim_expd=2000; + + % spatial resolution of the image + sub_size=data_meta.Resolution(1); + win_height=dim(1);win_width=dim(2); + % intervals within each matching process. + step_interval=2*sub_size*tan(sun_elevation_rad); + + %% project all potential cloud shadow and cloud (can be matched) along sun light based on DEMs. + % cloud shadow may be overlap with another cloud, so we need to + % project the all potential cloud and potential cloud shadow pixels. + [recorderRow,recorderCol] = ProjectDEM2Plane(dim,... + mask,... + data_dem,dem_base_heigh,sun_elevation_rad,sun_tazi_rad,... + sun_azimuth_deg,dim_expd,... + data_meta.Resolution); + + % create cloud objtects using 8-by-8 pixels connection. + [segm_cloud,num]=bwlabeln(data_cloud_potential,8); + s = regionprops(segm_cloud,'area'); + area_final = [s.Area]; + obj_num=area_final; + clear segm_cloud_init L idx area_final s; + + % Get the x,y of each cloud + % Matrix used in recording the x,y + stats= regionprops(segm_cloud,'Centroid','PixelList'); + + + match_clds=zeros(1,num,'uint8'); % cloud shadow match similarity (m) + matched_clds_centroid=[]; % centers of cloud having shadow + height_clds_recorder=[]; % cloud shadow match heights (m) + % Use iteration to get the optimal move distance + % Calulate the moving cloud shadow + similar_num=zeros(1,num); % cloud shadow match similarity (m) + l_pt=0.175; h_pt=1-l_pt; + dim_expand=dim+2*dim_expd; + record_base_h_num=0; + num_revisited = 0; + if num > num_near + num_revisited=num_near; + end + num_all=num+num_revisited; + + % min moving distance (min high 200 meters) unit: pixels + i_xy_min=200/(sub_size*tan(sun_elevation_rad)); + + for cloud_type_cur= 1:num_all %num + % revisit the first 14 cloud objects. + cloud_type=cloud_type_cur; + if cloud_type>num && num_revisitedt_obj)=t_obj; + if ~(t_templ max_match_num + % renew the arrays + % randomly selection. + samples_rand_all=randperm(obj_num(cloud_type)); + samples_mov=samples_rand_all(1:max_match_num); + clear samples_rand_all; + % moving cloud xys + XY_type=XY_type_all(samples_mov,:); + % corrected for view angle xys + tmp_xys=tmp_xys_all(samples_mov,:); + % record this orinal ids + orin_xys = orin_xys_all(samples_mov,:); +% orin_xys = orin_xys_all(samples_mov,1); + if ~isempty(data_bt_c) % if have no temperature data. + % Temperature of the cloud object + temp_obj=temp_obj_all(samples_mov); + end + + else + % give all pixels + % moving cloud xys + XY_type=XY_type_all; + % corrected for view angle xys + tmp_xys=tmp_xys_all; + % record the original xys + orin_xys=orin_xys_all; + % record this orinal ids +% orin_cid=orin_cid_all; + + if ~isempty(data_bt_c) % if have no temperature data. + % temperature + temp_obj=temp_obj_all; + end + end + +% record_h=0; + record_thresh=0; + record_base_h=0; + record_base_h_near=0;% it is available only when >0 + center_cur=stats(cloud_type,:).Centroid; + + if strcmp(data_meta.Sensor,'S_MSI') + VZAxy = pi/180*mean(single(VZA(orin_cid_all))/100); + VAAxy = pi/180*mean(single(VAA(orin_cid_all))/100); + end + + % height estimated by neighboring clouds. + if record_base_h_num>=num_near + % the centers of already matched clouds + % current cloud's center + % the nearest cloud among all matched clouds. + + % remove the self cloud heigh + [nearest_cloud_centers,nearest_dis]=knnsearch(matched_clds_centroid,center_cur,'k',num_near, 'distance','cityblock');% less time-consuming method chebychev + if cloud_type_cur>num % remove its self for the first 14 clouds when coming back. + nearest_cloud_centers(nearest_dis==0)=[]; + end + + % get all matched clouds' height. + record_base_h_tmp=height_clds_recorder(nearest_cloud_centers); + record_base_h_near=prctile(record_base_h_tmp,100*h_pt); + h_pre_std=std(record_base_h_tmp); + clear record_base_h_tmp; + if h_pre_std>=1000||record_base_h_near <= Min_cl_height||record_base_h_near >= Max_cl_height + record_base_h_near=0; + end + clear h_pre_std; + end + + dist_pre=0; + dist_passed=false; + dist_first=true; + % all pixels of projected cloud object + if numel(orin_cid_all(:))==0 + dist_passed=true; + else + cpc_i=center_cur(2); + cpc_j=center_cur(1); + end + + % indicates the number of the matched cloud shadows for this + % current cloud. + num_matched=0; + + for base_h=Min_cl_height:step_interval:Max_cl_height % iterate in height (m) + % Get the true postion of the cloud + % calculate cloud DEM with initial base height + if strcmp(data_meta.Sensor,'S_MSI') + h=base_h; % have no temperature data. cannot serve as 3D object. + elseif strcmp(data_meta.Sensor,'L_OLI_TIRS')||... + strcmp(data_meta.Sensor,'L_ETM_PLUS')||... + strcmp(data_meta.Sensor,'L_TM') + h=double(10*(t_obj-temp_obj)/rate_elapse+base_h);% relative to the reference plane. Cloud top's height. + end + + % the height for the bias of the real cloud location should exclude the + % surface elevation below the cloud object. + h_bias=h-base_heigh_cloud;% hc-Ec the height between cloud object and its surface. + if strcmp(data_meta.Sensor,'S_MSI') + [tmp_xys(:,1),tmp_xys(:,2)]= getRealCloudPositionS2(orin_xys(:,1),... + orin_xys(:,2),h_bias,VZAxy,VAAxy,data_meta.Resolution); + elseif strcmp(data_meta.Sensor,'L_OLI_TIRS')||... + strcmp(data_meta.Sensor,'L_ETM_PLUS')||... + strcmp(data_meta.Sensor,'L_TM') + sensor_heigh_bias=base_heigh_cloud+dem_base_heigh; % used to exclude the elevation of cloud' surface. + [tmp_xys(:,1),tmp_xys(:,2)]=getRealCloudPosition(orin_xys(:,1),... + orin_xys(:,2),h_bias,A,B,C,omiga_par,omiga_per,sensor_heigh_bias); + else + error('Only Landsats 4-7, Landsat 8 and Sentinel 2 data can be supported./n'); + end + + % shadow moved distance (pixel) to calculate the cloud + % shadow locaiton. + % real cloud height relative to the plane. + i_xy=h/(sub_size*tan(sun_elevation_rad)); + XY_type(:,2)=round(tmp_xys(:,1)-i_xy*cos(sun_tazi_rad)); % X is for j,2 + XY_type(:,1)=round(tmp_xys(:,2)-i_xy*sin(sun_tazi_rad)); % Y is for i,1 + + clear i_xy; + % this location is relative to reference plane. + tmp_j_plane=XY_type(:,2);% col + tmp_i_plane=XY_type(:,1);% row + clear XY_type; + % back project +% dim_expd=1000;% 1000 pixels buffer + % some projected pixels out of observations. + tmp_i_plane_expd_tmp=tmp_i_plane+dim_expd; + tmp_j_plane_expd_tmp=tmp_j_plane+dim_expd; + + avail_pixels=find(tmp_i_plane_expd_tmp>0&tmp_j_plane_expd_tmp>0&... + tmp_i_plane_expd_tmp<=dim_expand(1)&tmp_j_plane_expd_tmp<=dim_expand(2)); + + tmp_i_plane_expd=tmp_i_plane_expd_tmp(avail_pixels); + tmp_j_plane_expd=tmp_j_plane_expd_tmp(avail_pixels); + clear tmp_i_plane_expd_tmp tmp_j_plane_expd_tmp avail_pixels; + tmp_id_plane_expd=sub2ind(dim_expand,tmp_i_plane_expd,tmp_j_plane_expd); % matched shadow locations + clear tmp_i_plane_expd tmp_j_plane_expd; + + % search the responding locations in real surface (derived + % from the relation-lookup table). + tmp_i_obs=recorderRow(tmp_id_plane_expd); + tmp_j_obs=recorderCol(tmp_id_plane_expd); + clear tmp_id_plane_expd tmp_id_plane_expd; + + % cloud shadow must be beyond the location of the orgianl cloud. + if ~dist_passed + % the center of cloud shadow in real image. + sum_cpmp_i=sum(tmp_i_obs(:)); + sum_cpmp_j=sum(tmp_j_obs(:)); + area_cpmp=numel(tmp_j_obs(:)); + ctmp_i=sum_cpmp_i/area_cpmp; + ctmp_j=sum_cpmp_j/area_cpmp; + clear sum_cpmp_i sum_cpmp_j area_cpmp; + + % distance between orginal cloud and its cloud shadow, + % Note we ignored the mini bias from view angles here. + dist_cur = pdist2([ctmp_j,ctmp_i],[cpc_j,cpc_i],'Euclidean'); +% dist_cur = floor(dist_cur); + clear ctmp_j ctmp_i; + if dist_first + dist_pre = dist_cur; + dist_first = false; + else + % the distance between the center of cloud and + % cloud shadow over plane decreases + if dist_pre >= dist_cur || dist_curwin_height|tmp_j_obs<1|tmp_j_obs>win_width); + out_all=sum(out_id(:)); + + + % exclude the pixels out of the entire image. + tmp_ii_obs=tmp_i_obs(out_id==0); + tmp_jj_obs=tmp_j_obs(out_id==0); + clear out_id tmp_i_obs tmp_j_obs; + tmp_id=sub2ind(dim,tmp_ii_obs,tmp_jj_obs); % matched shadow locations + clear tmp_ii_obs tmp_jj_obs; + + out_obs=mask(tmp_id)==0; + + id_ex_self = segm_cloud(tmp_id)~=cloud_type; + % 1st rule: out of obervations; 2nd rule: located in + % potential shadow or other clouds (exclude the self cloud). + + % Special case #1: + % for the cloud shadow previoudly matched, the new one + % cannot reach the boundary and other clouds, which easily + % result in the overestimation of silimarity. + match_id_unsure = out_obs | ... + (id_ex_self&(data_cloud_potential(tmp_id)==1)); + + match_id_sure = id_ex_self&data_shadow_potential(tmp_id)==1; + + % give half weight to the macthed pixels located in outside and other + % clouds. + matched_all=sum(match_id_sure(:))+0.5*sum(match_id_unsure(:))+out_all; + + total_all=sum(id_ex_self(:))+out_all; + + thresh_match=matched_all/total_all; + clear match_id total_all; + + + % used to determine whether the iteration continues or not. + iter_con=true; % continues as default. + clear id_ex_self; + + if num_matched > 0&&... % already have cloud shadow previously + (record_base_h_near > 0 && base_h >= record_base_h_near) % or more than the predicted cloud height. + iter_con=false; + end + clear pt_unsure; + + % check the matched cloud shadow or not? + % the following rules are used to decide to continue or not. + if iter_con + if (thresh_match >= Tbuffer*record_thresh)&&... + (base_h < Max_cl_height-step_interval)&&... + (record_thresh < max_similar) + if thresh_match > record_thresh + % record max similarity and the corresponding cloud base height. + record_thresh=thresh_match; + % record_h=h; + record_base_h=base_h; + end + continue; + else + if (record_thresh >= Tsimilar) + % successfully find a cloud shadow + num_matched=num_matched+1; % indicates one more cloud shadow was found out. + % only when expected height available. (record_base_h_near>0) + if base_h=record_thresh||thresh_match>=max_similar + record_thresh=thresh_match; + % record_h=h; + record_base_h=base_h; + end + continue; % much reach the predicted cloud height. + end + else + record_thresh=0; + continue; + end + end + end + % 1: continues; 0: not continue and get a cloud shadow + if num_matched<1 + break; + end + + if r_obj>num_pix&&... + cloud_type_cur<=num % cannot re-add for the first 14 clouds + matched_clds_centroid=[matched_clds_centroid;center_cur]; + match_clds(cloud_type)=1; + height_clds_recorder=[height_clds_recorder;record_base_h]; +% height_clds_recorder(cloud_type)=record_base_h; + record_base_h_num=record_base_h_num+1; + end + similar_num(cloud_type)=record_thresh; + + if isequal(data_meta.Sensor,'S_MSI') + record_h = record_base_h; + h_bias=record_h-base_heigh_cloud;% hc-Ec + [tmp_xys_all(:,1),tmp_xys_all(:,2)]= getRealCloudPositionS2(orin_xys_all(:,1),... + orin_xys_all(:,2),h_bias,VZAxy,VAAxy,data_meta.Resolution); + elseif isequal(data_meta.Sensor,'L_OLI_TIRS')||... + isequal(data_meta.Sensor,'L_ETM_PLUS')||... + isequal(data_meta.Sensor,'L_TM') + record_h = double(10*(t_obj-temp_obj_all)/rate_elapse+record_base_h); + h_bias=record_h-base_heigh_cloud;% hc-Ec + sensor_heigh_bias=base_heigh_cloud+dem_base_heigh; + [tmp_xys_all(:,1),tmp_xys_all(:,2)]=getRealCloudPosition(orin_xys_all(:,1),... + orin_xys_all(:,2),h_bias,A,B,C,omiga_par,omiga_per,sensor_heigh_bias); + else + error('Only Landsats 4-7, Landsat 8 and Sentinel 2 data can be supported./n'); + end + clear orin_xys_all; + + i_vir=record_h/(sub_size*tan(sun_elevation_rad)); + + tmp_XY_type_all(:,2)=round(tmp_xys_all(:,1)-i_vir*cos(sun_tazi_rad)); % X is for col j,2 + tmp_XY_type_all(:,1)=round(tmp_xys_all(:,2)-i_vir*sin(sun_tazi_rad)); % Y is for row i,1 + + clear tmp_xys_all i_vir; + + tmp_scol_plane=tmp_XY_type_all(:,2); + tmp_srow_plane=tmp_XY_type_all(:,1); + clear tmp_XY_type_all; + tmp_tmp_i_plane_expd=tmp_srow_plane+dim_expd; + tmp_tmp_j_plane_expd=tmp_scol_plane+dim_expd; + clear tmp_srow_plane tmp_scol_plane; + + avail_pixels=find(tmp_tmp_i_plane_expd>0&tmp_tmp_j_plane_expd>0&... + tmp_tmp_i_plane_expdwin_height|tmp_scol>win_width; + tmp_srow(out_ids)=[]; + tmp_scol(out_ids)=[]; + clear out_ids; + + tmp_sid=sub2ind(dim,tmp_srow,tmp_scol); + clear tmp_srow tmp_scol; + + % give shadow_cal=1 +% data_shadow_matched(tmp_sid)=1; + + if cloud_type_cur>num % re-visit the first 14 clouds. + % remove the matched before. + data_shadow_matched((data_shadow_matched==cloud_type))=0; + % and give new cloud shadow to this. + end + data_shadow_matched(tmp_sid)=cloud_type; + clear tmp_sid; + clear center_cur; + break; + end + + end + data_cloud_matched=data_cloud_potential; + data_shadow_matched=uint8(data_shadow_matched>0); + % remove the cloud. + data_shadow_matched(data_cloud_matched==1)=0; + end +end diff --git a/NDBI.m b/NDBI.m new file mode 100644 index 0000000..316640b --- /dev/null +++ b/NDBI.m @@ -0,0 +1,34 @@ +function ndbi = NDBI( nir, swir ) +%NDBI Calculate Normalized Difference Build-up Index (NDBI) using NIR and +% SWIR bands. +% Syntax +% +% ndbi = NDBI(swir,nir) +% +% Description +% +% This function calculates Normalized Difference Build-up Index (NDBI) +% using SWIR and NIR bands (as following equation). +% +% Input arguments +% +% swir Short-wave infrared band +% nir Near infrared band +% +% Output arguments +% +% ndbi Normalized Difference Build-up Index +% +% +% Author: Shi Qiu (shi.qiu@ttu.edu) +% Date: 19. Dec., 2017 + + % calculate NDBI + ndbi=(swir-nir)./(swir+nir); + + % fix unnormal pixels + % not 0.01 any more because we will identify urban pixel using ndbi more than 0. +% ndbi((nir+swir)==0)=0.0; + +end + diff --git a/NDSI.m b/NDSI.m new file mode 100644 index 0000000..0e6d3b6 --- /dev/null +++ b/NDSI.m @@ -0,0 +1,41 @@ +function ndsi = NDSI( green,swir ) +%NDSI calculate Normalized Difference Snow Index (NDSI) using Green and +% SWIR bands. +% Syntax +% +% ndsi = NDSI(green,swir) +% +% Description +% +% This function calculates Normalized Difference Snow Index (NDSI) +% using Green and SWIR bands (as following equation). This can be used +% to detect snow, as the atmosphere is transparent at both these +% wavelengths, while snow is very reflective at 0.66 mm and not +% reflective at 1.6mm. At visible wavelengths (e.g. 0.66 microns), snow +% cover is just as bright as clouds, and is therefore difficult to +% distinguish from cloud cover. However, at 1.6 microns, snow cover +% absorbs sunlight, and therefore appears much darker than clouds. +% +% NDSI=(Green-SWIR)/(Green+SWIR). +% +% Input arguments +% +% green Green band +% swir Short-wave infrared band +% +% Output arguments +% +% ndsi Normalized Difference Snow Index +% +% +% Author: Shi Qiu (shi.qiu@ttu.edu) +% Date: 19. October, 2017 + + % calculate NDSI + ndsi=(green-swir)./(green+swir); + + % fix unnormal pixels + ndsi((green+swir)==0)=0.01; + +end + diff --git a/NDVI.m b/NDVI.m new file mode 100644 index 0000000..b23b219 --- /dev/null +++ b/NDVI.m @@ -0,0 +1,36 @@ +function ndvi = NDVI( red,nir ) +%NDVI Calculate Normalized Difference Vegetation Index (NDVI) using NIR and +% Red bands. +% +% Syntax +% +% ndvi = NDVI(red,nir) +% +% Description +% +% This function calculates Normalized Difference Vegetation Index (NDVI) +% using NIR and Red bands (as following equation). This range is between +% -1 and 1. +% NDVI=(NIR-Red)/(NIR+Red). +% +% Input arguments +% +% red Red band +% nir Near-infrared band +% +% Output arguments +% +% ndvi Normalized Difference Vegetation Index +% +% +% Author: Shi Qiu (shi.qiu@ttu.edu) +% Date: 19. October, 2017 + + % calculate NDVI + ndvi=(nir-red)./(nir+red); + + % fix unnormal pixels + ndvi((nir+red)==0)=0.01; + +end + diff --git a/NormalizaCirrusDEM.m b/NormalizaCirrusDEM.m new file mode 100644 index 0000000..f7bf000 --- /dev/null +++ b/NormalizaCirrusDEM.m @@ -0,0 +1,54 @@ +function cirrus_noml = NormalizaCirrusDEM( mask, idplcd, cirrus, dem ) +%NORMALIZACIRRUSDEM normalize Cirrus band using most dark object at Cirrus +%band based on DEMs. + cirrus_noml=zeros(size(cirrus),'double'); + idclr=idplcd==false&mask; % clear sky pixels + l_pt=2; + if isempty(dem) % when no DEMs, we also adjust the cirrus TOA to be around 0; + cirrus_noml(mask)=cirrus(mask)-prctile(cirrus(idclr),l_pt); + clear mask cirrus idclr l_pt; + else + dem_start=prctile(dem(mask),0.001); dem_end=prctile(dem(mask),99.999); % further exclude errors in DEM. + clear mask; + dem_step=100; + dem_intvl=dem_start:dem_step:dem_end; + clear dem_start dem_end; + n_slice=length(dem_intvl); + dem_LUT=dem_intvl; + clear dem_intvl; + cirrus_lowest=0; + cirrus_lowest_al=0; + for i=1:n_slice + % the dem intervals. + if i==n_slice + ids_inter=dem>dem_LUT(i)-dem_step/2; + else + if i==1 + ids_inter=demdem_LUT(i)-dem_step/2)&(dem0 + cirrus_lowest=prctile(cirrus(ids_inter_clr),l_pt); + if cirrus_lowest_al==0 % the first local lowest cirrus value will be given + cirrus_lowest_al=cirrus_lowest; + end + end + clear ids_inter_clr; + + cirrus_noml(ids_inter)=cirrus(ids_inter)-cirrus_lowest; + clear ids_inter; + end + clear cirrus cirrus_lowest idclr dem_LUT dem_step n_slice; +% % % when no DEMs, we also adjust the cirrus TOA to be around 0, +% % % based on the full lowest cirrus value. +% % cirrus_noml(~dem_have)=cirrus(~dem_have)-cirrus_lowest_al; + end + % the normalized cirrus value will be set as 0 when it is negative. + cirrus_noml(cirrus_noml<0)=0; +end \ No newline at end of file diff --git a/NormalizeBT.m b/NormalizeBT.m new file mode 100644 index 0000000..05887ba --- /dev/null +++ b/NormalizeBT.m @@ -0,0 +1,96 @@ +function Tempd = NormalizeBT( dim, dem,mask,Temp,ratelaspe_cl,l_pt,h_pt,resl) +%NORMALIZEBT Normalize BT along elevation (DEM) by using a linear model. +% +% Syntax +% +% Tempd = NormalizeBT( dim, dem,mask,Temp,ratelaspe_cl,l_pt,h_pt ) +% +% Description +% +% A linear model is used to normalize BT along with DEM (Qiu et al., +% 2017). +% History: +% 1. Create this function. (1. January, 2017) +% 2. This stratied sampling method sometimes results in no enough +% samples. If the stratied samples are less than 40,000 (not 50,000), +% the stratied sampling method will not be used anymore. (8. March, +% 2018) +% 3. no normalization if no dem data. (20. March, 2018) +% +% Input arguments +% +% dim Dim for data. +% dem Digital Elevation Model (DEM). +% Temp Temperature (BT). +% ratelaspe_cl Clear sky (land) pixels, which are used for this +% normalization. +% l_pt Low level (17.5 percentile). +% h_pt High level (81.5 percentile). +% resl Spatial resolution (Landsat 30 meters; Sentinel-2 20 meters). +% +% Output arguments +% +% Tempd Nomalized Temperature (BT). +% +% +% Author: Shi Qiu (shi.qiu@ttu.edu) +% Date: 8. March, 2018 + + if isempty (dem) + Tempd = Temp; + else + mask(isnan(dem))=0;% exclude nan dem pixel. + dem_b=double(prctile(dem(mask),0.0001)); + temp_cl=Temp(ratelaspe_cl); + % clear ratelaspe_cl; + temp_min=prctile(temp_cl,l_pt*100); + temp_max=prctile(temp_cl,h_pt*100); + clear temp_cl l_pt h_pt; + cls=(Temp>temp_min&Temptemp_min&temp_cl-9999; +% +% obs_mask=band~=0; +% +% %% simulate Sentinel 2 scene by 100 km x 100 km. +% % spatial resolution is 30 m for each pixel. That means there will be +% % 3333 x 3333 30m pixels should be selected expanding from the centre. +% s2_pixels=[3333 3333]; +% s2_pixels_half=round(s2_pixels./2); +% obs_dim=size(obs_mask); +% pixel_centre=round(obs_dim./2); +% +% s2_mask=obs_mask.*0; +% r_start=pixel_centre(1)-s2_pixels_half(1); +% r_end=pixel_centre(1)+s2_pixels_half(1); +% c_start=pixel_centre(2)-s2_pixels_half(2); +% c_end=pixel_centre(2)+s2_pixels_half(2); +% s2_mask(r_start:r_end,c_start:c_end)=1; +% obs_mask=s2_mask&obs_mask; +end + diff --git a/ProjectDEM2Plane.m b/ProjectDEM2Plane.m new file mode 100644 index 0000000..404b15e --- /dev/null +++ b/ProjectDEM2Plane.m @@ -0,0 +1,108 @@ +% verison +% change to project the pixels from the potential shadow layer. Mar., 3, 2018 by Shi Qiu +% sometimes the projection will generate Nan values. fixed this bug by +% Feb., 13, 2018 by Shi Qiu +% fix the bug some pixels out of image. Shi 9/12/2017 +% +function [dem_plane_r,dem_plane_c]= ProjectDEM2Plane(dim,pshadow,dem,dem_base,... + sun_elevation_rad,sun_tazi_rad,sun_azimuth_deg,dim_expd,resolu) + % DEM is vertically obervated by scene. + % assume resolu.x=resolu.y + spatial_resol=resolu(1); + % get the location of orginal pixel + dem_loc=zeros(dim,'uint8')+1; + [loc_i,loc_j]=find(dem_loc); + +% must project all pixels. +% [loc_i,loc_j]=find(pshadow==1); +% loc_i=uint16(loc_i); +% loc_j=uint16(loc_j); +% if isequal(dem,0) % when no DEMs, no cloud shadow shape correction. + if isempty(dem) % when no DEMs, no cloud shadow shape correction. + loc_i_plane=loc_i; + loc_j_plane=loc_j; + else % when having DEMs +% dem_dif=dem(pshadow==1)-dem_base; + dem_dif=dem-dem_base; + dem_dif=dem_dif(:); + clear dem_loc dem dem_base; + % sun angle geometry +% sun_elevation_deg=90-sun_zenith_deg; +% sun_elevation_rad=deg2rad(sun_elevation_deg); + d_ij=dem_dif./(spatial_resol*tan(sun_elevation_rad)); +% Sun_tazi=sun_azimuth_deg-90; +% sun_tazi_rad=deg2rad(Sun_tazi); +% if sun_azimuth_deg< 180 + + if sun_azimuth_deg< 180 + di=0-d_ij.*sin(sun_tazi_rad); % i for row, Y + dj=0-d_ij.*cos(sun_tazi_rad); % j for col, X + else + di=d_ij.*sin(sun_tazi_rad); % i for row, Y + dj=d_ij.*cos(sun_tazi_rad); % j for col, X + end + di=double(di); + dj=double(dj); + clear dem_dif d_ij Sun_tazi sun_elevation_rad sun_zenith_deg Sun_tazi sun_tazi_rad; + + % original location adds the bias. + loc_i_plane=round(loc_i+di); + loc_j_plane=round(loc_j+dj); + clear di dj; + end +% dim_expd=1000;% 1000 buffer + dim_plane_expd=dim+(2*dim_expd); + % expand the range of observations + loc_i_plane_expd=loc_i_plane+dim_expd; + loc_j_plane_expd=loc_j_plane+dim_expd; + + % remove the pixels out of box. +% % out_ids=loc_i_plane_expd(:)<1|loc_j_plane_expd(:)<1|... +% % loc_i_plane_expd(:)>dim_plane_expd(1)|loc_j_plane_expd(:)>dim_plane_expd(2); +% % loc_i_plane_expd(out_ids)=[]; +% % loc_j_plane_expd(out_ids)=[]; +% % loc_i(out_ids)=[]; +% % loc_j(out_ids)=[]; + + loc_i_plane_expd(loc_i_plane_expd(:)<1)=1; + loc_j_plane_expd(loc_j_plane_expd(:)<1)=1; + loc_i_plane_expd(loc_i_plane_expd(:)>dim_plane_expd(1))=dim_plane_expd(1); + loc_j_plane_expd(loc_j_plane_expd(:)>dim_plane_expd(2))=dim_plane_expd(2); + + clear loc_i_plane loc_j_plane dj dim_expd; + dem_plane_r=zeros(dim_plane_expd); + dem_plane_c=zeros(dim_plane_expd); +% dem_plane_r = NaN(dim_plane_expd,'double'); +% dem_plane_c = NaN(dim_plane_expd,'double'); + % recorders. + tmp_id_plane=sub2ind(dim_plane_expd,double(loc_i_plane_expd),double(loc_j_plane_expd)); + clear loc_i_plane_expd loc_j_plane_expd; + + % sometimes there may be some Nan values! + nan_ids=isnan(tmp_id_plane); + if sum(nan_ids(:))>0 + tmp_id_plane(nan_ids)=[]; + loc_i(nan_ids)=[]; + loc_j(nan_ids)=[]; + end + dem_plane_r(tmp_id_plane)=loc_i; + dem_plane_c(tmp_id_plane)=loc_j; + + + + clear loc_i loc_j tmp_id_plane; + % filled the holes due to the dicretization projection. +% dem_plane_r=fillmissing(dem_plane_r,'nearest',1);% by column. +% dem_plane_c=fillmissing(dem_plane_c,'nearest',1);% by column. + % fill remainings as 0 value. +% dem_plane_r=fillmissing(dem_plane_r,'constant',0);% by column. +% dem_plane_c=fillmissing(dem_plane_c,'constant',0);% by column. + +% dem_plane_r=round(dem_plane_r); +% dem_plane_c=round(dem_plane_c); + + +% dem_plane_r=imfill(dem_plane_r,'holes'); +% dem_plane_c=imfill(dem_plane_c,'holes'); +end + diff --git a/ReadMetadataMSI.m b/ReadMetadataMSI.m new file mode 100644 index 0000000..c860ed9 --- /dev/null +++ b/ReadMetadataMSI.m @@ -0,0 +1,118 @@ +function MSIinfo=ReadMetadataMSI(FilesInfo) +% DataStrip='S2A_OPER_PRD_MSIL1C_PDMC_20150818T101237_R022_V20150813T102406_20150813T102406'; +% Granule='S2A_OPER_MSI_L1C_TL_MTI__20150813T201603_A000734_T32TPS_N01'; + +DirIn=FilesInfo.DirIn; +% Num=FilesInfo.Num; +DataStrip = FilesInfo.DataStrip; +Granule=FilesInfo.Granule; + +Dmain = DirIn ; + +if Granule(1) == 'L' + FileName = fullfile(Dmain, [DataStrip '.SAFE'],'GRANULE', Granule, 'MTD_TL.xml'); +% FileName = [Dmain DataStrip '.SAFE/GRANULE/' Granule '/' 'MTD_TL.xml']; +else +% FileName = [Dmain DataStrip '.SAFE/GRANULE/' Granule '/' strrep(Granule(1:55),'MSI','MTD') '.xml']; + FileName = fullfile(Dmain,[DataStrip '.SAFE'],'GRANULE', Granule,[strrep(Granule(1:55),'MSI','MTD') '.xml']); +end + +S = xml2struct(FileName); + +S = S.n1_colon_Level_dash_1C_Tile_ID; + +TileID = S.n1_colon_General_Info.TILE_ID.Text; + +ds = S.n1_colon_General_Info.SENSING_TIME.Text ; +DN = (datenum( ds ,'yyyy-mm-ddTHH:MM:SS')+str2double(ds(end-4:end-1))/3600/24); + +DS = datestr(DN); + +bandIdList = {'01' '02' '03' '04' '05' '06' '07' '08' '8A' '09' '10' '11' '12'} ; + +GeoInfo.UTM_zone = S.n1_colon_Geometric_Info.Tile_Geocoding.HORIZONTAL_CS_NAME.Text(end-2:end); + +%% +M = S.n1_colon_Quality_Indicators_Info.Pixel_Level_QI.MASK_FILENAME ; + +for i=1:length(M) + if isfield(M{i}.Attributes,'bandId') + Mask.(M{i}.Attributes.type).(['B' M{i}.Attributes.bandId]) = M{i}.Text ; + else + Mask.(M{i}.Attributes.type) = M{i}.Text ; + end +end +%% +GeoInfo.Size.R10 = [str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Size{1}.NCOLS.Text) str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Size{1}.NROWS.Text)] ; +GeoInfo.Size.R20 = [str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Size{2}.NCOLS.Text) str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Size{2}.NROWS.Text)] ; +GeoInfo.Size.R60 = [str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Size{3}.NCOLS.Text) str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Size{3}.NROWS.Text)] ; + +GeoInfo.Xstart.R10 = str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Geoposition{1}.ULX.Text); +GeoInfo.Xstart.R20 = str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Geoposition{2}.ULX.Text); +GeoInfo.Xstart.R60 = str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Geoposition{3}.ULX.Text); + +GeoInfo.Ystart.R10 = str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Geoposition{1}.ULY.Text); +GeoInfo.Ystart.R20 = str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Geoposition{2}.ULY.Text); +GeoInfo.Ystart.R60 = str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Geoposition{3}.ULY.Text); + +%% +clear dum +for i=1:23 + dum(i,:) = str2num(S.n1_colon_Geometric_Info.Tile_Angles.Sun_Angles_Grid.Zenith.Values_List.VALUES{i}.Text) ; +end +Angles.SZA = dum ; + +clear dum +for i=1:length(S.n1_colon_Geometric_Info.Tile_Angles.Sun_Angles_Grid.Azimuth.Values_List.VALUES) + dum(i,:) = str2num(S.n1_colon_Geometric_Info.Tile_Angles.Sun_Angles_Grid.Azimuth.Values_List.VALUES{i}.Text) ; +end +Angles.SAA = dum ; + +bandIdList = 0:12 ; +DeteIdList = 1:12 ; + +Angles.VZA = nan(23,23,length(bandIdList),length(DeteIdList)); +Angles.VAA = nan(23,23,length(bandIdList),length(DeteIdList)); + +Sdum = S.n1_colon_Geometric_Info.Tile_Angles.Viewing_Incidence_Angles_Grids; +for j=1:length(Sdum) + bandId = str2num(Sdum{j}.Attributes.bandId) ; + DeteId = str2num(Sdum{j}.Attributes.detectorId) ; + clear dum + for i=1:23 + dum(i,:) = str2num(Sdum{j}.Zenith.Values_List.VALUES{i}.Text) ; + end + Angles.VZA(:,:,bandIdList==bandId,DeteIdList==DeteId) = dum ; + clear dum + for i=1:23 + dum(i,:) = str2num(Sdum{j}.Azimuth.Values_List.VALUES{i}.Text) ; + end + Angles.VAA(:,:,bandIdList==bandId,DeteIdList==DeteId) = dum ; +end + +Angles.Mean.SZA = str2num(S.n1_colon_Geometric_Info.Tile_Angles.Mean_Sun_Angle.ZENITH_ANGLE.Text) ; +Angles.Mean.SAA = str2num(S.n1_colon_Geometric_Info.Tile_Angles.Mean_Sun_Angle.AZIMUTH_ANGLE.Text) ; + +Angles.Mean.VZA2 = nan(length(bandIdList),1); +Angles.Mean.VAA2 = nan(length(bandIdList),1); + +Sdum = S.n1_colon_Geometric_Info.Tile_Angles.Mean_Viewing_Incidence_Angle_List.Mean_Viewing_Incidence_Angle; +for j=1:length(Sdum) + bandId = str2num(Sdum{j}.Attributes.bandId) ; + Angles.Mean.VZA2(bandIdList==bandId) = str2num(Sdum{j}.ZENITH_ANGLE.Text) ; + Angles.Mean.VAA2(bandIdList==bandId) = str2num(Sdum{j}.AZIMUTH_ANGLE.Text) ; +end + +Angles.Mean.VZA = mean(Angles.Mean.VZA2); +Angles.Mean.VAA = mean(Angles.Mean.VAA2); + +Angles.bandIdList=bandIdList; +Angles.DeteIdList=DeteIdList; +%% +MSIinfo.TileID = TileID; +MSIinfo.DN = DN; +MSIinfo.DS = DS; +MSIinfo.bandIdList = bandIdList ; +MSIinfo.Angles = Angles; +MSIinfo.GeoInfo = GeoInfo; +MSIinfo.Mask = Mask ; diff --git a/ReadS2InspireXML.m b/ReadS2InspireXML.m new file mode 100644 index 0000000..d3de2da --- /dev/null +++ b/ReadS2InspireXML.m @@ -0,0 +1,16 @@ +function bbox = ReadS2InspireXML(xml_path) +%READS2INSPIREXML Load INSPIRE XML data at .SAFE directory, and can give us +%image extent. + FileName = fullfile(xml_path,'INSPIRE.xml'); + S = xml2struct(FileName); + clear FileName; + S = S.gmd_colon_MD_Metadata.gmd_colon_identificationInfo.gmd_colon_MD_DataIdentification; + extentS=S.gmd_colon_extent.gmd_colon_EX_Extent.gmd_colon_geographicElement.gmd_colon_EX_GeographicBoundingBox; + clear S; + % bbox=[north,south,west,east]; + bbox=[str2double(extentS.gmd_colon_northBoundLatitude.gco_colon_Decimal.Text),... + str2double(extentS.gmd_colon_southBoundLatitude.gco_colon_Decimal.Text),... + str2double(extentS.gmd_colon_westBoundLongitude.gco_colon_Decimal.Text),... + str2double(extentS.gmd_colon_eastBoundLongitude.gco_colon_Decimal.Text)]; +end + diff --git a/ReadSunViewGeometryMSI.m b/ReadSunViewGeometryMSI.m new file mode 100644 index 0000000..5580f04 --- /dev/null +++ b/ReadSunViewGeometryMSI.m @@ -0,0 +1,322 @@ +function [MSIinfo Angles CM] = ReadSunViewGeometryMSI (DataStrip,Granule,BandSel,PsizeOut,Dmain) +% DataStrip='S2A_OPER_PRD_MSIL1C_PDMC_20150818T101451_R080_V20150817T114755_20150817T114755'; +% Granule='S2A_OPER_MSI_L1C_TL_SGS__20150817T131818_A000792_T28QBG_N01'; +% this is derived from Fmask 3.3 for Sentinel 2. + +InterpMethod = 'linear';'nearest'; + +% Dmain = '/nobackupp6/jju/S2SAFE/'; +Pppplot = 0 ; +% PsizeOut = 20 ; +if nargin<5 + FilesInfo.DirIn = CodeStartingLinesSentinel(-1); +else + FilesInfo.DirIn = Dmain; +end +% FilesInfo.Num = '.04'; +FilesInfo.DataStrip = DataStrip; +FilesInfo.Granule=Granule; + +if Granule(1) == 'L' + mainpath = fullfile(Dmain,[DataStrip '.SAFE']); +else + mainpath = fullfile(Dmain, [DataStrip '.SAFE'],'GRANULE', Granule, 'QI_DATA'); +end + +try + MSIinfo=ReadMetadataMSI(FilesInfo); +catch + error('The metadata was wrongly loaded. Please input Sentinel-2 TOA data.'); +end + +if nargout==1 + return +end +bandIdList = {'01' '02' '03' '04' '05' '06' '07' '08' '8A' '09' '10' '11' '12'} ; +BandList = 0:12 ; +if exist('BandSel','var') + bandIdList = bandIdList(BandSel); + BandList = BandList(BandSel); +end +if ~exist('PsizeOut','var') + PsizeOut = 10 ; +end +% bandIdList = bandIdList (8:9); +% BandList = BandList (8:9); + + +% MethInterp100m10m = 'nearest'; + +% [BandList]=find(MSIinfo.Angles.Mean.VZA2==median(MSIinfo.Angles.Mean.VZA2)) ; + +%% +X5km = MSIinfo.GeoInfo.Xstart.R10 : 5000 : MSIinfo.GeoInfo.Xstart.R10 + 5000*22 ; +Y5km = MSIinfo.GeoInfo.Ystart.R10 :-5000 : MSIinfo.GeoInfo.Ystart.R10 - 5000*22 ; +% [X5kmat Y5kmat]=meshgrid(X5km,Y5km); +% Psize = 100 ; +% X100m = single(MSIinfo.GeoInfo.Xstart.R20+Psize/2 : Psize : MSIinfo.GeoInfo.Xstart.R20-Psize/2 + Psize*(MSIinfo.GeoInfo.Size.R10(2)/10))'; +% Y100m = single(MSIinfo.GeoInfo.Ystart.R20-Psize/2 :-Psize : MSIinfo.GeoInfo.Ystart.R20+Psize/2 - Psize*(MSIinfo.GeoInfo.Size.R10(1)/10))'; +% [X100mat Y100mat]=meshgrid(X100m,Y100m); + +Psize = 10 ; +X10m = single(MSIinfo.GeoInfo.Xstart.R20 : Psize : MSIinfo.GeoInfo.Xstart.R20+(MSIinfo.GeoInfo.Size.R10(2)-1)*Psize) + Psize/2 ; +Y10m = single(MSIinfo.GeoInfo.Ystart.R20 :-Psize : MSIinfo.GeoInfo.Ystart.R20-(MSIinfo.GeoInfo.Size.R10(1)-1)*Psize)' - Psize/2 ; + +% X100m = imresize(X10m,10/100,'box') ; +% Y100m = imresize(Y10m,10/100,'box') ; + +XPsizeOut = imresize(X10m,10/PsizeOut,'box') ; +YPsizeOut = imresize(Y10m,10/PsizeOut,'box') ; +clear PsizeOut; + +% Psize = PsizeOut ; + +% [X100mat Y100mat]=meshgrid(X100m,Y100m); +[XPsizeOutmat YPsizeOutmat]=meshgrid(XPsizeOut,YPsizeOut); + + +if Pppplot == 1 + % Psize = PsizeOut ; + % X10m = single(MSIinfo.GeoInfo.Xstart.R20+Psize/2 : Psize : MSIinfo.GeoInfo.Xstart.R20-Psize/2 + (MSIinfo.GeoInfo.Size.R10(2)*10/Psize))'; + % Y10m = single(MSIinfo.GeoInfo.Ystart.R20-Psize/2 :-Psize : MSIinfo.GeoInfo.Ystart.R20+Psize/2 - (MSIinfo.GeoInfo.Size.R10(1)*10/Psize))'; + % [X10mat Y100mat]=meshgrid(X100m,Y100m); + [X5kmat Y5kmat]=meshgrid(X5km,Y5km); +end +% % kern = ones(20);imcircle(20); + +%% cloud Mask + +if nargout==3 + + FileName = fullfile(mainpath, MSIinfo.Mask.MSK_CLOUDS); + S = xml2struct(FileName); + + if isfield(S.eop_colon_Mask,'eop_colon_maskMembers') + + S = S.eop_colon_Mask.eop_colon_maskMembers; + + + + + + if length(S)==1 + % DetectFootPrint.Ndect(1) = str2num(S(1).eop_colon_MaskFeature.Attributes.gml_colon_id(24:25)); + % disp('attention') + % DetectFootPrint.Nband(1) = find(strcmp(MSIinfo.bandIdList , S(1).eop_colon_MaskFeature.Attributes.gml_colon_id(21:22))); + Vect = str2num(S(1).eop_colon_MaskFeature.eop_colon_extentOf.gml_colon_Polygon.gml_colon_exterior.gml_colon_LinearRing.gml_colon_posList.Text); + Vect = reshape(Vect' , [2 length(Vect)/2 ])'; + CloudMask.Data{1,1} = Vect ; + % Z = MSIinfo.Angles.VZA(:,:,MSIinfo.Angles.bandIdList==DetectFootPrint.Nband(1),MSIinfo.Angles.DeteIdList==DetectFootPrint.Ndect(1)) ; + else + + for i = 1:length(S) + % DetectFootPrint.Ndect(i,1) = str2num(S{i}.eop_colon_MaskFeature.Attributes.gml_colon_id(24:25)); + % DetectFootPrint.Nband(i,1) = strcmp(MSIinfo.bandIdList , S{i}.eop_colon_MaskFeature.Attributes.gml_colon_id(21:22)); + Vect = str2num(S{i}.eop_colon_MaskFeature.eop_colon_extentOf.gml_colon_Polygon.gml_colon_exterior.gml_colon_LinearRing.gml_colon_posList.Text); + Vect = reshape(Vect' , [2 length(Vect)/2 ])'; + CloudMask.Data{i,1} = Vect ; + % if Pppplot == 1 + % IDX = knnsearch(X10m,DetectFootPrint.Data{i,1}(:,1)); + % Ximg = X10m(IDX); + % IDX = knnsearch(Y10m,DetectFootPrint.Data{i,1}(:,2)); + % Yimg = Y10m(IDX); + % plot(Ximg,Yimg,'k') + % Z = MSIinfo.Angles.VAA(:,:,MSIinfo.Angles.bandIdList==DetectFootPrint.Nband(i),MSIinfo.Angles.DeteIdList==DetectFootPrint.Ndect(i)) ; + % scatter(X5kmat(:),Y5kmat(:),30,Myreshape(Z),sym(i)) + % end + + end + end + + Vect = [NaN NaN]; + for i = 1:length(S) + Vect = cat(1,Vect ,CloudMask.Data{i},[NaN NaN]); + end + + % X10m = single(MSIinfo.GeoInfo.Xstart.R20+Psize/2 : Psize : MSIinfo.GeoInfo.Xstart.R10-Psize/2 + Psize*(MSIinfo.GeoInfo.Size.R10(2)))'; + % Y10m = single(MSIinfo.GeoInfo.Ystart.R20-Psize/2 :-Psize : MSIinfo.GeoInfo.Ystart.R10+Psize/2 - Psize*(MSIinfo.GeoInfo.Size.R10(1)))'; + + IDX = knnsearch(XPsizeOut',Vect(:,1)); + IDY = knnsearch(YPsizeOut,Vect(:,2)); + CM = uint8(poly2mask(double(IDX),double(IDY),length(YPsizeOut),length(XPsizeOut))); + clear IDX IDY; + else + CM = zeros( length(YPsizeOut),length(XPsizeOut) ,'uint8'); + end +end + + +%% +%% +%% +%% +%% Angles +for iB = 1:length(BandList) + % tic + FileName = fullfile(mainpath,... + MSIinfo.Mask.MSK_DETFOO.(['B' num2str(BandList(iB))])); + + S = xml2struct(FileName); + clear FileName; + + if isfield(S.eop_colon_Mask.eop_colon_maskMembers,'eop_colon_MaskFeature') + S = S.eop_colon_Mask.eop_colon_maskMembers.eop_colon_MaskFeature; + else + S = S.eop_colon_Mask.eop_colon_maskMembers; + end + % figure + + if length(S)==1 + + try + % the following codes are unworkable anymore. + if ~(isfield(S,'eop_colon_MaskFeature')) + S{1}.eop_colon_MaskFeature = S{1}; + end + DetectFootPrint.Ndect(1) = str2num(S{1}.eop_colon_MaskFeature.Attributes.gml_colon_id(24:25)); + disp('attention') + DetectFootPrint.Nband(1) = find(strcmp(MSIinfo.bandIdList , S(1).eop_colon_MaskFeature.Attributes.gml_colon_id(21:22))); + Vect = str2num(S(1).eop_colon_MaskFeature.eop_colon_extentOf.gml_colon_Polygon.gml_colon_exterior.gml_colon_LinearRing.gml_colon_posList.Text); + Vect = reshape(Vect' , [3 length(Vect)/3 ])'; + DetectFootPrint.Data{1,1} = Vect ; + clear Vect; + catch + if ~(isfield(S,'eop_colon_MaskFeature')) + S.eop_colon_MaskFeature = S; + end + DetectFootPrint.Ndect(1) = str2num(S.eop_colon_MaskFeature.Attributes.gml_colon_id(24:25)); + disp('attention') + DetectFootPrint.Nband(1) = find(strcmp(bandIdList , S.eop_colon_MaskFeature.Attributes.gml_colon_id(21:22))); + Vect = str2num(S.eop_colon_MaskFeature.eop_colon_extentOf.gml_colon_Polygon.gml_colon_exterior.gml_colon_LinearRing.gml_colon_posList.Text); + Vect = reshape(Vect' , [3 length(Vect)/3 ])'; + DetectFootPrint.Data{1,1} = Vect ; + clear Vect; + end + % Z = MSIinfo.Angles.VZA(:,:,MSIinfo.Angles.bandIdList==DetectFootPrint.Nband(1),MSIinfo.Angles.DeteIdList==DetectFootPrint.Ndect(1)) ; + else + if Pppplot == 1 + figure + hold on + sym = 'oxoxoxoxox'; + end + for i = 1:length(S) + if ~(isfield(S{i},'eop_colon_MaskFeature')) + S{i}.eop_colon_MaskFeature = S{i}; + end + DetectFootPrint.Ndect(i,1) = str2num(S{i}.eop_colon_MaskFeature.Attributes.gml_colon_id(24:25)); + % DetectFootPrint.Nband(i,1) = strcmp(MSIinfo.bandIdList , S{i}.eop_colon_MaskFeature.Attributes.gml_colon_id(21:22)); + DetectFootPrint.Nband(i,1) = strcmp(bandIdList , S{i}.eop_colon_MaskFeature.Attributes.gml_colon_id(21:22)); + + Vect = str2num(S{i}.eop_colon_MaskFeature.eop_colon_extentOf.gml_colon_Polygon.gml_colon_exterior.gml_colon_LinearRing.gml_colon_posList.Text); + Vect = reshape(Vect' , [3 length(Vect)/3 ])'; + DetectFootPrint.Data{i,1} = Vect ; + clear Vect; + if Pppplot == 1 + + IDX = knnsearch(X10m,DetectFootPrint.Data{i,1}(:,1)); + Ximg = X10m(IDX); + IDX = knnsearch(Y10m,DetectFootPrint.Data{i,1}(:,2)); + Yimg = Y10m(IDX); + clear IDX; + plot(Ximg,Yimg,'k') + clear Ximg Yimg; + Z = MSIinfo.Angles.VAA(:,:,MSIinfo.Angles.bandIdList==DetectFootPrint.Nband(i),MSIinfo.Angles.DeteIdList==DetectFootPrint.Ndect(i)) ; + scatter(X5kmat(:),Y5kmat(:),30,Myreshape(Z),sym(i)) + end + end + end + clear S; + % % axis image + + + %% 5km => 10m + Matdetec = findMatdetecFootprint(DetectFootPrint,XPsizeOut,YPsizeOut); + + % JJ.B04_detfoo = hdfread('D:\UMD\Data\HLS\Products\detfoo.T36JTT.2015350.v1.0.hdf', '/B04_detfoo', 'Index', {[1 1],[1 1],[1830 1830]}); + + Angles.Matdetec(:,:,iB) = uint8(Matdetec) ; + Angles.DetectFootPrint{iB} = DetectFootPrint ; + % return + clear dum* + + % figure + % hold on + % imagesc(X10m,Y10m,Matdetec) + % for i = 1:length(S) + % plot(DetectFootPrint.Data{i,1}(:,1),DetectFootPrint.Data{i,1}(:,2),'k') + % end + + Chp = {'VAA' 'VZA'} ; + + for iC=1:2 + Angles.([Chp{iC} '_B' bandIdList{iB}]) = nan(size(Matdetec),'single'); + for i = 1:length(DetectFootPrint.Ndect) + + Z = MSIinfo.Angles.(Chp{iC})(:,:,BandSel,MSIinfo.Angles.DeteIdList==DetectFootPrint.Ndect(i)) ; + Z = single(inpaint_nans(Z)); + + testMatdetec = Matdetec==i ; + [Ista dum] = find(max(testMatdetec,[],2),1,'first'); + [Iend dum] = find(max(testMatdetec,[],2),1,'last'); + [dum Jsta] = find(max(testMatdetec,[],1),1,'first'); + [dum Jend] = find(max(testMatdetec,[],1),1,'last'); + clear dum; + % Ista = floor(Ista/10)+1; + % Jsta = floor(Jsta/10)+1; + % Iend = ceil(Iend/10); + % Jend = ceil(Jend/10); + + ZI = zeros(size(Matdetec),'single'); + + ZI(Ista:Iend,Jsta:Jend) = interp2(X5km,Y5km,Z,XPsizeOutmat(Ista:Iend,Jsta:Jend),YPsizeOutmat(Ista:Iend,Jsta:Jend),InterpMethod); + clear Z Ista Iend Jsta Jend; + % ZI(Ista:Iend,Jsta:Jend) = interp2(X5km,Y5km,Z,X10mat(Ista:Iend,Jsta:Jend),Y100mat(Ista:Iend,Jsta:Jend),InterpMethod); + % ZI = imresize(ZI,10,MethInterp100m10m); + + Angles.([Chp{iC} '_B' bandIdList{iB}])(testMatdetec) = ZI(testMatdetec) ; + clear ZI testMatdetec; + end + end + clear Matdetec; + % toc +end + + +Z = MSIinfo.Angles.SZA ; +ZI = interp2(X5km,Y5km,Z,XPsizeOutmat,YPsizeOutmat,InterpMethod); +clear Z; +% ZI=imresize(ZI,10,MethInterp100m10m); +Angles.SZA = ZI; + +Z = MSIinfo.Angles.SAA ; +ZI = interp2(X5km,Y5km,Z,XPsizeOutmat,YPsizeOutmat,InterpMethod); +clear Z X5km Y5km XPsizeOutmat YPsizeOutmat InterpMethod; +% ZI=imresize(ZI,10,MethInterp100m10m); +Angles.SAA = ZI; +clear ZId; + +% for iB = BandList +% Angles.(['RAA_B' bandIdList{iB+1}]) = abs(Angles.(['VAA_B' num2str(iB)]) - Angles.SAA) ; +% Angles=rmfield(Angles,['VAA_B' num2str(iB)]); +% end +% Angles=rmfield(Angles,'SAA'); + +fn = fieldnames(Angles); +dum = cellfun(@(x) isempty(strfind(x,'A')),fn); +fn(dum) = [] ; +clear dum; +for i=1:length(fn)-1 + dum = Angles.(fn{i}) ; + dum(dum<0) = 655.36 ; + Angles.(fn{i}) = uint16(dum*100); + clear dum; +end +clear fn; + +Angles.BandList = BandList ; +Angles.bandIdList = bandIdList ; +clear BandList bandIdList; + +return + +y = structfun(@(x) max(x(:)), Angles) diff --git a/Readopentopo.m b/Readopentopo.m new file mode 100644 index 0000000..0edf996 --- /dev/null +++ b/Readopentopo.m @@ -0,0 +1,121 @@ +function DEM = Readopentopo(varargin) + +%READOPENTOPO read DEM using the opentopography.org API +% +% Syntax +% +% DEM = readopentopo(pn,pv,...) +% +% Description +% +% readopentopo reads DEMs from opentopography.org using the API +% described on: +% http://www.opentopography.org/developers +% The DEM comes in geographic coordinates (WGS84) and should be +% projected to a projected coordinate system (use reproject2utm) before +% analysis in TopoToolbox. +% +% Input arguments +% +% Parameter name values +% 'filename' provide filename. By default, the function will save +% the DEM to a temporary file in the system's temporary +% folder. +% 'north' northern boundary in geographic coordinates (WGS84) +% 'south' southern boundary +% 'west' western boundary +% 'east' eastern boundary +% 'demtype' The global raster dataset - SRTM GL3 (90m) is +% 'SRTMGL3', SRTM GL1 (30m) is 'SRTMGL1', SRTM GL1 +% (Ellipsoidal) is 'SRTMGL1_E', and ALOS World 3D 30m +% is 'AW3D30' +% 'deletefile' 'true' or false. True, if file should be deleted +% after it was downloaded and added to the workspace. +% +% Output arguments +% +% DEM Digital elevation model in geographic coordinates +% (GRIDobj) +% +% See also: GRIDobj, websave +% +% Reference: http://www.opentopography.org/developers +% +% Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) +% Date: 19. June, 2017 + + +p = inputParser; +addParameter(p,'filename',[tempname '.tif']); +% addParameter(p,'interactive',false); +addParameter(p,'north',37.091337); +addParameter(p,'south',36.738884); +addParameter(p,'west',-120.168457); +addParameter(p,'east',-119.465576); +addParameter(p,'demtype','SRTMGL3'); +addParameter(p,'deletefile',true); +parse(p,varargin{:}); + +demtype = validatestring(p.Results.demtype,{'SRTMGL3','SRTMGL1','SRTMGL1_E','AW3D30'},'readopentopo'); + +url = 'http://opentopo.sdsc.edu/otr/getdem'; + +% create output file +f = fullfile(p.Results.filename); + +% save to drive +options = weboptions('Timeout',inf); + +west = p.Results.west; +east = p.Results.east; +south = p.Results.south; +north = p.Results.north; + +% if any([isempty(west) isempty(east) isempty(south) isempty(north)]) || p.Results.interactive; +% wm = webmap; +% % get dialog box +% messagetext = ['Zoom and resize the webmap window to choose DEM extent. ' ... +% 'Click the close button when you''re done.']; +% d = waitdialog(messagetext); +% uiwait(d); +% [latlim,lonlim] = wmlimits(wm); +% west = lonlim(1); +% east = lonlim(2); +% south = latlim(1); +% north = latlim(2); +% end + + + +websave(f,url,'west',west,... + 'east',east,... + 'north',north,... + 'south',south,... + 'outputFormat', 'GTiff', ... + 'demtype', demtype, ... + options); + +DEM = GRIDobj(f); +DEM.name = demtype; + +if p.Results.deletefile + delete(f); +end +end + +% function d = waitdialog(messagetext) +% d = dialog('Position',[300 300 250 150],'Name','Choose rectangle region',... +% 'WindowStyle','normal'); +% +% txt = uicontrol('Parent',d,... +% 'Style','text',... +% 'Position',[20 80 210 40],... +% 'String',messagetext); +% +% btn = uicontrol('Parent',d,... +% 'Position',[85 20 70 25],... +% 'String','Close',... +% 'Callback','delete(gcf)'); +% end + + diff --git a/Saturate.m b/Saturate.m new file mode 100644 index 0000000..7792ce7 --- /dev/null +++ b/Saturate.m @@ -0,0 +1,7 @@ +function satu_Bv = Saturate( satu_blue,satu_green,satu_red ) +%SATURE Summary of this function goes here +% Detailed explanation goes here + satu_Bv=satu_blue+satu_green+satu_red>=1; % saturated pixes at any visible bands. + clear satu_blue satu_green satu_red; +end + diff --git a/arcslope.m b/arcslope.m new file mode 100644 index 0000000..f5a4857 --- /dev/null +++ b/arcslope.m @@ -0,0 +1,90 @@ +function SLP = arcslope(DEM,unit) + +%ARCSLOPE mean gradient from a digital elevation model sensu ArcGIS +% +% Syntax +% +% SLP = arcslope(DEM) +% SLP = arcslope(DEM,unit) +% +% Description +% +% ARCSLOPE returns the gradient as calculated by ArcGIS (mean slope of +% 8 connected neighborhood). Default unit is as tangent, but you can +% specify alternative units identical to gradient8 function. +% +% Input +% +% DEM digital elevation model (class: GRIDobj) +% unit 'tan' --> tangent (default) +% 'rad' --> radian +% 'deg' --> degree +% 'sin' --> sine +% 'per' --> percent +% +% Output +% +% SLP mean gradient (class: GRIDobj) +% +% Example +% +% DEM = GRIDobj('srtm_bigtujunga30m_utm11.tif'); +% SLP = arcslope(DEM); +% imageschs(DEM,SLP); +% +% See also: GRIDobj/gradient8 +% +% Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) and +% Adam M. Forte (aforte[a]asu.edu) +% Date: 6. February, 2017 + + +z=DEM.Z; + +% Pad array to avoid edge effects +zp=padarray(z,[1 1],'replicate'); + +% Handle nans +I = isnan(zp); +in = any(I(:)); +if in + [~,L] = bwdist(~I); + zp = zp(L); +end + +% Define anon function to calculate the mean gradient of 8 connected neighborhood +% by same algorithm as the ArcGIS slope function +kernel = [ -1 -2 -1; 0 0 0; 1 2 1]'; +rr = sqrt((conv2(zp,kernel','valid')./(8*DEM.cellsize)).^2 + ... + (conv2(zp,kernel,'valid')./(8*DEM.cellsize)).^2); + +% Package output +if nargin == 1 + unit = 'tangent'; +else + unit = validatestring(unit,{'tangent' 'degree' 'radian' 'percent' 'sine'},'gradient8','unit',2); +end + +if in + rr(I(2:end-1,2:end-1)) = nan; +end + +% create a copy of the DEM instance +SLP = DEM; +SLP.name = 'slope (arcgis)'; +SLP.zunit = unit; +SLP.Z=rr; + +switch unit + case 'tangent' + % do nothing + case 'degree' + SLP.Z = atand(SLP.Z); + case 'radian' + SLP.Z = atan(SLP.Z); + case 'sine' + SLP.Z = sin(atan(SLP.Z)); + case 'percent' + SLP.Z = SLP.Z*100; +end + diff --git a/aspect.m b/aspect.m new file mode 100644 index 0000000..8785620 --- /dev/null +++ b/aspect.m @@ -0,0 +1,99 @@ +function OUT = aspect(DEM,classify) + +%ASPECT angle of exposition from a digital elevation model (GRIDobj) +% +% Syntax +% +% ASP = aspect(DEM) +% ASP = aspect(DEM,classify) +% +% Description +% +% aspect returns the slope exposition of each cell in a digital +% elevation model in degrees. In contrast to the second output of +% gradient8 which returns the steepest slope direction, aspect +% returns the angle as calculated by surfnorm. +% +% Input +% +% DEM digital elevation model (class: GRIDobj) +% classify false (default) or true. If true, directions are +% classified according to the scheme proposed by +% Gomez-Plaza et al. (2001) +% +% Output +% +% ASP aspect in degrees (clockwise from top) or classified grid, +% if classify is set to true +% +% Example +% +% DEM = GRIDobj('srtm_bigtujunga30m_utm11.tif'); +% ASP = aspect(DEM); +% imageschs(DEM,ASP) +% +% References +% +% Gómez-Plaza, A.; Martínez-Mena, M.; Albaladejo, J. & Castillo, V. M. +% (2001): Factors regulating spatial distribution of soil water content +% in small semiarid catchments. Journal of Hydrology, 253, 211 - 226. +% +% +% See also: GRIDobj/gradient8, GRIDobj/reclassify +% +% Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) +% Date: 17. August, 2017 + +narginchk(1,2) + +if nargin == 1; + classify = false; +else + +end + +OUT = DEM; +OUT.Z = []; + +if classify + aspedges = (0:45:360)'; + aspclass = [1 3 5 7 8 6 4 2]; +end + +% Large matrix support. Break calculations in chunks using blockproc +if numel(DEM.Z)>(5001*5001); + fun = @(x) aspfun(x); + blksiz = bestblk(size(DEM.Z),5000); + OUT.Z = blockproc(DEM.Z,blksiz,fun,'BorderSize',[1 1]); +else + OUT.Z = aspfun(DEM.Z); +end + +OUT.name = 'aspect'; +OUT.zunit = 'degree'; + + + + +function ASP = aspfun(Z) +if isstruct(Z); + Z = Z.data; +end + +[Nx,Ny] = surfnorm(Z); +ASP = cart2pol(Nx,Ny); +ASP = mod(90+ASP/pi*180,360); +% ASP = 270 - ASP/pi*180 -90; + +if classify + + [~,bin] = histc(ASP(:),aspedges); + bin = reshape(bin,size(Z)); + ASPc = zeros(size(Z),'uint8'); + ASPc(bin>0) = aspclass(bin(bin>0)); + ASP = ASPc; + +end +end + +end \ No newline at end of file diff --git a/autoFmask.m b/autoFmask.m new file mode 100644 index 0000000..3d52c83 --- /dev/null +++ b/autoFmask.m @@ -0,0 +1,236 @@ +function clr_pct = autoFmask(varargin) +% AUTOFMASK Automatedly detect clouds, cloud shadows, snow, and water for +% Landsats 4-7 TM/EMT+, Landsat 8 OLI/TIRS, and Sentinel 2 MSI images. +% +% +% Description +% Shi Qiu, Zhe Zhu, Binbin He +% shi.qiu@uconn.com, zhe.zhu@uconn.edu, binbinhe@uestc.edu.cn +% Automatically detect clouds, cloud shadows, snow, and water for +% Landsats 4-7, Landsat 8, and Sentinel 2 images. +% +% This 4.0 version has better cloud, cloud shadow, and snow detection +% results for Sentinel-2 data and better results (compared to the 3.3 +% version that is being used by USGS as the Colection 1 QA Band) for +% Landsats 4-8 data as well. +% +% Input arguments +% +% cloud Dilated number of pixels for cloud with default value of 3. +% shadow Dilated number of pixels for cloud shadow with default value of 3. +% snow Dilated number of pixels for snow with default value of 0. +% p Cloud probability threshold with default values of 10.0 for +% Landsats 4~7, 17.5 for Landsat 8, and 20.0 for Sentinel 2. +% e Radius of erosion for Potential False Positive Cloud such as +% urban/built-up and (mountian) snow/ice. Default: 150 meters +% for Landsats 4-7 and 90 meters for Landsat 8 and +% Sentinel-2. +% udem The path of User's DEM data. (.tiff). If users provide +% local DEM data, Fmask 4.0 will process the image along with this DEM +% data; or, the default USGS GTOPO30 will be used. +% +% Output arguments +% +% fmask 0: clear land +% 1: clear water +% 2: cloud shadow +% 3: snow +% 4: cloud +% 255: filled (outside) +% +% Examples +% +% clr_pct = autoFmask('cloud',0, 'shadow', 0) will produce the mask without buffers. +% clr_pct = autoFmask('p',20) forces cloud probablity thershold as 20. +% clr_pct = autoFmask('e',500) forces erosion radius for Potential False Positive Cloud as 500 meters to remove the large commission errors. +% +% +% Author: Shi Qiu (shi.qiu@uconn.com) +% Last Date: May 15, 2019 + + tic + fmask_soft_name='Fmask 4.0'; + fprintf('%s start ...\n',fmask_soft_name); + path_data=pwd; + + %% get parameters from inputs + p = inputParser; + p.FunctionName = 'FmaskParas'; + % optional + % default values. + addParameter(p,'cloud',3); + addParameter(p,'shadow',3); + addParameter(p,'snow',0); + + %% read info from .xml. + [sensor,~,InputFile,main_meta] = LoadSensorType(path_data); + if isempty(sensor) + error('%s works only for Landsats 4-7, Landsat 8, and Sentinel 2 images.\n',fmask_soft_name); + end + + default_paras = FmaskParameters(sensor); + tpw = default_paras.ThinWeight; + addParameter(p,'e',default_paras.PFPCErosionRadius); + addParameter(p,'p',default_paras.CloudProbabilityThershold); + addParameter(p,'resolution',default_paras.OutputResolution); + + % user's path for DEM + addParameter(p,'udem',''); + + % request user's input + parse(p,varargin{:}); + resolution=p.Results.resolution; + cldpix=p.Results.cloud; + sdpix=p.Results.shadow; + snpix=p.Results.snow; + erdpix=round(p.Results.e/resolution); + cldprob=p.Results.p; + + % users can use the local dem. + userdem = p.Results.udem; + clear p; + + fprintf('Cloud/cloud shadow/snow dilated by %d/%d/%d pixels.\n',cldpix,sdpix,snpix); + fprintf('Cloud probability threshold of %.2f%%.\n',cldprob); + + fprintf('Load or calculate TOA reflectances.\n'); + + %% load data + [data_meta,data_toabt,angles_view,trgt] = LoadData(path_data,sensor,InputFile,main_meta); + clear InputFile norMTL; + + if isempty(userdem) + % default DEM + [dem,slope,aspect,water_occur] = LoadAuxiData(fullfile(path_data,data_meta.Output),data_meta.Name,data_meta.BBox,trgt,false); % true false + else + [dem,slope,aspect,water_occur] = LoadAuxiData(fullfile(path_data,data_meta.Output),data_meta.Name,data_meta.BBox,trgt,false,'userdem',userdem); % true false + end + + fprintf('Detect potential clouds, cloud shadows, snow, and water.\n'); + + %% public data + mask=ObservMask(data_toabt.BandBlue); + + % a pixel's DEM can be set as the lowest value derived from the all workable pixels. + if ~isempty(dem) + dem_nan=isnan(dem); + dem(dem_nan)=double(prctile(dem(~dem_nan&mask),0.001)); % exclude DEM errors. + clear dem_nan; + end + + % NDVI NDSI NDBI + ndvi = NDVI(data_toabt.BandRed, data_toabt.BandNIR); + ndsi = NDSI(data_toabt.BandGreen, data_toabt.BandSWIR1); + cdi = CDI(data_toabt.BandVRE3,data_toabt.BandNIR8,data_toabt.BandNIR);% band 7, 8, AND 8a + + data_toabt.BandVRE3 = []; + data_toabt.BandNIR8 = []; + + % Statured Visible Bands + satu_Bv = Saturate(data_toabt.SatuBlue, data_toabt.SatuGreen, data_toabt.SatuRed); + data_toabt.SatuBlue = []; + + %% select potential cloud pixels (PCPs) + % inputs: BandSWIR2 BandBT BandBlue BandGreen BandRed BandNIR BandSWIR1 + % BandCirrus +% [idplcd,BandCirrusNormal,whiteness,HOT] = DetectPotentialPixels(mask,data_toabt,dem,ndvi,ndsi,satu_Bv); + + %% detect snow + psnow = DetectSnow(data_meta.Dim, data_toabt.BandGreen, data_toabt.BandNIR, data_toabt.BandBT, ndsi); + + %% detect water + water = DetectWater(data_meta.Dim, mask, data_toabt.BandNIR, ndvi, psnow, slope, water_occur); + clear water_occur; + + [idplcd,BandCirrusNormal,whiteness,HOT] = DetectPotentialPixels(mask,data_toabt,dem,ndvi,ndsi,satu_Bv); + + data_toabt.BandBlue = []; + data_toabt.BandRed = []; + data_toabt.BandSWIR2 = []; + clear satu_Bv; + data_toabt.BandCirrus = BandCirrusNormal; %refresh Cirrus band. + clear BandCirrusNormal; + + %% select pure snow/ice pixels. + abs_snow = DetectAbsSnow(data_toabt.BandGreen,data_toabt.SatuGreen,ndsi,psnow,data_meta.Resolution(1)); + + if ~isnan(abs_snow) + idplcd(abs_snow==1)=0; clear abs_snow; % remove pure snow/ice pixels from all PCPs. + end + + %% detect potential cloud + ndbi = NDBI(data_toabt.BandNIR, data_toabt.BandSWIR1); + + % inputs: BandCirrus BandBT BandSWIR1 SatuGreen SatuRed + [sum_clr,pcloud_all,idlnd,t_templ,t_temph]=DetectPotentialCloud(data_meta,mask,water,data_toabt, dem, ndvi,ndsi,ndbi,idplcd,whiteness,HOT,tpw,cldprob); + clear ndsi idplcd whiteness HOT tpw cldprob; + + data_toabt.SatuGreen = []; + data_toabt.SatuRed = []; + data_toabt.BandCirrus = []; + + %% detect potential flase positive cloud layer, including urban, coastline, and snow/ice. + pfpl = DetectPotentialFalsePositivePixels(mask, psnow, slope, ndbi, ndvi, data_toabt.BandBT,cdi, water,data_meta.Resolution(1)); + + clear ndbi ndvi; + %% remove most of commission errors from urban, bright rock, and coastline. + pcloud = ErodeCommissons(data_meta,pcloud_all,pfpl,water,cdi,erdpix); + clear cdi; + + %% detect cloud shadow + cs_final = zeros(data_meta.Dim,'uint8'); % final masks, including cloud, cloud shadow, snow, and water. + cs_final(water==1)=1; %water is fistly stacked because of its always lowest prioty. + + % note that 0.1% Landsat obersavtion is about 40,000 pixels, which will be used in the next statistic analyses. + % when potential cloud cover less than 0.1%, directly screen all PCPs out. + if sum_clr <= 40000 + fprintf('No clear pixel in this image (clear-sky pixels = %.0f%)\n',sum_clr); + pcloud=pcloud>0; + pshadow=~pcloud; + clear data_toabt; + else + fprintf('Match cloud shadows with clouds.\n'); + % detect potential cloud shadow + pshadow = DetectPotentialCloudShadow(data_meta, data_toabt.BandNIR,data_toabt.BandSWIR1,idlnd,mask,... + slope,aspect); + + data_toabt.BandNIR = []; + data_toabt.BandSWIR1 = []; + + data_bt_c=data_toabt.BandBT; + clear data_toabt; + % match cloud shadow, and return clouds and cloud shadows. + [ ~,pcloud, pshadow] = MatchCloudShadow(... + mask,pcloud,pshadow,pfpl,water, dem ,data_bt_c,t_templ,t_temph,data_meta,sum_clr,14,angles_view); + + % make buffer for final masks. + % the called cloud indicate those clouds are have highest piroity. + % This is final cloud! + [pcloud,pshadow,psnow] = BufferMasks(pcloud,cldpix,pshadow,sdpix,psnow,snpix); + end + %% stack results together. + % step 1 snow or unknow + cs_final(psnow==1)=3; % snow + % step 2 shadow above snow and everyting + cs_final(pshadow==1)=2; %shadow + % step 3 cloud above all + cs_final(pcloud==1)=4; % cloud + % mask out no data. + cs_final(mask==0)=255; % mask + + % clear pixels percentage + clr_pct=100*(1-sum(pcloud(:))/sum(mask(:))); + + %% output as geotiff. + trgt.Z=cs_final; + fmask_name=[data_meta.Name,'_Fmask4']; + trgt.name=fmask_name; + + fmask_output=fullfile(path_data,data_meta.Output,[fmask_name,'.tif']); + GRIDobj2geotiff(trgt,fmask_output); + time=toc; + time=time/60; + fprintf('%s finished (%.2f minutes)\nfor %s with %.2f%% clear pixels\n\n',... + fmask_soft_name,time,data_meta.Name,clr_pct); +% enviwrite([path_data,data_meta.Name,'_Fmask41'], cs_final, 'uint8', data_meta.Resolution, data_meta.UL, 'bsq', data_meta.ZC); +end diff --git a/autoFmaskBatch.m b/autoFmaskBatch.m new file mode 100644 index 0000000..7545e69 --- /dev/null +++ b/autoFmaskBatch.m @@ -0,0 +1,13 @@ +function autoFmaskBatch() +% This can automatically find all Landsats 4-8 and Sentinel-2 images +% (folder) and to process them one by one. + + fprintf('Fmask 4.0 batch starts ...\n'); + filepath_work = pwd; % CD to the path; or, type in the path of your working directory here. + [~, ~, paths, ~] = CheckImagesPath(filepath_work); + for i=1: length(paths) + cd(paths{i}); + fprintf('At %s.\n',paths{i}); + autoFmask('cloud',0);% 4.0 + end +end \ No newline at end of file diff --git a/findMatdetecFootprint.m b/findMatdetecFootprint.m new file mode 100644 index 0000000..be7697d --- /dev/null +++ b/findMatdetecFootprint.m @@ -0,0 +1,173 @@ +function Matdetec2 = findMatdetecFootprint(DetectFootPrint,XPsizeOut,YPsizeOut) +clear Matdetec k +for i = 1:length(DetectFootPrint.Ndect) + + IDX = knnsearch(XPsizeOut',DetectFootPrint.Data{i,1}(:,1)); + IDY = knnsearch(YPsizeOut,DetectFootPrint.Data{i,1}(:,2)); + + dum2 = single(poly2mask(double(IDX), double(IDY),length(XPsizeOut),length(XPsizeOut))) ; + clear IDX IDY; + dum2 = conv2(dum2,ones(3),'same')>0; % to fill boundary + Matdetec(:,:,i) = dum2; + clear dum* + + % find orientation of detect + slope for computing perpendicular kernel + I=nan(size(Matdetec,2),1); + for ii=1:size(Matdetec,1) + dum=find(Matdetec(ii,:,i)==1,1,'first'); + if ~isempty(dum) + I(ii,1)=dum; + end + end + clear dum; + J = [1:size(Matdetec,1)]' ; + test = ~isnan(I) & I > 1 & I < size(Matdetec,2); + warning off all % if warning => not enough point => slope=0 => good because tile boundary + k{i,1} = polyfit(J(test),I(test),1); + clear test; + + I=nan(size(Matdetec,2),1); + for ii=1:size(Matdetec,1) + dum=find(Matdetec(ii,:,i)==1,1,'last'); + if ~isempty(dum) + I(ii,1)=dum; + end + end + J = [1:size(Matdetec,1)]' ; + test = ~isnan(I) & I > 1 & I < size(Matdetec,2); + k{i,2} = polyfit(J(test),I(test),1); + clear test; + warning on all + +end + +% mediane +for i = 1:length(DetectFootPrint.Ndect)-1 + mediane = mean( [k{i,2} ; k{i+1,1}] ) ; + k{i,2} = mediane ; + k{i+1,1} = mediane ; + clear mediane; +end +J = [1:size(Matdetec,1)]' ; +I = [1:size(Matdetec,2)] ; + +[Jmat Imat] = meshgrid(I,J); +clear I J; + +Matdetec2 = nan(size(Matdetec,1),size(Matdetec,2)); +clear Matdetec; +for i = 1:length(DetectFootPrint.Ndect) + + liminf = polyval(k{i,1},Jmat); + limsup = polyval(k{i,2},Jmat); + + Matdetec2(Imat>=liminf & Imat<=limsup) = i ; + clear liminf limsup; +end +clear Imat ImatJ k; + +Matdetec2 = Matdetec2'; + +return + +%% +%% old codes +%% + + clear Matdetec k + for i = 1:length(DetectFootPrint.Ndect) + + IDX = knnsearch(XPsizeOut',DetectFootPrint.Data{i,1}(:,1)); + IDY = knnsearch(YPsizeOut,DetectFootPrint.Data{i,1}(:,2)); + + dum2 = single(poly2mask(double(IDX), double(IDY),length(XPsizeOut),length(XPsizeOut))) ; + dum2 = conv2(dum2,ones(3),'same')>0; % to fill boundary + Matdetec(:,:,i) = dum2; + clear dum* + + % find orientation of detect + slope for computing perpendicular kernel + I=nan(size(Matdetec,2),1); + for ii=1:size(Matdetec,1) + dum=find(Matdetec(ii,:,i)==1,1,'first'); + if ~isempty(dum) + I(ii,1)=dum; + end + end + J = [1:size(Matdetec,1)]' ; + test = ~isnan(I) & I > 1 & I < size(Matdetec,2); + k{i,1} = polyfit(J(test),I(test),1); + + I=nan(size(Matdetec,2),1); + for ii=1:size(Matdetec,1) + dum=find(Matdetec(ii,:,i)==1,1,'last'); + if ~isempty(dum) + I(ii,1)=dum; + end + end + J = [1:size(Matdetec,1)]' ; + test = ~isnan(I) & I > 1 & I < size(Matdetec,2); + k{i,2} = polyfit(J(test),I(test),1); + + + + % n = 100 ; + % x =1:n ; + % y = round(x*k(1)) ; + % y = y-min(y)+1 ; + % kern = zeros([max(y) n]) ; + % linearInd = sub2ind(size(kern), y, x); + % kern(linearInd)=1; + % kern = flipud(kern); + + end + + for i = 1:length(DetectFootPrint.Ndect)-1 + mediane = mean( [k{i,2} ; k{i+1,1}] ) ; + k{i,2} = mediane ; + k{i+1,1} = mediane ; + end + + figure + imagesc(Matdetec(:,:,1)) + hold on + for i = 1:length(DetectFootPrint.Ndect) + J = [1:size(Matdetec,1)]' ; +plot(polyval(k{i,2},J),J,'.g') + plot(polyval(k{i,1},J),J,'r') + end + % find orientation of detect + slope for computing perpendicular kernel + % [dum maxdetec]=max(sum(Myreshape(Matdetec,-1))); + % I=nan(size(Matdetec,2),1); + % for i=1:size(Matdetec,1) + % dum=find(Matdetec(i,:,maxdetec)==1,1,'first'); + % if ~isempty(dum) + % I(i,1)=dum; + % end + % end + % J = [1:size(Matdetec,1)]' ; + % k = polyfit(J(~isnan(I)),I(~isnan(I)),1); + % + % n = 100 ; + % x =1:n ; + % y = round(x*k(1)) ; + % y = y-min(y)+1 ; + % kern = zeros([max(y) n]) ; + % linearInd = sub2ind(size(kern), y, x); + % kern(linearInd)=1; + % kern = flipud(kern); + + + Matdetec = cat(1,Matdetec(1:100,:,:),Matdetec,Matdetec(end-99:end,:,:)); + Matdetec = cat(2,Matdetec(:,1:100,:),Matdetec,Matdetec(:,end-99:end,:)); + + % Matdetec=imresize(Matdetec,100/Psize,'bilinear'); %100m => 10m + % for i = 1:length(DetectFootPrint.Ndect) + % Matdetec2(:,:,i)=conv2(Matdetec(:,:,i),ones(100),'same'); %100m => 10m + % end +% for i = 1:length(DetectFootPrint.Ndect) +% Matdetec(:,:,i)=conv2(Matdetec(:,:,i),kern,'same'); %100m => 10m +% end +% Matdetec = Matdetec(101:end-100,101:end-100,:); + % [dum Matdetec2]=max(Matdetec2,[],3); + % Matdetec2(dum==0)=NaN; + [dum Matdetec]=max(Matdetec,[],3); diff --git a/geoimread.m b/geoimread.m new file mode 100644 index 0000000..43112f5 --- /dev/null +++ b/geoimread.m @@ -0,0 +1,399 @@ +function [A,x,y,I]=geoimread(filename,varargin) +%GEOIMREAD reads a sub region of a geotiff or geojp2 image. +% +% +%% Syntax +% +% A = geoimread(filename) +% A = geoimread(filename,xlim,ylim) +% A = geoimread(filename,latlim,lonlim) +% A = geoimread(...,buffer) +% [A,x,y,I] = geoimread(...) +% geoimread(...) +% +% +%% Description +% +% A = geoimread(filename) returns the full image given by a filename. This +% syntax is equivalent to A = geotiffread(filename). +% +% A = geoimread(filename,xlim,ylim) limits the region of the geotiff file to +% the limits given by xlim and ylim, which are map units (usually meters) relative +% to the data projection. For example, if the geotiff is projected in Texas Centric +% Mapping System/Lambert Conformal coordinates, xlim and ylim will have units of +% meters relative to the origin (100 W, 18 N). xlim and ylim can be multimensional, +% in which case the limits of the map will be taken as the limits of the limits of +% the distribution of all points in xlim, ylim. +% +% A = geoimread(filename,latlim,lonlim) if no values in xlim, ylim exceed +% normal values of latitudes and longitudes, geoimread assumes you've entered +% limits in geographic coordinates of degrees latitude and longitude. The first +% input is latitude, the second input is longitude. +% +% A = geoimread(...,buffer) adds a buffer in map units (usually meters or feet) to the +% limits of the region of interest. This may be useful if you want to load an image +% surrounding scattered lat/lon data. If you'd like an extra 2 kilometers of image around +% your data, enter 2000 as the buffer. If buffer is a two-element vector, the first +% element is applied to the left and right extents of the image, and the second element +% is applied to the top and bottom extents of the image. +% +% [A,x,y,I] = geoimread(...) also returns pixel center coordinates (x,y) of the +% output image and a geotiff info structure I. I is a useful input for projfwd and projinv. +% +% geoimread(...) without any outputs shows the output image A without loading +% any data into the workspace. +% +% +%% Examples: +% +% % Show a whole geotiff: +% geoimread('boston.tif'); +% +% % Compare results from above to a subset geotiff: +% mapx = [765884 766035 766963]; % units are feet +% mapy = [2959218 2957723 2958972]; +% geoimread('boston.tif',mapx,mapy) +% +% % Or if you have coordinates in lat/lon and you want a 500 foot buffer: +% lat = [42.3675288 42.3634246 42.3668397]; +% lon = [-71.0940009 -71.0934685 -71.0900125]; +% +% geoimread('boston.tif',lat,lon,500); +% +%% Author Info: +% +% (c) Aslak Grinsted 2014- (http://www.glaciology.net/) +% & Chad A. Greene (http://chadagreene.com/) +% +%% +% +% See also GEOTIFFREAD, GEOTIFFINFO, PIXCENTERS, and PROJFWD. + +%% CHAD'S CHANGES: +% The following changes were made by Chad A. Greene (http://chadagreene.com/) +% of the University of Texas Institute for Geophysics (UTIG) on Sept 17, 2014: +% +% * More input checking and error messages. +% +% * Clarified syntax and description in header. +% +% * The fileparts line now only writes the file extension because other outputs went unused. +% +% * If geographic coordinate limits are inferred, they are now ordered latlim,lonlim. <-- **FUNCTIONALITY CHANGE** +% +% * Limits xlim, ylim or latlim, lonlim can be scalar, vector, or matrix-- xlim is now taken +% as xlim = [min(xlim(:)) max(xlim(:))]. This will save a small step if you have some data points +% given by x,y. Now you can simply enter your data coordinates and geoimread will +% figure out the limits. +% +% * Output variable I now has correct corner coordinates for the subset image instead of +% the previous version which returned corner coordinates of the full original image. +% +% * A buffer can be specified around input limits. +% +% +% Syntax before the changes: +% geoimread('myimage.tif',[min(easting)-buffer_m max(easting)+buffer_m],... +% [min(northing)-buffer_m max(northing)+buffer_m]); +% +% Syntax after the changes: +% geoimread('myimage.tif',easting,northing,buffer_m) +% +% + +%TODO: support downsampling (ReductionLevel parameter in imread) +%TODO: use map2pix and latlon2pix instead of projfwd and pixcenters. more robust if it is a rotational coordinate system. + + +%% Set defaults: + +usegeocoordinates = false; +returnNONsubsetImage = true; +buffer_x = 0; +buffer_y = 0; + +%% Input checks: + +% Check for mapping toolbox: +% assert(license('test','map_toolbox')==1,'geoimread requires Matlab''s Mapping Toolbox.') + +% Check file type: +assert(isnumeric(filename)==0,'Input filename must be a string.') +[~,~,ext] = fileparts(filename); +switch upper(ext) + case {'.JP2' '.JPEG2000' '.GEOJP2'} + I = jp2tiffinfo(filename); + case {'.TIF' '.TIFF' '.GTIF' '.GTIFF'} + I = robustgeotiffinfo(filename); + otherwise + error('Unrecognized image file type. Must be tif, tiff, gtif, gtiff, jp2, jpeg2000, or geojp2.') +end + + +% Parse optional inputs: +if nargin>1 + returnNONsubsetImage = false; + assert(nargin>2,'If you specify an xlim or latlim, you must specify a corresponding ylim or lonlim.') + + % Parse limits: + xlimOrLatlim = varargin{1}(:); + ylimOrLonlim = varargin{2}(:); + + assert(isnumeric(xlimOrLatlim)==1,'Input xlim or latlim must be numeric.') + assert(isnumeric(ylimOrLonlim)==1,'Input ylim or lonlim must be numeric.') + + % Assume geo coordinates if no input limits exceed normal lat/lon values: + if max(abs(xlimOrLatlim))<=90 && max(abs(ylimOrLonlim))<=360 + usegeocoordinates = true; + end + + % Parse buffer: + if nargin>3 + buffer_m = varargin{3}; + assert(isnumeric(buffer_m)==1,'Buffer value must be either a scalar or a two-element array.') + assert(numel(buffer_m)<3,'Buffer value must be either a scalar or a two-element array.') + buffer_x = buffer_m(1); + if isscalar(buffer_m) + buffer_y = buffer_m(1); + else + buffer_y = buffer_m(2); + end + end + + if nargin>4 + error('Too many inputs in geoimread.') + end + +end + + +%% Begin work: + +% Get pixel coordinates of full (non-subset) image: +[x,y]=robustpixcenters(I); + +if nargout==0 + fprintf('No outputs specified.\n') + fprintf('X-range: %.1f:%.1f:%.1f\n',x(1),x(2)-x(1),x(end)) + fprintf('Y-range: %.1f:%.1f:%.1f\n',y(1),y(2)-y(1),y(end)) + clear A x y I + return +end + +% Set xlim and ylim depending on user inputs: +if returnNONsubsetImage + xlimOrLatlim = x(:); + ylimOrLonlim = y(:); +end + +if usegeocoordinates + % lat/lon limits switch to x/y limits here: + if ~strcmp(I.ModelType,'ModelTypeGeographic') + assert(license('test','map_toolbox')==1,'Mapping toolbox needed to project between lat/lon limits and x,y limits. Specify limits in x,y coordinates.') + [xlimOrLatlim,ylimOrLonlim]=projfwd(I,xlimOrLatlim,ylimOrLonlim); + end +end + + +xlim = [min(xlimOrLatlim)-buffer_x max(xlimOrLatlim)+buffer_x]; +ylim = [min(ylimOrLonlim)-buffer_y max(ylimOrLonlim)+buffer_y]; + + +% Rows and columns of pixels to read: +rows=find((y>=ylim(1))&(y<=ylim(2))); +cols=find((x>=xlim(1))&(x<=xlim(2))); + + + + +%% Display messages if region of interest is partly or wholly outside the image region: + +if xlim(1)max(x) + disp('geoimread limits extend beyond the available image output in the x direction.') +end + +if ylim(1)max(y) + disp('geoimread limits extend beyond the available image output in the y direction.') +end + +if isempty(rows)||isempty(cols) + error('No image coordinates can be found inside the specified limits.') +end + +%% Load region of interest: +reductionlevel=0; +if reductionlevel==0 + rows=sort(rows([1 end])); + cols=sort(cols([1 end])); +else + %% Load region of interest: + dpx=2^reductionlevel; + rows=round(rows/dpx);cols=round(cols/dpx); + + rows=sort(rows([1 end])); + cols=sort(cols([1 end])); +end +x=x(cols(1):cols(end)); +y=y(rows(1):rows(end)); + +A=imread(filename,'PixelRegion',{rows cols}); + + +%% Update info structure to more accurately reflect the new image: + +if nargout == 4 + I.FileSize = numel(A); + I.Height = size(A,1); + I.Width = size(A,2); + try + I.TiePoints.WorldPoints.X = x(1); + I.TiePoints.WorldPoints.Y = y(1); + I.SpatialRef.RasterSize = [size(A,1),size(A,2)]; + I.RefMatrix(3,1) = x(1); + I.RefMatrix(3,2) = y(1); + I.BoundingBox = [min(x) min(y); max(x) max(y)]; + I.CornerCoords.X = [min(x) max(x) max(x) min(x)]; + I.CornerCoords.Y = [max(y) max(y) min(y) min(y)]; + %TODO: check whether GTRasterTypeGeoKey is RasterPixelIsArea or RasterPixelIsPoint + I.CornerCoords.Row = .5 + [0 0 size(A,1) size(A,1)]; %TODO: is this .5 always true? + I.CornerCoords.Col = .5 + [0 size(A,2) size(A,2) 0]; + [I.CornerCoords.Lat,I.CornerCoords.Lon] = projinv(I,I.CornerCoords.X,I.CornerCoords.Y); + I.GeoTIFFTags.ModelTiepointTag(4) = x(1); + I.GeoTIFFTags.ModelTiepointTag(5) = y(1); + I.SpatialRef.XLimWorld = [min(x),max(x)]; + I.SpatialRef.YLimWorld = [min(y),max(y)]; + catch,end +end + +%% Clean up: + + + + + + +function I=jp2tiffinfo(fname) +% +% This is a function that attempts to extract the geo-info from a jp2 file. +% There are two "standards" for how this can be stored: +% * GeoJP2: an embedded geotiff is stored in a "uuid" box with a specific id. +% * GMLJP2: an embedded gml file is in an asoc box. +% (extremely annoying that there are multiple "standards".) +% + +%Documents: +% JP2 standard: http://www.jpeg.org/public/15444-1annexi.pdf +% GEOJP2 standard: http://wiki.opf-labs.org/download/attachments/11337762/15444-1annexi.pdf +% GMLJP2 standard: http://www.opengeospatial.org/standards/gmljp2 + +fid=fopen(fname,'r','ieee-be'); +lblcontents=''; +while ~feof(fid) + lbox=fread(fid,1,'uint32'); + type=fread(fid,[1 4],'uint8=>char'); + switch lbox + case 1 + lbox=fread(fid,1,'uint64');lbox=lbox-16; + case 2:7 %"reserved reserved for ISO use" (????) + lbox=8;%No idea why ... there is no info on how to interpret values 2-7. + %Setting this to 8 works for sentinel-2 images. + otherwise + lbox=lbox-8; + end + + switch type + case 'asoc' + %the asoc box is a container with other boxes. Set lbox to zero + %so that we also parse the containing boxes. + lbox=0; + case 'lbl ' + %in gmljp2 the xml box is preceded by a lbl box. Read the + %lbl box contents for later... + lblcontents=fread(fid,[1 lbox],'uint8=>char'); + lbox=0; + case 'xml ' + if (strfind(lblcontents,'gml')>0) %gmljp2: + gmlcontents=fread(fid,[1 lbox],'uint8=>char'); + fout=fopen('.temp.xml','w'); + fwrite(fout,gmlcontents); + fclose(fout); + X=xmlread('.temp.xml'); + delete('.temp.xml'); + + Elements=X.getElementsByTagName('gml:origin'); + origin=str2num(Elements.item(0).getTextContent()); + Elements=X.getElementsByTagName('gml:offsetVector'); + R=[]; + for ii=1:Elements.getLength + R{ii}=str2num(Elements.item(ii-1).getTextContent()); + end + R=cell2mat(R'); + %ModelPixelScaleTag: [15 15 0] + %ModelTiepointTag: [0 0 0 4.695e+05 7240500 0] + assert((R(1,2)==0)&(R(2,1)==0),'unexpected offsetvector in GML'); + I=imfinfo(fname); + I.GML=gmlcontents; + I.ModelPixelScaleTag=[R(1,1) -R(2,2) 0]; %order and sign.. + I.ModelTiepointTag=[0 0 0 origin 0]; + fclose(fid); + return %we have what we need - return + end + case 'uuid' % + uuid=fread(fid,[1 16],'uint8=>char'); + lbox=lbox-16; + geo=[177 75 248 189 8 61 75 67 165 174 140 215 213 166 206 3]; + if all(uuid==geo) + fout=fopen('.temp.tif','w'); + contents=fread(fid,lbox,'uint8');lbox=0; + fwrite(fout,contents); + fclose(fout); + fclose(fid); + I=geotiffinfo('.temp.tif'); + m=imfinfo(fname); % a little silly to use imfinfo when I already have a tag reader + I.Height=m.Height; + I.Width=m.Width; + delete('.temp.tif'); + + fclose(fid); + return %we have what we need - return + end + end + + fseek(fid,lbox,0); + +end +fclose(fid); + + + +%--- BELOW is to make geoimread work even if no mapping toolbox (but with reduced functionality) --- + +function I = robustgeotiffinfo(fname) +if license('test','map_toolbox') + I=geotiffinfo(fname); +else + I=imfinfo(fname); + % I.ModelType='ModelTypeProjected'; %TODO: fix + % %TODO: generate home-made refmatrix(?).... + % if isfield(tags, 'ModelTransformationTag') && numel(tags.ModelTransformationTag) >= 8 + % geoimread does not work for rotated systems + % + % else %use ModelPixelScaleTag instead + % dx = I.ModelPixelScaleTag(1); dy = -I.ModelPixelScaleTag(2); + % x0 = I.ModelTiepointTag(4) - dx * I.ModelTiepointTag(1); + % y0 = I.ModelTiepointTag(5) - dy * I.ModelTiepointTag(2); + % J = [dx 0; 0 dy]; + % end + % I.RefMatrix=[flipud(J); x0-J(1,1)-J(1,2)]; +end + +function [x,y]=robustpixcenters(I) +if license('test','map_toolbox') && isfield(I,'GeoTIFFCodes') + [x,y]=pixcenters(I); +else + %I have not read documentation... but this only works for rectilinear systems. + assert(I.ModelPixelScaleTag(3)==0,'unexpected ModelPixelScaleTag format.'); + assert(all(I.ModelTiepointTag(1:3)==0),'unexpected ModelTiepointTag format.'); + x=((0:I.Width-1)-I.ModelTiepointTag(1))*I.ModelPixelScaleTag(1)+I.ModelTiepointTag(4); + y=((0:I.Height-1)-I.ModelTiepointTag(2))*-I.ModelPixelScaleTag(2)+I.ModelTiepointTag(5); +end \ No newline at end of file diff --git a/getRealCloudPosition.m b/getRealCloudPosition.m new file mode 100644 index 0000000..c6d29d5 --- /dev/null +++ b/getRealCloudPosition.m @@ -0,0 +1,15 @@ +function [x_new,y_new] = getRealCloudPosition(x,y,h,A,B,C,omiga_par,omiga_per,sensor_heigh_bias) + % imput "x",j col + % imput "y",i row + % imput cloud height "h" + H=705000-sensor_heigh_bias; % average Landsat 7 height (m) is 705000, but the height of cloud's surface should be excluded. + dist=(A*x+B*y+C)/((A^2+B^2)^(0.5));% from the cetral perpendicular (unit: pixel) + dist_par=dist/cos(omiga_per-omiga_par); + dist_move=dist_par.*double(h)/double(H); % cloud move distance (m); + delt_x=dist_move*cos(omiga_par); + delt_y=dist_move*sin(omiga_par); + + x_new=x+delt_x; % new x, j + y_new=y+delt_y; % new y, i +end + diff --git a/getRealCloudPositionS2.m b/getRealCloudPositionS2.m new file mode 100644 index 0000000..e94b843 --- /dev/null +++ b/getRealCloudPositionS2.m @@ -0,0 +1,22 @@ +function [x_new,y_new]=getRealCloudPositionS2(x,y,h,VZAxy,VAAxy,resolu) + % imput "x",j col + % imput "y",i row + % imput cloud height "h" + % H=786000; % average S2 height (m) + +% dist_move = h * tan(VZAxy) ./ resolu ; % but the height of cloud's surface should be excluded. +% +% % dist=(A*x+B*y+C)/((A^2+B^2)^(0.5));% from the cetral perpendicular (unit: pixel) +% % dist_par=dist/cos(omiga_per-omiga_par); +% % dist_move=dist_par.*h/H; % cloud move distance (m); +% delt_x=dist_move(2)*cos(pi/2-VAAxy); +% delt_y=dist_move(1)*-sin(pi/2-VAAxy); + + dist_move = h .* tan(VZAxy) ./ resolu ; % but the height of cloud's surface should be excluded. + + delt_x=dist_move(2).*cos(pi/2-VAAxy); + delt_y=dist_move(1).*-sin(pi/2-VAAxy); + + x_new=x+delt_x; % new x, j + y_new=y+delt_y; % new y, i +end \ No newline at end of file diff --git a/getSensorViewGeo.m b/getSensorViewGeo.m new file mode 100644 index 0000000..d011876 --- /dev/null +++ b/getSensorViewGeo.m @@ -0,0 +1,23 @@ +function [A,B,C,omiga_par,omiga_per] = getSensorViewGeo( x_ul,y_ul,x_ur,y_ur,x_ll,y_ll,x_lr,y_lr ) +% imput "x",j +% imput "y",i +% imput cloud height "h" + + x_u=(x_ul+x_ur)/2; + x_l=(x_ll+x_lr)/2; + y_u=(y_ul+y_ur)/2; + y_l=(y_ll+y_lr)/2; + + K_ulr=(y_ul-y_ur)/(x_ul-x_ur); % get k of the upper left and right points + K_llr=(y_ll-y_lr)/(x_ll-x_lr); % get k of the lower left and right points + K_aver=(K_ulr+K_llr)/2; + omiga_par=atan(K_aver); % get the angle of scan lines k (in pi) + + % AX(j)+BY(i)+C=0 + A=y_u-y_l; + B=x_l-x_u; + C=y_l*x_u-x_l*y_u; + + omiga_per=atan(B/A); % get the angle which is perpendicular to the track +end + diff --git a/getcoordinates.m b/getcoordinates.m new file mode 100644 index 0000000..093c117 --- /dev/null +++ b/getcoordinates.m @@ -0,0 +1,32 @@ +function [x,y] = getcoordinates(DEM) + +%GETCOORDINATES get coordinate vectors of an instance of GRIDobj +% +% Syntax +% +% [x,y] = getcoordinates(DEM) +% +% Input arguments +% +% DEM grid (class: GRIDobj) +% +% Output arguments +% +% x coordinate vector in x direction (row vector) +% y coordinate vector in y direction (column vector) +% +% Example +% +% DEM = GRIDobj('srtm_bigtujunga30m_utm11.tif'); +% [x,y] = getcoordinates(DEM); +% surf(x,y,double(DEM.Z)) +% axis image; shading interp; camlight +% +% +% +% See also: GRIDobj2mat +% +% Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) +% Date: 6. February, 2013 + +[x,y] = refmat2XY(DEM.refmat,DEM.size); \ No newline at end of file diff --git a/imcircle.m b/imcircle.m new file mode 100644 index 0000000..ca5af78 --- /dev/null +++ b/imcircle.m @@ -0,0 +1,99 @@ +function y = imcircle(n) + +% y = imcircle(n); +% +% Draw a solid circle of ones with diameter n pixels +% in a square of zero-valued pixels. +% +% Example: y = imcircle(50); +% +% For a hollow circle (line circle) of diameter n, +% add the instruction bwmorph from the Image Processing +% Toolbox. +% +% Example: y = bwmorph(imcircle(50),'remove'); + +if rem(n,1) > 0, + disp(sprintf('n is not an integer and has been rounded to %1.0f',round(n))) + n = round(n); +end + +if n < 1 % invalid n + error('n must be at least 1') + +elseif n < 4 % trivial n + y = ones(n); + +elseif rem(n,2) == 0, % even n + + DIAMETER = n; + diameter = n-1; + RADIUS = DIAMETER/2; + radius = diameter/2; + height_45 = round(radius/sqrt(2)); + width = zeros(1,RADIUS); + semicircle = zeros(DIAMETER,RADIUS); + + for i = 1 : height_45 + upward = i - 0.5; + sine = upward/radius; + cosine = sqrt(1-sine^2); + width(i) = ceil(cosine * radius); + end + + array = width(1:height_45)-height_45; + + for j = max(array):-1:min(array) + width(height_45 + j) = max(find(array == j)); + end + + if min(width) == 0 + index = find(width == 0); + width(index) = round(mean([width(index-1) width(index+1)])); + end + + width = [fliplr(width) width]; + + for k = 1 : DIAMETER + semicircle(k,1:width(k)) = ones(1,width(k)); + end + + y = [fliplr(semicircle) semicircle]; + +else % odd n + + DIAMETER = n; + diameter = n-1; + RADIUS = DIAMETER/2; + radius = diameter/2; + semicircle = zeros(DIAMETER,radius); + height_45 = round(radius/sqrt(2) - 0.5); + width = zeros(1,radius); + + for i = 1 : height_45 + upward = i; + sine = upward/radius; + cosine = sqrt(1-sine^2); + width(i) = ceil(cosine * radius - 0.5); + end + + array = width(1:height_45) - height_45; + + for j = max(array):-1:min(array) + width(height_45 + j) = max(find(array == j)); + end + + if min(width) == 0 + index = find(width == 0); + width(index) = round(mean([width(index-1) width(index+1)])); + end + + width = [fliplr(width) max(width) width]; + + for k = 1 : DIAMETER + semicircle(k,1:width(k)) = ones(1,width(k)); + end + + y = [fliplr(semicircle) ones(DIAMETER,1) semicircle]; + +end diff --git a/inpaint_nans.m b/inpaint_nans.m new file mode 100644 index 0000000..2460b51 --- /dev/null +++ b/inpaint_nans.m @@ -0,0 +1 @@ +function B=inpaint_nans(A,method) % INPAINT_NANS: in-paints over nans in an array % usage: B=INPAINT_NANS(A) % default method % usage: B=INPAINT_NANS(A,method) % specify method used % % Solves approximation to one of several pdes to % interpolate and extrapolate holes in an array % % arguments (input): % A - nxm array with some NaNs to be filled in % % method - (OPTIONAL) scalar numeric flag - specifies % which approach (or physical metaphor to use % for the interpolation.) All methods are capable % of extrapolation, some are better than others. % There are also speed differences, as well as % accuracy differences for smooth surfaces. % % methods {0,1,2} use a simple plate metaphor. % method 3 uses a better plate equation, % but may be much slower and uses % more memory. % method 4 uses a spring metaphor. % method 5 is an 8 neighbor average, with no % rationale behind it compared to the % other methods. I do not recommend % its use. % % method == 0 --> (DEFAULT) see method 1, but % this method does not build as large of a % linear system in the case of only a few % NaNs in a large array. % Extrapolation behavior is linear. % % method == 1 --> simple approach, applies del^2 % over the entire array, then drops those parts % of the array which do not have any contact with % NaNs. Uses a least squares approach, but it % does not modify known values. % In the case of small arrays, this method is % quite fast as it does very little extra work. % Extrapolation behavior is linear. % % method == 2 --> uses del^2, but solving a direct % linear system of equations for nan elements. % This method will be the fastest possible for % large systems since it uses the sparsest % possible system of equations. Not a least % squares approach, so it may be least robust % to noise on the boundaries of any holes. % This method will also be least able to % interpolate accurately for smooth surfaces. % Extrapolation behavior is linear. % % Note: method 2 has problems in 1-d, so this % method is disabled for vector inputs. % % method == 3 --+ See method 0, but uses del^4 for % the interpolating operator. This may result % in more accurate interpolations, at some cost % in speed. % % method == 4 --+ Uses a spring metaphor. Assumes % springs (with a nominal length of zero) % connect each node with every neighbor % (horizontally, vertically and diagonally) % Since each node tries to be like its neighbors, % extrapolation is as a constant function where % this is consistent with the neighboring nodes. % % method == 5 --+ See method 2, but use an average % of the 8 nearest neighbors to any element. % This method is NOT recommended for use. % % % arguments (output): % B - nxm array with NaNs replaced % % % Example: % [x,y] = meshgrid(0:.01:1); % z0 = exp(x+y); % znan = z0; % znan(20:50,40:70) = NaN; % znan(30:90,5:10) = NaN; % znan(70:75,40:90) = NaN; % % z = inpaint_nans(znan); % % % See also: griddata, interp1 % % Author: John D'Errico % e-mail address: woodchips@rochester.rr.com % Release: 2 % Release date: 4/15/06 % I always need to know which elements are NaN, % and what size the array is for any method [n,m]=size(A); A=A(:); nm=n*m; k=isnan(A(:)); % list the nodes which are known, and which will % be interpolated nan_list=find(k); known_list=find(~k); % how many nans overall nan_count=length(nan_list); % convert NaN indices to (r,c) form % nan_list==find(k) are the unrolled (linear) indices % (row,column) form [nr,nc]=ind2sub([n,m],nan_list); % both forms of index in one array: % column 1 == unrolled index % column 2 == row index % column 3 == column index nan_list=[nan_list,nr,nc]; % supply default method if (nargin<2) || isempty(method) method = 0; elseif ~ismember(method,0:5) error 'If supplied, method must be one of: {0,1,2,3,4,5}.' end % for different methods switch method case 0 % The same as method == 1, except only work on those % elements which are NaN, or at least touch a NaN. % is it 1-d or 2-d? if (m == 1) || (n == 1) % really a 1-d case work_list = nan_list(:,1); work_list = unique([work_list;work_list - 1;work_list + 1]); work_list(work_list <= 1) = []; work_list(work_list >= nm) = []; nw = numel(work_list); u = (1:nw)'; fda = sparse(repmat(u,1,3),bsxfun(@plus,work_list,-1:1), ... repmat([1 -2 1],nw,1),nw,nm); else % a 2-d case % horizontal and vertical neighbors only talks_to = [-1 0;0 -1;1 0;0 1]; neighbors_list=identify_neighbors(n,m,nan_list,talks_to); % list of all nodes we have identified all_list=[nan_list;neighbors_list]; % generate sparse array with second partials on row % variable for each element in either list, but only % for those nodes which have a row index > 1 or < n L = find((all_list(:,2) > 1) & (all_list(:,2) < n)); nl=length(L); if nl>0 fda=sparse(repmat(all_list(L,1),1,3), ... repmat(all_list(L,1),1,3)+repmat([-1 0 1],nl,1), ... repmat([1 -2 1],nl,1),nm,nm); else fda=spalloc(n*m,n*m,size(all_list,1)*5); end % 2nd partials on column index L = find((all_list(:,3) > 1) & (all_list(:,3) < m)); nl=length(L); if nl>0 fda=fda+sparse(repmat(all_list(L,1),1,3), ... repmat(all_list(L,1),1,3)+repmat([-n 0 n],nl,1), ... repmat([1 -2 1],nl,1),nm,nm); end end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); k=find(any(fda(:,nan_list(:,1)),2)); % and solve... B=A; B(nan_list(:,1))=fda(k,nan_list(:,1))\rhs(k); case 1 % least squares approach with del^2. Build system % for every array element as an unknown, and then % eliminate those which are knowns. % Build sparse matrix approximating del^2 for % every element in A. % is it 1-d or 2-d? if (m == 1) || (n == 1) % a 1-d case u = (1:(nm-2))'; fda = sparse(repmat(u,1,3),bsxfun(@plus,u,0:2), ... repmat([1 -2 1],nm-2,1),nm-2,nm); else % a 2-d case % Compute finite difference for second partials % on row variable first [i,j]=ndgrid(2:(n-1),1:m); ind=i(:)+(j(:)-1)*n; np=(n-2)*m; fda=sparse(repmat(ind,1,3),[ind-1,ind,ind+1], ... repmat([1 -2 1],np,1),n*m,n*m); % now second partials on column variable [i,j]=ndgrid(1:n,2:(m-1)); ind=i(:)+(j(:)-1)*n; np=n*(m-2); fda=fda+sparse(repmat(ind,1,3),[ind-n,ind,ind+n], ... repmat([1 -2 1],np,1),nm,nm); end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); k=find(any(fda(:,nan_list),2)); % and solve... B=A; B(nan_list(:,1))=fda(k,nan_list(:,1))\rhs(k); case 2 % Direct solve for del^2 BVP across holes % generate sparse array with second partials on row % variable for each nan element, only for those nodes % which have a row index > 1 or < n % is it 1-d or 2-d? if (m == 1) || (n == 1) % really just a 1-d case error('Method 2 has problems for vector input. Please use another method.') else % a 2-d case L = find((nan_list(:,2) > 1) & (nan_list(:,2) < n)); nl=length(L); if nl>0 fda=sparse(repmat(nan_list(L,1),1,3), ... repmat(nan_list(L,1),1,3)+repmat([-1 0 1],nl,1), ... repmat([1 -2 1],nl,1),n*m,n*m); else fda=spalloc(n*m,n*m,size(nan_list,1)*5); end % 2nd partials on column index L = find((nan_list(:,3) > 1) & (nan_list(:,3) < m)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,3), ... repmat(nan_list(L,1),1,3)+repmat([-n 0 n],nl,1), ... repmat([1 -2 1],nl,1),n*m,n*m); end % fix boundary conditions at extreme corners % of the array in case there were nans there if ismember(1,nan_list(:,1)) fda(1,[1 2 n+1])=[-2 1 1]; end if ismember(n,nan_list(:,1)) fda(n,[n, n-1,n+n])=[-2 1 1]; end if ismember(nm-n+1,nan_list(:,1)) fda(nm-n+1,[nm-n+1,nm-n+2,nm-n])=[-2 1 1]; end if ismember(nm,nan_list(:,1)) fda(nm,[nm,nm-1,nm-n])=[-2 1 1]; end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); % and solve... B=A; k=nan_list(:,1); B(k)=fda(k,k)\rhs(k); end case 3 % The same as method == 0, except uses del^4 as the % interpolating operator. % del^4 template of neighbors talks_to = [-2 0;-1 -1;-1 0;-1 1;0 -2;0 -1; ... 0 1;0 2;1 -1;1 0;1 1;2 0]; neighbors_list=identify_neighbors(n,m,nan_list,talks_to); % list of all nodes we have identified all_list=[nan_list;neighbors_list]; % generate sparse array with del^4, but only % for those nodes which have a row & column index % >= 3 or <= n-2 L = find( (all_list(:,2) >= 3) & ... (all_list(:,2) <= (n-2)) & ... (all_list(:,3) >= 3) & ... (all_list(:,3) <= (m-2))); nl=length(L); if nl>0 % do the entire template at once fda=sparse(repmat(all_list(L,1),1,13), ... repmat(all_list(L,1),1,13) + ... repmat([-2*n,-n-1,-n,-n+1,-2,-1,0,1,2,n-1,n,n+1,2*n],nl,1), ... repmat([1 2 -8 2 1 -8 20 -8 1 2 -8 2 1],nl,1),nm,nm); else fda=spalloc(n*m,n*m,size(all_list,1)*5); end % on the boundaries, reduce the order around the edges L = find((((all_list(:,2) == 2) | ... (all_list(:,2) == (n-1))) & ... (all_list(:,3) >= 2) & ... (all_list(:,3) <= (m-1))) | ... (((all_list(:,3) == 2) | ... (all_list(:,3) == (m-1))) & ... (all_list(:,2) >= 2) & ... (all_list(:,2) <= (n-1)))); nl=length(L); if nl>0 fda=fda+sparse(repmat(all_list(L,1),1,5), ... repmat(all_list(L,1),1,5) + ... repmat([-n,-1,0,+1,n],nl,1), ... repmat([1 1 -4 1 1],nl,1),nm,nm); end L = find( ((all_list(:,2) == 1) | ... (all_list(:,2) == n)) & ... (all_list(:,3) >= 2) & ... (all_list(:,3) <= (m-1))); nl=length(L); if nl>0 fda=fda+sparse(repmat(all_list(L,1),1,3), ... repmat(all_list(L,1),1,3) + ... repmat([-n,0,n],nl,1), ... repmat([1 -2 1],nl,1),nm,nm); end L = find( ((all_list(:,3) == 1) | ... (all_list(:,3) == m)) & ... (all_list(:,2) >= 2) & ... (all_list(:,2) <= (n-1))); nl=length(L); if nl>0 fda=fda+sparse(repmat(all_list(L,1),1,3), ... repmat(all_list(L,1),1,3) + ... repmat([-1,0,1],nl,1), ... repmat([1 -2 1],nl,1),nm,nm); end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); k=find(any(fda(:,nan_list(:,1)),2)); % and solve... B=A; B(nan_list(:,1))=fda(k,nan_list(:,1))\rhs(k); case 4 % Spring analogy % interpolating operator. % list of all springs between a node and a horizontal % or vertical neighbor hv_list=[-1 -1 0;1 1 0;-n 0 -1;n 0 1]; hv_springs=[]; for i=1:4 hvs=nan_list+repmat(hv_list(i,:),nan_count,1); k=(hvs(:,2)>=1) & (hvs(:,2)<=n) & (hvs(:,3)>=1) & (hvs(:,3)<=m); hv_springs=[hv_springs;[nan_list(k,1),hvs(k,1)]]; end % delete replicate springs hv_springs=unique(sort(hv_springs,2),'rows'); % build sparse matrix of connections, springs % connecting diagonal neighbors are weaker than % the horizontal and vertical springs nhv=size(hv_springs,1); springs=sparse(repmat((1:nhv)',1,2),hv_springs, ... repmat([1 -1],nhv,1),nhv,nm); % eliminate knowns rhs=-springs(:,known_list)*A(known_list); % and solve... B=A; B(nan_list(:,1))=springs(:,nan_list(:,1))\rhs; case 5 % Average of 8 nearest neighbors % generate sparse array to average 8 nearest neighbors % for each nan element, be careful around edges fda=spalloc(n*m,n*m,size(nan_list,1)*9); % -1,-1 L = find((nan_list(:,2) > 1) & (nan_list(:,3) > 1)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([-n-1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % 0,-1 L = find(nan_list(:,3) > 1); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([-n, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % +1,-1 L = find((nan_list(:,2) < n) & (nan_list(:,3) > 1)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([-n+1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % -1,0 L = find(nan_list(:,2) > 1); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([-1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % +1,0 L = find(nan_list(:,2) < n); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % -1,+1 L = find((nan_list(:,2) > 1) & (nan_list(:,3) < m)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([n-1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % 0,+1 L = find(nan_list(:,3) < m); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([n, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % +1,+1 L = find((nan_list(:,2) < n) & (nan_list(:,3) < m)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([n+1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); % and solve... B=A; k=nan_list(:,1); B(k)=fda(k,k)\rhs(k); end % all done, make sure that B is the same shape as % A was when we came in. B=reshape(B,n,m); % ==================================================== % end of main function % ==================================================== % ==================================================== % begin subfunctions % ==================================================== function neighbors_list=identify_neighbors(n,m,nan_list,talks_to) % identify_neighbors: identifies all the neighbors of % those nodes in nan_list, not including the nans % themselves % % arguments (input): % n,m - scalar - [n,m]=size(A), where A is the % array to be interpolated % nan_list - array - list of every nan element in A % nan_list(i,1) == linear index of i'th nan element % nan_list(i,2) == row index of i'th nan element % nan_list(i,3) == column index of i'th nan element % talks_to - px2 array - defines which nodes communicate % with each other, i.e., which nodes are neighbors. % % talks_to(i,1) - defines the offset in the row % dimension of a neighbor % talks_to(i,2) - defines the offset in the column % dimension of a neighbor % % For example, talks_to = [-1 0;0 -1;1 0;0 1] % means that each node talks only to its immediate % neighbors horizontally and vertically. % % arguments(output): % neighbors_list - array - list of all neighbors of % all the nodes in nan_list if ~isempty(nan_list) % use the definition of a neighbor in talks_to nan_count=size(nan_list,1); talk_count=size(talks_to,1); nn=zeros(nan_count*talk_count,2); j=[1,nan_count]; for i=1:talk_count nn(j(1):j(2),:)=nan_list(:,2:3) + ... repmat(talks_to(i,:),nan_count,1); j=j+nan_count; end % drop those nodes which fall outside the bounds of the % original array L = (nn(:,1)<1)|(nn(:,1)>n)|(nn(:,2)<1)|(nn(:,2)>m); nn(L,:)=[]; % form the same format 3 column array as nan_list neighbors_list=[sub2ind([n,m],nn(:,1),nn(:,2)),nn]; % delete replicates in the neighbors list neighbors_list=unique(neighbors_list,'rows'); % and delete those which are also in the list of NaNs. neighbors_list=setdiff(neighbors_list,nan_list,'rows'); else neighbors_list=[]; end \ No newline at end of file diff --git a/lndhdrread.m b/lndhdrread.m new file mode 100644 index 0000000..95e8c93 --- /dev/null +++ b/lndhdrread.m @@ -0,0 +1,371 @@ +function [Lmax,Lmin,Qcalmax,Qcalmin,Refmax,Refmin,ijdim_ref,ijdim_thm,reso_ref,... + reso_thm,ul,bbox,zen,azi,zc,Lnum,doy]=lndhdrread(fullfile_path,filename) +% Revisions: +% Read in the latitude and longtitude for automate downloading DEM (Shi 7/9/2017) +% Read in the product level for Landsat data (Shi 28/03/2016) +% Read in the metadata for Landsat 8 (Zhe 04/04/2013) +% Read in the old or new metadata for Landsat TM/ETM+ images (Zhe 10/18/2012) +% [Lmax,Lmin,Qcalmax,Qcalmin,Refmax,Refmin,ijdim_ref,ijdim_thm,reso_ref,... +% reso_thm,ul,zen,azi,zc,Lnum,doy]=lndhdrread(filename) +% Where: +% Inputs: +% filename='L*MTL.txt'; +% Outputs: +% 1) Lmax = Max radiances; +% 2) Lmin = Min radiances; +% 3) Qcalmax = Max calibrated DNs; +% 4) Qcalmin = Min calibrated DNs; +% 5) ijdim_ref = [nrows,ncols]; % dimension of optical bands +% 6) ijdim_ref = [nrows,ncols]; % dimension of thermal band +% 7) reo_ref = 28/30; % resolution of optical bands +% 8) reo_thm = 60/120; % resolution of thermal band +% 9) ul = [upperleft_mapx upperleft_mapy]; +% 10) zen = solar zenith angle (degrees); +% 11) azi = solar azimuth angle (degrees); +% 12) zc = Zone Number +% 13) Lnum = 4,5,or 7; Landsat sensor number +% 14) doy = day of year (1,2,3,...,356); +% 15) pro_type=PRODUCT_TYPE (L1G: DEM bias; others: no DEM bias). +% +%% +% open and read hdr file +fid_in=fopen(fullfile(fullfile_path,filename),'r'); +geo_char=fscanf(fid_in,'%c',inf); +fclose(fid_in); +geo_char=geo_char'; +geo_str=strread(geo_char,'%s'); + +% initialize Refmax & Refmin +Refmax = -1; +Refmin = -1; + +% Identify Landsat Number (Lnum = 4, 5, 7, or 8) +LID=char(geo_str(strmatch('SPACECRAFT_ID',geo_str)+2)); +Lnum=str2double(LID(end-1)); + +if Lnum >= 4 && Lnum <=7 + % determine the metadata type "old" or "new" + if isempty(strmatch('LANDSAT_SCENE_ID',geo_str)) == false % "new" MTL.txt + % read in LMAX + Lmax_B1 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_1',geo_str)+2)); + Lmax_B2 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_2',geo_str)+2)); + Lmax_B3 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_3',geo_str)+2)); + Lmax_B4 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_4',geo_str)+2)); + Lmax_B5 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_5',geo_str)+2)); + if Lnum==7 + Lmax_B6 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_6_VCID_1',geo_str)+2)); + else + Lmax_B6 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_6',geo_str)+2)); + end + Lmax_B7 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_7',geo_str)+2)); + Lmax=[Lmax_B1,Lmax_B2,Lmax_B3,Lmax_B4,Lmax_B5,Lmax_B6,Lmax_B7]; + + % Read in LMIN + Lmin_B1 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_1',geo_str)+2)); + Lmin_B2 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_2',geo_str)+2)); + Lmin_B3 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_3',geo_str)+2)); + Lmin_B4 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_4',geo_str)+2)); + Lmin_B5 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_5',geo_str)+2)); + if Lnum==7 + Lmin_B6 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_6_VCID_1',geo_str)+2)); + else + Lmin_B6 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_6',geo_str)+2)); + end + Lmin_B7 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_7',geo_str)+2)); + Lmin=[Lmin_B1,Lmin_B2,Lmin_B3,Lmin_B4,Lmin_B5,Lmin_B6,Lmin_B7]; + + % Read in QCALMAX + Qcalmax_B1 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_1',geo_str)+2)); + Qcalmax_B2 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_2',geo_str)+2)); + Qcalmax_B3 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_3',geo_str)+2)); + Qcalmax_B4 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_4',geo_str)+2)); + Qcalmax_B5 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_5',geo_str)+2)); + if Lnum==7 + Qcalmax_B6 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_6_VCID_1',geo_str)+2)); + else + Qcalmax_B6 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_6',geo_str)+2)); + end + Qcalmax_B7 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_7',geo_str)+2)); + Qcalmax=[Qcalmax_B1,Qcalmax_B2,Qcalmax_B3,Qcalmax_B4,Qcalmax_B5,Qcalmax_B6,Qcalmax_B7]; + + % Read in QCALMIN + Qcalmin_B1 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_1',geo_str)+2)); + Qcalmin_B2 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_2',geo_str)+2)); + Qcalmin_B3 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_3',geo_str)+2)); + Qcalmin_B4 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_4',geo_str)+2)); + Qcalmin_B5 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_5',geo_str)+2)); + if Lnum==7 + Qcalmin_B6 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_6_VCID_1',geo_str)+2)); + else + Qcalmin_B6 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_6',geo_str)+2)); + end + Qcalmin_B7 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_7',geo_str)+2)); + Qcalmin=[Qcalmin_B1,Qcalmin_B2,Qcalmin_B3,Qcalmin_B4,Qcalmin_B5,Qcalmin_B6,Qcalmin_B7]; + + % Read in nrows & ncols of optical bands + Sample_ref = str2double(geo_str(strmatch('REFLECTIVE_SAMPLES',geo_str)+2)); + Line_ref = str2double(geo_str(strmatch('REFLECTIVE_LINES',geo_str)+2)); + % record ijdimension of optical bands + ijdim_ref=[Line_ref,Sample_ref]; + + Sample_thm = str2double(geo_str(strmatch('THERMAL_SAMPLES',geo_str)+2)); + Line_thm = str2double(geo_str(strmatch('THERMAL_LINES',geo_str)+2)); + % record thermal band dimensions (i,j) + ijdim_thm=[Line_thm,Sample_thm]; + + % Read in resolution of optical and thermal bands + reso_ref = str2double(geo_str(strmatch('GRID_CELL_SIZE_REFLECTIVE',geo_str)+2)); + reso_thm = str2double(geo_str(strmatch('GRID_CELL_SIZE_THERMAL',geo_str)+2)); + + % Read in UTM Zone Number + zc=str2double(geo_str(strmatch('UTM_ZONE',geo_str)+2)); + % Read in Solar Azimuth & Elevation angle (degrees) + azi=str2double(geo_str(strmatch('SUN_AZIMUTH',geo_str)+2)); + zen=90-str2double(geo_str(strmatch('SUN_ELEVATION',geo_str)+2)); + % Read in upperleft mapx,y + ulx=str2double(geo_str(strmatch('CORNER_UL_PROJECTION_X_PRODUCT',geo_str)+2)); + uly=str2double(geo_str(strmatch('CORNER_UL_PROJECTION_Y_PRODUCT',geo_str)+2)); + ul=[ulx,uly]; + % Read in date of year + char_doy=char(geo_str(strmatch('LANDSAT_SCENE_ID',geo_str)+2)); + doy=str2double(char_doy(15:17)); + + % Read in latidtude and longtitude. + ul_lat=str2double(geo_str(strmatch('CORNER_UL_LAT_PRODUCT',geo_str)+2)); + ul_lon=str2double(geo_str(strmatch('CORNER_UL_LON_PRODUCT',geo_str)+2)); + ur_lat=str2double(geo_str(strmatch('CORNER_UR_LAT_PRODUCT',geo_str)+2)); + ur_lon=str2double(geo_str(strmatch('CORNER_UR_LON_PRODUCT',geo_str)+2)); + ll_lat=str2double(geo_str(strmatch('CORNER_LL_LAT_PRODUCT',geo_str)+2)); + ll_lon=str2double(geo_str(strmatch('CORNER_LL_LON_PRODUCT',geo_str)+2)); + lr_lat=str2double(geo_str(strmatch('CORNER_LR_LAT_PRODUCT',geo_str)+2)); + lr_lon=str2double(geo_str(strmatch('CORNER_LR_LON_PRODUCT',geo_str)+2)); + + north=max(ul_lat,ur_lat); + south=min(ll_lat,lr_lat); + west=min(ul_lon,ll_lon); + east=max(ur_lon,lr_lon); + bbox=[north,south,west,east]; + + + else % "old" MTL.txt + % read in LMAX + Lmax_B1 = str2double(geo_str(strmatch('LMAX_BAND1',geo_str)+2)); + Lmax_B2 = str2double(geo_str(strmatch('LMAX_BAND2',geo_str)+2)); + Lmax_B3 = str2double(geo_str(strmatch('LMAX_BAND3',geo_str)+2)); + Lmax_B4 = str2double(geo_str(strmatch('LMAX_BAND4',geo_str)+2)); + Lmax_B5 = str2double(geo_str(strmatch('LMAX_BAND5',geo_str)+2)); + if Lnum==7 + Lmax_B6 = str2double(geo_str(strmatch('LMAX_BAND61',geo_str)+2)); + else + Lmax_B6 = str2double(geo_str(strmatch('LMAX_BAND6',geo_str)+2)); + end + Lmax_B7 = str2double(geo_str(strmatch('LMAX_BAND7',geo_str)+2)); + Lmax=[Lmax_B1,Lmax_B2,Lmax_B3,Lmax_B4,Lmax_B5,Lmax_B6,Lmax_B7]; + + % Read in LMIN + Lmin_B1 = str2double(geo_str(strmatch('LMIN_BAND1',geo_str)+2)); + Lmin_B2 = str2double(geo_str(strmatch('LMIN_BAND2',geo_str)+2)); + Lmin_B3 = str2double(geo_str(strmatch('LMIN_BAND3',geo_str)+2)); + Lmin_B4 = str2double(geo_str(strmatch('LMIN_BAND4',geo_str)+2)); + Lmin_B5 = str2double(geo_str(strmatch('LMIN_BAND5',geo_str)+2)); + if Lnum==7 + Lmin_B6 = str2double(geo_str(strmatch('LMIN_BAND61',geo_str)+2)); + else + Lmin_B6 = str2double(geo_str(strmatch('LMIN_BAND6',geo_str)+2)); + end + Lmin_B7 = str2double(geo_str(strmatch('LMIN_BAND7',geo_str)+2)); + Lmin=[Lmin_B1,Lmin_B2,Lmin_B3,Lmin_B4,Lmin_B5,Lmin_B6,Lmin_B7]; + + % Read in QCALMAX + Qcalmax_B1 = str2double(geo_str(strmatch('QCALMAX_BAND1',geo_str)+2)); + Qcalmax_B2 = str2double(geo_str(strmatch('QCALMAX_BAND2',geo_str)+2)); + Qcalmax_B3 = str2double(geo_str(strmatch('QCALMAX_BAND3',geo_str)+2)); + Qcalmax_B4 = str2double(geo_str(strmatch('QCALMAX_BAND4',geo_str)+2)); + Qcalmax_B5 = str2double(geo_str(strmatch('QCALMAX_BAND5',geo_str)+2)); + if Lnum==7 + Qcalmax_B6 = str2double(geo_str(strmatch('QCALMAX_BAND61',geo_str)+2)); + else + Qcalmax_B6 = str2double(geo_str(strmatch('QCALMAX_BAND6',geo_str)+2)); + end + Qcalmax_B7 = str2double(geo_str(strmatch('QCALMAX_BAND7',geo_str)+2)); + Qcalmax=[Qcalmax_B1,Qcalmax_B2,Qcalmax_B3,Qcalmax_B4,Qcalmax_B5,Qcalmax_B6,Qcalmax_B7]; + + % Read in QCALMIN + Qcalmin_B1 = str2double(geo_str(strmatch('QCALMIN_BAND1',geo_str)+2)); + Qcalmin_B2 = str2double(geo_str(strmatch('QCALMIN_BAND2',geo_str)+2)); + Qcalmin_B3 = str2double(geo_str(strmatch('QCALMIN_BAND3',geo_str)+2)); + Qcalmin_B4 = str2double(geo_str(strmatch('QCALMIN_BAND4',geo_str)+2)); + Qcalmin_B5 = str2double(geo_str(strmatch('QCALMIN_BAND5',geo_str)+2)); + if Lnum==7 + Qcalmin_B6 = str2double(geo_str(strmatch('QCALMIN_BAND61',geo_str)+2)); + else + Qcalmin_B6 = str2double(geo_str(strmatch('QCALMIN_BAND6',geo_str)+2)); + end + Qcalmin_B7 = str2double(geo_str(strmatch('QCALMIN_BAND7',geo_str)+2)); + Qcalmin=[Qcalmin_B1,Qcalmin_B2,Qcalmin_B3,Qcalmin_B4,Qcalmin_B5,Qcalmin_B6,Qcalmin_B7]; + + % Read in nrows & ncols of optical bands + Sample_ref = str2double(geo_str(strmatch('PRODUCT_SAMPLES_REF',geo_str)+2)); + Line_ref = str2double(geo_str(strmatch('PRODUCT_LINES_REF',geo_str)+2)); + % record ijdimension of optical bands + ijdim_ref=[Line_ref,Sample_ref]; + + Sample_thm = str2double(geo_str(strmatch('PRODUCT_SAMPLES_THM',geo_str)+2)); + Line_thm = str2double(geo_str(strmatch('PRODUCT_LINES_THM',geo_str)+2)); + % record thermal band dimensions (i,j) + ijdim_thm=[Line_thm,Sample_thm]; + + % Read in resolution of optical and thermal bands + reso_ref = str2double(geo_str(strmatch('GRID_CELL_SIZE_REF',geo_str)+2)); + reso_thm = str2double(geo_str(strmatch('GRID_CELL_SIZE_THM',geo_str)+2)); + + % Read in UTM Zone Number + zc=str2double(geo_str(strmatch('ZONE_NUMBER',geo_str)+2)); + % Read in Solar Azimuth & Elevation angle (degrees) + azi=str2double(geo_str(strmatch('SUN_AZIMUTH',geo_str)+2)); + zen=90-str2double(geo_str(strmatch('SUN_ELEVATION',geo_str)+2)); + % Read in upperleft mapx,y + ulx=str2double(geo_str(strmatch('PRODUCT_UL_CORNER_MAPX',geo_str)+2)); + uly=str2double(geo_str(strmatch('PRODUCT_UL_CORNER_MAPY',geo_str)+2)); + ul=[ulx,uly]; + % Read in date of year + char_doy=char(geo_str(strmatch('DATEHOUR_CONTACT_PERIOD',geo_str)+2)); + doy=str2double(char_doy(4:6)); + + % Read in latidtude and longtitude. + ul_lat=str2double(geo_str(strmatch('PRODUCT_UL_CORNER_LAT',geo_str)+2)); + ul_lon=str2double(geo_str(strmatch('PRODUCT_UL_CORNER_LON',geo_str)+2)); + ur_lat=str2double(geo_str(strmatch('PRODUCT_UR_CORNER_LAT',geo_str)+2)); + ur_lon=str2double(geo_str(strmatch('PRODUCT_UR_CORNER_LON',geo_str)+2)); + ll_lat=str2double(geo_str(strmatch('PRODUCT_LL_CORNER_LAT',geo_str)+2)); + ll_lon=str2double(geo_str(strmatch('PRODUCT_LL_CORNER_LON',geo_str)+2)); + lr_lat=str2double(geo_str(strmatch('PRODUCT_LR_CORNER_LAT',geo_str)+2)); + lr_lon=str2double(geo_str(strmatch('PRODUCT_LR_CORNER_LON',geo_str)+2)); + + north=max(ul_lat,ur_lat); + south=min(ll_lat,lr_lat); + west=min(ul_lon,ll_lon); + east=max(ur_lon,lr_lon); + bbox=[north,south,west,east]; + + end +elseif Lnum == 8 + % read in LMAX + Lmax_B2 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_2',geo_str)+2)); + Lmax_B3 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_3',geo_str)+2)); + Lmax_B4 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_4',geo_str)+2)); + Lmax_B5 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_5',geo_str)+2)); + Lmax_B6 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_6',geo_str)+2)); + Lmax_B7 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_7',geo_str)+2)); + Lmax_B9 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_9',geo_str)+2)); + Lmax_B10 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_10',geo_str)+2)); + + Lmax=[Lmax_B2,Lmax_B3,Lmax_B4,Lmax_B5,Lmax_B6,Lmax_B7,Lmax_B9,Lmax_B10]; + + % read in LMIN + Lmin_B2 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_2',geo_str)+2)); + Lmin_B3 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_3',geo_str)+2)); + Lmin_B4 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_4',geo_str)+2)); + Lmin_B5 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_5',geo_str)+2)); + Lmin_B6 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_6',geo_str)+2)); + Lmin_B7 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_7',geo_str)+2)); + Lmin_B9 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_9',geo_str)+2)); + Lmin_B10 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_10',geo_str)+2)); + + Lmin=[Lmin_B2,Lmin_B3,Lmin_B4,Lmin_B5,Lmin_B6,Lmin_B7,Lmin_B9,Lmin_B10]; + + % Read in QCALMAX + Qcalmax_B2 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_2',geo_str)+2)); + Qcalmax_B3 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_3',geo_str)+2)); + Qcalmax_B4 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_4',geo_str)+2)); + Qcalmax_B5 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_5',geo_str)+2)); + Qcalmax_B6 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_6',geo_str)+2)); + Qcalmax_B7 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_7',geo_str)+2)); + Qcalmax_B9 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_9',geo_str)+2)); + Qcalmax_B10 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_10',geo_str)+2)); + Qcalmax=[Qcalmax_B2,Qcalmax_B3,Qcalmax_B4,Qcalmax_B5,Qcalmax_B6,Qcalmax_B7,Qcalmax_B9,Qcalmax_B10]; + + % Read in QCALMIN + Qcalmin_B2 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_2',geo_str)+2)); + Qcalmin_B3 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_3',geo_str)+2)); + Qcalmin_B4 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_4',geo_str)+2)); + Qcalmin_B5 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_5',geo_str)+2)); + Qcalmin_B6 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_6',geo_str)+2)); + Qcalmin_B7 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_7',geo_str)+2)); + Qcalmin_B9 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_9',geo_str)+2)); + Qcalmin_B10 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_10',geo_str)+2)); + Qcalmin=[Qcalmin_B2,Qcalmin_B3,Qcalmin_B4,Qcalmin_B5,Qcalmin_B6,Qcalmin_B7,Qcalmin_B9,Qcalmin_B10]; + + % read in Refmax + Refmax_B2 = str2double(geo_str(strmatch('REFLECTANCE_MAXIMUM_BAND_2',geo_str)+2)); + Refmax_B3 = str2double(geo_str(strmatch('REFLECTANCE_MAXIMUM_BAND_3',geo_str)+2)); + Refmax_B4 = str2double(geo_str(strmatch('REFLECTANCE_MAXIMUM_BAND_4',geo_str)+2)); + Refmax_B5 = str2double(geo_str(strmatch('REFLECTANCE_MAXIMUM_BAND_5',geo_str)+2)); + Refmax_B6 = str2double(geo_str(strmatch('REFLECTANCE_MAXIMUM_BAND_6',geo_str)+2)); + Refmax_B7 = str2double(geo_str(strmatch('REFLECTANCE_MAXIMUM_BAND_7',geo_str)+2)); + Refmax_B9 = str2double(geo_str(strmatch('REFLECTANCE_MAXIMUM_BAND_9',geo_str)+2)); + + Refmax=[Refmax_B2,Refmax_B3,Refmax_B4,Refmax_B5,Refmax_B6,Refmax_B7,Refmax_B9]; + + % read in Refmin + Refmin_B2 = str2double(geo_str(strmatch('REFLECTANCE_MINIMUM_BAND_2',geo_str)+2)); + Refmin_B3 = str2double(geo_str(strmatch('REFLECTANCE_MINIMUM_BAND_3',geo_str)+2)); + Refmin_B4 = str2double(geo_str(strmatch('REFLECTANCE_MINIMUM_BAND_4',geo_str)+2)); + Refmin_B5 = str2double(geo_str(strmatch('REFLECTANCE_MINIMUM_BAND_5',geo_str)+2)); + Refmin_B6 = str2double(geo_str(strmatch('REFLECTANCE_MINIMUM_BAND_6',geo_str)+2)); + Refmin_B7 = str2double(geo_str(strmatch('REFLECTANCE_MINIMUM_BAND_7',geo_str)+2)); + Refmin_B9 = str2double(geo_str(strmatch('REFLECTANCE_MINIMUM_BAND_9',geo_str)+2)); + + Refmin=[Refmin_B2,Refmin_B3,Refmin_B4,Refmin_B5,Refmin_B6,Refmin_B7,Refmin_B9]; + + % Read in nrows & ncols of optical bands + Sample_ref = str2double(geo_str(strmatch('REFLECTIVE_SAMPLES',geo_str)+2)); + Line_ref = str2double(geo_str(strmatch('REFLECTIVE_LINES',geo_str)+2)); + % record ijdimension of optical bands + ijdim_ref=[Line_ref,Sample_ref]; + + Sample_thm = str2double(geo_str(strmatch('THERMAL_SAMPLES',geo_str)+2)); + Line_thm = str2double(geo_str(strmatch('THERMAL_LINES',geo_str)+2)); + % record thermal band dimensions (i,j) + ijdim_thm=[Line_thm,Sample_thm]; + + % Read in resolution of optical and thermal bands + reso_ref = str2double(geo_str(strmatch('GRID_CELL_SIZE_REFLECTIVE',geo_str)+2)); + reso_thm = str2double(geo_str(strmatch('GRID_CELL_SIZE_THERMAL',geo_str)+2)); + + % Read in UTM Zone Number + zc=str2double(geo_str(strmatch('UTM_ZONE',geo_str)+2)); + % Read in Solar Azimuth & Elevation angle (degrees) + azi=str2double(geo_str(strmatch('SUN_AZIMUTH',geo_str)+2)); + zen=90-str2double(geo_str(strmatch('SUN_ELEVATION',geo_str)+2)); + % Read in upperleft mapx,y + ulx=str2double(geo_str(strmatch('CORNER_UL_PROJECTION_X_PRODUCT',geo_str)+2)); + uly=str2double(geo_str(strmatch('CORNER_UL_PROJECTION_Y_PRODUCT',geo_str)+2)); + ul=[ulx,uly]; + % Read in latidtude and longtitude. + ul_lat=str2double(geo_str(strmatch('CORNER_UL_LAT_PRODUCT',geo_str)+2)); + ul_lon=str2double(geo_str(strmatch('CORNER_UL_LON_PRODUCT',geo_str)+2)); + ur_lat=str2double(geo_str(strmatch('CORNER_UR_LAT_PRODUCT',geo_str)+2)); + ur_lon=str2double(geo_str(strmatch('CORNER_UR_LON_PRODUCT',geo_str)+2)); + ll_lat=str2double(geo_str(strmatch('CORNER_LL_LAT_PRODUCT',geo_str)+2)); + ll_lon=str2double(geo_str(strmatch('CORNER_LL_LON_PRODUCT',geo_str)+2)); + lr_lat=str2double(geo_str(strmatch('CORNER_LR_LAT_PRODUCT',geo_str)+2)); + lr_lon=str2double(geo_str(strmatch('CORNER_LR_LON_PRODUCT',geo_str)+2)); + + north=max(ul_lat,ur_lat); + south=min(ll_lat,lr_lat); + west=min(ul_lon,ll_lon); + east=max(ur_lon,lr_lon); + bbox=[north,south,west,east]; + + + % Read in date of year + char_doy=char(geo_str(strmatch('LANDSAT_SCENE_ID',geo_str)+2)); + doy=str2double(char_doy(15:17)); +else + fprintf('This sensor is not Landsat 4, 5, 7, or 8!\n'); + return; +end + +end + diff --git a/nd2toarbt.m b/nd2toarbt.m new file mode 100644 index 0000000..2c5e66a --- /dev/null +++ b/nd2toarbt.m @@ -0,0 +1,691 @@ +function [im_th,TOAref,trgt,ijdim_ref,ul,bbox,zen,azi,zc,B1Satu,B2Satu,B3Satu,resolu]=nd2toarbt(path_top,filename) +% convert DNs to TOA ref and BT +% Revisions: +% Cannot use the GRIDobj to read our band, because it may result in Nan data. +% (Shi 2/26/2018) +% Added a target image based on blue band, which will be helpfull to +% resample and reproject the auxiliary data. (Shi 2/16/2018) +% Use REF vs. DN instead of RAD vs. DN (Zhe 06/20/2013) +% Combined the Earth-Sun distance table into the function (Zhe 04/09/2013) +% Process Landsat 8 DN values (Zhe 04/04/2013) +% Proces the new metadata for Landsat TM/ETM+ images (Zhe 09/28/2012) +% Fixed bugs caused by earth-sun distance table (Zhe 01/15/2012) +% +% [im_th,TOAref,ijdim_ref,ul,zen,azi,zc,B1Satu,B2Satu,B3Satu,resolu]=nd2toarbt(filename) +% Where: +% Inputs: +% filename='L*MTL.txt'; +% Outputs: +% 1) im_th = Brightness Temperature (BT) +% 2) TOAref = Top Of Atmoshpere (TOA) reflectance +% 3) trgt = Target Grid Object +% 4) ijdim = [nrows,ncols]; % dimension of optical bands +% 5) ul = [upperleft_mapx upperleft_mapy]; +% 6) zen = solar zenith angle (degrees); +% 7) azi = solar azimuth angle (degrees); +% 8) zc = Zone Number +% 9,10,11) Saturation (true) in the Visible bands +% 12) resolution of Fmask results + +% % % fprintf('Read in header information & TIF images\n'); +[Lmax,Lmin,Qcalmax,Qcalmin,Refmax,Refmin,ijdim_ref,ijdim_thm,reso_ref,... + reso_thm,ul,bbox,zen,azi,zc,Lnum,doy]=lndhdrread(path_top,filename); + + +% earth-sun distance see G. Chander et al. RSE 113 (2009) 893-903 +Tab_ES_Dist = [ + 1 0.98331 + 2 0.98330 + 3 0.98330 + 4 0.98330 + 5 0.98330 + 6 0.98332 + 7 0.98333 + 8 0.98335 + 9 0.98338 + 10 0.98341 + 11 0.98345 + 12 0.98349 + 13 0.98354 + 14 0.98359 + 15 0.98365 + 16 0.98371 + 17 0.98378 + 18 0.98385 + 19 0.98393 + 20 0.98401 + 21 0.98410 + 22 0.98419 + 23 0.98428 + 24 0.98439 + 25 0.98449 + 26 0.98460 + 27 0.98472 + 28 0.98484 + 29 0.98496 + 30 0.98509 + 31 0.98523 + 32 0.98536 + 33 0.98551 + 34 0.98565 + 35 0.98580 + 36 0.98596 + 37 0.98612 + 38 0.98628 + 39 0.98645 + 40 0.98662 + 41 0.98680 + 42 0.98698 + 43 0.98717 + 44 0.98735 + 45 0.98755 + 46 0.98774 + 47 0.98794 + 48 0.98814 + 49 0.98835 + 50 0.98856 + 51 0.98877 + 52 0.98899 + 53 0.98921 + 54 0.98944 + 55 0.98966 + 56 0.98989 + 57 0.99012 + 58 0.99036 + 59 0.99060 + 60 0.99084 + 61 0.99108 + 62 0.99133 + 63 0.99158 + 64 0.99183 + 65 0.99208 + 66 0.99234 + 67 0.99260 + 68 0.99286 + 69 0.99312 + 70 0.99339 + 71 0.99365 + 72 0.99392 + 73 0.99419 + 74 0.99446 + 75 0.99474 + 76 0.99501 + 77 0.99529 + 78 0.99556 + 79 0.99584 + 80 0.99612 + 81 0.99640 + 82 0.99669 + 83 0.99697 + 84 0.99725 + 85 0.99754 + 86 0.99782 + 87 0.99811 + 88 0.99840 + 89 0.99868 + 90 0.99897 + 91 0.99926 + 92 0.99954 + 93 0.99983 + 94 1.00012 + 95 1.00041 + 96 1.00069 + 97 1.00098 + 98 1.00127 + 99 1.00155 + 100 1.00184 + 101 1.00212 + 102 1.00240 + 103 1.00269 + 104 1.00297 + 105 1.00325 + 106 1.00353 + 107 1.00381 + 108 1.00409 + 109 1.00437 + 110 1.00464 + 111 1.00492 + 112 1.00519 + 113 1.00546 + 114 1.00573 + 115 1.00600 + 116 1.00626 + 117 1.00653 + 118 1.00679 + 119 1.00705 + 120 1.00731 + 121 1.00756 + 122 1.00781 + 123 1.00806 + 124 1.00831 + 125 1.00856 + 126 1.00880 + 127 1.00904 + 128 1.00928 + 129 1.00952 + 130 1.00975 + 131 1.00998 + 132 1.01020 + 133 1.01043 + 134 1.01065 + 135 1.01087 + 136 1.01108 + 137 1.01129 + 138 1.01150 + 139 1.01170 + 140 1.01191 + 141 1.01210 + 142 1.01230 + 143 1.01249 + 144 1.01267 + 145 1.01286 + 146 1.01304 + 147 1.01321 + 148 1.01338 + 149 1.01355 + 150 1.01371 + 151 1.01387 + 152 1.01403 + 153 1.01418 + 154 1.01433 + 155 1.01447 + 156 1.01461 + 157 1.01475 + 158 1.01488 + 159 1.01500 + 160 1.01513 + 161 1.01524 + 162 1.01536 + 163 1.01547 + 164 1.01557 + 165 1.01567 + 166 1.01577 + 167 1.01586 + 168 1.01595 + 169 1.01603 + 170 1.01610 + 171 1.01618 + 172 1.01625 + 173 1.01631 + 174 1.01637 + 175 1.01642 + 176 1.01647 + 177 1.01652 + 178 1.01656 + 179 1.01659 + 180 1.01662 + 181 1.01665 + 182 1.01667 + 183 1.01668 + 184 1.01670 + 185 1.01670 + 186 1.01670 + 187 1.01670 + 188 1.01669 + 189 1.01668 + 190 1.01666 + 191 1.01664 + 192 1.01661 + 193 1.01658 + 194 1.01655 + 195 1.01650 + 196 1.01646 + 197 1.01641 + 198 1.01635 + 199 1.01629 + 200 1.01623 + 201 1.01616 + 202 1.01609 + 203 1.01601 + 204 1.01592 + 205 1.01584 + 206 1.01575 + 207 1.01565 + 208 1.01555 + 209 1.01544 + 210 1.01533 + 211 1.01522 + 212 1.01510 + 213 1.01497 + 214 1.01485 + 215 1.01471 + 216 1.01458 + 217 1.01444 + 218 1.01429 + 219 1.01414 + 220 1.01399 + 221 1.01383 + 222 1.01367 + 223 1.01351 + 224 1.01334 + 225 1.01317 + 226 1.01299 + 227 1.01281 + 228 1.01263 + 229 1.01244 + 230 1.01225 + 231 1.01205 + 232 1.01186 + 233 1.01165 + 234 1.01145 + 235 1.01124 + 236 1.01103 + 237 1.01081 + 238 1.01060 + 239 1.01037 + 240 1.01015 + 241 1.00992 + 242 1.00969 + 243 1.00946 + 244 1.00922 + 245 1.00898 + 246 1.00874 + 247 1.00850 + 248 1.00825 + 249 1.00800 + 250 1.00775 + 251 1.00750 + 252 1.00724 + 253 1.00698 + 254 1.00672 + 255 1.00646 + 256 1.00620 + 257 1.00593 + 258 1.00566 + 259 1.00539 + 260 1.00512 + 261 1.00485 + 262 1.00457 + 263 1.00430 + 264 1.00402 + 265 1.00374 + 266 1.00346 + 267 1.00318 + 268 1.00290 + 269 1.00262 + 270 1.00234 + 271 1.00205 + 272 1.00177 + 273 1.00148 + 274 1.00119 + 275 1.00091 + 276 1.00062 + 277 1.00033 + 278 1.00005 + 279 0.99976 + 280 0.99947 + 281 0.99918 + 282 0.99890 + 283 0.99861 + 284 0.99832 + 285 0.99804 + 286 0.99775 + 287 0.99747 + 288 0.99718 + 289 0.99690 + 290 0.99662 + 291 0.99634 + 292 0.99605 + 293 0.99577 + 294 0.99550 + 295 0.99522 + 296 0.99494 + 297 0.99467 + 298 0.99440 + 299 0.99412 + 300 0.99385 + 301 0.99359 + 302 0.99332 + 303 0.99306 + 304 0.99279 + 305 0.99253 + 306 0.99228 + 307 0.99202 + 308 0.99177 + 309 0.99152 + 310 0.99127 + 311 0.99102 + 312 0.99078 + 313 0.99054 + 314 0.99030 + 315 0.99007 + 316 0.98983 + 317 0.98961 + 318 0.98938 + 319 0.98916 + 320 0.98894 + 321 0.98872 + 322 0.98851 + 323 0.98830 + 324 0.98809 + 325 0.98789 + 326 0.98769 + 327 0.98750 + 328 0.98731 + 329 0.98712 + 330 0.98694 + 331 0.98676 + 332 0.98658 + 333 0.98641 + 334 0.98624 + 335 0.98608 + 336 0.98592 + 337 0.98577 + 338 0.98562 + 339 0.98547 + 340 0.98533 + 341 0.98519 + 342 0.98506 + 343 0.98493 + 344 0.98481 + 345 0.98469 + 346 0.98457 + 347 0.98446 + 348 0.98436 + 349 0.98426 + 350 0.98416 + 351 0.98407 + 352 0.98399 + 353 0.98391 + 354 0.98383 + 355 0.98376 + 356 0.98370 + 357 0.98363 + 358 0.98358 + 359 0.98353 + 360 0.98348 + 361 0.98344 + 362 0.98340 + 363 0.98337 + 364 0.98335 + 365 0.98333 + 366 0.98331]; + +if Lnum >= 4 && Lnum <= 7 + % LPGS Upper lef corner alignment (see Landsat handbook for detail) + ul(1)=ul(1)-15; + ul(2)=ul(2)+15; + resolu=[reso_ref,reso_ref]; + % Read in all bands + % Band1 + n_B1=dir(fullfile(path_top,'L*B1*')); + if isempty(n_B1) + n_B1=dir(fullfile(path_top,'L*b1*')); + end + im_B1=single(imread(fullfile(path_top,n_B1.name))); + % Band2 + % Also used to be target or referenced image when resampling and reprojecting to the image extent and resolution. + n_B2=dir(fullfile(path_top,'L*B2*')); + if isempty(n_B2) + n_B2=dir(fullfile(path_top,'L*b2*')); + end + im_B2=single(imread(fullfile(path_top,n_B2.name))); + trgt = GRIDobj(fullfile(path_top,n_B2.name)); +% im_B2=single(trgt.Z); + trgt.Z=[];% remove non-used values to save memory. + + % Band3 + n_B3=dir(fullfile(path_top,'L*B3*')); + if isempty(n_B3) + n_B3=dir(fullfile(path_top,'L*b3*')); + end + im_B3=single(imread(fullfile(path_top,n_B3.name))); + % Band4 + n_B4=dir(fullfile(path_top,'L*B4*')); + if isempty(n_B4) + n_B4=dir(fullfile(path_top,'L*b4*')); + end + im_B4=single(imread(fullfile(path_top,n_B4.name))); + % Band5 + n_B5=dir(fullfile(path_top,'L*B5*')); + if isempty(n_B5) + n_B5=dir(fullfile(path_top,'L*b5*')); + end + im_B5=single(imread(fullfile(path_top,n_B5.name))); + % Band6 + if Lnum==7 + n_B6=dir(fullfile(path_top,'L*B6*1*')); + if isempty(n_B6) + n_B6=dir(fullfile(path_top,'L*b6*1*')); + end + else + n_B6=dir(fullfile(path_top,'L*B6*')); + if isempty(n_B6) + n_B6=dir(fullfile(path_top,'L*b6*')); + end + end + im_th=single(imread(fullfile(path_top,n_B6.name))); + % check to see whether need to resample thermal band + if reso_ref~=reso_thm + % resmaple thermal band + im_th=pixel2pixv([ul(2),ul(1)],[ul(2),ul(1)],... + resolu,[reso_thm,reso_thm],... + im_th,[ijdim_ref(2),ijdim_ref(1)],[ijdim_thm(2),ijdim_thm(1)]); + end + % Band7 + n_B7=dir(fullfile(path_top,'L*B7*')); + if isempty(n_B7) + n_B7=dir(fullfile(path_top,'L*b7*')); + end + im_B7=single(imread(fullfile(path_top,n_B7.name))); + % only processing pixesl where all bands have values (id_mssing) + id_missing=im_B1==0|im_B2==0|im_B3==0|im_B4==0|im_B5==0|im_th==0|im_B7==0; + % find pixels that are saturated in the visible bands + B1Satu=im_B1==255; + B2Satu=im_B2==255; + B3Satu=im_B3==255; + + % ND to radiance first +% % % fprintf('From DNs to TOA ref & BT\n'); + im_B1=((Lmax(1)-Lmin(1))/(Qcalmax(1)-Qcalmin(1)))*(im_B1-Qcalmin(1))+Lmin(1); + im_B2=((Lmax(2)-Lmin(2))/(Qcalmax(2)-Qcalmin(2)))*(im_B2-Qcalmin(2))+Lmin(2); + im_B3=((Lmax(3)-Lmin(3))/(Qcalmax(3)-Qcalmin(3)))*(im_B3-Qcalmin(3))+Lmin(3); + im_B4=((Lmax(4)-Lmin(4))/(Qcalmax(4)-Qcalmin(4)))*(im_B4-Qcalmin(4))+Lmin(4); + im_B5=((Lmax(5)-Lmin(5))/(Qcalmax(5)-Qcalmin(5)))*(im_B5-Qcalmin(5))+Lmin(5); + im_th=((Lmax(6)-Lmin(6))/(Qcalmax(6)-Qcalmin(6)))*(im_th-Qcalmin(6))+Lmin(6); + im_B7=((Lmax(7)-Lmin(7))/(Qcalmax(7)-Qcalmin(7)))*(im_B7-Qcalmin(7))+Lmin(7); + + % Landsat 4, 5, and 7 solar spectral irradiance + % see G. Chander et al. RSE 113 (2009) 893-903 + esun_L7=[1997.000, 1812.000, 1533.000, 1039.000, 230.800, -1.0, 84.90]; + esun_L5=[1983.0, 1796.0, 1536.0, 1031.0, 220.0, -1.0, 83.44]; + esun_L4=[1983.0, 1795.0, 1539.0, 1028.0, 219.8, -1.0, 83.49]; + + if Lnum==7 + ESUN=esun_L7; + elseif Lnum==5 + ESUN=esun_L5; + elseif Lnum==4 + ESUN=esun_L4; + end + + % earth-sun distance see G. Chander et al. RSE 113 (2009) 893-903 + dsun_doy = Tab_ES_Dist(doy,2); + + % compute TOA reflectances + % converted from degrees to radiance + s_zen=deg2rad(zen); + im_B1=10^4*pi*im_B1*dsun_doy^2/(ESUN(1)*cos(s_zen)); + im_B2=10^4*pi*im_B2*dsun_doy^2/(ESUN(2)*cos(s_zen)); + im_B3=10^4*pi*im_B3*dsun_doy^2/(ESUN(3)*cos(s_zen)); + im_B4=10^4*pi*im_B4*dsun_doy^2/(ESUN(4)*cos(s_zen)); + im_B5=10^4*pi*im_B5*dsun_doy^2/(ESUN(5)*cos(s_zen)); + im_B7=10^4*pi*im_B7*dsun_doy^2/(ESUN(7)*cos(s_zen)); + + % convert Band6 from radiance to BT + % fprintf('From Band 6 Radiance to Brightness Temperature\n'); + % see G. Chander et al. RSE 113 (2009) 893-903 + K1_L4= 671.62; + K2_L4= 1284.30; + K1_L5= 607.76; + K2_L5= 1260.56; + K1_L7 = 666.09; + K2_L7 = 1282.71; + + if Lnum==7 + K1=K1_L7; + K2=K2_L7; + elseif Lnum==5 + K1=K1_L5; + K2=K2_L5; + elseif Lnum==4 + K1=K1_L4; + K2=K2_L4; + end + + im_th=K2./log((K1./im_th)+1); + % convert from Kelvin to Celcius with 0.01 scale_facor + im_th=100*(im_th-273.15); + % get data ready for Fmask + im_B1(id_missing)=-9999; + im_B2(id_missing)=-9999; + im_B3(id_missing)=-9999; + im_B4(id_missing)=-9999; + im_B5(id_missing)=-9999; + im_th(id_missing)=-9999; + im_B7(id_missing)=-9999; + clear id_missing; + TOAref=zeros(ijdim_ref(1),ijdim_ref(2),6,'single');% Band1,2,3,4,5,&7 + TOAref(:,:,1)=im_B1;clear im_B1; + TOAref(:,:,2)=im_B2;clear im_B2; + TOAref(:,:,3)=im_B3;clear im_B3; + TOAref(:,:,4)=im_B4;clear im_B4; + TOAref(:,:,5)=im_B5;clear im_B5; + TOAref(:,:,6)=im_B7;clear im_B7; + +elseif Lnum == 8 + % LPGS Upper lef corner alignment (see Landsat handbook for detail) + ul(1)=ul(1)-15; + ul(2)=ul(2)+15; + resolu=[reso_ref,reso_ref]; + % Read in all bands + % Band2 + n_B2=dir(fullfile(path_top,'L*B2*')); + if isempty(n_B2) + n_B2=dir(fullfile(path_top,'L*b2*')); + end + im_B2=single(imread(fullfile(path_top,n_B2(1).name))); clear n_B2; + % Band3 + % Also used to be target or referenced image when resampling and reprojecting to the image extent and resolution. + n_B3=dir(fullfile(path_top,'L*B3*')); + if isempty(n_B3) + n_B3=dir(fullfile(path_top,'L*b3*')); + end + im_B3=single(imread(fullfile(path_top,n_B3(1).name))); + trgt = GRIDobj(fullfile(path_top,n_B3.name)); clear n_B3; +% im_B3=single(trgt.Z); + trgt.Z=[];% remove non-used values to save memory. + + % Band4 + n_B4=dir(fullfile(path_top,'L*B4*')); + if isempty(n_B4) + n_B4=dir(fullfile(path_top,'L*b4*')); + end + im_B4=single(imread(fullfile(path_top,n_B4(1).name))); clear n_B4; + % Band5 + n_B5=dir(fullfile(path_top,'L*B5*')); + if isempty(n_B5) + n_B5=dir(fullfile(path_top,'L*b5*')); + end + im_B5=single(imread(fullfile(path_top,n_B5(1).name))); clear n_B5; + % Band6 + n_B6=dir(fullfile(path_top,'L*B6*')); + if isempty(n_B6) + n_B6=dir(fullfile(path_top,'L*b6*')); + end + im_B6=single(imread(fullfile(path_top,n_B6(1).name))); clear n_B6; + % Band7 + n_B7=dir(fullfile(path_top,'L*B7*')); + if isempty(n_B7) + n_B7=dir(fullfile(path_top,'L*b7*')); + end + im_B7=single(imread(fullfile(path_top,n_B7(1).name))); clear n_B7; + % Band9 + n_B9=dir(fullfile(path_top,'L*B9.*')); + if isempty(n_B9) + n_B9=dir(fullfile(path_top,'L*b9.*')); + end + im_B9=single(imread(fullfile(path_top,n_B9(1).name))); clear n_B9; + % Band10 + n_B10=dir(fullfile(path_top,'L*B10*')); + if isempty(n_B10) + n_B10=dir(fullfile(path_top,'L*b10*')); + end + im_B10=single(imread(fullfile(path_top,n_B10(1).name))); clear n_B10 path_top; + + % check to see whether need to resample thermal band + if reso_ref~=reso_thm + % resmaple thermal band + im_B10=pixel2pixv([ul(2),ul(1)],[ul(2),ul(1)],... + resolu,[reso_thm,reso_thm],... + im_B10,[ijdim_ref(2),ijdim_ref(1)],[ijdim_thm(2),ijdim_thm(1)]); + end + + % only processing pixesl where all bands have values (id_mssing) + id_missing=im_B2==0|im_B3==0|im_B4==0|im_B5==0|im_B6==0|im_B7==0|im_B9==0|im_B10==0; + % find pixels that are saturated in the visible bands + B1Satu=im_B2==65535; + B2Satu=im_B3==65535; + B3Satu=im_B4==65535; + + % ND to TOA reflectance with 0.0001 scale_facor +% % % fprintf('From DNs to TOA ref & BT\n'); + im_B2=((Refmax(1)-Refmin(1))/(Qcalmax(1)-Qcalmin(1)))*(im_B2-Qcalmin(1))+Refmin(1); + im_B3=((Refmax(2)-Refmin(2))/(Qcalmax(2)-Qcalmin(2)))*(im_B3-Qcalmin(2))+Refmin(2); + im_B4=((Refmax(3)-Refmin(3))/(Qcalmax(3)-Qcalmin(3)))*(im_B4-Qcalmin(3))+Refmin(3); + im_B5=((Refmax(4)-Refmin(4))/(Qcalmax(4)-Qcalmin(4)))*(im_B5-Qcalmin(4))+Refmin(4); + im_B6=((Refmax(5)-Refmin(5))/(Qcalmax(5)-Qcalmin(5)))*(im_B6-Qcalmin(5))+Refmin(5); + im_B7=((Refmax(6)-Refmin(6))/(Qcalmax(6)-Qcalmin(6)))*(im_B7-Qcalmin(6))+Refmin(6); + im_B9=((Refmax(7)-Refmin(7))/(Qcalmax(7)-Qcalmin(7)))*(im_B9-Qcalmin(7))+Refmin(7); + im_B10=((Lmax(8)-Lmin(8))/(Qcalmax(8)-Qcalmin(8)))*(im_B10-Qcalmin(8))+Lmin(8); + + % compute TOA reflectances + % with a correction for the sun angle + s_zen=deg2rad(zen); + im_B2=10^4*im_B2/cos(s_zen); + im_B3=10^4*im_B3/cos(s_zen); + im_B4=10^4*im_B4/cos(s_zen); + im_B5=10^4*im_B5/cos(s_zen); + im_B6=10^4*im_B6/cos(s_zen); + im_B7=10^4*im_B7/cos(s_zen); + im_B9=10^4*im_B9/cos(s_zen); + + % convert Band6 from radiance to BT + % fprintf('From Band 6 Radiance to Brightness Temperature\n'); + K1_B10 = 774.89; + K2_B10 = 1321.08; + + im_B10=K2_B10./log((K1_B10./im_B10)+1); + + % convert from Kelvin to Celcius with 0.01 scale_facor + im_B10=100*(im_B10-273.15); + + % get data ready for Fmask + im_B2(id_missing)=-9999; + im_B3(id_missing)=-9999; + im_B4(id_missing)=-9999; + im_B5(id_missing)=-9999; + im_B6(id_missing)=-9999; + im_B7(id_missing)=-9999; + im_B9(id_missing)=-9999; + im_B10(id_missing) = -9999; + clear id_missing; % empty memory + + TOAref=zeros(ijdim_ref(1),ijdim_ref(2),7,'single');% Band 2,3,4,5,6,7,& 9 + TOAref(:,:,1)=im_B2; clear im_B2; + TOAref(:,:,2)=im_B3; clear im_B3; + TOAref(:,:,3)=im_B4; clear im_B4; + TOAref(:,:,4)=im_B5; clear im_B5; + TOAref(:,:,5)=im_B6; clear im_B6; + TOAref(:,:,6)=im_B7; clear im_B7; + TOAref(:,:,7)=im_B9; clear im_B9; + + im_th=zeros(ijdim_ref(1),ijdim_ref(2),'single');% Band 10 + im_th(:,:) = im_B10; clear im_B10; +else + fprintf('This sensor is not Landsat 4, 5, 7, or 8!\n'); + return; +end + +end + diff --git a/nd2toarbt_msi.m b/nd2toarbt_msi.m new file mode 100644 index 0000000..7808c11 --- /dev/null +++ b/nd2toarbt_msi.m @@ -0,0 +1,227 @@ +function [im_th,TOAref,trgt,ijdim_ref,bbox,ul,zen,azi,zc,Angles2,B1Satu,B2Satu,B3Satu,resolu]=nd2toarbt_msi2(im) +% read TOA refs function derived from Fmask 3.3 for Sentinel 2. +% Revisions: +% Use REF vs. DN instead of RAD vs. DN (Zhe 06/20/2013) +% Combined the Earth-Sun distance table into the function (Zhe 04/09/2013) +% Process Landsat 8 DN values (Zhe 04/04/2013) +% Proces the new metadata for Landsat TM/ETM+ images (Zhe 09/28/2012) +% Fixed bugs caused by earth-sun distance table (Zhe 01/15/2012) +% +% [im_th,TOAref,ijdim_ref,ul,zen,azi,zc,B1Satu,B2Satu,B3Satu,resolu]=nd2toarbt(filename) +% Where: +% Inputs: +% im= MSI filename structure including: +% - im.Dmain (root directory of the SAFE directory) +% - im.DataStrip directory (without ".SAFE") +% - im.Granule directory; +% im.Dmain = '/home/bernie/MSIdata/'; +% im.DataStrip = 'S2A_OPER_PRD_MSIL1C_PDMC_20151229T234852_R139_V20151229T144823_20151229T144823'; +% im.Granule = 'S2A_OPER_MSI_L1C_TL_SGS__20151229T201123_A002710_T20QPD_N02.01'; +% Outputs: +% 1) im_th = Brightness Temperature (BT) +% 2) TOAref = Top Of Atmoshpere (TOA) reflectance +% 3) ijdim = [nrows,ncols]; % dimension of optical bands +% 4) ul = [upperleft_mapx upperleft_mapy]; +% 5) zen = solar zenith angle (degrees); +% 6) azi = solar azimuth angle (degrees); +% 7) zc = Zone Number +% 8,9,10) Saturation (true) in the Visible bands +% 11) resolution of Fmask results +% eg. + + FilesInfo.DirIn = im.Dmain; + FilesInfo.DataStrip =im.DataStrip ; + FilesInfo.Granule=im.Granule; + + FilesInfo.InspireXML=im.InspireXML; + clear im; + + % obtain them from INSPIRE.xml + bbox = ReadS2InspireXML(FilesInfo.InspireXML); + + + %% Metadata read ReadSunViewGeometryMSI(DataStrip,Granule,BandSel,PsizeOut,Dmain) + [MSIinfo,Angles] = ReadSunViewGeometryMSI (FilesInfo.DataStrip,FilesInfo.Granule,4,10,FilesInfo.DirIn); + + Angles2.VAA = Angles.VAA_B04 ; + Angles2.VZA = Angles.VZA_B04 ; + clear Angles; + + %% output resolution of Fmask 20meters for Sentinel 2 images + resolu = [20 20] ; + %% + ijdim_ref = (MSIinfo.GeoInfo.Size.R10) *10 ./ resolu ; + + ul = [MSIinfo.GeoInfo.Xstart.R10 MSIinfo.GeoInfo.Ystart.R10] + [resolu(1)/2 0-resolu(2)/2]; % the center of the top-left pixel. + zen = MSIinfo.Angles.Mean.SZA ; + azi = MSIinfo.Angles.Mean.SAA ; + + zc_num=MSIinfo.GeoInfo.UTM_zone(1:end-1); + + % convert UTM zone to code by refering the bellow rule. + % ref. http://geotiff.maptools.org/spec/geotiff6.html#6.3.3.1 + zc_ns=MSIinfo.GeoInfo.UTM_zone(end); + clear MSIinfo; + if zc_ns=='N' + zc = abs(str2double(zc_num)); + if zc>10 + geokey = ['326',zc_num]; + else + geokey = ['3260',zc_num]; + end + geokey=str2double(geokey); + elseif zc_ns=='S' + zc = abs(str2double(zc_num)); + if zc>10 + geokey = ['327',zc_num]; + else + geokey = ['3270',zc_num]; + end + geokey=str2double(geokey); + end + + + %% open MSI data + BandCorr = {'02','03','04','8A','11','12','10','07','08'}; + Ratio = [10 10 10 20 20 20 60 20 10] /(resolu(1)); % resample all bands to the same resolu + Dmain = fullfile(FilesInfo.DirIn, [FilesInfo.DataStrip '.SAFE'],'GRANULE', FilesInfo.Granule, 'IMG_DATA'); + id_missing=zeros(ijdim_ref,'uint8'); + for iB=1:length(BandCorr) +% if FilesInfo.Granule(1) == 'L' +% tempfn = dir(fullfile(Dmain, [FilesInfo.Granule(5:10) '*_B' BandCorr{iB} '.jp2'])); +% % if isequal(iB,4) +% % [dum,~,~,info_jp2]=geoimread([Dmain tempfn(1).name]); +% % else +% dum=imread(fullfile(Dmain, tempfn(1).name)); +% % end +% else +% % if isequal(iB,4) +% % % [dum,~,~,info_jp2]=geoimread('/Volumes/Transcend/Fmask3.3S2/Test data/S2A_OPER_PRD_MSIL1C_T31TCJ_2016292A.SAFE/GRANULE/S2A_OPER_MSI_L1C_TL_SGS__20161018T175658_A006912_T31TCJ_N02.04/IMG_DATA/S2A_OPER_MSI_L1C_TL_SGS__20161018T175658_A006912_T31TCJ_B05.jp2'); +% % [dum,~,~,info_jp2]=geoimread([Dmain FilesInfo.Granule(1:end-7) 'B' BandCorr{iB} '.jp2']); +% % else +% % dum=imread([Dmain FilesInfo.Granule(1:end-6) 'B' BandCorr{iB} '.jp2']); +% dum=imread(fullfile(Dmain, [FilesInfo.Granule(1:end-7) 'B' BandCorr{iB} '.jp2'])); +% % end +% end + +% support all versions for Sentinel 2 images. + tempfn = dir(fullfile(Dmain, [FilesInfo.Granule(1) '*B' BandCorr{iB} '.jp2'])); + if isempty(tempfn) + tempfn = dir(fullfile(Dmain, [FilesInfo.Granule(5) '*B' BandCorr{iB} '.jp2'])); + if isempty(tempfn) + tempfn = dir(fullfile(Dmain, ['*B' BandCorr{iB} '.jp2'])); + end + end + dum=imread(fullfile(Dmain, tempfn(1).name)); + clear tempfn; + + dum=single(dum); + dum(dum==0)=NaN; + if Ratio(iB)<1 % box-agg pixel + TOAref(:,:,iB) = imresize(dum,Ratio(iB),'box'); + elseif Ratio(iB)>1 % split pixel + TOAref(:,:,iB) = imresize(dum,Ratio(iB),'nearest'); + elseif Ratio(iB)==1 + TOAref(:,:,iB) = dum; + end + clear dum; + +% % if isequal(BandCorr{iB},'08') +% % % Gaussfilt for band 8 to better scall up. +% % TOAref(:,:,iB) = imgaussfilt(TOAref(:,:,iB)); +% % end + + % only processing pixesl where all bands have values (id_mssing) + id_missing=id_missing|isnan(TOAref(:,:,iB)); + end + clear Dmain FilesInfo BandCorr Ratio iB; +% trgt=CreateTargetGRIDobj(info_jp2.BoundingBox,resolu,ul,ijdim_ref,zc_num,zc_ns,geokey); + trgt=CreateTargetGRIDobj(resolu,ul,ijdim_ref,zc_num,zc_ns,geokey); + + trgt.Z=[]; + +% trgt_test = GRIDobj('E:\New validation dataset\New accuracy assessment samples for Fmask 4_0\Sentinel2\S2A_MSIL1C_20171011T123151_N0205_R023_T17CNL_20171011T123149.SAFE\GRANULE\L1C_T17CNL_A012032_20171011T123149\Samples\assist data\Samples.tif'); + + %% + %%%%% WARNING - what is the MSI fill value? what is the saturation flag ? 65535 +% TOAref(isnan(TOAref))=-9999; + TOAref(id_missing)=-9999; + clear id_missing; + %%%%%%%%%%%% Not used but same format + im_th=-9999; + + % B1Satu = zeros([size(TOAref,1) size(TOAref,2)],'uint8')==1; + % B2Satu = zeros([size(TOAref,1) size(TOAref,2)],'uint8')==1; + % B3Satu = zeros([size(TOAref,1) size(TOAref,2)],'uint8')==1; + + % find pixels that are saturated in the visible bands + % SATURATED VALUE: 65535 set by Shi 10/18/2017 + B1Satu=TOAref(:,:,1)==65535; + B2Satu=TOAref(:,:,2)==65535; + B3Satu=TOAref(:,:,3)==65535; +end + +function trgt=CreateTargetGRIDobj(resolu,ul,ijdim_ref,zc_num,zc_ns,geokey) + + trgt = GRIDobj([]); + trgt.name='target'; + trgt.cellsize=resolu(1); + + ul_tmp=ul - [resolu(1)/2 0-resolu(2)/2]; %back to limits. + + % location of the 'center' of the first (1,1) pixel in the image. + trgt.refmat=makerefmat(ul(1),ul(2),resolu(1),0-resolu(2)); + clear ul; + trgt.size=ijdim_ref; + + % boundary. + xWorldLimits=[ul_tmp(1),ul_tmp(1)+resolu(1)*(ijdim_ref(1))]; + yWorldLimits=[ul_tmp(2)-resolu(2)*(ijdim_ref(2)),ul_tmp(2)]; + clear ul_tmp resolu; + + spatialRef = maprefcells(xWorldLimits,yWorldLimits,ijdim_ref,... + 'ColumnsStartFrom','north'); + clear xWorldLimits yWorldLimits ijdim_ref; + trgt.georef.SpatialRef=spatialRef; + clear spatialRef; +% % http://www.gdal.org/frmt_sentinel2.html + trgt.georef.GeoKeyDirectoryTag.GTModelTypeGeoKey = 1; + trgt.georef.GeoKeyDirectoryTag.GTRasterTypeGeoKey = 1; + trgt.georef.GeoKeyDirectoryTag.GTCitationGeoKey = ['WGS 84 / UTM zone ',zc_num,zc_ns]; + trgt.georef.GeoKeyDirectoryTag.ProjectedCSTypeGeoKey = geokey; clear geokey; + trgt.georef.GeoKeyDirectoryTag.PCSCitationGeoKey = ['WGS 84 / UTM zone ',zc_num,zc_ns]; % same + +% ellipsoid = utmgeoid([zc_num,zc_ns]); + + % this can be used as follows because the Sentinel 2 titles's projection + % will not be changable. + % WGS84 UTM: A UTM zone is a 6?° segment of the Earth. + E = wgs84Ellipsoid('meters'); + utmstruct = defaultm('tranmerc'); + utmstruct.geoid = [E.SemimajorAxis,E.Eccentricity]; + clear E; + % UTM false East (m) + % the central meridian is assigned 500,000 meters in each zone. + utmstruct.falseeasting=500000; + % UTM false North (m) + % If you are in the northern hemisphere, the equator has a northing + % value of 0 meters. In the southern hemisphere, the equator starts + % at 10,000,000 meters. This is because all values south of the + % equator will be positive. + if zc_ns=='N' + utmstruct.falsenorthing=0; + else % S + utmstruct.falsenorthing=10000000;% 10,000,000 + end + clear zc_ns; + % UTM scale factor + utmstruct.scalefactor=0.9996; + % each UTM zone is 6 degrees. a total of 60 zones. + origin=(str2double(zc_num)-31).*6+3; + clear zc_num; + utmstruct.origin=[0,origin,0]; + clear origin; + utmstruct = defaultm(utmstruct); + trgt.georef.mstruct=utmstruct; + clear utmstruct; +end \ No newline at end of file diff --git a/pixel2pixv.m b/pixel2pixv.m new file mode 100644 index 0000000..1c34d55 --- /dev/null +++ b/pixel2pixv.m @@ -0,0 +1,59 @@ +function outim=pixel2pixv(jiul1,jiul2,resolu1,resolu2,im2,jidim1,jidim2) +% from stack images of different resolution and different projection +% Improved version of lndpixel2pixv +% match image2 with image1 +% Input 1) jiul1 (upperleft corner of the pixel) of image 1 +% Input 2) jiul2 (upperleft corner of the pixel) of image 2 +% Input 3&4) j cols, i rows of image 1 +% Input 5&6) resolution of images 1&2 +% Input 7) image 2 data +% Input 8&9) image 1 and 2 dimension +% output matched data = outim(f(i),f(j))=>im1(i,j) +% i.e. matchdata=pixel2pixv(ul1,ul2,res1,res2,data2,dim1,dim2); +j=1:jidim1(1); +i=1:jidim1(2); +[x,y]=lndpixel2map(jiul1,j,i,resolu1); +[j2,i2]=lndmap2pixel(jiul2,x,y,resolu2); + +% the first data is assume to be the filled value +fill_v=im2(1,1); +outim=fill_v*ones(jidim1(2),jidim1(1),class(fill_v)); % give filled data first + + +jexist=(((j2 > 0)&(j2 <= jidim2(1)))); % matched data i,j exit in data2 +iexist=(((i2 > 0)&(i2 <= jidim2(2)))); + +%jexistv=j2(((j2 > 0)&(j2 <= jidim2(1)))); % exist i,j => data2 (f(i),f(j)) +%iexistv=i2(((i2 > 0)&(i2 <= jidim2(2)))); + +jexistv=j2(jexist); % exist i,j => data2 (f(i),f(j)) +iexistv=i2(iexist); + +outim(iexist,jexist)=im2(iexistv,jexistv); +end + +% pixel to map +function [x,y]=lndpixel2map(jiUL,j,i,resolu) +% from pixel to map +% Input jiUL (upperleft corner of the pixel) meters (x,y) +% resolution [x, y] +% output x, y of center of the pixel i,j +% i.e. [x,y]=lndpixel2maplndpixel2map(jiUL,j,i,resolu) +x=jiUL(1)+resolu(1)/2+(j-1)*resolu(1); +y=jiUL(2)-resolu(2)/2+(1-i)*resolu(2); + +end + +% map to pixel +function [j,i]=lndmap2pixel(jiUL,x,y,resolu) +% from map to pixel +% Input jiUL (upperleft corner of the pixel), and x meters, and y meters, and +% resolution [x, y] +% output j cols, i rows +% i.e. [j,i]=lndmap2pixel(jiUL,x,y,resolu) +j=ceil((x-jiUL(1))/resolu(1)); +i=ceil((jiUL(2)-y)/resolu(2)); + +end + + diff --git a/probThin.m b/probThin.m new file mode 100644 index 0000000..e0eb6be --- /dev/null +++ b/probThin.m @@ -0,0 +1,8 @@ +function prob_thin = probThin(BandCirrus) +%PROBCIRRUS + prob_thin = BandCirrus./400; + clear BandCirrus; +% prob_thin(prob_thin>1)=1; + prob_thin(prob_thin<0)=0; +end + diff --git a/problBrightness.m b/problBrightness.m new file mode 100644 index 0000000..e90656b --- /dev/null +++ b/problBrightness.m @@ -0,0 +1,18 @@ +function prob_brightness = problBrightness(HOT,idlnd,l_pt,h_pt) +%PROBLBRIGHTNESS calculate brightness probability using HOT + + F_hot=HOT(idlnd); % get clear HOTs + clear idlnd; + % 0.175 percentile background HOT (low) + t_hotl=prctile(F_hot,100*l_pt)-400; + clear l_pt; + % 0.825 percentile background HOT (high) + t_hoth=prctile(F_hot,100*h_pt)+400; + clear F_hot h_pt; + + prob_brightness=(HOT-t_hotl)./(t_hoth-t_hotl); + clear HOT t_hotl t_hoth; + prob_brightness(prob_brightness<0)=0; + prob_brightness(prob_brightness>1)=1; % this cannot be higher 1 (maybe have commission errors from bright surfaces). +end + diff --git a/problSpectralVaribility.m b/problSpectralVaribility.m new file mode 100644 index 0000000..efee9b5 --- /dev/null +++ b/problSpectralVaribility.m @@ -0,0 +1,11 @@ +function prob_vari = problSpectralVaribility(ndvi,ndsi,ndbi,whiteness,SatuGreen,SatuRed) +%PROBLSPECTRALVARIBILITY + % [varibility test over land] + ndsi(SatuGreen==true&ndsi<0)=0; + clear SatuGreen; + ndvi(SatuRed==true&ndvi>0)=0; + clear SatuRed; + prob_vari=1-max(max(max(abs(ndsi),abs(ndvi)),abs(ndbi)),whiteness); + clear ndsi ndvi ndbi whiteness; +end + diff --git a/problTemperature.m b/problTemperature.m new file mode 100644 index 0000000..29b7b23 --- /dev/null +++ b/problTemperature.m @@ -0,0 +1,26 @@ +function [prob_temp, t_templ,t_temph] = problTemperature(BandBT,idclr,l_pt,h_pt) +%PROBTEMPERATURE calculate temperature probability for land respectively. + + % [Temperature test (over land)] + F_temp=BandBT(idclr); % get clear temperature + clear idclr; + t_buffer=4*100; + % 0.175 percentile background temperature (low) + t_templ=prctile(F_temp,100*l_pt); + % 0.825 percentile background temperature (high) + t_temph=prctile(F_temp,100*h_pt); + clear F_temp l_pt h_pt; + + t_tempL=t_templ-t_buffer; + t_tempH=t_temph+t_buffer; + clear t_buffer; + Temp_l=t_tempH-t_tempL; + clear t_tempL; + prob_temp=(t_tempH-BandBT)/Temp_l; + clear BandBT t_tempH Temp_l; + % Temperature can have prob > 1 + prob_temp(prob_temp<0)=0; +% prob_temp(prob_temp>1)=1; + +end + diff --git a/probwBrightness.m b/probwBrightness.m new file mode 100644 index 0000000..fe0156e --- /dev/null +++ b/probwBrightness.m @@ -0,0 +1,10 @@ +function wprob_brightness = probwBrightness(BandSWIR) +%PROBWBRIGHTNESS calculate brightness probability + % [Brightness test (over water)] + t_bright=1100; + wprob_brightness=BandSWIR./t_bright; + clear BandSWIR t_bright; + wprob_brightness(wprob_brightness>1)=1; + wprob_brightness(wprob_brightness<0)=0; +end + diff --git a/probwTemperature.m b/probwTemperature.m new file mode 100644 index 0000000..4934c60 --- /dev/null +++ b/probwTemperature.m @@ -0,0 +1,14 @@ +function wprob_temp = probwTemperature(BandBT,idwt,h_pt) +%PROBTEMPERATURE calculate temperature probability for water. + + % [temperature test (over water)] + F_wtemp=BandBT(idwt); % get clear water temperature + clear idwt; + t_wtemp=prctile(F_wtemp,100*h_pt); + clear F_wtemp h_pt; + wprob_temp=(t_wtemp-BandBT)/400; + clear t_wtemp BandBT; + wprob_temp(wprob_temp<0)=0; + % wTemp_prob(wTemp_prob > 1) = 1; +end + diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..da178cd --- /dev/null +++ b/readme.txt @@ -0,0 +1,13 @@ +The software called Fmask (Function of mask) is used for automated clouds, cloud shadows, snow, and water masking for Landsats 4-8 and Sentinel 2 images. + +This 4.0 version has substantial better cloud, cloud shadow, and snow detection results for Sentinel 2 and better results (compared to the 3.3 version that is currently being used by USGS as the Collection 1 QA Band) for Landsats 4-8 . This one software can be used for **Landsats 4-8 Collection 1 Level 1 product (Digital Numbers) and Sentinel 2 Level-1C product (Top Of Atmosphere reflectance) at the same time. + +If you have any questions, please contact +Shi Qiu (shi.qiu@uconn.edu) +Zhe Zhu (zhe@uconn.edu) + + +Note: +autoFmask is the main function. +autoFmaskBacth function can search all Landsats 4-8 and Sentinel-2 images into a certain folder. + diff --git a/refmat2XY.m b/refmat2XY.m new file mode 100644 index 0000000..71d7384 --- /dev/null +++ b/refmat2XY.m @@ -0,0 +1,29 @@ +function [x,y] = refmat2XY(R,siz) + +% Convert referencing matrix (refmat) to coordinate vectors +% +% Syntax +% +% [x,y] = refmat2XY(R,siz) +% +% Input arguments +% +% R referencing matrix +% siz size of the grid +% +% Output arguments +% +% x x-coordinates +% y y-coordinates +% +% + +nrrows = siz(1); +nrcols = siz(2); + +x = [ones(nrcols,1) (1:nrcols)' ones(nrcols,1)]*R; +x = x(:,1)'; + +y = [(1:nrrows)' ones(nrrows,2)]*R; +y = y(:,2); + diff --git a/reproject2utm.m b/reproject2utm.m new file mode 100644 index 0000000..4a09369 --- /dev/null +++ b/reproject2utm.m @@ -0,0 +1,188 @@ +function [DEMr,zone] = reproject2utm(DEM,res,varargin) + +%REPROJECT2UTM Reproject DEM with WGS84 coordinate system to UTM-WGS84 +% +% Syntax +% +% [GRIDr,zone] = reproject2utm(GRID,res) +% [GRIDr,zone] = reproject2utm(GRID,res,pn,pv,...) +% GRIDr = reproject2utm(GRID,GRID2) +% GRIDr = reproject2utm(GRID,GRID2,'method',method) +% +% Description +% +% Reproject a grid (GRIDobj) with WGS84 geographic coordinates to UTM +% WGS84 (requires the mapping toolbox and image processing toolbox). +% +% Input arguments +% +% GRID raster (GRIDobj) with WGS84 geographic coordinates. +% res spatial resolution in x- and y-direction (scalar) +% GRID2 raster (GRIDobj) with projected coordinate system to which +% GRID shall be projected. The resulting grid will be +% perfectly spatially aligned (same cellsize, same upper left +% egde, same size) with GRID2. +% +% Parameter name/value pairs +% +% zone is automatically determined. If supplied, the value must +% be a string, e.g., '32T'. Note that this function requires +% the full grid zone reference that includes the uppercase +% letter indicating the latitudinal band. +% method interpolation method ('bilinear' (default), 'bicubic', +% or 'nearest') +% +% Output arguments +% +% GRIDr raster (GRIDobj) with UTM-WGS84 projected coordinates +% zone utm zone (string) +% +% +% See also: GRIDobj, imtransform, maketform, mfwdtran, minvtran, utmzone +% +% Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) +% Date: 12. January, 2017 + + +% get latitude and longitude vectors +[lon,lat] = getcoordinates(DEM); + +if ~isa(res,'GRIDobj'); + % and calculate centroid of DEM. The centroid is used to + % get the utmzone + lonc = sum(lon([1 end]))/2; + latc = sum(lat([1 end]))/2; + zone = utmzone(latc,lonc); + Nhemisphere = double(upper(zone(end)))>=78; +else + zone = ''; + +end + +% parse input arguments +p = inputParser; +validmethods = {'bicubic','bilinear','nearest','linear'}; +p.FunctionName = 'GRIDobj/reproject2UTM'; +% required +addRequired(p,'DEM',@(x) isa(x,'GRIDobj')); +addRequired(p,'res',@(x) (~isa(x,'GRIDobj') && isscalar(x) && x > 0) || isa(x,'GRIDobj')); +% optional +addParameter(p,'zone',zone,@(x) ischar(x)); +addParameter(p,'method','bilinear',@(x) ischar(validatestring(x,validmethods))); + +parse(p,DEM,res,varargin{:}); + +% get zone for output +zone = p.Results.zone; + + +% prepare mstruct (transformation structure) if only res supplied +if ~isa(res,'GRIDobj'); + mstruct = defaultm('utm'); + mstruct.zone = p.Results.zone; + mstruct.geoid = wgs84Ellipsoid; + mstruct = defaultm(utm(mstruct)); + + % use forward transformation of the corner locations of the DEM + % to calculate the bounds of the reprojected DEM + xlims = zeros(4,1); + ylims = xlims; + [xlims(1:2),ylims(1:2)] = mfwdtran(mstruct,[min(lat) max(lat)],[min(lon) max(lon)]); + [xlims(3:4),ylims(3:4)] = mfwdtran(mstruct,[min(lat) max(lat)],[max(lon) min(lon)]); + lims = [min(xlims) max(xlims) min(ylims) max(ylims)]; + +else + + mstruct = res.georef.mstruct; + [x,y] = getcoordinates(res); + lims = [min(x) max(x) min(y) max(y)]; +end + + +% prepare tform for the image transform +T = maketform('custom', 2, 2, ... + @FWDTRANS, ... + @INVTRANS, ... + []); + +% calculate image transform +if ~isa(res,'GRIDobj'); + [Znew,xdata,ydata] = imtransform(flipud(DEM.Z),T,p.Results.method,... + 'Xdata',lims([1 2]),... + 'Ydata',lims([3 4]),... + 'Udata',lon([1 end]),'Vdata',lat([end 1])',... + 'XYScale',[res res],... + 'Fillvalues',nan... + ); + % we have calculated the imtransform with 'ColumnsStartFrom' south. + % GRIDobjs use 'ColumnsStartFrom' north + Znew = flipud(Znew); + xnew = cumsum([xdata(1) repmat(res,1,size(Znew,2)-1)]); + ynew = flipud(cumsum([ydata(1) repmat(res,1,size(Znew,1)-1)])'); +else + Znew = imtransform(flipud(DEM.Z),T,p.Results.method,... + 'Xdata',lims([1 2]),... + 'Ydata',lims([3 4]),... + 'Udata',lon([1 end]),'Vdata',lat([end 1])',... + 'XYScale',[res.cellsize res.cellsize],... + 'Fillvalues',nan... + ); + Znew = flipud(Znew); +end + + + +if ~isa(res,'GRIDobj'); +% Construct GRIDobj +DEMr = GRIDobj(xnew,ynew,Znew); + +% and include geospatial information +R = refmatToMapRasterReference(DEMr.refmat,DEMr.size); + +% write GeoKeyDirectoryTag so that DEMr can be exported +% using GRIDobj2geotiff +DEMr.georef.SpatialRef = R; +GeoKeyDirectoryTag.GTModelTypeGeoKey = 1; % Projected coordinate system +GeoKeyDirectoryTag.GTRasterTypeGeoKey = 1; % RasterPixelIsArea +GeoKeyDirectoryTag.GTCitationGeoKey = ['PCS Name = WGS_84_UTM_zone_' mstruct.zone]; +GeoKeyDirectoryTag.GeogCitationGeoKey = 'GCS_WGS_1984'; +GeoKeyDirectoryTag.GeogAngularUnitsGeoKey = 9102; %Angular_Degree + +% get ProjectedCSTypeGeoKey +if Nhemisphere + hemisphere = '326'; +else + hemisphere = '327'; +end + +% http://www.remotesensing.org/geotiff/spec/geotiff6.html#6.3.3.1 +% WGS84 / UTM northern hemisphere: 326zz where zz is UTM zone number +% WGS84 / UTM southern hemisphere: 327zz where zz is UTM zone number +% GeoKeyDirectoryTag.ProjectedCSTypeGeoKey = str2double([hemisphere zone(1:2)]); +GeoKeyDirectoryTag.ProjectedCSTypeGeoKey = str2double([hemisphere sprintf('%02d',str2double(zone(regexp(zone,'[0-9]'))))]); +GeoKeyDirectoryTag.ProjLinearUnitsGeoKey = 9001; % Linear_Meter + +DEMr.georef.GeoKeyDirectoryTag = GeoKeyDirectoryTag; +DEMr.georef.mstruct = mstruct; +DEMr.name = [DEM.name ' (utm)']; + +else + DEMr = res; + DEMr.Z = Znew; + DEMr.name = [DEM.name ' (repr)']; + zone = []; +end + +% Transformation functions for imtransform +% (may want to check projfwd and projinv instead mfwdtran and minvtran) + function x = FWDTRANS(u,~) + [x,y] = mfwdtran(mstruct,u(:,2),u(:,1)); + x = [x y]; + end + + function u = INVTRANS(x,~) + [lati,long] = minvtran(mstruct,x(:,1),x(:,2)); + u = [long lati]; + end +end + diff --git a/stratiedSampleHanlder.m b/stratiedSampleHanlder.m new file mode 100644 index 0000000..9f55ae3 --- /dev/null +++ b/stratiedSampleHanlder.m @@ -0,0 +1,137 @@ +function samples_ids=stratiedSampleHanlder(data_dem_clear,dem_b,dem_t,dim,total_sample,ele_strata,distance,resolution) +%STRATIEDSAMPLEHANLDER Further select clear sky (land) pixels to normalize +%BT. +% +% Syntax +% +% samples_ids= +% stratiedSampleHanlder(data_dem_clear,dem_b,dem_t,dim,total_sample,ele_strata,distance) +% +% Description +% +% Stratied sampling method is used by Qiu et al., (2017). +% History: +% 1. Create this function. (1. January, 2017) +% 2. This stratied sampling method sometimes results in no enough +% samples. (8. March, 2018) +% +% Input arguments +% +% data_dem_clear Clear sky pixels' DEM. +% dem_b Basic elevation. +% dem_t Highest elevation. +% dim Dim for data. +% total_sample All clear sky samples (40,000). +% ele_strata Stratied sampling along elevation (300 meters). +% distance Minmum distance among different samples(450 meters). +% Distance rule will be removed if distance = 0. +% resolution Spatial resolution (Landsat 30 meters; Sentinel-2 20 meters). +% Output arguments +% +% Tempd Nomalized Temperature (BT). +% +% +% Author: Shi Qiu (shi.qiu@ttu.edu) +% Date: 8. March, 2018 + + +% % num_clear_sky_pixels=numel(data_dem_clear); +% % % no enough clear-sky pixels afther removing the pixels out of the upper +% % % and lower levels.more than 1/4 total samples (10,000) can be used for +% % % estimating lapse rate and c in topo correction. +% % if num_clear_sky_pixels=i_dem&data_dem_clear0 +% num_strata=num_strata+1; + strata_avail=[strata_avail;1]; + else + strata_avail=[strata_avail;0]; + end + clear dem_clear_index_tmp; + end + % equal samples in each stratum + num_sam_each_strata=round(total_sample/sum(strata_avail)); + clear total_sample; + samples_ids=[];% to restore samples selected. + % loop each strata and select samples + loop_i=1; + for i_dem=dem_b:ele_strata:dem_t + if strata_avail(loop_i) +% dem_clear_index_tmp=data_dem_clear>=i_dem&data_dem_clear=i_dem&data_dem_clearnum_max_tmp + num_tmp=num_max_tmp; + end + clear num_max_tmp; + samples_ids_rand_tmp=samples_ids_rand(1:num_tmp); + clear num_tmp; + % store data + samples_ids=[samples_ids;samples_ids_rand_tmp]; + clear samples_ids_rand samples_ids_rand_tmp; + else + num_max_tmp=num_sam_each_strata*samp_dis; + if num_tmp>num_max_tmp + num_tmp=num_max_tmp; + end + clear num_max_tmp; + samples_ids_rand_tmp=samples_ids_rand(1:num_tmp); + clear num_tmp; + samples_ids_rand=samples_ids_rand_tmp; + clear samples_ids_rand_tmp; + all_num_tmp=size(samples_ids_rand); + [i_tmp,j_tmp]=ind2sub(dim,samples_ids_rand); + ij_tmp=[i_tmp,j_tmp]; + clear i_tmp j_tmp; + % removing the clear-sky pixels of which distance lt 15. + idx_lt15 = rangesearch(ij_tmp,ij_tmp,samp_dis, 'distance','cityblock'); + recorder_tmp=zeros(all_num_tmp,'uint8')+1; + for i_idx=1:all_num_tmp + if recorder_tmp(i_idx)>0 % when this label is available. + recorder_tmp(cell2mat(idx_lt15(i_idx)))=0; + recorder_tmp(i_idx)=1;% reback the current label as 1. + end + end + clear all_num_tmp idx_lt15; + idx_used=find(recorder_tmp==1); + clear recorder_tmp; + num_tmp=size(idx_used,1); + num_max_tmp=num_sam_each_strata; + if num_tmp>num_max_tmp + num_tmp=num_max_tmp; + end + idx_used=idx_used(1:num_tmp); + clear num_tmp num_max_tmp; + % store data + samples_ids=[samples_ids;samples_ids_rand(idx_used)]; + clear samples_ids_rand idx_used; + end + end + loop_i=loop_i+1; + end + clear samp_dis num_sam_each_strata; + +% % % no enough clear-sky pixels afther removing the pixels out of the upper +% % % and lower levels.more than 1/4 total samples (10,000) can be used for +% % % estimating lapse rate and c in topo correction. +% % if numel(samples_ids) +% Some text +% Some more text +% Even more text +% +% +% Will produce: +% s.XMLname.Attributes.attrib1 = "Some value"; +% s.XMLname.Element.Text = "Some text"; +% s.XMLname.DifferentElement{1}.Attributes.attrib2 = "2"; +% s.XMLname.DifferentElement{1}.Text = "Some more text"; +% s.XMLname.DifferentElement{2}.Attributes.attrib3 = "2"; +% s.XMLname.DifferentElement{2}.Attributes.attrib4 = "1"; +% s.XMLname.DifferentElement{2}.Text = "Even more text"; +% +% Please note that the following characters are substituted +% '-' by '_dash_', ':' by '_colon_' and '.' by '_dot_' +% +% Written by W. Falkena, ASTI, TUDelft, 21-08-2010 +% Attribute parsing speed increased by 40% by A. Wanner, 14-6-2011 +% Added CDATA support by I. Smirnov, 20-3-2012 +% +% Modified by X. Mo, University of Wisconsin, 12-5-2012 + + if (nargin < 1) + clc; + help xml2struct + return + end + + if isa(file, 'org.apache.xerces.dom.DeferredDocumentImpl') || isa(file, 'org.apache.xerces.dom.DeferredElementImpl') + % input is a java xml object + xDoc = file; + else + %check for existance + if (exist(file,'file') == 0) + %Perhaps the xml extension was omitted from the file name. Add the + %extension and try again. + if (isempty(strfind(file,'.xml'))) + file = [file '.xml']; + end + + if (exist(file,'file') == 0) + error(['The file ' file ' could not be found']); + end + end + %read the xml file + xDoc = xmlread(file); + end + + %parse xDoc into a MATLAB structure + s = parseChildNodes(xDoc); + +end + +% ----- Subfunction parseChildNodes ----- +function [children,ptext,textflag] = parseChildNodes(theNode) + % Recurse over node children. + children = struct; + ptext = struct; textflag = 'Text'; + if hasChildNodes(theNode) + childNodes = getChildNodes(theNode); + numChildNodes = getLength(childNodes); + + for count = 1:numChildNodes + theChild = item(childNodes,count-1); + [text,name,attr,childs,textflag] = getNodeData(theChild); + + if (~strcmp(name,'#text') && ~strcmp(name,'#comment') && ~strcmp(name,'#cdata_dash_section')) + %XML allows the same elements to be defined multiple times, + %put each in a different cell + if (isfield(children,name)) + if (~iscell(children.(name))) + %put existsing element into cell format + children.(name) = {children.(name)}; + end + index = length(children.(name))+1; + %add new element + children.(name){index} = childs; + if(~isempty(fieldnames(text))) + children.(name){index} = text; + end + if(~isempty(attr)) + children.(name){index}.('Attributes') = attr; + end + else + %add previously unknown (new) element to the structure + children.(name) = childs; + if(~isempty(text) && ~isempty(fieldnames(text))) + children.(name) = text; + end + if(~isempty(attr)) + children.(name).('Attributes') = attr; + end + end + else + ptextflag = 'Text'; + if (strcmp(name, '#cdata_dash_section')) + ptextflag = 'CDATA'; + elseif (strcmp(name, '#comment')) + ptextflag = 'Comment'; + end + + %this is the text in an element (i.e., the parentNode) + if (~isempty(regexprep(text.(textflag),'[\s]*',''))) + if (~isfield(ptext,ptextflag) || isempty(ptext.(ptextflag))) + ptext.(ptextflag) = text.(textflag); + else + %what to do when element data is as follows: + %Text More text + + %put the text in different cells: + % if (~iscell(ptext)) ptext = {ptext}; end + % ptext{length(ptext)+1} = text; + + %just append the text + ptext.(ptextflag) = [ptext.(ptextflag) text.(textflag)]; + end + end + end + + end + end +end + +% ----- Subfunction getNodeData ----- +function [text,name,attr,childs,textflag] = getNodeData(theNode) + % Create structure of node info. + + %make sure name is allowed as structure name + name = toCharArray(getNodeName(theNode))'; + name = strrep(name, '-', '_dash_'); + name = strrep(name, ':', '_colon_'); + name = strrep(name, '.', '_dot_'); + + attr = parseAttributes(theNode); + if (isempty(fieldnames(attr))) + attr = []; + end + + %parse child nodes + [childs,text,textflag] = parseChildNodes(theNode); + + if (isempty(fieldnames(childs)) && isempty(fieldnames(text))) + %get the data of any childless nodes + % faster than if any(strcmp(methods(theNode), 'getData')) + % no need to try-catch (?) + % faster than text = char(getData(theNode)); + text.(textflag) = toCharArray(getTextContent(theNode))'; + end + +end + +% ----- Subfunction parseAttributes ----- +function attributes = parseAttributes(theNode) + % Create attributes structure. + + attributes = struct; + if hasAttributes(theNode) + theAttributes = getAttributes(theNode); + numAttributes = getLength(theAttributes); + + for count = 1:numAttributes + %attrib = item(theAttributes,count-1); + %attr_name = regexprep(char(getName(attrib)),'[-:.]','_'); + %attributes.(attr_name) = char(getValue(attrib)); + + %Suggestion of Adrian Wanner + str = toCharArray(toString(item(theAttributes,count-1)))'; + k = strfind(str,'='); + attr_name = str(1:(k(1)-1)); + attr_name = strrep(attr_name, '-', '_dash_'); + attr_name = strrep(attr_name, ':', '_colon_'); + attr_name = strrep(attr_name, '.', '_dot_'); + attributes.(attr_name) = str((k(1)+2):(end-1)); + end + end +end \ No newline at end of file