├── compile.sh ├── rpc_parser.py ├── README.md ├── utilities.py ├── orthorectify.py ├── c++ └── gwarp_worker_cpp.cpp └── gwarp++.py /compile.sh: -------------------------------------------------------------------------------- 1 | # This work was supported by the Intelligence Advanced 2 | # Research Projects Activity (IARPA) via Department of 3 | # Interior / Interior Business Center (DOI/IBC) contract 4 | # number D17PC00280. The U.S. Government is authorized to 5 | # reproduce and distribute reprints for Governmental purposes 6 | # notwithstanding any copyright annotation 7 | # thereon. Disclaimer: The views and conclusions contained 8 | # herein are those of the authors and should not be 9 | # interpreted as necessarily representing the official 10 | # policies or endorsements, either expressed or implied, of 11 | # IARPA, DOI/IBC, or the U.S. Government. 12 | 13 | # Author: Bharath Comandur, cjrbharath@gmail.com 14 | # Date: 11/24/2020 15 | 16 | set -e 17 | 18 | LIB_DIR=$CONDA_PREFIX/lib/ 19 | 20 | INCLUDE_DIR=$CONDA_PREFIX/include/ 21 | 22 | if [ ! -e "$LIB_DIR" ] 23 | then 24 | printf "\nERROR: "$LIB_DIR" not found\n$" 25 | exit 1 26 | fi 27 | 28 | if [ ! -e "$INCLUDE_DIR" ] 29 | then 30 | printf "\nERROR: "$INCLUDE_DIR" not found\n$" 31 | exit 1 32 | fi 33 | 34 | printf "\nCompiling\n" 35 | 36 | command="g++ -std=c++11 -O3 -fopenmp -L $LIB_DIR -I $INCLUDE_DIR c++/gwarp_worker_cpp.cpp -o c++/gwarp++ -lgdal -lboost_program_options -Wl,-rpath=$LIB_DIR" 37 | 38 | printf "\n$command\n" 39 | 40 | $command 41 | 42 | printf "\nDone\n\n" 43 | 44 | set +e 45 | -------------------------------------------------------------------------------- /rpc_parser.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This work was supported by the Intelligence Advanced 3 | Research Projects Activity (IARPA) via Department of 4 | Interior / Interior Business Center (DOI/IBC) contract 5 | number D17PC00280. The U.S. Government is authorized to 6 | reproduce and distribute reprints for Governmental purposes 7 | notwithstanding any copyright annotation 8 | thereon. Disclaimer: The views and conclusions contained 9 | herein are those of the authors and should not be 10 | interpreted as necessarily representing the official 11 | policies or endorsements, either expressed or implied, of 12 | IARPA, DOI/IBC, or the U.S. Government. 13 | 14 | Author: Bharath Comandur, cjrbharath@gmail.com 15 | Date: 11/24/2020 16 | 17 | Modified code written by Dr. Tanmay Prakash 18 | 19 | ''' 20 | 21 | import re 22 | import numpy as np 23 | 24 | 25 | RPB_KEYS = [ 26 | ('errBias','errBias'), 27 | ('errRand','errRand'), 28 | ('LINE_OFF','lineOffset'), 29 | ('SAMP_OFF','sampOffset'), 30 | ('LAT_OFF','latOffset'), 31 | ('LONG_OFF','longOffset'), 32 | ('HEIGHT_OFF','heightOffset'), 33 | ('LINE_SCALE','lineScale'), 34 | ('SAMP_SCALE','sampScale'), 35 | ('LAT_SCALE','latScale'), 36 | ('LONG_SCALE','longScale'), 37 | ('HEIGHT_SCALE','heightScale'), 38 | ('LINE_NUM_COEFF','lineNumCoef'), 39 | ('LINE_DEN_COEFF','lineDenCoef'), 40 | ('SAMP_NUM_COEFF','sampNumCoef'), 41 | ('SAMP_DEN_COEFF','sampDenCoef')] 42 | 43 | RPB_EXTRA_KEYS = ['satId', 'bandId', 'SpecId', 'errBias', 'errRand'] 44 | 45 | 46 | # Load the parameters from an RPB file into a dict 47 | def read_file_into_dict(rpcfile): 48 | f = open(rpcfile) 49 | d = {} 50 | flagGroup = False 51 | line = '' 52 | groupName = None 53 | for line_ in f: 54 | # Remove all whitespace 55 | line += line_.strip().replace(' ','').replace('\t','') 56 | if (not line.startswith('BEGIN_GROUP') 57 | and (not line.startswith('END_GROUP')) 58 | and (line.find(';') < 0)): 59 | # Keep appending until we have a ; 60 | continue 61 | if line.startswith('END;'): 62 | break 63 | # In case the line has stuff beyond the ; 64 | lines = line.split(';',1) 65 | if len(lines) > 1: 66 | line, lineNext = lines 67 | else: 68 | line = lines[0] 69 | lineNext = '' 70 | var, val = line.replace(';','').split('=') 71 | if flagGroup: 72 | if var == 'END_GROUP': 73 | assert groupName == val 74 | flagGroup = False 75 | else: 76 | d[groupName][var] = val 77 | else: 78 | if var == 'BEGIN_GROUP': 79 | groupName = val 80 | d[groupName] = {} 81 | flagGroup = True 82 | else: 83 | d[var] = val 84 | line = lineNext 85 | f.close() 86 | return d 87 | 88 | 89 | def load_rpb(rpcfile): 90 | 91 | d = read_file_into_dict(rpcfile) 92 | rpc_coefs = {} 93 | p = re.compile('\(' + '([^,]+),'*19 + '([^,]+),*\)') 94 | for key, rpb_key in RPB_KEYS: 95 | if key.endswith('COEFF'): 96 | poly_coefs = [float(x) 97 | for x in p.match(d['IMAGE'][rpb_key]).groups()] 98 | poly_coefs = np.array(poly_coefs) 99 | rpc_coefs[key] = poly_coefs 100 | else: 101 | rpc_coefs[key] = float(d['IMAGE'][rpb_key]) 102 | rpc_coefs['satId'] = d['satId'] 103 | rpc_coefs['SpecId'] = d['SpecId'] 104 | rpc_coefs['bandId'] = d['bandId'] 105 | 106 | return rpc_coefs 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gwarp++ - True orthorectification of satellite images using DSMs and RPCs 2 | 3 | Software to create true ortho photos of satellite images using rational polynomial coefficients (RPCs) and Digital Surface Models (DSMs). 4 | 5 | While the popular and powerful [gdalwarp](https://gdal.org/programs/gdalwarp.html) tool works well with a Digital Elevation Model (DEM), it does not account for the heights of elevated structures such as buildings and trees in a DSM. 6 | 7 | On the other hand, gwarp++ detects the occluded portions of the ground and creates a true ortho image. The occluded portions are marked with a no-data value. 8 | 9 | If you use gwarp++ in your research, **we would appreciate your citing our [paper](https://ieeexplore.ieee.org/document/9380895).** You can also find a copy on [arXiv](https://arxiv.org/abs/2008.10271). 10 | 11 | ```bash 12 | 13 | @article{Comandur_2021, 14 | title={Semantic Labeling of Large-Area Geographic Regions Using Multi-View and Multi-Date Satellite Images and Noisy OSM Training Labels}, 15 | ISSN={2151-1535}, 16 | url={http://dx.doi.org/10.1109/JSTARS.2021.3066944}, 17 | DOI={10.1109/jstars.2021.3066944}, 18 | journal={IEEE Journal of Selected Topics in Applied Earth Observations and Remote Sensing}, 19 | publisher={Institute of Electrical and Electronics Engineers (IEEE)}, 20 | author={Comandur, Bharath and Kak, Avinash}, 21 | year={2021}, 22 | pages={1–1} 23 | } 24 | 25 | ``` 26 | 27 | For additional details regarding gwarp++, please see Sections 2.5 and 5.1.2 of the dissertation [here](https://hammer.figshare.com/articles/thesis/Semantic_Labeling_of_Large_Geographic_Areas_Using_Multi-Date_and_Multi-View_Satellite_Images_and_Noisy_OpenStreetMap_Labels/12739556). 28 | 29 | ## Requirements: 30 | 31 | Install gdal (with C++ and python bindings). It is recommended to use an anaconda environment for this. 32 | 33 | Also install libboost program options as follows: 34 | ```bash 35 | $ sudo apt-get install -y libboost-program-options-dev 36 | ``` 37 | ### Compile gwarp C++ 38 | ```bash 39 | env= 40 | 41 | $ conda activate $env 42 | 43 | $ bash compile.sh 44 | ``` 45 | ### Run orthorectification 46 | 47 | There are two ways to do this. We can either use "orthorectify.py" to orthorectify multiple small images in parallel. Otherwise we can use "gwarp++.py" to orthorectify a single large image by dividing it into smaller pieces and orthorectifying the pieces in parallel. 48 | 49 | #### Orthorectify multiple images in parallel 50 | ```python 51 | $ python orthorectify.py [-h] -folder_img FOLDER_IMG 52 | [-folder_rpc FOLDER_RPC] 53 | -dsm_file DSM_FILE 54 | -dem_file DEM_FILE 55 | -folder_output FOLDER_OUTPUT 56 | -cache_dir CACHE_DIR 57 | [-parallel] 58 | [-num_workers NUM_WORKERS] 59 | [-dsm_mask_file DSM_MASK_FILE] 60 | [-image_interp IMAGE_INTERP] 61 | [-output_res OUTPUT_RES] 62 | [-nodata NODATA] 63 | ``` 64 | 65 | ##### Required Arguments 66 | 67 | ``` -folder_img FOLDER_IMG ``` - Folder containing images to orthorectify. Images should be in GTiff file format. The tifs can have the RPCs embedded in their metadata. 68 | 69 | ``` -dsm_file DSM_FILE ``` - DSM file with heights respect to WGS84 ellipsoid 70 | 71 | ``` -dem_file DEM_FILE ``` - DEM file indicating height of ground with respect to WGS84 ellipsoid 72 | 73 | ``` -folder_output FOLDER_OUTPUT ``` - Output folder 74 | 75 | ``` -cache_dir CACHE_DIR ``` - A cache/temporary directory. It should not exist. The program creates this directory and deletes it at the end. Please make sure it is not in any important folder such as /root or / . 76 | 77 | ##### Optional Arguments 78 | 79 | ``` -folder_rpc FOLDER_RPC ``` - Folder containing RPCs in RPB file format. There should be a one-to-one correspondence between the name of the tif files and the RPB files, i.e., an image A.tif should have a RPB file A.RPB. If this folder is specified, then only the images that have corresponding RPB files will be processed. Moreover this RPB file can be used to override any RPCs that are embedded in the GTiff metadata. 80 | 81 | ``` -parallel ``` - To process large tifs in parallel. The software chops up the image into smaller pieces. Warning - Consumes more memory. 82 | 83 | ``` -num_workers NUM_WORKERS ``` - Number of parallel processes to launch. The more workers, the more memory is used. 84 | 85 | ``` -dsm_mask_file DSM_MASK_FILE ``` - An optional file indicating points to ignore in the DSM. For example, water bodies might have noisy height values in a DSM. 86 | 87 | ``` -image_interp IMAGE_INTERP ``` - Interpolation method for the image. Can be either 'near' or 'bilinear'. 88 | 89 | ``` -output_res OUTPUT_RES ``` - Output ground sampling distance (GSD) in meters. Default is 0.5 m per pixel. Please note that the final output GTiff has its projection in the WGS84 coordinate system. The sampling distances in longitude and latitude are estimated using the specified output_res. 90 | 91 | ``` -nodata NODATA ``` - A value to indicate the occluded points in the final true ortho image. Default is -9999. 92 | 93 | #### Quickly orthorectify a large image 94 | ```bash 95 | Coming Soon 96 | ``` 97 | 98 | ## Acknowledgments 99 | 100 | ```bash 101 | This work was supported by the Intelligence Advanced 102 | Research Projects Activity (IARPA) via Department of 103 | Interior / Interior Business Center (DOI/IBC) contract 104 | number D17PC00280. The U.S. Government is authorized to 105 | reproduce and distribute reprints for Governmental purposes 106 | notwithstanding any copyright annotation 107 | thereon. Disclaimer: The views and conclusions contained 108 | herein are those of the authors and should not be 109 | interpreted as necessarily representing the official 110 | policies or endorsements, either expressed or implied, of 111 | IARPA, DOI/IBC, or the U.S. Government. 112 | ``` 113 | -------------------------------------------------------------------------------- /utilities.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This work was supported by the Intelligence Advanced 3 | Research Projects Activity (IARPA) via Department of 4 | Interior / Interior Business Center (DOI/IBC) contract 5 | number D17PC00280. The U.S. Government is authorized to 6 | reproduce and distribute reprints for Governmental purposes 7 | notwithstanding any copyright annotation 8 | thereon. Disclaimer: The views and conclusions contained 9 | herein are those of the authors and should not be 10 | interpreted as necessarily representing the official 11 | policies or endorsements, either expressed or implied, of 12 | IARPA, DOI/IBC, or the U.S. Government. 13 | 14 | Author: Bharath Comandur, cjrbharath@gmail.com 15 | Date: 11/24/2020 16 | ''' 17 | 18 | import sys,os 19 | 20 | 21 | def remove_folder_from_name(input_name): 22 | return os.path.split(os.path.abspath(input_name))[-1] 23 | 24 | def get_folder(input_name): 25 | return os.path.split(os.path.abspath(input_name))[0] 26 | 27 | def get_ext(input_name): 28 | return os.path.splitext(input_name)[-1] 29 | 30 | def remove_folder_ext(input_name): 31 | return os.path.splitext(os.path.split(os.path.abspath(input_name))[-1])[0] 32 | 33 | def create_directory(folder_name): 34 | 35 | if not os.path.isdir(folder_name): 36 | # Sometimes we get race conditions. To stop script from throwing error and exiting 37 | # I am actually surprised that I did not hit this till now 38 | try: 39 | os.makedirs(folder_name) 40 | except OSError as e: 41 | if e.errno != errno.EEXIST: 42 | print("\nERROR: Could not create %s" % folder_name) 43 | raise 44 | 45 | def remove_trailing_slash(input_folder): 46 | if input_folder.endswith('/'): 47 | return input_folder[:-1] 48 | else: 49 | return input_folder 50 | 51 | def check_if_exists(input_file_or_folder): 52 | if os.path.isfile(input_file_or_folder) or os.path.isdir(input_file_or_folder): 53 | return True 54 | else: 55 | raise ValueError("\n \n ERROR: " + input_file_or_folder + " does not exist. Stopping \n") 56 | return False -------------------------------------------------------------------------------- /orthorectify.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This work was supported by the Intelligence Advanced 3 | Research Projects Activity (IARPA) via Department of 4 | Interior / Interior Business Center (DOI/IBC) contract 5 | number D17PC00280. The U.S. Government is authorized to 6 | reproduce and distribute reprints for Governmental purposes 7 | notwithstanding any copyright annotation 8 | thereon. Disclaimer: The views and conclusions contained 9 | herein are those of the authors and should not be 10 | interpreted as necessarily representing the official 11 | policies or endorsements, either expressed or implied, of 12 | IARPA, DOI/IBC, or the U.S. Government. 13 | 14 | Author: Bharath Comandur, cjrbharath@gmail.com 15 | Date: 11/24/2020 16 | ''' 17 | 18 | import importlib 19 | gwarp = importlib.import_module('gwarp++') 20 | import glob 21 | import os, sys, shutil 22 | from utilities import remove_trailing_slash,remove_folder_ext, \ 23 | create_directory, check_if_exists, remove_folder_from_name, get_folder 24 | 25 | from multiprocessing import Pool 26 | 27 | #from pprint import pprint 28 | 29 | import argparse 30 | 31 | def call_ortho(input_list): 32 | 33 | #pp.pprint(input_list) 34 | """ 35 | If the chip and RPB file are in different folders, 36 | then c++ might not find the RPC metadata. So create 37 | tmp folder with symbolic links 38 | """ 39 | tmp_dir = input_list[0] 40 | rpc_name = input_list[-2] 41 | img_name = input_list[-1] 42 | output_name = input_list[-4] 43 | just_name = remove_folder_ext(img_name) 44 | tmp_dir = tmp_dir + '/' + just_name + '/' 45 | create_directory(tmp_dir) 46 | create_directory(tmp_dir + '/orthorectified') 47 | 48 | new_img_name = tmp_dir + '/' + remove_folder_from_name(img_name) 49 | if rpc_name: 50 | new_rpc_name = tmp_dir + '/' + remove_folder_from_name(rpc_name) 51 | else: 52 | new_rpc_name = None 53 | 54 | # Save output locally first 55 | new_output_name = tmp_dir + '/orthorectified/' + remove_folder_from_name(output_name) 56 | # create symbolic links 57 | os.symlink(os.path.abspath(img_name), new_img_name) 58 | print("\nSymbolinking ", img_name, new_img_name) 59 | 60 | if rpc_name: 61 | os.symlink(os.path.abspath(rpc_name), new_rpc_name) 62 | print("\nSymbolinking ", rpc_name, new_rpc_name) 63 | 64 | # Remove cache dir,rpc and img from input list 65 | copy_list = [i for i in input_list[1:-2]] 66 | if new_rpc_name: 67 | copy_list.extend([new_rpc_name]) 68 | opidx = -4 69 | else: 70 | copy_list = copy_list[:-1] 71 | opidx = -2 72 | copy_list.extend([new_img_name]) 73 | copy_list[opidx] = new_output_name 74 | 75 | #pprint(copy_list) 76 | # call orthorect 77 | gwarp.main(copy_list) 78 | 79 | # move temp output back to original 80 | shutil.move(new_output_name, output_name) 81 | 82 | # if coordmap was saved move it also to original 83 | new_output_coordmap_name = new_output_name.replace('.tif', '_coord_map.tif') 84 | if os.path.isfile(new_output_coordmap_name): 85 | shutil.move(new_output_coordmap_name, output_name.replace('.tif', '_coord_map.tif')) 86 | return 87 | 88 | def batch_orthorectify(folder_img, folder_rpc, dsm_file, dem_file, 89 | folder_output, CACHE_DIR, parallel_flag = False, num_workers = 1, dsm_mask_file = None, 90 | image_interp = 'bilinear', output_res=0.5, dst_nodata = -9999): 91 | 92 | """ 93 | """ 94 | 95 | ortho_cache_dir = CACHE_DIR + "/orthorectify/" 96 | if os.path.isdir(CACHE_DIR): 97 | print("ERROR: Please delete CACHE_DIR and rerun") 98 | sys.exit(1) 99 | # shutil.rmtree(CACHE_DIR) 100 | create_directory(ortho_cache_dir) 101 | 102 | # Only orthorecitfy chips for which rpc file is found 103 | # We require rpc files to be in RPB format 104 | folder_img = remove_trailing_slash(folder_img) 105 | check_if_exists(folder_img) 106 | if folder_rpc: 107 | folder_rpc = remove_trailing_slash(folder_rpc) 108 | check_if_exists(folder_rpc) 109 | check_if_exists(dsm_file) 110 | check_if_exists(dem_file) 111 | if dsm_mask_file is not None: 112 | check_if_exists(dsm_mask_file) 113 | 114 | folder_output = remove_trailing_slash(folder_output) 115 | create_directory(folder_output) 116 | 117 | initial_files_list = glob.glob(folder_img + "/*.tif") 118 | 119 | 120 | rpc_files_list = [] 121 | img_files_list = [] 122 | 123 | for img_file in initial_files_list: 124 | just_name = remove_folder_ext(img_file) 125 | if folder_rpc: 126 | rpc_file = os.path.join(folder_rpc, just_name + '.RPB') 127 | if os.path.isfile(rpc_file): 128 | img_files_list.append(img_file) 129 | rpc_files_list.append(rpc_file) 130 | else: 131 | print("WARNING:RPC file %s does not exist for img %s. Skipping" % (rpc_file, img_file)) 132 | else: 133 | img_files_list.append(img_file) 134 | rpc_files_list.append(None) 135 | 136 | 137 | # If parallel do orthorectify for img,rpc combo in batches 138 | input_mp_list = [] 139 | dem_interp = 'near' 140 | image_interp = image_interp 141 | output_res = str(output_res) 142 | dst_nodata = str(dst_nodata) 143 | 144 | for img_file, rpc_file in zip(img_files_list, rpc_files_list): 145 | input_mp = [] 146 | input_mp.extend([ortho_cache_dir]) 147 | input_mp.extend(['-RPC_DEM', dsm_file]) 148 | input_mp.extend(['-low_res_dem', dem_file]) 149 | input_mp.extend(['-dem_interp', dem_interp]) 150 | input_mp.extend(['-image_interp', image_interp]) 151 | input_mp.extend(['-output_res', output_res]) 152 | input_mp.extend(['-dst_nodata', dst_nodata]) 153 | input_mp.extend(['-save_mapping_flag']) 154 | input_mp.extend(['-dem_mask_file', dsm_mask_file]) 155 | output_file = os.path.join(folder_output, os.path.split(img_file)[-1]) 156 | input_mp.extend(['-output', output_file]) 157 | #input_mp.extend(['-utm']) 158 | #input_mp.extend(['-compress_flag']) 159 | #input_mp.extend(['-parallel']) 160 | #input_mp.extend(['-gdal_merge', GDAL_MERGE]) 161 | #input_mp.extend(['-n_workers' N_WORKERS]) 162 | input_mp.extend(['-rpc_file', rpc_file]) 163 | input_mp.extend([img_file]) 164 | input_mp_list.append(input_mp) 165 | 166 | if parallel_flag: 167 | assert num_workers > 1 168 | mypool = Pool(num_workers) 169 | mypool.map(call_ortho, input_mp_list) 170 | mypool.close() 171 | else: 172 | for input_mp in input_mp_list: 173 | call_ortho(input_mp) 174 | 175 | # remove cache dir 176 | shutil.rmtree(CACHE_DIR) 177 | 178 | return 179 | 180 | 181 | if __name__ == '__main__': 182 | 183 | parser = argparse.ArgumentParser(description = "a module to do orthorectification") 184 | 185 | parser.add_argument('-folder_img', '--folder_img', type = str, help = 'folder_img', required = True) 186 | parser.add_argument('-folder_rpc', '--folder_rpc', type = str, help = 'folder_rpc', default = None) 187 | parser.add_argument('-dsm_file', '--dsm_file', type = str, help = 'dsm_file', required = True) 188 | parser.add_argument('-dem_file', '--dem_file', type = str, help = 'dem_file', required = True) 189 | parser.add_argument('-folder_output', '--folder_output', type = str, help = 'output folder', required = True) 190 | parser.add_argument('-cache_dir', '--cache_dir', type = str, help = 'cache_dir', required = True) 191 | parser.add_argument('-parallel', '--parallel', action="store_true", help = 'parallel') 192 | parser.add_argument('-num_workers', '--num_workers', default = 1, help = 'num_workers', type = int) 193 | parser.add_argument('-dsm_mask_file', '--dsm_mask_file', default = None, type = str, help = 'dsm_mask_file') 194 | parser.add_argument('-image_interp', '--image_interp', default = 'bilinear', type = str, help = 'image_interp') 195 | parser.add_argument('-output_res', '--output_res', default = 0.5, type = float, help = 'output GSD in metres') 196 | parser.add_argument('-nodata', '--nodata', default = -9999, type = int, help = 'no data value to indicate occlusion in output') 197 | 198 | args = parser.parse_args() 199 | 200 | batch_orthorectify(args.folder_img, args.folder_rpc, args.dsm_file, args.dem_file, 201 | args.folder_output, args.cache_dir, parallel_flag = args.parallel, 202 | num_workers = args.num_workers, dsm_mask_file = args.dsm_mask_file, 203 | image_interp = args.image_interp, output_res = args.output_res, 204 | dst_nodata = args.nodata) 205 | -------------------------------------------------------------------------------- /c++/gwarp_worker_cpp.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This work was supported by the Intelligence Advanced 3 | Research Projects Activity (IARPA) via Department of 4 | Interior / Interior Business Center (DOI/IBC) contract 5 | number D17PC00280. The U.S. Government is authorized to 6 | reproduce and distribute reprints for Governmental purposes 7 | notwithstanding any copyright annotation 8 | thereon. Disclaimer: The views and conclusions contained 9 | herein are those of the authors and should not be 10 | interpreted as necessarily representing the official 11 | policies or endorsements, either expressed or implied, of 12 | IARPA, DOI/IBC, or the U.S. Government. 13 | 14 | Author: Bharath Comandur, cjrbharath@gmail.com 15 | Date: 11/24/2020 16 | */ 17 | 18 | #include "gdal_priv.h" // for common GDAL functions 19 | #include "gdal_alg.h" // for GDALCreateRPCTransformer and GDALRPCTransform function 20 | #include "cpl_conv.h" // for CPLMalloc() 21 | #include // for cout, cerr 22 | #include // for string datatype 23 | #include //NaN 24 | #include // for nice argument parsing 25 | #include 26 | #include //uint16_t 27 | 28 | void convert_pixel_coords_to_projected_coords(double col, double row, std::vector geotransform, std::vector & proj_x_y) 29 | { 30 | 31 | proj_x_y[0] = geotransform[0] + (geotransform[1]*col) + (geotransform[2]*row); 32 | 33 | proj_x_y[1] = geotransform[3] + (geotransform[4]*col) + (geotransform[5]*row); 34 | 35 | 36 | } 37 | 38 | void convert_projected_coords_to_pixel_coords(double proj_x, double proj_y, std::vector geotransform, double* col, double* row) 39 | { 40 | 41 | col[0] = (proj_x - geotransform[0])/geotransform[1]; 42 | 43 | row[0] = (proj_y - geotransform[3])/geotransform[5]; 44 | 45 | } 46 | 47 | template 48 | bool createOrthoImage(int start_row_final, int end_row_final, int start_col_final, int end_col_final, 49 | int ngrid_cols, std::vector& valid, std::vector image_grid_col, std::vector image_grid_row, 50 | int image_pre_ortho_height, int image_pre_ortho_width, std::vector height, std::vector lookup, 51 | double height_thresh_filter_metres, int nbands, int outputWidth, int outputHeight, 52 | std::string image_interpolate_method, GDALDataset *poRasterDataset, 53 | std::vector& outBandsAsArray, GDALDataType outputDataType, 54 | std::vector image_grid_col_float, std::vector image_grid_row_float, float halfshift, 55 | std::vector& output_raster_coord_map, bool save_mapping_flag) 56 | { 57 | //#pragma omp parallel for // Causes segmentation fault for large array 58 | int x,y, twod_idx, clip_twod_idx, agnostic_idx; 59 | float w_x, w_y, x_f, y_f; 60 | T img00, img01, img10, img11; 61 | int x0, x1, y0, y1; 62 | int lookup_idx; 63 | int readflag; 64 | double imgval; 65 | 66 | for (int j = start_row_final; j < end_row_final; j++) 67 | { 68 | for (int i = start_col_final; i < end_col_final; i++) 69 | { 70 | twod_idx = (j*ngrid_cols) + i; 71 | if (valid[twod_idx]) 72 | { 73 | agnostic_idx = ((j - start_row_final)*outputWidth) + (i - start_col_final); 74 | if (save_mapping_flag) 75 | { 76 | output_raster_coord_map[agnostic_idx] = image_grid_col_float[twod_idx]; 77 | output_raster_coord_map[agnostic_idx + (outputWidth*outputHeight)] = image_grid_row_float[twod_idx]; 78 | } 79 | 80 | x = image_grid_col[twod_idx]; 81 | y = image_grid_row[twod_idx]; 82 | lookup_idx = (x*image_pre_ortho_height) + y; 83 | if ( (lookup[lookup_idx] - height[twod_idx]) > height_thresh_filter_metres ) 84 | { 85 | valid[twod_idx] = 0; 86 | } 87 | else 88 | { 89 | if (save_mapping_flag) 90 | output_raster_coord_map[agnostic_idx + 2*(outputWidth*outputHeight)] = 1; 91 | 92 | if (image_interpolate_method == "near") 93 | { 94 | for (int bandIdx = 1; bandIdx <= nbands; bandIdx++) 95 | { 96 | clip_twod_idx = ((bandIdx -1)*outputWidth*outputHeight) + ((j - start_row_final)*outputWidth) + (i - start_col_final); 97 | readflag = poRasterDataset->GetRasterBand(bandIdx)->RasterIO(GF_Read, x, y, 1, 1, &outBandsAsArray[clip_twod_idx], 1, 1, outputDataType, 0, 0); 98 | } 99 | } 100 | else if (image_interpolate_method == "bilinear") 101 | { 102 | 103 | /* 104 | * Turns out the near and iinterpolated image look shifted 105 | * if I have an extra halfshift here 106 | */ 107 | x_f = image_grid_col_float[twod_idx]; //- halfshift; 108 | y_f = image_grid_row_float[twod_idx]; //- halfshift; 109 | 110 | x0 = floor(x_f); 111 | y0 = floor(y_f); 112 | 113 | x1 = x0 + 1; 114 | y1 = y0 + 1; 115 | 116 | w_x = x_f - (float)(x0); 117 | w_y = y_f - (float)(y0); 118 | 119 | if (x0 < 0) 120 | x0 = x1; 121 | if (y0 < 0) 122 | y0 = y1; 123 | if (x1 >= image_pre_ortho_width) 124 | x1 = x0; 125 | if (y1 >= image_pre_ortho_height) 126 | y1 = y0; 127 | 128 | for (int bandIdx = 1; bandIdx <= nbands; bandIdx++) 129 | { 130 | clip_twod_idx = ((bandIdx -1)*outputWidth*outputHeight) + ((j - start_row_final)*outputWidth) + (i - start_col_final); 131 | readflag = poRasterDataset->GetRasterBand(bandIdx)->RasterIO(GF_Read, x0, y0, 1, 1, &img00, 1, 1, outputDataType, 0, 0); 132 | readflag = poRasterDataset->GetRasterBand(bandIdx)->RasterIO(GF_Read, x1, y0, 1, 1, &img01, 1, 1, outputDataType, 0, 0); 133 | readflag = poRasterDataset->GetRasterBand(bandIdx)->RasterIO(GF_Read, x0, y1, 1, 1, &img10, 1, 1, outputDataType, 0, 0); 134 | readflag = poRasterDataset->GetRasterBand(bandIdx)->RasterIO(GF_Read, x1, y1, 1, 1, &img11, 1, 1, outputDataType, 0, 0); 135 | 136 | imgval = ( (((double)(img00))*(1.0 - w_x)*(1.0 - w_y)) + (((double)(img01))*w_x*(1.0 - w_y)) + 137 | (((double)(img10))*(1.0 - w_x)*w_y) + (((double)(img11))*w_x*w_y) ); 138 | 139 | /*if (outputDataType == GDT_Int16 || outputDataType == GDT_UInt16 || outputDataType == GDT_Int32 140 | || outputDataType == GDT_Byte || outputDataType == GDT_UInt32) 141 | outBandsAsArray[clip_twod_idx] = round(imgval); 142 | else if (outputDataType == GDT_Float32 || outputDataType == GDT_Float64) 143 | outBandsAsArray[clip_twod_idx] = imgval;*/ 144 | outBandsAsArray[clip_twod_idx] = imgval; 145 | 146 | } 147 | 148 | } 149 | 150 | } 151 | } 152 | 153 | } 154 | } 155 | 156 | return 1; 157 | 158 | } 159 | 160 | template 161 | bool writeGTiffImage(std::string outputRasterName, int outputWidth, int outputHeight, int nbands, GDALDataType outputDataType, 162 | std::vector raster_corners_lon, std::vector raster_corners_lat, 163 | double *spacing_lon, double *spacing_lat,GDALDataset *poDemDataset, 164 | std::vector& outBandsAsArray, T dst_nodata, bool *compress_flag, 165 | bool tiled_flag = 1) 166 | { 167 | int writeflag; 168 | const char *pszFormat = "GTiff"; 169 | GDALDriver *poDriver; 170 | char **papszMetadata; 171 | poDriver = GetGDALDriverManager()->GetDriverByName(pszFormat); 172 | if( poDriver == NULL ) 173 | exit( 1 ); 174 | papszMetadata = poDriver->GetMetadata(); 175 | /*if( CSLFetchBoolean( papszMetadata, GDAL_DCAP_CREATE, FALSE ) ) 176 | printf( "Driver %s supports Create() method.\n", pszFormat ); 177 | else 178 | return 0;*/ 179 | 180 | GDALDataset *poDstDS; 181 | char **ds_papszOptions = NULL; 182 | if (tiled_flag) 183 | ds_papszOptions = CSLSetNameValue( ds_papszOptions, "TILED", "YES" ); 184 | if (compress_flag[0]) 185 | { // In case of compression, gdal wont know if BIGTIFF is needed or not. So force for safety 186 | ds_papszOptions = CSLSetNameValue( ds_papszOptions, "COMPRESS", "LZW" ); 187 | ds_papszOptions = CSLSetNameValue( ds_papszOptions, "BIGTIFF", "YES" ); 188 | } 189 | 190 | poDstDS = poDriver->Create(outputRasterName.c_str(), outputWidth, outputHeight, nbands, outputDataType, ds_papszOptions); 191 | double geotransform[6] = {raster_corners_lon[0] - (spacing_lon[0]/2.0), spacing_lon[0], 0, raster_corners_lat[0] - (spacing_lat[0]/2.0), 0, spacing_lat[0]}; 192 | poDstDS->SetGeoTransform( geotransform ); 193 | 194 | poDstDS->SetProjection( poDemDataset->GetProjectionRef()); 195 | GDALRasterBand *poDstBand; 196 | 197 | //std::vector outBand (outputWidth*outputHeight, dst_nodata); 198 | for (int bandIdx = 1; bandIdx <= nbands; bandIdx++) 199 | { 200 | poDstBand = poDstDS->GetRasterBand(bandIdx); 201 | poDstBand->SetNoDataValue(dst_nodata); 202 | //std::copy (outBandsAsArray.begin() + (bandIdx -1)*outputWidth*outputHeight, outBandsAsArray.begin() + (bandIdx)*outputWidth*outputHeight, outBand.begin()); 203 | //writeflag = poDstBand->RasterIO( GF_Write, 0, 0, outputWidth, outputHeight, &outBand[0], outputWidth, outputHeight, outputDataType, 0, 0); 204 | writeflag = poDstBand->RasterIO( GF_Write, 0, 0, outputWidth, outputHeight, &outBandsAsArray[(bandIdx -1)*outputWidth*outputHeight], outputWidth, outputHeight, outputDataType, 0, 0); 205 | } 206 | // Once we're done, close the dataset 207 | 208 | GDALClose( (GDALDatasetH) poDstDS ); 209 | 210 | return 1; 211 | } 212 | 213 | 214 | std::string replaceFirstOccurrence(std::string& s, const std::string& toReplace, const std::string& replaceWith) 215 | { 216 | std::size_t pos = s.find(toReplace); 217 | if (pos == std::string::npos) return s; 218 | return s.replace(pos, toReplace.length(), replaceWith); 219 | } 220 | 221 | bool get_ortho_grid_worker (std::string outputRasterName, double *start_lon, double *end_lon, 222 | double *start_lat, double *end_lat, double *spacing_lon, double *spacing_lat, 223 | std::string raster_file, std::string dem_file, std::string dem_interpolate_method, 224 | std::string image_interpolate_method, double *dst_nodata, int*block_idx, bool *save_mapping_flag, 225 | bool *border_block_flag, std::string rpc_file, bool *compress_flag, std::string dem_mask_file, 226 | std::string dtm_file) 227 | 228 | { 229 | 230 | /* function that implements gwarp++ for block. A block could also cover an entire raster if it is small enough. 231 | By block we refer to a grid in lon lat coordinates */ 232 | 233 | float halfshift = 0.5; 234 | /* print inputs 235 | std::cout << std::endl; 236 | std::cout << outputRasterName << std::endl; 237 | std::cout << *start_lon << std::endl; 238 | std::cout << *end_lon << std::endl; 239 | std::cout << *start_lat << std::endl; 240 | std::cout << *end_lat << std::endl; 241 | std::cout << *spacing_lon << std::endl; 242 | std::cout << *spacing_lat << std::endl; 243 | std::cout << raster_file << std::endl; 244 | std::cout << dem_file << std::endl; 245 | std::cout << dem_interpolate_method << std::endl; 246 | std::cout << image_interpolate_method << std::endl; 247 | std::cout << *dst_nodata << std::endl; 248 | std::cout << *block_idx << std::endl; 249 | std::cout << *save_mapping_flag << std::endl; 250 | std::cout << *border_block_flag << std::endl << std::endl; */ 251 | 252 | /* We have to account for the effects of occlusion. Since we split the lon lat grid into blocks, we need to account for tall objects 253 | nearby. So we add a padding of 100 points in each direction. 254 | # BTODO - Make padding dependent on data. */ 255 | double padding = 200; 256 | 257 | /* A typical storey height is 3 metres. So all points that get projected to the same pixel whose height is within 1.5 metres 258 | of the maximum height for that pixel are considered valid. This is to reduce the holes in actual building rooftops */ 259 | double height_thresh_filter_metres = 1.5; 260 | 261 | bool valid_flag = 0; 262 | 263 | float no_data_val = -32000; 264 | // Encapsulate within a try catch statement to detect blocks that fail 265 | try 266 | { 267 | // register all known drivers 268 | GDALAllRegister(); 269 | 270 | // pointers to the raster and dem dataset 271 | GDALDataset *poRasterDataset, *poDemDataset, *poMaskDemDataset, *poDtmDataset; 272 | 273 | // open raster dataset 274 | poRasterDataset = (GDALDataset *) GDALOpen(raster_file.c_str(), GA_ReadOnly); 275 | 276 | if( poRasterDataset == NULL ) 277 | { 278 | std::cout << "ERROR IN OPENING RASTER DATASET" << std::endl; 279 | abort(); 280 | } 281 | 282 | /*std::cout << poRasterDataset->GetRasterBand(1)->GetNoDataValue() << std::endl; 283 | 284 | if (std::isnan(no_data_input_value)) 285 | { 286 | std::cout << "JJD"; 287 | no_data_input_value = no_data_val; 288 | } 289 | */ 290 | // open dem dataset 291 | poDemDataset = (GDALDataset *) GDALOpen(dem_file.c_str(), GA_ReadOnly); 292 | 293 | if( poDemDataset == NULL ) 294 | { 295 | std::cout << "ERROR IN OPENING DEM DATASET" << std::endl; 296 | abort(); 297 | } 298 | 299 | poMaskDemDataset = NULL; 300 | if (dem_mask_file != "None") 301 | { 302 | poMaskDemDataset = (GDALDataset *) GDALOpen(dem_mask_file.c_str(), GA_ReadOnly); 303 | 304 | if( poMaskDemDataset == NULL ) 305 | { 306 | std::cout << "ERROR IN OPENING MASK FOR DEM DATASET" << std::endl; 307 | abort(); 308 | } 309 | } 310 | 311 | poDtmDataset = NULL; 312 | if (dtm_file != "None") 313 | { 314 | poDtmDataset = (GDALDataset *) GDALOpen(dtm_file.c_str(), GA_ReadOnly); 315 | 316 | if( poDtmDataset == NULL ) 317 | { 318 | std::cout << "ERROR IN OPENING DTM DATASET" << std::endl; 319 | abort(); 320 | } 321 | } 322 | 323 | 324 | // Height and width of dem 325 | int dem_width = poDemDataset->GetRasterXSize(); 326 | int dem_height = poDemDataset->GetRasterYSize(); 327 | 328 | // The dem can have no data value as negative integers if it is stored as uint16. 329 | double no_data_height_value = (double)poDemDataset->GetRasterBand(1)->GetNoDataValue(); 330 | 331 | /* Declaring it as GDALRPCInfo rpcinfo could blow out the stack memory */ 332 | GDALRPCInfo *rpcinfo = new GDALRPCInfo; 333 | char *papszOptions = NULL; 334 | void *rpc_transformer; 335 | 336 | if( !GDALExtractRPCInfo(poRasterDataset->GetMetadata( "RPC" ), rpcinfo) ) 337 | { 338 | std::cout << " ERROR: No rpc metadata found" << std::endl; 339 | abort(); 340 | } 341 | 342 | // Create roc transformer. Note gdal's convention is opposite to normal rpc. So by default 343 | // it maps pixel line sample to lon, lat 344 | /*GDALCreateRPCTransformer( GDALRPCInfo *psRPCInfo, int bReversed, double dfPixErrThreshold, char **papszOptions )*/ 345 | rpc_transformer = GDALCreateRPCTransformer(rpcinfo, 0, 0, &papszOptions); 346 | 347 | // Raster corners for this block 348 | std::vector raster_corners_lon(2); 349 | raster_corners_lon[0] = start_lon[0]; 350 | raster_corners_lon[1] = end_lon[0]; 351 | 352 | std::vector raster_corners_lat(2); 353 | raster_corners_lat[0] = start_lat[0]; 354 | raster_corners_lat[1] = end_lat[0]; 355 | 356 | std::vector demGeoTransform(6); 357 | poDemDataset->GetGeoTransform(&demGeoTransform[0]); 358 | 359 | 360 | std::vector dem_top_left_lon_lat(2); 361 | convert_pixel_coords_to_projected_coords(0.0, 0.0, demGeoTransform, dem_top_left_lon_lat); 362 | 363 | std::vector dem_bottom_right_lon_lat(2); 364 | convert_pixel_coords_to_projected_coords((double) dem_width, (double)dem_height, demGeoTransform, dem_bottom_right_lon_lat); 365 | 366 | /* Skip if dsm does not cover block. Otherwise the rpc transformer assumes 367 | * 0 for NaNs and creates ghosts at the boundaries of the dem and the image.*/ 368 | 369 | if (start_lon[0] < dem_top_left_lon_lat[0] || start_lat[0] > dem_top_left_lon_lat[1]) 370 | { 371 | printf("\nDEM does not cover block (start_lon,start_lat=%lf,%lf dem_corners=%lf,%lf)\n", start_lon[0], start_lat[0], dem_top_left_lon_lat[0], dem_top_left_lon_lat[1]); 372 | //return 0; 373 | } 374 | 375 | if (end_lon[0] > dem_bottom_right_lon_lat[0] || end_lat[0] < dem_bottom_right_lon_lat[1]) 376 | { 377 | printf("\nDEM does not cover block (end_lon,end_lat=%lf,%lf dem_corners=%lf,%lf)\n", end_lon[0], end_lat[0], dem_bottom_right_lon_lat[0], dem_bottom_right_lon_lat[1]); 378 | //return 0; 379 | } 380 | 381 | //printf("%.15f,%.15f\n", dem_top_left_lon_lat[0], dem_top_left_lon_lat[1]); 382 | 383 | //printf("%.15f,%.15f\n", dem_bottom_right_lon_lat[0], dem_bottom_right_lon_lat[1]); 384 | 385 | // # Create the grid of points in lon lat coordinates with the padding 386 | int ngrid_cols = round( ((raster_corners_lon[1] + (padding*spacing_lon[0]) - (raster_corners_lon[0] - (padding*spacing_lon[0])))/spacing_lon[0]) ); 387 | int ngrid_rows = round( ((raster_corners_lat[1] + (padding*spacing_lat[0]) - (raster_corners_lat[0] - (padding*spacing_lat[0])))/spacing_lat[0]) ); 388 | //std::cout << ngrid_cols << " " << ngrid_rows << " " << raster_corners_lat[1]<< " " << raster_corners_lat[0] << " " << spacing_lat[0] << std::endl; 389 | 390 | // Get the indices of the actual grid we save, i.e minus the padding 391 | int start_col_final = padding; 392 | int end_col_final = (start_col_final + (1.0*(raster_corners_lon[1] - raster_corners_lon[0])/spacing_lon[0]) + 1); 393 | /*if (border_block_flag[0]) 394 | //end_col_final = end_col_final + 1;*/ 395 | int start_row_final = padding; 396 | int end_row_final = (start_row_final + (1.0*(raster_corners_lat[1] - raster_corners_lat[0])/spacing_lat[0]) + 1); 397 | /*if (border_block_flag[0]) 398 | end_row_final = end_row_final + 1;*/ 399 | 400 | //std::cout << end_col_final << " " << start_col_final << " " << end_row_final << " " << start_row_final << std::endl << std::endl; 401 | //std::cout << end_col_final - start_col_final << " " << end_row_final - start_row_final << std::endl << std::endl; 402 | 403 | //double* ortho_grid_lon = new double[ngrid_cols*ngrid_rows]; 404 | //double* ortho_grid_lat = new double[ngrid_cols*ngrid_rows]; 405 | 406 | std::vector image_grid_col_float (ngrid_cols*ngrid_rows, no_data_val); 407 | std::vector image_grid_row_float (ngrid_cols*ngrid_rows, no_data_val); 408 | 409 | std::vector image_grid_col (ngrid_cols*ngrid_rows, no_data_val); 410 | std::vector image_grid_row (ngrid_cols*ngrid_rows, no_data_val); 411 | 412 | std::vector height (ngrid_cols*ngrid_rows, no_data_height_value); 413 | std::vector valid (ngrid_cols*ngrid_rows, 0); 414 | 415 | double dem_col; 416 | double dem_row; 417 | double height_val; 418 | double lon; 419 | double lat; 420 | 421 | 422 | GDALRasterBand *poDemBand; 423 | poDemBand = poDemDataset->GetRasterBand( 1 ); 424 | 425 | GDALRasterBand *poMaskDemBand; 426 | if ( dem_mask_file != "None" ) 427 | { 428 | poMaskDemBand = poMaskDemDataset->GetRasterBand( 1 ); 429 | } 430 | 431 | GDALRasterBand *poDtmBand; 432 | std::vector dtmGeoTransform(6,0); 433 | double no_data_dtm_value; 434 | if ( dtm_file != "None" ) 435 | { 436 | poDtmBand = poDtmDataset->GetRasterBand( 1 ); 437 | poDtmDataset->GetGeoTransform(&dtmGeoTransform[0]); 438 | no_data_dtm_value = (double)poDtmBand->GetNoDataValue(); 439 | } 440 | 441 | int image_pre_ortho_width = poRasterDataset->GetRasterXSize(); 442 | int image_pre_ortho_height = poRasterDataset->GetRasterYSize(); 443 | int nbands = poRasterDataset->GetRasterCount(); 444 | int lookup_idx; 445 | int readflag, writeflag, readmaskflag,readdtmflag; 446 | 447 | std::vector lookup (image_pre_ortho_width*image_pre_ortho_height, no_data_height_value); 448 | 449 | //std::cout << no_data_height_value << std::endl; 450 | //std::cout << lookup[1] << std::endl; 451 | int twod_idx; 452 | int wall_twod_idx; 453 | 454 | int num_wallpts = 10; //21; 455 | double wall_step = 0; //0.5; 456 | double wall_step_size = 0.5; 457 | 458 | uint8_t dem_mask_value = 0; 459 | 460 | int16_t dtm_height_val; 461 | double dtm_col; 462 | double dtm_row; 463 | 464 | // Height and width of dtm 465 | int dtm_width = poDtmDataset->GetRasterXSize(); 466 | int dtm_height = poDtmDataset->GetRasterYSize(); 467 | 468 | //#pragma omp parallel for // Causes segmentation fault for large array 469 | for (int j = 0; j < ngrid_rows; j++) 470 | { 471 | for (int i=0; i < ngrid_cols; i++) 472 | { 473 | twod_idx = (j*ngrid_cols) + i; 474 | lon = raster_corners_lon[0] + (((double)(i - padding))*spacing_lon[0]); 475 | 476 | lat = raster_corners_lat[0] + (((double)(j - padding))*spacing_lat[0]); 477 | 478 | convert_projected_coords_to_pixel_coords(lon, lat, demGeoTransform, &dem_col, &dem_row); 479 | 480 | if ( dem_col < 0 || dem_col >= dem_width || dem_row < 0 || dem_row >= dem_height) 481 | { 482 | continue; 483 | } 484 | else 485 | { 486 | readflag = poDemBand->RasterIO(GF_Read, floor(dem_col), floor(dem_row), 1, 1, &height[twod_idx], 1, 1, GDT_Float64, 0, 0); 487 | 488 | if ( dem_mask_file != "None" ) 489 | { 490 | readmaskflag = poMaskDemBand->RasterIO(GF_Read, floor(dem_col), floor(dem_row), 1, 1, &dem_mask_value, 1, 1, GDT_Byte, 0, 0); 491 | if (dem_mask_value == 255) 492 | { 493 | std::cout << "Skipping Water Point" << std::endl; 494 | dem_mask_value = 0; // Reset dem_mask_value 495 | continue; 496 | } 497 | } 498 | 499 | if (height[twod_idx] != no_data_height_value && ! std::isnan(height[twod_idx])) 500 | { 501 | 502 | if ( dtm_file != "None ") 503 | { 504 | // Load height value from dtm and calculate num_wallpts accordingly 505 | convert_projected_coords_to_pixel_coords(lon, lat, dtmGeoTransform, &dtm_col, &dtm_row); 506 | 507 | if ( dtm_col < 0 || dtm_col >= dtm_width || dtm_row < 0 || dtm_row >= dtm_height) // check if we are inside dtm 508 | { 509 | std::cout << "WARNING OUTSIDE DTM FOR LON: " << lon << " , LAT: " << lat << std::endl; 510 | } 511 | else 512 | { 513 | readdtmflag = poDtmBand->RasterIO(GF_Read, floor(dtm_col), floor(dtm_row), 1, 1, &dtm_height_val, 1, 1, GDT_Int16, 0, 0); 514 | if ((double) dtm_height_val == no_data_dtm_value || std::isnan(dtm_height_val)) // check if we have valid dtm value 515 | { 516 | std::cout << "WARNING NO DTM FOUND FOR LON: " << lon << " , LAT: " << lat << std::endl; 517 | std::cout << "WARNING NO DTM FOUND FOR LON: " << dtm_col << " , LAT: " << dtm_row << std::endl; 518 | std::cout << "WARNING NO DTM FOUND : " << dtm_height_val << std::endl; 519 | } 520 | else if (1.0*(double)(dtm_height_val) < height[twod_idx]) // check if dem > dtm. If so update num_wallpts 521 | { 522 | num_wallpts = ceil((height[twod_idx] - (1.0*(double)(dtm_height_val)))/wall_step_size); 523 | if (num_wallpts <= 0) 524 | { 525 | num_wallpts = 1; 526 | } 527 | } 528 | } 529 | } 530 | //std::cout << num_wallpts << std::endl; 531 | std::vector height_walls(num_wallpts, 0); 532 | std::vector lon_walls(num_wallpts, 0); 533 | std::vector lat_walls(num_wallpts, 0); 534 | std::vector success_flag(num_wallpts, 0); 535 | 536 | height_walls[0] = height[twod_idx]; 537 | lon_walls[0] = lon; 538 | lat_walls[0] = lat; 539 | success_flag[0] = 0; 540 | 541 | for (int wall_slice_idx = 1; wall_slice_idx < num_wallpts; wall_slice_idx++) 542 | { 543 | height_walls[wall_slice_idx] = height[twod_idx] - wall_step_size*(((double)(wall_slice_idx)) + wall_step); // 1.0*(wall_slice_idx*wall_step);//- 1.0*(wall_slice_idx - 1 + wall_step); 544 | lon_walls[wall_slice_idx] = lon; 545 | lat_walls[wall_slice_idx] = lat; 546 | success_flag[wall_slice_idx] = 0; 547 | } 548 | 549 | GDALRPCTransform (rpc_transformer, 1, num_wallpts, &lon_walls[0], &lat_walls[0], &height_walls[0], &success_flag[0]); 550 | 551 | for (int wall_slice_idx = 0; wall_slice_idx < num_wallpts; wall_slice_idx++) 552 | { 553 | lon = lon_walls[wall_slice_idx]; 554 | lat = lat_walls[wall_slice_idx]; 555 | height_val = height_walls[wall_slice_idx]; 556 | if (success_flag[wall_slice_idx] == 1 && lon >= 0 && lon < image_pre_ortho_width && lat >= 0 && lat < image_pre_ortho_height) 557 | { 558 | if (wall_slice_idx == 0) 559 | { 560 | valid[twod_idx] = 1; 561 | image_grid_col_float[twod_idx] = lon - halfshift;// I believe this is causing a bug as 0.49999 is getting cast to 0.5 562 | image_grid_row_float[twod_idx] = lat - halfshift; 563 | 564 | image_grid_col[twod_idx] = floor(lon); 565 | image_grid_row[twod_idx] = floor(lat); 566 | 567 | valid_flag = 1; 568 | } 569 | 570 | lookup_idx = (floor(lon)*image_pre_ortho_height) + floor(lat); 571 | 572 | if ( lookup[lookup_idx] == no_data_height_value || std::isnan(lookup[lookup_idx]) ) 573 | { 574 | lookup[lookup_idx] = height_val; 575 | } 576 | else 577 | { 578 | if ((height_val - lookup[lookup_idx]) > 0)//height_thresh_filter_metres) 579 | { 580 | lookup[lookup_idx] = height_val; 581 | } 582 | } 583 | } 584 | //std::cout << lon << " " << lat << " " << height[200] << std::endl; 585 | } 586 | 587 | } 588 | } 589 | } 590 | 591 | } 592 | 593 | if (!valid_flag) 594 | { 595 | printf("\nNo valid points for block #%d\n", block_idx[0]); 596 | printf("\nFinished processing block #%d\n", block_idx[0]); 597 | return 0; 598 | } 599 | int outputWidth = end_col_final - start_col_final; 600 | int outputHeight = end_row_final - start_row_final; 601 | 602 | GDALDataType outputDataType = poRasterDataset->GetRasterBand(1)->GetRasterDataType(); 603 | 604 | //printf("%s",GDALGetDataTypeName(outputDataType)); 605 | int dst_nodata_int = (int) dst_nodata[0]; 606 | 607 | if (outputDataType == GDT_UInt16 && dst_nodata_int < 0) 608 | outputDataType = GDT_Int16; 609 | 610 | if (outputDataType == GDT_Byte) 611 | { 612 | if (dst_nodata_int < 0 || dst_nodata_int > 255) 613 | outputDataType = GDT_Int16; 614 | } 615 | 616 | std::vector output_raster_coord_map(3*outputWidth*outputHeight, (float)dst_nodata[0]); 617 | // free memory allocated to coord_map if not needed 618 | if (! save_mapping_flag[0]) 619 | std::vector().swap(output_raster_coord_map); 620 | bool tiled_flag = 0; 621 | 622 | if (outputDataType == GDT_Int16) 623 | { 624 | 625 | std::vector outBandsAsArray(nbands*outputWidth*outputHeight, dst_nodata_int); 626 | //printf("\nCreating output ortho grid\n"); 627 | bool flag = createOrthoImage(start_row_final, end_row_final, start_col_final, end_col_final, 628 | ngrid_cols, valid, image_grid_col, image_grid_row, 629 | image_pre_ortho_height, image_pre_ortho_width, height, lookup, 630 | height_thresh_filter_metres, nbands, outputWidth, outputHeight, image_interpolate_method, 631 | poRasterDataset, outBandsAsArray, outputDataType, 632 | image_grid_col_float, image_grid_row_float, halfshift, output_raster_coord_map, 633 | save_mapping_flag[0]); 634 | 635 | //printf("\nSaving output image\n"); 636 | bool writeSuccess = writeGTiffImage(outputRasterName, outputWidth, outputHeight, nbands, outputDataType, 637 | raster_corners_lon, raster_corners_lat, 638 | spacing_lon, spacing_lat, poDemDataset, 639 | outBandsAsArray, (int16_t) dst_nodata_int, compress_flag); 640 | 641 | if (save_mapping_flag[0]) 642 | { 643 | 644 | //printf("\nSaving output coordmap\n"); 645 | std::string outputCoordMapRasterName = replaceFirstOccurrence(outputRasterName, ".tif", "_coord_map.tif"); 646 | bool writeSuccess = writeGTiffImage(outputCoordMapRasterName, outputWidth, outputHeight, 3, GDT_Float32, 647 | raster_corners_lon, raster_corners_lat, 648 | spacing_lon, spacing_lat, poDemDataset, 649 | output_raster_coord_map, (float) dst_nodata[0], 650 | compress_flag, tiled_flag); 651 | } 652 | 653 | } 654 | else if (outputDataType == GDT_UInt16) 655 | { 656 | 657 | std::vector outBandsAsArray(nbands*outputWidth*outputHeight, dst_nodata_int); 658 | //printf("\nCreating output ortho grid\n"); 659 | bool flag = createOrthoImage(start_row_final, end_row_final, start_col_final, end_col_final, 660 | ngrid_cols, valid, image_grid_col, image_grid_row, 661 | image_pre_ortho_height, image_pre_ortho_width, height, lookup, 662 | height_thresh_filter_metres, nbands, outputWidth, outputHeight, image_interpolate_method, 663 | poRasterDataset, outBandsAsArray, outputDataType, 664 | image_grid_col_float, image_grid_row_float, halfshift, output_raster_coord_map, 665 | save_mapping_flag[0]); 666 | 667 | //printf("\nSaving output image\n"); 668 | bool writeSuccess = writeGTiffImage(outputRasterName, outputWidth, outputHeight, nbands, outputDataType, 669 | raster_corners_lon, raster_corners_lat, 670 | spacing_lon, spacing_lat, poDemDataset, 671 | outBandsAsArray, (uint16_t) dst_nodata_int, compress_flag); 672 | 673 | if (save_mapping_flag[0]) 674 | { 675 | 676 | //printf("\nSaving output coordmap\n"); 677 | std::string outputCoordMapRasterName = replaceFirstOccurrence(outputRasterName, ".tif", "_coord_map.tif"); 678 | bool writeSuccess = writeGTiffImage(outputCoordMapRasterName, outputWidth, outputHeight, 3, GDT_Float32, 679 | raster_corners_lon, raster_corners_lat, 680 | spacing_lon, spacing_lat, poDemDataset, 681 | output_raster_coord_map, (float) dst_nodata[0], 682 | compress_flag, tiled_flag); 683 | } 684 | 685 | } 686 | else if (outputDataType == GDT_Byte) 687 | { 688 | 689 | std::vector outBandsAsArray(nbands*outputWidth*outputHeight, dst_nodata_int); 690 | //printf("\nCreating output ortho grid\n"); 691 | bool flag = createOrthoImage(start_row_final, end_row_final, start_col_final, end_col_final, 692 | ngrid_cols, valid, image_grid_col, image_grid_row, 693 | image_pre_ortho_height, image_pre_ortho_width, height, lookup, 694 | height_thresh_filter_metres, nbands, outputWidth, outputHeight, image_interpolate_method, 695 | poRasterDataset, outBandsAsArray, outputDataType, 696 | image_grid_col_float, image_grid_row_float, halfshift, output_raster_coord_map, 697 | save_mapping_flag[0]); 698 | 699 | //printf("\nSaving output image\n"); 700 | bool writeSuccess = writeGTiffImage(outputRasterName, outputWidth, outputHeight, nbands, outputDataType, 701 | raster_corners_lon, raster_corners_lat, 702 | spacing_lon, spacing_lat, poDemDataset, 703 | outBandsAsArray, (uint8_t) dst_nodata_int, compress_flag); 704 | 705 | if (save_mapping_flag[0]) 706 | { 707 | 708 | //printf("\nSaving output coordmap\n"); 709 | std::string outputCoordMapRasterName = replaceFirstOccurrence(outputRasterName, ".tif", "_coord_map.tif"); 710 | bool writeSuccess = writeGTiffImage(outputCoordMapRasterName, outputWidth, outputHeight, 3, GDT_Float32, 711 | raster_corners_lon, raster_corners_lat, 712 | spacing_lon, spacing_lat, poDemDataset, 713 | output_raster_coord_map, (float) dst_nodata[0], 714 | compress_flag, tiled_flag); 715 | } 716 | 717 | } 718 | else if (outputDataType == GDT_Float32) 719 | { 720 | 721 | std::vector outBandsAsArray(nbands*outputWidth*outputHeight, dst_nodata_int); 722 | //printf("\nCreating output ortho grid\n"); 723 | bool flag = createOrthoImage(start_row_final, end_row_final, start_col_final, end_col_final, 724 | ngrid_cols, valid, image_grid_col, image_grid_row, 725 | image_pre_ortho_height, image_pre_ortho_width, height, lookup, 726 | height_thresh_filter_metres, nbands, outputWidth, outputHeight, image_interpolate_method, 727 | poRasterDataset, outBandsAsArray, outputDataType, 728 | image_grid_col_float, image_grid_row_float, halfshift, output_raster_coord_map, 729 | save_mapping_flag[0]); 730 | 731 | //printf("\nSaving output image\n"); 732 | bool writeSuccess = writeGTiffImage(outputRasterName, outputWidth, outputHeight, nbands, outputDataType, 733 | raster_corners_lon, raster_corners_lat, 734 | spacing_lon, spacing_lat, poDemDataset, 735 | outBandsAsArray, (float) dst_nodata[0], compress_flag); 736 | 737 | if (save_mapping_flag[0]) 738 | { 739 | 740 | //printf("\nSaving output coordmap\n"); 741 | std::string outputCoordMapRasterName = replaceFirstOccurrence(outputRasterName, ".tif", "_coord_map.tif"); 742 | bool writeSuccess = writeGTiffImage(outputCoordMapRasterName, outputWidth, outputHeight, 3, GDT_Float32, 743 | raster_corners_lon, raster_corners_lat, 744 | spacing_lon, spacing_lat, poDemDataset, 745 | output_raster_coord_map, (float) dst_nodata[0], 746 | compress_flag, tiled_flag); 747 | } 748 | 749 | } 750 | else if (outputDataType == GDT_Float64) 751 | { 752 | 753 | std::vector outBandsAsArray(nbands*outputWidth*outputHeight, dst_nodata_int); 754 | //printf("\nCreating output ortho grid\n"); 755 | bool flag = createOrthoImage(start_row_final, end_row_final, start_col_final, end_col_final, 756 | ngrid_cols, valid, image_grid_col, image_grid_row, 757 | image_pre_ortho_height, image_pre_ortho_width, height, lookup, 758 | height_thresh_filter_metres, nbands, outputWidth, outputHeight, image_interpolate_method, 759 | poRasterDataset, outBandsAsArray, outputDataType, 760 | image_grid_col_float, image_grid_row_float, halfshift, output_raster_coord_map, 761 | save_mapping_flag[0]); 762 | 763 | //printf("\nSaving output image\n"); 764 | bool writeSuccess = writeGTiffImage(outputRasterName, outputWidth, outputHeight, nbands, outputDataType, 765 | raster_corners_lon, raster_corners_lat, 766 | spacing_lon, spacing_lat, poDemDataset, 767 | outBandsAsArray, (double) dst_nodata[0], compress_flag); 768 | 769 | if (save_mapping_flag[0]) 770 | { 771 | 772 | //printf("\nSaving output coordmap\n"); 773 | std::string outputCoordMapRasterName = replaceFirstOccurrence(outputRasterName, ".tif", "_coord_map.tif"); 774 | bool writeSuccess = writeGTiffImage(outputCoordMapRasterName, outputWidth, outputHeight, 3, GDT_Float32, 775 | raster_corners_lon, raster_corners_lat, 776 | spacing_lon, spacing_lat, poDemDataset, 777 | output_raster_coord_map, (float) dst_nodata[0], 778 | compress_flag, tiled_flag); 779 | } 780 | 781 | } 782 | 783 | // close datasets 784 | GDALClose(poRasterDataset); 785 | GDALClose(poDemDataset); 786 | 787 | if (dem_mask_file != "None") 788 | { 789 | GDALClose(poMaskDemDataset); 790 | } 791 | 792 | if (dtm_file != "None") 793 | { 794 | GDALClose(poDtmDataset); 795 | } 796 | 797 | // destroy rpc transformer 798 | GDALDestroyRPCTransformer(rpc_transformer); 799 | 800 | // destroy variables on heap 801 | delete rpcinfo; 802 | printf("\nFinished processing block #%d\n", block_idx[0]); 803 | return 1; 804 | } 805 | catch(std::exception &err) 806 | { 807 | std::cerr << "Unhandled Exception" << err.what() << ", Exiting " << std::endl; 808 | return 0; 809 | } 810 | 811 | } 812 | 813 | 814 | int main(int argc, char **argv) 815 | { 816 | // Set precision of std::cout 817 | std::cout.precision(17); 818 | 819 | // parse input args 820 | std::string outputRasterName; 821 | double start_lon; 822 | double end_lon; 823 | double start_lat; 824 | double end_lat; 825 | double spacing_lon; 826 | double spacing_lat; 827 | std::string raster_file; 828 | std::string dem_file; 829 | std::string dem_interpolate_method; 830 | std::string image_interpolate_method; 831 | std::string rpc_file; 832 | double dst_nodata;; 833 | int block_idx; 834 | bool save_mapping_flag; 835 | bool border_block_flag; 836 | bool compress_flag; 837 | std::string dem_mask_file; 838 | std::string dtm_file; 839 | 840 | 841 | try 842 | { 843 | namespace po = boost::program_options; 844 | po::options_description desc("Allowed options"); 845 | desc.add_options() 846 | ("help", "Help for gwarp++.cpp") 847 | ("output", po::value(&outputRasterName)->required(), "Output raster file name") 848 | ("start_lon", po::value(&start_lon)->required(), "Start value for longitude") 849 | ("end_lon", po::value(&end_lon)->required(), "End value for longitude") 850 | ("start_lat", po::value(&start_lat)->required(), "Starting value for latitude") 851 | ("end_lat", po::value(&end_lat)->required(), "End value for latitude") 852 | ("spacing_lon", po::value(&spacing_lon)->required(), "Spacing for grid in longitude axis") 853 | ("spacing_lat", po::value(&spacing_lat)->required(), "Spacing for grid in latitude axis") 854 | ("raster_file", po::value(&raster_file)->required(),"Input raster file name") 855 | ("dem_file", po::value(&dem_file)->required(),"Input dem file name") 856 | ("dem_interpolate_method", po::value(&dem_interpolate_method)->required(),"dem interpolation method, One of near, bilinear or cubic is supported.") 857 | ("image_interpolate_method", po::value(&image_interpolate_method)->required(),"image interpolation method. Near or Bilinear is supported") 858 | ("dst_nodata", po::value(&dst_nodata)->required(), "No data value in output") 859 | ("block_idx", po::value(&block_idx)->required(), "Index of current block") 860 | ("save_mapping_flag", po::value(&save_mapping_flag)->required(), "Flag to save mapping") 861 | ("border_block_flag", po::value(&border_block_flag)->required(), "Flag to indicate whether current block is a border block or not") 862 | ("rpc_file", po::value(&rpc_file)->required(),"Input rpc file name") 863 | ("compress_flag", po::value(&compress_flag)->required(), "Flag to turn on compression") 864 | ("dem_mask_file", po::value(&dem_mask_file)->default_value("None"),"Optional mask file for dsm") 865 | ("dtm_file", po::value(&dtm_file)->default_value("None"),"Optional input dtm_file file name"); 866 | 867 | po::variables_map vm; 868 | 869 | try 870 | { 871 | po::store(po::command_line_parser(argc, argv) 872 | .options(desc) 873 | .style( 874 | po::command_line_style::unix_style | 875 | po::command_line_style::allow_long_disguise 876 | ) 877 | .run(), vm); 878 | 879 | po::notify(vm); 880 | 881 | } 882 | catch(po::error& err) 883 | { 884 | std::cerr << "ERROR: " << err.what() << std::endl << std::endl; 885 | std::cerr << desc << std::endl; 886 | return 0; 887 | } 888 | 889 | } 890 | catch (std::exception& err) 891 | { 892 | std::cerr << "Unhandled Exception" << err.what() << ", Exiting " << std::endl; 893 | return 0; 894 | 895 | } 896 | 897 | bool flag = get_ortho_grid_worker (outputRasterName, &start_lon, &end_lon, 898 | &start_lat, &end_lat, &spacing_lon, &spacing_lat, 899 | raster_file, dem_file, dem_interpolate_method, 900 | image_interpolate_method, &dst_nodata, &block_idx, &save_mapping_flag, 901 | &border_block_flag, rpc_file, &compress_flag, dem_mask_file, dtm_file); 902 | 903 | 904 | } 905 | 906 | -------------------------------------------------------------------------------- /gwarp++.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This work was supported by the Intelligence Advanced 3 | Research Projects Activity (IARPA) via Department of 4 | Interior / Interior Business Center (DOI/IBC) contract 5 | number D17PC00280. The U.S. Government is authorized to 6 | reproduce and distribute reprints for Governmental purposes 7 | notwithstanding any copyright annotation 8 | thereon. Disclaimer: The views and conclusions contained 9 | herein are those of the authors and should not be 10 | interpreted as necessarily representing the official 11 | policies or endorsements, either expressed or implied, of 12 | IARPA, DOI/IBC, or the U.S. Government. 13 | 14 | Author: Bharath Comandur, cjrbharath@gmail.com 15 | Date: 11/24/2020 16 | ''' 17 | 18 | from __future__ import print_function 19 | import argparse 20 | import os, sys, glob 21 | import numpy as np 22 | import gdal, osr 23 | import pyproj 24 | import pprint as pp 25 | 26 | import subprocess 27 | import shutil 28 | 29 | from scipy import interpolate 30 | 31 | import time 32 | 33 | from multiprocessing import Pool 34 | 35 | import copy 36 | 37 | import traceback 38 | 39 | #from skimage import morphology 40 | 41 | from rpc_parser import load_rpb 42 | 43 | import pickle,cv2 44 | 45 | codePath = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) 46 | 47 | # GDAL data type to the corresponding Numpy data type. 48 | DICT_GDAL_TO_NP = {gdal.GDT_Byte : np.dtype('uint8'), 49 | gdal.GDT_UInt16 : np.dtype('uint16'), 50 | gdal.GDT_UInt32 : np.dtype('uint32'), 51 | gdal.GDT_Int16 : np.dtype('int16'), 52 | gdal.GDT_Int32 : np.dtype('int32'), 53 | gdal.GDT_Float32 : np.dtype('float32'), 54 | gdal.GDT_Float64 : np.dtype('float64') 55 | } 56 | ############################################################################### 57 | 58 | def extract_subraster_mem_efficient(rasterds, rasterband, bbox, fill_value = 0, raiseError = True): 59 | ''' 60 | extract subraster with empty padding. 61 | bottom right corner of bbox is included 62 | This is primarily useful for gdal when the image is too large to be loaded fully 63 | ''' 64 | 65 | raster_h = rasterds.RasterYSize 66 | raster_w = rasterds.RasterXSize 67 | rasterdtype = rasterds.GetRasterBand(1).DataType 68 | rasterdtype = DICT_GDAL_TO_NP[rasterdtype] 69 | 70 | top_left_col_row = bbox[0] 71 | bottom_right_col_row = bbox[1] 72 | 73 | if bbox[0][0] > raster_w - 1 or bbox[0][1] > raster_h - 1 or bbox[1][0] < 0 or bbox[1][1] < 0: 74 | if raiseError: 75 | raise ValueError("bbox outside raster") 76 | else: 77 | print ("bbox outside raster, returning empty array") 78 | height = bottom_right_col_row[1] - top_left_col_row[1] + 1 79 | width = bottom_right_col_row[0] - top_left_col_row[0] + 1 80 | empty_array = fill_value*np.ones([height,width]) 81 | return empty_array.astype(rasterdtype) 82 | 83 | ### Extremely important to use int/np.int and not np.int64. Refer this bug - https://github.com/conda-forge/gdal-feedstock/issues/167 84 | ### gdal ReadAsArray will throw strange error if it is np.int64 claiming that it is double 85 | ### By default np.minimum and np.maximum will return np.int64 even if the input to it are ints 86 | left_zero_pad = int(np.abs(np.minimum(0, top_left_col_row[0]))) 87 | 88 | top_zero_pad = int(np.abs(np.minimum(0, top_left_col_row[1]))) 89 | 90 | right_zero_pad = int(np.abs(np.minimum(0, raster_w - bottom_right_col_row[0] - 1))) 91 | 92 | bottom_zero_pad = int(np.abs(np.minimum(0, raster_h - bottom_right_col_row[1] - 1 ))) 93 | 94 | top_left_col = np.int(np.maximum(0, top_left_col_row[0])) 95 | 96 | top_left_row = int(np.maximum(0, top_left_col_row[1])) 97 | 98 | bottom_right_col = int(np.minimum(raster_w, bottom_right_col_row[0] + 1)) 99 | 100 | bottom_right_row = int(np.minimum(raster_h, bottom_right_col_row[1] + 1)) 101 | 102 | return cv2.copyMakeBorder(rasterband.ReadAsArray(top_left_col, top_left_row, bottom_right_col - top_left_col, bottom_right_row - top_left_row), 103 | top_zero_pad, bottom_zero_pad, left_zero_pad, right_zero_pad, cv2.BORDER_CONSTANT, value = fill_value) 104 | 105 | 106 | def verify_if_mask_file_is_needed(start_lon, start_lat, end_lon, end_lat, spacing_lon, spacing_lat, mask_file): 107 | 108 | verify_flag = False 109 | mask_file_ds = gdal.Open(mask_file) 110 | mask_file_gt = mask_file_ds.GetGeoTransform() 111 | padding = 200 112 | bbox = [[ start_lon - (padding*spacing_lon), start_lat - (padding*spacing_lat) ], 113 | [ end_lon + (padding*spacing_lon), end_lat + (padding*spacing_lat) ] ] 114 | bbox_pixels = convert_projected_coords_to_pixel_coords(np.array(bbox), mask_file_gt) 115 | bbox_pixels = np.floor(bbox_pixels).astype(np.int) 116 | 117 | """ 118 | bbox_pixels[0,0] = np.maximum(0, bbox_pixels[0,0]) 119 | bbox_pixels[0,1] = np.maximum(0, bbox_pixels[0,1]) 120 | 121 | bbox_pixels[1,0] = np.minimum(mask_file_ds.RasterXSize - 1, bbox_pixels[1,0]) 122 | bbox_pixels[1,1] = np.minimum(mask_file_ds.RasterYSize - 1, bbox_pixels[1,1]) 123 | 124 | bbox_pixels = np.floor(bbox_pixels).astype(np.int) 125 | """ 126 | 127 | bbox_vals = extract_subraster_mem_efficient(mask_file_ds, mask_file_ds.GetRasterBand(1), bbox_pixels, fill_value = 0, raiseError = False) 128 | 129 | if np.sum(bbox_vals) > 0: 130 | verify_flag = True 131 | 132 | mask_file_ds = None 133 | 134 | return verify_flag 135 | 136 | 137 | def create_memory_raster_obj_without_data(source_raster_gdal_obj, name_string = ''): 138 | outRasterSRS = osr.SpatialReference() 139 | outRasterSRS.ImportFromWkt(source_raster_gdal_obj.GetProjectionRef()) 140 | projection = outRasterSRS.ExportToWkt() 141 | 142 | geotransform = source_raster_gdal_obj.GetGeoTransform() 143 | 144 | outputDataType = source_raster_gdal_obj.GetRasterBand(1).DataType 145 | 146 | width = source_raster_gdal_obj.RasterXSize 147 | height = source_raster_gdal_obj.RasterYSize 148 | nbands = source_raster_gdal_obj.RasterCount 149 | 150 | # create the output tif 151 | driver = gdal.GetDriverByName ('MEM') 152 | dstGdalObj = driver.Create (name_string, width, height, nbands, outputDataType) 153 | 154 | dstGdalObj.SetGeoTransform (geotransform) 155 | 156 | """ 157 | outputNoDataValue = source_raster_gdal_obj.GetRasterBand(1).GetNoDataValue() 158 | for i,outBandArray in enumerate(outBandsAsArray): 159 | dstGdalObj.GetRasterBand(i + 1).WriteArray (outBandArray) 160 | 161 | if outputNoDataValue is not None: 162 | dstGdalObj.GetRasterBand(i + 1).SetNoDataValue(outputNoDataValue) 163 | 164 | dstGdalObj.GetRasterBand(i + 1).FlushCache() 165 | """ 166 | 167 | dstGdalObj.SetProjection (projection) 168 | 169 | # copy all meta data: 170 | dstGdalObj.SetMetadata (source_raster_gdal_obj.GetMetadata(""), "") 171 | for domain in ["RPC", "IMAGE_STRUCTURE", "SUBDATASETS", "GEOLOCATION"]: 172 | dstGdalObj.SetMetadata (source_raster_gdal_obj.GetMetadata(domain), domain) 173 | dstGdalObj.SetGCPs (source_raster_gdal_obj.GetGCPs(), source_raster_gdal_obj.GetGCPProjection()) 174 | 175 | return dstGdalObj 176 | 177 | def update_rpcs_from_rpb(raster_object, new_rpc_file): 178 | ''' 179 | Function to update rpc metadata of a gdal object 180 | using a new rpc file 181 | ''' 182 | rpc_metadata = raster_object.GetMetadata('RPC') 183 | if len(rpc_metadata.keys()) == 0: 184 | #print(" No rpc metadata found. Using standard dict\n") 185 | 186 | rpc_metadata = { 'HEIGHT_OFF': None, 187 | 'SAMP_OFF': None, 188 | 'LINE_NUM_COEFF' : None, 189 | 'LONG_OFF' : None, 190 | #'MIN_LAT' : None, 191 | #'MAX_LONG' : None, 192 | 'LINE_SCALE' : None, 193 | 'SAMP_NUM_COEFF' : None, 194 | 'LONG_SCALE' : None, 195 | 'SAMP_DEN_COEFF' : None, 196 | #'MIN_LONG' : None, 197 | 'SAMP_SCALE' : None, 198 | #'MAX_LAT' : None, 199 | 'LAT_SCALE' : None, 200 | 'LAT_OFF' : None, 201 | 'LINE_OFF' : None, 202 | 'LINE_DEN_COEFF' : None, 203 | 'HEIGHT_SCALE' : None} 204 | 205 | 206 | check_if_exists(new_rpc_file) 207 | 208 | new_rpcs = load_rpb(new_rpc_file) 209 | 210 | for key in rpc_metadata: 211 | # RAPDR rpc class does not bother with these following keys. So it is ok if they are missing 212 | # in new_rpc_file 213 | if key in ['MIN_LAT', 'MAX_LONG', 'MIN_LONG', 'MAX_LAT']: 214 | if key not in new_rpcs: 215 | continue 216 | 217 | x = new_rpcs[key] 218 | if isinstance(x, np.ndarray): 219 | x = [str(i) for i in x] 220 | x = ' '.join(x) 221 | else: 222 | x = str(x) 223 | rpc_metadata[key] = x 224 | 225 | for key in rpc_metadata: 226 | assert rpc_metadata[key] is not None 227 | 228 | for domain in ["RPC"]: 229 | raster_object.SetMetadata(rpc_metadata, domain) 230 | 231 | new_rpcs = None 232 | return raster_object 233 | 234 | def get_epsg_code_from_lon_lat(lon, lat): 235 | 236 | utm_zone = np.int(np.floor( 31.0 + 1.0*lon/6.0 )) 237 | 238 | if lat >= 0: 239 | epsg_utm_code = "EPSG:" + str(32600 + utm_zone) 240 | else: 241 | epsg_utm_code = "EPSG:" + str(32700 + utm_zone) 242 | 243 | return epsg_utm_code 244 | 245 | def get_epsg_code_of_raster(input_gtiff): 246 | 247 | srs_ds = gdal.Open(input_gtiff) 248 | 249 | mid_pt_pix = [[(srs_ds.RasterXSize/2) + 0.5, (srs_ds.RasterYSize/2) + 0.5]] 250 | 251 | mid_pt_lon_lat = convert_pixel_coords_to_projected_coords(mid_pt_pix, srs_ds.GetGeoTransform()) 252 | 253 | return get_epsg_code_from_lon_lat(mid_pt_lon_lat[0,0], mid_pt_lon_lat[0,1]) 254 | 255 | 256 | def bilinear_interpolation(input_array_coords, mask_rows, input_array, no_data_value = None, pixelis = 'area'): 257 | 258 | # Bilinear interpolation for image 259 | x = input_array_coords[mask_rows,0] 260 | y = input_array_coords[mask_rows,1] 261 | 262 | """ 263 | # In this case pixel centers represent pixel, so 264 | # weights must be measured relative to these 265 | 266 | * Turns out the near and iinterpolated image look shifted 267 | * if I have an extra halfshift here 268 | """ 269 | """ 270 | if pixelis == "area": 271 | 272 | halfshift = 0.5 273 | x = x - halfshift 274 | y = y - halfshift 275 | """ 276 | x_min = 0 277 | y_min = 0 278 | x0 = np.floor(x).astype(int) 279 | y0 = np.floor(y).astype(int) 280 | x1 = x0 + 1 281 | y1 = y0 + 1 282 | w_x = x - x0 283 | w_y = y - y0 284 | 285 | x0[x0 < 0] = x1[x0 < 0] 286 | y0[y0 < 0] = y1[y0 < 0] 287 | 288 | x1[x1 >= input_array.shape[2] ] = x0[x1 >= input_array.shape[2]] 289 | y1[y1 >= input_array.shape[1] ] = y0[y1 >= input_array.shape[1]] 290 | 291 | img00 = input_array[:, y0-y_min, x0-x_min] 292 | img01 = input_array[:, y0-y_min, x1-x_min] 293 | img10 = input_array[:, y1-y_min, x0-x_min] 294 | img11 = input_array[:, y1-y_min, x1-x_min] 295 | 296 | # If we have no data values 297 | if no_data_value is not None: 298 | 299 | no_data_mask = np.ones_like(img00, dtype = np.bool) 300 | # Find points where all four coords used for bilinear interpolation have no data 301 | no_data_mask = np.logical_and(no_data_mask, (img00 == no_data_value)) 302 | no_data_mask = np.logical_and(no_data_mask, (img01 == no_data_value)) 303 | no_data_mask = np.logical_and(no_data_mask, (img10 == no_data_value)) 304 | no_data_mask = np.logical_and(no_data_mask, (img11 == no_data_value)) 305 | 306 | # No data is implicitl set to 0 while interpolating 307 | output = (img00 != no_data_value)*img00*(1-w_x)*(1-w_y) +\ 308 | (img01 != no_data_value)*img01*w_x *(1-w_y) +\ 309 | (img10 != no_data_value)*img10*(1-w_x)*w_y +\ 310 | (img11 != no_data_value)*img11*w_x *w_y 311 | 312 | # Set points where all four surrounding values are no data 313 | output[no_data_mask] = no_data_value 314 | 315 | else: 316 | output = img00*(1-w_x)*(1-w_y) +\ 317 | img01*w_x *(1-w_y) +\ 318 | img10*(1-w_x)*w_y +\ 319 | img11*w_x *w_y 320 | 321 | 322 | return output 323 | 324 | def nearest_interpolation(input_array_coords, mask_rows, input_array): 325 | 326 | # Nearest interpolation for image 327 | x = input_array_coords[mask_rows,0] 328 | y = input_array_coords[mask_rows,1] 329 | 330 | x0 = np.round(x).astype(int) 331 | y0 = np.round(y).astype(int) 332 | 333 | output = input_array[:, y0, x0] 334 | 335 | 336 | return output, y0, x0 337 | 338 | def writeGTiffImage (projection, geotransform, outBandsAsArray, outputRasterName, outputDataType, outputNoDataValue = None, compress_flag = False): 339 | 340 | """ create an output geotiff file using explicitly provided metadata params """ 341 | 342 | if os.path.isfile(outputRasterName): 343 | os.remove(outputRasterName) 344 | #command = "rm -rf " + outputRasterName; 345 | #os.system(command); 346 | 347 | 348 | (height, width) = np.shape(outBandsAsArray[0]) 349 | nbands = len(outBandsAsArray) 350 | # create the output tif 351 | driver = gdal.GetDriverByName ('GTiff') 352 | if compress_flag: 353 | dstGdalObj = driver.Create (outputRasterName, width, height, nbands, outputDataType, options=["TILED=YES", "COMPRESS=LZW", "BIGTIFF=YES"]) 354 | else: 355 | dstGdalObj = driver.Create (outputRasterName, width, height, nbands, outputDataType, options=["TILED=YES", "BIGTIFF=YES"]) 356 | 357 | dstGdalObj.SetGeoTransform (geotransform) 358 | 359 | for i,outBandArray in enumerate(outBandsAsArray): 360 | dstGdalObj.GetRasterBand(i + 1).WriteArray (outBandArray) 361 | 362 | if outputNoDataValue is not None: 363 | dstGdalObj.GetRasterBand(i + 1).SetNoDataValue(outputNoDataValue) 364 | 365 | dstGdalObj.GetRasterBand(i + 1).FlushCache() 366 | 367 | dstGdalObj.SetProjection (projection) 368 | 369 | dstGdalObj = None 370 | 371 | 372 | def convert_projected_coords_to_pixel_coords(projected_x_y_array, geotransform): 373 | 374 | pixel_col_row_array = projected_x_y_array - np.array((geotransform[0], geotransform[3])) 375 | 376 | pixel_col_row_array[:,0] = pixel_col_row_array[:,0]/geotransform[1] 377 | 378 | pixel_col_row_array[:,1] = pixel_col_row_array[:,1]/geotransform[5] 379 | 380 | return pixel_col_row_array 381 | 382 | 383 | def convert_pixel_coords_to_projected_coords(pixel_col_row_list, geotransform): 384 | 385 | geotr_mat = np.reshape(geotransform, (2,3)) 386 | 387 | n_pixels = len(pixel_col_row_list) 388 | 389 | return np.transpose(np.dot(geotr_mat, np.concatenate([ np.ones((1,n_pixels)), np.transpose(np.array(pixel_col_row_list)) ]))) 390 | 391 | # aa 392 | 393 | def check_if_exists(input_file_or_folder): 394 | if os.path.isfile(input_file_or_folder) or os.path.isdir(input_file_or_folder): 395 | return True 396 | else: 397 | raise ValueError("\n\nERROR: " + input_file_or_folder + " does not exist. Stopping \n") 398 | return False 399 | 400 | 401 | def get_ortho_grid_worker(input_list): 402 | """ 403 | function that implements gwarp++ for a block. A block could also cover an entire raster if it is small enough. 404 | By block we refer to a grid in lon lat coordinates 405 | """ 406 | 407 | # Encapsulate within a try catch statement to detect blocks that fail 408 | try: 409 | #print(input_list) 410 | outputRasterName, start_lon, start_lat, end_lon, end_lat, spacing_lon, spacing_lat, \ 411 | raster_file, dem_file, dem_interpolate_method, image_interpolate_method, dst_nodata, block_idx, \ 412 | save_mapping_flag, border_block_flag, rpc_file, compress_flag, dem_mask_file, dem_low_res_file = input_list 413 | 414 | # verify if we need mask file 415 | if dem_mask_file is not None and str(dem_mask_file) != "None": 416 | verify_flag = verify_if_mask_file_is_needed(start_lon, start_lat, end_lon, end_lat, spacing_lon, spacing_lat, dem_mask_file) 417 | if not verify_flag: 418 | print("\nDEM MASK NOT REQUIRED\n") 419 | dem_mask_file = None 420 | 421 | command = codePath + "/c++/gwarp++ --output %s --start_lon %s --start_lat %s --end_lon %s --end_lat %s --spacing_lon %s --spacing_lat %s " + \ 422 | " --raster_file %s --dem_file %s --dem_interpolate_method %s --image_interpolate_method %s --dst_nodata %s --block_idx %s " + \ 423 | " --save_mapping_flag %s --border_block_flag %s --rpc_file %s --compress_flag %s --dem_mask_file %s --dtm_file %s" 424 | # note block_idx is set to 0 for worker cpp function 425 | command = (command % (outputRasterName, start_lon, start_lat, end_lon, end_lat, spacing_lon, spacing_lat, \ 426 | raster_file, dem_file, dem_interpolate_method, image_interpolate_method, dst_nodata, block_idx, \ 427 | save_mapping_flag, border_block_flag, rpc_file, compress_flag, str(dem_mask_file), dem_low_res_file )) 428 | 429 | print(command) 430 | 431 | os.system(command) 432 | 433 | except Exception as e: 434 | 435 | print("ID is " + str(block_idx)) 436 | traceback.print_exc() 437 | 438 | return 439 | 440 | def get_ortho_grid(outputRasterName, utm_code, raster_file, dem_file, dem_low_res_file, dem_interpolate_method, image_interpolate_method, parallel_flag, 441 | output_resolution = None, n_workers = 12, gdal_merge = None, dst_nodata = -9999, save_mapping_flag = False, rpc_file = None, 442 | compress_flag = False, dem_mask_file = None): 443 | halfshift = 0.5 444 | """ 445 | function that prepares blocks and grids for orthorectification 446 | """ 447 | 448 | if parallel_flag and gdal_merge is None: 449 | raise Exception('When using parallel, need to supply gdal_merge.py path') 450 | 451 | # Check if input raster has rpc metadata 452 | raster_object = gdal.Open(raster_file) 453 | 454 | # Create rpc based transformer 455 | if rpc_file is not None: 456 | check_if_exists(rpc_file) 457 | raster_obj_rpc = create_memory_raster_obj_without_data(raster_object) 458 | raster_obj_rpc = update_rpcs_from_rpb(raster_obj_rpc, rpc_file) 459 | else: 460 | raster_obj_rpc = raster_object 461 | 462 | rpc_metadata = raster_obj_rpc.GetMetadata('RPC') 463 | if len(rpc_metadata.keys()) == 0: 464 | raise ValueError(" ERROR: No rpc metadata found\n") 465 | 466 | rpc_dem_missing_value = rpc_metadata['HEIGHT_OFF'] 467 | 468 | 469 | # default error is 0 pixel 470 | # Specify dem missing value as 0. Otherwise due to a bug in gdal, if the dem has Nans, then 471 | # the code will fail with errors. This is true for standard gdalwarp as well 472 | rpc_transformer_options_list = ['METHOD=RPC','RPC_PIXEL_ERROR_THRESHOLD=0.001','RPC_DEM=%s' % dem_file, 473 | 'RPC_DEMINTERPOLATION=%s' % dem_interpolate_method, 474 | 'RPC_DEM_MISSING_VALUE='+rpc_dem_missing_value] 475 | 476 | # rpc_transformer_options_list = ['METHOD=RPC','RPC_PIXEL_ERROR_THRESHOLD=0.001'] 477 | # Create rpc based transformer 478 | rpc_transformer = gdal.Transformer(raster_obj_rpc, None, rpc_transformer_options_list) 479 | 480 | # Get raster width and height 481 | raster_w = raster_object.RasterXSize 482 | raster_h = raster_object.RasterYSize 483 | 484 | # Corners of raster in pixel space 485 | raster_corners_col_row = [(halfshift,halfshift), (raster_w + halfshift, halfshift), (halfshift, raster_h + halfshift), (raster_w + halfshift, raster_h + halfshift)] 486 | 487 | # Corners of raster in orthorectified space 488 | raster_corners_lon_lat, flags = rpc_transformer.TransformPoints(0, raster_corners_col_row) 489 | 490 | # Check if all four corners were correctly found. Most common reason for failure is missing DEM value. 491 | # In this case there will be a 0 in the flags 492 | rpc_error = 0.001 493 | 494 | while (0 in flags): 495 | 496 | if rpc_error >= 0.1: 497 | break 498 | 499 | rpc_error = rpc_error + 0.005 500 | rpc_transformer_options_list = ['METHOD=RPC','RPC_PIXEL_ERROR_THRESHOLD=' + str(rpc_error),'RPC_DEM=%s' % dem_file, 501 | 'RPC_DEMINTERPOLATION=%s' % dem_interpolate_method, 502 | 'RPC_DEM_MISSING_VALUE='+rpc_dem_missing_value] 503 | # Create rpc based transformer 504 | rpc_transformer = gdal.Transformer(raster_obj_rpc, None, rpc_transformer_options_list) 505 | 506 | # Corners of raster in orthorectified space 507 | raster_corners_lon_lat, flags = rpc_transformer.TransformPoints(0, raster_corners_col_row) 508 | 509 | if 0 in flags: 510 | rpc_error = -0.004 511 | print("Trying low res dem\n") 512 | 513 | while (0 in flags): 514 | 515 | if rpc_error >= 0.1: 516 | break 517 | 518 | rpc_error = rpc_error + 0.005 519 | 520 | rpc_transformer_options_list = ['METHOD=RPC','RPC_PIXEL_ERROR_THRESHOLD=' + str(rpc_error),'RPC_DEM=%s' % dem_low_res_file, 521 | 'RPC_DEMINTERPOLATION=%s' % dem_interpolate_method, 522 | 'RPC_DEM_MISSING_VALUE='+rpc_dem_missing_value] 523 | # Create rpc based transformer 524 | rpc_transformer = gdal.Transformer(raster_obj_rpc, None, rpc_transformer_options_list) 525 | 526 | # Corners of raster in orthorectified space 527 | raster_corners_lon_lat, flags = rpc_transformer.TransformPoints(0, raster_corners_col_row) 528 | 529 | if 0 in flags: 530 | 531 | rpc_transformer_options_list = ['METHOD=RPC','RPC_PIXEL_ERROR_THRESHOLD=0.001'] 532 | 533 | print("NOT USING DEM FOR CORNERS\n") 534 | 535 | # Create rpc based transformer 536 | rpc_transformer = gdal.Transformer(raster_obj_rpc, None, rpc_transformer_options_list) 537 | 538 | # Corners of raster in orthorectified space 539 | raster_corners_lon_lat, flags = rpc_transformer.TransformPoints(0, raster_corners_col_row) 540 | 541 | else: 542 | print ("Final rpc error ", rpc_error) 543 | 544 | if 0 in flags: 545 | print("\nRaster corners in pixel are ", raster_corners_col_row) 546 | print("\nRaster corners in lon lat are ", raster_corners_lon_lat) 547 | raise ValueError("\n\nERROR: Unable to get height from dem at bounds.\n") 548 | 549 | raster_object = None 550 | raster_obj_rpc = None 551 | 552 | raster_corners_lon = [raster_corners_lon_lat[i][0] for i in range(4)] 553 | raster_corners_lat = [raster_corners_lon_lat[i][1] for i in range(4)] 554 | 555 | min_lon = np.min(raster_corners_lon) 556 | max_lon = np.max(raster_corners_lon) 557 | 558 | min_lat = np.min(raster_corners_lat) 559 | max_lat = np.max(raster_corners_lat) 560 | 561 | raster_corners_lon = [min_lon, max_lon, min_lon, max_lon] 562 | raster_corners_lat = [max_lat, max_lat, min_lat, min_lat] 563 | 564 | # If the output resolution is not specified in metres, then it is set to 0.5*the resolution of the epsg4326 dem 565 | # i.e in degrees 566 | if output_resolution is None: 567 | dem_object = gdal.Open(dem_file) 568 | dem_geotransform = dem_object.GetGeoTransform() 569 | spacing_lon, spacing_lat = dem_geotransform[1], dem_geotransform[5] 570 | dem_object = None 571 | scale = 0.5 572 | spacing_lon = scale*spacing_lon 573 | spacing_lat = scale*spacing_lat 574 | else: 575 | # If the output resolution is specified in metres, then depending upon the location, the resolution in degrees can vary. 576 | # Hence we first project the corner of the raster into utm, find the next point. Project it back into lon lat. 577 | # The difference gives us the resolution in degrees 578 | output_resolution_x = output_resolution 579 | # y coordinate is flipped. Hence the negative sign. 580 | output_resolution_y = -output_resolution 581 | epsg4326_proj = pyproj.Proj(init = 'EPSG:4326') 582 | utm_proj = pyproj.Proj(init = utm_code) 583 | # Find raster corners in projected coordinates 584 | raster_corners_proj_col, raster_corners_proj_row = pyproj.transform(epsg4326_proj, utm_proj, raster_corners_lon, raster_corners_lat) 585 | # Find the next point by stepping output resolution metres along x and y axis 586 | next_point_grid_lon, next_point_grid_lat = pyproj.transform(utm_proj, epsg4326_proj, 587 | raster_corners_proj_col[0] + output_resolution_x, 588 | raster_corners_proj_row[0] + output_resolution_y) 589 | 590 | 591 | spacing_lon = 1.0*(next_point_grid_lon - raster_corners_lon[0]) 592 | spacing_lat = 1.0*(next_point_grid_lat - raster_corners_lat[0]) 593 | 594 | print("\nCorners of the output raster are ") 595 | pp.pprint(list(zip(raster_corners_lon, raster_corners_lat))) 596 | print("\nOutput resolution in degrees is %s, %s\n" % (spacing_lon, spacing_lat)) 597 | 598 | if parallel_flag: 599 | # We split the lon lat grid into blocks. The number of points in each block is 2000x2000. In practice 600 | # we add padding to the block to account for height effects 601 | n_pts_block = 2000 602 | 603 | # Starting lon lat for each block 604 | blocks_lon = np.arange(raster_corners_lon[0], raster_corners_lon[-1], spacing_lon*n_pts_block) 605 | blocks_lat = np.arange(raster_corners_lat[0], raster_corners_lat[-1], spacing_lat*n_pts_block) 606 | 607 | # Check distance between last pt in arange and actual end 608 | dist_lon_end = (raster_corners_lon[-1] - blocks_lon[-1])/(1.0*spacing_lon*n_pts_block) 609 | dist_lat_end = (raster_corners_lat[-1] - blocks_lat[-1])/(1.0*spacing_lat*n_pts_block) 610 | 611 | # If the last sample pt is close to actual end, just replace it. 612 | # If it is not, add the actual end to the list 613 | # By close, we mean if its distance to actual end < block_size/4 614 | if dist_lon_end < n_pts_block/4.0: 615 | blocks_lon[-1] = raster_corners_lon[-1] 616 | else: 617 | blocks_lon.append(raster_corners_lon[-1]) 618 | 619 | if dist_lat_end < n_pts_block/4.0: 620 | blocks_lat[-1] = raster_corners_lat[-1] 621 | else: 622 | blocks_lat.append(raster_corners_lat[-1]) 623 | 624 | input_mp_list = [] 625 | 626 | block_idx = 0 627 | 628 | outputRasterFolder, outputRasterName_only = os.path.split(outputRasterName) 629 | if outputRasterFolder == '': 630 | outputRasterFolder = '.' 631 | 632 | outputRasterFolder_workers = os.path.join(outputRasterFolder, 'worker', os.path.splitext(outputRasterName_only)[0]) 633 | if not os.path.isdir(outputRasterFolder_workers): 634 | os.makedirs(outputRasterFolder_workers) 635 | 636 | # For loop to get the start and end coordinates for each block (without padding) 637 | for lon_index,lon in enumerate(blocks_lon): 638 | 639 | for lat_index,lat in enumerate(blocks_lat): 640 | 641 | if (lon_index == blocks_lon.size - 1) or (lat_index == blocks_lat.size - 1): 642 | continue 643 | 644 | border_block_flag = False 645 | 646 | if (lon_index == blocks_lon.size -2) or (lat_index == blocks_lat.size - 2): 647 | border_block_flag = True 648 | 649 | start_lon = lon 650 | start_lat = lat 651 | 652 | end_lon = blocks_lon[lon_index + 1] 653 | end_lat = blocks_lat[lat_index + 1] 654 | 655 | # name the output raster based on block index 656 | outputRasterName_worker = outputRasterName_only.replace('.tif', '_' + str(block_idx) + '.tif') 657 | outputRasterName_worker = os.path.join(outputRasterFolder_workers, outputRasterName_worker) 658 | blk_list = [outputRasterName_worker, start_lon, start_lat, end_lon, end_lat, spacing_lon, spacing_lat, raster_file, 659 | dem_file, dem_interpolate_method, image_interpolate_method, dst_nodata, block_idx, save_mapping_flag, 660 | border_block_flag, rpc_file, compress_flag, dem_mask_file, dem_low_res_file] 661 | input_mp_list.append(blk_list) 662 | 663 | block_idx += 1 664 | 665 | print("\nTotal number of blocks is %s" % block_idx) 666 | 667 | # Launch pool 668 | pool_workers = Pool(n_workers) 669 | 670 | pool_workers.map(get_ortho_grid_worker, input_mp_list) 671 | 672 | #with open('debug.pickle','wb') as f: 673 | # pickle.dump(input_mp_list, f) 674 | pool_workers.close() 675 | 676 | tmpFileList = os.path.join(outputRasterFolder_workers, "tmpFileList.txt"); 677 | f = open(tmpFileList, 'w') 678 | 679 | if f is None: 680 | print('Error opening tmp file {0} for writing'.format(tmpFileList)) 681 | return None 682 | 683 | tileCount = 0 684 | 685 | for file in os.listdir(outputRasterFolder_workers): 686 | if file.endswith(".tif"): 687 | # skip coordmap tifs 688 | if not file.endswith("_coord_map.tif"): 689 | f.write(os.path.join(outputRasterFolder_workers, file) + '\n') 690 | tileCount = tileCount + 1 691 | 692 | f.close() 693 | 694 | my_env = os.environ.copy() 695 | 696 | outputRasterVrt = outputRasterName.replace('.tif', '.vrt') 697 | command = "%s/gdalbuildvrt %s -tr %s %s -srcnodata %i -input_file_list %s"%(gdal_merge, outputRasterVrt, np.abs(spacing_lon), np.abs(spacing_lat), dst_nodata, tmpFileList) 698 | p = subprocess.Popen(command, shell=True, env=my_env) 699 | retval = p.wait() 700 | 701 | if compress_flag: 702 | #command = "%s -o %s -co TILED=YES -co BIGTIFF=YES -co COMPRESS=LZW -a_nodata %i --optfile %s"%(gdal_merge, outputRasterName, dst_nodata, tmpFileList) 703 | command = "%s/gdal_translate -co TILED=YES -co BIGTIFF=YES -co COMPRESS=LZW -a_nodata %i %s %s"%(gdal_merge, dst_nodata, outputRasterVrt, outputRasterName) 704 | else: 705 | #command = "%s -o %s -co TILED=YES -co BIGTIFF=YES -a_nodata %i --optfile %s"%(gdal_merge, outputRasterName, dst_nodata, tmpFileList) 706 | command = "%s/gdal_translate -co TILED=YES -co BIGTIFF=YES -a_nodata %i %s %s"%(gdal_merge, dst_nodata, outputRasterVrt, outputRasterName) 707 | 708 | p = subprocess.Popen(command, shell=True, env=my_env) 709 | retval = p.wait() 710 | 711 | # save pixel coords map between ortho image and raw image 712 | if save_mapping_flag: 713 | 714 | # First merge all the worker 2 band rasters with the same metadata as the orthorectified image 715 | # First band will contain the column index of the corresponding raw image pixel 716 | # Second band will contain the row index of the corresponding raw image pixel 717 | outputCoordMapRasterName = outputRasterName.replace('.tif', '_coord_map.tif') 718 | 719 | tmpFileList_coordmap = os.path.join(outputRasterFolder_workers, "tmpFileList_coordmap.txt"); 720 | f = open(tmpFileList_coordmap, 'w') 721 | 722 | if f is None: 723 | print('Error opening tmp file {0} for writing'.format(tmpFileList_coordmap)) 724 | return None 725 | 726 | tileCount = 0 727 | 728 | for file in os.listdir(outputRasterFolder_workers): 729 | if file.endswith("_coord_map.tif"): 730 | f.write(os.path.join(outputRasterFolder_workers, file) + '\n') 731 | tileCount = tileCount + 1 732 | 733 | f.close() 734 | 735 | outputCoordMapRasterVrt = outputCoordMapRasterName.replace('.tif', '.vrt') 736 | command = "%s/gdalbuildvrt %s -tr %s %s -srcnodata %i -input_file_list %s"%(gdal_merge, outputCoordMapRasterVrt, np.abs(spacing_lon), np.abs(spacing_lat), dst_nodata, tmpFileList_coordmap) 737 | p = subprocess.Popen(command, shell=True, env=my_env) 738 | retval = p.wait() 739 | 740 | if compress_flag: 741 | """ 742 | command = "%s -o %s -co TILED=YES -co BIGTIFF=YES -co COMPRESS=LZW --optfile %s -a_nodata %i -init %i "%(gdal_merge, 743 | outputCoordMapRasterName, 744 | tmpFileList_coordmap, 745 | dst_nodata, dst_nodata) 746 | """ 747 | command = "%s/gdal_translate -co TILED=YES -co BIGTIFF=YES -co COMPRESS=LZW -a_nodata %i %s %s"%(gdal_merge, dst_nodata, outputCoordMapRasterVrt, outputCoordMapRasterName) 748 | else: 749 | """ 750 | #command = "%s -o %s -co TILED=YES -co BIGTIFF=YES --optfile %s -a_nodata %i -init %i "%(gdal_merge, 751 | outputCoordMapRasterName, 752 | tmpFileList_coordmap, 753 | dst_nodata, dst_nodata) 754 | """ 755 | command = "%s/gdal_translate -co TILED=YES -co BIGTIFF=YES -a_nodata %i %s %s"%(gdal_merge, dst_nodata, outputCoordMapRasterVrt, outputCoordMapRasterName) 756 | 757 | 758 | q = subprocess.Popen(command, shell=True, env=my_env) 759 | retval_q = q.wait() 760 | 761 | shutil.rmtree(outputRasterFolder_workers) 762 | #command = 'rm -rf ' + outputRasterFolder_workers 763 | #os.system(command) 764 | 765 | else: 766 | # Ensure that the image is small enough. Otherwise we will run into memory issues. 767 | start_lon = raster_corners_lon[0] 768 | start_lat = raster_corners_lat[0] 769 | 770 | end_lon = raster_corners_lon[-1] 771 | end_lat = raster_corners_lat[-1] 772 | 773 | input_mp_list = [outputRasterName, start_lon, start_lat, end_lon, end_lat, spacing_lon, spacing_lat, raster_file, 774 | dem_file, dem_interpolate_method, image_interpolate_method, dst_nodata, 0, save_mapping_flag, True, 775 | rpc_file, compress_flag, dem_mask_file, dem_low_res_file] 776 | get_ortho_grid_worker(input_mp_list) 777 | 778 | 779 | # Convert coordmap tif to npy file for easy use. Only for small images. NOT FOR BIG IMAGES. 780 | # Will take about 30GB of memory for huge images as locations can be 32 bit or 64 bit 781 | # unlike images which can be 16 bit. 782 | """ 783 | if save_mapping_flag: 784 | 785 | outputCoordMapRasterName = outputRasterName.replace('.tif', '_coord_map.tif') 786 | if not os.path.isfile(outputCoordMapRasterName): 787 | print("ERROR:Coord map file not found.Stopping\n") 788 | else: 789 | coordmap_object = gdal.Open(outputCoordMapRasterName) 790 | coordmap_raster_col = coordmap_object.GetRasterBand(1).ReadAsArray().flatten() 791 | coordmap_raster_row = coordmap_object.GetRasterBand(2).ReadAsArray().flatten() 792 | 793 | # Append the ortho image width and height as the last column 794 | ''' 795 | output_coord_map_file_name = outputCoordMapRasterName.replace('.tif', '.csv') 796 | np.savetxt(output_coord_map_file_name, 797 | np.vstack((np.transpose([coordmap_raster_col, 798 | coordmap_raster_row]), 799 | np.array([coordmap_object.RasterXSize, 800 | coordmap_object.RasterYSize])[None,:] )), delimiter = ',', fmt="%d") 801 | 802 | ''' 803 | output_coord_map_file_name = outputCoordMapRasterName.replace('.tif', '.npy') 804 | np.save(output_coord_map_file_name, 805 | np.vstack((np.transpose([coordmap_raster_col, 806 | coordmap_raster_row]), 807 | np.array([coordmap_object.RasterXSize, 808 | coordmap_object.RasterYSize])[None,:] ))) 809 | 810 | # Delete the coordmap raster 811 | #os.remove(outputCoordMapRasterName) 812 | """ 813 | 814 | def check_input(args): 815 | 816 | check_if_exists(args.input_raster) 817 | 818 | if args.RPC_DEM is not None: 819 | check_if_exists(args.RPC_DEM) 820 | 821 | if args.dem_interp not in ['near', 'bilinear', 'cubic']: 822 | raise ValueError("\n\nERROR:DEM interpolation should be one of near, bilinear or cubic. You have provided %s\n" % args.dem_interp) 823 | 824 | if args.image_interp not in ['near', 'bilinear']: 825 | raise ValueError("\n\nERROR:Image interpolation should be one of near or bilinear. You have provided %s\n" % args.image_interp) 826 | 827 | 828 | def main(argv=None): 829 | if argv is None: 830 | argv = sys.argv[1:] 831 | 832 | parser = argparse.ArgumentParser(prog = 'gwarp++', description = 'gwarp to orthorectify images using high resolution dsms') 833 | 834 | parser.add_argument('input_raster', help = 'ntf or tif to orthorectify') 835 | 836 | parser.add_argument('-RPC_DEM', help = 'optional dem or dsm') 837 | 838 | parser.add_argument('-low_res_dem', help = 'low res dem wrt to wgs84 ellipsoid') 839 | 840 | parser.add_argument('-utm', help = 'flag to specify if dem is in utm. If so a epsg4326 dem is created from it', action='store_true') 841 | 842 | parser.add_argument('-dem_interp', help = 'dem interpolation - one of near, bilinear or cubic', default = 'bilinear') 843 | 844 | parser.add_argument('-image_interp', help = 'raster interpolation - one of near or bilinear', default = 'bilinear') 845 | 846 | parser.add_argument('-output_res', type=float , help = 'resolution of orthorectified image in metres', default = None) 847 | 848 | parser.add_argument('-parallel', help = 'flag for parallel processing', action='store_true') 849 | 850 | parser.add_argument('-n_workers', type=int , help = 'number of workers for parallel processing', default = 12) 851 | 852 | parser.add_argument('-output', type=str , help = 'optional name of output raster', default = None) 853 | 854 | parser.add_argument('-gdal_merge', type=str , help = 'path to gdal_merge.py', default = None) 855 | 856 | parser.add_argument('-dst_nodata', type=int, help='no data value for destination', default=-9999) 857 | 858 | parser.add_argument('-save_mapping_flag', help = 'flag to save pixel mapping between raw and orthorectified image', action='store_true') 859 | 860 | parser.add_argument('-rpc_file', help = 'optional rpc file with updated bias values', type = str, default = None) 861 | 862 | parser.add_argument('-compress_flag', help = 'flag to turn on compression for output image', action='store_true') 863 | 864 | parser.add_argument('-dem_mask_file', help = 'uint8 bit mask file for dem indicating points to skip', type = str, default = None) 865 | 866 | args = parser.parse_args(argv) 867 | 868 | check_input(args) 869 | 870 | if args.parallel and args.gdal_merge is None: 871 | raise Exception('When using parallel, need to supply gdal_merge.py path') 872 | ''' 873 | create temporary folder 874 | 875 | temp_folder = '/tmp/gwarp/' 876 | if os.path.isdir(temp_folder): 877 | shutil.rmtree(temp_folder) 878 | 879 | os.makedirs(temp_folder) 880 | if not os.path.isdir(temp_folder): 881 | raise ValueError("\n\nERROR: Could not create %s \n" % temp_folder) 882 | ''' 883 | ''' 884 | Output raster name 885 | ''' 886 | if args.output is None : 887 | 888 | # We add _ortho at the end of the name of the input tif. We create a folder called orthorectified in the same folder as the input image 889 | # and save the output in that folder 890 | extension = os.path.splitext(args.input_raster)[-1] 891 | outputRasterName = args.input_raster.replace(extension, '_ortho.tif') 892 | 893 | outputRasterFolder = os.path.join(os.path.split(outputRasterName)[0], 'orthorectified') 894 | if not os.path.isdir(outputRasterFolder): 895 | os.makedirs(outputRasterFolder) 896 | 897 | outputRasterName = os.path.join(outputRasterFolder, os.path.split(outputRasterName)[-1]) 898 | 899 | else: 900 | # Use specified name for output 901 | outputRasterName = os.path.abspath(args.output) 902 | # Create output folder if needed 903 | outputRasterFolder = os.path.split(outputRasterName)[0] 904 | if not os.path.isdir(outputRasterFolder): 905 | os.makedirs(outputRasterFolder) 906 | 907 | dem_epsg4326 = None 908 | # If DEM is none, then use standard gdalwarp 909 | if args.RPC_DEM is None: 910 | command = "set GDAL_CACHEMAX=3000;set GDAL_SWATH_SIZE=3073741824;set GDAL_NUM_THREADS=2; " \ 911 | "gdalwarp -to RPC_DEM_MISSING_VALUE=0 -co COMPRESS=PACKBITS -co INTERLEAVE=BAND -co BIGTIFF=YES " \ 912 | "-co TILED=YES -rpc -et 0 -overwrite -wo WRITE_FLUSH=YES " \ 913 | "-wo OPTIMIZE_SIZE=YES -multi -r cubic -dstnodata %i -wm 3000 -of GTIFF -t_srs EPSG:4326 " % args.dst_nodata + args.input_raster + " " + outputRasterName 914 | 915 | os.system(command) 916 | 917 | else: 918 | 919 | dem_object = gdal.Open(args.RPC_DEM) 920 | dem_srs= osr.SpatialReference(wkt=dem_object.GetProjectionRef()) 921 | dem_projcs = dem_srs.GetAttrValue('PROJCS') 922 | 923 | if args.utm: 924 | ''' 925 | convert dem or dsm from utm to epsg4326 i.e. longitude, latitude 926 | ''' 927 | # Get dem projcs. i.e. utm code 928 | 929 | dem_srs_epsg = dem_srs.GetAttrValue('AUTHORITY', 0) + ':' + dem_srs.GetAttrValue('AUTHORITY', 1) 930 | 931 | # Name of dem epsg4326 tif 932 | dem_ext = os.path.splitext(args.RPC_DEM)[-1] 933 | dem_epsg4326 = args.RPC_DEM.replace(dem_ext, '_epsg4326.tif') 934 | 935 | if dem_projcs is not None and 'UTM' in dem_projcs: 936 | if not os.path.isfile(dem_epsg4326): 937 | # warp dem from utm to epsg 4326 if not done already 938 | print("Converting dem from " + dem_srs_epsg + " to EPSG:4326" ) 939 | 940 | dem_nodataval = dem_object.GetRasterBand(1).GetNoDataValue() 941 | 942 | if dem_nodataval is None: 943 | dem_nodataval = np.nan 944 | 945 | dem_nodataval = str(dem_nodataval) 946 | 947 | command = 'gdalwarp -t_srs EPSG:4326 -r near -srcnodata ' + dem_nodataval + ' -dstnodata ' + dem_nodataval + ' ' + args.RPC_DEM + ' ' + dem_epsg4326 948 | os.system(command) 949 | else: 950 | raise ValueError("\nAre you sure if DEM is in UTM projection? Stopping") 951 | 952 | else: 953 | if dem_projcs is not None and 'UTM' in dem_projcs: 954 | raise ValueError("UTM Dem provided. Use the -utm flag\n") 955 | 956 | dem_srs_epsg = get_epsg_code_of_raster(args.RPC_DEM) 957 | dem_epsg4326 = args.RPC_DEM 958 | 959 | dem_object = None 960 | 961 | get_ortho_grid(outputRasterName, dem_srs_epsg, args.input_raster, 962 | dem_epsg4326, args.low_res_dem, args.dem_interp, args.image_interp, args.parallel, 963 | args.output_res, args.n_workers, args.gdal_merge, args.dst_nodata, args.save_mapping_flag, 964 | args.rpc_file, args.compress_flag, args.dem_mask_file) 965 | 966 | 967 | if __name__ == "__main__": 968 | sys.exit(main()) 969 | --------------------------------------------------------------------------------