FV3 Bundle
netcdf_to_odb2.F90
Go to the documentation of this file.
1 ! (C) Copyright 2018 UCAR
2 !
3 ! This software is licensed under the terms of the Apache Licence Version 2.0
4 ! which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5 
6 !> Fortran program to convert the existing NetCDF file into an ODB-2
7 
9 
10 use netcdf, only: &
11  nf90_close, &
12  nf90_get_att, &
13  nf90_global, &
14  nf90_noerr, &
15  nf90_nowrite, &
16  nf90_open, &
17  nf90_strerror
18 
19 use odb_c_binding, only: &
20  odb_integer, &
21  odb_real
22 
23 use odb_helper_mod, only: &
26 
27 use odbql_wrappers, only: &
28  odbql, &
29  odbql_bind_double, &
30  odbql_bind_int, &
31  odbql_close, &
32  odbql_finalize, &
33  odbql_float, &
34  odbql_integer, &
35  odbql_open, &
36  odbql_prepare_v2, &
37  odbql_step, &
38  odbql_stmt
39 
40 use, intrinsic :: iso_c_binding, only: &
41  c_double, &
42  c_int
43 
44 implicit none
45 
46 character(len=1000) :: filename
47 character(len=1000) :: output_filename
48 integer :: rc
49 integer :: file_id
50 character(len=7000) :: messages(4)
51 real, allocatable :: latitudes(:)
52 real, allocatable :: longitudes(:)
53 real, allocatable :: times_netcdf(:)
54 real, allocatable :: observations(:)
55 real, allocatable :: pressures(:)
56 integer, allocatable :: times(:)
57 integer, allocatable :: dates(:)
58 type(odbql) :: odb
59 integer(kind=c_int) :: odb_rc
60 character(len=*), parameter :: odb_column_names(9) = [character(len=18) :: "seqno", "lat", "lon", "entryno", &
61  "date", "time", "varno", "vertco_reference_1", "obsvalue"]
62 integer :: odb_column_types(9) = [odb_integer, odb_real, odb_real, odb_integer, odb_integer, &
63  odb_integer, odb_integer, odb_real, odb_real]
64 character(len=5000) :: create_table_sql_string
65 character(len=5000) :: insert_into_sql_string
66 character(len=5000) :: unparsed_sql
67 type(odbql_stmt) :: stmt
68 integer(kind=c_int) :: col_name_index
69 integer :: iob
70 real(kind=c_double) :: buffer(size(odb_column_names))
71 integer :: date_time
72 integer :: arg_num
73 character(len=2000) :: arguments(2)
74 
75 call get_command_argument (1, filename)
76 call get_command_argument (2, output_filename)
77 
78 if (filename == "" .or. output_filename == "") then
79  write (messages(1), '(a,i0)') "Please provide two arguments, input-filename output-filename"
80  call fail (messages(1:1))
81 end if
82 
83 rc = nf90_open(filename, nf90_nowrite, file_id)
84 if (rc /= nf90_noerr) then
85  write (messages(1), '(a,i0)') "nf90_open failed with rc = ", rc
86  write (messages(2), '(2a)') "file name = ", trim(filename)
87  messages(3) = nf90_strerror(rc)
88  call fail (messages(1:3))
89 end if
90 
91 call get_netcdf_array (file_id, 'Latitude', latitudes)
92 call get_netcdf_array (file_id, 'Longitude', longitudes)
93 call get_netcdf_array (file_id, 'Time', times_netcdf)
94 
95 allocate (times(size(times_netcdf)))
96 allocate (dates(size(times_netcdf)))
97 
98 rc = nf90_get_att(file_id, nf90_global, "date_time", date_time)
99 if (rc /= nf90_noerr) then
100  write (messages(1), '(a,i0)') "nf90_get_at failed for date_time with rc = ", rc
101  write (messages(2), '(a,i0)') "file id = ", file_id
102  messages(3) = nf90_strerror(rc)
103  call fail (messages(1:3))
104 end if
105 
106 dates = date_time / 100
107 times = (date_time - (date_time / 100) * 100) * 10000
108 times = times + 10000 * int(times_netcdf)
109 times_netcdf = times_netcdf - int(times_netcdf)
110 where (times_netcdf >= 0)
111  times = times + nint(60 * times_netcdf) * 100
112 elsewhere
113  times = times - 10000
114  times = times + nint(60 * (1 - abs(times_netcdf))) * 100
115 end where
116 
117 call get_netcdf_array (file_id, 'Observation', observations)
118 call get_netcdf_array (file_id, 'Pressure', pressures)
119 
120 rc = nf90_close(file_id)
121 if (rc /= nf90_noerr) then
122  write (messages(1), '(a,i0)') "nf90_close failed with rc = ", rc
123  write (messages(2), '(a,i0)') "file id = ", file_id
124  messages(3) = nf90_strerror(rc)
125  call fail (messages(1:3))
126 end if
127 
128 call odbql_open ("", odb, odb_rc)
129 if (odb_rc /= 0) then
130  write (messages(1), '(a,i0)') "odbql_open failed with rc = ", odb_rc
131  call fail (messages(1:1))
132 end if
133 
134 call create_table_sql (odb_column_names, odb_column_types, output_filename, create_table_sql_string)
135 
136 call odbql_prepare_v2 (odb, trim(create_table_sql_string), -1_c_int, stmt, unparsed_sql, odb_rc)
137 if (odb_rc /= 0) then
138  write (messages(1), '(a,i0)') "odbql_prepare_v2 failed (create table), rc = ", odb_rc
139  messages(2) = create_table_sql_string
140  call fail (messages(1:2))
141 end if
142 
143 call insert_into_sql (odb_column_names, insert_into_sql_string)
144 
145 call odbql_prepare_v2 (odb, trim(insert_into_sql_string), -1_c_int, stmt, unparsed_sql, odb_rc)
146 if (odb_rc /= 0) then
147  write (messages(1), '(a,i0)') "odbql_prepare_v2 failed (insert into), rc = ", odb_rc
148  messages(2) = insert_into_sql_string
149  call fail (messages(1:2))
150 end if
151 
152 do iob = 1, size(latitudes)
153  buffer(1) = iob
154  buffer(2) = latitudes(iob)
155  buffer(3) = longitudes(iob)
156  buffer(5) = dates(iob)
157  buffer(6) = times(iob)
158  buffer(7) = 2
159  buffer(4) = 1
160  buffer(8) = pressures(iob)
161  buffer(9) = observations(iob)
162  do col_name_index = 1, size (odb_column_names)
163  select case (odb_column_types(col_name_index))
164  case (odbql_float)
165  call odbql_bind_double (stmt, col_name_index, buffer(col_name_index))
166  case (odbql_integer)
167  call odbql_bind_int (stmt, col_name_index, nint(buffer(col_name_index), kind = c_int))
168  end select
169  end do
170  call odbql_step (stmt)
171 end do
172 
173 call odbql_finalize (stmt)
174 
175 call odbql_close (odb)
176 
177 contains
178 
179 subroutine fail (messages)
181 character(len=*), intent(in) :: messages(:)
182 
183 integer :: i
184 
185 do i = 1, size(messages)
186  write (0, '(a)') trim(messages(i))
187  write (*, '(a)') trim(messages(i))
188 end do
189 call exit (1)
190 
191 end subroutine fail
192 
193 subroutine get_netcdf_array (file_id, var_name, output_array)
195 use netcdf, only: &
196  nf90_get_var, &
197  nf90_inq_varid, &
198  nf90_inquire_dimension, &
199  nf90_inquire_variable, &
200  nf90_noerr, &
201  nf90_strerror
202 
203 integer, intent(in) :: file_id
204 character(len=*), intent(in) :: var_name
205 real, allocatable, intent(out) :: output_array(:)
206 
207 integer :: var_id, rc, ndims, length, dimids(1)
208 character(len=7000) :: messages(5)
209 
210 rc = nf90_inq_varid(file_id, var_name, var_id)
211 if (rc /= nf90_noerr) then
212  write (messages(1), '(a,i0)') "nf90_inq_varid failed for " // trim(var_name) // " with rc = ", rc
213  write (messages(2), '(a,i0)') "file id = ", file_id
214  messages(3) = nf90_strerror(rc)
215  call fail (messages(1:3))
216 end if
217 
218 rc = nf90_inquire_variable(file_id, var_id, ndims = ndims, dimids = dimids)
219 if (rc /= nf90_noerr) then
220  write (messages(1), '(a,i0)') "nf90_inquire_variable failed for " // trim(var_name) // " with rc = ", rc
221  write (messages(2), '(a,i0)') "file id = ", file_id
222  write (messages(3), '(a,i0)') "var id = ", var_id
223  messages(4) = nf90_strerror(rc)
224  call fail (messages(1:4))
225 end if
226 
227 if (ndims /= 1) then
228  write (messages(1), '(a,i0)') "Should be a 1d arrays for " // trim(var_name)
229  write (messages(2), '(a,i0,a)') "Actually got ", ndims, "d array"
230  write (messages(3), '(a,i0)') "file id = ", file_id
231  write (messages(4), '(a,i0)') "var id = ", var_id
232  call fail (messages(1:4))
233 end if
234 
235 rc = nf90_inquire_dimension(file_id, dimids(1), len = length)
236 if (rc /= nf90_noerr) then
237  write (messages(1), '(a,i0)') "nf90_inquire_dimension failed for " // trim(var_name) // " with rc = ", rc
238  write (messages(2), '(a,i0)') "file id = ", file_id
239  write (messages(3), '(a,i0)') "var id = ", var_id
240  write (messages(4), '(a,i0)') "dim id = ", dimids(1)
241  messages(5) = nf90_strerror(rc)
242  call fail (messages(1:5))
243 end if
244 
245 allocate (output_array(length))
246 
247 rc = nf90_get_var(file_id, var_id, output_array)
248 if (rc /= nf90_noerr) then
249  write (messages(1), '(a,i0)') "nf90_get_var failed for " // trim(var_name) // " with rc = ", rc
250  write (messages(2), '(a,i0)') "file id = ", file_id
251  write (messages(2), '(a,i0)') "var id = ", var_id
252  messages(4) = nf90_strerror(rc)
253  call fail (messages(1:4))
254 end if
255 
256 end subroutine get_netcdf_array
257 
258 end program netcdf_to_odb2
subroutine insert_into_sql(column_names, sql)
subroutine get_netcdf_array(file_id, var_name, output_array)
subroutine fail(messages)
Fortran module with ODB API utility routines.
program netcdf_to_odb2
Fortran program to convert the existing NetCDF file into an ODB-2.
subroutine create_table_sql(column_names, column_types, filename, sql)
************************************************************************GNU Lesser General Public License **This file is part of the GFDL Flexible Modeling System(FMS). ! *! *FMS is free software without even the implied warranty of MERCHANTABILITY or *FITNESS FOR A PARTICULAR PURPOSE See the GNU General Public License *for more details **You should have received a copy of the GNU Lesser General Public *License along with FMS If see< http:! ***********************************************************************subroutine READ_RECORD_CORE_(unit, field, nwords, data, start, axsiz) integer, intent(in) ::unit type(fieldtype), intent(in) ::field integer, intent(in) ::nwords MPP_TYPE_, intent(inout) ::data(nwords) integer, intent(in) ::start(:), axsiz(:) integer(SHORT_KIND) ::i2vals(nwords)!rab used in conjunction with transfer intrinsic to determine size of a variable integer(KIND=1) ::one_byte(8) integer ::word_sz!#ifdef __sgi integer(INT_KIND) ::ivals(nwords) real(FLOAT_KIND) ::rvals(nwords)!#else! integer ::ivals(nwords)! real ::rvals(nwords)!#endif real(DOUBLE_KIND) ::r8vals(nwords) pointer(ptr1, i2vals) pointer(ptr2, ivals) pointer(ptr3, rvals) pointer(ptr4, r8vals) if(mpp_io_stack_size< nwords) call mpp_io_set_stack_size(nwords) call mpp_error(FATAL, 'MPP_READ currently requires use_netCDF option') end subroutine READ_RECORD_CORE_ subroutine READ_RECORD_(unit, field, nwords, data, time_level, domain, position, tile_count, start_in, axsiz_in)!routine that is finally called by all mpp_read routines to perform the read!a non-netCDF record contains:! field ID! a set of 4 coordinates(is:ie, js:je) giving the data subdomain! a timelevel and a timestamp(=NULLTIME if field is static)! 3D real data(stored as 1D)!if you are using direct access I/O, the RECL argument to OPEN must be large enough for the above!in a global direct access file, record position on PE is given by %record.!Treatment of timestamp:! We assume that static fields have been passed without a timestamp.! Here that is converted into a timestamp of NULLTIME.! For non-netCDF fields, field is treated no differently, but is written! with a timestamp of NULLTIME. There is no check in the code to prevent! the user from repeatedly writing a static field. integer, intent(in) ::unit, nwords type(fieldtype), intent(in) ::field MPP_TYPE_, intent(inout) ::data(nwords) integer, intent(in), optional ::time_level type(domain2D), intent(in), optional ::domain integer, intent(in), optional ::position, tile_count integer, intent(in), optional ::start_in(:), axsiz_in(:) integer, dimension(size(field%axes(:))) ::start, axsiz integer ::tlevel !, subdomain(4) integer ::i, error, is, ie, js, je, isg, ieg, jsg, jeg type(domain2d), pointer ::io_domain=> tlevel if(PRESENT(start_in) .AND. PRESENT(axsiz_in)) then if(size(start(! the data domain and compute domain must refer to the subdomain being passed ! In this ! since that attempts to gather all data on PE size(field%axes(:)) axsiz(i)