├── LICENSE ├── README.md ├── arsf_dem ├── __init__.py ├── arsf_dem.cfg ├── dem_common.py ├── dem_common_functions.py ├── dem_lidar │ ├── __init__.py │ ├── ascii_lidar.py │ ├── fusion_lidar.py │ ├── grass_lidar.py │ ├── laspy_lidar.py │ ├── lastools_lidar.py │ ├── lidar_utilities.py │ ├── points2grid_lidar.py │ └── spdlib_lidar.py ├── dem_nav_utilities.py ├── dem_utilities.py ├── get_gdal_drivers.py └── grass_library.py ├── data └── grass_db_template │ ├── UKBNG │ └── PERMANENT │ │ ├── DEFAULT_WIND │ │ ├── MYNAME │ │ ├── PROJ_INFO │ │ ├── PROJ_UNITS │ │ └── WIND │ └── WGS84LL │ └── PERMANENT │ ├── DEFAULT_WIND │ ├── MYNAME │ ├── PROJ_INFO │ ├── PROJ_UNITS │ └── WIND ├── doc ├── Makefile ├── make.bat └── source │ ├── conf.py │ ├── create_scripts_rst.py │ ├── dem_common.rst │ ├── dem_lidar.rst │ ├── dem_nav_utilities.rst │ ├── dem_utilities.rst │ ├── figures │ ├── EUFAR11_02-2011-187_dsm_20m_contours.png │ ├── dtm_dsm.png │ ├── grass_dsm_idw.png │ ├── grass_dsm_with_holes.png │ ├── lag_profile.png │ └── plasio_screenshot.png │ ├── grass_library.rst │ ├── index.rst │ ├── installation.rst │ ├── scripts.rst │ ├── tutorial_lidar.md │ └── tutorial_lidar_windows.md ├── scripts ├── create_apl_dem.bat ├── create_apl_dem.py ├── create_dem_from_lidar.bat ├── create_dem_from_lidar.py ├── las_to_dsm.bat ├── las_to_dsm.py ├── las_to_dtm.bat ├── las_to_dtm.py ├── las_to_intensity.bat ├── las_to_intensity.py ├── load_lidar_to_grass.bat ├── load_lidar_to_grass.py ├── mosaic_dem_tiles.bat ├── mosaic_dem_tiles.py ├── spdlib_create_dems_from_las.bat └── spdlib_create_dems_from_las.py └── setup.py /README.md: -------------------------------------------------------------------------------- 1 | NERC-ARF DEM Scripts 2 | ===================== 3 | 4 | About 5 | ------ 6 | 7 | A collection of scripts developed by the [NERC](http://www.nerc.ac.uk/) Airborne Research Facility 8 | Data Analysis Node (NERC-ARF-DAN; https://nerc-arf-dan.pml.ac.uk) for working with DEMs. 9 | 10 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.322341.svg)](https://doi.org/10.5281/zenodo.322341) 11 | 12 | 13 | Licensing 14 | ---------- 15 | 16 | This software is available under the General Public License (GPL) Version 3. 17 | See the file 'LICENSE' for more details. 18 | 19 | Installation 20 | ------------- 21 | 22 | For the scripts to work a number of other software packages, coordinate transform and offset files are also required. 23 | 24 | Required Software: 25 | 26 | * GRASS 27 | * Python (Needs to be the same version used by GRASS, currently 2.7) 28 | * LAStools (free/paid) - Free tools required for importing LAS files, paid tools can be used to create DTMs/DSMs. 29 | 30 | Following installation of these packages, install ARSF DEM scripts using: 31 | 32 | python setup.py install --prefix=~/install/path 33 | 34 | More advanced functionality (e.g., coordinate transforms) requires additional files and packages. 35 | 36 | For more details on installation see [installation](doc/source/installation.rst). 37 | 38 | Usage 39 | ------ 40 | 41 | See the [tutorial](doc/source/tutorial_lidar.md) for more details on creating DEMs from LiDAR data and 42 | the [scripts](doc/source/scripts.rst) page for details on available scripts. 43 | 44 | Contributing 45 | --------------- 46 | 47 | ### Internal (within PML) 48 | 49 | Internal development takes place using an in-house GitLab instance. Clone from the internal GitLab and create a branch from 'development-branch'. Push this branch to GitLab and submit a merge request (into 'development-branch') once changes are ready to be reviewed. Once changes have been merged into 'development-branch' push them to GitHub. Merge into master branch before tagging a new release. 50 | 51 | ### External 52 | 53 | External contributions are welcome. 54 | 55 | If you have found a bug or would like to suggest new features create an issue providing as much detail as possible. 56 | 57 | If you would like to contribute to the code create a fork and make changes into the branch 'development-branch'. Submit a pull request once you are ready for your changes to be merged. 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /arsf_dem/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | #Author: Dan Clewley (dac) 4 | #Created On: 05/10/2014 5 | 6 | # This file has been created by ARSF Data Analysis Node and 7 | # is licensed under the GPL v3 Licence. A copy of this 8 | # licence is available to download with this file. 9 | 10 | """ 11 | ARSF DEM module 12 | 13 | A Python library containing a number of tools for 14 | working with Digital Elevation models. 15 | """ 16 | from . import dem_utilities 17 | from . import dem_nav_utilities 18 | from . import dem_common 19 | from . import dem_lidar 20 | -------------------------------------------------------------------------------- /arsf_dem/arsf_dem.cfg: -------------------------------------------------------------------------------- 1 | # Config file containing default 2 | # parameters for arsf_dem library 3 | # 4 | # By default this file is installed 5 | # alongside the library. 6 | # 7 | # To override system settings the folder 8 | # can be copied to a project folder or to 9 | # ~/.arsf_dem 10 | 11 | [DEFAULT] 12 | # Set path to data directory 13 | # Assumes files use the following structure: 14 | # datadir 15 | # |-- projection_files 16 | # |-- separation_files 17 | # 18 | # The paths of other files is set relative to this 19 | # 20 | #datadir = /local1/data/scratch/arsf_dem_scripts_data 21 | 22 | [system] 23 | 24 | # Path for temporary files. 25 | # Will try to use system default if this is not set. 26 | # On Windows this is on the C: drive. If you have another disk 27 | # with more space (e.g., D:) or which is faster (e.g., an SSD) 28 | # Uncomment and set this variable 29 | 30 | #TEMP_PATH = /tmp/ 31 | 32 | # Debug mode set this to run scripts in debug mode 33 | # which will print more information for debugging and raise exceptions rather 34 | # than catching them. 35 | # Can also set using the environmental variable DEM_SCRIPTS_DEBUG 36 | 37 | #DEBUG = yes 38 | 39 | [grass] 40 | # Template for GRASS database. 41 | # This is included with the library source in the folder 'data' and installed to $PREFIX/share 42 | 43 | #GRASS_DATABASE_TEMPLATE = 44 | 45 | # Path for GRASS Library if not set will try standard location 46 | # for OS. 47 | # Under Windows assumes GRASS has been installed through OSGeo4W to 48 | # the default location (C:\OSGeo4W), if GRASS has been installed 49 | # somewhere else need to set this path 50 | 51 | #GRASS_LIB_PATH = /usr/lib64/grass/ 52 | 53 | # Path for GRASS Python library 54 | # Will get from 'GRASS_LIB_PATH'. If the Python library is not 55 | # where expected with respect to 'GRASS_LIB_PATH' this also 56 | # needs to be set. 57 | 58 | #GRASS_PYTHON_LIB_PATH = /usr/lib64/grass/etc/python/ 59 | 60 | [projection] 61 | # Location of OSTN02 transform file 62 | # This is needed to accurately convert to / from UKBNG projection 63 | # This file can be downloaded from: 64 | # http://www.ordnancesurvey.co.uk/business-and-government/help-and-support/navigation-technology/os-net/ostn02-ntv2-format.html 65 | 66 | OSTN02_NTV2_BIN_FILE = /users/rsg/arsf/dems/ostn02/OSTN02_NTv2.gsb 67 | #OSTN02_NTV2_BIN_FILE = %(datadir)s/projection_files/OSTN02_NTv2.gsb 68 | 69 | [separationfiles] 70 | # If vertical reprojection of data is needed seperation files must be set 71 | 72 | # Default location of vertical separation file between Newlyn and WGS-84 datum (WGS84LL projection) 73 | UKBNG_SEP_FILE_WGS84 = /users/rsg/arsf/dems/aster/separation_files/uk_separation_file_WGS84LL.dem 74 | #UKBNG_SEP_FILE_WGS84 = %(datadir)s/separation_files/uk_separation_file_WGS84LL.dem 75 | 76 | # Default location of vertical separation file between Newlyn and WGS-84 datum (UKBNG projection) 77 | UKBNG_SEP_FILE_UKBNG = /users/rsg/arsf/dems/aster/separation_files/uk_separation_file_UKBNG.dem 78 | #UKBNG_SEP_FILE_UKBNG = %(datadir)s/separation_files/uk_separation_file_UKBNG.dem 79 | 80 | # Default location of vertical separation file between geoid and WGS-84 datum. 81 | # This file can be downloaded from http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm96/egm96.html 82 | WWGSG_FILE = /users/rsg/arsf/dems/geoid-spheroid/ww15mgh.grd 83 | #WWGSG_FILE = %(datadir)s/separation_files/ww15mgh.grd 84 | 85 | # Default location of vertical separation file between EGM96 and Newlyn vertical datum (UKBNG projection). 86 | EGM96_UKBNG_SEP_FILE_WGS84 = /users/rsg/arsf/dems/aster/separation_files/ww15mgh_minus_uk_separation_file_WGS84LL.dem 87 | #EGM96_UKBNG_SEP_FILE_WGS84 = %(datadir)s/separation_files/ww15mgh_minus_uk_separation_file_WGS84LL.dem 88 | 89 | # Default location of vertical separation file between EGM96 and Newlyn vertical datum (UKBNG projection). 90 | EGM96_UKBNG_SEP_FILE_UKBNG = /users/rsg/arsf/dems/aster/separation_files/ww15mgh_minus_uk_separation_file_UKBNG.dem 91 | #EGM96_UKBNG_SEP_FILE_UKBNG = %(datadir)/separation_files/ww15mgh_minus_uk_separation_file_UKBNG.dem 92 | 93 | [rastercreation] 94 | # Standard options for raster creation. 95 | 96 | # Default method for interpolation when resampling 97 | RESAMPLE_METHOD = near 98 | 99 | # Default GDAL output format 100 | GDAL_OUTFILE_FORMAT = ENVI 101 | 102 | # Default GDAL output data type 103 | GDAL_OUTFILE_DATATYPE = Float32 104 | 105 | # Default GDAL creation options 106 | GDAL_CREATION_OPTIONS = INTERLEAVE=BIL 107 | 108 | # Default nodata value 109 | NODATA_VALUE = -9999 110 | 111 | [lidar] 112 | # Default parameters for LiDAR processing 113 | # All of these can be changed from within the functions 114 | 115 | # Default lidar resolution (in metres) 116 | DEFAULT_LIDAR_RES_METRES = 2 117 | 118 | # Default lidar projection for grass 119 | DEFAULT_LIDAR_PROJECTION_GRASS = UKBNG 120 | 121 | # Default buffer distance to use when patching lidar DEM (if not using hyperspectral navigation data) 122 | DEFAULT_LIDAR_DEM_BUFFER_DISTANCE = 2000 123 | 124 | [lastools] 125 | # LAStools 126 | # Required to convert LAS files to ASCII 127 | # Windows binaries available to download from: http://www.cs.unc.edu/~isenburg/lastools/ 128 | # Source availalbe to download from: https://github.com/LAStools/LAStools 129 | # 130 | 131 | # Path to open source LAStools binaries (can be compiled for Linux / OS X) 132 | # If in main path leave this blank. 133 | # For Windows only need to set if LAStools have not been installed to 134 | # C:\LAStools 135 | 136 | #LASTOOLS_FREE_BIN_PATH = 137 | 138 | # Path to commercial LAStools binaries (Windows only, must be run through Wine under Linux). 139 | # Without a valid license artefacts will be introduced when using these 140 | # tools. 141 | # They are not required by the main scripts can be called by functions to create 142 | # DTMs and DSMs 143 | # If not available or in main path leave this blank 144 | # For Windows only need to set if LAStools have not been installed to 145 | # C:\LAStools 146 | 147 | LASTOOLS_NONFREE_BIN_PATH = /users/rsg/arsf/usr/opt/lastools 148 | 149 | [dems] 150 | # When creating a DEM for APL or patching LiDAR data 151 | # with a DEM, default mosaic locatins are used when 152 | # passing in --aster or --nextmap 153 | 154 | # Default location of ASTER DEM mosaic 155 | # Must cover a larger area than required and be a format GDAL can read 156 | # Assumed to be WGS84LL projection with vertical heights relative to EGM96 geoid 157 | # (as downloaded) 158 | 159 | ASTER_MOSAIC_FILE = /users/rsg/arsf/dems/aster/aster_v2_dem_mosaic.vrt 160 | 161 | # Default location of NextMap DEM mosaic 162 | # Must cover a larger area than required and be a format GDAL can read 163 | # Assumed to be UKBNG projection with vertical heights relative to Newlyn datum 164 | # (as downloaded) 165 | 166 | NEXTMAP_MOSAIC_FILE = /users/rsg/arsf/dems/nextmap/neodc/nextmap_dsm_mosaic_bng.vrt 167 | 168 | # Default location of SRTM Mosaic 169 | # Must cover a larger area than required and be a format GDAL can read 170 | # Assumed to be WGS84LL projection with vertical heights relative to EGM96 geoid 171 | # (as downloaded) 172 | 173 | SRTM_MOSAIC_FILE = /users/rsg/arsf/dems/SRTM/global/srtm_global_mosaic_1arc_v3.vrt 174 | 175 | [hyperspectral] 176 | # Hyperspectral parameters, used when creating a DEM 177 | # for use in APL 178 | 179 | # Default sensor to get view vectors for when calculating DEM size. 180 | 181 | #DEFAULT_SENSOR_VIEW_VECTORS = eagle 182 | 183 | # Maximum view vector (in degrees) for hyperspectral data. Value from Eagle (2013). 184 | 185 | #HYPERSPECTRAL_VIEW_ANGLE_MAX = 18.76 186 | 187 | # Default buffer, in degrees, for APL DEM around hyperspectral extent 188 | 189 | DEFAULT_APL_DEM_BUFFER_DISTANCE = 0.05 190 | 191 | [spdlib] 192 | # SPDLib 193 | # Not required for main scripts but provides additional functions to 194 | # create DTMs and DEMs within arsf_dem.dem_lidar.spdlib_lidar 195 | # SPDLib binaries for Windows and source can be downloaded from: 196 | # https://bitbucket.org/petebunting/spdlib 197 | # For Linux / OS X binaries are available through conda from binstar: 198 | # https://binstar.org/osgeo 199 | 200 | # Path to SPDLib binaries (only required for arsf_dem.dem_lidar.spdlib_lidar) 201 | # If not available or in main path leave this blank 202 | 203 | SPDLIB_BIN_PATH = /users/rsg/arsf/scratch_space/anaconda_python/bin 204 | 205 | # Default interpolation used by SPDLib 206 | 207 | SPD_DEFAULT_INTERPOLATION = NATURAL_NEIGHBOR 208 | 209 | [fusion] 210 | # FUSION 211 | # Not required for main scripts but provides additional functions to 212 | # create DTMs and DSMs within arsf_dem.dem_lidar.fusion_lidar 213 | 214 | FUSION_BIN_PATH = 215 | 216 | [points2grid] 217 | # Not required for main scripts but provides additional functions to 218 | # create DEMs within arsf_dem.dem_lidar.points2grid_lidar 219 | 220 | POINTS2GRID_BIN_PATH = 221 | 222 | -------------------------------------------------------------------------------- /arsf_dem/dem_common_functions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding:utf-8 -*- 3 | """ 4 | Copy of common ARSF Python functions required 5 | by arsf_dem. 6 | 7 | Functions have been modified so they also work under Windows. 8 | 9 | """ 10 | # This file has been created by ARSF Data Analysis Node and 11 | # is licensed under the GPL v3 Licence. A copy of this 12 | # licence is available to download with this file. 13 | 14 | from __future__ import print_function # Import print function (so we can use Python 3 syntax with Python 2) 15 | import os 16 | import sys 17 | import select 18 | import inspect 19 | import subprocess 20 | 21 | def WARNING(strOutput): 22 | """Function that emphasises text in the terminal""" 23 | #Black background + bold white text 24 | if sys.platform != 'win32': 25 | print('\033[40;37;1m'+strOutput+'\033[0m') 26 | # If on windows don't bother trying to change colours, it won't work 27 | else: 28 | print(strOutput) 29 | 30 | def ERROR(strOutput,tostdouttoo=False): 31 | """Function that emphasises text in the sys.stderr stream""" 32 | 33 | try: 34 | callerid="%s : %s"%(inspect.stack()[1][1],inspect.stack()[1][3]) 35 | 36 | except Exception: 37 | callerid="" 38 | 39 | if sys.platform != 'win32': 40 | print('\033[41;33;1m'+"Error in "+callerid+": "+str(strOutput)+'\033[0m', file=sys.stderr) 41 | if tostdouttoo: 42 | print('\033[41;33;1m'+"Error in "+callerid+": "+str(strOutput)+'\033[0m', file=sys.stdout) 43 | else: 44 | # If on windows don't bother trying to change colours, it won't work 45 | print("Error in "+callerid+": "+str(strOutput), file=sys.stderr) 46 | 47 | def CallSubprocessOn(command=None,redirect=False,quiet=False,logger=None): 48 | """ 49 | CallSubprocessOn - run a command via subprocess and output stdout and stderr 50 | if redirect == True the returns the stdout/stderr rather than printing 51 | if quiet == True will not print out command it is running 52 | """ 53 | if command is None: 54 | raise TypeError("Command to be run must be specified") 55 | 56 | if isinstance(command,str): 57 | command_to_run=command.split(' ') 58 | elif isinstance(command,list): 59 | command_to_run=command 60 | else: 61 | raise TypeError("Expected command to be list or space separated string") 62 | 63 | if not quiet: 64 | print("\nAttempting to run command: "+" ".join(str(x) for x in command_to_run)) 65 | 66 | redirecttext=[] 67 | try: 68 | process=subprocess.Popen(command_to_run,stderr=subprocess.PIPE,stdout=subprocess.PIPE) 69 | # If on Windows don't try to run this, as it doesn't work 70 | if sys.platform != 'win32': 71 | while process.poll() is None: 72 | if redirect==False and not quiet: 73 | lines, _, _ =select.select([process.stdout,process.stderr],[],[],0.1) 74 | if lines:#if there is data read a line of it 75 | someline=lines[0].readline() 76 | if someline: 77 | if logger is None: 78 | print(someline.rstrip()) 79 | else: 80 | logger.info(someline.rstrip()) 81 | 82 | #Get anything left over in buffer 83 | stdout,stderr=process.communicate() 84 | if redirect==True: 85 | redirecttext=[stdout,stderr] 86 | 87 | #only output if not redirecting and not quiet 88 | elif stdout and redirect==False and not quiet: 89 | print(stdout) 90 | 91 | #still output error if quiet but not if redirecting 92 | #elif stderr and redirect==False: ERROR(stderr) 93 | elif stderr and redirect==False: 94 | if logger is not None: 95 | logger.error(stderr) 96 | raise StandardError(stderr) 97 | 98 | except StandardError as e: 99 | raise 100 | 101 | if redirecttext: 102 | return True,redirecttext 103 | else: 104 | return True 105 | 106 | def FileListInDirectory(path): 107 | """Function to return a list of files in the given directory""" 108 | if not os.path.exists(path): 109 | ERROR("Directory does not exist: %s"%path) 110 | return False 111 | 112 | try: 113 | listing=os.listdir(path) 114 | except Exception as e: 115 | ERROR("Error getting file listing of directory: %s\n%s"%(path,str(e))) 116 | return [] 117 | 118 | files=[] 119 | for item in listing: 120 | fullitem=os.path.join(path,item) 121 | if os.path.isdir(fullitem): 122 | pass 123 | elif os.path.isfile(fullitem): 124 | files.append(item) 125 | return files 126 | 127 | 128 | def PrintTermWidth(text, padding_char=' '): 129 | """ 130 | Prints a string padding with a character so the string is in the centre of the 131 | terminal. 132 | 133 | Function modified from one in https://bitbucket.org/chchrsc/envmaster by 134 | Sam Gillingham and make available under GPLv2 License 135 | 136 | """ 137 | # Try to get terminal width, not possible on Windows so will 138 | # raise exception. 139 | try: 140 | import termios 141 | import fcntl 142 | import struct 143 | 144 | # Get number of rows and columns in terminal 145 | data = fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ,'1234') 146 | (textrows,textcols) = struct.unpack('hh', data) 147 | 148 | # put some spaces around it 149 | if text != '': 150 | paddedtext = ' {} '.format(text) 151 | # If an empty string is passed in don't want to print a message 152 | # just a line of 'padding_chars', in this case don't want a 153 | # space (as this will look silly). 154 | else: 155 | paddedtext = text 156 | # how many padding symbols 157 | nequals = textcols - len(paddedtext) 158 | # put both sizes of the text 159 | paddedtext = padding_char * int(nequals / 2) + paddedtext 160 | paddedtext += padding_char * (textcols - len(paddedtext)) 161 | # write it out 162 | # Default is to place a single character each side with spaces. 163 | except Exception: 164 | paddedtext = ' {0} {1} {0} '.format(padding_char,text) 165 | 166 | print(paddedtext) 167 | 168 | def CheckPathExistsAndIsWritable(path): 169 | """ 170 | Check path exists and can be written to 171 | 172 | Returns True if path exists and can be written to 173 | 174 | """ 175 | # Check path exists 176 | if not os.path.exists(path): 177 | raise IOError("No such file or directory: {}".format(path)) 178 | # Check output directory is writable 179 | if not os.access(path,os.W_OK): 180 | raise IOError("Path does not have write permissions: {}".format(path)) 181 | return True 182 | -------------------------------------------------------------------------------- /arsf_dem/dem_lidar/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Dan Clewley (dac) 4 | # Created On: 06/10/2014 5 | 6 | # This file has been created by ARSF Data Analysis Node and 7 | # is licensed under the GPL v3 Licence. A copy of this 8 | # licence is available to download with this file. 9 | 10 | """ 11 | A collection of utilities for working with LiDAR data. 12 | 13 | Two high level functions las_to_dsm and las_dtm are included which are used 14 | to create a Digital Surface Model (DSM) and Digital Terrain Model (DTM) from 15 | LAS files. 16 | 17 | You can use these Python fuctions to look through all LAS files in an directory 18 | and create a DSM / DTM for each. 19 | 20 | For example:: 21 | 22 | import os 23 | import glob 24 | from arsf_dem import dem_lidar 25 | 26 | # Search current directory for all files ending matching '*.LAS' 27 | in_las_list = glob.glob('*.[Ll][Aa][Ss]') 28 | 29 | # Iterate through list of files found to create a raster for each line 30 | for in_las in in_las_list: 31 | # Set name of output DEM as the same as LAS file 32 | # but with '_dsm.tif' suffix 33 | out_dem_basename = os.path.splitext(os.path.split(in_las)[-1])[0] 34 | out_dsm = os.path.join(out_dir, out_dem_basename + '_dsm.tif') 35 | out_dtm = os.path.join(out_dir, out_dem_basename + '_dtm.tif') 36 | 37 | # Run function to create DSM 38 | dem_lidar.las_to_dsm(in_las,out_dsm, method='GRASS') 39 | 40 | # Run function to create DTM 41 | dem_lidar.las_to_dtm(in_las,out_dtm, method='GRASS') 42 | 43 | """ 44 | import tempfile 45 | import os 46 | from . import lidar_utilities 47 | from . import grass_lidar 48 | from . import ascii_lidar 49 | from . import lastools_lidar 50 | from . import spdlib_lidar 51 | from . import fusion_lidar 52 | from . import points2grid_lidar 53 | from . import laspy_lidar 54 | from .. import dem_common 55 | from .. import dem_utilities 56 | from .. import dem_common_functions 57 | from .. import grass_library 58 | 59 | #: Methods which can create a DEM from LAS files 60 | LAS_TO_DEM_METHODS = ['GRASS','SPDLib','LAStools','FUSION','points2grid'] 61 | #: Methods which can create an intensity image from LAS files 62 | LAS_TO_INTENSITY_METHODS = ['GRASS', 'LAStools'] 63 | #: Methods which can't filter out noisy points in LAS files and require these to be removed first 64 | METHODS_REQUIRE_LAS_NOISE_REMOVAL = ['SPDLib'] 65 | 66 | def _las_to_dem(in_las,out_raster, 67 | resolution=dem_common.DEFAULT_LIDAR_RES_METRES, 68 | projection=None, 69 | demtype='DSM', 70 | method='GRASS'): 71 | """ 72 | Helper function to generate a Digital Surface Model (DSM) or 73 | Digital Terrain Model (DTM) from a LAS file. 74 | 75 | Called by las_to_dtm or las_to_dsm. 76 | 77 | Utility function to call las_to_dtm / las_to_dsm from grass_lidar, lastools_lidar or 78 | spdlib_lidar 79 | 80 | When using GRASS the DTM will be created using only last returns. For SPDLib and 81 | LAStools methods, the data will be filtered to try and remove vegetation and buildings. 82 | 83 | Arguments: 84 | 85 | * in_las - Input LAS file or list of LAS files 86 | * out_raster - Output raster 87 | * resolution - Resolution to use for output raster. 88 | * projection - Projection of input LAS files (and output DEM) as GRASS location format (e.g., UTM30N). 89 | * method - GRASS, SPDLib, LAStools, FUSION or points2grid 90 | 91 | Returns: 92 | 93 | None 94 | 95 | """ 96 | # Check output path exists and can be written to 97 | # (will raise exception if it doesn't) 98 | dem_common_functions.CheckPathExistsAndIsWritable(os.path.split( 99 | os.path.abspath(out_raster))[0]) 100 | 101 | tmp_las_handler, tmp_las_file = tempfile.mkstemp(suffix='.las') 102 | 103 | # If a list is passed in merge to a single LAS file 104 | if isinstance(in_las, list): 105 | # Check if there is only one item in the list (will get this from 106 | # argparse). 107 | if len(in_las) == 1: 108 | if not os.path.isfile(in_las[0]): 109 | raise Exception('The file "{}" does not exist'.format(in_las[0])) 110 | elif method.upper() in [s.upper() for s in METHODS_REQUIRE_LAS_NOISE_REMOVAL]: 111 | # If method can't filter LAS files do this first 112 | # use merge_las function. 113 | print('Creating LAS file with noise points removed.' 114 | ' Required for {}'.format(method)) 115 | lastools_lidar.merge_las(in_las, tmp_las_file, drop_class=7) 116 | in_las_merged = tmp_las_file 117 | else: 118 | in_las_merged = in_las[0] 119 | else: 120 | print('Multiple LAS files have been passed in - merging') 121 | lastools_lidar.merge_las(in_las, tmp_las_file, drop_class=7) 122 | in_las_merged = tmp_las_file 123 | else: 124 | in_las_merged = in_las 125 | 126 | # Get output type from extension (if not specified) 127 | out_raster_format = dem_utilities.get_gdal_type_from_path(out_raster) 128 | 129 | if method.upper() == 'GRASS': 130 | # Set projection to default if not provided 131 | grass_location = projection 132 | if grass_location is None: 133 | grass_location = dem_common.DEFAULT_LIDAR_PROJECTION_GRASS 134 | 135 | if demtype.upper() == 'DSM': 136 | grass_lidar.las_to_dsm(in_las_merged, out_raster, 137 | bin_size=resolution, 138 | projection=grass_location) 139 | elif demtype.upper() == 'DTM': 140 | grass_lidar.las_to_dtm(in_las_merged, out_raster, 141 | bin_size=resolution, 142 | projection=grass_location) 143 | elif demtype.upper() == 'INTENSITY': 144 | grass_lidar.las_to_intensity(in_las_merged, out_raster, 145 | bin_size=resolution, 146 | projection=grass_location) 147 | else: 148 | raise Exception('DEM Type not recognised - options are DSM, DTM or Intensity') 149 | 150 | elif method.upper() == 'SPDLIB': 151 | # Create WKT file with projection 152 | if projection is not None: 153 | wktfile_handler, wkt_tmp = tempfile.mkstemp(suffix='.wkt', dir=dem_common.TEMP_PATH) 154 | grass_library.grass_location_to_wkt(projection, wkt_tmp) 155 | else: 156 | wkt_tmp = None 157 | 158 | if demtype.upper() == 'DSM': 159 | spdlib_lidar.las_to_dsm(in_las_merged, out_raster, 160 | bin_size=resolution, 161 | wkt=wkt_tmp, 162 | out_raster_format=out_raster_format) 163 | elif demtype.upper() == 'DTM': 164 | spdlib_lidar.las_to_dtm(in_las_merged, out_raster, 165 | bin_size=resolution, 166 | wkt=wkt_tmp, 167 | out_raster_format=out_raster_format) 168 | else: 169 | raise Exception('DEM Type not recognised - options are DSM or DTM') 170 | 171 | if projection is not None: 172 | # Close and remove temp WKT file created 173 | os.close(wktfile_handler) 174 | os.remove(wkt_tmp) 175 | 176 | elif method.upper() == 'LASTOOLS': 177 | # Set resolution flag 178 | lastools_flags = ['-step {}'.format(float(resolution))] 179 | 180 | # Get projection 181 | try: 182 | if projection is not None: 183 | lastools_proj = lastools_lidar.grass_proj_to_lastools_flag(projection) 184 | lastools_flags.extend([lastools_proj]) 185 | except Exception as err: 186 | dem_common_functions.WARNING('Could not convert projection to LAStools flags. {}. Will try to get projection from LAS file'.format(err)) 187 | 188 | if demtype.upper() == 'DSM': 189 | # Set spike-free flag, advice is ~ 3 x average pulse spacing 190 | # so approximate as 2 x resolution 191 | lastools_flags.extend(['-spike_free {}'.format(2*float(resolution))]) 192 | lastools_lidar.las_to_dsm(in_las_merged, out_raster, flags=lastools_flags) 193 | elif demtype.upper() == 'DTM': 194 | lastools_lidar.las_to_dtm(in_las_merged, out_raster, flags=lastools_flags) 195 | elif demtype.upper() == 'INTENSITY': 196 | lastools_lidar.las_to_intensity(in_las_merged, out_raster, flags=lastools_flags) 197 | else: 198 | raise Exception('DEM Type not recognised - options are DSM, DTM or Intensity') 199 | 200 | elif method.upper() == 'FUSION': 201 | if demtype.upper() == 'DSM': 202 | fusion_lidar.las_to_dsm(in_las_merged, out_raster, resolution=resolution) 203 | elif demtype.upper() == 'DTM': 204 | fusion_lidar.las_to_dtm(in_las_merged, out_raster, resolution=resolution) 205 | else: 206 | raise Exception('DEM Type not recognised - options are DSM or DTM') 207 | 208 | elif method.upper() == 'POINTS2GRID': 209 | # Create WKT file with projection 210 | if projection is not None: 211 | wktfile_handler, wkt_tmp = tempfile.mkstemp(suffix='.wkt', dir=dem_common.TEMP_PATH) 212 | grass_library.grass_location_to_wkt(projection, wkt_tmp) 213 | else: 214 | wkt_tmp = None 215 | 216 | # Create surface. Use IDW interpolation 217 | if demtype.upper() == 'DSM': 218 | points2grid_lidar.las_to_dsm(in_las_merged, out_raster, 219 | resolution=resolution, 220 | projection=wkt_tmp, 221 | grid_method='idw', 222 | fill_window_size=7) 223 | elif demtype.upper() == 'DTM': 224 | points2grid_lidar.las_to_dtm(in_las_merged, out_raster, 225 | resolution=resolution, 226 | projection=wkt_tmp, 227 | grid_method='idw', 228 | fill_window_size=7) 229 | else: 230 | raise Exception('DEM Type not recognised - options are DSM or DTM') 231 | 232 | if projection is not None: 233 | # Close and remove temp WKT file created 234 | os.close(wktfile_handler) 235 | os.remove(wkt_tmp) 236 | else: 237 | raise Exception('Invalid method "{}", expected GRASS, SPDLIB or LASTOOLS'.format(method)) 238 | 239 | # If an ENVI file remove .aux.xml file GDAL creates. This function will copy 240 | # any relevant parameters (e.g., no data value) to the .hdr file 241 | if out_raster_format == 'ENVI': 242 | dem_utilities.remove_gdal_aux_file(out_raster) 243 | 244 | os.close(tmp_las_handler) 245 | os.remove(tmp_las_file) 246 | 247 | def las_to_dsm(in_las,out_raster, 248 | resolution=dem_common.DEFAULT_LIDAR_RES_METRES, 249 | projection=None, 250 | method='GRASS'): 251 | """ 252 | Helper function to generate a Digital Surface Model (DSM) from a LAS file. 253 | 254 | Utility function to call las_to_dsm from grass_lidar, lastools_lidar or 255 | spdlib_lidar 256 | GRASS and points2grid generate the DSM using only first return points. 257 | When using LAStools the spike free method is used to generate the DSM from 258 | all returns. 259 | 260 | Arguments: 261 | 262 | * in_las - Input LAS file or list of LAS files 263 | * out_raster - Output raster 264 | * resolution - Resolution to use for output raster. 265 | * projection - Projection of input LAS files (and output DEM) as GRASS location format (e.g., UTM30N). 266 | * method - GRASS, SPDLib or LAStools 267 | 268 | Returns: 269 | 270 | None 271 | 272 | 273 | 274 | Example:: 275 | 276 | from arsf_dem import dem_lidar 277 | dem_lidar.las_to_dsm('in_las_file.las','out_dsm.dem') 278 | 279 | """ 280 | 281 | _las_to_dem(in_las, out_raster, 282 | resolution=resolution, 283 | projection=projection, 284 | demtype='DSM', 285 | method=method) 286 | 287 | def las_to_dtm(in_las,out_raster, 288 | resolution=dem_common.DEFAULT_LIDAR_RES_METRES, 289 | projection=None, 290 | method='GRASS'): 291 | """ 292 | Helper function to generate a Digital Terrain Model (DTM) from a LAS file. 293 | 294 | Utility function to call las_to_dtm from grass_lidar, lastools_lidar or 295 | spdlib_lidar 296 | 297 | When using GRASS the DTM will be created using only last returns. For SPDLib and 298 | LAStools methods, the data will be filtered to try and remove vegetation and buildings. 299 | When using LAStools the new ground classification in `lasground_new` is used.. 300 | When using SPDLib a combination of Progressive Morphology Filter and 301 | Multi-Scale Curvature algorithm are used. 302 | 303 | Arguments: 304 | 305 | * in_las - Input LAS file or list of LAS files 306 | * out_raster - Output raster 307 | * resolution - Resolution to use for output raster. 308 | * projection - Projection of input LAS files (and output DEM) as GRASS location format (e.g., UTM30N). 309 | * method - GRASS, SPDLib or LAStools 310 | 311 | Returns: 312 | 313 | None 314 | 315 | 316 | Example:: 317 | 318 | from arsf_dem import dem_lidar 319 | dem_lidar.las_to_dtm('in_las_file.las','out_dsm.dem') 320 | 321 | """ 322 | 323 | _las_to_dem(in_las, out_raster, 324 | resolution=resolution, 325 | projection=projection, 326 | demtype='DTM', 327 | method=method) 328 | 329 | def las_to_intensity(in_las,out_raster, 330 | resolution=dem_common.DEFAULT_LIDAR_RES_METRES, 331 | projection=None, 332 | method='GRASS'): 333 | """ 334 | Helper function to generate an Intensity image from a LAS file. 335 | 336 | Utility function to call las_to_intensity from grass_lidar or lastools_lidar 337 | 338 | Arguments: 339 | 340 | * in_las - Input LAS file or list of LAS files 341 | * out_raster - Output raster 342 | * resolution - Resolution to use for output raster. 343 | * projection - Projection of input LAS files (and output raster) as GRASS location format (e.g., UTM30N). 344 | * method - GRASS or LAStools 345 | 346 | Returns: 347 | 348 | None 349 | 350 | Example:: 351 | 352 | from arsf_dem import dem_lidar 353 | dem_lidar.las_to_intensity('in_las_file.las','out_intensity.tif') 354 | 355 | """ 356 | 357 | _las_to_dem(in_las, out_raster, 358 | resolution=resolution, 359 | projection=projection, 360 | demtype='INTENSITY', 361 | method=method) 362 | -------------------------------------------------------------------------------- /arsf_dem/dem_lidar/ascii_lidar.py: -------------------------------------------------------------------------------- 1 | # ascii_lidar 2 | # 3 | # Author: Dan Clewley (dac@pml.ac.uk) 4 | # Created On: 19/11/2014 5 | # 6 | # remove_ascii_class from grass_library by Stephen Gould 7 | 8 | # This file has been created by ARSF Data Analysis Node and 9 | # is licensed under the GPL v3 Licence. A copy of this 10 | # licence is available to download with this file. 11 | 12 | """ 13 | Functions for working with lidar data in ASCII format. 14 | 15 | Available functions: 16 | 17 | * get_ascii_bounds - get bounds from a lidar file. 18 | 19 | """ 20 | from __future__ import print_function # Import print function (so we can use Python 3 syntax with Python 2) 21 | import os 22 | import csv 23 | # Import common files 24 | from .. import dem_common 25 | 26 | def get_ascii_bounds(in_ascii): 27 | """ 28 | Gets bounds of ASCII format LiDAR file. 29 | 30 | Currently pure Python using CSV reader, could be 31 | made faster. 32 | 33 | Arguments: 34 | 35 | * in_ascii - Input ASCII file 36 | 37 | Returns: 38 | 39 | * bounding box in format: [[min_x,max_x], 40 | [min_y,max_y], 41 | [min_z,max_z]] 42 | """ 43 | 44 | if not os.path.isfile(in_ascii): 45 | raise Exception('File "{in_ascii}" does not exist') 46 | 47 | # Set initial min/max bounds to the same as thouse in 48 | # 'check_ascii_lidar.sh' 49 | min_x = 10000000 50 | max_x = -100000000 51 | min_y = 10000000 52 | max_y = -100000000 53 | min_z = 9999999 54 | max_z = 0 55 | 56 | in_ascii_handler = open(in_ascii, 'rU') 57 | in_ascii_csv = csv.reader(in_ascii_handler, delimiter=' ') 58 | 59 | for line in in_ascii_csv: 60 | 61 | line_x = float(line[dem_common.LIDAR_ASCII_ORDER['x']-1]) 62 | line_y = float(line[dem_common.LIDAR_ASCII_ORDER['y']-1]) 63 | line_z = float(line[dem_common.LIDAR_ASCII_ORDER['z']-1]) 64 | 65 | if line_x < min_x: 66 | min_x = line_x 67 | elif line_x > max_x: 68 | max_x = line_x 69 | 70 | if line_y < min_y: 71 | min_y = line_y 72 | elif line_y > max_y: 73 | max_y = line_y 74 | 75 | if line_z < min_z: 76 | min_z = line_z 77 | elif line_z > max_z: 78 | max_z = line_z 79 | 80 | # Close file 81 | in_ascii_handler.close() 82 | 83 | return [[min_x,max_x], 84 | [min_y,max_y], 85 | [min_z,max_z]] 86 | -------------------------------------------------------------------------------- /arsf_dem/dem_lidar/fusion_lidar.py: -------------------------------------------------------------------------------- 1 | #Author: Dan Clewley (dac) 2 | #Created on: 31 March 2015 3 | 4 | # This file has been created by ARSF Data Analysis Node and 5 | # is licensed under the GPL v3 Licence. A copy of this 6 | # licence is available to download with this file. 7 | """ 8 | Functions for working with LiDAR data using FUSION (http://forsys.cfr.washington.edu/fusion/fusion_overview.html) 9 | 10 | Requires FUSION to be installed. 11 | 12 | """ 13 | 14 | from __future__ import print_function # Import print function (so we can use Python 3 syntax with Python 2) 15 | import os 16 | import shutil 17 | import tempfile 18 | # Import common files 19 | from .. import dem_common 20 | from .. import dem_common_functions 21 | 22 | def _checkFUSION(): 23 | """Check if FUSION is installed.""" 24 | 25 | try: 26 | dem_common_functions.CallSubprocessOn([os.path.join(dem_common.FUSION_BIN_PATH,'groundfilter.exe')], 27 | redirect=True, quiet=True) 28 | return True 29 | except OSError: 30 | return False 31 | 32 | def _set_windows_path(in_path): 33 | """ 34 | Replace all '/' in paths with double backslash 35 | """ 36 | out_path = in_path.replace('/','\\') 37 | return out_path 38 | 39 | def classify_ground_las(in_las,out_las, 40 | resolution=dem_common.DEFAULT_LIDAR_RES_METRES): 41 | """ 42 | Classify ground returns in a LAS/LDA file using a 43 | 44 | Calls the groundfilter.exe tool. 45 | 46 | Arguments: 47 | 48 | * in_spd - Input LAS/LDA file 49 | * out_las - Output LAS file 50 | * resolution - Resolution used 51 | 52 | Returns: 53 | 54 | * None 55 | 56 | """ 57 | if not _checkFUSION(): 58 | raise Exception('Could not find FUSION') 59 | 60 | if not os.path.isfile(in_las): 61 | raise Exception('Input file "{}" does not exist'.format(in_spd)) 62 | 63 | groundCMD = [os.path.join(dem_common.FUSION_BIN_PATH,'groundfilter.exe'), 64 | _set_windows_path(out_las), str(resolution), 65 | _set_windows_path(in_las)] 66 | dem_common_functions.CallSubprocessOn(groundCMD) 67 | 68 | def export_dtm_raster(in_dtm, out_raster): 69 | """ 70 | Export FUSION DTM format raster to ENVI or Geotiff 71 | 72 | Arguments: 73 | 74 | * in_dtm - Input DTM in Fusion DTM format 75 | * out_raster - Output file in GeoTiff (.tif extension) or ENVI (all other extensions) format. 76 | 77 | """ 78 | 79 | convertCMD = [os.path.join(dem_common.FUSION_BIN_PATH,'DTM2ENVI.exe')] 80 | 81 | # If a tiff is requested use seperate command, else assume ENVI format 82 | if os.path.splitext(out_raster)[-1].lower() == '.tif' or os.path.splitext(out_raster)[-1].lower() == '.tiff': 83 | convertCMD = [os.path.join(dem_common.FUSION_BIN_PATH,'DTM2TIF.exe')] 84 | 85 | convertCMD.extend([_set_windows_path(in_dtm), _set_windows_path(out_raster)]) 86 | dem_common_functions.CallSubprocessOn(convertCMD) 87 | 88 | 89 | def las_to_dsm(in_las, out_dsm, 90 | resolution=dem_common.DEFAULT_LIDAR_RES_METRES): 91 | """ 92 | Create Digital Surface Model (DSM) from a LAS file using FUSION 93 | 94 | Arguments: 95 | 96 | * in_las - Input LAS File 97 | * out_dsm - Output DTM file 98 | * resolution - output resolution 99 | 100 | 101 | """ 102 | outdtm_handler, dtm_tmp = tempfile.mkstemp(suffix='.dtm', dir=dem_common.TEMP_PATH) 103 | 104 | print('Creating surface') 105 | surfaceCMD = [os.path.join(dem_common.FUSION_BIN_PATH,'canopymodel.exe'), 106 | _set_windows_path(dtm_tmp), 107 | str(resolution), 'M', 'M', '0','0','0','0', 108 | _set_windows_path(in_las)] 109 | dem_common_functions.CallSubprocessOn(surfaceCMD) 110 | 111 | print('Exporting') 112 | export_dtm_raster(dtm_tmp, out_dsm) 113 | 114 | os.close(outdtm_handler) 115 | os.remove(dtm_tmp) 116 | 117 | return None 118 | 119 | def las_to_dtm(in_las, out_dtm, 120 | resolution=dem_common.DEFAULT_LIDAR_RES_METRES): 121 | """ 122 | Create Digital Terrain Model (DTM) from a LAS file using FUSION 123 | 124 | Arguments: 125 | 126 | * in_las - Input LAS File 127 | * out_dsm - Output DTM file 128 | * resolution - output resolution 129 | 130 | """ 131 | outlas_handler, las_tmp = tempfile.mkstemp(suffix='.las', dir=dem_common.TEMP_PATH) 132 | outdtm_handler, dtm_tmp = tempfile.mkstemp(suffix='.dtm', dir=dem_common.TEMP_PATH) 133 | 134 | print('Classifying ground returns') 135 | classify_ground_las(in_las, las_tmp, resolution=resolution) 136 | 137 | print('Creating surface') 138 | surfaceCMD = [os.path.join(dem_common.FUSION_BIN_PATH,'GridSurfaceCreate.exe'), 139 | _set_windows_path(dtm_tmp), 140 | str(resolution), 'M', 'M', '0','0','0','0', 141 | _set_windows_path(las_tmp)] 142 | dem_common_functions.CallSubprocessOn(surfaceCMD) 143 | 144 | print('Exporting') 145 | export_dtm_raster(dtm_tmp, out_dtm) 146 | 147 | os.close(outlas_handler) 148 | os.close(outdtm_handler) 149 | 150 | os.remove(las_tmp) 151 | os.remove(dtm_tmp) 152 | 153 | return None 154 | -------------------------------------------------------------------------------- /arsf_dem/dem_lidar/laspy_lidar.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # 3 | # Author: Dan Clewley (dac) 4 | # Created on: 10 November 2014 5 | 6 | # This file has been created by ARSF Data Analysis Node and 7 | # is licensed under the GPL v3 Licence. A copy of this 8 | # licence is available to download with this file. 9 | 10 | """ 11 | Functions for working with LiDAR data in LAS format using the laspy 12 | Python library. 13 | 14 | http://pythonhosted.org/laspy/ 15 | 16 | Available Function: 17 | 18 | * get_las_bounds - get bounds of LAS file or list of LAS files. 19 | * get_las_bounds_single - used by get_las_bounds, don't call directly. 20 | 21 | """ 22 | 23 | from __future__ import print_function # Import print function (so we can use Python 3 syntax with Python 2) 24 | 25 | #: laspy library is available 26 | HAVE_LASPY = True 27 | try: 28 | import laspy 29 | except ImportError: 30 | # Don't raise error until a function 31 | # which requires laspy is called 32 | HAVE_LASPY = False 33 | 34 | def get_las_bounds_single(in_las_file,from_header=True): 35 | """ 36 | Gets bounds of a single LAS file using 37 | the laspy library. Used by get_las_bounds, it is reccomended 38 | to call this function as it can take a list or single file. 39 | 40 | Arguments: 41 | 42 | * in_las_file - input las file 43 | * from_header - get bounds from header (default) 44 | or by reading points (slower) 45 | 46 | Returns: 47 | 48 | * bounding box of las file [[min_x,max_x], 49 | [min_y,max_y], 50 | [min_z,max_z]] 51 | 52 | """ 53 | 54 | if not HAVE_LASPY: 55 | raise ImportError('Could not import laspy') 56 | 57 | in_las = laspy.file.File(in_las_file, mode='r') 58 | 59 | if from_header: 60 | min_x = in_las.header.min[0] 61 | max_x = in_las.header.max[0] 62 | min_y = in_las.header.min[1] 63 | max_y = in_las.header.max[1] 64 | min_z = in_las.header.min[2] 65 | max_z = in_las.header.max[2] 66 | 67 | else: 68 | min_x = in_las.x.min() 69 | max_x = in_las.x.max() 70 | min_y = in_las.y.min() 71 | max_y = in_las.y.max() 72 | min_z = in_las.z.min() 73 | max_z = in_las.z.max() 74 | 75 | return [[min_x,max_x], 76 | [min_y,max_y], 77 | [min_z,max_z]] 78 | 79 | def get_las_bounds(in_las, from_header=True): 80 | """ 81 | Gets bounds of a single LAS file using or outer bounds of a list 82 | of LAS files using the laspy library. 83 | 84 | Arguments: 85 | 86 | * in_las - input las file / list of files 87 | * from_header - get bounds from header (default) 88 | or by reading points (slower) 89 | 90 | Returns: 91 | 92 | * bounding box of all las files [[min_x,max_x], 93 | [min_y,max_y], 94 | [min_z,max_z]] 95 | 96 | """ 97 | if not HAVE_LASPY: 98 | raise ImportError('Could not import laspy') 99 | 100 | if isinstance(in_las,str): 101 | return get_las_bounds_single(in_las, from_header=from_header) 102 | elif isinstance(in_las, list): 103 | # Get bounds from LAS files 104 | min_x = None 105 | max_x = None 106 | min_y = None 107 | max_y = None 108 | min_z = None 109 | max_z = None 110 | 111 | for in_las_file in in_las: 112 | try: 113 | las_bounds = get_las_bounds_single(in_las_file, 114 | from_header=from_header) 115 | 116 | if min_x is None: 117 | min_x = las_bounds[0][0] 118 | elif las_bounds[0][0] < min_x: 119 | min_x = las_bounds[0][0] 120 | 121 | if max_x is None: 122 | max_x = las_bounds[0][1] 123 | elif las_bounds[0][1] > max_x: 124 | max_x = las_bounds[0][1] 125 | 126 | if min_y is None: 127 | min_y = las_bounds[1][0] 128 | elif las_bounds[1][0] < min_y: 129 | min_y = las_bounds[1][0] 130 | 131 | if max_y is None: 132 | max_y = las_bounds[1][1] 133 | elif las_bounds[1][1] > max_y: 134 | max_y = las_bounds[1][1] 135 | 136 | if min_z is None: 137 | min_z = las_bounds[2][0] 138 | elif las_bounds[2][0] < min_z: 139 | min_z = las_bounds[2][0] 140 | 141 | if max_z is None: 142 | max_z = las_bounds[2][1] 143 | elif las_bounds[2][1] > max_z: 144 | max_z = las_bounds[2][1] 145 | 146 | except Exception as err: 147 | dem_common_functions.WARNING('Could not get bounds for {}'.format(in_las_file)) 148 | 149 | return [[min_x,max_x], 150 | [min_y,max_y], 151 | [min_z,max_z]] 152 | else: 153 | raise Exception('Did not understand input, expected string or list') 154 | -------------------------------------------------------------------------------- /arsf_dem/dem_lidar/lastools_lidar.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # 3 | # Author: Dan Clewley (dac) 4 | # Created on: 05 November 2014 5 | 6 | # This file has been created by ARSF Data Analysis Node and 7 | # is licensed under the GPL v3 Licence. A copy of this 8 | # licence is available to download with this file. 9 | 10 | """ 11 | Functions for working with LiDAR data using LAStools: 12 | 13 | http://rapidlasso.com/lastools/ 14 | 15 | Note, some functions require a valid LAStools license. 16 | 17 | """ 18 | 19 | from __future__ import print_function # Import print function (so we can use Python 3 syntax with Python 2) 20 | import os 21 | import tempfile 22 | import glob 23 | import subprocess 24 | # Import common files 25 | from .. import dem_common 26 | from .. import dem_common_functions 27 | 28 | def _checkFreeLAStools(): 29 | """Check if LAStools are installed.""" 30 | 31 | try: 32 | dem_common_functions.CallSubprocessOn([os.path.join(dem_common.LASTOOLS_FREE_BIN_PATH,'las2txt'),'-h'], 33 | redirect=True, quiet=True) 34 | return True 35 | except OSError: 36 | return False 37 | 38 | def _checkPaidLAStools(): 39 | """Check if paid LAStools are installed.""" 40 | 41 | try: 42 | dem_common_functions.CallSubprocessOn([os.path.join(dem_common.LASTOOLS_NONFREE_BIN_PATH,'las2dem.exe'),'-h'], 43 | redirect=True, quiet=True) 44 | return True 45 | except OSError: 46 | return False 47 | 48 | def _check_flags(in_flags): 49 | """ 50 | Check if flags have been passed in in format '-flag'. 51 | If not add dash. If flag contains spaces splits and returns as list 52 | 53 | Arguments: 54 | 55 | * in_flag / list of input flags 56 | 57 | Returns: 58 | 59 | * list containing flag to pass to subprocess 60 | """ 61 | outflags_list = [] 62 | 63 | if isinstance(in_flags,list): 64 | for flag in in_flags: 65 | if flag[0] != '-': 66 | flag = '-' + flag 67 | # Split strings into separate list components for 68 | # subprocess. 69 | outflags_list.extend(flag.split()) 70 | 71 | elif isinstance(in_flags,str): 72 | if in_flags[0] != '-': 73 | in_flags = '-' + in_flags 74 | # Split strings into separate list components for 75 | # subprocess. 76 | outflags_list.extend(in_flags.split()) 77 | 78 | return outflags_list 79 | 80 | def convert_las_to_ascii(in_las, out_ascii, drop_class=None, keep_class=None, 81 | flags=None, print_only=False): 82 | """ 83 | Convert LAS files to ASCII using las2txt 84 | tool. 85 | 86 | http://www.cs.unc.edu/~isenburg/lastools/download/las2txt_README.txt 87 | 88 | Calls with the following options: 89 | 90 | las2txt -parse txyzicrna -sep space 91 | -i in_las -o out_ascii 92 | 93 | If a list of classes to drop is supplied will drop using the following 94 | command: 95 | 96 | las2txt -parse txyzicrna -sep space 97 | -drop_class 7 98 | -i in_las -o out_ascii 99 | 100 | If a list of classes to keep is supplied will drop using the following 101 | command: 102 | 103 | las2txt -parse txyzicrna -sep space 104 | -keep_class 7 105 | -i in_las -o out_ascii 106 | 107 | Can use flags to only keep first (-first_only) or last returns (-last_only) 108 | 109 | If a list 110 | 111 | Arguments: 112 | 113 | * in_las - Input LAS file / directory containing LAS files 114 | * out_ascii - Output ASCII file / directory containing LAS files 115 | * drop_class - Integer or list of integer class codes to drop 116 | * keep_class - Integer or list of integer class codes to keep 117 | * flags - List of additional flags for las2txt 118 | * print_only - Don't run commands, only print 119 | 120 | Returns: 121 | 122 | * None 123 | 124 | """ 125 | if not _checkFreeLAStools(): 126 | raise Exception('Could not find LAStools, checked ' 127 | '{}'.format(dem_common.LASTOOLS_FREE_BIN_PATH)) 128 | 129 | las2txt_cmd_base = [os.path.join(dem_common.LASTOOLS_FREE_BIN_PATH, 'las2txt'), 130 | '-parse', 131 | 'txyzicrna', 132 | '-sep', 133 | 'space'] 134 | 135 | if drop_class is not None: 136 | if isinstance(drop_class,list): 137 | drop_class_str = [] 138 | for item in drop_class: 139 | drop_class_str.append(str(item)) 140 | las2txt_cmd_base = las2txt_cmd_base + ['-drop_class'] + drop_class_str 141 | 142 | elif isinstance(drop_class,int): 143 | las2txt_cmd_base = las2txt_cmd_base + ['-drop_class',str(drop_class)] 144 | 145 | if keep_class is not None: 146 | if isinstance(keep_class,list): 147 | keep_class_str = [] 148 | for item in keep_class: 149 | keep_class_str.append(str(item)) 150 | las2txt_cmd_base = las2txt_cmd_base + ['-keep_class'] + keep_class_str 151 | 152 | elif isinstance(keep_class,int): 153 | las2txt_cmd_base = las2txt_cmd_base + ['-keep_class',str(keep_class)] 154 | 155 | # Check for flags 156 | if flags is not None: 157 | las2txt_cmd_base += _check_flags(flags) 158 | 159 | if isinstance(in_las,list): 160 | # If a list is passed in, run for each file 161 | for in_las_file in in_las: 162 | out_ascii_base = os.path.splitext(os.path.basename(in_las_file))[0] 163 | out_ascii_file = os.path.join(out_ascii, out_ascii_base + '.txt') 164 | las2txt_cmd = las2txt_cmd_base + ['-i',in_las_file, 165 | '-o',out_ascii_file] 166 | if print_only: 167 | print(" ", " ".join(las2txt_cmd)) 168 | else: 169 | dem_common_functions.CallSubprocessOn(las2txt_cmd) 170 | 171 | elif os.path.isdir(in_las): 172 | # If a directoy is passed in 173 | # Look for LAS or LAZ files 174 | in_las_list = glob.glob( 175 | os.path.join(in_las,'*[Ll][Aa][Ss]')) 176 | in_las_list.extend(glob.glob( 177 | os.path.join(in_las,'*[Ll][Aa][Zz]'))) 178 | if len(in_las_list) == 0: 179 | raise IOError('Could not find any LAS files in directory' 180 | ':\n {}'.format(in_las)) 181 | 182 | # Check a directory has been provided for output 183 | if not os.path.isdir(out_ascii): 184 | raise Exception('Must provide path to existing directory if an ' 185 | 'input directory is provided') 186 | 187 | for in_las_file in in_las_list: 188 | out_ascii_base = os.path.splitext(os.path.basename(in_las_file))[0] 189 | out_ascii_file = os.path.join(out_ascii, out_ascii_base + '.txt') 190 | las2txt_cmd = las2txt_cmd_base + ['-i',in_las_file, 191 | '-o',out_ascii_file] 192 | 193 | if print_only: 194 | print(" ", " ".join(las2txt_cmd)) 195 | else: 196 | dem_common_functions.CallSubprocessOn(las2txt_cmd) 197 | 198 | else: 199 | las2txt_cmd = las2txt_cmd_base + ['-i',in_las, 200 | '-o',out_ascii] 201 | 202 | if print_only: 203 | print(" ", " ".join(las2txt_cmd)) 204 | else: 205 | dem_common_functions.CallSubprocessOn(las2txt_cmd) 206 | 207 | def merge_las(in_las_list, out_las_file, 208 | drop_class=None, keep_class=None, flags=None): 209 | """ 210 | Merge multiple LAS files into a single file using: 211 | 212 | http://www.cs.unc.edu/~isenburg/lastools/download/lasmerge_README.txt 213 | 214 | 215 | Arguments: 216 | 217 | * in_las_list - List of input LAS files 218 | * out_las_file - Output LAS file 219 | * drop_class - Integer or list of integer class codes to drop 220 | * keep_class - Integer or list of integer class codes to keep 221 | * flags - List of additional flags for las2txt 222 | 223 | Returns: 224 | 225 | * None 226 | 227 | """ 228 | if not _checkFreeLAStools(): 229 | raise Exception('Could not find LAStools, ' 230 | 'checked {}'.format(dem_common.LASTOOLS_FREE_BIN_PATH)) 231 | 232 | lasmerge_cmd = [os.path.join(dem_common.LASTOOLS_FREE_BIN_PATH, 233 | 'lasmerge')] 234 | 235 | if drop_class is not None: 236 | if isinstance(drop_class,list): 237 | drop_class_str = [] 238 | for item in drop_class: 239 | drop_class_str.append(str(item)) 240 | lasmerge_cmd = lasmerge_cmd + ['-drop_class'] + drop_class_str 241 | 242 | elif isinstance(drop_class,int): 243 | lasmerge_cmd = lasmerge_cmd + ['-drop_class',str(drop_class)] 244 | 245 | if keep_class is not None: 246 | if isinstance(keep_class,list): 247 | keep_class_str = [] 248 | for item in keep_class: 249 | keep_class_str.append(str(item)) 250 | lasmerge_cmd = lasmerge_cmd + ['-keep_class'] + keep_class_str 251 | 252 | elif isinstance(keep_class,int): 253 | lasmerge_cmd = lasmerge_cmd + ['-keep_class',str(keep_class)] 254 | 255 | # Check for flags 256 | if flags is not None: 257 | lasmerge_cmd += _check_flags(flags) 258 | 259 | for in_las_file in in_las_list: 260 | lasmerge_cmd.extend(['-i',in_las_file]) 261 | 262 | lasmerge_cmd.extend(['-o', out_las_file]) 263 | 264 | dem_common_functions.CallSubprocessOn(lasmerge_cmd) 265 | 266 | def classify_ground_las(in_las,out_las, flags=None): 267 | """ 268 | Classify ground returns in a LAS file. 269 | 270 | Calls the lasground tool. 271 | 272 | http://www.cs.unc.edu/~isenburg/lastools/download/lasground_README.txt 273 | 274 | Note: this tool requires a license. 275 | 276 | Arguments: 277 | 278 | * in_las - Input LAS file 279 | * out_las - Output LAS file 280 | * flags - List of additional flags for lasground 281 | 282 | Returns: 283 | 284 | * None 285 | 286 | """ 287 | if not _checkPaidLAStools(): 288 | raise Exception('Could not find LAStools, checked {}'.format(dem_common.LASTOOLS_NONFREE_BIN_PATH)) 289 | 290 | lasground_cmd = [os.path.join(dem_common.LASTOOLS_NONFREE_BIN_PATH,'lasground_new.exe')] 291 | 292 | # Check for flags 293 | if flags is not None: 294 | lasground_cmd += _check_flags(flags) 295 | lasground_cmd.extend(['-extra_fine']) 296 | lasground_cmd.extend(['-i',in_las, '-o',out_las]) 297 | 298 | # Run directly through subprocess, as CallSubprocessOn 299 | # raises exception under windows for unlicensed LAStools 300 | print('Attempting to run command: ' + ' '.join(lasground_cmd)) 301 | subprocess.check_output(lasground_cmd) 302 | 303 | def las_to_dsm(in_las, out_dsm, flags=None): 304 | """ 305 | Create Digital Surface Model (DSM) 306 | from LAS file using the las2dem tool. 307 | 308 | http://www.cs.unc.edu/~isenburg/lastools/download/las2dem_README.txt 309 | 310 | Note: this tool requires a license. 311 | 312 | To use the spike_free method pass in the flag ['-spike_free 4']. 313 | For more details see: 314 | 315 | https://rapidlasso.com/2016/02/03/generating-spike-free-digital-surface-models-from-lidar/ 316 | 317 | Arguments: 318 | 319 | * in_las - Input LAS file 320 | * out_dsm - Output DSM, format depends on extension. 321 | * flags - List of additional flags for las2dem 322 | 323 | Returns: 324 | 325 | * None 326 | 327 | """ 328 | 329 | if not _checkPaidLAStools(): 330 | raise Exception('Could not find LAStools, checked {}'.format(dem_common.LASTOOLS_NONFREE_BIN_PATH)) 331 | 332 | print('Creating DSM') 333 | las2dem_cmd = [os.path.join(dem_common.LASTOOLS_NONFREE_BIN_PATH,'las2dem.exe')] 334 | # Check for flags 335 | if flags is not None: 336 | las2dem_cmd += _check_flags(flags) 337 | 338 | las2dem_cmd.extend(['-i',in_las, '-o',out_dsm]) 339 | 340 | # Run directly through subprocess, as CallSubprocessOn 341 | # raises exception under windows for unlicensed LAStools 342 | print('Attempting to run command: ' + ' '.join(las2dem_cmd)) 343 | subprocess.check_output(las2dem_cmd) 344 | 345 | def las_to_dtm(in_las, out_dtm, keep_las=False, flags=None): 346 | """ 347 | Create Digital Terrain Model (DTM) from LAS file 348 | using the las2dem tool. 349 | 350 | http://www.cs.unc.edu/~isenburg/lastools/download/las2dem_README.txt 351 | 352 | Note: this tool requires a license. 353 | 354 | Arguments: 355 | 356 | * in_las - Input LAS file 357 | * out_dtm - Output DTM, format depends on extension. 358 | * keep_las - Keep ground classified LAS file 359 | * flags - List of additional flags for las2dem 360 | 361 | Returns: 362 | 363 | * Ground classified LAS file / None 364 | 365 | """ 366 | 367 | if not _checkPaidLAStools(): 368 | raise Exception('Could not find LAStools, checked {}'.format(dem_common.LASTOOLS_NONFREE_BIN_PATH)) 369 | 370 | lasfile_grd_tmp = tempfile.mkstemp(suffix='.LAS', dir=dem_common.TEMP_PATH)[1] 371 | 372 | print('Classifying ground returns') 373 | classify_ground_las(in_las, lasfile_grd_tmp, flags=['-ignore_class 7']) 374 | 375 | print('Creating DTM') 376 | las2dem_cmd = [os.path.join(dem_common.LASTOOLS_NONFREE_BIN_PATH,'las2dem.exe')] 377 | # Check for flags 378 | if flags is not None: 379 | las2dem_cmd += _check_flags(flags) 380 | 381 | las2dem_cmd.extend(['-keep_class', '2']) 382 | las2dem_cmd.extend(['-i',lasfile_grd_tmp, '-o',out_dtm]) 383 | 384 | # Run directly through subprocess, as CallSubprocessOn 385 | # raises exception under windows for unlicensed LAStools 386 | print('Attempting to run command: ' + ' '.join(las2dem_cmd)) 387 | subprocess.check_output(las2dem_cmd) 388 | 389 | if keep_las: 390 | return lasfile_grd_tmp 391 | else: 392 | os.remove(lasfile_grd_tmp) 393 | return None 394 | 395 | def las_to_intensity(in_las, out_intensity, flags=None): 396 | """ 397 | Create an Intensity image from a 398 | from LAS file using the las2dem tool. 399 | 400 | http://www.cs.unc.edu/~isenburg/lastools/download/las2dem_README.txt 401 | 402 | Note: this tool requires a license. 403 | 404 | Arguments: 405 | 406 | * in_las - Input LAS file 407 | * out_intensity - Output intensity image, format depends on extension. 408 | * flags - List of additional flags for las2dem 409 | 410 | Returns: 411 | 412 | * None 413 | 414 | """ 415 | 416 | if not _checkPaidLAStools(): 417 | raise Exception('Could not find LAStools, checked {}'.format(dem_common.LASTOOLS_NONFREE_BIN_PATH)) 418 | 419 | print('Creating Intensity image') 420 | las2dem_cmd = [os.path.join(dem_common.LASTOOLS_NONFREE_BIN_PATH, 421 | 'las2dem.exe')] 422 | # Check for flags 423 | if flags is not None: 424 | las2dem_cmd += _check_flags(flags) 425 | 426 | las2dem_cmd.extend(['-i',in_las, '-o',out_intensity, '-intensity']) 427 | 428 | # Run directly through subprocess, as CallSubprocessOn 429 | # raises exception under windows for unlicensed LAStools 430 | print('Attempting to run command: ' + ' '.join(las2dem_cmd)) 431 | subprocess.check_output(las2dem_cmd) 432 | 433 | 434 | def grass_proj_to_lastools_flag(in_grass_proj): 435 | """ 436 | Converts GRASS projection (e.g., UTM30N) 437 | into projection flags for LAStools 438 | 439 | Currently only works with UTM 440 | 441 | Arguments: 442 | 443 | * in_grass_proj - Input GRASS projection. 444 | 445 | Returns: 446 | 447 | * flags for LAStools (e.g., -utm 30N) 448 | 449 | """ 450 | 451 | if in_grass_proj[:3].upper() != 'UTM': 452 | raise Exception('Currently only UTM projections are supported') 453 | else: 454 | return '-utm {}'.format(in_grass_proj[3:]) 455 | 456 | 457 | def convert_las_to_laz(in_las, out_laz=None, print_only=False, delete_las=False): 458 | """ 459 | Compress LAS files to LAZ using laszip 460 | tool. 461 | 462 | http://www.cs.unc.edu/~isenburg/lastools/download/laszip_README.txt 463 | 464 | Calls with the following options: 465 | 466 | laszip -i in_las -o out_laz 467 | 468 | Arguments: 469 | 470 | * in_las - Input LAS file / directory containing LAS files 471 | * out_laz - Output LAZ file / output directory for LAZ files. 472 | If None will assume same as input directory 473 | * print_only - Don't run commands, only print 474 | * delete_las - Delete the input LAS files after compression 475 | 476 | Returns: 477 | 478 | * None 479 | 480 | """ 481 | if not _checkFreeLAStools(): 482 | raise Exception('Could not find LAStools, checked ' 483 | '{}'.format(dem_common.LASTOOLS_FREE_BIN_PATH)) 484 | 485 | laszip_cmd_base = [os.path.join(dem_common.LASTOOLS_FREE_BIN_PATH, 'laszip')] 486 | 487 | 488 | if isinstance(in_las,list): 489 | # If a list is passed in, run for each file 490 | for in_las_file in in_las: 491 | out_laz_base = os.path.splitext(os.path.basename(in_las_file))[0] 492 | out_laz_file = os.path.join(out_laz, out_laz_base + '.laz') 493 | laszip_cmd = laszip_cmd_base + ['-i',in_las_file, 494 | '-o',out_laz_file] 495 | if print_only: 496 | print(" ", " ".join(laszip_cmd)) 497 | else: 498 | dem_common_functions.CallSubprocessOn(laszip_cmd) 499 | if delete_las: 500 | if print_only: 501 | print("Will remove file ",in_las_file) 502 | else: 503 | os.remove(in_las_file) 504 | 505 | elif os.path.isdir(in_las): 506 | # If a directoy is passed in 507 | # Look for LAS 508 | in_las_list = glob.glob( 509 | os.path.join(in_las,'*[Ll][Aa][Ss]')) 510 | if len(in_las_list) == 0: 511 | raise IOError('Could not find any LAS files in directory' 512 | ':\n {}'.format(in_las)) 513 | 514 | # Check a directory has been provided for output 515 | if out_laz is None: 516 | out_laz = in_las 517 | elif not os.path.isdir(out_laz): 518 | raise Exception('Output directory must exist if supplied') 519 | 520 | for in_las_file in in_las_list: 521 | out_laz_base = os.path.splitext(os.path.basename(in_las_file))[0] 522 | out_laz_file = os.path.join(out_laz, out_laz_base + '.laz') 523 | laszip_cmd = laszip_cmd_base + ['-i',in_las_file, 524 | '-o',out_laz_file] 525 | 526 | if print_only: 527 | print(" ", " ".join(laszip_cmd)) 528 | else: 529 | dem_common_functions.CallSubprocessOn(laszip_cmd) 530 | if delete_las: 531 | if print_only: 532 | print("Will remove file ", in_las_file) 533 | else: 534 | os.remove(in_las_file) 535 | else: 536 | if out_laz is None: 537 | out_laz = os.path.splitext(in_las)[0] + '.laz' 538 | laszip_cmd = laszip_cmd_base + ['-i',in_las, 539 | '-o',out_laz] 540 | 541 | if print_only: 542 | print(" ", " ".join(laszip_cmd)) 543 | else: 544 | dem_common_functions.CallSubprocessOn(laszip_cmd) 545 | if delete_las: 546 | if print_only: 547 | print("Will remove file ",in_las) 548 | else: 549 | os.remove(in_las) 550 | -------------------------------------------------------------------------------- /arsf_dem/dem_lidar/points2grid_lidar.py: -------------------------------------------------------------------------------- 1 | #Author: Dan Clewley (dac) 2 | #Created on: 02 April 2015 3 | """ 4 | Functions for working with LiDAR data using points2grid: 5 | 6 | https://github.com/CRREL/points2grid 7 | 8 | Requires the development version of points2grid to be installed which 9 | has filters for LAS points. 10 | 11 | Documentation on points2grid is available from: 12 | 13 | http://www.opentopography.org/otsoftware/points2grid 14 | 15 | """ 16 | 17 | from __future__ import print_function # Import print function (so we can use Python 3 syntax with Python 2) 18 | import os 19 | import shutil 20 | import tempfile 21 | # Import common files 22 | from .. import dem_common 23 | from .. import dem_common_functions 24 | from .. import get_gdal_drivers 25 | 26 | def _checkPoints2Grid(): 27 | """ 28 | Check if Points2Grid is installed. 29 | """ 30 | 31 | try: 32 | dem_common_functions.CallSubprocessOn([os.path.join(dem_common.POINTS2GRID_BIN_PATH,'points2grid'),'--help'], 33 | redirect=True, quiet=True) 34 | return True 35 | except OSError: 36 | return False 37 | 38 | def export_ascii_raster(points2dem_outfile, out_raster, 39 | output_type='mean',projection=None): 40 | """ 41 | Exports raster created by points2dem 42 | 43 | Arguments: 44 | 45 | * points2dem_outfile - Output file passed to points2dem 46 | * out_raster - Output file (extension determines format). 47 | * output_type - points2dem output type (min, max, mean, idw, std, den, all) 48 | * projection - Proj4 string / WKT file defining projection 49 | 50 | """ 51 | 52 | in_raster = points2dem_outfile + '.{}.asc'.format(output_type) 53 | 54 | # If ASCII output is wanted just copy file 55 | if os.path.splitext(out_raster)[-1] == '.asc': 56 | shutil.copy(in_raster, out_raster) 57 | # Otherwise use gdal_translate 58 | else: 59 | # Set output options 60 | out_ext = os.path.splitext(out_raster)[-1] 61 | out_format = get_gdal_drivers.GDALDrivers().get_driver_from_ext(out_ext) 62 | out_options = \ 63 | get_gdal_drivers.GDALDrivers().get_creation_options_from_ext(out_ext) 64 | 65 | gdal_translate_cmd = ['gdal_translate', 66 | '-of',out_format] 67 | # If there are creation options add these 68 | for creation_option in out_options: 69 | gdal_translate_cmd.extend(['-co', creation_option]) 70 | 71 | if projection is not None: 72 | gdal_translate_cmd.extend(['-a_srs',projection]) 73 | 74 | gdal_translate_cmd.extend([in_raster, out_raster]) 75 | dem_common_functions.CallSubprocessOn(gdal_translate_cmd) 76 | 77 | def _las_to_dem(in_las, out_dem, 78 | resolution=dem_common.DEFAULT_LIDAR_RES_METRES, 79 | projection=None, 80 | demtype='DSM', 81 | grid_method='mean', 82 | search_radius=None, 83 | fill_window_size=None, 84 | exclude_class=[7], 85 | quiet=True): 86 | """ 87 | Create Digital Elevation Model (DEM) from a LAS file using points2grid 88 | Called by las_to_dtm or las_to_dem 89 | 90 | Arguments: 91 | 92 | * in_las - Input LAS File 93 | * out_dem - Output DTM file 94 | * resolution - output resolution 95 | * demtype - DSM / DTM 96 | * grid_method - points2grid output type (min, max, mean, idw or std) 97 | * search_radius - specifies the search radius (default is 2 or resolution, whichever is greater) 98 | * fill_window_size - window size to use for filling nulls 99 | * exclude_class - list of classes to exclude (default = class 7) 100 | * quiet - don't print output from points2grid command 101 | 102 | """ 103 | if not _checkPoints2Grid(): 104 | raise Exception('Could not find points2grid, checked {}'.format(dem_common.POINTS2GRID_BIN_PATH)) 105 | 106 | outdem_handler, dem_tmp = tempfile.mkstemp(suffix='', dir=dem_common.TEMP_PATH) 107 | 108 | # Set search raduis. For 'typical' ARSF 109 | if search_radius is None: 110 | if resolution < 2: 111 | search_radius = 2 112 | else: 113 | search_radius = resolution 114 | 115 | print('Creating surface') 116 | surfaceCMD = [os.path.join(dem_common.POINTS2GRID_BIN_PATH,'points2grid'), 117 | '--output_file_name',dem_tmp, 118 | '--output_format','arc', 119 | '--search_radius', str(search_radius), 120 | '--resolution',str(resolution)] 121 | surfaceCMD.extend(['--exclude_class']) 122 | surfaceCMD.extend([str(c) for c in exclude_class]) 123 | 124 | if grid_method.lower() == 'min': 125 | surfaceCMD.extend(['--min']) 126 | elif grid_method.lower() == 'max': 127 | surfaceCMD.extend(['--max']) 128 | elif grid_method.lower() == 'mean': 129 | surfaceCMD.extend(['--mean']) 130 | elif grid_method.lower() == 'idw': 131 | surfaceCMD.extend(['--idw']) 132 | elif grid_method.lower() == 'std': 133 | surfaceCMD.extend(['--std']) 134 | 135 | if fill_window_size is not None: 136 | if fill_window_size not in [3, 5, 7]: 137 | raise ValueError('Size for fill window must be 3, 5 or 7. ' 138 | '{} was provided'.format(fill_window_size)) 139 | surfaceCMD.extend(['--fill', 140 | '--fill_window_size', str(fill_window_size)]) 141 | 142 | if demtype.upper() == 'DSM': 143 | surfaceCMD.extend(['--first_return_only']) 144 | elif demtype.upper() == 'DTM': 145 | surfaceCMD.extend(['--last_return_only']) 146 | else: 147 | raise Exception('DEM Type must be "DSM" or "DTM"') 148 | 149 | surfaceCMD.extend(['-i',in_las]) 150 | dem_common_functions.CallSubprocessOn(surfaceCMD, redirect=quiet) 151 | 152 | print('Exporting') 153 | export_ascii_raster(dem_tmp, out_dem, projection=projection, 154 | output_type=grid_method.lower()) 155 | 156 | os.close(outdem_handler) 157 | os.remove(dem_tmp + '.{}.asc'.format(grid_method.lower())) 158 | 159 | return None 160 | 161 | def las_to_dsm(in_las, out_dsm, 162 | resolution=dem_common.DEFAULT_LIDAR_RES_METRES, 163 | projection=None, 164 | grid_method='mean', 165 | fill_window_size=None, 166 | quiet=True): 167 | """ 168 | Create Digital Surface Model (DSM) from a LAS file using points2grid 169 | 170 | Arguments: 171 | 172 | * in_las - Input LAS File 173 | * out_dsm - Output DTM file 174 | * resolution - output resolution 175 | * grid_method - points2grid output type (min, max, mean, idw or std) 176 | * fill_window_size - window size to use for filling nulls 177 | * quiet - don't print output from points2grid command 178 | 179 | """ 180 | _las_to_dem(in_las, out_dsm, 181 | resolution=resolution, 182 | projection=projection, 183 | demtype='DSM', 184 | grid_method=grid_method, 185 | fill_window_size=fill_window_size, 186 | quiet=quiet) 187 | 188 | return None 189 | 190 | def las_to_dtm(in_las, out_dtm, 191 | resolution=dem_common.DEFAULT_LIDAR_RES_METRES, 192 | projection=None, 193 | grid_method='mean', 194 | fill_window_size=None, 195 | quiet=True): 196 | """ 197 | Create Digital Terrain Model (DSM) from a LAS file using points2grid 198 | 199 | The DTM is created using only last returns, therefore is not a true DTM as 200 | not all last returns will be from the ground. 201 | 202 | Arguments: 203 | 204 | * in_las - Input LAS File 205 | * out_dtm - Output DTM file 206 | * resolution - output resolution 207 | * grid_method - points2grid output type (min, max, mean, idw or std) 208 | * fill_window_size - window size to use for filling nulls 209 | * quiet - don't print output from points2grid command 210 | 211 | """ 212 | _las_to_dem(in_las, out_dtm, 213 | resolution=resolution, 214 | projection=projection, 215 | demtype='DTM', 216 | grid_method=grid_method, 217 | fill_window_size=fill_window_size, 218 | quiet=quiet) 219 | 220 | return None 221 | 222 | def classified_las_to_dtm(in_las, out_dtm, 223 | resolution=dem_common.DEFAULT_LIDAR_RES_METRES, 224 | projection=None, 225 | grid_method='mean', 226 | fill_window_size=7, 227 | quiet=True): 228 | """ 229 | Create Digital Terrain Model (DTM) from a LAS file where the ground 230 | returns have already been classified (class 2). 231 | 232 | Arguments: 233 | 234 | * in_las - Input classified LAS File 235 | * out_dtm - Output DTM file 236 | * resolution - output resolution 237 | * grid_method - points2grid output type (min, max, mean, idw or std) 238 | * fill_window_size - window size to use for filling nulls 239 | * quiet - don't print output from points2grid command 240 | 241 | """ 242 | non_ground_classes = [i for i in range(0,32)] 243 | non_ground_classes.remove(2) 244 | 245 | _las_to_dem(in_las, out_dtm, 246 | resolution=resolution, 247 | projection=projection, 248 | demtype='DTM', 249 | grid_method=grid_method, 250 | fill_window_size=fill_window_size, 251 | exclude_class=non_ground_classes, 252 | quiet=quiet) 253 | 254 | return None 255 | -------------------------------------------------------------------------------- /arsf_dem/dem_lidar/spdlib_lidar.py: -------------------------------------------------------------------------------- 1 | #Author: Dan Clewley (dac) 2 | #Created on: 05 November 2014 3 | #Licensing: Uses SPDLib, subject to GNU GPL. 4 | """ 5 | Functions for working with LiDAR data using SPDLib (http://spdlib.org/) 6 | 7 | Requires SPDLib to be installed. 8 | 9 | For more details about SPDLib see the following publications: 10 | 11 | Bunting, P., Armston, J., Lucas, R. M., & Clewley, D. (2013). Sorted pulse data (SPD) library. Part I: A generic file format for LiDAR data from pulsed laser systems in terrestrial environments. Computers and Geosciences, 56, 197-206. doi:10.1016/j.cageo.2013.01.019 12 | 13 | Bunting, P., Armston, J., Clewley, D., & Lucas, R. M. (2013). Sorted pulse data (SPD) library-Part II: A processing framework for LiDAR data from pulsed laser systems in terrestrial environments. Computers and Geosciences, 56, 207-215. doi:10.1016/j.cageo.2013.01.010 14 | 15 | """ 16 | 17 | from __future__ import print_function # Import print function (so we can use Python 3 syntax with Python 2) 18 | import os 19 | import shutil 20 | import subprocess 21 | import tempfile 22 | # Import common files 23 | from .. import dem_common 24 | from .. import dem_common_functions 25 | from .. import dem_utilities 26 | 27 | def _checkSPDLib(): 28 | """Check if SPDLib is installed.""" 29 | 30 | try: 31 | dem_common_functions.CallSubprocessOn([os.path.join(dem_common.SPDLIB_BIN_PATH,'spdtranslate')], 32 | redirect=True, quiet=True) 33 | return True 34 | except OSError: 35 | return False 36 | 37 | def convert_las_to_spd(in_las,out_spd,wkt=None, 38 | bin_size=dem_common.DEFAULT_LIDAR_RES_METRES, 39 | no_pulse=True): 40 | """ 41 | Convert LAS file to spatially indexed SPD file by calling 42 | spdtranslate. 43 | 44 | Indexes using last return 45 | Uses temp files for conversion process. 46 | 47 | Known Issues: If LAS file isn't sorted SPDLib will print lots of warnings 48 | about writing incomplete pulses (1 for each pulse in the worst case), which 49 | prints a lot of messages to sdterr. Therefore using 'no_pulse=True' (default) 50 | is recommend if SPD file is only going to be used in DEM scripts. 51 | 52 | Arguments: 53 | 54 | * in_las - Input LAS file 55 | * out_spd - Output SPD file 56 | * wkt - WKT file defining projection (will obtain from LAS if not provided) 57 | * bin_size - Bin size for spatial indexing 58 | * no_pulse - Don't try to import as pulses. 59 | 60 | Returns: 61 | 62 | * None 63 | 64 | """ 65 | 66 | if not _checkSPDLib(): 67 | raise Exception('Could not find SPDLib') 68 | 69 | temp_dir = tempfile.mkdtemp(dir=dem_common.TEMP_PATH) 70 | spdtmppath = os.path.join(temp_dir, 'spd_tmp_') 71 | 72 | # If not using pulses import using 'LAS (No Pulse) importer 73 | # as this won't generate warnings if returns can't be matched 74 | # to a pulse 75 | las_importer = 'LAS' 76 | if no_pulse: 77 | las_importer = 'LASNP' 78 | 79 | spdtranslate_cmd = [os.path.join(dem_common.SPDLIB_BIN_PATH,'spdtranslate'), 80 | '--if', las_importer, 81 | '--of','SPD', 82 | '-b',str(bin_size), 83 | '-x','LAST_RETURN', 84 | '--temppath',spdtmppath, 85 | '-i',in_las,'-o',out_spd] 86 | 87 | if wkt is not None: 88 | spdtranslate_cmd.extend(['--input_proj',wkt, '--output_proj', wkt]) 89 | 90 | subprocess.check_call(spdtranslate_cmd) 91 | 92 | # Remove temp files 93 | shutil.rmtree(temp_dir) 94 | 95 | def classify_ground_spd(in_spd,out_spd, 96 | bin_size=dem_common.DEFAULT_LIDAR_RES_METRES): 97 | """ 98 | Classify ground returns in an SPD file using a progressive morphology 99 | filter for the initial classification and a combination of two algorithms: 100 | 101 | 1. Progressive Morphology Filter (PMF; Zhang et al., 2003): spdpmfgrd. 102 | 2. Multi-Scale Curvature algorithm (MCC; Evans and Hudak, 2007): spdmccgrd 103 | 104 | Arguments: 105 | 106 | * in_spd - Input SPD File 107 | * out_spd - Output SPD file 108 | * bin_size - Bin size for spatial indexing 109 | 110 | Returns: 111 | 112 | * None 113 | 114 | """ 115 | if not _checkSPDLib(): 116 | raise Exception('Could not find SPDLib') 117 | 118 | if not os.path.isfile(in_spd): 119 | raise Exception('Input SPD file "{}" does not exist'.format(in_spd)) 120 | 121 | spdfile_handler, spdfile_grd_tmp = tempfile.mkstemp(suffix='.spd', 122 | dir=dem_common.TEMP_PATH) 123 | 124 | # 1. PMF Filter 125 | pmfCMD = [os.path.join(dem_common.SPDLIB_BIN_PATH,'spdpmfgrd'), 126 | '-b',str(bin_size), 127 | '--grd', '1', 128 | '-i',in_spd,'-o',spdfile_grd_tmp] 129 | 130 | subprocess.check_call(pmfCMD) 131 | 132 | # 2. MCC applied to ground classified returns. 133 | mccCMD = [os.path.join(dem_common.SPDLIB_BIN_PATH,'spdmccgrd'), 134 | '-b',str(bin_size), 135 | '--class', '3', 136 | '--initcurvetol', '1', 137 | '-i',spdfile_grd_tmp,'-o',out_spd] 138 | 139 | subprocess.check_call(mccCMD) 140 | 141 | os.close(spdfile_handler) 142 | os.remove(spdfile_grd_tmp) 143 | 144 | def _spd_to_raster(in_spd, out_raster, 145 | raster_type='DSM', 146 | interpolation=dem_common.SPD_DEFAULT_INTERPOLATION, 147 | out_raster_format=dem_common.GDAL_OUTFILE_FORMAT, 148 | bin_size=dem_common.DEFAULT_LIDAR_RES_METRES): 149 | """ 150 | Create a raster from a SPD file 151 | 152 | Calls the spdinterp tool. 153 | 154 | Arguments: 155 | 156 | * in_spd - Input SPD File 157 | * out_raster - Output raster file 158 | * raster_type - type of raster to create, DTM, DSM or CHM 159 | * interpolation - Interpolation method 160 | * out_raster_format - GDAL format name for output raster (e.g., ENVI) 161 | * bin_size - Bin size for spatial indexing 162 | 163 | Returns: 164 | 165 | * None 166 | 167 | """ 168 | if not _checkSPDLib(): 169 | raise Exception('Could not find SPDLib') 170 | 171 | if not os.path.isfile(in_spd): 172 | raise Exception('Input SPD file "{}" does not exist'.format(in_spd)) 173 | 174 | dem_cmd = [os.path.join(dem_common.SPDLIB_BIN_PATH,'spdinterp'), 175 | '--in',interpolation, 176 | '-f',out_raster_format, 177 | '-b',str(bin_size), 178 | '-i',in_spd,'-o',out_raster] 179 | 180 | if raster_type.upper() == 'DSM': 181 | dem_cmd.extend(['--dsm','--topo']) 182 | elif raster_type.upper() == 'DTM': 183 | dem_cmd.extend(['--dtm','--topo']) 184 | elif raster_type.upper() == 'CHM': 185 | dem_cmd.extend(['--chm','--height']) 186 | else: 187 | raise Exception('Raster type "{}" was not recognised'.format(raster_type)) 188 | 189 | subprocess.check_call(dem_cmd) 190 | 191 | # Set nodata value (SPDLib uses nan but doesn't explicitly set as no data) 192 | dem_utilities.set_nodata_value(out_raster, float('NaN')) 193 | 194 | def spd_to_dsm(in_spd, out_dsm, interpolation=dem_common.SPD_DEFAULT_INTERPOLATION, 195 | out_raster_format=dem_common.GDAL_OUTFILE_FORMAT, 196 | bin_size=dem_common.DEFAULT_LIDAR_RES_METRES): 197 | """ 198 | Create a Digital Surface Model (DSM) from an SPD file 199 | 200 | Calls the spdinterp tool. 201 | 202 | Arguments: 203 | 204 | * in_spd - Input SPD File 205 | * out_dsm - Output DSM file 206 | * interpolation - Interpolation method 207 | * out_raster_format - GDAL format name for output raster (e.g., ENVI) 208 | * bin_size - Bin size for spatial indexing 209 | 210 | Returns: 211 | 212 | * None 213 | 214 | """ 215 | _spd_to_raster(in_spd, out_dsm, 216 | raster_type='DSM', 217 | interpolation=interpolation, 218 | out_raster_format=out_raster_format, 219 | bin_size=bin_size) 220 | 221 | def spd_to_dtm(in_spd, out_dtm, interpolation=dem_common.SPD_DEFAULT_INTERPOLATION, 222 | out_raster_format=dem_common.GDAL_OUTFILE_FORMAT, 223 | bin_size=dem_common.DEFAULT_LIDAR_RES_METRES, 224 | keep_spd=False): 225 | """ 226 | Create a Digital Surface Model (DTM) from an SPD file 227 | 228 | First classifies ground returns using a combination of ia Progressive 229 | Morphology filter and the Multi-scale Curvature algorithm 230 | then calls the spdinterp tool. 231 | 232 | Arguments: 233 | 234 | * in_spd - Input SPD File 235 | * out_dtm - Output DTM file 236 | * interpolation - Interpolation method 237 | * out_raster_format - GDAL format name for output raster (e.g., ENVI) 238 | * bin_size - Bin size for spatial indexing 239 | * keep_spd - Keep ground classified SPD file and return path (default is to remove) 240 | 241 | Returns: 242 | 243 | * Path to ground classified SPD file 244 | 245 | """ 246 | if not _checkSPDLib(): 247 | raise Exception('Could not find SPDLib') 248 | 249 | if not os.path.isfile(in_spd): 250 | raise Exception('Input SPD file "{}" does not exist'.format(in_spd)) 251 | 252 | spdfile_handler, spdfile_grd_tmp = tempfile.mkstemp(suffix='.spd', dir=dem_common.TEMP_PATH) 253 | 254 | print('Classifying ground returns') 255 | classify_ground_spd(in_spd, spdfile_grd_tmp) 256 | 257 | print('Creating DTM') 258 | _spd_to_raster(spdfile_grd_tmp, out_dtm, 259 | raster_type='DTM', 260 | interpolation=interpolation, 261 | out_raster_format=out_raster_format, 262 | bin_size=bin_size) 263 | 264 | os.close(spdfile_handler) 265 | if keep_spd: 266 | return spdfile_grd_tmp 267 | else: 268 | os.remove(spdfile_grd_tmp) 269 | return None 270 | 271 | def spd_to_chm(in_spd, out_chm, interpolation=dem_common.SPD_DEFAULT_INTERPOLATION, 272 | out_raster_format=dem_common.GDAL_OUTFILE_FORMAT, 273 | bin_size=dem_common.DEFAULT_LIDAR_RES_METRES, 274 | keep_spd=False): 275 | """ 276 | Create a Canopy Height Model (CMH) from an SPD file 277 | 278 | First attributes height filed in SPD file using spddefheight 279 | 280 | Arguments: 281 | 282 | * in_spd - Input SPD File 283 | * out_chm - Output CHM file 284 | * interpolation - Interpolation method 285 | * out_raster_format - GDAL format name for output raster (e.g., ENVI) 286 | * bin_size - Bin size for spatial indexing 287 | * keep_spd - Keep ground classified SPD file and return path (default is to remove) 288 | 289 | Returns: 290 | 291 | * Path to SPD file with height filed populated 292 | 293 | """ 294 | if not _checkSPDLib(): 295 | raise Exception('Could not find SPDLib') 296 | 297 | if not os.path.isfile(in_spd): 298 | raise Exception('Input SPD file "{}" does not exist'.format(in_spd)) 299 | 300 | spdfile_handler, spdfile_height_tmp = tempfile.mkstemp(suffix='.spd', 301 | dir=dem_common.TEMP_PATH) 302 | 303 | print('Classifying ground returns') 304 | spddefheight_cmd = [os.path.join(dem_common.SPDLIB_BIN_PATH,'spddefheight'), 305 | '--interp', 306 | '--in', interpolation, 307 | '-i', in_spd, 308 | '-o', spdfile_height_tmp] 309 | subprocess.check_call(spddefheight_cmd) 310 | 311 | print('Creating CHM') 312 | _spd_to_raster(spdfile_height_tmp, out_chm, 313 | raster_type='CHM', 314 | interpolation=interpolation, 315 | out_raster_format=out_raster_format, 316 | bin_size=bin_size) 317 | 318 | os.close(spdfile_handler) 319 | if keep_spd: 320 | return spdfile_height_tmp 321 | else: 322 | os.remove(spdfile_height_tmp) 323 | return None 324 | 325 | 326 | def las_to_dsm(in_las, out_dsm, 327 | interpolation=dem_common.SPD_DEFAULT_INTERPOLATION, 328 | out_raster_format=dem_common.GDAL_OUTFILE_FORMAT, 329 | bin_size=dem_common.DEFAULT_LIDAR_RES_METRES, 330 | wkt=None, 331 | keep_spd=False): 332 | """ 333 | Create Digital Surface Model (DSM) from a LAS file using SPDLib 334 | 335 | Utility function to convert LAS to SPD and call 336 | 'spd_to_dsm'. 337 | 338 | Arguments: 339 | 340 | * in_las - Input LAS File 341 | * out_dsm - Output DTM file 342 | * interpolation - Interpolation method 343 | * out_raster_format - GDAL format name for output raster (e.g., ENVI) 344 | * bin_size - Bin size for spatial indexing 345 | * wkt - WKT file defining projection (will obtain from LAS if not provided) 346 | * keep_spd - Keep SPD file and return path (default is to remove) 347 | 348 | Returns: 349 | 350 | * Path to SPD file 351 | 352 | """ 353 | 354 | spdfile_handler, spdfile_tmp = tempfile.mkstemp(suffix='.spd', 355 | dir=dem_common.TEMP_PATH) 356 | 357 | convert_las_to_spd(in_las, spdfile_tmp,bin_size=bin_size, wkt=wkt) 358 | spd_to_dsm(spdfile_tmp, out_dsm, 359 | interpolation=interpolation, 360 | out_raster_format=out_raster_format, 361 | bin_size=bin_size) 362 | 363 | os.close(spdfile_handler) 364 | if keep_spd: 365 | return spdfile_tmp 366 | else: 367 | os.remove(spdfile_tmp) 368 | return None 369 | 370 | def las_to_dtm(in_las, out_dtm, 371 | interpolation=dem_common.SPD_DEFAULT_INTERPOLATION, 372 | out_raster_format=dem_common.GDAL_OUTFILE_FORMAT, 373 | bin_size=dem_common.DEFAULT_LIDAR_RES_METRES, 374 | wkt=None, 375 | keep_spd=False): 376 | """ 377 | Create Digital Terrain Model (DTM) from a LAS file using SPDLib 378 | 379 | Utility function to convert LAS to SPD and call 380 | 'spd_to_dtm'. 381 | 382 | Arguments: 383 | 384 | * in_las - Input LAS File 385 | * out_dsm - Output DTM file 386 | * interpolation - Interpolation method 387 | * out_raster_format - GDAL format name for output raster (e.g., ENVI) 388 | * bin_size - Bin size for spatial indexing 389 | * wkt - WKT file defining projection (will obtain from LAS if not provided) 390 | * keep_spd - Keep SPD file and return path (default is to remove). 391 | 392 | Returns: 393 | 394 | * Path to ground classified SPD file 395 | 396 | """ 397 | 398 | spdfile_handler, spdfile_tmp = tempfile.mkstemp(suffix='.spd', dir=dem_common.TEMP_PATH) 399 | 400 | convert_las_to_spd(in_las, spdfile_tmp,bin_size=bin_size, wkt=wkt) 401 | spdfile_grd_tmp = spd_to_dtm(spdfile_tmp, out_dtm, 402 | interpolation=interpolation, 403 | out_raster_format=out_raster_format, 404 | bin_size=bin_size, 405 | keep_spd=keep_spd) 406 | 407 | os.close(spdfile_handler) 408 | os.remove(spdfile_tmp) 409 | 410 | if keep_spd: 411 | return spdfile_grd_tmp 412 | else: 413 | return None 414 | -------------------------------------------------------------------------------- /arsf_dem/get_gdal_drivers.py: -------------------------------------------------------------------------------- 1 | """ 2 | get_gdal_drivers.py 3 | 4 | General library to get driver names and extensions for all supported 5 | GDAL drivers. 6 | 7 | Author: Dan Clewley (dac) 8 | Date: 07/05/2015 9 | 10 | License restrictions: None known. Uses GDAL (MIT/X license) 11 | 12 | Known issues: Some drivers must be used with creation options to get the desired 13 | output. For example .bil and .bsq both use the ENVI driver but must use creation 14 | options to specify the interleave. 15 | 16 | Available functions: 17 | 18 | * GDALDrivers().get_driver_from_ext - Get GDAL driver from extension 19 | * GDALDrivers().get_ext_from_driver - Get extension from GDAL driver name 20 | * GDALDrivers().get_creation_options_from_ext - Get GDAL creation options from extension 21 | * GDALDrivers().get_creation_options_from_driver - Get GDAL creation options driver name 22 | 23 | """ 24 | 25 | from osgeo import gdal 26 | 27 | #: List of Non-GDAL extensions (mostly for ENVI) 28 | NON_GDAL_DRIVER_FROM_EXT = {'bil' : 'ENVI', 29 | 'bsq' : 'ENVI', 30 | 'bip' : 'ENVI', 31 | 'dem' : 'ENVI', 32 | 'raw' : 'ENVI', 33 | 'h5' : 'HDF5', 34 | 'tiff': 'GTiff'} 35 | 36 | #: Preferred creation options for GDAL 37 | GDAL_CREATION_OPTIONS = {'bil' : ['INTERLEAVE=BIL'], 38 | 'bsq' : ['INTERLEAVE=BSQ'], 39 | 'tif' : ['COMPRESS=LZW'], 40 | 'nc' : ['FORMAT=NC4C', 'COMPRESS=DEFLATE']} 41 | 42 | class GDALDrivers(object): 43 | """ 44 | Class to get GDAL drivers or 45 | extensions. 46 | 47 | Gets list of all available GDAL drivers and adds some 48 | additional extensions for existing drives (e.g., for ENVI) 49 | 50 | Example usage:: 51 | 52 | import get_gdal_drivers 53 | get_gdal_drivers.GDALDrivers().get_driver_from_ext('.tif') 54 | get_gdal_drivers.GDALDrivers().get_ext_from_driver('GTiff') 55 | 56 | """ 57 | 58 | def __init__(self): 59 | # Set up two empty dictionaries 60 | # One uses the extension as the key and one the driver 61 | self.gdal_ext_from_driver = {} 62 | self.gdal_driver_from_ext = {} 63 | 64 | # Go through all drivers registered to GDAL 65 | for driver_num in range(gdal.GetDriverCount()): 66 | try: 67 | driver = gdal.GetDriver(driver_num) 68 | driver_ext = driver.GetMetadata()['DMD_EXTENSION'] 69 | 70 | # For ENVI use BSQ (will get this if we don't pass in creation options) 71 | if driver.ShortName == 'ENVI': 72 | driver_ext = 'bsq' 73 | 74 | # If there is no extension, use the driver name in lower case 75 | if driver_ext == '': 76 | driver_ext = driver.ShortName.lower() 77 | 78 | # Add to dictionaries 79 | self.gdal_ext_from_driver[driver.ShortName] = driver_ext 80 | self.gdal_driver_from_ext[driver_ext] = driver.ShortName 81 | 82 | # If they don't have an extension specified - skip 83 | except KeyError: 84 | pass 85 | # Add non-GDAL drivers 86 | self.gdal_driver_from_ext.update(NON_GDAL_DRIVER_FROM_EXT) 87 | 88 | def get_driver_from_ext(self, file_ext): 89 | """ 90 | Get GDAL driver short name from file 91 | extension. 92 | 93 | :param file_ext: File extension (e.g., .bil) 94 | :type file_ext: str 95 | 96 | """ 97 | # Remove '.' if there is one before the extension. 98 | file_ext = file_ext.lstrip('.') 99 | try: 100 | return self.gdal_driver_from_ext[file_ext] 101 | except KeyError: 102 | raise KeyError('The driver for file extension {} could not be found'.format(file_ext)) 103 | 104 | def get_ext_from_driver(self, driver_name): 105 | """ 106 | Get file extension from GDAL short 107 | driver name. 108 | 109 | :param driver_name: Driver Name (e.g., GTiff) 110 | :type driver_name: str 111 | 112 | """ 113 | try: 114 | return '.' + self.gdal_ext_from_driver[driver_name] 115 | except KeyError: 116 | raise KeyError('The extension for driver {} could not be found'.format(driver_name)) 117 | 118 | def get_creation_options_from_ext(self, file_ext): 119 | """ 120 | Get preferred GDAL creation options from file 121 | extension. 122 | 123 | :param file_ext: File extension (e.g., .bil) 124 | :type file_ext: str 125 | 126 | """ 127 | # Remove '.' if there is one before the extension. 128 | file_ext = file_ext.lstrip('.') 129 | try: 130 | return GDAL_CREATION_OPTIONS[file_ext] 131 | except KeyError: 132 | # If there are no creation options defined return an empty list 133 | return [] 134 | 135 | def get_creation_options_from_driver(self, driver_name): 136 | """ 137 | Get preferred GDAL creation options from file 138 | extension. 139 | 140 | :param driver_name: Driver Name (e.g., GTiff) 141 | :type driver_name: str 142 | 143 | """ 144 | file_ext = self.get_ext_from_driver(driver_name) 145 | file_ext = file_ext.lstrip('.') 146 | try: 147 | return GDAL_CREATION_OPTIONS[file_ext] 148 | except KeyError: 149 | # If there are no creation options defined return an empty list 150 | return [] 151 | 152 | def get_all_gdal_extensions(self): 153 | """ 154 | Get all available GDAL extensions 155 | """ 156 | return self.gdal_driver_from_ext.keys() 157 | 158 | def get_all_gdal_drivers(self): 159 | """ 160 | Get all available GDAL drivers 161 | """ 162 | return self.gdal_ext_from_driver.keys() 163 | -------------------------------------------------------------------------------- /data/grass_db_template/UKBNG/PERMANENT/DEFAULT_WIND: -------------------------------------------------------------------------------- 1 | proj: 99 2 | zone: 0 3 | north: 1 4 | south: 0 5 | east: 1 6 | west: 0 7 | cols: 1 8 | rows: 1 9 | e-w resol: 1 10 | n-s resol: 1 11 | top: 1 12 | bottom: 0 13 | cols3: 1 14 | rows3: 1 15 | depths: 1 16 | e-w resol3: 1 17 | n-s resol3: 1 18 | t-b resol: 1 19 | -------------------------------------------------------------------------------- /data/grass_db_template/UKBNG/PERMANENT/MYNAME: -------------------------------------------------------------------------------- 1 | UK BNG 2 | -------------------------------------------------------------------------------- /data/grass_db_template/UKBNG/PERMANENT/PROJ_INFO: -------------------------------------------------------------------------------- 1 | name: Transverse Mercator 2 | datum: osgb36 3 | towgs84: 446.448,-125.157,542.060,0.1502,0.2470,0.8421,-20.4894 4 | proj: tmerc 5 | ellps: airy 6 | a: 6377563.3959999997 7 | es: 0.0066705400 8 | f: 299.3249646000 9 | lat_0: 49.0000000000 10 | lon_0: -2.0000000000 11 | k_0: 0.9996012717 12 | x_0: 400000.0000000000 13 | y_0: -100000.0000000000 14 | -------------------------------------------------------------------------------- /data/grass_db_template/UKBNG/PERMANENT/PROJ_UNITS: -------------------------------------------------------------------------------- 1 | unit: meter 2 | units: meters 3 | meters: 1.0 4 | -------------------------------------------------------------------------------- /data/grass_db_template/UKBNG/PERMANENT/WIND: -------------------------------------------------------------------------------- 1 | proj: 99 2 | zone: 0 3 | north: 1 4 | south: 0 5 | east: 1 6 | west: 0 7 | cols: 1 8 | rows: 1 9 | e-w resol: 1 10 | n-s resol: 1 11 | top: 1 12 | bottom: 0 13 | cols3: 1 14 | rows3: 1 15 | depths: 1 16 | e-w resol3: 1 17 | n-s resol3: 1 18 | t-b resol: 1 19 | -------------------------------------------------------------------------------- /data/grass_db_template/WGS84LL/PERMANENT/DEFAULT_WIND: -------------------------------------------------------------------------------- 1 | proj: 3 2 | zone: 0 3 | north: 12:30N 4 | south: 11:30N 5 | east: 37:30E 6 | west: 36:30E 7 | cols: 1200 8 | rows: 1200 9 | e-w resol: 0:00:03 10 | n-s resol: 0:00:03 11 | top: 1 12 | bottom: 0 13 | cols3: 1 14 | rows3: 1 15 | depths: 1 16 | e-w resol3: 1 17 | n-s resol3: 1 18 | t-b resol: 1 19 | -------------------------------------------------------------------------------- /data/grass_db_template/WGS84LL/PERMANENT/MYNAME: -------------------------------------------------------------------------------- 1 | latitude longitude wgs84 2 | -------------------------------------------------------------------------------- /data/grass_db_template/WGS84LL/PERMANENT/PROJ_INFO: -------------------------------------------------------------------------------- 1 | name: Latitude-Longitude 2 | datum: wgs84 3 | towgs84: 0.000,0.000,0.000 4 | proj: ll 5 | ellps: wgs84 6 | -------------------------------------------------------------------------------- /data/grass_db_template/WGS84LL/PERMANENT/PROJ_UNITS: -------------------------------------------------------------------------------- 1 | unit: degree 2 | units: degrees 3 | meters: 1.0 4 | -------------------------------------------------------------------------------- /data/grass_db_template/WGS84LL/PERMANENT/WIND: -------------------------------------------------------------------------------- 1 | proj: 3 2 | zone: 0 3 | north: 12:30N 4 | south: 11:30N 5 | east: 37:30E 6 | west: 36:30E 7 | cols: 1200 8 | rows: 1200 9 | e-w resol: 0:00:03 10 | n-s resol: 0:00:03 11 | top: 1 12 | bottom: 0 13 | cols3: 1 14 | rows3: 1 15 | depths: 1 16 | e-w resol3: 1 17 | n-s resol3: 1 18 | t-b resol: 1 19 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | scriptsdoc: 42 | python source/create_scripts_rst.py 43 | 44 | clean: 45 | -rm -rf $(BUILDDIR)/* 46 | 47 | html: scriptsdoc 48 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 49 | @echo 50 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 51 | 52 | dirhtml: 53 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 56 | 57 | singlehtml: 58 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 59 | @echo 60 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 61 | 62 | pickle: 63 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 64 | @echo 65 | @echo "Build finished; now you can process the pickle files." 66 | 67 | json: 68 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 69 | @echo 70 | @echo "Build finished; now you can process the JSON files." 71 | 72 | htmlhelp: 73 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 74 | @echo 75 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 76 | ".hhp project file in $(BUILDDIR)/htmlhelp." 77 | 78 | qthelp: 79 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 80 | @echo 81 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 82 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 83 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ARSFDEM.qhcp" 84 | @echo "To view the help file:" 85 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ARSFDEM.qhc" 86 | 87 | devhelp: 88 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 89 | @echo 90 | @echo "Build finished." 91 | @echo "To view the help file:" 92 | @echo "# mkdir -p $$HOME/.local/share/devhelp/ARSFDEM" 93 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ARSFDEM" 94 | @echo "# devhelp" 95 | 96 | epub: 97 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 98 | @echo 99 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 100 | 101 | latex: 102 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 103 | @echo 104 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 105 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 106 | "(use \`make latexpdf' here to do that automatically)." 107 | 108 | latexpdf: 109 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 110 | @echo "Running LaTeX files through pdflatex..." 111 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 112 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 113 | 114 | text: 115 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 116 | @echo 117 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 118 | 119 | man: 120 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 121 | @echo 122 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 123 | 124 | texinfo: 125 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 126 | @echo 127 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 128 | @echo "Run \`make' in that directory to run these through makeinfo" \ 129 | "(use \`make info' here to do that automatically)." 130 | 131 | info: 132 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 133 | @echo "Running Texinfo files through makeinfo..." 134 | make -C $(BUILDDIR)/texinfo info 135 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 136 | 137 | gettext: 138 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 139 | @echo 140 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 141 | 142 | changes: 143 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 144 | @echo 145 | @echo "The overview file is in $(BUILDDIR)/changes." 146 | 147 | linkcheck: 148 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 149 | @echo 150 | @echo "Link check complete; look for any errors in the above output " \ 151 | "or in $(BUILDDIR)/linkcheck/output.txt." 152 | 153 | doctest: 154 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 155 | @echo "Testing of doctests in the sources finished, look at the " \ 156 | "results in $(BUILDDIR)/doctest/output.txt." 157 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\ARSFDEM.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\ARSFDEM.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # ARSF DEM documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Nov 19 10:50:54 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.pngmath'] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'ARSF DEM' 44 | copyright = u'2014, ARSF-DAN' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '0.1' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '0.1' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = [] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | #html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | #html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | #html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'ARSFDEMdoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | latex_elements = { 173 | # The paper size ('letterpaper' or 'a4paper'). 174 | #'papersize': 'letterpaper', 175 | 176 | # The font size ('10pt', '11pt' or '12pt'). 177 | #'pointsize': '10pt', 178 | 179 | # Additional stuff for the LaTeX preamble. 180 | #'preamble': '', 181 | } 182 | 183 | # Grouping the document tree into LaTeX files. List of tuples 184 | # (source start file, target name, title, author, documentclass [howto/manual]). 185 | latex_documents = [ 186 | ('index', 'ARSFDEM.tex', u'ARSF DEM Documentation', 187 | u'ARSF-DAN', 'manual'), 188 | ] 189 | 190 | # The name of an image file (relative to this directory) to place at the top of 191 | # the title page. 192 | #latex_logo = None 193 | 194 | # For "manual" documents, if this is true, then toplevel headings are parts, 195 | # not chapters. 196 | #latex_use_parts = False 197 | 198 | # If true, show page references after internal links. 199 | #latex_show_pagerefs = False 200 | 201 | # If true, show URL addresses after external links. 202 | #latex_show_urls = False 203 | 204 | # Documents to append as an appendix to all manuals. 205 | #latex_appendices = [] 206 | 207 | # If false, no module index is generated. 208 | #latex_domain_indices = True 209 | 210 | 211 | # -- Options for manual page output -------------------------------------------- 212 | 213 | # One entry per manual page. List of tuples 214 | # (source start file, name, description, authors, manual section). 215 | man_pages = [ 216 | ('index', 'arsfdem', u'ARSF DEM Documentation', 217 | [u'ARSF-DAN'], 1) 218 | ] 219 | 220 | # If true, show URL addresses after external links. 221 | #man_show_urls = False 222 | 223 | 224 | # -- Options for Texinfo output ------------------------------------------------ 225 | 226 | # Grouping the document tree into Texinfo files. List of tuples 227 | # (source start file, target name, title, author, 228 | # dir menu entry, description, category) 229 | texinfo_documents = [ 230 | ('index', 'ARSFDEM', u'ARSF DEM Documentation', 231 | u'ARSF-DAN', 'ARSFDEM', 'One line description of project.', 232 | 'Miscellaneous'), 233 | ] 234 | 235 | # Documents to append as an appendix to all manuals. 236 | #texinfo_appendices = [] 237 | 238 | # If false, no module index is generated. 239 | #texinfo_domain_indices = True 240 | 241 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 242 | #texinfo_show_urls = 'footnote' 243 | -------------------------------------------------------------------------------- /doc/source/create_scripts_rst.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Script to generate .rst file for scripts documentation. Runs scripts to get output and 4 | inserts into text. 5 | 6 | Inspired by answer suggesting modifying Makefile here: 7 | 8 | http://stackoverflow.com/questions/7250659/python-code-to-generate-part-of-sphinx-documentation-is-it-possible 9 | 10 | """ 11 | 12 | import subprocess 13 | import os 14 | 15 | def get_command_out(command): 16 | """ Get output from command """ 17 | 18 | out = subprocess.Popen(command,stdin=subprocess.PIPE, 19 | stdout=subprocess.PIPE,stderr=subprocess.PIPE) 20 | 21 | (stdout, stderr) = out.communicate() 22 | 23 | out_tabs = '' 24 | 25 | for line in stdout.decode().split('\n'): 26 | out_tabs += ' {}\n'.format(line) 27 | 28 | return out_tabs 29 | 30 | outfile = os.path.join(os.path.split(__file__)[0],'scripts.rst') 31 | 32 | # Run commands and get output 33 | create_apl_dem_out = get_command_out(['create_apl_dem.py','-h']) 34 | create_dem_from_lidar_out = get_command_out(['create_dem_from_lidar.py','-h']) 35 | las_to_dsm_out = get_command_out(['las_to_dsm.py','-h']) 36 | las_to_dtm_out = get_command_out(['las_to_dtm.py','-h']) 37 | las_to_intensity_out = get_command_out(['las_to_intensity.py','-h']) 38 | mosaic_dem_tiles_out = get_command_out(['mosaic_dem_tiles.py','-h']) 39 | load_lidar_to_grass_out = get_command_out(['load_lidar_to_grass.py','-h']) 40 | spdlib_create_dem_from_las_out = get_command_out( 41 | ['spdlib_create_dems_from_las.py', '-h']) 42 | 43 | scripts_text = ''' 44 | 45 | ARSF DEM Scripts 46 | ================ 47 | 48 | The following command line tools are provided by ARSF DEM. 49 | 50 | Note under Windows, there is no need to type the '.py' at the end of the scripts. Batch files have been created to run the Python scripts, which don't need an extension to be provided. 51 | 52 | create_apl_dem 53 | ------------------- 54 | 55 | .. code-block:: bash 56 | 57 | {} 58 | 59 | create_dem_from_lidar 60 | ------------------------- 61 | 62 | .. code-block:: bash 63 | 64 | {} 65 | 66 | las_to_dsm 67 | -------------- 68 | 69 | .. code-block:: bash 70 | 71 | {} 72 | 73 | las_to_dtm 74 | -------------- 75 | 76 | .. code-block:: bash 77 | 78 | {} 79 | 80 | las_to_intensity 81 | ------------------ 82 | 83 | .. code-block:: bash 84 | 85 | {} 86 | 87 | spdlib_create_dem_from_las 88 | ---------------------------- 89 | 90 | .. code-block:: bash 91 | 92 | {} 93 | 94 | mosaic_dem_tiles 95 | ------------------ 96 | 97 | .. code-block:: bash 98 | 99 | {} 100 | 101 | load_lidar_to_grass 102 | --------------------- 103 | 104 | .. code-block:: bash 105 | 106 | {} 107 | 108 | '''.format(create_apl_dem_out, 109 | create_dem_from_lidar_out, 110 | las_to_dsm_out, 111 | las_to_dtm_out, 112 | las_to_intensity_out, 113 | spdlib_create_dem_from_las_out, 114 | mosaic_dem_tiles_out, 115 | load_lidar_to_grass_out) 116 | 117 | f = open(outfile,'w') 118 | f.write(scripts_text) 119 | f.close() 120 | -------------------------------------------------------------------------------- /doc/source/dem_common.rst: -------------------------------------------------------------------------------- 1 | DEM Common 2 | =========== 3 | 4 | .. automodule:: arsf_dem.dem_common 5 | :members: 6 | :undoc-members: 7 | 8 | * :ref:`genindex` 9 | * :ref:`modindex` 10 | * :ref:`search` 11 | -------------------------------------------------------------------------------- /doc/source/dem_lidar.rst: -------------------------------------------------------------------------------- 1 | DEM LiDAR 2 | =========== 3 | 4 | .. automodule:: arsf_dem.dem_lidar 5 | :members: 6 | :undoc-members: 7 | 8 | LiDAR Utilities 9 | ---------------- 10 | 11 | .. automodule:: arsf_dem.dem_lidar.lidar_utilities 12 | :members: 13 | :undoc-members: 14 | 15 | GRASS LiDAR 16 | ------------ 17 | 18 | .. automodule:: arsf_dem.dem_lidar.grass_lidar 19 | :members: 20 | :undoc-members: 21 | 22 | ASCII LiDAR 23 | ------------ 24 | 25 | .. automodule:: arsf_dem.dem_lidar.ascii_lidar 26 | :members: 27 | :undoc-members: 28 | 29 | LAStools LiDAR 30 | -------------- 31 | 32 | .. automodule:: arsf_dem.dem_lidar.lastools_lidar 33 | :members: 34 | :undoc-members: 35 | 36 | SPDLib LiDAR 37 | -------------- 38 | 39 | .. automodule:: arsf_dem.dem_lidar.spdlib_lidar 40 | :members: 41 | :undoc-members: 42 | 43 | FUSION LiDAR 44 | -------------- 45 | 46 | .. automodule:: arsf_dem.dem_lidar.fusion_lidar 47 | :members: 48 | :undoc-members: 49 | 50 | points2grid LiDAR 51 | -------------- 52 | 53 | .. automodule:: arsf_dem.dem_lidar.points2grid_lidar 54 | :members: 55 | :undoc-members: 56 | 57 | Laspy LiDAR 58 | -------------- 59 | 60 | .. automodule:: arsf_dem.dem_lidar.laspy_lidar 61 | :members: 62 | :undoc-members: 63 | 64 | * :ref:`genindex` 65 | * :ref:`modindex` 66 | * :ref:`search` 67 | -------------------------------------------------------------------------------- /doc/source/dem_nav_utilities.rst: -------------------------------------------------------------------------------- 1 | DEM Navigation Utilities 2 | ========================== 3 | 4 | .. automodule:: arsf_dem.dem_nav_utilities 5 | :members: 6 | :undoc-members: 7 | 8 | * :ref:`genindex` 9 | * :ref:`modindex` 10 | * :ref:`search` 11 | -------------------------------------------------------------------------------- /doc/source/dem_utilities.rst: -------------------------------------------------------------------------------- 1 | DEM Utilities 2 | ============== 3 | 4 | .. automodule:: arsf_dem.dem_utilities 5 | :members: 6 | :undoc-members: 7 | 8 | * :ref:`genindex` 9 | * :ref:`modindex` 10 | * :ref:`search` 11 | -------------------------------------------------------------------------------- /doc/source/figures/EUFAR11_02-2011-187_dsm_20m_contours.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmlrsg/arsf_dem_scripts/4b4c76de59fc5c8fa17bad643ca85411afaac22d/doc/source/figures/EUFAR11_02-2011-187_dsm_20m_contours.png -------------------------------------------------------------------------------- /doc/source/figures/dtm_dsm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmlrsg/arsf_dem_scripts/4b4c76de59fc5c8fa17bad643ca85411afaac22d/doc/source/figures/dtm_dsm.png -------------------------------------------------------------------------------- /doc/source/figures/grass_dsm_idw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmlrsg/arsf_dem_scripts/4b4c76de59fc5c8fa17bad643ca85411afaac22d/doc/source/figures/grass_dsm_idw.png -------------------------------------------------------------------------------- /doc/source/figures/grass_dsm_with_holes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmlrsg/arsf_dem_scripts/4b4c76de59fc5c8fa17bad643ca85411afaac22d/doc/source/figures/grass_dsm_with_holes.png -------------------------------------------------------------------------------- /doc/source/figures/lag_profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmlrsg/arsf_dem_scripts/4b4c76de59fc5c8fa17bad643ca85411afaac22d/doc/source/figures/lag_profile.png -------------------------------------------------------------------------------- /doc/source/figures/plasio_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmlrsg/arsf_dem_scripts/4b4c76de59fc5c8fa17bad643ca85411afaac22d/doc/source/figures/plasio_screenshot.png -------------------------------------------------------------------------------- /doc/source/grass_library.rst: -------------------------------------------------------------------------------- 1 | GRASS Library 2 | ============== 3 | 4 | .. automodule:: arsf_dem.grass_library 5 | :members: 6 | :undoc-members: 7 | 8 | * :ref:`genindex` 9 | * :ref:`modindex` 10 | * :ref:`search` 11 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | .. ARSF DEM documentation master file, created by 2 | sphinx-quickstart on Wed Nov 19 10:50:54 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | ARSF DEM Scripts 7 | ================= 8 | 9 | A library and set of utility scripts for working with Digital Elevation Models. 10 | 11 | Tutorials 12 | --------- 13 | .. toctree:: 14 | :maxdepth: 2 15 | 16 | installation 17 | 18 | Scripts 19 | ------- 20 | 21 | .. toctree:: 22 | :maxdepth: 2 23 | 24 | scripts 25 | 26 | 27 | Available modules 28 | --------------------- 29 | 30 | .. toctree:: 31 | :maxdepth: 1 32 | 33 | dem_utilities 34 | dem_nav_utilities 35 | dem_lidar 36 | dem_common 37 | 38 | 39 | -------------------------------------------------------------------------------- /doc/source/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | For the scripts to work a number of other software packages, coordinate transform and offset files are also required. 5 | 6 | Required Software: 7 | 8 | * Python (Needs to be the same version used by GRASS, currently 2.7) 9 | * GRASS 10 | * LAStools (free/paid) - Free tools required for importing LAS files, paid tools can be used to create DTMs/DSMs. 11 | 12 | Optional Software: 13 | 14 | * SPDLib - Can be used to create DTMs/DSMs. 15 | * laspy - Used to get bounds of LAS file (if not available will use ASCII). 16 | 17 | Windows 18 | ~~~~~~~~ 19 | 20 | The recommended way to install GRASS is using the OSGeo4W installer: 21 | 22 | 1. Download the OSGeo4W installer from: http://trac.osgeo.org/osgeo4w/ 23 | 2. Select Advanced install and select 'QGIS Full' and 'GRASS' from Desktop applications, choose the standard install location of C:\\OSGeo4W 24 | 3. Download the arsf_dem library, open a command prompt and navigate to the directory the code was downloaded to. Then run: 25 | 26 | .. code-block:: bash 27 | 28 | python setup.py 29 | python setup.py install 30 | 31 | For more information on installing Python modules see https://docs.python.org/2/install/. 32 | 33 | 4. Download LAStools from http://lastools.org, unzip the folder and copy the folder 'LAStools' to the C drive. 34 | 5. (Optional) Download Windows binaries of SPDLib from https://bitbucket.org/petebunting/spdlib/downloads and copy the folder 'spdlib' to the C drive. 35 | 36 | Linux 37 | ~~~~~~ 38 | 39 | 1. Install GRASS from the package manager using: 40 | 41 | .. code-block:: bash 42 | 43 | sudo yum install grass 44 | 45 | if you are using a Red Hat derivative e.g., Fedora or CentOS. 46 | For a Debian derivative (e.g., Ubuntu) use: 47 | 48 | .. code-block:: bash 49 | 50 | sudo apt-get install grass 51 | 52 | 2. Install the arsf_dem library using: 53 | 54 | .. code-block:: bash 55 | 56 | python setup.py 57 | sudo python setup.py install 58 | 59 | This will install the scripts to /usr/local/bin, so they will be available on the 60 | main path. 61 | 62 | 3. Download the ARSF fork of LAStools from from https://github.com/arsf/LAStools and install using: 63 | 64 | .. code-block:: bash 65 | 66 | make 67 | sudo make install 68 | 69 | 70 | OS X 71 | ~~~~~ 72 | 73 | 1. Follow the instructions to install GRASS from http://www.kyngchaos.com/software/grass 74 | 75 | 2. Install the arsf_dem library using: 76 | 77 | .. code-block:: bash 78 | 79 | python setup.py 80 | sudo python setup.py install 81 | 82 | 3. Download the ARSF fork of LAStools from from https://github.com/arsf/LAStools and install using: 83 | 84 | .. code-block:: bash 85 | 86 | make 87 | sudo make install 88 | 89 | Configuration 90 | --------------- 91 | 92 | There are a number of variables used by the DEM scripts to set default parameters, 93 | locations of files (e.g., DEMs, separation files). These can be overridden by setting 94 | them in 'arsf_dem.cfg', by default this file is installed to the same location as the 95 | Python library. By placing a copy in the home directory (`~\.arsf_dem.cfg`) the settings 96 | can be changed for a particular user. They can also be changed by placing a copy 97 | of arsf_dem.cfg in the working directory. 98 | 99 | 100 | -------------------------------------------------------------------------------- /doc/source/tutorial_lidar.md: -------------------------------------------------------------------------------- 1 | # NERC-ARF Workshop - Discrete LiDAR Practical # 2 | 3 | This worksheet is for Linux, it is identical to the [Windows](tutorial_lidar_windows.md) version but scripts have the extension `.py` and lines are broken by `\` rather than `^`. 4 | 5 | ## Datasets and computing set up ## 6 | 7 | This tutorial uses the NERC-ARF DEM Scripts, developed by NERC-ARF-DAN and available to download from 8 | [https://github.com/pmlrsg/arsf_dem_scripts](https://github.com/pmlrsg/arsf_dem_scripts). 9 | You will need to have these scripts installed before starting the tutorial. 10 | 11 | LiDAR data from two flights will be used for this tutorial. If you are using a NERC-ARF computer or the Virtual Machine these data are under: 12 | ``` 13 | ~/nerc-arf-workshop/lidar_practical 14 | ``` 15 | If you are using your own machine you will need to download the data from [NEODC](http://neodc.nerc.ac.uk/) [^1]. 16 | 17 | ### EUFAR11/02 187 ### 18 | 19 | Data acquired over Svalbard on day 187/2011 ([NEODC Link](http://browse.ceda.ac.uk/browse/neodc/arsf/2011/EUFAR11_02/EUFAR11_02-2011_187_SVALBD_PGLACIAL/LiDAR) [^2]) 20 | For this tutorial LAS 1.0 files 1 - 10 will be used. The ASTER DEM supplied with the hyperspectral data will also be used. 21 | 22 | ### GB13/08 217 ### 23 | 24 | Data acquired over Montrose Bay on day 217/2014 ([NEODC Link](http://browse.ceda.ac.uk/browse/neodc/arsf/2014/GB13_08/GB13_08-2014_217_Montrose_Bay/LiDAR) [^3]) 25 | For this tutorial LAS 1.2 file 2 will be used. 26 | 27 | ## View Point Cloud ## 28 | 29 | NERC-ARF deliver LiDAR data as point clouds. Before starting analysis open the point cloud using the online plas.io viewer or the NERC-ARF Lidar Analysis GUI ([LAG](http://arsf.github.io/lag/) [^4]) viewer. 30 | 31 | 32 | ### plas.io ### 33 | 34 | 1) Open [http://plas.io/](http://plas.io/) in your browser (Chrome is recommended). 35 | 36 | 2) Click browse and navigate to one of the practical LAS files to display 37 | 38 | 3) You can use the mouse to rotate and pan around the dataset. 39 | 40 | 4) Zoom in close (scroll wheel) to see the individual points. 41 | 42 | ![A point cloud displayed using plas.io](figures/plasio_screenshot.png) 43 | 44 | ### LAG ### 45 | 46 | 1) Open a terminal window (look for a black rectangular icon with a white frame, normally under the 'Applications' menu) and type `lag` to open LAG. 47 | 48 | 2) Click the 'Open' button and select a LAS file. 49 | 50 | 3) Using the 'Profile' tool draw a line along the dataset in the 'LAG Overview' window and press the spacebar to display a profile. 51 | 52 | 4) Select 'Colour by' and 'Classification' in both windows to display points flagged as noise. 53 | 54 | ![A point cloud displayed in LAS](figures/lag_profile.png) 55 | 56 | ## Create a simple DSM using the command line utility ## 57 | 58 | The first task is to create a simple Digital Surface Model (DSM) from the point cloud. A DSM is a form of a Digital Elevation Model (DEM) which is a raster or gridded dataset (image) where the value of each pixel represents the elevation of that pixel. Specifically a DSM represents the 'surface' or 'top-of-canopy' elevation and includes the heights of buildings and vegetation. 59 | 60 | To create a simple DSM we will use the `create_dem_from_lidar.py` tool. 61 | This tool only uses first-return points, which assumes if there are multiple returns received (common for vegetation) the first return represents the top. Points flagged as noise (class 7) are also removed as part of the process. 62 | As each flight comprises multiple lines after generating a DSM from each point cloud the lines are mosaiced together to create a single file. 63 | 64 | 1) Open a terminal window (look for a black rectangular icon with a white frame, normally under the 'Applications' menu). 65 | 66 | 2) Navigate to the directory the data are stored using: 67 | ``` 68 | cd ~/nerc-arf-workshop/lidar_practical/EUFAR11_02-187 69 | ``` 70 | (note if you are using your own machine you will need to input a different location). 71 | 72 | 3) Run the following command to create a DSM using only LiDAR data: 73 | 74 | ```bash 75 | create_dem_from_lidar.py --in_projection UTM33N \ 76 | --outdem EUFAR11_02-2011-187_dsm.dem \ 77 | las1.0/LDR-EUFAR11_02-2011-187-01.LAS \ 78 | las1.0/LDR-EUFAR11_02-2011-187-02.LAS 79 | ``` 80 | 81 | This will create a DSM from files 1 and 2. 82 | Note to use all lines in the folder just provide the directory (las1.0) rather than individual lines. It is recommended you only use two for the tutorial so it runs through quicker. 83 | The flag `--in_projection` is used to specify the projection of the LAS files, which is UTM33N. 84 | The resolution can be specified using `--resolution `, where you replace `` with the required resolution in metres. If this is not supplied the default resolution (2 m) will be used 85 | Note that the command options (starting with `--`) do not need to be written in any specific order. 86 | 87 | As part of the process of creating a DSM, only points which are the 88 | first return are kept and any points flagged as noise (class 7) are 89 | dropped. 90 | 91 | Using the extension 'dem' provides an ENVI format file (binary with text 92 | header), it is also possible to export other formats (e.g., GeoTiff) by 93 | changing the extension. 94 | 95 | It will take a couple of minutes to run. While it is running it will output to 96 | the terminal showing a temporary ASCII file being created from 97 | the LAS file using the 98 | [las2txt](http://www.cs.unc.edu/~isenburg/lastools/download/las2txt_README.txt) [^5] 99 | command, dropping class 7 and keeping only the first return. 100 | 101 | The command used for this is: 102 | ```bash 103 | las2txt -parse txyzicrna -sep space \ 104 | -drop_class 7 -first_only \ 105 | -i las1.0/LDR-EUFAR11_02-2011-187-01.LAS \ 106 | -o LDR-EUFAR11_02-2011-187-01.txt 107 | ``` 108 | Where `txyzicrna` specifies the attribute stored in each column: 109 | 110 | 1. Time 111 | 2. x coordinate 112 | 3. y coordinate 113 | 4. Elevation 114 | 5. Intensity 115 | 6. Classification 116 | 7. Return number 117 | 8. Number of returns 118 | 10. Scan angle rank 119 | 120 | This ASCII file is then imported into [GRASS](https://grass.osgeo.org/) [^6] using 121 | [r.in.xyz](http://grass.osgeo.org/grass64/manuals/r.in.xyz.html) [^7] and 122 | gridded at the specified resolution taking the mean elevation of all 123 | points in each cell. Once all files have been imported into GRASS and 124 | gridded a mosaic is generated and exported. 125 | 126 | Once the command has run through the file can be opened in [TuiView](http://tuiview.org/) [^8] (if installed) using: 127 | ``` 128 | tuiview EUFAR11_02-2011-187_dsm.dem 129 | ``` 130 | or another packages such as QGIS or ArcMap. 131 | 132 | 4) To help visualise the data you can create a hillshade image using the [gdaldem](http://www.gdal.org/gdaldem.html) [^9] command: 133 | 134 | ```bash 135 | gdaldem hillshade \ 136 | EUFAR11_02-2011-187_dsm.dem \ 137 | EUFAR11_02-2011-187_dsm_hillshade.tif 138 | ``` 139 | 140 | 5) You can also create contour lines using the [gdalccontour](http://www.gdal.org/gdal_contour.html) [^10] command: 141 | 142 | ```bash 143 | gdal_contour -i 20 -a elevation \ 144 | EUFAR11_02-2011-187_dsm.dem \ 145 | EUFAR11_02-2011-187_dsm_20m_contours.shp 146 | ``` 147 | 148 | To open the hillshade image with contour lines overlain in TuiView use: 149 | 150 | ```bash 151 | tuiview -v EUFAR11_02-2011-187_dsm_20m_contours.shp \ 152 | EUFAR11_02-2011-187_dsm_hillshade.tif 153 | ``` 154 | 155 | Click 'OK' on the default layer name in the dialogue which pops up. 156 | 157 | With all flight lines this will look like the map below: 158 | 159 | ![Hillshade image created from LiDAR data over Svalbard with contour lines overlain.](figures/EUFAR11_02-2011-187_dsm_20m_contours.png) 160 | 161 | ## Create a LiDAR / ASTER DSM for use in APL ## 162 | 163 | To create a DSM from the LiDAR, suitable for using in the Airborne 164 | Processing Library (APL) to geocorrect hyperspectral data, some extra 165 | consideration are needed: 166 | 167 | * The DSM needs to use WGS-84 Lat/Long projection and heights need to be relative to the WGS-84 ellipsoid. 168 | * Areas of no-data need to be filled (e.g., with a coarser resolution DEM). 169 | * The format needs to be ENVI Band Interleaved by Line (BIL) or Band Sequential (BSQ). 170 | 171 | The same `create_dem_from_lidar.py` script can be used to generate a DSM 172 | for use in APL, by setting some options: 173 | 174 | ```bash 175 | create_dem_from_lidar.py --in_projection UTM33N \ 176 | --out_projection WGS84LL \ 177 | --lidar_bounds \ 178 | --demmosaic dem/EUFAR11_02-2011-187_ASTER.dem \ 179 | --outdem EUFAR11_02-2011-187-lidar_ASTER-wgs84_latlong.dem \ 180 | las1.0/LDR-EUFAR11_02-2011-187-01.LAS \ 181 | las1.0/LDR-EUFAR11_02-2011-187-02.LAS 182 | ``` 183 | As previously only two lines are specified to reduce processing time. 184 | 185 | This will create a DSM mosaic from the LAS files, reproject to WGS84 Lat/Long and patch with 'EUFAR11\_02-2011-187\_ASTER.dem' (as provided with the NERC-ARF hyperspectral delivery), cropped to the bounding box of all LiDAR data plus a buffer of 2 km. This assumes the vertical datum of the DEM mosaic is the same as that required for the output projection. 186 | 187 | ## Create DSM / DTM using additional programs ## 188 | 189 | So far only a simple DSM has been created by taking the average of all first-return points within a pixel. Pixels which do not contain a return are left as no-data. There are more advanced methods of interpolation in GRASS (which we will come onto later), there are also other programs which can be used to produce a DSM. These can be accessed by two utility programs, described below. For this section the Montrose Bay data will be used, navigate to the directory the data are stored in using: 190 | ``` 191 | cd ~/nerc-arf-workshop/lidar_practical/GB13_08-217 192 | ``` 193 | (note if you are using your own machine you will need to input a different location). 194 | 195 | For the workshop we will use a subset, you can create this using the 196 | [las2las](http://www.cs.unc.edu/~isenburg/lastools/download/las2las_README.txt) [^11] 197 | command: 198 | ```bash 199 | las2las -keep_xy 374200 764500 375000 765500 \ 200 | -i las1.2/LDR-GB13_08-2014-217-02.LAS \ 201 | -o las1.2/LDR-GB13_08-2014-217-02_subset.LAS 202 | ``` 203 | In addition to subsetting LAS files the `las2las` command can be used to apply other filters to LAS files. 204 | 205 | ### Digital Surface Model (DSM) ### 206 | 207 | To create a DSM using GRASS the following is used 208 | 209 | ```bash 210 | las_to_dsm.py -o LDR-GB13_08-2014-217-02_subset_dsm_grass.tif \ 211 | --projection UKBNG \ 212 | --method GRASS \ 213 | las1.2/LDR-GB13_08-2014-217-02_subset.LAS 214 | ``` 215 | 216 | The format of the output file is set using the extension, using '.tif' will create a GeoTIFF. 217 | 218 | Other programs such as [LAStools](http://rapidlasso.com/lastools/) [^12], [SPDLib](http://spdlib.org) [^13], [FUSION](http://forsys.cfr.washington.edu/fusion/fusion_overview.html) [^14] and [points2grid](https://github.com/CRREL/points2grid) [^15] can be used, if they have been installed, by setting the --method flag. Note, unlike the other methods, the tools required to create a DEM in LAStools are not free and binaries are only provided for Windows (although work on Linux through Wine). It is possible to run without a license for non-profit and personal work but they will introduce noise and artifacts (such as black diagonal lines) in the data. For more details see [LAStools License](http://www.cs.unc.edu/~isenburg/lastools/LICENSE.txt) [16^] 219 | 220 | If you have installed SPDLib or the non-free LAStools (running through wine on Linux) try running the same command but setting `--method SPDLib` or `--method LAStools`. For example: 221 | 222 | ```bash 223 | las_to_dsm.py -o LDR-GB13_08-2014-217-02_subset_dsm_spdlib.tif \ 224 | --projection UKBNG \ 225 | --method SPDLib \ 226 | las1.2/LDR-GB13_08-2014-217-02_subset.LAS 227 | ``` 228 | 229 | ### Digital Terrain Model (DTM) ### 230 | 231 | In addition to producing a DSM another common product from LiDAR data is a Digital Terrain Model (DTM), this represents the 'bare-earth', that is the elevation excluding buildings and vegetation. Producing a DTM is more complicated than a DSM, a first order approximation is to take only last returns, which assumes multiple returns are received from a pulse and that the last is from the ground. However, many last returns are not from the ground for example dense vegetation where the last return does not reach the ground or buildings where there are not multiple returns. Producing an accurate DTM first requires ground returns to be selected from the point cloud and gaps where there are no ground returns (e.g., buildings) to be interpolated. 232 | 233 | Packages such as [LAStools](http://rapidlasso.com/lastools/) [^12], [SPDLib](http://spdlib.org) [^13] and [FUSION](http://forsys.cfr.washington.edu/fusion/fusion_overview.html) [^14] have more advanced methods for classifying ground returns (see their 234 | respective manuals for more details). 235 | 236 | To create a DTM the `las_to_dtm.py` command is used: 237 | 238 | ```bash 239 | las_to_dtm.py -o LDR-GB13_08-2014-217-02_subset_dtm_spdlib.tif \ 240 | --projection UKBNG \ 241 | --method SPDLib \ 242 | las1.2/LDR-GB13_08-2014-217-02_subset.LAS 243 | ``` 244 | 245 | As shown earlier the `gdaldem` command can be used to produce hillshade images for visualisation as shown below. 246 | 247 | ![Comparison of Digital Terrain Model (DTM) and Digital Surface Model (DSM)](figures/dtm_dsm.png) 248 | 249 | Depending on the cover, the default classification and interpolation parameters for each program used by las\_to\_dtm.py may not provide the best possible results. 250 | In these cases it is recommended you access the programs directly, as this will provide more control over the available options. 251 | 252 | ## More Advanced Manipulation in GRASS ## 253 | 254 | Outside the DEM scripts it is possible to perform more advanced manipulation of LiDAR data in GRASS. A good tutorial is available on the GRASS wiki: https://grasswiki.osgeo.org/wiki/LIDAR. 255 | 256 | ```bash 257 | load_lidar_to_grass.py --projection UKBNG \ 258 | --rastertype DSM \ 259 | --resolution 1 \ 260 | las1.2/LDR-GB13_08-2014-217-02_subset.LAS 261 | ``` 262 | 263 | This will return: 264 | ``` 265 | Loaded the following files: 266 | lidar_ppScIf.dem 267 | To GRASS database: /tmp/grassdb-12869-114950/UKBNG/PERMANENT 268 | ``` 269 | (the specific names will be different when you run it) 270 | 271 | Open GRASS with the database created using: 272 | ``` 273 | grass /tmp/grassdb-12869-114950/UKBNG/PERMANENT 274 | ``` 275 | Within GRASS type: 276 | ``` 277 | g.gui 278 | ``` 279 | To open the GRASS interface. Within this use File -> Map Display -> Add Raster to display the file. 280 | Note there are gaps in the DSM where pixels do not have any points falling within them, as shown below. 281 | 282 | ![Dataset imported into GRASS prior to interpolation](figures/grass_dsm_with_holes.png) 283 | 284 | To fill in some of these gaps we can interpolate between them using Inverse Distance 285 | Weighted interpolation (IDW): 286 | ``` 287 | r.surf.idw input=INPUTLAYER output=OUTPUTLAYERNAME 288 | ``` 289 | 290 | Changing 'INPUTLAYER' to the name of the file in GRASS and 'OUTPUTLAYERNAME' to name you wish to use for the interpolated raster. 291 | 292 | 293 | 294 | Load this file into GRASS and compare with the original. 295 | 296 | ![Dataset in GRASS after Inverse Distance Weighted (IDW) interpolation.](figures/grass_dsm_idw.png) 297 | 298 | [^1]: NEODC [http://neodc.nerc.ac.uk/](http://neodc.nerc.ac.uk/) 299 | [^2]: EUFAR11/02 NEODC link [http://browse.ceda.ac.uk/browse/neodc/arsf/2011/EUFAR11_02/EUFAR11_02-2011_187_SVALBD_PGLACIAL/LiDAR](http://browse.ceda.ac.uk/browse/neodc/arsf/2011/EUFAR11_02/EUFAR11_02-2011_187_SVALBD_PGLACIAL/LiDAR) 300 | [^3]: GB13/08 NEODC link [http://browse.ceda.ac.uk/browse/neodc/arsf/2014/GB13_08/GB13_08-2014_217_Montrose_Bay/LiDAR](http://browse.ceda.ac.uk/browse/neodc/arsf/2014/GB13_08/GB13_08-2014_217_Montrose_Bay/LiDAR) 301 | [^4]: LAG [http://arsf.github.io/lag/](http://arsf.github.io/lag/) 302 | [^5]: las2txt documentation [http://www.cs.unc.edu/~isenburg/lastools/download/las2txt_README.txt](http://www.cs.unc.edu/~isenburg/lastools/download/las2txt_README.txt) 303 | [^6]: GRASS [https://grass.osgeo.org/](https://grass.osgeo.org/) 304 | [^7]: r.in.xyz [http://grass.osgeo.org/grass64/manuals/r.in.xyz.html](http://grass.osgeo.org/grass64/manuals/r.in.xyz.html) 305 | [^8]: TuiView [http://tuiview.org/](http://tuiview.org/) 306 | [^9]: gdaldem [http://www.gdal.org/gdaldem.html](http://www.gdal.org/gdaldem.html) 307 | [^10]: gdal_contour [http://www.gdal.org/gdal_contour.html](http://www.gdal.org/gdal_contour.html) 308 | [^11]: las2las documentation [http://www.cs.unc.edu/~isenburg/lastools/download/las2las_README.txt](http://www.cs.unc.edu/~isenburg/lastools/download/las2las_README.txt) 309 | [^12]: LAStools [http://rapidlasso.com/lastools/](http://rapidlasso.com/lastools/) 310 | [^13]: SPDLib [http://spdlib.org](http://spdlib.org) 311 | [^14]: Fusion [http://forsys.cfr.washington.edu/fusion/fusion_overview.html](http://forsys.cfr.washington.edu/fusion/fusion_overview.html) 312 | [^15]: points2grid [https://github.com/CRREL/points2grid](https://github.com/CRREL/points2grid) 313 | [^16]: LAStools license [http://www.cs.unc.edu/~isenburg/lastools/LICENSE.txt](http://www.cs.unc.edu/~isenburg/lastools/LICENSE.txt) 314 | -------------------------------------------------------------------------------- /doc/source/tutorial_lidar_windows.md: -------------------------------------------------------------------------------- 1 | # NERC-ARF Workshop - Discrete LiDAR Practical # 2 | 3 | This worksheet is for Windows, it is identical to the [Linux](tutorial_lidar.md) version but scripts don't have the extension `.py` and lines are broken by `^` rather than `\`. 4 | 5 | ## Datasets and computing set up ## 6 | 7 | This tutorial uses the NERC-ARF DEM Scripts, developed by NERC-ARF-DAN and available to download from 8 | [https://github.com/pmlrsg/arsf_dem_scripts](https://github.com/pmlrsg/arsf_dem_scripts). 9 | You will need to have these scripts installed before starting the tutorial. 10 | 11 | You will also need to download the data from [NEODC](http://neodc.nerc.ac.uk/) [^1]. 12 | 13 | ### EUFAR11/02 187 ### 14 | 15 | Data acquired over Svalbard on day 187/2011 ([NEODC Link](http://browse.ceda.ac.uk/browse/neodc/arsf/2011/EUFAR11_02/EUFAR11_02-2011_187_SVALBD_PGLACIAL/LiDAR) [^2]) 16 | For this tutorial LAS 1.0 files 1 - 10 will be used. The ASTER DEM supplied with the hyperspectral data will also be used. 17 | 18 | ### GB13/08 217 ### 19 | 20 | Data acquired over Montrose Bay on day 217/2014 ([NEODC Link](http://browse.ceda.ac.uk/browse/neodc/arsf/2014/GB13_08/GB13_08-2014_217_Montrose_Bay/LiDAR) [^3]) 21 | For this tutorial LAS 1.2 file 2 will be used. 22 | 23 | ## View Point Cloud ## 24 | 25 | NERC-ARF deliver LiDAR data as point clouds. Before starting analysis open the point cloud using the online plas.io viewer or the NERC-ARF Lidar Analysis GUI ([LAG](http://arsf.github.io/lag/) [^4]) viewer. 26 | 27 | 28 | 29 | ### plas.io ### 30 | 31 | 1) Open [http://plas.io/](http://plas.io/) in your browser (Chrome is recommended). 32 | 33 | 2) Click browse and navigate to one of the practical LAS files to display 34 | 35 | 3) You can use the mouse to rotate and pan around the dataset. 36 | 37 | 4) Zoom in close (scroll wheel) to see the individual points. 38 | 39 | ![A point cloud displayed using plas.io](figures/plasio_screenshot.png) 40 | 41 | 42 | ## Create a simple DSM using the command line utility ## 43 | 44 | The first task is to create a simple Digital Surface Model (DSM) from the point cloud. A DSM is a form of a Digital Elevation Model (DEM) which is a raster or gridded dataset (image) where the value of each pixel represents the elevation of that pixel. Specifically a DSM represents the 'surface' or 'top-of-can' elevation and includes the heights of buildings and vegetation. 45 | 46 | To create a simple DSM we will use the `create_dem_from_lidar` tool. 47 | This tool only uses first-return points, which assumes if there are multiple returns received (common for vegetation) the first return represents the top. Points flagged as noise (class 7) are also removed as part of the process. 48 | As each flight comprises multiple lines after generating a DSM from each point cloud the lines are mosaiced together to create a single file. 49 | 50 | 1) Open a command prompt 51 | 52 | 2) Navigate to the directory the data are stored using: 53 | ``` 54 | cd C:\nerc-arf-workshop\lidar_practical\EUFAR11_02-187 55 | ``` 56 | (changing the location as required). 57 | 58 | 3) Run the following command to create a DSM using only LiDAR data: 59 | 60 | ```bash 61 | create_dem_from_lidar --in_projection UTM33N ^ 62 | --outdem EUFAR11_02-2011-187_dsm.dem ^ 63 | las1.0\LDR-EUFAR11_02-2011-187-01.LAS ^ 64 | las1.0\LDR-EUFAR11_02-2011-187-02.LAS 65 | ``` 66 | 67 | This will create a DSM from files 1 and 2. 68 | Note to use all lines in the folder just provide the directory (las1.0) rather than individual lines. It is recommended you only use two for the tutorial so it runs through quicker. 69 | The flag `--in_projection` is used to specify the projection of the LAS files, which is UTM33N. 70 | The resolution can be specified using `--resolution `, where you replace `` with the required resolution in metres. If this is not supplied the default resolution (2 m) will be used 71 | Note that the command options (starting with `--`) do not need to be written in any specific order. 72 | 73 | As part of the process of creating a DSM, only points which are the 74 | first return are kept and any points flagged as noise (class 7) are 75 | dropped. 76 | 77 | Using the extension 'dem' provides an ENVI format file (binary with text 78 | header), it is also possible to export other formats (e.g., GeoTiff) by 79 | changing the extension. 80 | 81 | It will take a couple of minutes to run. While it is running it will output to 82 | the terminal showing a temporary ASCII file being created from 83 | the LAS file using the 84 | [las2txt](http://www.cs.unc.edu/~isenburg/lastools/download/las2txt_README.txt) [^5] 85 | command, dropping class 7 and keeping only the first return. 86 | 87 | The command used for this is: 88 | ```bash 89 | las2txt -parse txyzicrna -sep space ^ 90 | -drop_class 7 -first_only ^ 91 | -i las1.0\LDR-EUFAR11_02-2011-187-01.LAS ^ 92 | -o LDR-EUFAR11_02-2011-187-01.txt 93 | ``` 94 | Where `txyzicrna` specifies the attribute stored in each column: 95 | 96 | 1. Time 97 | 2. x coordinate 98 | 3. y coordinate 99 | 4. Elevation 100 | 5. Intensity 101 | 6. Classification 102 | 7. Return number 103 | 8. Number of returns 104 | 10. Scan angle rank 105 | 106 | This ASCII file is then imported into [GRASS](https://grass.osgeo.org/) [^6] using 107 | [r.in.xyz](http://grass.osgeo.org/grass64/manuals/r.in.xyz.html) [^7] and 108 | gridded at the specified resolution taking the mean elevation of all 109 | points in each cell. Once all files have been imported into GRASS and 110 | gridded a mosaic is generated and exported. 111 | 112 | Once the command has run through the file can be opened in [TuiView](http://tuiview.org/) [^8] (if installed) using: 113 | ``` 114 | tuiview EUFAR11_02-2011-187_dsm.dem 115 | ``` 116 | or another packages such as QGIS or ArcMap. 117 | 118 | 4) To help visualise the data you can create a hillshade image using the [gdaldem](http://www.gdal.org/gdaldem.html) [^9] command: 119 | 120 | ```bash 121 | gdaldem hillshade ^ 122 | EUFAR11_02-2011-187_dsm.dem ^ 123 | EUFAR11_02-2011-187_dsm_hillshade.tif 124 | ``` 125 | 126 | 5) You can also create contour lines using the [gdalccontour](http://www.gdal.org/gdal_contour.html) [^10] command: 127 | 128 | ```bash 129 | gdal_contour -i 20 -a elevation ^ 130 | EUFAR11_02-2011-187_dsm.dem ^ 131 | EUFAR11_02-2011-187_dsm_20m_contours.shp 132 | ``` 133 | 134 | To open the hillshade image with contour lines overlain in TuiView use: 135 | 136 | ```bash 137 | tuiview -v EUFAR11_02-2011-187_dsm_20m_contours.shp ^ 138 | EUFAR11_02-2011-187_dsm_hillshade.tif 139 | ``` 140 | 141 | Click 'OK' on the default layer name in the dialogue which pops up. 142 | With all flight lines this will look like the map below: 143 | 144 | ![Hillshade image created from LiDAR data over Svalbard with contour lines overlain.](figures/EUFAR11_02-2011-187_dsm_20m_contours.png) 145 | 146 | ## Create a LiDAR / ASTER DSM for use in APL ## 147 | 148 | To create a DSM from the LiDAR, suitable for using in the Airborne 149 | Processing Library (APL) to geocorrect hyperspectral data, some extra 150 | consideration are needed: 151 | 152 | * The DSM needs to use WGS-84 Lat/Long projection and heights need to be relative to the WGS-84 ellipsoid. 153 | * Areas of no-data need to be filled (e.g., with a coarser resolution DEM). 154 | * The format needs to be ENVI Band Interleaved by Line (BIL) or Band Sequential (BSQ). 155 | 156 | The same `create_dem_from_lidar` script can be used to generate a DSM 157 | for use in APL, by setting some options: 158 | 159 | ```bash 160 | create_dem_from_lidar --in_projection UTM33N ^ 161 | --out_projection WGS84LL ^ 162 | --lidar_bounds ^ 163 | --demmosaic dem\EUFAR11_02-2011-187_ASTER.dem ^ 164 | --outdem EUFAR11_02-2011-187-lidar_ASTER-wgs84_latlong.dem ^ 165 | las1.0\LDR-EUFAR11_02-2011-187-01.LAS ^ 166 | las1.0\LDR-EUFAR11_02-2011-187-02.LAS 167 | ``` 168 | As previously only two lines are specified to reduce processing time. 169 | 170 | This will create a DSM mosaic from the LAS files, reproject to WGS84 Lat/Long and patch with 'EUFAR11\_02-2011-187\_ASTER.dem' (as provided with the NERC-ARF hyperspectral delivery), cropped to the bounding box of all LiDAR data plus a buffer of 2 km. This assumes the vertical datum of the DEM mosaic is the same as that required for the output projection. 171 | 172 | ## Create DSM / DTM using additional programs ## 173 | 174 | So far only a simple DSM has been created by taking the average of all first-return points within a pixel. Pixels which do not contain a return are left as no-data. There are more advanced methods of interpolation in GRASS (which we will come onto later), there are also other programs which can be used to produce a DSM. These can be accessed by two utility programs, described below. For this section the Montrose Bay data will be used, navigate to the directory the data are stored in using: 175 | ``` 176 | cd C:\nerc-arf-workshop\lidar_practical\GB13_08-217 177 | ``` 178 | (note if you are using your own machine you will need to input a different location). 179 | 180 | For the workshop we will use a subset, you can create this using the 181 | [las2las](http://www.cs.unc.edu/~isenburg/lastools/download/las2las_README.txt) [^11] 182 | command: 183 | ```bash 184 | las2las -keep_xy 374200 764500 375000 765500 ^ 185 | -i las1.2\LDR-GB13_08-2014-217-02.LAS ^ 186 | -o las1.2\LDR-GB13_08-2014-217-02_subset.LAS 187 | ``` 188 | In addition to subsetting LAS files the `las2las` command can be used to apply other filters to LAS files. 189 | 190 | ### Digital Surface Model (DSM) ### 191 | 192 | To create a DSM using GRASS the following is used 193 | 194 | ```bash 195 | las_to_dsm -o LDR-GB13_08-2014-217-02_subset_dsm_grass.tif ^ 196 | --projection UKBNG ^ 197 | --method GRASS ^ 198 | las1.2\LDR-GB13_08-2014-217-02_subset.LAS 199 | ``` 200 | 201 | The format of the output file is set using the extension, using '.tif' will create a GeoTIFF. 202 | 203 | Other programs such as [LAStools](http://rapidlasso.com/lastools/) [^12], [SPDLib](http://spdlib.org) [^13], [FUSION](http://forsys.cfr.washington.edu/fusion/fusion_overview.html) [^14] and [points2grid](https://github.com/CRREL/points2grid) [^15] can be used, if they have been installed, by setting the --method flag. Note, unlike the other methods, the tools required to create a DEM in LAStools are not free and binaries are only provided for Windows (although work on Linux through Wine). It is possible to run without a license for non-profit and personal work but they will introduce noise and artifacts (such as black diagonal lines) in the data. For more details see [LAStools License](http://www.cs.unc.edu/~isenburg/lastools/LICENSE.txt) [16^] 204 | 205 | Try running the same command but setting `--method LAStools` or `--method SPDLib` (if SPDLib is installed). For example: 206 | ```bash 207 | las_to_dsm -o LDR-GB13_08-2014-217-02_subset_dsm_spdlib.tif ^ 208 | --projection UKBNG ^ 209 | --method LAStools ^ 210 | las1.2\LDR-GB13_08-2014-217-02_subset.LAS 211 | ``` 212 | 213 | ### Digital Terrain Model (DTM) ### 214 | 215 | In addition to producing a DSM another common product from LiDAR data is a Digital Terrain Model (DTM), this represents the 'bare-earth', that is the elevation excluding buildings and vegetation. Producing a DTM is more complicated than a DSM, a first order approximation is to take only last returns, which assumes multiple returns are received from a pulse and that the last is from the ground. However, many last returns are not from the ground for example dense vegetation where the last return does not reach the ground or buildings where there are not multiple returns. Producing an accurate DTM first requires ground returns to be selected from the point cloud and gaps where there are no ground returns (e.g., buildings) to be interpolated. 216 | 217 | Packages such as [LAStools](http://rapidlasso.com/lastools/) [^12], [SPDLib](http://spdlib.org) [^13] and [FUSION](http://forsys.cfr.washington.edu/fusion/fusion_overview.html) [^14] have more advanced methods for classifying ground returns (see their 218 | respective manuals for more details). 219 | 220 | To create a DTM the `las_to_dtm` command is used: 221 | 222 | ```bash 223 | las_to_dtm -o LDR-GB13_08-2014-217-02_subset_dtm_spdlib.tif ^ 224 | --projection UKBNG ^ 225 | --method SPDLib ^ 226 | las1.2/LDR-GB13_08-2014-217-02_subset.LAS 227 | ``` 228 | 229 | As shown earlier the `gdaldem` command can be used to produce hillshade images for visualisation as shown below. 230 | 231 | ![Comparison of Digital Terrain Model (DTM) and Digital Surface Model (DSM)](figures/dtm_dsm.png) 232 | 233 | Note, depending on the cover the default classification and interpolation parameters for each program used by las\_to\_dtm may not provide the best possible results. 234 | In these cases it is recommended you access the programs directly, as this will provide more control over the available options. 235 | 236 | ## More Advanced Manipulation in GRASS ## 237 | 238 | Outside the DEM scripts it is possible to perform more advanced manipulation of LiDAR data in GRASS. A good tutorial is available on the GRASS wiki: https://grasswiki.osgeo.org/wiki/LIDAR. 239 | 240 | ```bash 241 | load_lidar_to_grass --projection UKBNG ^ 242 | --rastertype DSM ^ 243 | --resolution 1 ^ 244 | las1.2\LDR-GB13_08-2014-217-02_subset.LAS 245 | ``` 246 | 247 | This will return: 248 | ``` 249 | Loaded the following files: 250 | lidar_ppScIf.dem 251 | To GRASS database: /tmp/grassdb-12869-114950/UKBNG/PERMANENT 252 | ``` 253 | (the specific names will be different when you run it) 254 | 255 | Open GRASS with the database created using: 256 | ``` 257 | grass /tmp/grassdb-12869-114950/UKBNG/PERMANENT 258 | ``` 259 | Within GRASS type: 260 | ``` 261 | g.gui 262 | ``` 263 | To open the GRASS interface. Within this use File -> Map Display -> Add Raster to display the file. 264 | Note there are gaps in the DSM where pixels do not have any points falling within them, as shown below. 265 | 266 | ![Dataset imported into GRASS prior to interpolation](figures/grass_dsm_with_holes.png) 267 | 268 | To fill in some of these gaps we can interpolate between them using Inverse Distance 269 | Weighted interpolation (IDW): 270 | ``` 271 | r.surf.idw input=INPUTLAYER output=OUTPUTLAYERNAME 272 | ``` 273 | 274 | Changing 'INPUTLAYER' to the name of the file in GRASS and 'OUTPUTLAYERNAME' to name you wish to use for the interpolated raster. 275 | 276 | Note that this command will apply some smoothing and output the data as integer, therefore it will lose smaller vertical details within the LiDAR. 277 | 278 | Load this file into GRASS and compare with the original. 279 | 280 | ![Dataset in GRASS after Inverse Distance Weighted (IDW) interpolation.](figures/grass_dsm_idw.png) 281 | 282 | [^1]: NEODC [http://neodc.nerc.ac.uk/](http://neodc.nerc.ac.uk/) 283 | [^2]: EUFAR11/02 NEODC link [http://browse.ceda.ac.uk/browse/neodc/arsf/2011/EUFAR11_02/EUFAR11_02-2011_187_SVALBD_PGLACIAL/LiDAR](http://browse.ceda.ac.uk/browse/neodc/arsf/2011/EUFAR11_02/EUFAR11_02-2011_187_SVALBD_PGLACIAL/LiDAR) 284 | [^3]: GB13/08 NEODC link [http://browse.ceda.ac.uk/browse/neodc/arsf/2014/GB13_08/GB13_08-2014_217_Montrose_Bay/LiDAR](http://browse.ceda.ac.uk/browse/neodc/arsf/2014/GB13_08/GB13_08-2014_217_Montrose_Bay/LiDAR) 285 | [^4]: LAG [http://arsf.github.io/lag/](http://arsf.github.io/lag/) 286 | [^5]: las2txt documentation [http://www.cs.unc.edu/~isenburg/lastools/download/las2txt_README.txt](http://www.cs.unc.edu/~isenburg/lastools/download/las2txt_README.txt) 287 | [^6]: GRASS [https://grass.osgeo.org/](https://grass.osgeo.org/) 288 | [^7]: r.in.xyz [http://grass.osgeo.org/grass64/manuals/r.in.xyz.html](http://grass.osgeo.org/grass64/manuals/r.in.xyz.html) 289 | [^8]: TuiView [http://tuiview.org/](http://tuiview.org/) 290 | [^9]: gdaldem [http://www.gdal.org/gdaldem.html](http://www.gdal.org/gdaldem.html) 291 | [^10]: gdal_contour [http://www.gdal.org/gdal_contour.html](http://www.gdal.org/gdal_contour.html) 292 | [^11]: las2las documentation [http://www.cs.unc.edu/~isenburg/lastools/download/las2las_README.txt](http://www.cs.unc.edu/~isenburg/lastools/download/las2las_README.txt) 293 | [^12]: LAStools [http://rapidlasso.com/lastools/](http://rapidlasso.com/lastools/) 294 | [^13]: SPDLib [http://spdlib.org](http://spdlib.org) 295 | [^14]: Fusion [http://forsys.cfr.washington.edu/fusion/fusion_overview.html](http://forsys.cfr.washington.edu/fusion/fusion_overview.html) 296 | [^15]: points2grid [https://github.com/CRREL/points2grid](https://github.com/CRREL/points2grid) 297 | [^16]: LAStools license [http://www.cs.unc.edu/~isenburg/lastools/LICENSE.txt](http://www.cs.unc.edu/~isenburg/lastools/LICENSE.txt) 298 | -------------------------------------------------------------------------------- /scripts/create_apl_dem.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | python "%~dp0\create_apl_dem.py" %* 3 | -------------------------------------------------------------------------------- /scripts/create_apl_dem.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #Description: A script to create a DEM suitable for use in APL from ASTER, NextMap or another DEM mosaic. Uses arsf_dem library 3 | """ 4 | Author: Dan Clewley (dac) 5 | 6 | Created on: 29 October 2014 7 | 8 | This file has been created by ARSF Data Analysis Node and 9 | is licensed under the GPL v3 Licence. A copy of this 10 | licence is available to download with this file. 11 | 12 | """ 13 | 14 | from __future__ import print_function # Import print function (so we can use Python 3 syntax with Python 2) 15 | import sys 16 | import argparse 17 | 18 | # Import DEM library 19 | try: 20 | from arsf_dem import dem_common 21 | from arsf_dem import dem_nav_utilities 22 | from arsf_dem import dem_common_functions 23 | except ImportError as err: 24 | print("Could not import ARSF DEM library", file=sys.stderr) 25 | print(err, file=sys.stderr) 26 | sys.exit(1) 27 | 28 | #: Debug mode 29 | DEBUG = dem_common.DEBUG 30 | 31 | if __name__ == '__main__': 32 | 33 | description_str = '''A script to create a DEM for use in APL subset to bounds 34 | of hyperspectral navigation data. 35 | 36 | If not running on ARSF systems need to pass in bil format navigation files 37 | (supplied with delivered hyperspectral data). 38 | 39 | Typical usage: 40 | 41 | 1) Create Next map DEM 42 | 43 | create_apl_dem.py --nextmap -p /users/rsg/arsf/arsf_data/2014/flight_data/arsf_internal/GB14_00-2014_216_Little_Riss_Fenix/ -o GB14_00-2014_216_NEXTMAP.dem 44 | 45 | 2) Create ASTER DEM 46 | 47 | create_apl_dem.py --aster -p /users/rsg/arsf/arsf_data/2014/flight_data/arsf_internal/GB14_00-2014_216_Little_Riss_Fenix/ -o GB14_00-2014_216_ASTER.dem 48 | 49 | 3) Create STRM DEM 50 | 51 | create_apl_dem.py --srtm -p /users/rsg/arsf/arsf_data/2014/flight_data/arsf_internal/GB14_00-2014_216_Little_Riss_Fenix/ -o GB14_00-2014_216_SRTM.dem 52 | 53 | 4) Create DEM from custom dataset, where heights are relative to geoid 54 | 55 | create_apl_dem.py --demmosaic local_dem_egm96.tif --separation_file {0} \\ 56 | -p /users/rsg/arsf/arsf_data/2014/flight_data/arsf_internal/GB14_00-2014_216_Little_Riss_Fenix/ -o 2014_216_custom.dem 57 | 58 | 5) Create DEM from custom dataset, where heights are already relative to WGS-84 ellipsoid 59 | 60 | create_apl_dem.py --demmosaic local_dem_utm10n.bil \\ 61 | -p /users/rsg/arsf/arsf_data/2014/flight_data/arsf_internal/GB14_00-2014_216_Little_Riss_Fenix/ -o 2014_216_custom.dem 62 | 63 | 6) Create DEM from ASTER using post-processed bil format navigation data (for delivered data) 64 | 65 | create_apl_dem.py --aster --bil_navigation flightlines/navigation -o 2014_216_aster.dem 66 | 67 | 7) Create a DEM from downloaded SRTM tiles for use in APL using processed navigation files 68 | 69 | # Create VRT mosaic of downloaded tiles 70 | 71 | gdalbuildvrt srtm_mosaic.vrt *1arc_v3.tif 72 | 73 | # Create DEM 74 | 75 | create_apl_dem.py --demmosaic strm_mosaic.vrt --separation_file {0} \\ 76 | --bil_navigation flightlines/navigation -o 2014_216_strm.dem 77 | 78 | If calling from within the project directory, there should be no need to specify the 79 | project path as it will be found from the current location. 80 | 81 | Known issues: 82 | If the correct project path is not found or passed in, or for another reason 83 | there is a problem finding hyperspectral navigation files the script will 84 | print a warning but continue and produce a DEM much larger than required. 85 | 86 | 'create_apl_dem' was created by ARSF-DAN at Plymouth Marine Laboratory (PML) 87 | and is made available under the terms of the GPLv3 license. 88 | 89 | '''.format(dem_common.WWGSG_FILE) 90 | 91 | try: 92 | parser = argparse.ArgumentParser(description=description_str, formatter_class=argparse.RawDescriptionHelpFormatter) 93 | parser.add_argument('-o', '--outdem', 94 | metavar ='Out DEM', 95 | help ='''Output name for DEM. 96 | If not provided will output to standard location for hyperspectral data processing.''', 97 | required=False, 98 | default=None) 99 | parser.add_argument('-n', '--nav', 100 | metavar ='Nav file', 101 | help ='Navigation data (.sol / .sbet file)', 102 | default=None, 103 | required=False) 104 | parser.add_argument('-p', '--project', 105 | metavar ='Main project directory', 106 | help ='Main project directory (default=".")', 107 | default='.', 108 | required=False) 109 | dem_group = parser.add_mutually_exclusive_group() 110 | dem_group.add_argument('--aster', 111 | action='store_true', 112 | help='Use ASTER data ({})'.format(dem_common.ASTER_MOSAIC_FILE), 113 | default=False, 114 | required=False) 115 | dem_group.add_argument('--nextmap', 116 | action='store_true', 117 | help='Use Nextmap data ({})'.format(dem_common.NEXTMAP_MOSAIC_FILE), 118 | default=False, 119 | required=False) 120 | dem_group.add_argument('--srtm', 121 | action='store_true', 122 | help='Use SRTM data ({})'.format(dem_common.SRTM_MOSAIC_FILE), 123 | default=False, 124 | required=False) 125 | dem_group.add_argument('--demmosaic', 126 | metavar ='Input DEM mosaic', 127 | help ='Input DEM mosaic. For non-standard DEM. ' 128 | 'Use "--aster" or "--nextmap" ' 129 | 'for standard DEMs.', 130 | required=False, 131 | default=None) 132 | parser.add_argument('--separation_file', 133 | metavar ='Seperation file', 134 | help ='''File with Height offset to add if "--demmosaic" is used and 135 | DEM heights are not relative to WGS-84 elepsoid. 136 | Not required if using "--aster", "--nextmap" or "--srtm" for standard DEMs.''', 137 | required=False, 138 | default=None) 139 | parser.add_argument('-b', '--bil_navigation', 140 | metavar ='BIL Navigation Files', 141 | help ='''Directory containing post-processed navigation files in BIL format. 142 | By default raw navigation data will be used for "--project". 143 | If this is not available (e.g., for ARSF-DAN delivered data) use this option and point to 144 | "flightlines/navigation" within delivery directory''', 145 | default=None, 146 | required=False) 147 | parser.add_argument('--keepgrassdb', 148 | action='store_true', 149 | help='Keep GRASS database (default=False)', 150 | default=False, 151 | required=False) 152 | args=parser.parse_args() 153 | 154 | dem_source = None 155 | 156 | # ASTER DEM 157 | if args.aster: 158 | dem_source = 'ASTER' 159 | # NEXTMap DEM 160 | elif args.nextmap: 161 | dem_source = 'NEXTMAP' 162 | # SRTM DEM 163 | elif args.srtm: 164 | dem_source = 'SRTM' 165 | # Custom DEM 166 | elif args.demmosaic is not None: 167 | dem_source = 'USER' 168 | else: 169 | parser.print_help() 170 | print('\nMust provide at least "--nextmap", "--aster" or "--srtm" flag' 171 | ' for standard DEM locations or supply custom DEM with ' 172 | '"--demmosaic"', file=sys.stderr) 173 | sys.exit(1) 174 | 175 | dem_nav_utilities.create_apl_dem_from_mosaic(args.outdem, 176 | dem_source=dem_source, 177 | dem_mosaic=args.demmosaic, 178 | separation_file=args.separation_file, 179 | project=args.project, 180 | nav=args.nav, 181 | bil_navigation=args.bil_navigation, 182 | remove_grassdb=(not args.keepgrassdb)) 183 | 184 | except KeyboardInterrupt: 185 | sys.exit(2) 186 | except Exception as err: 187 | if DEBUG: 188 | raise 189 | dem_common_functions.ERROR(err) 190 | sys.exit(1) 191 | -------------------------------------------------------------------------------- /scripts/create_dem_from_lidar.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | python "%~dp0\create_dem_from_lidar.py" %* 3 | -------------------------------------------------------------------------------- /scripts/create_dem_from_lidar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #Description: A script to create a DEM from lidar data. 3 | """ 4 | Author: Dan Clewley (dac) 5 | 6 | Created on: 07 Nov 2014 7 | 8 | This file has been created by ARSF Data Analysis Node and 9 | is licensed under the GPL v3 Licence. A copy of this 10 | licence is available to download with this file. 11 | 12 | """ 13 | 14 | from __future__ import print_function # Import print function (so we can use Python 3 syntax with Python 2) 15 | import sys 16 | import argparse 17 | # Import DEM library 18 | try: 19 | from arsf_dem import dem_common 20 | from arsf_dem import dem_lidar 21 | from arsf_dem import dem_common_functions 22 | except ImportError as err: 23 | print("Could not import ARSF DEM library.", file=sys.stderr) 24 | print(err, file=sys.stderr) 25 | sys.exit(1) 26 | 27 | #: Debug mode 28 | DEBUG = dem_common.DEBUG 29 | 30 | if __name__ == '__main__': 31 | description_str = '''A script to create a DEM from LiDAR data in LAS or ASCII format and optionally patch with a DEM 32 | 33 | Typical usage 34 | 35 | 1) Create DEM from LiDAR files in default projection ({0}) 36 | create_dem_from_lidar.py -o lidar_dsm.dem *LAS 37 | 38 | 2) Create DEM from LiDAR files in UTM30N projection 39 | create_dem_from_lidar.py --in_projection UTM30N -o lidar_dsm.dem *LAS 40 | 41 | 3) Create DEM from LiDAR files and patch with ASTER data 42 | Output DEM in WGS84LL projection 43 | create_dem_from_lidar.py --aster --out_projection WGS84LL -o lidar_aster_dsm.dem *LAS 44 | 45 | 4) Create DEM from LiDAR files and patch with ASTER data, output bounds based on navigation data. 46 | Output DEM in WGS84LL projection suitible for use in APL. Also export screenshot in JPEG format. 47 | 48 | create_dem_from_lidar.py --aster --out_projection WGS84LL \\ 49 | -p /users/rsg/arsf/arsf_data/2014/flight_data/arsf_internal/GB14_00-2014_216_Little_Riss_Fenix/ \\ 50 | -o 2014_216_lidar_aster_dsm.dem \\ 51 | --screenshot /screenshots/2014_216_lidar_aster_dsm.jpg \\ 52 | ../las1.2 53 | 54 | Known issues: 55 | If you don't pass in the correct project path, or there is a problem 56 | finding hyperspectral navigation files will print warning but continue and produce 57 | a DEM much larger than is required. If the DEM is not required for APL you can use 58 | the flag '--lidar_bounds', which only uses the bounds of the lidar data, not navigation files 59 | plus a buffer of {1} m. 60 | 61 | 'create_dem_from_lidar' was created by ARSF-DAN at Plymouth Marine Laboratory (PML) 62 | and is made available under the terms of the GPLv3 license. 63 | 64 | '''.format(dem_common.DEFAULT_LIDAR_PROJECTION_GRASS, dem_common.DEFAULT_LIDAR_DEM_BUFFER['N']) 65 | 66 | try: 67 | parser = argparse.ArgumentParser(description=description_str,formatter_class=argparse.RawDescriptionHelpFormatter) 68 | parser.add_argument("lidarfiles", nargs='+',type=str, help="List or directory containing input LiDAR files") 69 | parser.add_argument('-o', '--outdem', 70 | metavar ='Out DEM', 71 | help ='Output name for DEM', 72 | required=True) 73 | parser.add_argument('-s', '--screenshot', 74 | metavar ='Out Screenshot File or Directory', 75 | help ='Output directory for screenshots or single file for screenshot of mosaic, in JPEG format.', 76 | default=None, 77 | required=False) 78 | parser.add_argument('--shadedrelief', 79 | action='store_true', 80 | help='Create shaded relief images for screenshots', 81 | default=False, 82 | required=False) 83 | lidar_group = parser.add_mutually_exclusive_group() 84 | lidar_group.add_argument('--las', 85 | action='store_true', 86 | help='Input LiDAR data are in LAS format ' 87 | '(default=True)', 88 | default=True, 89 | required=False) 90 | lidar_group.add_argument('--ascii', 91 | action='store_true', 92 | help='Input LiDAR data are in ASCII format ' 93 | '(default=False)', 94 | default=False, 95 | required=False) 96 | lidar_group.add_argument('--gridded', 97 | action='store_true', 98 | help='Input LiDAR data are in a gridded (raster)' 99 | ' format (default=False)', 100 | default=False, 101 | required=False) 102 | parser.add_argument('-r', '--resolution', 103 | metavar ='Resolution', 104 | help ='Resolution for output DEM (default={})'.format(dem_common.DEFAULT_LIDAR_RES_METRES), 105 | default=dem_common.DEFAULT_LIDAR_RES_METRES, 106 | required=False) 107 | parser.add_argument('--in_projection', 108 | metavar ='In Projection', 109 | help ='Input projection (e.g., UTM30N; default={})'.format(dem_common.DEFAULT_LIDAR_PROJECTION_GRASS), 110 | default=dem_common.DEFAULT_LIDAR_PROJECTION_GRASS, 111 | required=False) 112 | parser.add_argument('--out_projection', 113 | metavar ='Out Projection', 114 | help ='Out projection. Default is same as input', 115 | default=None, 116 | required=False) 117 | parser.add_argument('-n', '--nav', 118 | metavar ='Nav file', 119 | help ='Navigation data (.sbet / .sol file) used if patching with another DEM', 120 | default=None, 121 | required=False) 122 | parser.add_argument('-p', '--project', 123 | metavar ='Main project directory', 124 | help ='Main project directory, used if patching with another DEM', 125 | default='.', 126 | required=False) 127 | dem_group = parser.add_mutually_exclusive_group() 128 | dem_group.add_argument('--demmosaic', 129 | metavar ='Input DEM mosaic', 130 | help ='Input DEM mosaic to patch with in GDAL ' 131 | 'compatible format. Vertical datum needs ' 132 | 'to be the same as output projection. ' 133 | 'Only required for non-standard DEM. ' 134 | 'Use "--aster" or "--nextmap" for ' 135 | 'standard DEMs.', 136 | required=False, 137 | default=None) 138 | dem_group.add_argument('--aster', 139 | action='store_true', 140 | help='Patch with ASTER data ' 141 | '({})'.format(dem_common.ASTER_MOSAIC_FILE), 142 | default=False, 143 | required=False) 144 | dem_group.add_argument('--nextmap', 145 | action='store_true', 146 | help='Patch with Nextmap data ' 147 | ' ({})'.format(dem_common.NEXTMAP_MOSAIC_FILE), 148 | default=False, 149 | required=False) 150 | dem_group.add_argument('--srtm', 151 | action='store_true', 152 | help='Use SRTM data ' 153 | '({})'.format(dem_common.SRTM_MOSAIC_FILE), 154 | default=False, 155 | required=False) 156 | bounds_group = parser.add_mutually_exclusive_group() 157 | bounds_group.add_argument('--hyperspectral_bounds', 158 | action='store_true', 159 | help='If patching with another DEM, get extent ' 160 | 'from hyperspectral navigation data, ' 161 | 'recommended if DEM is to be used with APL ' 162 | 'and navigation data are available. ' 163 | 'This is the default behaviour''', 164 | default=False, 165 | required=False) 166 | bounds_group.add_argument('--lidar_bounds', 167 | action='store_true', 168 | help='If patching with another DEM, get extent ' 169 | 'from lidar data plus default buffer of ' 170 | '{} m. If DEM is not required to be used ' 171 | 'with APL this option is recommended' 172 | ''.format(dem_common.DEFAULT_LIDAR_DEM_BUFFER['N']), 173 | default=False, 174 | required=False) 175 | parser.add_argument('--fill_lidar_nulls', 176 | action='store_true', 177 | help='''Fill NULL values in LiDAR data using interpolation. 178 | Not available if patching with another DEM''', 179 | default=False, 180 | required=False) 181 | parser.add_argument('-t', '--rastertype', 182 | metavar ='Output raster type', 183 | help ='Output raster type (default DSM)', 184 | default='DSM', 185 | required=False) 186 | parser.add_argument('--keepgrassdb', 187 | action='store_true', 188 | help='Keep GRASS database (default=False)', 189 | default=False, 190 | required=False) 191 | args=parser.parse_args() 192 | 193 | # Set format for input lidar data 194 | lidar_format = 'LAS' 195 | if args.ascii: 196 | lidar_format = 'ASCII' 197 | if args.gridded: 198 | lidar_format = 'GRIDDED' 199 | # Set source DEM (if patching) 200 | # ASTER DEM 201 | if args.aster: 202 | dem_source = 'ASTER' 203 | # SRTM DEM 204 | elif args.srtm: 205 | dem_source = 'SRTM' 206 | # NEXTMap DEM 207 | elif args.nextmap: 208 | dem_source = 'NEXTMAP' 209 | else: 210 | dem_source = None 211 | 212 | # Set if hyperspectral bounds are to be used or lidar 213 | if args.lidar_bounds: 214 | use_lidar_bounds = True 215 | else: 216 | use_lidar_bounds = False 217 | 218 | if args.lidar_bounds and args.hyperspectral_bounds: 219 | dem_common_functions.ERROR("Can't use '--lidar_bounds' and '--hyperspectral_bounds' together.") 220 | sys.exit(1) 221 | 222 | in_lidar_projection = args.in_projection.upper() 223 | 224 | dem_lidar.lidar_utilities.create_patched_lidar_mosaic(args.lidarfiles, 225 | args.outdem, 226 | in_lidar_projection=in_lidar_projection, 227 | resolution=args.resolution, 228 | lidar_format=lidar_format, 229 | out_projection=args.out_projection, 230 | screenshot=args.screenshot, 231 | shaded_relief_screenshots=args.shadedrelief, 232 | out_raster_type=args.rastertype, 233 | dem_source=dem_source, 234 | dem_mosaic=args.demmosaic, 235 | project=args.project, 236 | nav=args.nav, 237 | lidar_bounds=use_lidar_bounds, 238 | fill_lidar_nulls=args.fill_lidar_nulls) 239 | 240 | except KeyboardInterrupt: 241 | sys.exit(2) 242 | except Exception as err: 243 | if DEBUG: 244 | raise 245 | dem_common_functions.ERROR(err) 246 | sys.exit(1) 247 | -------------------------------------------------------------------------------- /scripts/las_to_dsm.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | python "%~dp0\las_to_dsm.py" %* 3 | -------------------------------------------------------------------------------- /scripts/las_to_dsm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #Description: A script to create a DSM from as LAS file. 3 | """ 4 | Author: Dan Clewley (dac) 5 | 6 | Created on: 16 March 2015 7 | 8 | """ 9 | # This file has been created by ARSF Data Analysis Node and 10 | # is licensed under the GPL v3 Licence. A copy of this 11 | # licence is available to download with this file. 12 | 13 | from __future__ import print_function # Import print function (so we can use Python 3 syntax with Python 2) 14 | import sys 15 | import argparse 16 | # Import DEM library 17 | try: 18 | from arsf_dem import dem_common 19 | from arsf_dem import dem_utilities 20 | from arsf_dem import dem_lidar 21 | from arsf_dem import dem_common_functions 22 | except ImportError as err: 23 | print("Could not import ARSF DEM library.", file=sys.stderr) 24 | print(err, file=sys.stderr) 25 | sys.exit(1) 26 | 27 | #: Debug mode 28 | DEBUG = dem_common.DEBUG 29 | 30 | if __name__ == '__main__': 31 | description_str = '''Create a Digital Surface Model (DSM) from a LAS file(s). 32 | 33 | 'las_to_dsm' was created by ARSF-DAN at Plymouth Marine Laboratory (PML) 34 | and is made available under the terms of the GPLv3 license. 35 | 36 | The programs used by las_to_dsm are available under a range of licenses, please 37 | consult their respective documentation for more details. 38 | 39 | ''' 40 | 41 | try: 42 | parser = argparse.ArgumentParser(description=description_str,formatter_class=argparse.RawDescriptionHelpFormatter) 43 | parser.add_argument("lasfile", nargs="+",type=str, 44 | help="Input LAS file(s)") 45 | parser.add_argument('-o', '--outdem', 46 | metavar ='Out DEM', 47 | help ='Output name for DTM', 48 | required=True) 49 | parser.add_argument('--hillshade', 50 | metavar ='Out Hillshade', 51 | help ='Output name for hillshade image (optional)', 52 | default=None, 53 | required=False) 54 | parser.add_argument('-r', '--resolution', 55 | metavar ='Resolution', 56 | help ='Resolution for output DEM (default={})'.format(dem_common.DEFAULT_LIDAR_RES_METRES), 57 | default=dem_common.DEFAULT_LIDAR_RES_METRES, 58 | required=False) 59 | parser.add_argument('--projection', 60 | metavar ='In Projection', 61 | help ='Input projection (e.g., UTM30N)', 62 | default=None, 63 | required=False) 64 | parser.add_argument('--method', 65 | metavar ='Method', 66 | help ='Software package to use. Options are:\n{}'.format(','.join(dem_lidar.LAS_TO_DEM_METHODS)), 67 | default='GRASS', 68 | required=False) 69 | args=parser.parse_args() 70 | 71 | dem_lidar.las_to_dsm(args.lasfile, args.outdem, 72 | resolution=args.resolution, 73 | projection=args.projection, 74 | method=args.method) 75 | 76 | # If hillshade image is required, create this 77 | if args.hillshade is not None: 78 | out_raster_format = dem_utilities.get_gdal_type_from_path(args.hillshade) 79 | 80 | dem_utilities.call_gdaldem(args.outdem, args.hillshade, 81 | dem_product='hillshade', 82 | of=out_raster_format) 83 | 84 | except KeyboardInterrupt: 85 | sys.exit(2) 86 | except Exception as err: 87 | if DEBUG: 88 | raise 89 | dem_common_functions.ERROR(err) 90 | sys.exit(1) 91 | -------------------------------------------------------------------------------- /scripts/las_to_dtm.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | python "%~dp0\las_to_dtm.py" %* 3 | -------------------------------------------------------------------------------- /scripts/las_to_dtm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #Description: A script to create a DTM from a LAS file 3 | """ 4 | Author: Dan Clewley (dac) 5 | 6 | Created on: 16 March 2015 7 | 8 | This file has been created by ARSF Data Analysis Node and 9 | is licensed under the GPL v3 Licence. A copy of this 10 | licence is available to download with this file. 11 | 12 | """ 13 | 14 | from __future__ import print_function # Import print function (so we can use Python 3 syntax with Python 2) 15 | import sys 16 | import argparse 17 | # Import DEM library 18 | try: 19 | from arsf_dem import dem_common 20 | from arsf_dem import dem_utilities 21 | from arsf_dem import dem_lidar 22 | from arsf_dem import dem_common_functions 23 | except ImportError as err: 24 | print("Could not import ARSF DEM library.", file=sys.stderr) 25 | print(err, file=sys.stderr) 26 | sys.exit(1) 27 | 28 | #: Debug mode 29 | DEBUG = dem_common.DEBUG 30 | 31 | if __name__ == '__main__': 32 | description_str = '''Create a Digital Terrain Model (DTM) from a LAS file(s). 33 | 34 | 'las_to_dtm' was created by ARSF-DAN at Plymouth Marine Laboratory (PML) 35 | and is made available under the terms of the GPLv3 license. 36 | 37 | The programs used by las_to_dtm are available under a range of licenses, please 38 | consult their respective documentation for more details. 39 | 40 | ''' 41 | 42 | try: 43 | parser = argparse.ArgumentParser(description=description_str,formatter_class=argparse.RawDescriptionHelpFormatter) 44 | parser.add_argument("lasfile", nargs="+",type=str, 45 | help="Input LAS file(s)") 46 | parser.add_argument('-o', '--outdem', 47 | metavar ='Out DEM', 48 | help ='Output name for DTM', 49 | required=True) 50 | parser.add_argument('--hillshade', 51 | metavar ='Out Hillshade', 52 | help ='Output name for hillshade image (optional)', 53 | default=None, 54 | required=False) 55 | parser.add_argument('-r', '--resolution', 56 | metavar ='Resolution', 57 | help ='Resolution for output DEM (default={})'.format(dem_common.DEFAULT_LIDAR_RES_METRES), 58 | default=dem_common.DEFAULT_LIDAR_RES_METRES, 59 | required=False) 60 | parser.add_argument('--projection', 61 | metavar ='In Projection', 62 | help ='Input projection (e.g., UTM30N)', 63 | default=None, 64 | required=False) 65 | parser.add_argument('--method', 66 | metavar ='Method', 67 | help ='Software package to use. Options are:\n{}'.format(','.join(dem_lidar.LAS_TO_DEM_METHODS)), 68 | default='GRASS', 69 | required=False) 70 | args=parser.parse_args() 71 | 72 | dem_lidar.las_to_dtm(args.lasfile, args.outdem, 73 | resolution=args.resolution, 74 | projection=args.projection, 75 | method=args.method) 76 | 77 | # If hillshade image is required, create this 78 | if args.hillshade is not None: 79 | out_raster_format = dem_utilities.get_gdal_type_from_path(args.hillshade) 80 | 81 | dem_utilities.call_gdaldem(args.outdem, args.hillshade, 82 | dem_product='hillshade', 83 | of=out_raster_format) 84 | except KeyboardInterrupt: 85 | sys.exit(2) 86 | except Exception as err: 87 | if DEBUG: 88 | raise 89 | dem_common_functions.ERROR(err) 90 | sys.exit(1) 91 | -------------------------------------------------------------------------------- /scripts/las_to_intensity.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | python "%~dp0\las_to_intensity.py" %* 3 | -------------------------------------------------------------------------------- /scripts/las_to_intensity.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #Description: A script to create an intensity raster from a LAS file. 3 | """ 4 | Author: Dan Clewley (dac) 5 | 6 | Created on: 19 January 2016 7 | 8 | """ 9 | # This file has been created by ARSF Data Analysis Node and 10 | # is licensed under the GPL v3 Licence. A copy of this 11 | # licence is available to download with this file. 12 | 13 | from __future__ import print_function # Import print function (so we can use Python 3 syntax with Python 2) 14 | import sys 15 | import argparse 16 | # Import DEM library 17 | try: 18 | from arsf_dem import dem_common 19 | from arsf_dem import dem_lidar 20 | from arsf_dem import dem_common_functions 21 | except ImportError as err: 22 | print("Could not import ARSF DEM library.", file=sys.stderr) 23 | print(err, file=sys.stderr) 24 | sys.exit(1) 25 | 26 | #: Debug mode 27 | DEBUG = dem_common.DEBUG 28 | 29 | if __name__ == '__main__': 30 | description_str = '''Create an Intensity Raster from a LAS file. 31 | 32 | 'las_to_intensity' was created by ARSF-DAN at Plymouth Marine Laboratory (PML) 33 | and is made available under the terms of the GPLv3 license. 34 | 35 | The programs used by las_to_intensity are available under a range of licenses, please 36 | consult their respective documentation for more details. 37 | 38 | ''' 39 | 40 | try: 41 | parser = argparse.ArgumentParser(description=description_str,formatter_class=argparse.RawDescriptionHelpFormatter) 42 | parser.add_argument("lasfile", nargs=1,type=str, help="Input LAS file") 43 | parser.add_argument('-o', '--outintensity', 44 | metavar ='Out Intensity', 45 | help ='Output name for Intensity image', 46 | required=True) 47 | parser.add_argument('-r', '--resolution', 48 | metavar ='Resolution', 49 | help ='Resolution for output image (default={})'.format(dem_common.DEFAULT_LIDAR_RES_METRES), 50 | default=dem_common.DEFAULT_LIDAR_RES_METRES, 51 | required=False) 52 | parser.add_argument('--projection', 53 | metavar ='In Projection', 54 | help ='Input projection (e.g., UTM30N)', 55 | default=None, 56 | required=False) 57 | parser.add_argument('--method', 58 | metavar ='Method', 59 | help ='Software package to use. Options are:\n{}'.format(','.join(dem_lidar.LAS_TO_INTENSITY_METHODS)), 60 | default='GRASS', 61 | required=False) 62 | args=parser.parse_args() 63 | 64 | dem_lidar.las_to_intensity(args.lasfile[0], args.outintensity, 65 | resolution=args.resolution, 66 | projection=args.projection, 67 | method=args.method) 68 | 69 | except KeyboardInterrupt: 70 | sys.exit(2) 71 | except Exception as err: 72 | if DEBUG: 73 | raise 74 | dem_common_functions.ERROR(err) 75 | sys.exit(1) 76 | -------------------------------------------------------------------------------- /scripts/load_lidar_to_grass.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | python "%~dp0\load_lidar_to_grass.py" %* 3 | -------------------------------------------------------------------------------- /scripts/load_lidar_to_grass.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #Description: A script to create a DSM from as LAS file. 3 | """ 4 | Load LiDAR data in LAS or ASCII format to GRASS for further processing. 5 | 6 | Author: Dan Clewley (dac) 7 | 8 | Created on: 10 Febuary 2016 9 | 10 | """ 11 | # This file has been created by ARSF Data Analysis Node and 12 | # is licensed under the GPL v3 Licence. A copy of this 13 | # licence is available to download with this file. 14 | 15 | from __future__ import print_function 16 | import glob 17 | import os 18 | import sys 19 | import argparse 20 | # Import DEM library 21 | try: 22 | from arsf_dem import dem_common 23 | from arsf_dem.dem_lidar import grass_lidar 24 | from arsf_dem import dem_common_functions 25 | except ImportError as err: 26 | print("Could not import ARSF DEM library.", file=sys.stderr) 27 | print(err, file=sys.stderr) 28 | sys.exit(1) 29 | 30 | #: Debug mode 31 | DEBUG = dem_common.DEBUG 32 | 33 | def load_files_to_grass(in_lidar_files, 34 | in_projection=dem_common.DEFAULT_LIDAR_PROJECTION_GRASS, 35 | resolution=dem_common.DEFAULT_LIDAR_RES_METRES, 36 | lidar_format='LAS', 37 | raster_type='DSM', 38 | drop_noise=True, 39 | load_as_vector=False): 40 | 41 | drop_class = None 42 | keep_class = None 43 | las2txt_flags = None 44 | returns_to_keep = 'all' 45 | 46 | # Set options for raster type to be exported 47 | # DSM - first returns (top of canopy) 48 | if raster_type.upper() == 'DSM': 49 | val_field = 'z' 50 | las2txt_flags = '-first_only' 51 | returns_to_keep = 'first' 52 | # DSM - last returns (ground surface) 53 | elif raster_type.upper() == 'DTM': 54 | val_field = 'z' 55 | las2txt_flags = '-last_only' 56 | returns_to_keep = 'last' 57 | # DEM - general term (all returns) 58 | elif raster_type.upper() == 'DEM': 59 | val_field = 'z' 60 | drop_class = 7 61 | returns_to_keep = 'all' 62 | # UNFILTEREDDEM - keep all values 63 | elif raster_type.upper() == 'UNFILTEREDDEM': 64 | val_field = 'z' 65 | elif raster_type.upper() == 'INTENSITY': 66 | val_field = 'intensity' 67 | else: 68 | raise Exception('raster_type "{}" was not recognised'.format(raster_type)) 69 | 70 | if drop_noise: 71 | drop_class = 7 72 | 73 | # Expect a list of files, if passed in string 74 | # create list. 75 | if isinstance(in_lidar_files,str): 76 | in_lidar_files = [in_lidar_files] 77 | 78 | # If a directory, look for files 79 | if os.path.isdir(in_lidar_files[0]): 80 | if lidar_format.upper() == 'LAS': 81 | in_lidar_files_list = glob.glob( 82 | os.path.join(in_lidar_files[0],'*[Ll][Aa][Ss]')) 83 | in_lidar_files_list.extend(glob.glob( 84 | os.path.join(in_lidar_files[0],'*[Ll][Aa][Zz]'))) 85 | 86 | # If ASCII format or not las files found check for txt files 87 | if lidar_format.upper() == 'ASCII' or len(in_lidar_files_list) == 0: 88 | in_lidar_files_list = glob.glob( 89 | os.path.join(in_lidar_files[0],'*txt')) 90 | if len(in_lidar_files_list) != 0: 91 | lidar_format = 'ASCII' 92 | 93 | # Check if wild character has been passed in which wasn't expanded (e.g., on windows) 94 | # or no matching files were found (which will raise exception later). 95 | elif in_lidar_files[0].find('*') > -1: 96 | in_lidar_files_list = glob.glob(in_lidar_files[0]) 97 | else: 98 | in_lidar_files_list = in_lidar_files 99 | if os.path.splitext(in_lidar_files_list[0])[-1].lower() != '.las' \ 100 | and os.path.splitext(in_lidar_files_list[0])[-1].lower() != '.laz': 101 | lidar_format = 'ASCII' 102 | 103 | if len(in_lidar_files_list) == 0: 104 | raise Exception('No lidar files were passed in or found from path provided') 105 | 106 | # Create variable for GRASS path 107 | grassdb_path = None 108 | grass_dataset_names = [] 109 | 110 | # Create raster from point cloud files 111 | totlines = len(in_lidar_files_list) 112 | 113 | for linenum,in_lidar_file in enumerate(in_lidar_files_list): 114 | dem_common_functions.PrintTermWidth('Loading "{0}" to GRASS ({1}/{2})'.format(os.path.split(in_lidar_file)[-1],linenum+1, totlines)) 115 | # Check file exists 116 | if not os.path.isfile(in_lidar_file): 117 | raise Exception('Could not open "{}"'.format(in_lidar_file)) 118 | 119 | if lidar_format.upper() == 'LAS' and not load_as_vector: 120 | out_grass_name, grassdb_path = \ 121 | grass_lidar.las_to_raster(in_lidar_file, 122 | out_raster=None, 123 | remove_grassdb=False, 124 | grassdb_path=grassdb_path, 125 | val_field=val_field, 126 | drop_class=drop_class, 127 | keep_class=keep_class, 128 | las2txt_flags=las2txt_flags, 129 | projection=in_projection, 130 | bin_size=resolution, 131 | out_raster_type=raster_type) 132 | elif lidar_format.upper() == 'LAS' and load_as_vector: 133 | out_grass_name, grassdb_path = \ 134 | grass_lidar.las_to_vector(in_lidar_file, 135 | grassdb_path=grassdb_path, 136 | drop_class=drop_class, 137 | keep_class=keep_class, 138 | las2txt_flags=las2txt_flags, 139 | projection=in_projection) 140 | elif lidar_format.upper() == 'ASCII' and not load_as_vector: 141 | out_grass_name, grassdb_path = \ 142 | grass_lidar.ascii_to_raster(in_lidar_file, 143 | out_raster=None, 144 | remove_grassdb=False, 145 | grassdb_path=grassdb_path, 146 | val_field=val_field, 147 | drop_class=drop_class, 148 | keep_class=keep_class, 149 | returns=returns_to_keep, 150 | projection=in_projection, 151 | bin_size=resolution, 152 | out_raster_type=raster_type) 153 | elif lidar_format.upper() == 'ASCII' and load_as_vector: 154 | out_grass_name, grassdb_path = \ 155 | grass_lidar.ascii_to_vector(in_lidar_file, 156 | grassdb_path=grassdb_path, 157 | drop_class=drop_class, 158 | keep_class=keep_class, 159 | returns=returns_to_keep, 160 | projection=in_projection) 161 | 162 | grass_dataset_names.append(out_grass_name) 163 | 164 | print('Loaded the following files:') 165 | for grass_dataset in grass_dataset_names: 166 | print(' {}'.format(grass_dataset)) 167 | print('To GRASS database: {0}/{1}/PERMANENT'.format(grassdb_path, 168 | in_projection)) 169 | 170 | if __name__ == '__main__': 171 | description_str = '''Load LiDAR files into GRASS for further processing. 172 | 173 | For LAS files converts to ASCII first using las2txt. 174 | 175 | Points flagged as noise (class 7) are dropped before being added. 176 | 177 | Performs the following steps: 178 | 179 | 1. Sets up GRASS database in the required projection 180 | 2. Loads converted files using r.in.xyz 181 | 182 | Then returns the path of the database which can be opened using: 183 | 184 | grass PATH_TO_DATABASE 185 | 186 | For examples of futher processing see: 187 | 188 | https://grasswiki.osgeo.org/wiki/LIDAR 189 | 190 | Created by ARSF-DAN at Plymouth Marine Laboratory (PML) 191 | and is made available under the terms of the GPLv3 license. 192 | 193 | ''' 194 | 195 | try: 196 | parser = argparse.ArgumentParser(description=description_str,formatter_class=argparse.RawDescriptionHelpFormatter) 197 | parser.add_argument("lidarfiles", nargs='+',type=str, 198 | help="List or directory containing input LiDAR files") 199 | parser.add_argument('-r', '--resolution', 200 | metavar='Resolution', 201 | help='Resolution for output DEM (default={})'.format(dem_common.DEFAULT_LIDAR_RES_METRES), 202 | default=dem_common.DEFAULT_LIDAR_RES_METRES, 203 | required=False) 204 | parser.add_argument('--projection', 205 | metavar='In Projection', 206 | help='Input projection (e.g., UTM30N)', 207 | default=dem_common.DEFAULT_LIDAR_PROJECTION_GRASS, 208 | required=False) 209 | parser.add_argument('-t', '--rastertype', 210 | metavar='Output raster type', 211 | help='Raster type - determines the lidar returns to' 212 | ' load into GRASS. For all select DEM (default),' 213 | ' for first only select DSM,' 214 | ' for last only select DTM.', 215 | default='DEM', 216 | required=False) 217 | parser.add_argument('--vector', 218 | action='store_true', 219 | default=False, 220 | help='Load points as vector.' 221 | ' WARNING - this can require a lot of memory' 222 | ' ensure sufficient RAM is available before ' 223 | ' using this options', 224 | required=False) 225 | args=parser.parse_args() 226 | 227 | load_files_to_grass(args.lidarfiles, 228 | in_projection=args.projection, 229 | resolution=args.resolution, 230 | lidar_format='LAS', 231 | raster_type=args.rastertype, 232 | load_as_vector=args.vector, 233 | drop_noise=True) 234 | 235 | except KeyboardInterrupt: 236 | sys.exit(2) 237 | except Exception as err: 238 | if DEBUG: 239 | raise 240 | dem_common_functions.ERROR(err) 241 | sys.exit(1) 242 | -------------------------------------------------------------------------------- /scripts/mosaic_dem_tiles.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | python "%~dp0\mosaic_dem_tiles.py" %* 3 | -------------------------------------------------------------------------------- /scripts/mosaic_dem_tiles.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #Description: A script to create a DEM from tiles 3 | """ 4 | Author: Dan Clewley (dac) 5 | 6 | Created on: 08 June 2015 7 | 8 | """ 9 | 10 | # This file has been created by ARSF Data Analysis Node and 11 | # is licensed under the GPL v3 Licence. A copy of this 12 | # licence is available to download with this file. 13 | 14 | import sys 15 | import argparse 16 | import glob 17 | from arsf_dem import dem_utilities 18 | from arsf_dem import dem_common 19 | from arsf_dem import dem_common_functions 20 | 21 | description_str = """ 22 | mosaic_dem_tiles.py 23 | 24 | Create a mosaic from DEM tiles (e.g., ASTER / SRTM) and apply offsets 25 | so heights are relative to WGS-84 ellipsoid rather than geoid. 26 | 27 | Entire extent of DEM is kept. If subsetting to navigation data is required 28 | use 'create_apl_dem.py' instead. See example 7 in help. 29 | 30 | """ 31 | try: 32 | parser = argparse.ArgumentParser(description=description_str, formatter_class=argparse.RawDescriptionHelpFormatter) 33 | parser.add_argument("demtiles", nargs='+',type=str, help="Tiles to create DEM from") 34 | parser.add_argument('-o', '--outdem', 35 | metavar ='Out DEM', 36 | help ='Output name for mosaiced DEM', 37 | required=True, 38 | default=None) 39 | args=parser.parse_args() 40 | 41 | # On Windows don't have shell expansion so fake it using glob 42 | if args.demtiles[0].find('*') > -1: 43 | input_tile_list = glob.glob(args.demtiles[0]) 44 | else: 45 | input_tile_list = args.demtiles 46 | 47 | out_mosaic, grassdb_path = dem_utilities.patch_files(input_tile_list, 48 | out_file=None, 49 | import_to_grass=True, 50 | projection='WGS84LL', 51 | nodata=dem_common.NODATA_VALUE, 52 | remove_grassdb=False) 53 | 54 | dem_utilities.offset_null_fill_dem(out_mosaic, 55 | out_demfile=args.outdem, 56 | import_to_grass=False, 57 | separation_file=dem_common.WWGSG_FILE, 58 | ascii_separation_file=dem_common.WWGSG_FILE_IS_ASCII, 59 | fill_nulls=True, 60 | projection='WGS84LL', 61 | nodata=dem_common.NODATA_VALUE, 62 | out_raster_format=dem_utilities.get_gdal_type_from_path(args.outdem), 63 | remove_grassdb=True, 64 | grassdb_path=grassdb_path) 65 | 66 | except KeyboardInterrupt: 67 | sys.exit(2) 68 | except Exception as err: 69 | dem_common_functions.ERROR(err) 70 | sys.exit(1) 71 | -------------------------------------------------------------------------------- /scripts/spdlib_create_dems_from_las.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | python "%~dp0\spdlib_create_dems_from_las.py" %* 3 | -------------------------------------------------------------------------------- /scripts/spdlib_create_dems_from_las.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #Description: A script to generate DTM, DSM and CHM from LAS files using SPDLib 3 | """ 4 | Author: Dan Clewley (dac) 5 | 6 | Generates a DTM, DSM and optionally CHM from LAS files using SPDLib 7 | 8 | Created on: 09 May 2016 9 | 10 | """ 11 | from __future__ import print_function 12 | import argparse 13 | import os 14 | import shutil 15 | import sys 16 | import tempfile 17 | 18 | from arsf_dem import dem_common 19 | from arsf_dem import dem_lidar 20 | from arsf_dem import dem_common_functions 21 | from arsf_dem import grass_library 22 | 23 | #: Debug mode 24 | DEBUG = dem_common.DEBUG 25 | 26 | #: Set interpolation method to use 27 | interpolation = dem_common.SPD_DEFAULT_INTERPOLATION 28 | 29 | def create_directory(directory_name): 30 | """ 31 | Check if a directory exists and create if it doesn't 32 | """ 33 | if not os.path.isdir(directory_name): 34 | os.makedirs(directory_name) 35 | 36 | 37 | if __name__ == '__main__': 38 | description_str = '''Create a Digital Terrain Model (DTM), Digital Surface 39 | Model (DSM) and optionally Canopy Height Model (CHM) from LAS file(s). Uses 40 | SPDLib. 41 | 42 | SPDLib is available under a GPLv3 license. For more details see: 43 | 44 | http://spdlib.org/ 45 | 46 | Bunting, P., Armston, J., Clewley, D., & Lucas, R. M. (2013). Sorted pulse data 47 | (SPD) library-Part II: A processing framework for LiDAR data from pulsed laser 48 | systems in terrestrial environments. Computers and Geosciences, 56, 207-215. 49 | doi:10.1016/j.cageo.2013.01.010 50 | 51 | 'spdlib_create_dems_from_las' was created by ARSF-DAN at Plymouth Marine 52 | Laboratory (PML) and is made available under the terms of the GPLv3 license. 53 | 54 | ''' 55 | 56 | 57 | temp_dir = tempfile.mkdtemp(dir=dem_common.TEMP_PATH) 58 | try: 59 | parser = argparse.ArgumentParser(description=description_str) 60 | parser.add_argument("lasfile", nargs='+', type=str, 61 | help="Input LAS file") 62 | parser.add_argument('-o', '--out_dir', 63 | help ='Base output directory. ' 64 | 'Will create subdirectories for "dsm", "dtm" ' 65 | 'and "chm" within this', 66 | required=True) 67 | parser.add_argument('-r', '--resolution', 68 | help ='Resolution for output DEM (Default={})' 69 | ''.format(dem_common.DEFAULT_LIDAR_RES_METRES), 70 | default=dem_common.DEFAULT_LIDAR_RES_METRES, 71 | required=False) 72 | parser.add_argument('--projection', 73 | help ='Input projection (e.g., UTM30N)', 74 | default=None, 75 | required=True) 76 | parser.add_argument('--chm', 77 | help='Export raster Canopy Height Model (CHM)', 78 | default=False, 79 | action='store_true', 80 | required=False) 81 | args=parser.parse_args() 82 | 83 | base_out_dir = os.path.abspath(args.out_dir) 84 | 85 | out_dtm_dir = os.path.join(base_out_dir, 'dtm') 86 | out_dsm_dir = os.path.join(base_out_dir, 'dsm') 87 | out_chm_dir = os.path.join(base_out_dir, 'chm') 88 | 89 | # Create output directories if they don't exist 90 | create_directory(out_dtm_dir) 91 | create_directory(out_dsm_dir) 92 | if args.chm: 93 | create_directory(out_chm_dir) 94 | 95 | las_basename = os.path.basename(args.lasfile[0]) 96 | las_basename = os.path.splitext(las_basename)[0] 97 | 98 | if len(args.lasfile) > 0: 99 | las_basename = '{}_merged'.format(las_basename) 100 | 101 | out_dtm = os.path.join(out_dtm_dir, '{}_dtm.dem'.format(las_basename)) 102 | out_dsm = os.path.join(out_dsm_dir, '{}_dsm.dem'.format(las_basename)) 103 | out_chm = os.path.join(out_chm_dir, '{}_chm.dem'.format(las_basename)) 104 | 105 | # Tempory LAS file (with class 7 dropped) 106 | tmp_las_file = os.path.join(temp_dir, 'merged_las.las') 107 | # Tempory SPD file (if classifying ground returns) 108 | spd_convert_out = os.path.join(temp_dir, 'merged_las2spd.spd') 109 | 110 | # WKT file containing projection (required by SPDLib) 111 | wkt_tmp = os.path.join(temp_dir, 112 | '{}.wkt'.format(args.projection.lower())) 113 | grass_library.grass_location_to_wkt(args.projection, wkt_tmp) 114 | # Merge LAS files (if multiple are passed in) and drop 115 | # class 7 116 | # SPDLib doesn't ignore points flagged as noise so 117 | # need to create LAS file without class 7 anyway - merge command is 118 | # easiest way to do this. 119 | args.lasfile = [os.path.abspath(f) for f in args.lasfile] 120 | dem_lidar.lastools_lidar.merge_las(args.lasfile, 121 | tmp_las_file, 122 | drop_class=7) 123 | 124 | # Create DTM - keep SPD file 125 | grd_spd = dem_lidar.spdlib_lidar.las_to_dtm(tmp_las_file, out_dtm, 126 | interpolation=interpolation, 127 | out_raster_format='ENVI', 128 | bin_size=args.resolution, 129 | wkt=wkt_tmp, 130 | keep_spd=True) 131 | 132 | # Generate DSM from existing SPD file 133 | dem_lidar.spdlib_lidar.spd_to_dsm(grd_spd, out_dsm, 134 | interpolation=interpolation, 135 | out_raster_format='ENVI', 136 | bin_size=args.resolution) 137 | 138 | # If a CHM is requested generate this from existing SPD file 139 | if args.chm: 140 | dem_lidar.spdlib_lidar.spd_to_chm(grd_spd, out_chm, 141 | interpolation=interpolation, 142 | out_raster_format='ENVI', 143 | bin_size=args.resolution) 144 | 145 | # Remove all temp files 146 | shutil.rmtree(temp_dir) 147 | 148 | except KeyboardInterrupt: 149 | shutil.rmtree(temp_dir) 150 | sys.exit(2) 151 | except Exception as err: 152 | if DEBUG: 153 | raise 154 | shutil.rmtree(temp_dir) 155 | dem_common_functions.ERROR(err) 156 | sys.exit(1) 157 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Setup script for arsf_dem 4 | 5 | This file has been created by ARSF Data Analysis Node and 6 | is licensed under the GPL v3 Licence. A copy of this 7 | licence is available to download with this file. 8 | 9 | """ 10 | 11 | import glob, os, sys 12 | from distutils.core import setup 13 | 14 | # For windows also copy batch files, incase .py files 15 | # aren't associated with Python. 16 | if sys.platform == 'win32': 17 | scripts_list = glob.glob('scripts\\*.py') 18 | scripts_list.extend(glob.glob('scripts\\*.bat')) 19 | else: 20 | scripts_list = glob.glob('scripts/*.py') 21 | 22 | setup( 23 | name='arsf_dem', 24 | version = '0.1', 25 | description = 'ARSF-DAN utilities for working with DEMs', 26 | url = 'https://arsf-dan.nerc.ac.uk/trac/', 27 | packages = ['arsf_dem','arsf_dem.dem_lidar'], 28 | package_dir={'arsf_dem': 'arsf_dem'}, 29 | package_data={'arsf_dem' : ['arsf_dem.cfg']}, 30 | data_files=[(os.path.join('share','grass_db_template','WGS84LL','PERMANENT'),glob.glob(os.path.join('data','grass_db_template','WGS84LL','PERMANENT','*'))), 31 | (os.path.join('share','grass_db_template','UKBNG','PERMANENT'),glob.glob(os.path.join('data','grass_db_template','UKBNG','PERMANENT','*')))], 32 | scripts = scripts_list, 33 | ) 34 | --------------------------------------------------------------------------------