1 /*--------------------------------------------------------------------
2  *
3  *	Copyright (c) 1991-2021 by the GMT Team (https://www.generic-mapping-tools.org/team.html)
4  *	See LICENSE.TXT file for copying and redistribution conditions.
5  *
6  *	This program is free software; you can redistribute it and/or modify
7  *	it under the terms of the GNU Lesser General Public License as published by
8  *	the Free Software Foundation; version 3 or any later version.
9  *
10  *	This program is distributed in the hope that it will be useful,
11  *	but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *	GNU Lesser General Public License for more details.
14  *
15  *	Contact info: www.generic-mapping-tools.org
16  *--------------------------------------------------------------------*/
17 /*
18  *
19  *	G M T _ N C . C   R O U T I N E S
20  *
21  * Takes care of all grd input/output built on NCAR's NetCDF routines.
22  * This version is intended to provide more general support for reading
23  * NetCDF files that were not generated by GMT. At the same time, the grids
24  * written by these routines are intended to be more conform COARDS conventions.
25  * These routines are to eventually replace the older gmt_cdf_ routines.
26  *
27  * Most functions will return with error message if an internal error is returned.
28  * There functions are only called indirectly via the GMT_* grdio functions.
29  *
30  * Author:  Remko Scharroo
31  * Date:    04-AUG-2005
32  * Version: 1
33  *
34  * Added support for chunked I/O, Florian Wobbe, June 2012.
35  *
36  * Functions include:
37  *
38  *  gmt_nc_read_grd_info:   Read header from file
39  *  gmt_nc_read_grd:        Read data set from file
40  *  gmt_nc_update_grd_info: Update header in existing file
41  *  gmt_nc_write_grd_info:  Write header to new file
42  *  gmt_nc_write_grd:       Write header and data set to new file
43  *  gmt_nc_read_cube_info:  Read information from cube file
44  *  gmt_nc_write_cube:      rite header and cube to new file(s)
45  *  gmtlib_is_nc_grid:	    Determine if we have a nc grid
46  *
47  * Private functions:
48  *  gmtnc_setup_chunk_cache:      Change the default HDF5 chunk cache settings
49  *  gmtnc_pad_grid:               Add padding to a grid
50  *  gmtnc_unpad_grid:             Remove padding from a grid
51  *  gmtnc_padding_copy:           Fill padding by replicating the border cells
52  *  gmtnc_padding_zero:           Fill padding with zeros
53  *  gmtnc_n_chunked_rows_in_cache Determines how many chunks to read at once
54  *  gmtnc_io_nc_grid              Does the actual netcdf I/O
55  *  gmtnc_netcdf_libvers          returns the netCDF library version
56  *  gmtnc_right_shift_grid
57  *  gmtnc_set_optimal_chunksize   Determines the optimal chunksize
58  *  gmtnc_get_units
59  *  gmtnc_put_units
60  *  gmtnc_check_step
61  *  gmtnc_grd_info
62  *  gmtnc_grid_fix_repeat_col
63  * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
64 
65 #include "gmt_dev.h"
66 #include "gmt_internals.h"
67 #include <netcdf.h>
68 
69 /* Declaration modifier for netcdf DLL support
70  * annoying: why can't netcdf.h do this on its own? */
71 #if defined WIN32 && ! defined NETCDF_STATIC
72 #define DLL_NETCDF
73 #endif
74 
75 /* HDF5 chunk cache: reasonable defaults assuming min. chunk size of 128x128 and type byte */
76 #define NC_CACHE_SIZE       33554432 /* 32MiB */
77 #define NC_CACHE_NELEMS     2053     /* prime > NC_CACHE_SIZE / (128*128*1byte) */
78 #define NC_CACHE_PREEMPTION 0.75f
79 
80 int gmt_cdf_grd_info (struct GMT_CTRL *GMT, int ncid, struct GMT_GRID_HEADER *header, char job);
81 int gmt_cdf_read_grd (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *header, gmt_grdfloat *grid, double wesn[], unsigned int *pad, unsigned int complex_mode);
82 
83 static int nc_libvers[] = {-1, -1, -1, -1}; /* holds the version of the netCDF library */
84 
85 static char *regtype[2] = {"gridline", "pixel"};
86 
gmtnc_netcdf_libvers(void)87 GMT_LOCAL const int * gmtnc_netcdf_libvers (void) {
88 	static bool inquired = false;
89 
90 	if (!inquired) {
91 		const char *vers_string = nc_inq_libvers();
92 		sscanf (vers_string, "%d.%d.%d.%d",
93 				nc_libvers, nc_libvers+1, nc_libvers+2, nc_libvers+3);
94 		inquired = true;
95 	}
96 
97 	return nc_libvers; /* return pointer to version array */
98 }
99 
100 /* netcdf I/O mode */
101 enum Netcdf_io_mode {
102 	k_put_netcdf = 0,
103 	k_get_netcdf
104 };
105 
106 /* Wrapper around gmt_nc_put_vara_grdfloat and gmt_nc_get_vara_grdfloat */
gmtnc_vara_grdfloat(int ncid,int varid,const size_t * startp,const size_t * countp,gmt_grdfloat * fp,unsigned io_mode)107 static inline int gmtnc_vara_grdfloat (int ncid, int varid, const size_t *startp, const size_t *countp, gmt_grdfloat *fp, unsigned io_mode) {
108 	if (io_mode == k_put_netcdf)	/* write netcdf */
109 		return gmt_nc_put_vara_grdfloat (ncid, varid, startp, countp, fp);
110 	/* read netcdf */
111 	return gmt_nc_get_vara_grdfloat (ncid, varid, startp, countp, fp);
112 }
113 
114 /* Wrapper around gmt_nc_put_varm_grdfloat and gmt_nc_get_varm_grdfloat */
gmtnc_varm_grdfloat(int ncid,int varid,const size_t * startp,const size_t * countp,const ptrdiff_t * stridep,const ptrdiff_t * imapp,gmt_grdfloat * fp,unsigned io_mode)115 static inline int gmtnc_varm_grdfloat (int ncid, int varid, const size_t *startp, const size_t *countp, const ptrdiff_t *stridep, const ptrdiff_t *imapp, gmt_grdfloat *fp, unsigned io_mode) {
116 	if (io_mode == k_put_netcdf)	/* write netcdf */
117 		return gmt_nc_put_varm_grdfloat (ncid, varid, startp, countp, stridep, imapp, fp);
118 	/* read netcdf */
119 	return gmt_nc_get_varm_grdfloat (ncid, varid, startp, countp, stridep, imapp, fp);
120 }
121 
122 /* Get number of chunked rows that fit into cache (32MiB) */
gmtnc_n_chunked_rows_in_cache(struct GMT_CTRL * GMT,struct GMT_GRID_HEADER * header,unsigned width,unsigned height,size_t * n_contiguous_chunk_rows,size_t * chunksize)123 GMT_LOCAL int gmtnc_n_chunked_rows_in_cache (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *header, unsigned width, unsigned height, size_t *n_contiguous_chunk_rows, size_t *chunksize) {
124 	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (header);
125 	nc_type z_type;		/* type of z variable */
126 	size_t z_size;		/* size of z variable */
127 	size_t z_bytes;		/* Number of bytes */
128 	size_t width_t = (size_t)width;
129 	size_t height_t = (size_t)height;
130 	unsigned yx_dim[2] = {HH->xy_dim[1], HH->xy_dim[0]}; /* because xy_dim not row major */
131 	int err, storage_in;
132 
133 	gmt_M_err_trap (nc_inq_vartype(HH->ncid, HH->z_id, &z_type)); /* type of z */
134 	gmt_M_err_trap (nc_inq_type(HH->ncid, z_type, NULL, &z_size)); /* size of z elements in bytes */
135 	gmt_M_err_trap (nc_inq_var_chunking (HH->ncid, HH->z_id, &storage_in, chunksize));
136 	if (storage_in != NC_CHUNKED) {
137 		/* default if NC_CONTIGUOUS */
138 		chunksize[yx_dim[0]] = 128;   /* 128 rows */
139 		chunksize[yx_dim[1]] = width_t; /* all columns */
140 	}
141 
142 	z_bytes = height_t * height_t * z_size;
143 	if (z_bytes > NC_CACHE_SIZE) {
144 		/* memory needed for subset exceeds the cache size */
145 		unsigned int level;
146 		size_t chunks_per_row = (size_t) ceil ((double)width / chunksize[yx_dim[1]]);
147 		*n_contiguous_chunk_rows = NC_CACHE_SIZE / (width_t * z_size) / chunksize[yx_dim[0]];
148 #ifdef NC4_DEBUG
149 		level = GMT_MSG_WARNING;
150 #else
151 		level = GMT_MSG_DEBUG;
152 #endif
153 		GMT_Report (GMT->parent, level,
154 				"processing at most %" PRIuS " (%" PRIuS "x%" PRIuS ") chunks at a time (%.1lf MiB)...\n",
155 				*n_contiguous_chunk_rows * chunks_per_row,
156 				*n_contiguous_chunk_rows, chunks_per_row,
157 				*n_contiguous_chunk_rows * z_size * width * chunksize[yx_dim[0]] / 1048576.0);
158 	}
159 	else
160 		*n_contiguous_chunk_rows = 0; /* all chunks fit into cache */
161 
162 	return GMT_NOERROR;
163 }
164 
165 /* Read and write classic or chunked netcdf files */
gmtnc_io_nc_grid(struct GMT_CTRL * GMT,struct GMT_GRID_HEADER * header,unsigned dim[],unsigned origin[],size_t stride,unsigned io_mode,gmt_grdfloat * grid,bool cube)166 GMT_LOCAL int gmtnc_io_nc_grid (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *header, unsigned dim[], unsigned origin[], size_t stride, unsigned io_mode, gmt_grdfloat* grid, bool cube) {
167 	/* io_mode = k_get_netcdf: read a netcdf file to grid
168 	 * io_mode = k_put_netcdf: write a grid to netcdf */
169 	int status = NC_NOERR;
170 	unsigned int d_off = (cube) ? 1 : 0;
171 	unsigned width = dim[1+d_off], height = dim[0+d_off];
172 	unsigned yx_dim[2];  /* because xy_dim is not row major! */
173 	size_t width_t = (size_t)width;
174 	size_t height_t = (size_t)height;
175 	size_t chunksize[5]; /* chunksize of z */
176 	size_t start[5] = {0,0,0,0,0}, count[5] = {1,1,1,1,1};
177 	size_t n_contiguous_chunk_rows = 0;  /* that are processed at once, 0 = all */
178 	ptrdiff_t imap[5] = {1,1,1,1,1}; /* mapping between dims of netCDF and in-memory grid */
179 	const ptrdiff_t onestride[5] = {1,1,1,1,1};	/* Passing this instead of NULL bypasses netCDF bug in 4.6.2 */
180 	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (header);
181 
182 	/* catch illegal io_mode in debug */
183 	assert (io_mode == k_put_netcdf || io_mode == k_get_netcdf);
184 
185 #ifdef NC4_DEBUG
186 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "%s n_columns:%u n_rows:%u x0:%u y0:%u y-order:%s\n",
187 			io_mode == k_put_netcdf ? "writing," : "reading,",
188 			dim[1+d_off], dim[0+d_off], origin[1], origin[0],
189 			HH->row_order == k_nc_start_south ? "S->N" : "N->S");
190 #endif
191 
192 	/* set index of input origin */
193 	yx_dim[0] = HH->xy_dim[1], yx_dim[1] = HH->xy_dim[0]; /* xy_dim not row major */
194 	memcpy (start, HH->t_index, 3 * sizeof(size_t)); /* set lower dimensions first (e.g. layer) */
195 	start[yx_dim[0]] = origin[0]; /* first row */
196 	start[yx_dim[1]] = origin[1]; /* first col */
197 
198 	/* set mapping of complex grids or if reading a part of a grid */
199 	imap[yx_dim[0]] = (stride == 0 ? width : stride); /* distance between each row */
200 	imap[yx_dim[1]] = 1;                              /* distance between values in a row */
201 	if (cube) imap[0] = header->nm;                   /* distance between values in a layer (?) */
202 
203 	/* determine how many chunks to process at once */
204 	gmtnc_n_chunked_rows_in_cache (GMT, header, width, height, &n_contiguous_chunk_rows, chunksize);
205 
206 	if (n_contiguous_chunk_rows) {
207 		/* read/write grid in chunks to keep memory footprint low */
208 		size_t remainder;
209 #ifdef NC4_DEBUG
210 		unsigned row_num = 0;
211 			GMT_Report (GMT->parent, GMT_MSG_WARNING, "stride: %u width: %u\n",
212 					stride, width);
213 #endif
214 
215 		/* adjust row count, so that it ends on the bottom of a chunk */
216 		count[yx_dim[0]] = chunksize[yx_dim[0]] * n_contiguous_chunk_rows;
217 		remainder = start[yx_dim[0]] % chunksize[yx_dim[0]];
218 		count[yx_dim[0]] -= remainder;
219 
220 		count[yx_dim[1]] = width_t;
221 		while ( (start[yx_dim[0]] + count[yx_dim[0]]) <= height_t && status == NC_NOERR) {
222 #ifdef NC4_DEBUG
223 			GMT_Report (GMT->parent, GMT_MSG_WARNING, "chunked row #%u start-y:%" PRIuS " height:%" PRIuS "\n",
224 					++row_num, start[yx_dim[0]], count[yx_dim[0]]);
225 #endif
226 			/* get/put chunked rows */
227 			if (stride)
228 				status = gmtnc_varm_grdfloat (HH->ncid, HH->z_id, start, count, onestride, imap, grid, io_mode);
229 			else
230 				status = gmtnc_vara_grdfloat (HH->ncid, HH->z_id, start, count, grid, io_mode);
231 
232 			/* advance grid location and set new origin */
233 			grid += count[yx_dim[0]] * ((stride == 0 ? width_t : stride));
234 			start[yx_dim[0]] += count[yx_dim[0]];
235 			if (remainder) {
236 				/* reset count to full chunk height */
237 				count[yx_dim[0]] += remainder;
238 				remainder = 0;
239 			}
240 		}
241 		if ( start[yx_dim[0]] != height_t && status == NC_NOERR ) {
242 			/* get/put last chunked row */
243 			count[yx_dim[0]] = height_t - start[yx_dim[0]] + origin[0];
244 #ifdef NC4_DEBUG
245 			GMT_Report (GMT->parent, GMT_MSG_WARNING, "chunked row #%u start-y:%" PRIuS " height:%" PRIuS "\n",
246 					++row_num, start[yx_dim[0]], count[yx_dim[0]]);
247 #endif
248 			if (stride)
249 				status = gmtnc_varm_grdfloat (HH->ncid, HH->z_id, start, count, onestride, imap, grid, io_mode);
250 			else
251 				status = gmtnc_vara_grdfloat (HH->ncid, HH->z_id, start, count, grid, io_mode);
252 		}
253 	}
254 	else {
255 		/* get/put whole grid contiguous */
256 		count[yx_dim[0]] = height_t;
257 		count[yx_dim[1]] = width_t;
258 		if (stride)
259 			status = gmtnc_varm_grdfloat (HH->ncid, HH->z_id, start, count, onestride, imap, grid, io_mode);
260 		else
261 			status = gmtnc_vara_grdfloat (HH->ncid, HH->z_id, start, count, grid, io_mode);
262 	}
263 	return status;
264 }
265 
gmtnc_get_units(struct GMT_CTRL * GMT,int ncid,int varid,char * name_units)266 GMT_LOCAL void gmtnc_get_units (struct GMT_CTRL *GMT, int ncid, int varid, char *name_units) {
267 	/* Get attributes long_name and units for given variable ID
268 	 * and assign variable name if attributes are not available.
269 	 * ncid, varid		: as in nc_get_att_text
270 	 * nameunit		: long_name and units in form "long_name [units]"
271 	 */
272 	char name[GMT_GRID_UNIT_LEN80], units[GMT_GRID_UNIT_LEN80];
273 	if (gmtlib_nc_get_att_text (GMT, ncid, varid, "long_name", name, GMT_GRID_UNIT_LEN80))
274 		nc_inq_varname (ncid, varid, name);
275 	if (!gmtlib_nc_get_att_text (GMT, ncid, varid, "units", units, GMT_GRID_UNIT_LEN80) && units[0])
276 		snprintf (name_units, GMT_GRID_UNIT_LEN80, "%s [%s]", name, units);
277 	else
278 		strncpy (name_units, name, GMT_GRID_UNIT_LEN80-1);
279 }
280 
gmtnc_put_units(int ncid,int varid,char * name_units)281 GMT_LOCAL void gmtnc_put_units (int ncid, int varid, char *name_units) {
282 	/* Put attributes long_name and units for given variable ID based on
283 	 * string name_unit in the form "long_name [units]".
284 	 * ncid, varid		: as is nc_put_att_text
285 	 * name_units		: string in form "long_name [units]"
286 	 */
287 	bool copy = false;
288 	int i = 0, j = 0;
289 	char name[GMT_GRID_UNIT_LEN80], units[GMT_GRID_UNIT_LEN80];
290 
291 	strncpy (name, name_units, GMT_GRID_UNIT_LEN80-1);
292 	units[0] = '\0';
293 	for (i = 0; i < GMT_GRID_UNIT_LEN80 && name[i]; i++) {
294 		if (name[i] == ']') copy = false, units[j] = '\0';
295 		if (copy) units[j++] = name[i];
296 		if (name[i] == '[') {
297 			name[i] = '\0';
298 			if (i > 0 && name[i-1] == ' ') name[i-1] = '\0';
299 			copy = true;
300 		}
301 	}
302 	if (name[0]) nc_put_att_text (ncid, varid, "long_name", strlen(name), name);
303 	if (units[0]) nc_put_att_text (ncid, varid, "units", strlen(units), units);
304 	if (strstr(units, "degrees_east")) {
305 		nc_put_att_text (ncid, varid, "standard_name", 9, "longitude");
306 		nc_put_att_text (ncid, varid, "axis", 1, "X");
307 	}
308 	else if (strstr(units, "degrees_north")) {
309 		nc_put_att_text (ncid, varid, "standard_name", 8, "latitude");
310 		nc_put_att_text (ncid, varid, "axis", 1, "Y");
311 	}
312 }
313 
gmtnc_check_step(struct GMT_CTRL * GMT,uint32_t n,double * x,char * varname,char * file,bool save_xy_array)314 GMT_LOCAL int gmtnc_check_step (struct GMT_CTRL *GMT, uint32_t n, double *x, char *varname, char *file, bool save_xy_array) {
315 	/* Check if all steps in range are the same (within 0.1%). Returns 0 if OK and 1 if variable spacing */
316 	double step, step_min, step_max;
317 	uint32_t i;
318 	unsigned int wlevel = (save_xy_array) ? GMT_MSG_INFORMATION : GMT_MSG_WARNING;
319 	if (n < 2) return 0;
320 	step_min = step_max = x[1]-x[0];
321 	for (i = 2; i < n; i++) {
322 		step = x[i]-x[i-1];
323 		if (step < step_min) step_min = step;
324 		if (step > step_max) step_max = step;
325 	}
326 	if (fabs (step_min-step_max)/(fabs (step_min)*0.5 + fabs (step_max)*0.5) > 0.001) {
327 		GMT_Report (GMT->parent, wlevel,
328 			"The step size of coordinate (%s) in grid %s is not constant.\n", varname, file);
329 		GMT_Report (GMT->parent, wlevel,
330 			"GMT will use a constant step size of %g; the original ranges from %g to %g.\n",
331 			(x[n-1]-x[0])/(n-1), step_min, step_max);
332 		return 1;
333 	}
334 	return 0;
335 }
336 
gmtnc_set_optimal_chunksize(struct GMT_CTRL * GMT,struct GMT_GRID_HEADER * header)337 GMT_LOCAL void gmtnc_set_optimal_chunksize (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *header) {
338 	/* For optimal performance, set the number of elements in a given chunk
339 	 * dimension (n) to be the ceiling of the number of elements in that
340 	 * dimension of the array variable (d) divided by a natural number N>1.
341 	 * That is, set n = ceil (d / N).  Using a chunk size slightly larger than
342 	 * this value is also acceptable.  For example: 129 = ceil (257 / 2).
343 	 * Do NOT set n = floor (d / N), for example 128 = floor (257 / 2). */
344 
345 	double chunksize[2] = {128, 128};                            /* default min chunksize */
346 	const size_t min_chunk_pixels = (size_t)(chunksize[0] * chunksize[1]); /* min pixel count per chunk */
347 
348 	if (GMT->current.setting.io_nc4_chunksize[0] == k_netcdf_io_classic)
349 		/* no chunking with classic model */
350 		return;
351 
352 	if (GMT->current.setting.io_nc4_chunksize[0] != k_netcdf_io_chunked_auto && (
353 			header->n_rows >= GMT->current.setting.io_nc4_chunksize[0] ||
354 			header->n_columns >= GMT->current.setting.io_nc4_chunksize[1])) {
355 		/* if chunk size is smaller than grid size */
356 		return;
357 	}
358 
359 	/* here, chunk size is either k_netcdf_io_chunked_auto or the chunk size is
360 	 * larger than grid size */
361 
362 	if ( header->nm < min_chunk_pixels ) {
363 		/* the grid dimension is too small for chunking to make sense. switch to
364 		 * classic model */
365 		GMT->current.setting.io_nc4_chunksize[0] = k_netcdf_io_classic;
366 		return;
367 	}
368 
369 	/* adjust default chunk sizes for grids that have more than min_chunk_pixels
370 	 * cells but less than chunksize (default 128) cells in one dimension */
371 	if (header->n_rows < chunksize[0]) {
372 		chunksize[0] = header->n_rows;
373 		chunksize[1] = floor (min_chunk_pixels / chunksize[0]);
374 	}
375 	else if (header->n_columns < chunksize[1]) {
376 		chunksize[1] = header->n_columns;
377 		chunksize[0] = floor (min_chunk_pixels / chunksize[1]);
378 	}
379 
380 	/* determine optimal chunk size in the range [chunksize,2*chunksize) */
381 	GMT->current.setting.io_nc4_chunksize[0] = (size_t) ceil (header->n_rows / floor (header->n_rows / chunksize[0]));
382 	GMT->current.setting.io_nc4_chunksize[1] = (size_t) ceil (header->n_columns / floor (header->n_columns / chunksize[1]));
383 }
384 
gmtnc_not_obviously_global(double * we)385 GMT_LOCAL bool gmtnc_not_obviously_global (double *we) {
386 	/* If range is not 360 and boundaries are not 0, 180, 360 then we pass true */
387 	if (!gmt_M_360_range (we[0], we[1])) return true;
388 	if (!(doubleAlmostEqualZero (we[0], 0.0) || doubleAlmostEqual (we[0], -180.0))) return true;
389 	return false;
390 }
391 
gmtnc_not_obviously_polar(double * se)392 GMT_LOCAL bool gmtnc_not_obviously_polar (double *se) {
393 	/* If range is not 180 and boundaries are not -90/90 then we pass true */
394 	if (!gmt_M_180_range (se[0], se[1])) return true;
395 	if (!(doubleAlmostEqual (se[0], -90.0) && doubleAlmostEqual (se[1], 90.0))) return true;
396 	return false;
397 }
398 
gmtnc_put_xy_vectors(struct GMT_CTRL * GMT,struct GMT_GRID_HEADER * header)399 GMT_LOCAL int gmtnc_put_xy_vectors (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *header) {
400 	/* Write the x and y vectors to the netCDF file */
401 	int err = GMT_NOERROR;
402 	/* Create enough memory to store the x- and y-coordinate values */
403 	double *xy = gmt_M_memory (GMT, NULL, MAX (header->n_columns,header->n_rows), double);
404 	unsigned int col, row;
405 	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (header);
406 
407 	for (col = 0; col < header->n_columns; col++) xy[col] = gmt_M_grd_col_to_x (GMT, col, header);
408 	gmt_M_err_trap (nc_put_var_double (HH->ncid, HH->xyz_id[GMT_X], xy));
409 
410 	/* Depending on row_order, write y-coordinate array bottom-to-top or top-to-bottom */
411 	if (HH->row_order == k_nc_start_south) {
412 		for (row = 0; row < header->n_rows; row++)
413 			xy[row] = (double) gmt_M_col_to_x (GMT, row, header->wesn[YLO], header->wesn[YHI], header->inc[GMT_Y], 0.5 * header->registration, header->n_rows);
414 	}
415 	else {
416 		for (row = 0; row < header->n_rows; row++)
417 			xy[row] = (double) gmt_M_row_to_y (GMT, row, header->wesn[YLO], header->wesn[YHI], header->inc[GMT_Y], 0.5 * header->registration, header->n_rows);
418 	}
419 	gmt_M_err_trap (nc_put_var_double (HH->ncid, HH->xyz_id[GMT_Y], xy));
420 	gmt_M_free (GMT, xy);
421 	return GMT_NOERROR;
422 }
423 
gmtnc_grd_info(struct GMT_CTRL * GMT,struct GMT_GRID_HEADER * header,char job,bool cube,uint64_t n_layers)424 GMT_LOCAL int gmtnc_grd_info (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *header, char job, bool cube, uint64_t n_layers) {
425 	/* Used when reading and writing 2-D grids and when writing 3-D data cubes */
426 	int j, err, has_vector, has_range, registration, var_spacing = 0;
427 	int old_fill_mode, status;
428 	unsigned int d_off = (cube) ? 1 : 0;
429 	size_t len;
430 	double dummy[2], min, max;
431 	char dimname[GMT_GRID_UNIT_LEN80], coord[GMT_LEN8];
432 	nc_type z_type;
433 	bool save_xy_array = !strncmp (GMT->init.module_name, "grd2xyz", 7U);
434 	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (header);
435 
436 	/* Dimension ids, variable ids, etc.. */
437 	int i, ncid, z_id = GMT_NOTSET, ids[5] = {-1,-1,-1,-1,-1}, gm_id = GMT_NOTSET, dims[5], nvars, ndims = 0;
438 	size_t lens[5], item[2];
439 
440 	/* If not yet determined, attempt to get the layer IDs from the variable name */
441 	int n_t_index_by_value = 0;
442 	double t_value[3];
443 
444 	if (HH->varname[0]) {
445 		char *c = strpbrk (HH->varname, "(["); /* find first occurrence of ( or [ */
446 		if (c != NULL && *c == '(') {
447 			n_t_index_by_value = sscanf (c+1, "%lf,%lf,%lf", &t_value[0], &t_value[1], &t_value[2]);
448 			*c = '\0';
449 			/* t_index will be determined later from t_value when the nc-file is opened */
450 		}
451 		else if (c != NULL && *c == '[') {
452 			sscanf (c+1, "%" SCNuS ",%" SCNuS ",%" SCNuS "", &HH->t_index[0], &HH->t_index[1], &HH->t_index[2]);
453 			*c = '\0';
454 		}
455 	}
456 
457 	/* Open NetCDF file */
458 	if (!strcmp (HH->name,"=")) return (GMT_GRDIO_NC_NO_PIPE);
459 	switch (job) {
460 		case 'r':
461 			gmt_M_err_trap (gmt_nc_open (GMT, HH->name, NC_NOWRITE, &ncid));
462 			break;
463 		case 'u':
464 			gmt_M_err_trap (gmt_nc_open (GMT, HH->name, NC_WRITE, &ncid));
465 			gmt_M_err_trap (nc_set_fill (ncid, NC_NOFILL, &old_fill_mode));
466 			break;
467 		default:
468 			/* create new nc-file */
469 			gmtnc_set_optimal_chunksize (GMT, header);
470 			if (GMT->current.setting.io_nc4_chunksize[0] != k_netcdf_io_classic) {
471 				/* create chunked nc4 file */
472 				gmt_M_err_trap (gmt_nc_create (GMT, HH->name, NC_NETCDF4, &ncid));
473 				HH->is_netcdf4 = true;
474 			}
475 			else {
476 				/* create nc using classic data model */
477 				gmt_M_err_trap (gmt_nc_create (GMT, HH->name, NC_CLOBBER, &ncid));
478 				HH->is_netcdf4 = false;
479 			}
480 			gmt_M_err_trap (nc_set_fill (ncid, NC_NOFILL, &old_fill_mode));
481 			break;
482 	}
483 
484 	/* Retrieve or define dimensions and variables */
485 
486 	if (job == 'r' || job == 'u') {
487 		int kind;
488 		/* determine netCDF data model */
489 		gmt_M_err_trap (nc_inq_format(ncid, &kind));
490 		HH->is_netcdf4 = (kind == NC_FORMAT_NETCDF4 || kind == NC_FORMAT_NETCDF4_CLASSIC);
491 
492 		/* First see if this is an old NetCDF formatted file */
493 		if (!nc_inq_dimid (ncid, "xysize", &i)) return (gmt_cdf_grd_info (GMT, ncid, header, job));
494 
495 		/* Find first 2-dimensional (z) variable or specified variable */
496 		if (!HH->varname[0]) {	/* No specific named variable was requested; search for suitable matrix */
497 			int ID = GMT_NOTSET, dim = 0;
498 			gmt_M_err_trap (nc_inq_nvars (ncid, &nvars));
499 			i = 0;
500 			while (i < nvars && z_id < 0) {	/* Look for first 2D grid, with fallback to first higher-dimension (3-4D) grid if 2D not found */
501 				gmt_M_err_trap (nc_inq_varndims (ncid, i, &ndims));
502 				if (ndims == 2)	/* Found the first 2-D grid */
503 					z_id = i;
504 				else if (ID == GMT_NOTSET && ndims > 2 && ndims < 5) {	/* Also look for higher-dim grid in case no 2-D */
505 					ID = i;
506 					dim = ndims;
507 				}
508 				i++;
509 			}
510 			if (z_id < 0) {	/* No 2-D grid found, check if we found a higher dimension cube */
511 				if (ID == GMT_NOTSET) return (GMT_GRDIO_NO_2DVAR);	/* No we didn't */
512 				z_id = ID;		/* Pick the higher dimensioned cube instead, get its name, and warn */
513 				nc_inq_varname (ncid, z_id, HH->varname);
514 				GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "No 2-D array in file %s.  Selecting first 2-D slice in the %d-D array %s\n", HH->name, dim, HH->varname);
515 			}
516 		}
517 		else if (nc_inq_varid (ncid, HH->varname, &z_id) == NC_NOERR) {	/* Gave a named variable that exist, is it 2-4 D? */
518 			gmt_M_err_trap (nc_inq_varndims (ncid, z_id, &ndims));
519 			if (ndims < 2 || ndims > 5) return (GMT_GRDIO_BAD_DIM);
520 		}
521 		else	/* No such named variable in the grid */
522 			return (GMT_GRDIO_NO_VAR);
523 
524 		if (z_id < 0) return (GMT_GRDIO_NO_2DVAR);
525 
526 		/* Get the z data type and determine its dimensions */
527 		gmt_M_err_trap (nc_inq_vartype (ncid, z_id, &z_type));
528 		gmt_M_err_trap (nc_inq_vardimid (ncid, z_id, dims));
529 		switch (z_type) {
530 			case NC_UBYTE:  header->type = GMT_GRID_IS_NB; HH->orig_datatype = GMT_UCHAR; break;
531 			case NC_BYTE:   header->type = GMT_GRID_IS_NB; HH->orig_datatype = GMT_CHAR; break;
532 			case NC_SHORT:  header->type = GMT_GRID_IS_NS; HH->orig_datatype = GMT_SHORT; break;
533 			case NC_INT:    header->type = GMT_GRID_IS_NI; HH->orig_datatype = GMT_INT; break;
534 			case NC_FLOAT:  header->type = GMT_GRID_IS_NF; HH->orig_datatype = GMT_FLOAT; break;
535 			case NC_DOUBLE: header->type = GMT_GRID_IS_ND; HH->orig_datatype = GMT_DOUBLE; break;
536 			default:        header->type = k_grd_unknown_fmt; break;
537 		}
538 
539 		/* Get the ids of the x and y (and depth and time) coordinate variables */
540 		for (i = 0; i < ndims; i++) {
541 			gmt_M_err_trap (nc_inq_dim (ncid, dims[i], dimname, &lens[i]));
542 			//if (nc_inq_varid (ncid, dimname, &ids[i])) return (GMT_GRDIO_NC_NOT_COARDS);
543 			if ((status = nc_inq_varid (ncid, dimname, &ids[i])) != NC_NOERR)
544 				GMT_Report (GMT->parent, GMT_MSG_WARNING, "\"%s\", %s\n\tIf something bad happens later, try importing via GDAL.\n",
545 				                                         dimname, nc_strerror(status));
546 		}
547 		HH->xy_dim[0] = ndims-1;
548 		HH->xy_dim[1] = ndims-2;
549 		HH->xyz_id[GMT_X] = ids[1];
550 		HH->xyz_id[GMT_Y] = ids[0];
551 
552 		/* Check if LatLon variable exists, then we may need to flip x and y */
553 		if (nc_inq_varid (ncid, "LatLon", &i) == NC_NOERR) nc_get_var_int (ncid, i, HH->xy_dim);
554 		header->n_columns = (uint32_t) lens[HH->xy_dim[0]];
555 		header->n_rows = (uint32_t) lens[HH->xy_dim[1]];
556 
557 		/* Check if the spatial_ref attribute exists, first via the z_id variable, and if not then via a global attribute */
558 		if (nc_inq_attlen (ncid, z_id, "grid_mapping", &len) == NC_NOERR) {	/* The data layer has an attribute with the name of the variable that has the spatial_ref */
559 			char *v_name = gmt_M_memory (GMT, NULL, len+1, char);           /* Allocate the needed space for the variable name */
560 			if (nc_get_att_text (ncid, z_id, "grid_mapping", v_name) == NC_NOERR) {	/* Get the name of the variable */
561 				if (nc_inq_varid (ncid, v_name, &i) == NC_NOERR)	/* Got the id of this variable */
562 					gm_id = i;
563 			}
564 			gmt_M_free (GMT, v_name);
565 		}
566 		else if (nc_inq_varid (ncid, "grid_mapping", &i) == NC_NOERR)	/* A global variable has it */
567 			gm_id = i;
568 	} /* if (job == 'r' || job == 'u') */
569 	else {
570 		/* Define dimensions of z variable */
571 		ndims = (cube) ? 3 : 2;
572 		HH->xy_dim[0] = 1 + d_off;
573 		HH->xy_dim[1] = 0 + d_off;
574 
575 		strcpy (coord, (gmt_M_x_is_lon (GMT, GMT_OUT)) ? "lon" : (gmt_M_type (GMT, GMT_OUT, GMT_X) & GMT_IS_RATIME) ? "time" : "x");
576 		gmt_M_err_trap (nc_def_dim (ncid, coord, (size_t) header->n_columns, &dims[1+d_off]));
577 		gmt_M_err_trap (nc_def_var (ncid, coord, NC_DOUBLE, 1, &dims[1+d_off], &ids[1+d_off]));
578 		HH->xyz_id[GMT_X] = ids[1+d_off];
579 
580 		strcpy (coord, (gmt_M_y_is_lat (GMT, GMT_OUT)) ? "lat" : (gmt_M_type (GMT, GMT_OUT, GMT_Y) & GMT_IS_RATIME) ? "time" : "y");
581 		gmt_M_err_trap (nc_def_dim (ncid, coord, (size_t) header->n_rows, &dims[0+d_off]));
582 		gmt_M_err_trap (nc_def_var (ncid, coord, NC_DOUBLE, 1, &dims[0+d_off], &ids[0+d_off]));
583 		HH->xyz_id[GMT_Y] = ids[0+d_off];
584 
585 		if (cube) {	/* Allow for cube expansion by setting layer dimension to 0 (NC_UNLIMITED) */
586 			strcpy (coord, (gmt_M_type (GMT, GMT_OUT, GMT_Z) & GMT_IS_RATIME) ? "time" : "z");
587 			gmt_M_err_trap (nc_def_dim (ncid, coord, (size_t) n_layers, &dims[0]));
588 			gmt_M_err_trap (nc_def_var (ncid, coord, NC_DOUBLE, 1, &dims[0], &HH->xyz_id[GMT_Z]));
589 		}
590 
591 		switch (header->type) {
592 			case GMT_GRID_IS_NB: z_type = NC_BYTE; break;
593 			case GMT_GRID_IS_NS: z_type = NC_SHORT; break;
594 			case GMT_GRID_IS_NI: z_type = NC_INT; break;
595 			case GMT_GRID_IS_NF: z_type = NC_FLOAT; break;
596 			case GMT_GRID_IS_ND: z_type = NC_DOUBLE; break;
597 			default: z_type = NC_NAT;
598 		}
599 
600 		/* Variable name is given, or defaults to "z" (or "cube" under cube) */
601 		if (!HH->varname[0])
602 			strcpy (HH->varname, (cube) ? "cube" : "z");
603 		gmt_M_err_trap (nc_def_var (ncid, HH->varname, z_type, (cube) ? 3 : 2, dims, &z_id));
604 
605 		/* set deflation and chunking */
606 		if (GMT->current.setting.io_nc4_chunksize[0] != k_netcdf_io_classic) {
607 			/* set chunk size */
608 			size_t nc4_chunksize[3] = {0, 0, 0};
609 			gmt_M_memcpy (&nc4_chunksize[cube], GMT->current.setting.io_nc4_chunksize, 2U, size_t);
610 			gmt_M_err_trap (nc_def_var_chunking (ncid, z_id, NC_CHUNKED, nc4_chunksize));
611 			/* set deflation level and shuffle for x, y[, z], and z[|w] variable */
612 			if (GMT->current.setting.io_nc4_deflation_level) {
613 				gmt_M_err_trap (nc_def_var_deflate (ncid, HH->xyz_id[GMT_X], true, true, GMT->current.setting.io_nc4_deflation_level));
614 				gmt_M_err_trap (nc_def_var_deflate (ncid, HH->xyz_id[GMT_Y], true, true, GMT->current.setting.io_nc4_deflation_level));
615 				if (cube && n_layers == 0) gmt_M_err_trap (nc_def_var_deflate (ncid, HH->xyz_id[GMT_Z], true, true, GMT->current.setting.io_nc4_deflation_level));
616 				gmt_M_err_trap (nc_def_var_deflate (ncid, z_id, true, true, GMT->current.setting.io_nc4_deflation_level));
617 			}
618 		} /* GMT->current.setting.io_nc4_chunksize[0] != k_netcdf_io_classic */
619 	} /* if (job == 'r' || job == 'u') */
620 	HH->z_id = z_id;
621 	HH->ncid = ncid;
622 
623 	/* Query or assign attributes */
624 
625 	if (job == 'u') gmt_M_err_trap (nc_redef (ncid));
626 
627 	if (job == 'r') {
628 		bool set_reg = true;
629 		double dx = 0, dy = 0, threshold = 0.0;
630 		/* Create enough memory to store the x- and y-coordinate values */
631 		double *xy = gmt_M_memory (GMT, NULL, MAX (header->n_columns,header->n_rows), double);
632 		/* Get global information */
633 		if (gmtlib_nc_get_att_text (GMT, ncid, NC_GLOBAL, "title", header->title, GMT_GRID_TITLE_LEN80))
634 			gmtlib_nc_get_att_text (GMT, ncid, z_id, "long_name", header->title, GMT_GRID_TITLE_LEN80);
635 		if (gmtlib_nc_get_att_text (GMT, ncid, NC_GLOBAL, "history", header->command, GMT_GRID_COMMAND_LEN320))
636 			gmtlib_nc_get_att_text (GMT, ncid, NC_GLOBAL, "source", header->command, GMT_GRID_COMMAND_LEN320);
637 		gmtlib_nc_get_att_text (GMT, ncid, NC_GLOBAL, "description", header->remark, GMT_GRID_REMARK_LEN160);
638 		header->registration = GMT_GRID_NODE_REG;
639 		if (!nc_get_att_int (ncid, NC_GLOBAL, "node_offset", &i)) {	/* GMT wrote the registration in the grid */
640 			header->registration = i;
641 			set_reg = false;	/* Do not update it below since we know the registration */
642 		}
643 
644 		if (gm_id != GMT_NOTSET && nc_inq_attlen (ncid, gm_id, "spatial_ref", &len) == NC_NOERR) {	/* Good to go */
645 			char *attrib = NULL;
646 			gmt_M_str_free (header->ProjRefWKT);	/* Make sure we didn't have a previously allocated one */
647 			attrib = gmt_M_memory (GMT, NULL, len+1, char);		/* and allocate the needed space */
648 			gmt_M_err_trap (nc_get_att_text (ncid, gm_id, "spatial_ref", attrib));
649 			header->ProjRefWKT = strdup (attrib);	/* Turn it into a strdup allocation to be compatible with other instances elsewhere */
650 			gmt_M_free (GMT, attrib);
651 		}
652 
653 		/* Explanation for the logic below: Not all netCDF grids are proper COARDS grids and hence we sometime must guess
654 		 * regarding the settings.  The x and y coordinates may be written as arrays, which reflect the positions of the
655 		 * nodes.  There may also be the attributes actual_range which specifies the x range.  These will differ if the
656 		 * grid is pixel registered, hence when both are present we use this difference to detect a pixel grid. However,
657 		 * some external tools such as xarray may slice a grid but not update the attributes.  In this case the actual_range
658 		 * may have an initial range that is no longer the case.  We have added a check if these differ by more than a
659 		 * half grid increment.  If not then we can trust it.  If actual_range is missing then we have to guess the registration
660 		 * which we do by checking if the start coordinate is an integer multiple of the increment.  If not, we guess pixel registration
661 		 * but cannot know if this is the case unless the adjusted coordinates in x has a range of 360 and in y a range of 180.
662 		 * Finally, if there is no array just the actual_range, then we cannot tell the registration from the range but try
663 		 * and leave it as gridline registration. */
664 
665 		/* Get information about x variable */
666 		gmtnc_get_units (GMT, ncid, ids[HH->xy_dim[0]], header->x_units);
667 		/* Set default range to number of nodes in case nothing is found further down */
668 		dummy[0] = 0.0, dummy[1] = (double) header->n_columns-1; /* Default */
669 		registration = GMT_GRID_NODE_REG;
670 
671 		/* Look for the x-coordinate vector */
672 		if ((has_vector = !nc_get_var_double (ncid, ids[HH->xy_dim[0]], xy)) && header->n_columns > 1) {
673 			var_spacing = gmtnc_check_step (GMT, header->n_columns, xy, header->x_units, HH->name, save_xy_array);
674 			if (save_xy_array && var_spacing) {
675 				if (GMT->current.io.nc_xarray) gmt_M_free (GMT, GMT->current.io.nc_xarray);
676 				GMT->current.io.nc_xarray = gmt_M_memory (GMT, NULL, header->n_columns, double);
677 				gmt_M_memcpy (GMT->current.io.nc_xarray, xy, header->n_columns, double);
678 				HH->var_spacing[GMT_X] = var_spacing;
679 			}
680 			dx = fabs (xy[1] - xy[0]);	/* Grid spacing in x */
681 		}
682 		if (has_vector && header->n_columns == 1) has_vector = false;	/* One coordinate does not a vector make */
683 
684 		/* Look for the x-coordinate range attributes */
685 		has_range = (!nc_get_att_double (ncid, ids[HH->xy_dim[0]], "actual_range", dummy) ||
686 			!nc_get_att_double (ncid, ids[HH->xy_dim[0]], "valid_range", dummy) ||
687 			!(nc_get_att_double (ncid, ids[HH->xy_dim[0]], "valid_min", &dummy[0]) +
688 			nc_get_att_double (ncid, ids[HH->xy_dim[0]], "valid_max", &dummy[1])));
689 
690 		if (has_vector && has_range) {	/* Has both so we can do a basic sanity check */
691 			threshold = (0.5+GMT_CONV5_LIMIT) * dx;
692 			min = xy[0];	max = xy[header->n_columns-1];
693 			if (min > max) gmt_M_double_swap (min, max);	/* Got it backwards in the array */
694 			if (fabs (dummy[0] - min) > threshold || fabs (dummy[1] - max) > threshold) {
695 				if (gmtnc_not_obviously_global (dummy)) {
696 					GMT_Report (GMT->parent, GMT_MSG_WARNING, "The x-coordinates and range attribute are in conflict; must rely on coordinates only\n");
697 					dummy[0] = xy[0], dummy[1] = xy[header->n_columns-1];
698 					has_range = false;	/* Since useless information */
699 					/* For registration, we have to assume that the actual range is an integer multiple of increment.
700 					 * If so, then if the coordinates are off by 0.5*dx then we assume we have pixel registration */
701 					threshold = (0.5-GMT_CONV5_LIMIT) * dx;
702 					if (set_reg && fabs (fmod (dummy[0], dx)) > threshold) {	/* Pixel registration */
703 						registration = header->registration = GMT_GRID_PIXEL_REG;
704 						dummy[0] -= 0.5 * dx;	dummy[1] += 0.5 * dx;
705 						if (gmt_M_360_range (dummy[0], dummy[1]))
706 							GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Grid x-coordinates after pixel registration adjustment have exactly 360 range\n");
707 					}
708 				}
709 				else {	/* Got clean global longitude range, stick with that information and ignore xy */
710 					GMT_Report (GMT->parent, GMT_MSG_WARNING, "The x-coordinates and range attribute are in conflict but range is exactly 360; we rely on this range\n");
711 					if (set_reg && (header->n_columns%2) == 0) {	/* Pixel registration */
712 						registration = header->registration = GMT_GRID_PIXEL_REG;
713 						GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Global longitudes, guessing registration to be %s since nx is odd\n", regtype[header->registration]);
714 					}
715 				}
716 			}
717 			else {	/* Data seems OK; determine registration */
718 				dummy[0] = xy[0], dummy[1] = xy[header->n_columns-1];
719 				if (set_reg && (fabs(dummy[1] - dummy[0]) / fabs(xy[header->n_columns-1] - xy[0]) - 1.0 > 0.5 / (header->n_columns - 1)))
720 					registration = header->registration = GMT_GRID_PIXEL_REG;
721 			}
722 		}
723 		else if (has_vector) {	/* No attribute for range, use coordinates */
724 			dummy[0] = xy[0], dummy[1] = xy[header->n_columns-1];
725 			threshold = (0.5-GMT_CONV5_LIMIT) * dx;
726 			if (set_reg && fabs (fmod (dummy[0], dx)) > threshold) {	/* Most likely pixel registration since off by dx/2 */
727 				registration = header->registration = GMT_GRID_PIXEL_REG;
728 				dummy[0] -= 0.5 * dx;	dummy[1] += 0.5 * dx;
729 				if (gmt_M_360_range (dummy[0], dummy[1]))
730 					GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Grid x-coordinates after pixel registration adjustment have exactly 360 range\n");
731 			}
732 			GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "No range attribute, guessing registration to be %s\n", regtype[header->registration]);
733 		}
734 		else {	/* Only has the valid_range settings.  If no registration set, and no dx available, guess based on nx */
735 			if (set_reg && (header->n_columns%2) == 0) {	/* Pixel registration */
736 				registration = header->registration = GMT_GRID_PIXEL_REG;
737 				GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "No x-coordinates, guessing registration to be %s since nx is odd\n", regtype[header->registration]);
738 			}
739 		}
740 
741 		/* Determine grid step */
742 		header->inc[GMT_X] = gmt_M_get_inc (GMT, dummy[0], dummy[1], header->n_columns, registration);
743 		if (gmt_M_is_dnan(header->inc[GMT_X]) || gmt_M_is_zero (header->inc[GMT_X])) header->inc[GMT_X] = 1.0;
744 		if (header->n_columns == 1) registration = GMT_GRID_PIXEL_REG;	/* The only way to have a grid like that */
745 
746 #ifdef NC4_DEBUG
747 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "x registration: %u\n", header->registration);
748 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "x dummy: %g %g\n", dummy[0], dummy[1]);
749 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "x[0] x[nx-1]: %g %g\n", xy[0], xy[header->n_columns-1]);
750 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "xinc: %g %g\n", header->inc[GMT_X]);
751 #endif
752 
753 		/* Extend x boundaries by half if we found pixel registration */
754 		if (registration == GMT_GRID_NODE_REG && header->registration == GMT_GRID_PIXEL_REG)
755 			header->wesn[XLO] = dummy[0] - header->inc[GMT_X] / 2.0,
756 			header->wesn[XHI] = dummy[1] + header->inc[GMT_X] / 2.0;
757 		else	/* Use as is */
758 			header->wesn[XLO] = dummy[0], header->wesn[XHI] = dummy[1];
759 
760 		/* Get information about y variable */
761 		gmtnc_get_units (GMT, ncid, ids[HH->xy_dim[1]], header->y_units);
762 		/* Set default range to number of nodes in case nothing is found further down */
763 		dummy[0] = 0.0, dummy[1] = (double) header->n_rows-1; /* Default */
764 		registration = GMT_GRID_NODE_REG;
765 
766 		/* Read the y-coordinate vector (if available), otherwise just look for range attributes */
767 		if ((has_vector = !nc_get_var_double (ncid, ids[HH->xy_dim[1]], xy)) && header->n_rows > 1) {
768 			var_spacing = gmtnc_check_step (GMT, header->n_rows, xy, header->y_units, HH->name, save_xy_array);
769 			if (save_xy_array && var_spacing) {
770 				if (GMT->current.io.nc_yarray) gmt_M_free (GMT, GMT->current.io.nc_yarray);
771 				GMT->current.io.nc_yarray = gmt_M_memory (GMT, NULL, header->n_rows, double);
772 				gmt_M_memcpy (GMT->current.io.nc_yarray, xy, header->n_rows, double);
773 				HH->var_spacing[GMT_Y] = var_spacing;
774 				/* Flip-ud y-array since row = 0 is the last value */
775 				gmt_grd_flip_vertical (GMT->current.io.nc_yarray, 1, header->n_rows, 0, sizeof(double));
776 			}
777 			dummy[0] = xy[0], dummy[1] = xy[header->n_rows-1];
778 			dy = fabs (xy[1] - xy[0]);	/* Grid spacing in y */
779 		}
780 		if (has_vector && header->n_rows == 1) has_vector = false;	/* One coordinate does not a vector make */
781 		has_range = (!nc_get_att_double (ncid, ids[HH->xy_dim[1]], "actual_range", dummy) ||
782 			!nc_get_att_double (ncid, ids[HH->xy_dim[1]], "valid_range", dummy) ||
783 			!(nc_get_att_double (ncid, ids[HH->xy_dim[1]], "valid_min", &dummy[0]) +
784 			nc_get_att_double (ncid, ids[HH->xy_dim[1]], "valid_max", &dummy[1])));
785 
786 		if (has_vector && has_range) {	/* Has both so we can do a basic sanity check */
787 			threshold = (0.5+GMT_CONV5_LIMIT) * dy;
788 			min = xy[0];	max = xy[header->n_rows-1];
789 			if (min > max) gmt_M_double_swap (min, max);	/* Got it backwards in the array */
790 			if (fabs (dummy[0] - min) > threshold || fabs (dummy[1] - max) > threshold) {
791 				if (gmtnc_not_obviously_polar (dummy)) {
792 					GMT_Report (GMT->parent, GMT_MSG_WARNING, "The y-coordinates and range attribute are in conflict; must rely on coordinates only\n");
793 					dummy[0] = xy[0], dummy[1] = xy[header->n_rows-1];
794 					has_range = false;	/* Since useless information */
795 					/* Registration was set using x values, so here we just check that we get the same result.
796 					 * If the coordinates are off by 0.5*dy then we assume we have pixel registration */
797 					threshold = (0.5-GMT_CONV5_LIMIT) * dy;
798 					if (fabs (fmod (dummy[0], dy)) > threshold) {	/* Pixel registration? */
799 						if (header->registration == GMT_GRID_NODE_REG)	/* No, somehow messed up now */
800 							GMT_Report (GMT->parent, GMT_MSG_WARNING, "Guessing of registration in conflict between x and y, using %s\n", regtype[header->registration]);
801 						else {	/* Pixel registration confirmed */
802 							dummy[0] -= 0.5 * dy;	dummy[1] += 0.5 * dy;
803 							registration = GMT_GRID_PIXEL_REG;
804 							if (gmt_M_180_range (dummy[0], dummy[1]))
805 								GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Grid y-coordinates after pixel registration adjustment have exactly 180 range\n");
806 						}
807 					}
808 				}
809 				else {
810 					GMT_Report (GMT->parent, GMT_MSG_WARNING, "The y-coordinates and range attribute are in conflict but range is exactly 180; we rely on this range\n");
811 					if ((header->n_rows%2) == 1 && header->registration == GMT_GRID_NODE_REG)	/* Pixel registration? */
812 						GMT_Report (GMT->parent, GMT_MSG_WARNING, "Guessing of registration in conflict between x and y, using %s\n", regtype[header->registration]);
813 					else
814 						registration = header->registration;
815 				}
816 			}
817 			else {	/* Data seems OK; determine registration and set dummy from data coordinates */
818 				dummy[0] = xy[0], dummy[1] = xy[header->n_rows-1];
819 				if ((fabs(dummy[1] - dummy[0]) / fabs(xy[header->n_rows-1] - xy[0]) - 1.0 > 0.5 / (header->n_rows - 1)) && header->registration == GMT_GRID_NODE_REG)
820 					GMT_Report (GMT->parent, GMT_MSG_WARNING, "Guessing of registration in conflict between x and y, using %s\n", regtype[header->registration]);
821 			}
822 		}
823 		else if (has_vector) {	/* No attribute for range, use coordinates */
824 			threshold = (0.5-GMT_CONV5_LIMIT) * dy;
825 			dummy[0] = xy[0], dummy[1] = xy[header->n_rows-1];
826 			if (fabs (fmod (dummy[0], dy)) > threshold) {	/* Most likely pixel registration since off by dy/2 */
827 				if (header->registration == GMT_GRID_NODE_REG)	/* No, somehow messed up now */
828 					GMT_Report (GMT->parent, GMT_MSG_WARNING, "Guessing of registration in conflict between x and y, using %s\n", regtype[header->registration]);
829 				else {	/* Pixel registration confirmed */
830 					if (dummy[0] > dummy[1]) {		/* Check for reverse order of y-coordinate */
831 						gmt_M_double_swap (dummy[0], dummy[1]);
832 						HH->row_order = k_nc_start_north;
833 					}
834 					dummy[0] -= 0.5 * dy;	dummy[1] += 0.5 * dy;
835 					registration = GMT_GRID_PIXEL_REG;
836 					if (gmt_M_180_range (dummy[0], dummy[1]))
837 						GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Grid y-coordinates after pixel registration adjustment have exactly 180 range\n");
838 				}
839 			}
840 		}
841 		else {	/* Only has the valid_range settings.  If no registration set, and no dy available, guess based on ny */
842 			if ((header->n_rows%2) == 1 && header->registration == GMT_GRID_NODE_REG)	/* Pixel registration? */
843 				GMT_Report (GMT->parent, GMT_MSG_WARNING, "Guessing of registration in conflict between x and y, using %s\n", regtype[header->registration]);
844 		}
845 
846 		/* Check for reverse order of y-coordinate */
847 		if (dummy[0] > dummy[1]) {
848 			HH->row_order = k_nc_start_north;
849 			gmt_M_double_swap (dummy[0], dummy[1]);
850 		}
851 		else if (has_vector && (xy[0] > xy[header->n_rows-1]) && (dummy[0] > dummy[1])) 	/* Here the lat vector is top-down but range is bottum-up */
852 			HH->row_order = k_nc_start_north;
853 		else if (HH->row_order != k_nc_start_north)		/* If not already set to north in an above test */
854 			HH->row_order = k_nc_start_south;
855 
856 		/* Determine grid step */
857 		header->inc[GMT_Y] = gmt_M_get_inc (GMT, dummy[0], dummy[1], header->n_rows, registration);
858 		if (gmt_M_is_dnan(header->inc[GMT_Y]) || gmt_M_is_zero (header->inc[GMT_Y])) header->inc[GMT_Y] = 1.0;
859 		if (header->n_rows == 1) registration = GMT_GRID_PIXEL_REG;	/* The only way to have a grid like that */
860 
861 #ifdef NC4_DEBUG
862 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "y registration: %u\n", header->registration);
863 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "y dummy: %g %g\n", dummy[0], dummy[1]);
864 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "y[0] y[ny-1]: %g %g\n", xy[0], xy[header->n_rows-1]);
865 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "xinc: %g %g\n", header->inc[GMT_Y]);
866 #endif
867 
868 		/* Extend y boundaries by half if we found pixel registration */
869 		if (registration == GMT_GRID_NODE_REG && header->registration == GMT_GRID_PIXEL_REG)
870 			header->wesn[YLO] = dummy[0] - header->inc[GMT_Y] / 2.0,
871 			header->wesn[YHI] = dummy[1] + header->inc[GMT_Y] / 2.0;
872 		else	/* Use as is */
873 			header->wesn[YLO] = dummy[0], header->wesn[YHI] = dummy[1];
874 
875 		gmt_M_free (GMT, xy);	/* Done with the array */
876 
877 		/* Get information about z variable */
878 		gmtnc_get_units (GMT, ncid, z_id, header->z_units);
879 		if (nc_get_att_double (ncid, z_id, "scale_factor", &header->z_scale_factor)) header->z_scale_factor = 1.0;
880 		if (nc_get_att_double (ncid, z_id, "add_offset", &header->z_add_offset)) header->z_add_offset = 0.0;
881 #ifdef DOUBLE_PRECISION_GRID
882 		if (nc_get_att_double (ncid, z_id, "_FillValue", &header->nan_value))
883 		    nc_get_att_double (ncid, z_id, "missing_value", &header->nan_value);
884 #else
885 		if (nc_get_att_float (ncid, z_id, "_FillValue", &header->nan_value))
886 		    nc_get_att_float (ncid, z_id, "missing_value", &header->nan_value);
887 #endif
888 		if (!nc_get_att_double (ncid, z_id, "actual_range", dummy)) {
889 			/* z-limits need to be converted from actual to internal grid units. */
890 			header->z_min = (dummy[0] - header->z_add_offset) / header->z_scale_factor;
891 			header->z_max = (dummy[1] - header->z_add_offset) / header->z_scale_factor;
892 		}
893 		else if (!nc_get_att_double (ncid, z_id, "valid_range", dummy) ||
894 			!(nc_get_att_double (ncid, ids[HH->xy_dim[1]], "valid_min", &dummy[0]) +
895 			nc_get_att_double (ncid, ids[HH->xy_dim[1]], "valid_max", &dummy[1]))) {
896 			/* Valid range is already in packed units, so do not convert */
897 			header->z_min = dummy[0], header->z_max = dummy[1];
898 		}
899 		if (gmt_M_is_dnan (header->z_min) && gmt_M_is_dnan (header->z_max)) {
900 			GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "netCDF grid %s information has zmin = zmax = NaN. Reset to 0/0.\n", HH->name);
901 			header->z_min = header->z_max = 0.0;
902 		}
903 		{	/* Get deflation and chunking info */
904 			int storage_mode, shuffle = 0, deflate = 0, deflate_level = 0;
905 			size_t chunksize[5]; /* chunksize of z */
906 			gmt_M_err_trap (nc_inq_var_chunking (ncid, z_id, &storage_mode, chunksize));
907 			if (storage_mode == NC_CHUNKED) {
908 				HH->z_chunksize[0] = chunksize[dims[0]]; /* chunk size in vertical dimension */
909 				HH->z_chunksize[1] = chunksize[dims[1]]; /* chunk size in horizontal dimension */
910 			}
911 			else { /* NC_CONTIGUOUS */
912 				HH->z_chunksize[0] = HH->z_chunksize[1] = 0;
913 			}
914 			if (HH->is_netcdf4) gmt_M_err_trap (nc_inq_var_deflate (ncid, z_id, &shuffle, &deflate, &deflate_level));
915 			HH->z_shuffle = shuffle ? true : false; /* if shuffle filter is turned on */
916 			HH->z_deflate_level = deflate ? deflate_level : 0; /* if deflate filter is in use */
917 		}
918 
919 		/* Determine t_index from t_value */
920 		item[0] = 0;
921 		for (i = 0; i < n_t_index_by_value; i++) {
922 			item[1] = lens[i]-1;
923 			if (nc_get_att_double (ncid, ids[i], "actual_range", dummy)) {
924 				gmt_M_err_trap (nc_get_var1_double (ncid, ids[i], &item[0], &dummy[0]));
925 				gmt_M_err_trap (nc_get_var1_double (ncid, ids[i], &item[1], &dummy[1]));
926 			}
927 			if (item[1] != 0 && dummy[0] != dummy[1]) { /* avoid dvision by 0 */
928 				double index = (t_value[i] - dummy[0]) / (dummy[1] - dummy[0]) * item[1];
929 				if (index > 0)
930 					HH->t_index[i] = (size_t)index;
931 			}
932 		}
933 
934 #ifdef NC4_DEBUG
935 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->wesn: %g %g %g %g\n",
936 		            header->wesn[XLO], header->wesn[XHI], header->wesn[YLO], header->wesn[YHI]);
937 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->registration:%u\n", header->registration);
938 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->row_order: %s\n",
939 		            HH->row_order == k_nc_start_south ? "S->N" : "N->S");
940 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->n_columns: %3d   head->n_rows:%3d\n", header->n_columns, header->n_rows);
941 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->inc: %g %g\n", header->inc[GMT_X], header->inc[GMT_Y]);
942 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->mx: %3d   head->my:%3d\n", header->mx, header->my);
943 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->nm: %3d head->size:%3d\n", header->nm, header->size);
944 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->t-index %d,%d,%d\n",
945 		            HH->t_index[0], HH->t_index[1], HH->t_index[2]);
946 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->pad xlo:%u xhi:%u ylo:%u yhi:%u\n",
947 		            header->pad[XLO], header->pad[XHI], header->pad[YLO], header->pad[YHI]);
948 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->BC  xlo:%u xhi:%u ylo:%u yhi:%u\n",
949 		            HH->BC[XLO], HH->BC[XHI], HH->BC[YLO], HH->BC[YHI]);
950 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->grdtype:%u %u\n", HH->grdtype, GMT_GRID_GEOGRAPHIC_EXACT360_REPEAT);
951 #endif
952 	}
953 	else {	/* Here we are writing */
954 		/* Store global attributes */
955 		const int *nc_vers = gmtnc_netcdf_libvers();
956 		GMT_Report (GMT->parent, GMT_MSG_DEBUG, "netCDF Library version: %d\n", *nc_vers);
957 		gmt_M_err_trap (nc_put_att_text (ncid, NC_GLOBAL, "Conventions", strlen(GMT_NC_CONVENTION), GMT_NC_CONVENTION));
958 		if (header->title[0]) gmt_M_err_trap (nc_put_att_text (ncid, NC_GLOBAL, "title", strlen(header->title), header->title));
959 		gmt_M_err_trap (nc_put_att_text (ncid, NC_GLOBAL, "history", strlen(header->command), header->command));
960 		if (header->remark[0]) gmt_M_err_trap (nc_put_att_text (ncid, NC_GLOBAL, "description", strlen(header->remark), header->remark));
961 		gmt_M_err_trap (nc_put_att_text (ncid, NC_GLOBAL, "GMT_version", strlen(GMT_VERSION), (const char *) GMT_VERSION));
962 		if (header->registration == GMT_GRID_PIXEL_REG) {
963 			int reg = header->registration;
964 			gmt_M_err_trap (nc_put_att_int (ncid, NC_GLOBAL, "node_offset", NC_LONG, 1U, &reg));
965 		}
966 		else
967 			nc_del_att (ncid, NC_GLOBAL, "node_offset");
968 
969 #ifdef HAVE_GDAL
970 		/* If we have projection information create a container variable named "grid_mapping" with an attribute
971 		   "spatial_ref" that will hold the projection info in WKT format. GDAL and Mirone know use this info */
972 		if ((header->ProjRefWKT != NULL) || (header->ProjRefPROJ4 != NULL)) {
973 			int id[1], dim[1];
974 
975 			if (header->ProjRefWKT == NULL || !header->ProjRefWKT[0]) {				/* Must convert from proj4 string to WKT */
976 				OGRSpatialReferenceH hSRS = OSRNewSpatialReference(NULL);
977 
978 				if (header->ProjRefPROJ4 && (!strncmp(header->ProjRefPROJ4, "+unavailable", 4) || strlen(header->ProjRefPROJ4) <= 5)) {	/* Silently jump out of here */
979 					OSRDestroySpatialReference(hSRS);
980 					goto L100;
981 				}
982 				GMT_Report(GMT->parent, GMT_MSG_INFORMATION, "Proj4 string to be converted to WKT:\n\t%s\n", header->ProjRefPROJ4);
983 				if (OSRImportFromProj4(hSRS, header->ProjRefPROJ4) == CE_None) {
984 					char *pszPrettyWkt = NULL;
985 					OSRExportToPrettyWkt(hSRS, &pszPrettyWkt, false);
986 					header->ProjRefWKT = strdup(pszPrettyWkt);
987 					CPLFree(pszPrettyWkt);
988 					GMT_Report(GMT->parent, GMT_MSG_INFORMATION, "WKT converted from proj4 string:\n%s\n", header->ProjRefWKT);
989 				}
990 				else {
991 					header->ProjRefWKT = NULL;
992 					GMT_Report(GMT->parent, GMT_MSG_WARNING, "gmtnc_grd_info failed to convert the proj4 string\n%s\n to WKT\n",
993 							header->ProjRefPROJ4);
994 				}
995 				OSRDestroySpatialReference(hSRS);
996 			}
997 
998 			if (header->ProjRefWKT != NULL) {			/* It may be NULL if the above conversion failed */
999 				if (nc_inq_varid(ncid, "grid_mapping", &id[0]) != NC_NOERR) {
1000 					gmt_M_err_trap(nc_def_dim(ncid, "grid_mapping", 12U, &dim[0]));
1001 					gmt_M_err_trap(nc_def_var(ncid, "grid_mapping", NC_CHAR,  1, dim, &id[0]));
1002 				}
1003 				gmt_M_err_trap(nc_put_att_text(ncid, id[0], "spatial_ref", strlen(header->ProjRefWKT), header->ProjRefWKT));
1004 				gmt_M_err_trap(nc_put_att_text(ncid, z_id, "grid_mapping", 12U, "grid_mapping"));	/* Create attrib in z variable */
1005 			}
1006 		}
1007 L100:
1008 #endif
1009 
1010 		/* Avoid NaN increments */
1011 		if (gmt_M_is_dnan(header->inc[GMT_X])) header->inc[GMT_X] = 1.0;
1012 		if (gmt_M_is_dnan(header->inc[GMT_Y])) header->inc[GMT_Y] = 1.0;
1013 
1014 		/* Define x variable */
1015 		gmtnc_put_units (ncid, ids[HH->xy_dim[0]], header->x_units);
1016 		dummy[0] = header->wesn[XLO], dummy[1] = header->wesn[XHI];
1017 		gmt_M_err_trap (nc_put_att_double (ncid, ids[HH->xy_dim[0]], "actual_range", NC_DOUBLE, 2U, dummy));
1018 		gmt_M_err_trap (nc_put_att_text (ncid, ids[HH->xy_dim[0]], "axis", 1U, "X"));
1019 
1020 		/* Define y variable */
1021 		gmtnc_put_units (ncid, ids[HH->xy_dim[1]], header->y_units);
1022 		dummy[0] = header->wesn[YLO], dummy[1] = header->wesn[YHI];
1023 		gmt_M_err_trap (nc_put_att_double (ncid, ids[HH->xy_dim[1]], "actual_range", NC_DOUBLE, 2U, dummy));
1024 		gmt_M_err_trap (nc_put_att_text (ncid, ids[HH->xy_dim[1]], "axis", 1U, "Y"));
1025 
1026 		/* When varname is given, and z_units is default, overrule z_units with varname */
1027 		if (HH->varname[0] && !strcmp (header->z_units, cube ? "cube" : "z"))
1028 			strncpy (header->z_units, HH->varname, GMT_GRID_UNIT_LEN80-1);
1029 
1030 		/* Define z variable. Attempt to remove "scale_factor" or "add_offset" when no longer needed */
1031 		gmtnc_put_units (ncid, z_id, header->z_units);
1032 
1033 		if (header->z_scale_factor != 1.0) {
1034 			gmt_M_err_trap (nc_put_att_double (ncid, z_id, "scale_factor", NC_DOUBLE, 1U, &header->z_scale_factor));
1035 		}
1036 		else if (job == 'u')
1037 			nc_del_att (ncid, z_id, "scale_factor");
1038 
1039 		if (header->z_add_offset != 0.0) {
1040 			gmt_M_err_trap (nc_put_att_double (ncid, z_id, "add_offset", NC_DOUBLE, 1U, &header->z_add_offset));
1041 		}
1042 		else if (job == 'u')
1043 			nc_del_att (ncid, z_id, "add_offset");
1044 
1045 		if (z_type != NC_FLOAT && z_type != NC_DOUBLE)
1046 			header->nan_value = rintf (header->nan_value); /* round to integer */
1047 		if (job == 'u' && HH->is_netcdf4) {
1048 			/* netCDF-4 has a bug and crash when * rewriting the _FillValue attribute in netCDF-4 files
1049 			   https://bugtracking.unidata.ucar.edu/browse/NCF-187
1050 			   To work-around it we implement the renaming trick advised on NCF-133
1051          Edit: This work-around should eventually be removed because the bug was fixed as of 2015-04-02 (any version >4.3.3.1 should be fine).
1052 			*/
1053 			gmt_M_err_trap (nc_rename_att (ncid, z_id, "_FillValue", "_fillValue"));
1054 #ifdef DOUBLE_PRECISION_GRID
1055 			gmt_M_err_trap (nc_put_att_double (ncid, z_id, "_fillValue", z_type, 1U, &header->nan_value));
1056 #else
1057 			gmt_M_err_trap (nc_put_att_float (ncid, z_id, "_fillValue", z_type, 1U, &header->nan_value));
1058 #endif
1059 			gmt_M_err_trap (nc_rename_att (ncid, z_id, "_fillValue", "_FillValue"));
1060 		}
1061 		else {
1062 #ifdef DOUBLE_PRECISION_GRID
1063 			gmt_M_err_trap (nc_put_att_double (ncid, z_id, "_FillValue", z_type, 1U, &header->nan_value));
1064 #else
1065 			gmt_M_err_trap (nc_put_att_float (ncid, z_id, "_FillValue", z_type, 1U, &header->nan_value));
1066 #endif
1067 		}
1068 
1069 		/* Limits need to be stored in actual, not internal grid, units */
1070 		if (header->z_min <= header->z_max) {
1071 			dummy[0] = header->z_min * header->z_scale_factor + header->z_add_offset;
1072 			dummy[1] = header->z_max * header->z_scale_factor + header->z_add_offset;
1073 		}
1074 		else
1075 			dummy[0] = 0.0, dummy[1] = 0.0;
1076 		gmt_M_err_trap (nc_put_att_double (ncid, z_id, "actual_range", NC_DOUBLE, 2U, dummy));
1077 
1078 		/* Store values along x and y axes */
1079 		nc_inq_nvars (ncid, &nvars);
1080 		for (j = 0; j < nvars; j++) {
1081 			gmt_M_err_trap (nc_inq_varndims (ncid, j, &ndims));
1082 		}
1083 		if (!cube) {	/* Hold off on this until we also have the 3rd dimension */
1084 			gmt_M_err_trap (nc_enddef (ncid));	/* End definition mode */
1085 			gmtnc_put_xy_vectors (GMT, header);	/* Place x and y vectors */
1086 		}
1087 	}
1088 
1089 	/* Close NetCDF file, unless job == 'W' */
1090 
1091 	if (job != 'W') gmt_M_err_trap (gmt_nc_close (GMT, ncid));
1092 	return (GMT_NOERROR);
1093 }
1094 
1095 /* Shift columns in a grid to the right (n_shift < 0) or to the left (n_shift < 0) */
gmtnc_right_shift_grid(void * gridp,const unsigned n_cols,const unsigned n_rows,int n_shift,size_t cell_size)1096 GMT_LOCAL void gmtnc_right_shift_grid (void *gridp, const unsigned n_cols, const unsigned n_rows, int n_shift, size_t cell_size) {
1097 	char *tmp = NULL, *grid = (char*)gridp;
1098 	size_t row, n_shift_abs = abs (n_shift), nm;
1099 	size_t n_cols_t = (size_t)n_cols, n_rows_t = (size_t)n_rows;
1100 
1101 	assert (n_shift_abs != 0 && n_cols_t > n_shift_abs && n_cols_t > 0 && n_rows_t > 0);
1102 
1103 	tmp = calloc (n_shift_abs, cell_size);
1104 
1105 	if (n_shift > 0) { /* right shift */
1106 		for (row = 0; row < n_rows_t; ++row) {
1107 			nm = row * n_cols_t;
1108 			/* copy last n_shift_abs cols into tmp buffer */
1109 			memcpy (tmp, grid + (nm + n_cols_t - n_shift_abs) * cell_size, n_shift_abs * cell_size);
1110 			/* right shift row */
1111 			memmove (grid + (nm + n_shift_abs) * cell_size,
1112 							 grid + nm * cell_size,
1113 							 (n_cols_t - n_shift_abs) * cell_size);
1114 			/* prepend tmp buffer */
1115 			memcpy (grid + nm * cell_size, tmp, n_shift_abs * cell_size);
1116 		}
1117 	}
1118 	else { /* n_shift_abs < 0 */
1119 		for (row = 0; row < n_rows_t; ++row) {
1120 			nm = row * n_cols_t;
1121 			/* copy first n_shift_abs cols into tmp buffer */
1122 			memcpy (tmp, grid + nm * cell_size, n_shift_abs * cell_size);
1123 			/* left shift row */
1124 			memmove (grid + nm * cell_size,
1125 							 grid + (nm + n_shift_abs) * cell_size,
1126 							 (n_cols_t - n_shift_abs) * cell_size);
1127 			/* append tmp buffer */
1128 			memcpy (grid + (nm + n_cols_t - n_shift_abs) * cell_size, tmp, n_shift_abs * cell_size);
1129 		}
1130 	}
1131 	gmt_M_str_free (tmp);
1132 }
1133 
1134 /* Fill padding by replicating the border cells or wrapping around a
1135  * row if columns are periodic */
gmtnc_padding_copy(void * gridp,const unsigned n_cols,const unsigned n_rows,const unsigned * n_pad,size_t cell_size,bool periodic_cols)1136 GMT_LOCAL void gmtnc_padding_copy (void *gridp, const unsigned n_cols, const unsigned n_rows, const unsigned *n_pad, size_t cell_size, bool periodic_cols) {
1137 	/* n_cols and n_rows are dimensions of the padded grid */
1138 	char *grid = (char*)gridp;
1139 	size_t row, cell, nm, nm2;
1140 	size_t n_cols_t = (size_t)n_cols, n_rows_t = (size_t)n_rows;
1141 	size_t pad_t[4];
1142 
1143 	assert (n_cols > n_pad[XLO] + n_pad[XHI] && n_rows > n_pad[YLO] + n_pad[YHI] &&
1144 		n_pad[XLO] + n_pad[XHI] + n_pad[YLO] + n_pad[YHI] > 0 && cell_size > 0);
1145 
1146 	for (row = 0; row < 4; row++) pad_t[row] = (size_t)n_pad[row];
1147 
1148 	if (periodic_cols) {
1149 		/* A periodic grid wraps around */
1150 		for (row = pad_t[YHI]; (row + pad_t[YLO]) < n_rows_t; ++row) {
1151 			nm = row * n_cols_t;
1152 			/* Iterate over rows that contain data */
1153 			for (cell = 0; cell < pad_t[XLO]; ++cell) {
1154 				/* Copy end of this row into first pad_t[XLO] columns:
1155 				 * X X 0 1 2 3 4 5 X X -> 4 5 0 1 2 3 4 5 X X */
1156 				memcpy (grid + (nm + cell) * cell_size,
1157 								grid + (nm + n_cols_t + cell - pad_t[XLO] - pad_t[XHI]) * cell_size,
1158 								cell_size);
1159 			}
1160 			for (cell = 0; cell < pad_t[XHI]; ++cell) {
1161 				/* Copy start of this row into last n_pad[XHI] columns:
1162 				 * 4 5 0 1 2 3 4 5 X X -> 4 5 0 1 2 3 4 5 0 1 */
1163 				memcpy (grid + (nm + n_cols_t - cell - 1) * cell_size,
1164 								grid + (nm + pad_t[XLO] + pad_t[XHI] - cell - 1) * cell_size,
1165 								cell_size);
1166 			}
1167 		}
1168 	}
1169 	else { /* !periodic_cols */
1170 		for (row = pad_t[YHI]; (row + pad_t[YLO]) < n_rows_t; ++row) {
1171 			nm = row * n_cols_t;
1172 			/* Iterate over rows that contain data */
1173 			for (cell = 0; cell < pad_t[XLO]; ++cell) {
1174 				/* Duplicate first pad_t[XLO] columns in this row:
1175 				 * 4 5 0 1 2 3 4 5 X X -> 0 0 0 1 2 3 4 5 X X */
1176 				memcpy (grid + (nm + cell) * cell_size,
1177 								grid + (nm + pad_t[XLO]) * cell_size,
1178 								cell_size);
1179 			}
1180 			for (cell = 0; cell < pad_t[XHI]; ++cell) {
1181 				/* Duplicate last pad_t[XHI] columns in this row:
1182 				 * 0 0 0 1 2 3 4 5 X X -> 0 0 0 1 2 3 4 5 5 5 */
1183 				memcpy (grid + (nm + n_cols_t - cell - 1) * cell_size,
1184 								grid + (nm + n_cols_t - pad_t[XHI] - 1) * cell_size,
1185 								cell_size);
1186 			}
1187 		}
1188 	}
1189 
1190 	for (cell = 0; cell < pad_t[YHI]; ++cell) {
1191 		nm = cell * n_cols_t;
1192 		/* Duplicate pad_t[YHI] rows in the beginning */
1193 		memcpy (grid + nm * cell_size,
1194 					 grid + pad_t[YHI] * n_cols_t * cell_size,
1195 					 n_cols_t * cell_size);
1196 	}
1197 	nm2 = (n_rows_t - pad_t[YLO] - 1) * n_cols_t;
1198 	for (cell = 0; cell < pad_t[YLO]; ++cell) {
1199 		nm = (n_rows_t - cell - 1) * n_cols_t;
1200 		/* Duplicate last pad_t[YLO] rows */
1201 		memcpy (grid + nm * cell_size,
1202 					 grid + nm2 * cell_size,
1203 					 n_cols_t * cell_size);
1204 	}
1205 }
1206 
1207 /* Fill padding with zeros */
gmtnc_padding_zero(void * gridp,const unsigned n_cols,const unsigned n_rows,const unsigned * n_pad,size_t cell_size)1208 GMT_LOCAL void gmtnc_padding_zero (void *gridp, const unsigned n_cols, const unsigned n_rows, const unsigned *n_pad, size_t cell_size) {
1209 	/* n_cols and n_rows are dimensions of the padded grid */
1210 	char *grid = (char*)gridp;
1211 	size_t row, nm;
1212 	size_t n_cols_t = (size_t)n_cols, n_rows_t = (size_t)n_rows;
1213 	size_t pad_t[4];
1214 
1215 	assert (n_cols > n_pad[XLO] + n_pad[XHI] && n_rows > n_pad[YLO] + n_pad[YHI] &&
1216 		n_pad[XLO] + n_pad[XHI] + n_pad[YLO] + n_pad[YHI] > 0 && cell_size > 0);
1217 
1218 	for (row = 0; row < 4; row++) pad_t[row] = (size_t)n_pad[row];
1219 
1220 	/* Iterate over rows that contain data */
1221 	for (row = pad_t[YHI]; (row + pad_t[YLO]) < n_rows_t; ++row) {
1222 		nm = row * n_cols_t;
1223 		/* Zero n cells at beginning of row */
1224 		memset (grid + nm * cell_size, 0, pad_t[XLO] * cell_size);
1225 		/* Zero n cells at end of row */
1226 		memset (grid + (nm + n_cols_t - pad_t[XHI]) * cell_size, 0, pad_t[XHI] * cell_size);
1227 	}
1228 	/* Zero pad_t[YHI] rows in the beginning */
1229 	memset (grid, 0, pad_t[YHI] * n_cols_t * cell_size);
1230 	/* Zero last pad_t[YLO] rows */
1231 	memset(grid + (n_rows_t-pad_t[YLO]) * n_cols_t * cell_size, 0, pad_t[YLO] * n_cols_t * cell_size);
1232 }
1233 
1234 /* Fill mode for grid padding */
1235 enum Grid_padding_mode {
1236 	k_pad_fill_none = 0, /* Leave padded cells untouched */
1237 	k_pad_fill_zero,     /* Fill padded grid cells with zeros */
1238 	k_pad_fill_copy,     /* Padded cells get the value of their nearest neighbor */
1239 	k_pad_fill_copy_wrap /* Padded cells get wrapped values from the other side of the row (gridswith periodic columns) */
1240 };
1241 
1242 /* Add padding to a matrix/grid and reshape data */
gmtnc_pad_grid(void * gridp,const unsigned n_cols,const unsigned n_rows,const unsigned * n_pad,size_t cell_size,unsigned filltype)1243 GMT_LOCAL void gmtnc_pad_grid (void *gridp, const unsigned n_cols, const unsigned n_rows, const unsigned *n_pad, size_t cell_size, unsigned filltype) {
1244 	/* n_cols and n_rows are dimensions of the grid without padding
1245 	 * cell_size is the size in bytes of each element in grid
1246 	 * n_pad[4] contains the number of cols/rows to pad on each side {W,E,S,N}
1247 	 *
1248 	 * Note: when grid is complex, we pass 2x n_rows */
1249 	char *grid = (char*)gridp;
1250 	size_t n_cols_t = (size_t)n_cols, n_rows_t = (size_t)n_rows;
1251 	size_t new_row;
1252 	size_t old_row = n_rows_t-1;
1253 	size_t n_new_cols = n_cols_t + n_pad[XLO] + n_pad[XHI];
1254 	size_t n_new_rows = n_rows_t + n_pad[YLO] + n_pad[YHI];
1255 
1256 #ifdef NC4_DEBUG
1257 	fprintf (stderr, "pad grid w:%u e:%u s:%u n:%u\n",
1258 			n_pad[XLO], n_pad[XHI], n_pad[YLO], n_pad[YHI]);
1259 #endif
1260 	if (n_pad[XLO] + n_pad[XHI] + n_pad[YLO] + n_pad[YHI] == 0)
1261 		return; /* nothing to pad */
1262 
1263 	assert (n_cols > 0 && n_rows > 0 && cell_size > 0);
1264 
1265 	/* Reshape matrix */
1266 	if (n_pad[XLO] + n_pad[XHI] + n_pad[YHI] != 0) {
1267 		/* When padding W, E, and N (not necessary when padding S only). */
1268 		for (new_row = n_new_rows - n_pad[YLO] - 1; new_row + 1 > n_pad[YHI]; --new_row, --old_row) {
1269 			/* Copy original row to new row, bottom upwards */
1270 			void *from = grid + old_row * n_cols_t * cell_size;
1271 			void *to   = grid + (new_row * n_new_cols + ((size_t)n_pad[XLO])) * cell_size;
1272 			if (n_pad[YHI] == 0) /* rows overlap! */
1273 				memmove (to, from, n_cols_t * cell_size);
1274 			else /* no overlap, memcpy is safe */
1275 				memcpy  (to, from, n_cols_t * cell_size);
1276 		}
1277 	}
1278 
1279 	/* Fill padded grid cells */
1280 	switch (filltype) {
1281 		case k_pad_fill_zero:
1282 			gmtnc_padding_zero (grid, (const unsigned)n_new_cols, (const unsigned)n_new_rows, n_pad, cell_size);
1283 			break;
1284 		case k_pad_fill_copy:
1285 			gmtnc_padding_copy (grid, (const unsigned)n_new_cols, (const unsigned)n_new_rows, n_pad, cell_size, false);
1286 			break;
1287 		case k_pad_fill_copy_wrap:
1288 			gmtnc_padding_copy (grid, (const unsigned)n_new_cols, (const unsigned)n_new_rows, n_pad, cell_size, true);
1289 			break;
1290 	}
1291 }
1292 
1293 /* Remove padding from a matrix/grid and reshape data */
gmtnc_unpad_grid(void * gridp,const unsigned n_cols,const unsigned n_rows,const unsigned * n_pad,size_t cell_size)1294 GMT_LOCAL void gmtnc_unpad_grid (void *gridp, const unsigned n_cols, const unsigned n_rows, const unsigned *n_pad, size_t cell_size) {
1295 	/* n_cols and n_rows are dimensions of the grid without padding
1296 	 * cell_size is the size in bytes of each element in grid
1297 	 * n_pad[4] contains the number of cols/rows to pad on each side {W,E,S,N}
1298 	 *
1299 	 * Note: when grid is complex, we pass 2x n_rows */
1300 	char *grid = (char*)gridp;
1301 	size_t n_old_cols = n_cols + n_pad[XLO] + n_pad[XHI];
1302 	size_t n_cols_t = (size_t)n_cols, n_rows_t = (size_t)n_rows;
1303 	size_t row;
1304 
1305 #ifdef NC4_DEBUG
1306 	fprintf (stderr, "unpad grid w:%u e:%u s:%u n:%u\n",
1307 			n_pad[XLO], n_pad[XHI], n_pad[YLO], n_pad[YHI]);
1308 #endif
1309 	if (n_pad[XLO] + n_pad[XHI] + n_pad[YHI] == 0)
1310 		return; /* nothing to unpad (we just ignore n_pad[YLO]) */
1311 
1312 	assert (n_cols > 0 && n_rows > 0 && cell_size > 0);
1313 
1314 	/* Reshape matrix */
1315 	for (row = 0; row < n_rows_t; ++row) {
1316 		size_t old_row = row + (size_t)n_pad[YHI];
1317 		void *from = grid + (old_row * n_old_cols + ((size_t)n_pad[XLO])) * cell_size;
1318 		void *to   = grid + row * n_cols_t * cell_size;
1319 		/* Copy original row to new row */
1320 		if (n_pad[YHI] == 0) /* rows overlap! */
1321 			memmove (to, from, n_cols_t * cell_size);
1322 		else /* no overlap, memcpy is safe */
1323 			memcpy  (to, from, n_cols_t * cell_size);
1324 	}
1325 }
1326 
1327 /* Ensure that repeating columns in geographic gridline registered grids
1328  * do not contain conflicting information */
gmtnc_grid_fix_repeat_col(struct GMT_CTRL * GMT,void * gridp,const unsigned n_cols,const unsigned n_rows,size_t cell_size)1329 GMT_LOCAL void gmtnc_grid_fix_repeat_col (struct GMT_CTRL *GMT, void *gridp, const unsigned n_cols, const unsigned n_rows, size_t cell_size) {
1330 	/* Note: when grid is complex, pass 2x n_rows */
1331 	char *grid = (char*)gridp;
1332 	unsigned n_conflicts = 0;
1333 	size_t row;
1334 	size_t n_cols_t = (size_t)n_cols, n_rows_t = (size_t)n_rows;
1335 
1336 	for (row = 0; row < n_rows_t; ++row) {
1337 		char *first = grid + row * n_cols_t * cell_size;                /* first element in row */
1338 		char *last =  grid + (row * n_cols_t + n_cols_t - 1) * cell_size; /* last element in row */
1339 		if ( memcmp(last, first, cell_size) ) {
1340 			/* elements differ: replace value of last element in row with value of first */
1341 			memcpy (last, first, cell_size);
1342 			++n_conflicts;
1343 		}
1344 	}
1345 
1346 	if (n_conflicts)
1347 		GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Detected %u inconsistent values along east boundary of grid. Values fixed by duplicating west boundary.\n", n_conflicts);
1348 }
1349 
1350 /* Change the default chunk cache settings in the HDF5 library for all variables
1351  * in the nc-file. The settings apply for subsequent file opens/creates. */
gmtnc_setup_chunk_cache(void)1352 static inline void gmtnc_setup_chunk_cache (void) {
1353 	static bool already_setup = false;
1354 	if (!already_setup) {
1355 		nc_set_chunk_cache (NC_CACHE_SIZE, NC_CACHE_NELEMS, NC_CACHE_PREEMPTION);
1356 		already_setup = true;
1357 	}
1358 }
1359 
1360 
gmtnc_grd_prep_io(struct GMT_CTRL * GMT,struct GMT_GRID_HEADER * header,double wesn[4],unsigned int * width,unsigned int * height,int * n_shift,unsigned origin[2],unsigned dim[2],unsigned origin2[2],unsigned dim2[2])1361 GMT_LOCAL int gmtnc_grd_prep_io (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *header, double wesn[4], unsigned int *width, unsigned int *height, int *n_shift, unsigned origin[2], unsigned dim[2], unsigned origin2[2], unsigned dim2[2]) {
1362 	/* Determines which rows and columns to extract from a grid, based on
1363 	 * w,e,s,n.  This routine first rounds the w,e,s,n boundaries to the nearest
1364 	 * gridlines or pixels, then determines the first and last columns and rows,
1365 	 * and the width and height of the subset (in cells).
1366 	 */
1367 	bool is_gridline_reg, is_global, is_global_repeat;
1368 	unsigned last_row, first_col, last_col, first_row;
1369 	unsigned first_col2, last_col2;
1370 	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (header);
1371 
1372 	*n_shift = 0;
1373 	memset (origin2, 0, 2 * sizeof(unsigned));
1374 	memset (dim2, 0, 2 * sizeof(unsigned));
1375 
1376 	is_global = gmt_grd_is_global (GMT, header);
1377 	is_global_repeat = HH->grdtype == GMT_GRID_GEOGRAPHIC_EXACT360_REPEAT;
1378 	is_gridline_reg = header->registration != GMT_GRID_PIXEL_REG;
1379 
1380 #ifdef NC4_DEBUG
1381 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "  x-region: %g %g, grid: %g %g\n", wesn[XLO], wesn[XHI], header->wesn[XLO], header->wesn[XHI]);
1382 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "  y-region: %g %g, grid: %g %g\n", wesn[YLO], wesn[YHI], header->wesn[YLO], header->wesn[YHI]);
1383 #endif
1384 
1385 	if (wesn[XLO] == 0 && wesn[XHI] == 0 && wesn[YLO] == 0 && wesn[YHI] == 0) {
1386 		/* When -R... matches the exact dimensions of the grid: read whole grid */
1387 		*width  = header->n_columns;
1388 		*height = header->n_rows;
1389 		first_col = first_row = 0;
1390 		last_col  = header->n_columns - 1;
1391 		last_row  = header->n_rows - 1;
1392 		gmt_M_memcpy (wesn, header->wesn, 4, double);
1393 	}
1394 	else {
1395 		/* Must deal with a subregion */
1396 		double x;
1397 		x = fabs (header->wesn[YLO] - wesn[YLO]);	/* if |x| < GMT_CONV4_LIMIT * header->inc[GMT_Y] we set wesn to the grid limit */
1398 		if (x > 0.0 && x < GMT_CONV4_LIMIT * header->inc[GMT_Y]) wesn[YLO] = header->wesn[YLO];	/* Avoid snafu */
1399 		x = fabs (header->wesn[YHI] - wesn[YHI]);	/* if |x| < GMT_CONV4_LIMIT * header->inc[GMT_Y] we set wesn to the grid limit */
1400 		if (x > 0.0 && x < GMT_CONV4_LIMIT * header->inc[GMT_Y]) wesn[YHI] = header->wesn[YHI];	/* Avoid snafu */
1401 		if (wesn[YLO] < header->wesn[YLO] || wesn[YHI] > header->wesn[YHI])
1402 			return (GMT_GRDIO_DOMAIN_VIOLATION);	/* Calling program goofed... */
1403 
1404 		/* Make sure w,e,s,n are proper multiples of x_inc,y_inc away from x_min,y_min */
1405 		gmt_M_err_pass (GMT, gmt_adjust_loose_wesn (GMT, wesn, header), HH->name);
1406 
1407 		/* Global grids: ensure that wesn >= header->wesn (w+e only) */
1408 		if ( is_global ) {	/* Deal with wrapping issues */
1409 			while (wesn[XLO] > header->wesn[XHI]) {
1410 				wesn[XLO] -= 360;
1411 				wesn[XHI] -= 360;
1412 			}
1413 			while (wesn[XLO] < header->wesn[XLO]) {
1414 				wesn[XLO] += 360;
1415 				wesn[XHI] += 360;
1416 			}
1417 		}
1418 		else /* Apply a 10^-4 times inc sloppiness in the test [it was zero which was too harsh for sloppy grids] */
1419 			assert ((wesn[XLO]+GMT_CONV4_LIMIT*header->inc[GMT_X]) >= header->wesn[XLO] && (wesn[XHI]-GMT_CONV4_LIMIT*header->inc[GMT_X]) <= header->wesn[XHI]);
1420 
1421 		/* Get dimension of subregion */
1422 		*width  = urint ((wesn[XHI] - wesn[XLO]) * HH->r_inc[GMT_X]) + is_gridline_reg;
1423 		*height = urint ((wesn[YHI] - wesn[YLO]) * HH->r_inc[GMT_Y]) + is_gridline_reg;
1424 
1425 		/* Get first and last row and column numbers */
1426 		first_col = urint ((wesn[XLO] - header->wesn[XLO]) * HH->r_inc[GMT_X]);
1427 		last_col  = urint ((wesn[XHI] - header->wesn[XLO]) * HH->r_inc[GMT_X]) + is_gridline_reg - 1;
1428 		first_row = urint ((header->wesn[YHI] - wesn[YHI]) * HH->r_inc[GMT_Y]);
1429 		last_row  = urint ((header->wesn[YHI] - wesn[YLO]) * HH->r_inc[GMT_Y]) + is_gridline_reg - 1;
1430 
1431 		/* Adjust first_row */
1432 		if (HH->row_order == k_nc_start_south)
1433 			first_row = header->n_rows - 1 - last_row;
1434 
1435 		/* Global grids: if -R + padding is equal or exceeds grid bounds */
1436 		if (is_global && 1 + is_global_repeat + last_col - first_col >= header->n_columns) {
1437 			/* Number of requested cols >= n_columns: read whole grid and shift */
1438 			*n_shift  = -(int)first_col;
1439 			first_col = 0;
1440 			last_col  = header->n_columns - 1;
1441 		}
1442 		else if (is_global && last_col + 1 > header->n_columns) {
1443 			/* Subset of a global grid that wraps around east boundary. This means
1444 			 * we have to read the grid in two parts and stich them together. */
1445 			first_col2 = 0;
1446 			last_col2 = is_global_repeat + last_col - header->n_columns;
1447 			last_col = header->n_columns - 1;
1448 #ifdef NC4_DEBUG
1449 			GMT_Report (GMT->parent, GMT_MSG_WARNING, "col2: %u %u\n", first_col2, last_col2);
1450 #endif
1451 
1452 			origin2[0] = first_row;
1453 			origin2[1] = first_col2;
1454 			dim2[0] = *height;
1455 			assert (first_col2 <= 1 + last_col2); /* check for unsigned overflow */
1456 			dim2[1] = 1 + last_col2 - first_col2;
1457 		}
1458 		assert (last_col + 1 <= header->n_columns);
1459 	}
1460 
1461 	/* Do not read last column from global repeating grids */
1462 	if (is_global_repeat && last_col + 1 == header->n_columns)
1463 		--last_col;
1464 
1465 	origin[0] = first_row;
1466 	origin[1] = first_col;
1467 	dim[0] = *height;
1468 	assert (first_col <= 1 + last_col); /* check for unsigned overflow */
1469 	dim[1] = 1 + last_col - first_col;
1470 
1471 #ifdef NC4_DEBUG
1472 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "-> x-region: %g %g, grid: %g %g\n", wesn[XLO], wesn[XHI], header->wesn[XLO], header->wesn[XHI]);
1473 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "-> y-region: %g %g, grid: %g %g\n", wesn[YLO], wesn[YHI], header->wesn[YLO], header->wesn[YHI]);
1474 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "row: %u %u  col: %u %u  r_shift: %d\n", first_row, last_row, first_col, last_col, *n_shift);
1475 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "origin : %u,%u  dim : %u,%u\n", origin[0], origin[1], dim[0], dim[1]);
1476 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "origin2: %u,%u  dim2: %u,%u\n", origin2[0], origin2[1], dim2[0], dim2[1]);
1477 #endif
1478 
1479 	return GMT_NOERROR;
1480 }
1481 
gmt_nc_create(struct GMT_CTRL * GMT,char * path,int mode,int * id)1482 int gmt_nc_create (struct GMT_CTRL *GMT, char *path, int mode, int *id) {
1483 	int err = nc_create (path, mode, id);
1484 	GMT_Report (GMT->parent, GMT_MSG_DEBUG, "Calling nc_create on %s, ncid = %d, err = %d\n", path, *id, err);
1485 	return (err);
1486 }
1487 
gmt_nc_open(struct GMT_CTRL * GMT,char * path,int mode,int * id)1488 int gmt_nc_open (struct GMT_CTRL *GMT, char *path, int mode, int *id) {
1489 	int err = nc_open (path, mode, id);	/* Open the file, return error code */
1490 	GMT_Report (GMT->parent, GMT_MSG_DEBUG, "Calling nc_open on %s, ncid = %d, err = %d\n", path, *id, err);
1491 	return err;	/* Open the file, return error code */
1492 }
1493 
gmt_nc_close(struct GMT_CTRL * GMT,int id)1494 int gmt_nc_close (struct GMT_CTRL *GMT, int id) {
1495 	int err = nc_close (id);
1496 	GMT_Report (GMT->parent, GMT_MSG_DEBUG, "Calling nc_close on ncid %d, err = %d\n", id, err);
1497 	return err;
1498 }
1499 
gmtlib_is_nc_grid(struct GMT_CTRL * GMT,struct GMT_GRID_HEADER * header)1500 int gmtlib_is_nc_grid (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *header) {
1501 	/* Returns GMT_NOERROR if NetCDF grid */
1502 	int ncid, z_id = GMT_NOTSET, j = 0, nvars, ndims, err, old = false;
1503 	nc_type z_type;
1504 	char varname[GMT_GRID_VARNAME_LEN80];
1505 	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (header);
1506 
1507 	/* Extract levels name from variable name */
1508 	strncpy (varname, HH->varname, GMT_GRID_VARNAME_LEN80-1);
1509 	if (varname[0]) {
1510 		j = 0;
1511 		while (varname[j] && varname[j] != '[' && varname[j] != '(') j++;
1512 		if (varname[j])
1513 			varname[j] = '\0';
1514 	}
1515 	if (!strcmp (HH->name, "="))
1516 		return (GMT_GRDIO_NC_NO_PIPE);
1517 
1518 	/* Open the file and look for the required variable */
1519 	if (gmt_access (GMT, HH->name, F_OK))
1520 		return (GMT_GRDIO_FILE_NOT_FOUND);
1521 	if ((err = gmt_nc_open (GMT, HH->name, NC_NOWRITE, &ncid)))
1522 		return (GMT_GRDIO_OPEN_FAILED);
1523 	if (!nc_inq_dimid (ncid, "xysize", &z_id)) {
1524 		/* Old style GMT netCDF grid */
1525 		old = true;
1526 		if (nc_inq_varid (ncid, "z", &z_id))
1527 			return (GMT_GRDIO_NO_VAR);
1528 	}
1529 	else if (varname[0]) {
1530 		/* ?<varname> used */
1531 		if (nc_inq_varid (ncid, varname, &z_id))
1532 			return (GMT_GRDIO_NO_VAR);
1533 	}
1534 	else {	/* Look for first 2D grid, with fallback to first higher-dimension (3-4D) grid if 2D not found */
1535 		int ID = GMT_NOTSET, dim = 0;
1536 		nc_inq_nvars (ncid, &nvars);
1537 		for (j = 0; j < nvars && z_id < 0; j++) {
1538 			gmt_M_err_trap (nc_inq_varndims (ncid, j, &ndims));
1539 			if (ndims == 2)	/* Found the first 2-D grid */
1540 				z_id = j;
1541 			else if (ID == GMT_NOTSET && ndims > 2 && ndims < 5) {	/* Also look for higher-dim grid in case no 2-D */
1542 				ID = j;
1543 				dim = ndims;
1544 			}
1545 		}
1546 		if (z_id < 0) {	/* No 2-D grid found, check if we found a higher dimension cube */
1547 			if (ID == GMT_NOTSET) return (GMT_GRDIO_NO_2DVAR);	/* No we didn't */
1548 			z_id = ID;	/* Pick the higher dimensioned cube instead, get its name, and warn */
1549 			nc_inq_varname (ncid, z_id, varname);
1550 			GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "No 2-D array in file %s.  Selecting first 2-D slice in the %d-D array %s\n", HH->name, dim, varname);
1551 		}
1552 	}
1553 
1554 	gmt_M_err_trap (nc_inq_vartype (ncid, z_id, &z_type));
1555 	switch (z_type) {
1556 		case NC_UBYTE:  header->type = old ? GMT_GRID_IS_CB : GMT_GRID_IS_NB; HH->orig_datatype = GMT_UCHAR;  break;
1557 		case NC_BYTE:   header->type = old ? GMT_GRID_IS_CB : GMT_GRID_IS_NB; HH->orig_datatype = GMT_CHAR;   break;
1558 		case NC_SHORT:  header->type = old ? GMT_GRID_IS_CS : GMT_GRID_IS_NS; HH->orig_datatype = GMT_SHORT;  break;
1559 		case NC_INT:    header->type = old ? GMT_GRID_IS_CI : GMT_GRID_IS_NI; HH->orig_datatype = GMT_INT;    break;
1560 		case NC_FLOAT:  header->type = old ? GMT_GRID_IS_CF : GMT_GRID_IS_NF; HH->orig_datatype = GMT_FLOAT;  break;
1561 		case NC_DOUBLE: header->type = old ? GMT_GRID_IS_CD : GMT_GRID_IS_ND; HH->orig_datatype = GMT_DOUBLE; break;
1562 		default:        header->type = k_grd_unknown_fmt; break;
1563 	}
1564 	gmt_nc_close (GMT, ncid);
1565 	return GMT_NOERROR;
1566 }
1567 
gmt_nc_read_grd_info(struct GMT_CTRL * GMT,struct GMT_GRID_HEADER * header)1568 int gmt_nc_read_grd_info (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *header) {
1569 	return (gmtnc_grd_info (GMT, header, 'r', false, 0));
1570 }
1571 
gmt_nc_update_grd_info(struct GMT_CTRL * GMT,struct GMT_GRID_HEADER * header)1572 int gmt_nc_update_grd_info (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *header) {
1573 	return (gmtnc_grd_info (GMT, header, 'u', false, 0));
1574 }
1575 
gmt_nc_write_grd_info(struct GMT_CTRL * GMT,struct GMT_GRID_HEADER * header)1576 int gmt_nc_write_grd_info (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *header) {
1577 	return (gmtnc_grd_info (GMT, header, 'w', false, 0));
1578 }
1579 
gmt_nc_read_grd(struct GMT_CTRL * GMT,struct GMT_GRID_HEADER * header,gmt_grdfloat * grid,double wesn[],unsigned int * pad,unsigned int complex_mode)1580 int gmt_nc_read_grd (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *header, gmt_grdfloat *grid, double wesn[], unsigned int *pad, unsigned int complex_mode) {
1581 	/* header:       grid structure header
1582 	 * grid:         array with final grid
1583 	 * wesn:         Sub-region to extract  [Use entire file if 0,0,0,0]
1584 	 * padding:      # of empty rows/columns to add on w, e, s, n of grid, respectively
1585 	 * complex_mode:	&4 | &8 if complex array is to hold real (4) and imaginary (8) parts (otherwise read as real only)
1586 	 *		Note: The file has only real values, we simply allow space in the complex array
1587 	 *		for real and imaginary parts when processed by grdfft etc.
1588 	 *
1589 	 * Reads a subset of a grdfile and optionally pads the array with extra rows and columns
1590 	 * header values for n_columns and n_rows are reset to reflect the dimensions of the logical array,
1591 	 * not the physical size (i.e., the padding is not counted in n_columns and n_rows)
1592 	 */
1593 
1594 	bool adj_nan_value; /* if we need to change the fill value */
1595 	int err;            /* netcdf errors */
1596 	int n_shift;
1597 	int error;
1598 	unsigned dim[2], dim2[2], origin[2], origin2[2]; /* dimension and origin {y,x} of subset to read from netcdf */
1599 	unsigned width = 0, height = 0;
1600 	size_t width_t, height_t, row, stride_t;
1601 	uint64_t imag_offset;
1602 	gmt_grdfloat *pgrid = NULL;
1603 	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (header);
1604 
1605 	/* Check type: is file in old NetCDF format or not at all? */
1606 	if (GMT->session.grdformat[header->type][0] == 'c')
1607 		return (gmt_cdf_read_grd (GMT, header, grid, wesn, pad, complex_mode));
1608 	else if (GMT->session.grdformat[header->type][0] != 'n')
1609 		return (NC_ENOTNC);
1610 
1611 	if ((error = gmt_M_err_fail (GMT, gmtnc_grd_prep_io (GMT, header, wesn, &width, &height, &n_shift, origin, dim, origin2, dim2), HH->name)))
1612 		return (error);
1613 
1614 	/* Set stride and offset if complex */
1615 	(void)gmtlib_init_complex (header, complex_mode, &imag_offset);	/* Set offset for imaginary complex component */
1616 	pgrid = grid + imag_offset;	/* Start of this complex component (or start of non-complex grid) */
1617 
1618 #ifdef NC4_DEBUG
1619 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "      wesn: %g %g %g %g\n", wesn[XLO], wesn[XHI], wesn[YLO], wesn[YHI]);
1620 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->wesn: %g %g %g %g\n", header->wesn[XLO], header->wesn[XHI], header->wesn[YLO], header->wesn[YHI]);
1621 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->registration:%u\n", header->registration);
1622 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->row_order: %s\n", HH->row_order == k_nc_start_south ? "S->N" : "N->S");
1623 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "width:    %3d     height:%3d\n", width, height);
1624 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->n_columns: %3d   head->n_rows:%3d\n", header->n_columns, header->n_rows);
1625 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->mx: %3d   head->my:%3d\n", header->mx, header->my);
1626 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->nm: %3d head->size:%3d\n", header->nm, header->size);
1627 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->t-index %d,%d,%d\n", HH->t_index[0], HH->t_index[1], HH->t_index[2]);
1628 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "      pad xlo:%u xhi:%u ylo:%u yhi:%u\n", pad[XLO], pad[XHI], pad[YLO], pad[YHI]);
1629 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->pad xlo:%u xhi:%u ylo:%u yhi:%u\n", header->pad[XLO], header->pad[XHI], header->pad[YLO], header->pad[YHI]);
1630 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->BC  xlo:%u xhi:%u ylo:%u yhi:%u\n", HH->BC[XLO], HH->BC[XHI], HH->BC[YLO], HH->BC[YHI]);
1631 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "head->grdtype:%u %u\n", HH->grdtype, GMT_GRID_GEOGRAPHIC_EXACT360_REPEAT);
1632 	GMT_Report (GMT->parent, GMT_MSG_WARNING, "imag_offset: %" PRIu64 "\n", imag_offset);
1633 #endif
1634 
1635 	/* Open the NetCDF file */
1636 	if (!strcmp (HH->name,"="))
1637 		return (GMT_GRDIO_NC_NO_PIPE);
1638 	gmtnc_setup_chunk_cache();
1639 	gmt_M_err_trap (gmt_nc_open (GMT, HH->name, NC_NOWRITE, &HH->ncid));
1640 
1641 	/* Read grid */
1642 	if (dim2[1] == 0)
1643 		gmtnc_io_nc_grid (GMT, header, dim, origin, HH->stride, k_get_netcdf, pgrid + HH->data_offset, false);
1644 	else {
1645 		/* Read grid in two parts */
1646 		unsigned int stride_or_width = HH->stride != 0 ? HH->stride : width;
1647 		gmtnc_io_nc_grid (GMT, header, dim, origin, stride_or_width, k_get_netcdf, pgrid + HH->data_offset, false);
1648 		gmtnc_io_nc_grid (GMT, header, dim2, origin2, stride_or_width, k_get_netcdf, pgrid + HH->data_offset + dim[1], false);
1649 	}
1650 
1651 	/* If we need to shift grid */
1652 	if (n_shift) {
1653 #ifdef NC4_DEBUG
1654 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "gmtnc_right_shift_grid: %d\n", n_shift);
1655 #endif
1656 		gmtnc_right_shift_grid (pgrid, dim[1], dim[0], n_shift, sizeof(grid[0]));
1657 	}
1658 
1659 	/* If dim[1] + dim2[1] was < requested width: wrap-pad east border */
1660 	if (gmt_grd_is_global(GMT, header) && width > dim[1] + dim2[1]) {
1661 		unsigned int fix_pad[4] = {0,0,0,0};
1662 		fix_pad[XHI] = width - dim[1] - dim2[1];
1663 		gmtnc_pad_grid (pgrid, width - fix_pad[XHI], height, fix_pad, sizeof(grid[0]), k_pad_fill_copy_wrap);
1664 	}
1665 	else
1666 		assert (width == dim[1] + dim2[1]);
1667 
1668 	/* Get stats */
1669 	width_t = (size_t)width;
1670 	height_t = (size_t)height;
1671 	stride_t = (size_t)HH->stride;
1672 	header->z_min = DBL_MAX;
1673 	header->z_max = -DBL_MAX;
1674 	adj_nan_value = !isnan (header->nan_value);
1675 	HH->has_NaNs = GMT_GRID_NO_NANS;	/* We are about to check for NaNs and if none are found we retain 1, else 2 */
1676 	for (row = 0; row < height_t; ++row) {
1677 		gmt_grdfloat *p_data = pgrid + row * (HH->stride ? stride_t : width_t);
1678 		unsigned col;
1679 		for (col = 0; col < width; col ++) {
1680 			if (adj_nan_value && p_data[col] == header->nan_value) {
1681 				p_data[col] = (gmt_grdfloat)NAN;
1682 				HH->has_NaNs = GMT_GRID_HAS_NANS;
1683 				continue;
1684 			}
1685 			else if (!isnan (p_data[col])) {
1686 				header->z_min = MIN (header->z_min, p_data[col]);
1687 				header->z_max = MAX (header->z_max, p_data[col]);
1688 			}
1689 			else
1690 				HH->has_NaNs = GMT_GRID_HAS_NANS;
1691 		}
1692 	}
1693 	/* Check limits */
1694 	if (header->z_min > header->z_max) {
1695 		header->z_min = NAN;
1696 		header->z_max = NAN;
1697 		GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "No valid values in grid [%s]\n", HH->name);
1698 	}
1699 	else {
1700 		/* Report z-range of grid (with scale and offset applied): */
1701 		unsigned int level;
1702 #ifdef NC4_DEBUG
1703 		level = GMT_MSG_WARNING;
1704 #else
1705 		level = GMT_MSG_DEBUG;
1706 #endif
1707 		GMT_Report (GMT->parent, level,
1708 				"packed z-range: [%g,%g]\n", header->z_min, header->z_max);
1709 	}
1710 
1711 	/* Flip grid upside down */
1712 	if (HH->row_order == k_nc_start_south)
1713 		gmt_grd_flip_vertical (pgrid + HH->data_offset, width, height, HH->stride, sizeof(grid[0]));
1714 
1715 	/* Add padding with border replication */
1716 	gmtnc_pad_grid (pgrid, width, height, pad, sizeof(grid[0]), k_pad_fill_zero);
1717 
1718 #ifdef NC4_DEBUG
1719 	if (header->size < 160) {
1720 		unsigned n;
1721 		unsigned pad_x = pad[XLO] + pad[XHI];
1722 		unsigned stride = HH->stride ? HH->stride : width;
1723 		gmt_grdfloat *p_data = pgrid + HH->data_offset;
1724 		for (n = 0; n < (stride + pad_x) * (height + pad[YLO] + pad[YHI]); n++) {
1725 			if (n % (stride + pad_x) == 0)
1726 				fprintf (stderr, "\n");
1727 			fprintf (stderr, "%4.0f", p_data[n]);
1728 		}
1729 		fprintf (stderr, "\n");
1730 	}
1731 #endif
1732 
1733 	/* Adjust header */
1734 	gmt_M_memcpy (header->wesn, wesn, 4, double);
1735 	header->n_columns = width;
1736 	header->n_rows = height;
1737 
1738 	gmt_M_err_trap (gmt_nc_close (GMT, HH->ncid));
1739 
1740 	return GMT_NOERROR;
1741 }
1742 
gmt_nc_write_grd(struct GMT_CTRL * GMT,struct GMT_GRID_HEADER * header,gmt_grdfloat * grid,double wesn[],unsigned int * pad,unsigned int complex_mode)1743 int gmt_nc_write_grd (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *header, gmt_grdfloat *grid, double wesn[], unsigned int *pad, unsigned int complex_mode)
1744 { /* header:       grid structure header
1745 	 * grid:         array with final grid
1746 	 * wesn:         Sub-region to write out  [Use entire file if 0,0,0,0]
1747 	 * padding:      # of empty rows/columns to add on w, e, s, n of grid, respectively
1748 	 * complex_mode:	&4 | &8 if complex array is to hold real (4) and imaginary (8) parts (otherwise read as real only)
1749 	 *		Note: The file has only real values, we simply allow space in the complex array
1750 	 *		for real and imaginary parts when processed by grdfft etc.
1751 	 */
1752 
1753 	int status = NC_NOERR;
1754 	bool adj_nan_value;   /* if we need to change the fill value */
1755 	bool do_round = true; /* if we need to round to integral */
1756 	unsigned int width, height, *actual_col = NULL;
1757 	unsigned int dim[2], origin[2]; /* dimension and origin {y,x} of subset to write to netcdf */
1758 	int first_col, last_col, first_row, last_row;
1759 	uint64_t imag_offset;
1760 	size_t n, nm;
1761 	size_t width_t, height_t;
1762 	size_t was_chunk_setting = GMT->current.setting.io_nc4_chunksize[0];	/* Remember current setting */
1763 	double limit[2];      /* minmax of z variable */
1764 	gmt_grdfloat *pgrid = NULL;
1765 	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (header);
1766 
1767 	/* Determine the value to be assigned to missing data, if not already done so */
1768 	switch (header->type) {
1769 		case GMT_GRID_IS_NB:
1770 			if (isnan (header->nan_value))
1771 				header->nan_value = NC_MIN_BYTE;
1772 			break;
1773 		case GMT_GRID_IS_NS:
1774 			if (isnan (header->nan_value))
1775 				header->nan_value = NC_MIN_SHORT;
1776 			break;
1777 		case GMT_GRID_IS_NI:
1778 			if (isnan (header->nan_value))
1779 				header->nan_value = NC_MIN_INT;
1780 			break;
1781 		case GMT_GRID_IS_ND:
1782 #ifndef DOUBLE_PRECISION_GRID
1783 			GMT_Report (GMT->parent, GMT_MSG_WARNING, "Precision loss! GMT's internal grid representation is 32-bit float.\n");
1784 #endif
1785 			/* Intentionally fall through */
1786 		default: /* don't round float */
1787 			do_round = false;
1788 	}
1789 
1790 	gmt_M_err_pass (GMT, gmt_grd_prep_io (GMT, header, wesn, &width, &height, &first_col, &last_col, &first_row, &last_row, &actual_col), HH->name);
1791 	gmt_M_free (GMT, actual_col);
1792 
1793 	/* Adjust header */
1794 	gmt_M_memcpy (header->wesn, wesn, 4, double);
1795 	header->n_columns = width;
1796 	header->n_rows = height;
1797 
1798 	/* Adjust first_row */
1799 	if (HH->row_order == k_nc_start_south)
1800 		first_row = header->n_rows - 1 - last_row;
1801 
1802 	/* Write grid header without closing file afterwards */
1803 	gmtnc_setup_chunk_cache();
1804 	status = gmtnc_grd_info (GMT, header, 'W', false, 0);
1805 	if (status != NC_NOERR)
1806 		goto nc_err;
1807 
1808 	/* Set stride and offset if complex */
1809 	(void)gmtlib_init_complex (header, complex_mode, &imag_offset);	/* Set offset for imaginary complex component */
1810 	pgrid = grid + imag_offset;	/* Beginning of this complex component (or the regular non-complex grid) */
1811 
1812 	/* Remove padding from grid */
1813 	gmtnc_unpad_grid (pgrid, width, height, pad, sizeof(grid[0]));
1814 
1815 	/* Check that repeating columns do not contain conflicting information */
1816 	if (HH->grdtype == GMT_GRID_GEOGRAPHIC_EXACT360_REPEAT)
1817 		gmtnc_grid_fix_repeat_col (GMT, pgrid, width, height, sizeof(grid[0]));
1818 
1819 	/* Flip grid upside down */
1820 	if (HH->row_order == k_nc_start_south)
1821 		gmt_grd_flip_vertical (pgrid, width, height, 0, sizeof(grid[0]));
1822 
1823 	/* Get stats */
1824 	header->z_min = DBL_MAX;
1825 	header->z_max = -DBL_MAX;
1826 	adj_nan_value = !isnan (header->nan_value);
1827 	n = 0;
1828 	width_t  = (size_t)width;
1829 	height_t = (size_t)height;
1830 	nm = width_t * height_t;
1831 	while (n < nm) {
1832 		if (adj_nan_value && isnan (pgrid[n]))
1833 			pgrid[n] = header->nan_value;
1834 		else if (!isnan (pgrid[n])) {
1835 			if (do_round)
1836 				pgrid[n] = rintf (pgrid[n]); /* round to int */
1837 			header->z_min = MIN (header->z_min, pgrid[n]);
1838 			header->z_max = MAX (header->z_max, pgrid[n]);
1839 		}
1840 		n++;
1841 	}
1842 
1843 	/* Write grid */
1844 	dim[0]    = height,    dim[1]    = width;
1845 	origin[0] = first_row, origin[1] = first_col;
1846 	status = gmtnc_io_nc_grid (GMT, header, dim, origin, 0, k_put_netcdf, pgrid, false);
1847 	if (status != NC_NOERR)
1848 		goto nc_err;
1849 
1850 	if (header->z_min <= header->z_max) {
1851 		/* Warn if z-range exceeds the precision of a single precision float: */
1852 		static const uint32_t exp2_24 = 0x1000000; /* exp2 (24) */
1853 		unsigned int level;
1854 		if (fabs(header->z_min) >= exp2_24 || fabs(header->z_max) >= exp2_24)
1855 			GMT_Report (GMT->parent, GMT_MSG_DEBUG, "The z-range, [%g,%g], might exceed the significand's precision of 24 bits; round-off errors may occur.\n", header->z_min, header->z_max);
1856 
1857 		/* Report z-range of grid (with scale and offset applied): */
1858 #ifdef NC4_DEBUG
1859 		level = GMT_MSG_WARNING;
1860 #else
1861 		level = GMT_MSG_DEBUG;
1862 #endif
1863 		GMT_Report (GMT->parent, level,
1864 				"packed z-range: [%g,%g]\n", header->z_min, header->z_max);
1865 
1866 		/* Limits need to be written in actual, not internal grid, units: */
1867 		limit[0] = header->z_min * header->z_scale_factor + header->z_add_offset;
1868 		limit[1] = header->z_max * header->z_scale_factor + header->z_add_offset;
1869 	}
1870 	else {
1871 		GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "No valid values in grid [%s]\n", HH->name);
1872 		limit[0] = limit[1] = NAN; /* Set limit to NaN */
1873 	}
1874 	status = nc_put_att_double (HH->ncid, HH->z_id, "actual_range", NC_DOUBLE, 2, limit);
1875 	if (status != NC_NOERR)
1876 		goto nc_err;
1877 
1878 	/* Close grid */
1879 	status = gmt_nc_close (GMT, HH->ncid);
1880 	if (status != NC_NOERR)
1881 		goto nc_err;
1882 
1883 	GMT->current.setting.io_nc4_chunksize[0] = was_chunk_setting;	/* Restore to previous status */
1884 
1885 	return GMT_NOERROR;
1886 
1887 nc_err:
1888 	/* exit gracefully */
1889 	gmt_nc_close (GMT, HH->ncid); /* close nc-file */
1890 	unlink (HH->name);  /* remove nc-file */
1891 	if (status == NC_ERANGE) {
1892 		/* report out of range z variable */
1893 		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Cannot write format %s.\n", GMT->session.grdformat[header->type]);
1894 		GMT_Report (GMT->parent, GMT_MSG_ERROR, "The packed z-range, [%g,%g], exceeds the maximum representable size. Adjust scale and offset parameters or remove out-of-range values.\n", header->z_min, header->z_max);
1895 	}
1896 	return status;
1897 }
1898 
gmt_nc_is_cube(struct GMTAPI_CTRL * API,char * file)1899 bool gmt_nc_is_cube (struct GMTAPI_CTRL *API, char *file) {
1900 	/* Return true if the file is a 3-D netCDF cube */
1901 	bool is_cube = false;
1902 	int i, ID = GMT_NOTSET, ncid, z_id = GMT_NOTSET, nvars, ndims = 0;
1903 	char varname[GMT_GRID_NAME_LEN256] = {""}, *c = NULL;
1904 	gmt_M_unused (API);
1905 
1906 	if (file == NULL || file[0] == '\0') return false;	/* Got no file */
1907 	if (!strcmp (file,"=")) return (false);	/* NetCDF is not pipeable */
1908 	if (gmt_M_file_is_memory (file)) return ((file[GMTAPI_OBJECT_FAMILY_START] == 'U') ? true : false);	/* Memory cube or not */
1909 	if ((c = strchr (file, '?'))) {	/* Specific variable */
1910 		strcpy (varname, &c[1]);
1911 		c[0] = '\0';	/* Chop off for now */
1912 	}
1913 	if (gmt_nc_open (API->GMT, file, NC_NOWRITE, &ncid)) {
1914 		if (c) c[0] = '?';	/* Restore */
1915 		return false;	/* Unable to open, so probably not netCDF file */
1916 	}
1917 	/* Here file is open so must close before returning */
1918 	if (c) c[0] = '?';	/* Restore */
1919 	if (nc_inq_nvars (ncid, &nvars)) goto nc_cube_return;	/* No variables, no good */
1920 	if (c) {
1921 		if (nc_inq_varid (ncid, varname, &z_id) == NC_NOERR) {	/* Gave a named variable that exist, is it 2-4 D? */
1922 			if (nc_inq_varndims (ncid, z_id, &ndims)) goto nc_cube_return;	/* Not god */
1923 			if (ndims != 3) z_id = GMT_NOTSET;	/* But not 3-D */
1924 			if (ndims > 2 && ndims <= 5) ID = z_id;
1925 		}
1926 	}
1927 	else {	/* Must search */
1928 		i = 0;
1929 		while (i < nvars && z_id < 0) {	/* Look for first 3D grid, with fallback to first higher-dimension (4-%D) grid if 3D not found */
1930 			if (nc_inq_varndims (ncid, i, &ndims)) goto nc_cube_return;	/* Not good */
1931 			if (ndims == 3)	/* Found the first 3-D grid */
1932 				z_id = i;
1933 			else if (ID == GMT_NOTSET && ndims > 3 && ndims < 5)	/* Also look for higher-dim grid in case no 3-D */
1934 				ID = i;
1935 			i++;
1936 		}
1937 	}
1938 	if (ID != GMT_NOTSET || z_id >= 0) is_cube = true;	/* Found  3-D grid */
1939 nc_cube_return:
1940 	gmt_nc_close (API->GMT, ncid); /* close nc-file */
1941 	return (is_cube);
1942 }
1943 
1944 /* Examine the netCDF data cube and determine if it is a 3-D cube and return the knots */
1945 
gmt_nc_read_cube_info(struct GMT_CTRL * GMT,char * file,double * w_range,uint64_t * nz,double ** zarray,char * z_unit)1946 int gmt_nc_read_cube_info (struct GMT_CTRL *GMT, char *file, double *w_range, uint64_t *nz, double **zarray, char *z_unit) {
1947 	int i, err, ID = GMT_NOTSET, dim = 0, ncid, z_id = GMT_NOTSET, ids[5] = {-1,-1,-1,-1,-1}, dims[5], nvars;
1948 	int has_vector, ndims = 0, z_dim, status;
1949 	uint64_t n_layers = 0;
1950 	size_t lens[5];
1951 	char varname[GMT_GRID_VARNAME_LEN80], dimname[GMT_GRID_UNIT_LEN80], z_units[GMT_GRID_UNIT_LEN80];
1952 	double *z = NULL, dummy[2] = {0.0, 0.0};
1953 
1954 	gmt_M_err_trap (gmt_nc_open (GMT, file, NC_NOWRITE, &ncid));
1955 
1956 	gmt_M_err_trap (nc_inq_nvars (ncid, &nvars));
1957 	i = 0;
1958 	while (i < nvars && z_id < 0) {	/* Look for first 3D grid, with fallback to first higher-dimension (4-5D) grid if 3D not found */
1959 		gmt_M_err_trap (nc_inq_varndims (ncid, i, &ndims));
1960 		if (ndims == 3)	/* Found the first 3-D grid */
1961 			z_id = i;
1962 		else if (ID == GMT_NOTSET && ndims > 3 && ndims < 5) {	/* Also look for higher-dim grid in case no 3-D */
1963 			ID = i;
1964 			dim = ndims;
1965 		}
1966 		i++;
1967 	}
1968 	if (z_id < 0) {	/* No 3-D grid found, check if we found a higher dimension cube */
1969 		if (ID == GMT_NOTSET) {	/* No we didn't */
1970 			GMT_Report (GMT->parent, GMT_MSG_ERROR, "No 3-D data cube found in file %s.\n", file);
1971 			return GMT_GRDIO_NO_2DVAR;
1972 		}
1973 		z_id = ID;	/* Pick the higher dimensioned cube instead, get its name, and warn */
1974 		nc_inq_varname (ncid, z_id, varname);
1975 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "No 3-D array in file %s.  Selecting first 3-D slice in the %d-D array %s\n", file, dim, varname);
1976 	}
1977 	gmt_M_err_trap (nc_inq_vardimid (ncid, z_id, dims));
1978 
1979 	/* Get the ids of the x and y (and depth and time) coordinate variables */
1980 	for (i = 0; i < ndims; i++) {
1981 		gmt_M_err_trap (nc_inq_dim (ncid, dims[i], dimname, &lens[i]));
1982 		if ((status = nc_inq_varid (ncid, dimname, &ids[i])) != NC_NOERR)
1983 			GMT_Report (GMT->parent, GMT_MSG_WARNING, "\"%s\", %s\n\tIf something bad happens later, try importing via GDAL.\n",
1984 				dimname, nc_strerror(status));
1985 	}
1986 	z_dim = ndims-3;
1987 	n_layers = lens[z_dim];
1988 
1989 	/* Create enough memory to store the level-coordinate values */
1990 	z = gmt_M_memory (GMT, NULL, n_layers, double);
1991 
1992 	/* Get information about z variable */
1993 	gmtnc_get_units (GMT, ncid, ids[z_dim], z_units);
1994 	if (strstr (z_units, "seconds since 1970-01-01"))
1995 		gmt_set_column_type (GMT, GMT_IN, GMT_Z, GMT_IS_ABSTIME);
1996 	else if (z_units[0] == '\0')
1997 		strcpy (z_units, "z");	/* Set default z-unit name */
1998 	/* Look for the z-coordinate vector */
1999 	if ((has_vector = nc_get_var_double (ncid, ids[z_dim], z))) {
2000 		GMT_Report (GMT->parent, GMT_MSG_ERROR, "No 3rd-dimension coordinate vector found in %s\n", file);
2001 		return GMT_GRDIO_NO_2DVAR;
2002 	}
2003 
2004 	if (w_range) gmt_M_memset (w_range, 2U, double);
2005 	if (w_range && (!nc_get_att_double (ncid, z_id, "actual_range", dummy) ||
2006 			!nc_get_att_double (ncid, z_id, "valid_range", dummy) ||
2007 			!(nc_get_att_double (ncid, z_id, "valid_min", &dummy[0]) +
2008 			nc_get_att_double (ncid, z_id, "valid_max", &dummy[1]))))
2009 		gmt_M_memcpy (w_range, dummy, 2U, double);
2010 
2011 	*zarray = z;
2012 	*nz = n_layers;
2013 	if (z_unit)	/* Passed pointer to storage for z-unit in cubes */
2014 		strncpy (z_unit, z_units, GMT_GRID_UNIT_LEN80);
2015 
2016 	return GMT_NOERROR;
2017 }
2018 
2019 /* Write a 3-D cube to file; cube is represented internally by a stack of 2-D grids and a layer z-array */
gmt_nc_write_cube(struct GMT_CTRL * GMT,struct GMT_CUBE * C,double wesn[],const char * file)2020 int gmt_nc_write_cube (struct GMT_CTRL *GMT, struct GMT_CUBE *C, double wesn[], const char *file) {
2021 	/* Depending on mode, we either write individual layer grid files or a single 3-D data cube */
2022 	int status = NC_NOERR, err = 0;
2023 	uint64_t k, k0, k1, n_layers_used, save_n_bands, here = 0;
2024 	struct GMTAPI_CTRL *API = GMT->parent;
2025 
2026 	/* Determine which layers we want to write */
2027 	k0 = 0;	k1 = C->header->n_bands - 1;	/* Default is all */
2028 	if (wesn && wesn[ZHI] > wesn[ZLO]) {	/* Want to write a subset of layers */
2029 		if (gmt_get_active_layers (GMT, C, &(wesn[ZLO]), &k0, &k1) == 0) {
2030 			gmtlib_report_error (API, GMT_RUNTIME_ERROR);
2031 		}
2032 	}
2033 	n_layers_used = k1 - k0 + 1;	/* Total number of layers actually to be written */
2034 	if (n_layers_used == 0) {
2035 		GMT_Report (API, GMT_MSG_ERROR, "gmt_nc_write_cube: No layers selected from GMT_IS_CUBE.\n");
2036 		return (gmtlib_report_error (API, GMT_DIM_TOO_SMALL));
2037 	}
2038 	save_n_bands = C->header->n_bands;	/* Remember how many bands */
2039 	here = k0 * C->header->size;	/* Start position in the cube for layer k0 */
2040 
2041 	if (strchr (file, '%') || n_layers_used == 1) {	/* Format specifier found, do individual layer grids, or a single layer so no format needed */
2042 		char gfile[PATH_MAX] = {""};
2043 		struct GMT_GRID *G = NULL;
2044 
2045 		/* !! Remember that the gmt_nc.c codes will unpad and mess up C->data */
2046 		G = gmt_get_grid (GMT);	/* Need a dummy grid for writing */
2047 		G->header = C->header;	/* Use this pointer for now */
2048 		G->header->n_bands = 1;	/* Grids only have one band [must undo this below] */
2049 		/* Write the layers via a grid */
2050 		for (k = k0; k <= k1; k++) {	/* For all selected output levels */
2051 			if (n_layers_used > 1 || strchr (file, '%'))	/* Create the k'th layer file name from template */
2052 				sprintf (gfile, file, C->z[k]);
2053 			else	/* Just this one layer grid */
2054 				sprintf (gfile, "%s", file);
2055 			G->data = &C->data[here];	/* Point to start of this layer */
2056 			GMT_Report (API, GMT_MSG_DEBUG, "gmt_nc_write_cube: Layer %" PRIu64 ", offset = %" PRIu64 ".\n", k, here);
2057 			if (GMT_Write_Data (API, GMT_IS_GRID, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_AND_DATA, wesn, gfile, G) != GMT_NOERROR) {
2058 				return (API->error);
2059 			}
2060 			here += C->header->size;	/* Move to next level */
2061 		}
2062 		/* Wipe and free the temporary grid structure */
2063 		G->header->n_bands = save_n_bands;	/* Restore bands */
2064 		G->data = NULL;
2065 		G->header = NULL;
2066 		gmt_free_grid (GMT, &G, true);
2067 		return (GMT_NOERROR);
2068 	}
2069 	else {	/* Here we must write a 3-D netcdf data cube NOT COMPLETED SO NOT WORKING */
2070 		bool adj_nan_value;   /* if we need to change the fill value */
2071 		bool do_round = true; /* if we need to round to integral */
2072 		unsigned int width, height;
2073 		unsigned int dim[3], origin[2]; /* dimension and origin {y,x} of subset to write to netcdf */
2074 		int first_col, first_row, last_row;
2075 		size_t n, nm;
2076 		size_t width_t, height_t;
2077 		double level_min, level_max;      /* minmax of level variable */
2078 		gmt_grdfloat *pgrid = NULL;
2079 		struct GMT_GRID_HEADER *header = C->header;
2080 		struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (header);
2081 
2082 		width = header->n_columns;	height = header->n_rows;
2083 		header->n_bands = n_layers_used;	/* Number of layers to actually write */
2084 		strncpy (HH->name, file, GMT_GRID_NAME_LEN256);
2085 		/* Determine the value to be assigned to missing data, if not already done so */
2086 		switch (header->type) {
2087 			case GMT_GRID_IS_NB:
2088 				if (isnan (header->nan_value))
2089 					header->nan_value = NC_MIN_BYTE;
2090 				break;
2091 			case GMT_GRID_IS_NS:
2092 				if (isnan (header->nan_value))
2093 					header->nan_value = NC_MIN_SHORT;
2094 				break;
2095 			case GMT_GRID_IS_NI:
2096 				if (isnan (header->nan_value))
2097 					header->nan_value = NC_MIN_INT;
2098 				break;
2099 			case GMT_GRID_IS_ND:
2100 #ifndef DOUBLE_PRECISION_GRID
2101 				GMT_Report (GMT->parent, GMT_MSG_WARNING, "Precision loss! GMT's internal grid representation is 32-bit float.\n");
2102 #endif
2103 				/* Intentionally fall through */
2104 			default: /* don't round float */
2105 				do_round = false;
2106 		}
2107 
2108 		first_col = first_row = 0;
2109 		last_row  = header->n_rows - 1;
2110 		level_min = DBL_MAX;
2111 		level_max = -DBL_MAX;
2112 
2113 		/* Get stats */
2114 		adj_nan_value = !isnan (header->nan_value);
2115 		width_t  = (size_t)width;
2116 		height_t = (size_t)height;
2117 		nm = width_t * height_t;
2118 		for (k = k0; k <= k1; k++) {	/* For all selected output levels */
2119 			pgrid = &C->data[here];
2120 			/* Update stats and set NaN-values */
2121 			n = 0;
2122 			while (n < nm) {
2123 				if (adj_nan_value && isnan (pgrid[n]))
2124 					pgrid[n] = header->nan_value;
2125 				else if (!isnan (pgrid[n])) {
2126 					if (do_round)
2127 						pgrid[n] = rintf (pgrid[n]); /* round to int */
2128 					level_min = MIN (level_min, pgrid[n]);
2129 					level_max = MAX (level_max, pgrid[n]);
2130 				}
2131 				n++;
2132 			}
2133 			here += C->header->size;
2134 		}
2135 		/* The min/max of the cube */
2136 		header->z_min = level_min;
2137 		header->z_max = level_max;
2138 
2139 		/* Adjust first_row */
2140 		if (HH->row_order == k_nc_start_south)
2141 			first_row = header->n_rows - 1 - last_row;
2142 
2143 		/* Write grid header without closing file afterwards so more items can be added */
2144 		gmtnc_setup_chunk_cache();
2145 		status = gmtnc_grd_info (GMT, header, 'W', true, n_layers_used);
2146 		if (status != NC_NOERR)
2147 			goto nc_err;
2148 
2149 		/* Add cube parameters, etc not done by gmtnc_grd_info */
2150 
2151 		/* Define 3-D z variable and write the attributes */
2152 
2153 		gmtnc_put_units (HH->ncid, HH->xyz_id[GMT_Z], C->units);
2154 
2155 	//	if (C->name[0])
2156 	//		nc_put_att_text (HH->ncid, HH->z_id, "long_name", strlen(C->name), C->name);
2157 	//	else
2158 	//		nc_put_att_text (HH->ncid, HH->z_id, "long_name", 4U, "cube");
2159 	//	if (C->units[0]) {	/* This is the 3rd dimension name and units */
2160 	//		int i = 0, j = 0;
2161 	//		bool copy = false;
2162 	//		char name[GMT_GRID_UNIT_LEN80], units[GMT_GRID_UNIT_LEN80];
2163 	//		strncpy (name, C->units, GMT_GRID_UNIT_LEN80-1);
2164 	//		units[0] = '\0';
2165 	//		for (i = 0; i < GMT_GRID_UNIT_LEN80 && name[i]; i++) {
2166 	//			if (name[i] == ']') copy = false, units[j] = '\0';
2167 	//			if (copy) units[j++] = name[i];
2168 	//			if (name[i] == '[') {
2169 	//				name[i] = '\0';
2170 	//				if (i > 0 && name[i-1] == ' ') name[i-1] = '\0';
2171 	//				copy = true;
2172 	//			}
2173 	//		}
2174 	//		if (name[0]) nc_put_att_text (HH->ncid, HH->xyz_id[GMT_Z], "long_name", strlen(name), name);
2175 	//		if (units[0]) nc_put_att_text (HH->ncid, HH->xyz_id[GMT_Z], "units", strlen(units), units);
2176 	//		if (name[0] && strstr (name, "time"))
2177 	//			nc_put_att_text (HH->ncid, HH->xyz_id[GMT_Z], "standard_name", 4U, "time");
2178 	//		else if (name[0] == '\0')
2179 	//			nc_put_att_text (HH->ncid, HH->xyz_id[GMT_Z], "long_name", 1U, "z");
2180 	//	}
2181 	//	else	/* No information about z, so generic z */
2182 	//		nc_put_att_text (HH->ncid, HH->xyz_id[GMT_Z], "long_name", 1U, "z");
2183 		if (gmt_M_is_geographic (GMT, GMT_OUT))
2184 			nc_put_att_text (HH->ncid, HH->xyz_id[GMT_Z], "axis", 1, "Z");
2185 		gmt_M_err_trap (nc_put_att_double (HH->ncid, HH->xyz_id[GMT_Z], "actual_range", NC_DOUBLE, 2U, C->z_range));
2186 
2187 		gmt_M_err_trap (nc_enddef (HH->ncid));	/* Belatedly end definition mode */
2188 		gmtnc_put_xy_vectors (GMT, header);	/* Place x and y vectors */
2189 		{
2190 			size_t z_zero = 0, z_count = n_layers_used;
2191 			gmt_M_err_trap (nc_put_vara_double (HH->ncid, HH->xyz_id[GMT_Z], &z_zero, &z_count, &C->z[k0]));
2192 		}
2193 
2194 		/* Get stats */
2195 		//dim[0]    = height,    dim[1]    = width;    dim[2]    = n_layers_used;
2196 		dim[1]    = height,    dim[2]    = width;    dim[0]    = n_layers_used;
2197 		origin[0] = 0, origin[1] = 0;
2198 		here = k0 * C->header->size;	/* Start position in the cube for layer k0 */
2199 
2200 		for (k = k0; k <= k1; k++) {	/* For all selected output levels */
2201 			pgrid = &C->data[here];
2202 			HH->t_index[0] = k - k0;	/* Layer index */
2203 			/* Remove padding from grid */
2204 			gmtnc_unpad_grid (pgrid, width, height, header->pad, sizeof(pgrid[0]));
2205 
2206 			/* Check that repeating columns do not contain conflicting information */
2207 			if (HH->grdtype == GMT_GRID_GEOGRAPHIC_EXACT360_REPEAT)
2208 				gmtnc_grid_fix_repeat_col (GMT, pgrid, width, height, sizeof(pgrid[0]));
2209 
2210 			/* Flip grid upside down */
2211 			if (HH->row_order == k_nc_start_south)
2212 				gmt_grd_flip_vertical (pgrid, width, height, 0, sizeof(pgrid[0]));
2213 
2214 			/* Write grid layer */
2215 			status = gmtnc_io_nc_grid (GMT, header, dim, origin, 0, k_put_netcdf, pgrid, true);
2216 			if (status != NC_NOERR)
2217 				goto nc_err;
2218 			here += C->header->size;
2219 		}
2220 		if (level_min <= level_max) {
2221 			/* Warn if level-range exceeds the precision of a single precision float: */
2222 			static const uint32_t exp2_24 = 0x1000000; /* exp2 (24) */
2223 			unsigned int level;
2224 			if (fabs(level_min) >= exp2_24 || fabs(level_max) >= exp2_24)
2225 				GMT_Report (GMT->parent, GMT_MSG_WARNING, "The level-range, [%g,%g], might exceed the significand's precision of 24 bits; round-off errors may occur.\n", level_min, level_max);
2226 
2227 			/* Report level-range of grid layer (with scale and offset applied): */
2228 #ifdef NC4_DEBUG
2229 			level = GMT_MSG_WARNING;
2230 #else
2231 			level = GMT_MSG_DEBUG;
2232 #endif
2233 			GMT_Report (GMT->parent, level,
2234 					"packed z-range: [%g,%g]\n", level_min, level_max);
2235 
2236 		}
2237 		else
2238 			GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "No valid values in grid [%s]\n", HH->name);
2239 
2240 		if (status != NC_NOERR)
2241 			goto nc_err;
2242 		/* Close grid */
2243 		status = gmt_nc_close (GMT, HH->ncid);
2244 		if (status != NC_NOERR)
2245 			goto nc_err;
2246 
2247 		return GMT_NOERROR;
2248 
2249 nc_err:
2250 		/* exit gracefully */
2251 		gmt_nc_close (GMT, HH->ncid); /* close nc-file */
2252 		unlink (HH->name);  /* remove nc-file */
2253 		if (status == NC_ERANGE) {
2254 			/* report out of range z variable */
2255 			GMT_Report (GMT->parent, GMT_MSG_ERROR, "Cannot write format %s.\n", GMT->session.grdformat[header->type]);
2256 			GMT_Report (GMT->parent, GMT_MSG_ERROR, "The packed z-range, [%g,%g], exceeds the maximum representable size. Adjust scale and offset parameters or remove out-of-range values.\n", level_min, level_max);
2257 		}
2258 		return status;
2259 	}
2260 }
2261