I have a script that basically takes a low resolution raster grid and creates elevation band text file for this raster based on a high resolution DEM. The data can be accessed from here.
On Conda prompt I wrote:
python format_snow_params.py ~/VIC_GRID_Rstr1.tif ~/DEM.tif ~/snow_params.txt 492
Where VIC_GRID_Rstr1.tif is the lower resolution grid raster (basinMask), DEM is a high resolution raster(elvHiRes), snow_params.txt (outsnow) is the output file to generated and lastly 492 (interval) is the elevation interval in meters.
While running the script I get the following error:
File "format_snow_params.py", line 218, in <module>
main()
File "format_snow_params.py", line 212, in main
format_snow_params(sys.argv[1],sys.argv[2],sys.argv[3],sys.argv[4])
File "format_snow_params.py", line 70, in format_snow_params
elvhires[np.where(elvhires<0)] = np.nan
ValueError: cannot convert float NaN to integer
The whole script (obtained from github) is as follows:
#******************************************************************************
# FILE: format_snow_params.py
# AUTHOR: Kel Markert
# EMAIL: kel.markert@nasa.gov
# ORGANIZATION: NASA-SERVIR, UAH/ESSC
# MODIFIED BY: n/a
# CREATION DATE: 28 Oct. 2016
# LAST MOD DATE: 03 Apr. 2017
# PURPOSEe This script takes geotiff data for snow band parameterization and converts
# it to the needed text format for the VIC model
# DEPENDENCIES: numpy, osgeo (gdal)
#******************************************************************************
# import dependencies
from __future__ import print_function
import os
import sys
import warnings
import numpy as np
from osgeo import gdal
from osgeo.gdalnumeric import *
from osgeo.gdalconst import *
# set system to ignore simple warnings
warnings.simplefilter("ignore")
def format_snow_params(basinMask,elvHiRes,outSnow,interval):
"""
FUNCTION: format_snow_params
ARGUMENTS: basinMask - path to template raster to run VIC model at
elvHiRes - path elevation raster dataset at native resolution
outsnow - path output snow parameter file
interval - vertical distance to do equal interval segmentation
KEYWORDS: n/a
RETURNS: n/a
NOTES: Does not return a variable but writes an output file
"""
band = 1 # constant variable for reading in data
interval = int(interval) # force equal interval value to be int type
# make a list of input raster files
infiles = [basinMask,elvHiRes]
# try to read in the raster data
try:
# read basin grid raster
ds = gdal.Open(infiles[0],GA_ReadOnly)
b1 = ds.GetRasterBand(band)
mask = BandReadAsArray(b1)
maskRes = ds.GetGeoTransform()[1]
ds = None
b1 = None
# read hi res elevation raster
ds = gdal.Open(infiles[1],GA_ReadOnly)
b1 = ds.GetRasterBand(band)
elvhires = BandReadAsArray(b1)
clsRes = ds.GetGeoTransform()[1]
ds = None
b1 = None
# if not working, give error message
except AttributeError:
raise IOError('Raster file input error, check that all paths are correct')
# mask elevation values less than 0
elvhires[np.where(elvhires<0)] = np.nan
# get ratio of high resoltion to low resolution
clsRatio = int(maskRes/clsRes)
# check if the output parameter file exists, if so delete it
if os.path.exists(outSnow)==True:
os.remove(outSnow)
nbands = [] # blank list
# try to write to output snow parameter file
try:
with open(outSnow, 'w') as f:
cnt = 1 # set grid cell id counter
# pass counter
pass_counter = range(2)
# perform two passes on the raster data
# 1) to grab the maximum number of bands for a given pixel
# 2) to calculate the snow band parameters and write to output file
for pass_cnt in pass_counter:
# loop over each pixel in the template raster
for i in range(mask.shape[0]):
cy1 = i*clsRatio
cy2 = cy1+clsRatio
for j in range(mask.shape[1]):
cx1 = j*clsRatio
cx2 = cx1+clsRatio
# get all hi res pixels in a template pixel
tmp = elvhires[cy1:cy2,cx1:cx2]
if tmp.size == 0:
tmp=tmp2[:]
# create blank array for number of bands calculation...
if mask[i,j] == 1: # ...if it is not a masked pixel
tmp2=tmp
if np.all(tmp == np.nan) == True:
tmp[:,:] = 0
# find min and max values for interval
minelv = np.nanmin(tmp.astype(int)) - (np.nanmin(tmp.astype(int))%interval)
maxelv = np.nanmax(tmp.astype(int)) + (np.nanmax(tmp.astype(int))%interval)
#print(np.min(tmp).mask)
# create an array of band limits
bands = np.arange(minelv, maxelv+interval,interval)
bcls = np.zeros_like(tmp)
bcls[:,:] = -1
# get the number of bands per pixel
for b in range(bands.size-1):
bcls[np.where((tmp>=bands[b])&(tmp<bands[b+1]))] = b # band counter
# if it's the first pass get number of bands for each pixel
if pass_cnt == 0:
uniqcnt = np.unique(bcls[np.where(tmp>0)])
nbands.append(uniqcnt.size) # save to a list for second pass
if pass_cnt == 1:
uniqcnt = np.unique(bcls[np.where(tmp>0)])
#clscnt = np.bincount(tmp.ravel())
f.write('{0}\t'.format(cnt)) # write grid cell id
# find frational area for each band and write to file
for c in range(maxbands):
try:
idx = np.where(bcls==uniqcnt[c])
num = np.float(bcls[np.where(bcls>=0)].size)
if num == 0:
num = np.float(idx[0].size)
frac = np.float(idx[0].size) / num
except IndexError:
frac = 0
f.write('{0:.4f}\t'.format(frac))
# calculate the mean elevation for each band and write to file
for c in range(maxbands):
try:
idx = np.where(bcls==uniqcnt[c])
muelv = np.nanmean(tmp[idx])
except IndexError:
muelv = 0
f.write('{0:.4f}\t'.format(muelv))
# calculate the precipitation fractions and write to file
for c in range(maxbands):
try:
idx = np.where(bcls==uniqcnt[c])
num = np.float(bcls[np.where(bcls>=0)].size)
if num == 0:
num = np.float(idx[0].size)
frac = np.float(idx[0].size) / num
except IndexError:
frac = 0
f.write('{0:.4f}\t'.format(frac))
f.write('\n') # write return value for new line
if pass_cnt == 1 & mask[i,j] == 1:
cnt += 1 # plus one to the grid cell id counter
if pass_cnt == 0:
maxbands = max(nbands) # maximum number of bands for a pixel
# print the number of bands for user to input into global parameter file
print('Number of maximum bands: {0}'.format(maxbands))
# except raise an error when it doesn't work
except IOError:
raise IOError('Cannot write output file, error with output snow parameter file path')
return
def main():
n_args = len(sys.argv)
# Check user inputs
if n_args != 5:
print ("Wrong user input")
print ("Script writes the snow band parameter file for the VIC model")
print ("usage: python format_snow_params.py <template raster> <elevation raster> <output snow band file> <interval for snow bands>")
print ("Exiting system...")
sys.exit()
else:
# Pass command line arguments into function
format_snow_params(sys.argv[1],sys.argv[2],sys.argv[3],sys.argv[4])
return
# Execute the main level program if run as standalone
if __name__ == "__main__":
main()
I did a try a solution mentioned here, by basically changing the line:
elvhires[np.where(elvhires<0)] = np.nan
to
elvhires=np.where(elvhires<0,np.NaN,elvhires)
But this then gives me another error:
File "format_snow_params.py", line 218, in <module>
main()
File "format_snow_params.py", line 212, in main
format_snow_params(sys.argv[1],sys.argv[2],sys.argv[3],sys.argv[4])
File "format_snow_params.py", line 188, in format_snow_params
maxbands = max(nbands) # maximum number of bands for a pixel
ValueError: max() arg is an empty sequence
How can I resolve this issue?
Update 1
Based on the feedback, I exported (using ArcGIS) the DEM (DEM_0-n2) again in .tif format and set no data to 0, but the Float Nan error still remains. And if I remove the line
elvhires[np.where(elvhires<0)] = np.nan
then I get the error at line 188, which is:
ValueError: max() arg is an empty sequence
Update 2
Even when I view the DEM values in array form in Python, the actual values of the DEM start at column 910-1134. The rest of the columns are all zeros. I am attaching an screenshot of the array just for reference. The script can also be downloaded from the same link. I also tried the script both in Python 2.7 and 3.6, but the error still remains the same. Please let me know if there are data access issues.
Best Answer
It might be worth avoiding use of
np.NaN
altogether. NaN literally means "not a number", and it cannot be converted to an integer.There are two ways of doing this, depending on the nature of the data, and what the negative numbers mean in that data (it is the negative values that the script is attempting to convert to
np.Nan
).Option 1:
Delete that
np.Nan
line from the script altogether, and leave the negative numbers as they are, and allow them to be converted to their negative integer equivalents.Option 2:
Choose a number that never appears in the raster (eg,
99999
) and use that instead. Depending on the nature of the data (and what the negative numbers represent), perhaps even0
might be an appropriate number.Depending on how you intend to use the data afterwards, and if it is appropriate, you may then be able to configure the dataset to know that your chosen number represents "nodata" (in ArcGIS for example, you can select a "nodata" value for rasters).