netcdf4raster.py 6.56 KB
Newer Older
Hoek, Steven's avatar
Hoek, Steven committed
1
# Copyright (c) 2004-2020 WUR, Wageningen
Hoek, Steven's avatar
Hoek, Steven committed
2
3
4
5
6
from .netcdf4envelope2d import Netcdf4Envelope2D;
from .gridenvelope2d import GridEnvelope2D;
from netCDF4 import Dataset;
import os;

Hoek, Steven's avatar
Hoek, Steven committed
7
8
__author__ = "Steven B. Hoek"

Hoek, Steven's avatar
Hoek, Steven committed
9
10
11
class Netcdf4Raster(Netcdf4Envelope2D):
    # Constants
    DATAFILEXT = 'nc4';
12
    _original_name = "Band1" # TODO: set it when a template file is opened for appending
Hoek, Steven's avatar
Hoek, Steven committed
13
14
15
16
17
18
19
20
21
    
    # Data attributes - assign some dummy values for the mean time
    name = "dummy.nc4";
    folder = os.getcwd();
    _dataset = None;
    _varname = "";
    _currow = 0;
    cellsize = 1;
    nodatavalue = -9999.0;
22
23
    __X = ""
    __Y = ""
Hoek, Steven's avatar
Hoek, Steven committed
24
25
26
27
28
29
    
    def __init__(self, filepath):
        # Retrieve the name from the filepath and assign - incl. extension
        self.name = os.path.basename(filepath);
        # Also derive the folder
        self.folder = os.path.dirname(filepath);
30
31
32
33
        
    def set_xy_ranges(self, xvar, yvar):
        self.__X = xvar
        self.__Y = yvar
Hoek, Steven's avatar
Hoek, Steven committed
34
35
36
37
38
39

    def open(self, mode, ncols=1, nrows=1, xll=0, yll=0, cellsize=1, nodatavalue=-9999.0):
        # If file does not exist and mode[0] = 'w', create it!
        fpath = os.path.join(self.folder, self.name);
        if mode[0] == 'a':
            if os.path.exists(fpath):
40
                print("About to open file " + fpath + " in append mode");
Hoek, Steven's avatar
Hoek, Steven committed
41
            self._dataset = Dataset(fpath, 'a', format='NETCDF4');
42
43
44
45
46
            self.nodatavalue = nodatavalue
            if (self.__X == "") or (self.__Y == ""):
                Netcdf4Envelope2D.__init__(self, self._dataset);
            else:
                Netcdf4Envelope2D.__init__(self, self._dataset, self.__X, self.__Y);
Hoek, Steven's avatar
Hoek, Steven committed
47
48
49
50
51
            return True;
        else:
            if os.path.exists(fpath):  
                # Open the netCDF4 file          
                ds = Dataset(fpath, 'r');
52
53
54
55
                if (self.__X == "") or (self.__Y == ""):
                    Netcdf4Envelope2D.__init__(self, ds);
                else:
                    Netcdf4Envelope2D.__init__(self, self._dataset, self.__X, self.__Y);   
Hoek, Steven's avatar
Hoek, Steven committed
56
57
                self._dataset = ds;
                self._varname = self._get_varname();
58
59
60
61
                try:
                    self.nodatavalue = ds.variables[self._varname]._FillValue
                except Exception as e:
                    print(e)
Hoek, Steven's avatar
Hoek, Steven committed
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
                return True;
            else: return False;    
    
    def _get_varname(self):
        # Establish which variable is stored in it - assume it's only 1!
        ds = self._dataset; 
        diff = set(ds.variables) - set(ds.dimensions);
        if len(diff) > 0:
            return diff.pop();
        else:
            return "";
            
    def readheader(self):
        pass;
    
    def __iter__(self):
        return self;
        
    def next(self, parseLine=True):
        # The netCDF4 format is not one that is read in a sequential manner. Is it a good idea
        # that this method is part of the interface?
        result = None;
        if self._varname != "":
            if parseLine:
                # Indexing: 1. years 2. by rows 3. columns
87
88
                # 3D: result = self.getVariables(self._varname)[:, self._currow, :]; 
                result = self.getVariables(self._varname)[self._currow, :] # 2D
Hoek, Steven's avatar
Hoek, Steven committed
89
90
91
92
93
94
95
96
            self._currow += 1;  # row index is zero-based! 
            return result;
    
    @staticmethod
    def getDataFileExt(self):
        return self.DATAFILEXT;
    
    def writeheader(self, name, long_name, units):
97
        # TODO: if _original_name is not one of the variables in the file -> error
Hoek, Steven's avatar
Hoek, Steven committed
98
99
100
101
102
103
        v = self._dataset.variables[self._original_name]
        v.setncattr("long_name", long_name)
        v.setncattr("units", units)
        if self._original_name != name:
            self._dataset.renameVariable(self._original_name, name)
        self._varname = name; 
104
        self._dataset.variables[self._varname].missing_value = self.nodatavalue
Hoek, Steven's avatar
Hoek, Steven committed
105
106
107
108
109
110
111
112
113
114
    
    def writenext(self, sequence_with_data):
        # Assume that the sequence is indexed 1. by year and 2. by column
        key = self._varname;
        if not hasattr(sequence_with_data, '__iter__'):
            raise ValueError("Input value not an array")
        if len(sequence_with_data.shape) != 2:
            raise ValueError("Input array has unexpected shape")
        if sequence_with_data.shape[1] != self.ncols:
            raise ValueError("Input array has unexpected dimension")
115
116
117
        # TODO adapt so that Netcdf files with different dimensions can be written
        # 3D: self._dataset.variables[key][:, self._currow, :] = sequence_with_data
        self._dataset.variables[key][self._currow, :] = sequence_with_data #2D
Hoek, Steven's avatar
Hoek, Steven committed
118
119
120
        self._currow += 1;
        
    def close(self):
121
122
123
        if not self._dataset is None:
            if self._dataset.isopen():
                self._dataset.close(); 
Hoek, Steven's avatar
Hoek, Steven committed
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141

    def reset(self):
        self._currow = 0; 
    
    def getEnvelope(self):
        # TODO: this cannot be solved by invoking super or so?
        return GridEnvelope2D(self.ncols, self.nrows, self.xll, self.yll, self.dx, self.dy);
        
    def getVariables(self, dimDescriptor):
        return self._dataset.variables[dimDescriptor];
    
    def getFilePath(self):
        filepath = os.path.join(self.folder, self.name);
        return filepath;
    
    def getVariableName(self):
        return self._varname;
    
142
143
144
145
146
147
148
    def get_value(self, i, k):    
        # Return the wanted value
        for _ in range(0, i): self.next(False)
        line = self.next()
        self.reset()
        return line[int(k)]
    
Hoek, Steven's avatar
Hoek, Steven committed
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
"""
def write_nc4_file(filename, crop_no):
    # Write the data to the file
    pass
       
def initialise_nc4_file(filename, crop_no):
    ds = None
    try:
        # Open file
        ncdfname_fp = os.path.join(run_settings.output_folder, filename)
        ds = Dataset(ncdfname_fp, 'w', format='NETCDF4')
        
        # Create dimensions        
        ds.createDimension("lon", 720)
        ds.createDimension("lat", 360)
        ds.createDimension("time", None)
        
        # Create variables and fill them
        longitudes = ds.createVariable('lon', np.float32, ('lon',))
        longitudes[:] = arange(-179.75, 180.25, 0.5)
        longitudes.units = 'degrees east'
        latitudes  = ds.createVariable('lat', np.float32, ('lat',))
        latitudes[:] = arange(-89.75, 90.25, 0.5)
        latitudes.units = 'degrees north'
        times = ds.createVariable('time', np.float64, ('time',))
        # times.units = 'hours since 0001-01-01 00:00:00.0' ???

    except RuntimeError:
        msg = "Error opening netCDF4 dataset on crop %i." % crop_no
        print msg
        logging.exception(msg)
    return ds
"""