/*--------------------------------------------------------------------
 *
 *	Copyright (c) 1991-2026 by the GMT Team (https://www.generic-mapping-tools.org/team.html)
 *	See LICENSE.TXT file for copying and redistribution conditions.
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU Lesser General Public License as published by
 *	the Free Software Foundation; version 3 or any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU Lesser General Public License for more details.
 *
 *	Contact info: www.generic-mapping-tools.org
 *--------------------------------------------------------------------*/
/*
 * Public functions for the GMT C/C++ API.  The API consist of functions
 * in gmt_api.c, gmt_parse.c, and all the GMT modules; see gmt.h for list.
 *
 * Author: 	Paul Wessel
 * Date:	1-JUN-2013
 * Version:	5
 *
 * The API presently consists of 69 documented functions.  For a full
 * description of the API, see the api.rst documentation [in doc/rst/source/devdocs].
 * These functions have FORTRAN bindings as well, provided you add
 * -DFORTRAN_API to the C preprocessor flags [in ConfigUserAdvanced.cmake].
 *
 * There are 2 public functions used for GMT API session handling.
 * This part of the API helps the developer create and delete GMT sessions:
 *
 * GMT_Create_Session	   : Initialize a new GMT session
 * GMT_Destroy_Session	   : Destroy a GMT session
 *
 * There is 2 public functions for common error reporting.
 * Errors will be reported to stderr or selected log file:
 *
 * GMT_Message             : Report an message given a verbosity level
 * GMT_Report              : Report an error given an error code
 *
 * There are 33 further public functions used for GMT i/o activities:
 *
 * GMT_Alloc_Segment       : Allocate a single GMT_DATASEGMENT segment
 * GMT_Begin_IO            : Allow i/o to take place for rec-by-rec operations
 * GMT_Convert_Data        : Convert between different data sets, if possible
 * GMT_Create_Data         : Return an empty container for a new data set, allocation optional
 * GMT_Destroy_Data        : Destroy a data set and its container
 * GMT_Duplicate_Data      : Make an exact duplicate of a data set
 * GMT_Duplicate_String    : Allocates a copy of a string to be freed by the API
 * GMT_Get_FilePath        : Check existence of file and replace with full path
 * GMT_End_IO              : Disallow further rec-by-rec i/o
 * GMT_Get_Info            : Get meta-data from the object passed
 * GMT_Get_Record          : Get the next single data record from the source(s)
 * GMT_Get_Row             : Read one row from a grid when in row-by-row mode
 * GMT_Get_Status          : Examine current status of record-by-record i/o
 * GMT_Get_Matrix          : Get user matrix from GMT_MATRIX array
 * GMT_Get_Vector          : Get user vector from GMT_VECTOR column
 * GMT_Get_Strings         : Get user strings from GMT_VECTOR or GMT_MATRIX containers
 * GMT_Init_IO             : Initialize rec-by-rec table i/o machinery before program use
 * GMT_Init_VirtualFile    : Reset a virtual file for reuse
 * GMT_Inquire_VirtualFile : Determine family of a virtual file
 * GMT_Open_VirtualFile    : Open a memory location for reading or writing a "file" by a module
 * GMT_Put_Levels          : Place an array with 3rd dimension (z or time) coordinates for a cube
 * GMT_Put_Record          : Send the next output record to its destination
 * GMT_Put_Row             : Write one row to a grid when in row-by-row mode
 * GMT_Put_Matrix          : Hook user matrix to a GMT_MATRIX array
 * GMT_Put_Vector          : Hook user vector to a GMT_VECTOR column
 * GMT_Put_Strings         : Hook user strings to GMT_VECTOR or GMT_MATRIX containers
 * GMT_Read_Data           : Load data into program memory from selected type and source
 * GMT_Read_Group          : Read numerous data sets from files into an array of objects
 * GMT_Read_VirtualFile    : Obtain the memory resource that a module wrote to
 * GMT_Register_IO         : Register a source (or destination) for i/o use
 * GMT_Set_Comment         : Update a meta-data comment for a data set
 * GMT_Write_Data          : Place data set from program memory to selected destination
 * GMT_Encode_Options      : Used by external APIs to fill out options from implicit rules

 * The above functions deal with registration of input sources (files,
 * streams, file handles, or memory locations) and output destinations
 * (same flavors as input), the setup of the i/o, and generic functions
 * to access the data either in one go (GMT_Get|Put_Data) or on a
 * record-by-record basis (GMT_Get|Put_Record).  Finally, data sets that
 * are allocated can then be destroyed when no longer needed. If you do not
 * deallocate then our GMT Garbage Collector will do so when the session is
 * destroyed.
 *
 * There are 6 functions that deal with options, defaults and arguments:
 *
 * GMT_Get_Common          : Checks for and returns values for GMT common options
 * GMT_Get_Default         : Return the value of a GMT default parameter as a string
 * GMT_Get_Enum            : Return the integer constant of a GMT API enum
 * GMT_Get_Values          : Convert string to one or more coordinates or dimensions
 * GMT_Set_Default         : Set a GMT default parameter via a strings
 * GMT_Option              : Display syntax for one or more GMT common options
 *
 * One function handles the listing of modules and the calling of any GMT module:
 *
 * GMT_Call_Module         : Call the specified GMT module; flags determine type of action
 *
 * Five functions are used to get grid index from row, col, and to obtain coordinates
 *
 * GMT_Get_Coord           : Return array of coordinates for one dimension (x-, y-, or z-array)
 * GMT_Get_Index           : Return 1-D grid index given row, col (deals with optional padding)
 * GMT_Get_Index3          : Return 1-D cube index given row, col, layer (deals with optional padding)
 * GMT_Get_Pixel           : Return 1-D image index given row, col, layer (deals with optional padding)
 * GMT_Set_Columns         : Specify number of output columns for rec-by-rec writing and if trailing text is present
 *
 * For FFT operations there are 8 additional API functions:
 *
 * GMT_FFT                 : Call the forward or inverse FFT for specified dimension
 * GMT_FFT_1D              :   Lower-level 1-D FFT call
 * GMT_FFT_2D              :   Lower-level 2-D FFT call
 * GMT_FFT_Create          : Initialize the FFT machinery for given dimension
 * GMT_FFT_Destroy         : Destroy FFT machinery
 * GMT_FFT_Option          : Display the syntax of the GMT FFT command-line option settings
 * GMT_FFT_Parse           : Parse the GMT FFT option argument
 * GMT_FFT_Wavenumber      : Return selected wavenumber value given its type and grid index
 *
 * There are also 13 functions for argument and option parsing.  See gmt_parse.c for these.
 *
 * Finally, three low-level F77-callable functions for grid i/o are given:
 *
 * gmt_f77_readgrdinfo_    : Read the header of a GMT grid
 * gmt_f77_readgrd_        : Read a GMT grid from file
 * gmt_f77_writegrd_       : Write a GMT grid to file
 *
 * --------------------------------------------------------------------------------------------
 * Guru notes on memory management: Paul Wessel, June 2013.
 *
 * GMT maintains control over allocating, reallocating, and freeing of GMT objects created by GMT.
 * Because GMT_modules may be given files, memory locations, streams, etc., as input and output we
 * have to impose some structure as how this will work seamlessly.  Here, "GMT object" refers to
 * any of the 6 GMT resources: grids, images, cubes, datasets, palettes, and PostScript.
 *
 * 1. When GMT allocates memory for a GMT object it sets its alloc_mode to GMT_ALLOC_INTERNALLY (1)
 *    and its alloc_level to <module level>.  This is 0 for the gmt.c UNIX application as well as
 *    for any external API (MEX, Python, Julia), 1 for any GMT module called, 2 for modules called
 *    by top-level modules, etc., as far down as the thread goes.
 * 2. Memory not allocated by GMT will have an implicit alloc_mode = GMT_ALLOC_EXTERNALLY [0]
 *    and alloc_level = 0 (i.e., gmt executable or API level) but it does not matter since such memory is
 *    only used for reading and we may never free it or reallocate it within GMT. This alloc_mode
 *    only applies to data arrays inside objects (e.g., G->data), not the GMT objects themselves.
 *    The GMT objects (the "containers") are GMT_allocated and freed at the end of their level, if not before.
 * 3. Memory passed into modules as "input file" requires no special treatment since its level
 *    will be lower than that of the module it is used in, and when that module tries to free it
 *    (directly with GMT_Destroy_Data or via end-of-module gmtlib_garbage_collection) it will skip
 *    it as its level does not match the current module level.  A module can only free memory that
 *    it allocated; the exception is the top-level gmt application.
 * 4. Passing memory out of a module (i.e., "writing to memory") requires that the calling function
 *    first create an output object and use the ID to encode the memory filename (@GMTAPI@-#***...#).
 *    The object stores the level it was called at.  Pass the encoded filename as the new output file.
 *    When GMT_Create_Data is called with no dimensions then the direction is set to GMT_OUT and
 *    we set the object's messenger flag to true.  This is used so that when the data set we wish to
 *    return out of a module is built it replaces the empty initial data set but inherits that data set's
 *    alloc_level so it may survive the life of the module exit process.
 *    Internally, the memory that the module allocates (e.g., grid, dataset, etc.) will initially
 *    have an alloc_level matching the module level (and would be freed if written to a regular
 *    file).  However, when GMT_Write_Data is called and we branch into the GMT_REFERENCE case we
 *    instead take the following steps:
 *	a) The registered output API->object's resource pointer is set to the GMT object that the
 *         module allocated (this is how we pass the data out of a module).
 *	b) The GMT object's alloc_level is changed to equal the output API->object's level (this
 *         is how it will survive beyond the end of the module that created the data).
 *	c) The API object originally pointing to the GMT object is flagged by having its variable
 *         no_longer_owner set to true (this is how we avoid freeing something twice).
 *    When the module ends there are two API objects with references to the GMT object: the internal
 *    module object and the output object.  The first is set to NULL by gmtlib_garbage_collection because
 *    the object is no longer the owner of the data. The second is ignored because its level is too low.
 *    After that any empty API objects are removed (so the no_longer_owner one is removed), while
 *    the second survives the life of the module, as we require, and is deleted after it has been used.
 *
 * Thus, at the session (gmt) level all GMT objects have alloc_level = 0 since anything higher will
 * have been freed by a module.  GMT_Destroy_Session finally calls gmtlib_garbage_collection a final
 * time and s/he frees any remaining GMT objects.
 *
 * Notes on family vs actual_family:
 *
 * The S->actual_family contains the object type that we allocated.  However, we allow modules
 * that expect a GMT_DATASET to instead be passed a GMT_VECTOR or GMT_MATRIX.  If so, then S->family
 * will be GMT_IS_DATASET while the actual_family remains GMT_VECTOR|GMT_MATRIX.  The i/o functions
 * GMT_Read_Data, GMT_Put_Record, etc. then knows how to deal with this scenario.
 *
 * Best viewed if a TAB = 8 spaces
 */

/*!
 * \file gmt_api.c
 * \brief Public functions for the GMT C/C++ API.
 *
 * A) List of exported gmt_* functions available to modules and libraries via gmt_dev.h:
 *
 * gmt_copy
 * gmt_eliminate_duplicates
 * gmt_get_api_ptr
 * gmt_get_header
 * gmt_is_an_object
 * gmt_whole_earth
 *
 * B) List of exported gmtlib_* functions available to libraries via gmt_internals.h:
 *
 * gmtlib_GDALDestroyDriverManager
 * gmtlib_close_grd
 * gmtlib_count_objects
 * gmtlib_create_header_item
 * gmtlib_data_is_geographic
 * gmtlib_garbage_collection
 * gmtlib_ind2rgb
 * gmtlib_module_classic_all
 * gmtlib_module_list_all
 * gmtlib_module_show_all
 * gmtlib_pick_in_col_number
 * gmtlib_report_error
 * gmtlib_unregister_io
 * gmtlib_validate_id
 */

#include "gmt_dev.h"
#include "gmt_internals.h"
#include "gmt_sharedlibs.h" 	/* Common shared libs structures */
#include <stdarg.h>
#include "gmt_gsformats.h"

#ifdef HAVE_DIRENT_H_
#	include <dirent.h>
#endif

#ifdef HAVE_SYS_DIR_H_
#	include <sys/dir.h>
#endif

/* Possibly define missing shared library constants for odd systems */
#ifndef DT_DIR
#	define DT_DIR 4
#endif

#ifndef RTLD_LAZY
#	define RTLD_LAZY 1
#endif

#ifdef WIN32	/* Special functions and includes needed under Windows */
#	include <windows.h>
#	include <process.h>
#	define getpid _getpid
#else
#	include <sys/ioctl.h>
#endif

#define GMTAPI_MAX_ID		999999	/* Largest integer that will fit in the %06d format */
#define GMTAPI_UNLIMITED	0	/* Using 0 to mean we may allow 1 or more data objects of this family */

#ifdef FORTRAN_API
/* Global structure pointer needed for FORTRAN-77 [PW: not tested yet - is it even needed?] */
static struct GMTAPI_CTRL *GMT_FORTRAN = NULL;
#endif

static int GMTAPI_session_counter = 0;	/* Keeps track of the ID of new sessions for multi-session programs */

/* Image node local lookup function pointer */
static uint64_t (*GMTAPI_index_function) (struct GMT_GRID_HEADER *, uint64_t, uint64_t, uint64_t);	/* Pointer to index function (for images only) */

/*! Macros that report error, then return a NULL pointer, the error, or a value, respectively */
#define return_null(API,err) { gmtlib_report_error(API,err); return (NULL);}
#define return_error(API,err) { gmtlib_report_error(API,err); return (err);}
#define return_value(API,err,val) { gmtlib_report_error(API,err); return (val);}

/* We asked for a subset of the grid if the wesn pointer is not NULL and indicates a nonzero region */
#define full_region(wesn) (!wesn || (wesn[XLO] == wesn[XHI] && wesn[YLO] == wesn[YHI]))

/* GMT_DATASET can be given via many individual files. */
#define multiple_files_ok(family) (family == GMT_IS_DATASET)
/* GRID and IMAGE can be read it two steps (header, then data). */
#define grid_or_image(family) (family == GMT_IS_GRID || family == GMT_IS_IMAGE)
/* GRID, IMAGE, and CUBE can also be read it two steps (header, then data). */
#define grid_or_image_or_cube(family) (family == GMT_IS_GRID || family == GMT_IS_IMAGE || family == GMT_IS_CUBE)
/* A MATRIX read as a SURFACE will read a grid */
#define matrix_is_a_surface(family,geometry) (family == GMT_IS_MATRIX && geometry == GMT_IS_SURFACE)

/* Misc. local text strings needed in this file only, used when debug verbose is on (-Vd).
 * NOTE: The order of these MUST MATCH the order in the corresponding enums in gmt_resources.h! */

static const char *GMT_method[] = {"File", "Stream", "File Descriptor", "Memory Copy", "Memory Reference"};
static const char *GMT_family[] = {"Data Table", "Grid", "Image", "CPT", "PostScript", "Matrix", "Vector", "Cube", "Coord"};
static const char *GMT_direction[] = {"Input", "Output"};
static const char *GMT_stream[] = {"Standard", "User-supplied"};
static const char *GMT_status[] = {"Unused", "In-use", "Used"};
static const char *GMT_geometry[] = {"Not Set", "Point", "Line", "Polygon", "Point|Line|Poly", "Line|Poly", "Surface", "Volume", "Non-Geographical", "Text"};
static const char *GMT_class[] = {"QUIET", "NOTICE", "ERROR", "WARNING", "TIMING", "INFORMATION", "COMPATIBILITY", "DEBUG"};
static unsigned int GMT_no_pad[4] = {0, 0, 0, 0};
static const char *GMT_family_abbrev[] = {"D", "G", "I", "C", "X", "M", "V", "U", "-"};
static const char *GMT_type[GMT_N_TYPES] = {"byte", "byte", "integer", "integer", "integer", "integer",
                                            "integer", "integer", "double", "double", "string", "datetime"};

/*! Two different i/o mode: GMT_Put|Get_Data vs GMT_Put|Get_Record */
enum GMT_enum_iomode {
	GMTAPI_BY_SET 	= 0,	/* Default is to read the entire dataset */
	GMTAPI_BY_REC	= 1};	/* Means we will access the registered files on a record-by-record basis */

/*! Entries into dim[] for matrix or vector */
enum GMT_dim {
	GMTAPI_HDR_POS = 3,	/* Used with curr_pos to keep track of table headers only */
	GMTAPI_DIM_COL	= 0,	/* Holds the number of columns for vectors and x-nodes for matrix */
	GMTAPI_DIM_ROW = 1};	/* Holds the number of rows for vectors and y-nodes for matrix */

enum GMTAPI_enum_input {
	GMTAPI_OPTION_INPUT 	= 0,	/* Input resource specified via an option (e.g., -G<file>) */
	GMTAPI_MODULE_INPUT 	= 1};	/* Input resource specified via the module command line */

enum GMTAPI_enum_status {
	GMTAPI_GOT_SEGHEADER 	= -1,	/* Read a segment header */
	GMTAPI_GOT_SEGGAP 	= -2};	/* Detected a gap and insertion of new segment header */

enum GMTAPI_enum_takes_mod {
	GMTAPI_MOD_NOTSET	= 0,	/* Not assigned */
	GMTAPI_MOD_SPECIAL	= 1,	/* Found = and possibly modifiers */
	GMTAPI_MOD_NORMAL	= 2};	/* No arg found after stripping modifiers (if any) */

/*==================================================================================================
 *		PRIVATE FUNCTIONS ONLY USED BY THIS LIBRARY FILE
 *==================================================================================================
 *
 * gmtapi_* functions are static and only used in gmt_api.c
 * gmtlib_* functions are exported and may be used in other gmt_*.c files
 */

GMT_LOCAL const char *gmtapi_method (unsigned int M) {
	if (M < GMT_IS_DUPLICATE) return (GMT_method[M]);
	if (M == GMT_IS_DUPLICATE) return (GMT_method[3]);
	if (M == GMT_IS_REFERENCE) return (GMT_method[4]);
	return NULL;
}

GMT_LOCAL void gmtapi_get_record_init (struct GMTAPI_CTRL *API);	/* See prototype elsewhere */

GMT_LOCAL int gmtapi_sort_on_classic (const void *vA, const void *vB) {
	const struct GMT_MODULEINFO *A = vA, *B = vB;
	if (A == NULL) return +1;	/* Get the NULL entry to the end */
	if (B == NULL) return -1;	/* Get the NULL entry to the end */
	return strcmp(A->cname, B->cname);
}

GMT_LOCAL int gmtapi_sort_on_modern (const void *vA, const void *vB) {
	const struct GMT_MODULEINFO *A = vA, *B = vB;
	if (A == NULL) return +1;	/* Get the NULL entry to the end */
	if (B == NULL) return -1;	/* Get the NULL entry to the end */
	return strcmp(A->mname, B->mname);
}

/* Function to exclude some special core modules from being reported by gmt --help|show-modules */
GMT_LOCAL int gmtapi_skip_this_module (const char *name) {
	if (!strncmp (name, "gmtread", 7U)) return 1;	/* Skip the gmtread module */
	if (!strncmp (name, "gmtwrite", 8U)) return 1;	/* Skip the gmtwrite module */
	return 0;	/* Display this one */
}

/* Function to exclude modern mode modules from being reported by gmt --show-classic */
GMT_LOCAL int gmtapi_skip_modern_module (const char *name) {
	if (!strncmp (name, "subplot", 7U)) return 1;	/* Skip the subplot module */
	if (!strncmp (name, "figure", 6U)) return 1;	/* Skip the figure module */
	if (!strncmp (name, "begin", 5U)) return 1;		/* Skip the begin module */
	if (!strncmp (name, "clear", 5U)) return 1;		/* Skip the clear module */
	if (!strncmp (name, "inset", 5U)) return 1;		/* Skip the inset module */
	if (!strncmp (name, "movie", 5U)) return 1;		/* Skip the movie module */
	if (!strncmp (name, "docs", 4U)) return 1;		/* Skip the docs module */
	if (!strncmp (name, "end", 3U)) return 1;		/* Skip the end module */
	return 0;	/* Display this one */
}

struct GMT_WORD {	/* Used by GMT_Wrap_Line only */
	char *word;
	unsigned int space;
};

EXTERN_MSC int gmt_nc_write_cube (struct GMT_CTRL *GMT, struct GMT_CUBE *C, double wesn[], const char *file);

/* Pretty print all GMT core module names and their purposes for gmt --help */
void gmtlib_module_show_all (void *V_API, struct GMT_MODULEINFO M[], const char *title) {
	unsigned int module_id = 0, n;
	char message[GMT_LEN256];
	struct GMTAPI_CTRL *API = gmt_get_api_ptr (V_API);

	GMT_Message (V_API, GMT_TIME_NONE, "\n===  %s  ===\n", title);
	while (M[module_id].cname != NULL) {
		if (module_id == 0 || strcmp (M[module_id-1].component, M[module_id].component)) {
			/* Start of new supplemental group */
			snprintf (message, GMT_LEN256, "\nModule name:     Purpose of %s module:\n", M[module_id].component);
			GMT_Message (V_API, GMT_TIME_NONE, message);
			GMT_Message (V_API, GMT_TIME_NONE, "----------------------------------------------------------------\n");
		}
		n = module_id + 1;	/* Determine extent of this component lib */
		while (M[n].cname != NULL && !strcmp (M[n-1].component, M[n].component)) n++;
		/* Sort array on modern names */
		qsort (&M[module_id], n-module_id, sizeof (struct GMT_MODULEINFO), gmtapi_sort_on_modern);

		if (API->external || !gmtapi_skip_this_module (M[module_id].cname)) {
			snprintf (message, GMT_LEN256, "%-16s %s\n",
				M[module_id].mname, M[module_id].purpose);
				GMT_Message (V_API, GMT_TIME_NONE, message);
		}
		++module_id;
	}
}

/* Produce single list on stdout of all GMT core module names for gmt --show-modules */
void gmtlib_module_list_all (void *V_API, struct GMT_MODULEINFO M[]) {
	unsigned int module_id = 0;
	size_t n_modules = 0;
	struct GMTAPI_CTRL *API = gmt_get_api_ptr (V_API);

	while (M[n_modules].cname != NULL)	/* Count the modules */
		++n_modules;

	/* Sort array on modern names */
	qsort (M, n_modules, sizeof (struct GMT_MODULEINFO), gmtapi_sort_on_modern);

	while (M[module_id].cname != NULL) {
		if (API->external || !gmtapi_skip_this_module (M[module_id].cname))
			printf ("%s\n", M[module_id].mname);
		++module_id;
	}
}

/* Produce single list on stdout of all GMT core module names for gmt --show-classic [i.e., classic mode names] */
void gmtlib_module_classic_all (void *V_API, struct GMT_MODULEINFO M[]) {
	unsigned int module_id = 0;
	size_t n_modules = 0;
	struct GMTAPI_CTRL *API = gmt_get_api_ptr (V_API);

	while (M[n_modules].cname != NULL)	/* Count the modules */
		++n_modules;

	/* Sort array on classic names */
	qsort (M, n_modules, sizeof (struct GMT_MODULEINFO), gmtapi_sort_on_classic);

	while (M[module_id].cname != NULL) {
		if (API->external || !(gmtapi_skip_this_module (M[module_id].cname) || gmtapi_skip_modern_module (M[module_id].cname)))
			printf ("%s\n", M[module_id].cname);
		++module_id;
	}
}

/* Lookup module id by name, return option keys pointer (for external API developers) */
const char *gmtlib_module_keys (void *API, struct GMT_MODULEINFO M[], char *candidate) {
	int module_id = 0;
	gmt_M_unused(API);

	/* Match actual_name against M[module_id].cname */
	while (M[module_id].cname != NULL &&
	       strcmp (candidate, M[module_id].cname))
		++module_id;

	/* Return Module keys or NULL */
	return (M[module_id].keys);
}

/* Lookup module id by name, return group char name (for external API developers) */
const char *gmtlib_module_group (void *API, struct GMT_MODULEINFO M[], char *candidate) {
	int module_id = 0;
	gmt_M_unused(API);

	/* Match actual_name against M[module_id].cname */
	while (M[module_id].cname != NULL &&
	       strcmp (candidate, M[module_id].cname))
		++module_id;

	/* Return Module keys or NULL */
	return (M[module_id].component);
}

int GMT_Show_ModuleInfo (void *API, struct GMT_MODULEINFO M[], char *arg, unsigned int mode) {
	/* API function to display module information from shared libraries */
	if (API == NULL) return_error (API, GMT_NOT_A_SESSION);
	switch (mode) {
		case GMT_MODULE_HELP:
			if (arg == NULL) return_error (API, GMT_ARG_IS_NULL);
			gmtlib_module_show_all (API, M, arg);
			break;
		case GMT_MODULE_SHOW_MODERN:
			gmtlib_module_list_all (API, M);
			break;
		case GMT_MODULE_SHOW_CLASSIC:
			gmtlib_module_classic_all (API, M);
			break;
		default:
			GMT_Report (API, GMT_MSG_ERROR, "Internal error in GMT_Show_ModuleInfo: Passed bad mode (%d)\n", mode);
			return_error (API, GMT_NOT_A_VALID_MODE);
			break;
	}
	return (GMT_NOERROR);
}

const char * GMT_Get_ModuleInfo (void *API, struct GMT_MODULEINFO M[], char *module, unsigned int mode) {
	/* API function to display module information from shared libraries */
	const char *answer = NULL;
	if (API == NULL) return_null (NULL, GMT_NOT_A_SESSION);
	if (module == NULL) return_null (NULL, GMT_ARG_IS_NULL);
	switch (mode) {
		case GMT_MODULE_KEYS:
			answer = gmtlib_module_keys (API, M, module);
			break;
		case GMT_MODULE_GROUP:
			answer = gmtlib_module_group (API, M, module);
			break;
		default:
			GMT_Report (API, GMT_MSG_ERROR, "Internal error in GMT_Get_ModuleInfo: Passed bad mode (%d)\n", mode);
			return_null (NULL, GMT_NOT_A_VALID_MODE);
			break;
	}
	return (answer);
}

/* Series of one-line functions to assign val to a particular union member of array u at position row, rounding if integer output */
GMT_LOCAL void gmtapi_put_val_double (union GMT_UNIVECTOR *u, uint64_t row, double val) { u->f8[row]  =                  val; }
GMT_LOCAL void gmtapi_put_val_float  (union GMT_UNIVECTOR *u, uint64_t row, double val) { u->f4[row]  = (float)          val; }
GMT_LOCAL void gmtapi_put_val_ulong  (union GMT_UNIVECTOR *u, uint64_t row, double val) { u->ui8[row] = (uint64_t)lrint(val); }
GMT_LOCAL void gmtapi_put_val_long   (union GMT_UNIVECTOR *u, uint64_t row, double val) { u->si8[row] =  (int64_t)lrint(val); }
GMT_LOCAL void gmtapi_put_val_uint   (union GMT_UNIVECTOR *u, uint64_t row, double val) { u->ui4[row] = (uint32_t)lrint(val); }
GMT_LOCAL void gmtapi_put_val_int    (union GMT_UNIVECTOR *u, uint64_t row, double val) { u->si4[row] =  (int32_t)lrint(val); }
GMT_LOCAL void gmtapi_put_val_ushort (union GMT_UNIVECTOR *u, uint64_t row, double val) { u->ui2[row] = (uint16_t)lrint(val); }
GMT_LOCAL void gmtapi_put_val_short  (union GMT_UNIVECTOR *u, uint64_t row, double val) { u->si2[row] =  (int16_t)lrint(val); }
GMT_LOCAL void gmtapi_put_val_uchar  (union GMT_UNIVECTOR *u, uint64_t row, double val) { u->uc1[row] =  (uint8_t)lrint(val); }
GMT_LOCAL void gmtapi_put_val_char   (union GMT_UNIVECTOR *u, uint64_t row, double val) { u->sc1[row] =   (int8_t)lrint(val); }

GMT_LOCAL void gmtapi_get_val_double (union GMT_UNIVECTOR *u, uint64_t row, double *val) { *val = u->f8[row]; }
GMT_LOCAL void gmtapi_get_val_float  (union GMT_UNIVECTOR *u, uint64_t row, double *val) { *val = u->f4[row]; }
GMT_LOCAL void gmtapi_get_val_ulong  (union GMT_UNIVECTOR *u, uint64_t row, double *val) { *val = (double)u->ui8[row]; }	/* Must cast/truncate since longs integer range exceed that of double */
GMT_LOCAL void gmtapi_get_val_long   (union GMT_UNIVECTOR *u, uint64_t row, double *val) { *val = (double)u->si8[row]; }	/* Must cast/truncate since longs integer range exceed that of double */
GMT_LOCAL void gmtapi_get_val_uint   (union GMT_UNIVECTOR *u, uint64_t row, double *val) { *val = u->ui4[row]; }
GMT_LOCAL void gmtapi_get_val_int    (union GMT_UNIVECTOR *u, uint64_t row, double *val) { *val = u->si4[row]; }
GMT_LOCAL void gmtapi_get_val_ushort (union GMT_UNIVECTOR *u, uint64_t row, double *val) { *val = u->ui2[row]; }
GMT_LOCAL void gmtapi_get_val_short  (union GMT_UNIVECTOR *u, uint64_t row, double *val) { *val = u->si2[row]; }
GMT_LOCAL void gmtapi_get_val_uchar  (union GMT_UNIVECTOR *u, uint64_t row, double *val) { *val = u->uc1[row]; }
GMT_LOCAL void gmtapi_get_val_char   (union GMT_UNIVECTOR *u, uint64_t row, double *val) { *val = u->sc1[row]; }

GMT_LOCAL inline GMT_putfunction gmtapi_select_put_function (struct GMTAPI_CTRL *API, unsigned int type) {
	switch (type) {	/* Use type to select the correct put function with which to place a value in the union */
		case GMT_DOUBLE:	return (gmtapi_put_val_double);	break;
		case GMT_FLOAT:		return (gmtapi_put_val_float);	break;
		case GMT_ULONG:		return (gmtapi_put_val_ulong);	break;
		case GMT_LONG:		return (gmtapi_put_val_long);	break;
		case GMT_UINT:		return (gmtapi_put_val_uint);	break;
		case GMT_INT:		return (gmtapi_put_val_int);	break;
		case GMT_USHORT:	return (gmtapi_put_val_ushort);	break;
		case GMT_SHORT:		return (gmtapi_put_val_short);	break;
		case GMT_UCHAR:		return (gmtapi_put_val_uchar);	break;
		case GMT_CHAR:		return (gmtapi_put_val_char);	break;
		default:
			GMT_Report (API, GMT_MSG_ERROR, "Internal error in gmtapi_select_put_function: Passed bad type (%d), Will be unable to place binary data\n", type);
			return NULL;
			break;
	}
}

GMT_LOCAL inline GMT_getfunction gmtapi_select_get_function (struct GMTAPI_CTRL *API, unsigned int type) {
	switch (type) {	/* Use type to select the correct get function with which to extract a value from the union */
		case GMT_DOUBLE:	return (gmtapi_get_val_double);	break;
		case GMT_FLOAT:		return (gmtapi_get_val_float);	break;
		case GMT_ULONG:		return (gmtapi_get_val_ulong);	break;
		case GMT_LONG:		return (gmtapi_get_val_long);	break;
		case GMT_UINT:		return (gmtapi_get_val_uint);	break;
		case GMT_INT:		return (gmtapi_get_val_int);	break;
		case GMT_USHORT:	return (gmtapi_get_val_ushort);	break;
		case GMT_SHORT:		return (gmtapi_get_val_short);	break;
		case GMT_UCHAR:		return (gmtapi_get_val_uchar);	break;
		case GMT_CHAR:		return (gmtapi_get_val_char);	break;
		default:
			GMT_Report (API, GMT_MSG_ERROR, "Internal error in gmtapi_select_get_function: Passed bad type (%d), will be unable to convert binary data\n", type);
			return NULL;
			break;
	}
}

GMT_LOCAL bool gmtapi_valid_input_family (unsigned int family) {
	/* Return true for the main input types */
	return (family == GMT_IS_DATASET || family == GMT_IS_GRID || family == GMT_IS_CUBE \
	       || family == GMT_IS_IMAGE || family == GMT_IS_PALETTE || family == GMT_IS_POSTSCRIPT);
}

GMT_LOCAL bool gmtapi_valid_actual_family (unsigned int family) {
	/* Return true for the main actual family types */
	return (family < GMT_N_FAMILIES);
}

GMT_LOCAL bool gmtapi_valid_output_family (unsigned int family) {
	if (family == GMT_IS_VECTOR || family == GMT_IS_MATRIX || family == GMT_IS_POSTSCRIPT) return true;
	return gmtapi_valid_input_family (family);
}

GMT_LOCAL bool gmtapi_valid_via_family (unsigned int family) {
	if (family == GMT_IS_VECTOR || family == GMT_IS_MATRIX) return true;
	return false;
}

GMT_LOCAL bool gmtapi_valid_type (int type) {	/* Check for valid matrix/vector data types */
	if (type < GMT_CHAR || type > GMT_DOUBLE) return false;
	return true;
}

/*! . */
GMT_LOCAL int gmtapi_get_item (struct GMTAPI_CTRL *API, unsigned int family, void *data) {
	/* Get the first item of requested family from list of objects, allowing for
	 * datasets and grids to masquerade as other things (Matrix, vector). */
	unsigned int i;
	int item;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;

	API->error = GMT_NOERROR;
	for (i = 0, item = GMT_NOTSET; item == GMT_NOTSET && i < API->n_objects; i++) {
		if (!API->object[i]) continue;		/* Skip empty object */
		S_obj = API->object[i];			/* Just a short-hand */
		if (!S_obj->resource) continue;		/* No resource */
		if (S_obj->family != (enum GMT_enum_family)family) {	/* Not the required data type; check for exceptions... */
			if (family == GMT_IS_DATASET && gmtapi_valid_via_family (S_obj->family))
				S_obj->family = GMT_IS_DATASET;	/* Vectors or Matrix masquerading as dataset are valid. Change their family here. */
			else if (family == GMT_IS_GRID && S_obj->family == GMT_IS_MATRIX)
				S_obj->family = GMT_IS_GRID;	/* Matrix masquerading as grid is valid. Change its family here. */
			else	/* We don't like your kind */
				continue;
		}
		if (S_obj->resource == data) item = i;	/* Found the requested data item */
	}
	if (item == GMT_NOTSET) { API->error = GMT_NOT_A_VALID_ID; return (GMT_NOTSET); }	/* No such data found */
	return (item);
}

/*! . */
GMT_LOCAL inline uint64_t gmtapi_n_cols_needed_for_gaps (struct GMT_CTRL *GMT, uint64_t n) {
	/* Return the actual columns needed (which may be more than n if gap testing demands it) */
	if (GMT->common.g.active) return (MAX (n, GMT->common.g.n_col));	/* n or n_col (if larger) */
	return (n);	/* No gap checking, n it is */
}

/*! . */
GMT_LOCAL inline void gmtapi_update_prev_rec (struct GMT_CTRL *GMT, uint64_t n_use) {
	/* Update previous record before reading the new record, but only if needed */
	if (GMT->current.io.need_previous) gmt_M_memcpy (GMT->current.io.prev_rec, GMT->current.io.curr_rec, n_use, double);
}

/*! . */
GMT_LOCAL int gmtapi_alloc_grid (struct GMT_CTRL *GMT, struct GMT_GRID *G) {
	/* Use information in Grid header to allocate the grid data array.
	 * We assume gmtapi_init_grdheader has already been called. */
	struct GMT_GRID_HIDDEN *GG = NULL;
	struct GMT_GRID_HEADER_HIDDEN *GH = NULL;

	if (G == NULL) return (GMT_PTR_IS_NULL);
	if (G->data) return (GMT_PTR_NOT_NULL);
	if (G->header->size == 0U) return (GMT_SIZE_IS_ZERO);
	if ((G->data = gmt_M_memory_aligned (GMT, NULL, G->header->size, gmt_grdfloat)) == NULL) return (GMT_MEMORY_ERROR);
	GH = gmt_get_H_hidden (G->header);
	GH->orig_datatype = (sizeof (gmt_grdfloat) == sizeof (float)) ? GMT_FLOAT : GMT_DOUBLE;
	GG = gmt_get_G_hidden (G);
	GG->alloc_mode = GMT_ALLOC_INTERNALLY;      /* Memory can be freed by GMT. */
	GG->alloc_level = GMT->hidden.func_level;   /* Must be freed at this level. */
	return (GMT_NOERROR);
}

/*! . */
GMT_LOCAL double * gmtapi_grid_coord (struct GMTAPI_CTRL *API, int dim, struct GMT_GRID *G) {
	/* Return pointer to one of the grid dimension coordinate arrays (x, y) */
	return (gmt_grd_coord (API->GMT, G->header, dim));
}

/*! . */
GMT_LOCAL double * gmtapi_cube_coord (struct GMTAPI_CTRL *API, int dim, struct GMT_CUBE *U) {
	/* Return pointer to one of the cube dimension coordinate arrays (x, y, z) */
	return (gmt_grd_coord (API->GMT, U->header, dim));
}

/*! . */
GMT_LOCAL int gmtapi_alloc_grid_xy (struct GMTAPI_CTRL *API, struct GMT_GRID *G) {
	/* Use information in Grid header to allocate the grid x/y dimension vectors.
	 * We assume gmtapi_init_grdheader has been called. */
	struct GMT_GRID_HIDDEN *GH = NULL;
	if (G == NULL) return (GMT_PTR_IS_NULL);
	if (G->x || G->y) return (GMT_PTR_NOT_NULL);
	GH = gmt_get_G_hidden (G);
	G->x = gmtapi_grid_coord (API, GMT_X, G);	/* Get array of x coordinates */
	G->y = gmtapi_grid_coord (API, GMT_Y, G);	/* Get array of y coordinates */
	GH->xy_alloc_mode[GMT_X] = GH->xy_alloc_mode[GMT_Y] = GMT_ALLOC_INTERNALLY;
	return (GMT_NOERROR);
}

/*! . */
GMT_LOCAL int gmtapi_alloc_image (struct GMT_CTRL *GMT, uint64_t *dim, unsigned int mode, struct GMT_IMAGE *I) {
	/* Use information in Image header to allocate the image data.
	 * We assume gmtapi_init_grdheader has been called.
	 * If dim given and is 2 or 4 then we have 1 or 3 bands plus alpha channel
	 * Depending on mode, the alpha layer is part of image or separate array. */
	unsigned int n_bands = I->header->n_bands;
	struct GMT_IMAGE_HIDDEN *IU = NULL;

	if (I == NULL) return (GMT_PTR_IS_NULL);
	if (I->data) return (GMT_PTR_NOT_NULL);
	if (I->header->size == 0U) return (GMT_SIZE_IS_ZERO);
	IU = gmt_get_I_hidden (I);
	if (dim && (dim[GMT_Z] == 2 || dim[GMT_Z] == 4)) {	/* Transparency layer is requested */
		if ((mode & GMT_IMAGE_ALPHA_LAYER) == 0) {	/* Use a separate alpha array */
			if ((I->alpha = gmt_M_memory_aligned (GMT, NULL, I->header->size, unsigned char)) == NULL) return (GMT_MEMORY_ERROR);
			n_bands--;	/* One less layer to allocate */
		}
	}
	if ((I->data = gmt_M_memory_aligned (GMT, NULL, I->header->size * n_bands, unsigned char)) == NULL) return (GMT_MEMORY_ERROR);
	I->header->n_bands = n_bands;	/* Update as needed */
	IU->alloc_mode = GMT_ALLOC_INTERNALLY;      /* Memory can be freed by GMT. */
	IU->alloc_level = GMT->hidden.func_level;   /* Must be freed at this level. */
	return (GMT_NOERROR);
}

/*! . */
GMT_LOCAL double * gmtapi_image_coord (struct GMTAPI_CTRL *API, int dim, struct GMT_IMAGE *I) {
	/* Return pointer to one of the image dimension coordinate arrays (x, y) */
	return (gmt_grd_coord (API->GMT, I->header, dim));
}

/*! . */
GMT_LOCAL int gmtapi_alloc_image_xy (struct GMTAPI_CTRL *API, struct GMT_IMAGE *I) {
	/* Use information in the Image header to allocate the image x,y vectors.
	 * We assume gmtapi_init_grdheader has been called. */
	if (I == NULL) return (GMT_PTR_IS_NULL);
	if (I->x || I->y) return (GMT_PTR_NOT_NULL);
	I->x = gmtapi_image_coord (API, GMT_X, I);	/* Get array of x coordinates */
	I->y = gmtapi_image_coord (API, GMT_Y, I);	/* Get array of y coordinates */
	return (GMT_NOERROR);
}

/*! . */
GMT_LOCAL int gmtapi_print_func (FILE *fp, const char *message) {
	/* Just print this message to fp.  It is being used indirectly via
	 * API->print_func.  Purpose of this mechanism is to allow external APIs such
	 * as MATLAB (which cannot use printf) to reset API->print_func to
	 * mexPrintf or similar functions. Default is gmtapi_print_func. */

	fprintf (fp, "%s", message);
	return 0;
}

/*! . */
GMT_LOCAL unsigned int gmtapi_gmtry (unsigned int geometry) {
	/* Return index to text representation in the array GMT_geometry[] for debug messages */
	if (geometry == GMT_IS_POINT)   return 1;
	if (geometry == GMT_IS_LINE)    return 2;
	if (geometry == GMT_IS_POLY)    return 3;
	if (geometry == GMT_IS_PLP)     return 4;
	if ((geometry & GMT_IS_LINE) && (geometry & GMT_IS_POLY)) return 5;
	if (geometry == GMT_IS_SURFACE) return 6;
	if (geometry == GMT_IS_NONE)    return 7;
	if (geometry == GMT_IS_TEXT)    return 8;
	return 0;
}
/* We also need to return the pointer to an object given a void * address of that pointer.
 * This needs to be done on a per data-type basis, e.g., to cast that void * to a struct GMT_GRID **
 * so we may return the value at that address: */

GMT_LOCAL inline struct GMTAPI_CTRL    * gmtapi_get_api_ptr      (struct GMTAPI_CTRL *ptr)     {return (ptr);}
GMT_LOCAL inline struct GMT_PALETTE    * gmtapi_get_cpt_ptr      (struct GMT_PALETTE **ptr)    {return (*ptr);}
GMT_LOCAL inline struct GMT_DATASET    * gmtapi_get_dataset_ptr  (struct GMT_DATASET **ptr)    {return (*ptr);}
GMT_LOCAL inline struct GMT_GRID       * gmtapi_get_grid_ptr     (struct GMT_GRID **ptr)       {return (*ptr);}
GMT_LOCAL inline struct GMT_POSTSCRIPT * gmtapi_get_ps_ptr       (struct GMT_POSTSCRIPT **ptr) {return (*ptr);}
GMT_LOCAL inline struct GMT_CUBE       * gmtapi_get_cube_ptr     (struct GMT_CUBE **ptr)       {return (*ptr);}
GMT_LOCAL inline struct GMT_MATRIX     * gmtapi_get_matrix_ptr   (struct GMT_MATRIX **ptr)     {return (*ptr);}
GMT_LOCAL inline struct GMT_VECTOR     * gmtapi_get_vector_ptr   (struct GMT_VECTOR **ptr)     {return (*ptr);}
GMT_LOCAL inline double      	       * gmtapi_get_coord_ptr    (double **ptr)                {return (*ptr);}
GMT_LOCAL inline struct GMT_IMAGE      * gmtapi_get_image_ptr    (struct GMT_IMAGE **ptr)      {return (*ptr);}
/* Various inline functs to convert void pointer to specific type */
GMT_LOCAL inline struct GMT_GRID_ROWBYROW * gmtapi_get_rbr_ptr (struct GMT_GRID_ROWBYROW *ptr) {return (ptr);}
GMT_LOCAL inline struct GMT_FFT_INFO * gmtapi_get_fftinfo_ptr (struct GMT_FFT_INFO *ptr) {return (ptr);}
GMT_LOCAL inline struct GMT_FFT_WAVENUMBER * gmtapi_get_fftwave_ptr (struct GMT_FFT_WAVENUMBER *ptr) {return (ptr);}
GMT_LOCAL inline struct GMT_FFT_WAVENUMBER ** gmtapi_get_fftwave_addr (struct GMT_FFT_WAVENUMBER **ptr) {return (ptr);}
GMT_LOCAL inline struct GMT_GRID    * gmtapi_get_grid_data (struct GMT_GRID *ptr) {return (ptr);}
GMT_LOCAL inline struct GMT_IMAGE   * gmtapi_get_image_data (struct GMT_IMAGE *ptr) {return (ptr);}
GMT_LOCAL inline struct GMT_DATASET * gmtapi_get_dataset_data (struct GMT_DATASET *ptr) {return (ptr);}
GMT_LOCAL inline struct GMT_VECTOR  * gmtapi_get_vector_data (struct GMT_VECTOR *ptr) {return (ptr);}
GMT_LOCAL inline struct GMT_MATRIX  * gmtapi_get_matrix_data (struct GMT_MATRIX *ptr) {return (ptr);}
GMT_LOCAL inline struct GMT_CUBE  * gmtapi_get_cube_data (struct GMT_CUBE *ptr) {return (ptr);}
GMT_LOCAL inline struct GMT_POSTSCRIPT  * gmtapi_get_postscript_data (struct GMT_POSTSCRIPT *ptr) {return (ptr);}
GMT_LOCAL inline struct GMT_PALETTE  * gmtapi_get_palette_data (struct GMT_PALETTE *ptr) {return (ptr);}
GMT_LOCAL inline char ** gmtapi_get_char_char_ptr     (char **ptr)  {return (ptr);}

/*! gmtapi_return_address is a convenience function that, given type, calls the correct converter */
GMT_LOCAL void * gmtapi_return_address (void *data, unsigned int type) {
	void *p = NULL;
	switch (type) {
		case GMT_IS_GRID:       p = gmtapi_get_grid_ptr (data);     break;
		case GMT_IS_DATASET:    p = gmtapi_get_dataset_ptr (data);  break;
		case GMT_IS_PALETTE:    p = gmtapi_get_cpt_ptr (data);      break;
		case GMT_IS_POSTSCRIPT: p = gmtapi_get_ps_ptr (data);       break;
		case GMT_IS_CUBE:	p = gmtapi_get_cube_ptr (data);     break;
		case GMT_IS_MATRIX:     p = gmtapi_get_matrix_ptr (data);   break;
		case GMT_IS_VECTOR:     p = gmtapi_get_vector_ptr (data);   break;
		case GMT_IS_COORD:      p = gmtapi_get_coord_ptr (data);    break;
		case GMT_IS_IMAGE:      p = gmtapi_get_image_ptr (data);    break;
	}
	return (p);
}

/*! . */
struct GMTAPI_CTRL * gmt_get_api_ptr (struct GMTAPI_CTRL *ptr) {
	/* Clean casting of void to API pointer at start of a module
 	 * If ptr is NULL we are in deep trouble...
	 */
	if (ptr == NULL) return_null (NULL, GMT_NOT_A_SESSION);
	return (ptr);
}

/*! gmtapi_alloc_object_array is a convenience function that, given type, allocates an array of pointers to the corresponding container */
GMT_LOCAL void * gmtapi_alloc_object_array (struct GMTAPI_CTRL *API, unsigned int n_items, unsigned int type) {
	void *p = NULL;
	switch (type) {
		case GMT_IS_GRID:	p = gmt_M_memory (API->GMT, NULL, n_items, struct GMT_GRID *);		break;
		case GMT_IS_DATASET:	p = gmt_M_memory (API->GMT, NULL, n_items, struct GMT_DATASET *);	break;
		case GMT_IS_PALETTE:	p = gmt_M_memory (API->GMT, NULL, n_items, struct GMT_PALETTE *);	break;
		case GMT_IS_CUBE:	p = gmt_M_memory (API->GMT, NULL, n_items, struct GMT_CUBE *);		break;
		case GMT_IS_POSTSCRIPT:	p = gmt_M_memory (API->GMT, NULL, n_items, struct GMT_POSTSCRIPT *);	break;
		case GMT_IS_IMAGE:	p = gmt_M_memory (API->GMT, NULL, n_items, struct GMT_IMAGE *);		break;
		case GMT_IS_MATRIX:	p = gmt_M_memory (API->GMT, NULL, n_items, struct GMT_MATRIX *);	break;
		case GMT_IS_VECTOR:	p = gmt_M_memory (API->GMT, NULL, n_items, struct GMT_VECTOR *);	break;
	}
	return (p);
}

#ifdef DEBUG
/*! Can be used to display API->object info wherever it is called as part of a debug operation.
 *  JUst search for gmtapi_list_objects to see where it is used and how. */
GMT_LOCAL void gmtapi_list_objects (struct GMTAPI_CTRL *API, char *txt) {
	unsigned int item, ext;
	struct GMTAPI_DATA_OBJECT *S;
	char message[GMT_LEN256] = {""}, O, M;
	/* if (API->deep_debug == false) return; */
	if (!gmt_M_is_verbose (API->GMT, GMT_MSG_DEBUG)) return;
	snprintf (message, GMT_LEN256, "==> %d API Objects at end of %s\n", API->n_objects, txt);
	GMT_Message (API, GMT_TIME_NONE, message);
	if (API->n_objects == 0) return;
	GMT_Message (API, GMT_TIME_NONE, "--------------------------------------------------------\n");
	snprintf (message, GMT_LEN256,   "K.. ID RESOURCE.... FAMILY.... ACTUAL.... DIR... S O M L\n");
	GMT_Message (API, GMT_TIME_NONE, message);
	GMT_Message (API, GMT_TIME_NONE, "--------------------------------------------------------\n");
	for (item = 0; item < API->n_objects; item++) {
		if ((S = API->object[item]) == NULL) continue;
		O = (S->no_longer_owner) ? 'N' : 'Y';
		M = (S->messenger) ? 'Y' : 'N';
		ext = (S->alloc_mode == GMT_ALLOC_EXTERNALLY) ? '*' : ' ';
		snprintf (message, GMT_LEN256, "%c%2d %2d %12" PRIxS " %-10s %-10s %-6s %d %c %c %d\n", ext, item, S->ID, (size_t)S->resource,
			GMT_family[S->family], GMT_family[S->actual_family], GMT_direction[S->direction], S->status, O, M, S->alloc_level);
		GMT_Message (API, GMT_TIME_NONE, message);
	}
	GMT_Message (API, GMT_TIME_NONE, "--------------------------------------------------------\n");
}

/*! Mostly for debugging */
GMT_LOCAL void gmtapi_set_object (struct GMTAPI_CTRL *API, struct GMTAPI_DATA_OBJECT *obj) {
	/* This is mostly for debugging and may go away or remain under DEBUG */
	GMT_Report (API, GMT_MSG_DEBUG, "Set_Object for family: %d\n", obj->family);
	switch (obj->family) {
		case GMT_IS_GRID:	obj->G = obj->resource; break;
		case GMT_IS_DATASET:	obj->D = obj->resource; break;
		case GMT_IS_PALETTE:	obj->C = obj->resource; break;
		case GMT_IS_CUBE:	obj->U = obj->resource; break;
		case GMT_IS_POSTSCRIPT:	obj->P = obj->resource; break;
		case GMT_IS_MATRIX:	obj->M = obj->resource; break;
		case GMT_IS_VECTOR:	obj->V = obj->resource; break;
		case GMT_IS_COORD:	break;	/* No worries */
		case GMT_IS_IMAGE:	obj->I = obj->resource; break;
		case GMT_N_FAMILIES:	break;
	}
}
#endif

GMT_LOCAL bool gmtapi_modern_casename (char *arg) {
    /* Return true if argument is all letters of same case, e.g., pny, PNN, tift, jxx, i.e.,
     * the user did -pny map or -jxx map etc.  We will assume that is likely a typo and report that,
     * but this cannot possibly be foolproof. */

    unsigned int k = 1;	/* First position beyond the leading letter */
	if (islower (arg[0])) {	/* Expect all to be lower case letters */
		while (arg[k]) {
			if (!islower (arg[k])) return false;    /* Failed the test */
			else k++;
		}
	}
	else {	/* Check for all upper case letters */
		while (arg[k]) {
			if (!isupper (arg[k])) return false;    /* Failed the test */
			else k++;
		}
	}
	return (true);	/* Passed the letter check */
}

GMT_LOCAL int gmtapi_modern_oneliner (struct GMTAPI_CTRL *API, struct GMT_OPTION *head) {
	/* Must check if a one-liner with special graphics format settings were given, e.g., "gmt pscoast -Rg -JH0/15c -Gred -png map" */
	int modern = 0;
	unsigned pos;
	size_t len;
	char format[GMT_LEN128] = {""}, p[GMT_LEN16] = {""}, *c = NULL;
	struct GMT_OPTION *opt;

	for (opt = head; opt; opt = opt->next) {
		if (opt->option == GMT_OPT_INFILE || opt->option == GMT_OPT_OUTFILE) continue;  /* Skip file names */
		if (opt->next == NULL || opt->next->option != GMT_OPT_INFILE) continue;  /* Skip arg not followed by file names (we expect -png map, for instance) */
		if (strchr (gmt_session_codestr, opt->option) == NULL) continue;	/* Option not the first letter of a valid graphics format */
		if ((len = strlen (opt->arg)) == 0 || len >= GMT_GRAPHIC_MAXLEN) continue;	/* No arg or too long args that are filenames can be skipped */
		if (opt->option == 'b' && strchr ("df", opt->arg[0])) continue;   /* Ignore [-bd|f] */
		if (opt->option == 'b' && strchr ("io", opt->arg[0]) && strchr ("df", opt->arg[1])) continue;   /* Ignore -[b][io][d|f]] */
		if (opt->option == 'j' && len == 1 && strchr ("efg", opt->arg[0])) continue;   /* Ignore -je|f|g */
		if (strchr ("bdhq", opt->option) && strchr ("io", opt->arg[0]) && (opt->arg[1] == '\0' || isdigit (opt->arg[1]))) continue;   /* Ignore -[bdhq][io][<n>]] */
		snprintf (format, GMT_LEN128, "%c%s", opt->option, opt->arg);	/* Get a local copy so we can mess with it */
		if ((c = strchr (format, ','))) c[0] = 0;	/* Chop off other formats for the initial id test */
		if (gmt_get_graphics_id (API->GMT, format) != GMT_NOTSET) {	/* Found a valid graphics format option */
			modern = 1;	/* Seems like it is, but check the rest of the formats, if there are more */
			if (c == NULL) continue;	/* Nothing else to check, go to next option */
			/* Make sure any other formats are valid, too */
			if (c) c[0] = ',';	/* Restore any comma we found */
			pos = 0;
			while (modern && gmt_strtok (format, ",", &pos, p)) {	/* Check each format to make sure each is OK */
				if (gmt_get_graphics_id (API->GMT, p) == GMT_NOTSET)	/* Oh, something wrong was given, cannot be modern */
					modern = 0;
			}
		}
		else if (gmtapi_modern_casename (format)) {  /* Most likely typo in graphics name */
			modern = GMT_NOTSET;    /* Flag this type of error */
			GMT_Report (API, GMT_MSG_ERROR, "Unrecognized graphics format %s for modern mode one-liner command\n", format);
		}
	}
	return modern;
}

GMT_LOCAL int gmtapi_check_for_modern_oneliner (struct GMTAPI_CTRL *API, const char *module, int mode, void *args) {
	/* Determine if user is attempting a modern mode one-liner plot, and if so, set run mode to GMT_MODERN.
	 * This is needed since there is not gmt begin | end sequence in this case.
	 * Also, if a user wants to get the usage message for a modern mode module then it is also a type
	 * of one-liner and thus we set to GMT_MODERN as well, but only for modern module names. */

	bool usage = false;
	int error = GMT_NOERROR, modern;
	struct GMT_OPTION *opt, *head = NULL;

	head = GMT_Create_Options (API, mode, args);	/* Get option list */
	modern = gmtapi_modern_oneliner (API, head);		/* Return true if one-liner syntax was detected */
	if (modern == GMT_NOTSET) {
		error = GMT_RUNTIME_ERROR;
		goto free_and_return;
	}
	if (API->GMT->current.setting.run_mode == GMT_MODERN) {	/* Need to check if a classic name was given or if user tried a one-liner in modern mode */
		if (modern) {	/* Yikes, someone is using one-liner within a GMT modern mode session */
			GMT_Report (API, GMT_MSG_ERROR, "Cannot run a one-liner modern command within an existing modern mode session\n");
			error = GMT_RUNTIME_ERROR;
			goto free_and_return;
		}
		if (!strncmp (module, "ps", 2U) && strncmp (module, "psconvert", 9U)) {	/* Gave classic ps* name in modern mode but not psconvert */
			char not_used[GMT_LEN32] = {""};
			const char *mod_name = gmt_current_name (module, not_used);
			GMT_Report (API, GMT_MSG_INFORMATION, "Detected a classic module name (%s) in modern mode - please use the modern mode name %s instead.\n", module, mod_name);
		}
		goto free_and_return;
	}

	if ((opt = GMT_Find_Option (API, 'V', head)))	/* Remove -V here so that we can run gmt plot -? -Vd and still get modern mode usage plus debug info */
		GMT_Delete_Option (API, opt, &head);

	if (!strcmp (module, "grdcontour") && GMT_Find_Option (API, 'N', head))	/* Special case of two module calls cannot be oneliner here */
		goto free_and_return;

	API->GMT->current.setting.use_modern_name = gmtlib_is_modern_name (API, module);

	if (API->GMT->current.setting.use_modern_name) {	/* Make some checks needed to handle synopsis and usage messages in classic vs modern mode */
		if (head == NULL) {	/* Gave none or a single argument */
			if (API->GMT->current.setting.run_mode == GMT_CLASSIC) {
				API->usage = true;	/* Modern mode name given with no args so not yet in modern mode - allow it to get usage */
				API->GMT->current.setting.run_mode = GMT_MODERN;	/* Safe here to flag it as modern since no session will be started */
			}
			return GMT_NOERROR;
		}
		if (head->next == NULL) {	/* Gave a single argument */
			if (head->option == GMT_OPT_USAGE || head->option == GMT_OPT_SYNOPSIS || (head->option == '+' && !head->arg[0])) modern = 1;
			if (modern) usage = true;
		}
	}

	if (modern) {	/* This is indeed a modern mode one-liner command */
		API->GMT->current.setting.run_mode = GMT_MODERN;
		API->usage = usage;
	}
	if (API->GMT->current.setting.run_mode == GMT_MODERN)	/* If running in modern mode we want to use modern names */
		API->GMT->current.setting.use_modern_name = true;

free_and_return:

	if (GMT_Destroy_Options (API, &head))	/* Done with these here */
		GMT_Report (API, GMT_MSG_WARNING, "Unable to free options in gmtapi_check_for_modern_oneliner?\n");
	return error;
}

/* Function to get PPID under Windows is a bit different */
#ifdef _WIN32
#include <tlhelp32.h>
GMT_LOCAL int gmtapi_winppid (int pidin) {
	/* If pidin == 0 get the PPID of current process
	   otherwise, get the PPID of pidin process
	*/
	int pid, ppid = GMT_NOTSET;
	if (pidin)
		pid = pidin;
	else
		pid = GetCurrentProcessId ();
	HANDLE h = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0);
	PROCESSENTRY32 pe = { 0 };
	pe.dwSize = sizeof (PROCESSENTRY32);

	if (Process32First(h, &pe)) {
		do {
			if (pe.th32ProcessID == (unsigned int)pid)
				ppid = pe.th32ParentProcessID;
		} while (ppid == GMT_NOTSET && Process32Next(h, &pe));
	}
	CloseHandle (h);
	return (ppid);
}
#endif

/* Safety valve to remove non-alphanumeric characters =*/
GMT_LOCAL char * gmtapi_alnum_only (struct GMTAPI_CTRL *API, char *string) {
	unsigned int k = 0, n_changed = 0;
	while (string[k]) {
		if (!isalnum (string[k])) {
			n_changed++;
			string[k] = '#';
		}
		k++;
	}
	if (n_changed)
		GMT_Report (API, GMT_MSG_DEBUG, "Cleaned GMT_SESSION_NAME to %s\n", string);
	return (string);
}

/*! . */
GMT_LOCAL char * gmtapi_get_ppid (struct GMTAPI_CTRL *API) {
	/* Return the parent process ID [i.e., shell for command line use or gmt app for API] */
	int ppid = GMT_NOTSET;
	unsigned int k = 0;
	static char *source[4] = {"GMT_SESSION_NAME", "parent", "app", "hardwired choice"};
	char *str = NULL, string[GMT_LEN8];
	if ((str = getenv ("GMT_SESSION_NAME")) != NULL) {	/* GMT_SESSION_NAME was set in the environment */
		char *tmp = strdup (str);	/* Duplicate the given string */
		GMT_Report (API, GMT_MSG_DEBUG, "Obtained GMT_SESSION_NAME from the environment: %s\n", str);
		return (gmtapi_alnum_only (API, tmp)); /* Replace any non-alphanumeric characters with # */
	}
	/* Here we just need to get the PPID and format to string */
#ifdef DEBUG_MODERN	/* To simplify debugging we set it to 1 */
	if (ppid == GMT_NOTSET) ppid = 1, k = 3;
#elif defined(WIN32)
	/* OK, the trouble is the following. On Win, if the Windows executables are run from within a bash window
	   gmtapi_get_ppid returns different values for each call, and this completely breaks the idea
	   of using the constant PPID (parent PID) to create unique file names for each session.
	   So, given that we didn't yet find a way to make this work from within MSYS (and likely Cygwin)
	   we are forcing PPID = 0 in all Windows variants unless set via GMT_SESSION_NAME. A corollary of this
	   is that Windows users running many bash windows concurrently should use GMT_SESSION_NAME in their scripts
	   to give unique values to different scripts.  */
	if ((str = getenv ("SHELL")) != NULL) {	/* GMT_SESSION_NAME was set in the environment */
		//if (ppid == GMT_NOTSET) ppid = 0, k = 3;
		ppid = gmtapi_winppid(0);	/* First time get PPID of current process */
		ppid = gmtapi_winppid(ppid);	/* Second time get PPPID of current process */
		k = 1;
	}
	else {
		if (ppid == GMT_NOTSET) ppid = gmtapi_winppid(0), k = 1;
	}
#else	/* Normal situation */
	else if (API->external)	/* Return PID of the controlling app instead for external interfaces */
		ppid = getpid (), k = 2;
	else	/* Here we are probably running from the command line and want the shell's PID */
		ppid = getppid(), k = 1; /* parent process id */
#endif
	GMT_Report (API, GMT_MSG_DEBUG, "Obtained the ppid from %s: %d\n", source[k], ppid);
	snprintf (string, GMT_LEN8, "%d", ppid);
	return (strdup (string));
}

/*! . */
GMT_LOCAL char * gmtapi_lib_tag (char *name) {
	/* Pull out the tag from a name like <tag>[.extension] */
	char *extension = NULL, *pch = NULL, *tag = NULL;
	if (!strchr (name, '.')) return NULL;	/* No file with extension given, probably just a directory due to user confusion */
	tag = strdup (name);
	extension = strrchr (tag, '.'); /* last period in name */
	if (extension) *extension = '\0'; /* remove extension */
	/* if name has the "_w32|64" suffix or any other suffix that starts with a '_', remove it. */
	pch = strrchr(tag, '_');
	if (pch) *pch = '\0';
	return (tag);
}

/*! . */
GMT_LOCAL int gmtapi_init_sharedlibs (struct GMTAPI_CTRL *API) {
	/* At the end of GMT_Create_Session we are done with processing gmt.conf.
	 * We can now determine how many shared libraries and plugins to consider, and open the core lib */
	struct GMT_CTRL *GMT = API->GMT;
	unsigned int n_custom_libs, k, e, n_alloc = GMT_TINY_CHUNK;
	char text[PATH_MAX] = {""}, plugindir[PATH_MAX] = {""}, path[PATH_MAX] = {""};
	char *libname = NULL, **list = NULL;
#ifdef WIN32
	char *extension[1] = {".dll"};
	unsigned int n_extensions = 1;
#elif  defined(__APPLE__)	/* Look for both .so and .dylib shared libs on OSX */
	char *extension[2] = {".so", ".dylib"};
	unsigned int n_extensions = 2;
#else	/* Linux, etc. only use .so */
	char *extension[1] = {".so"};
	unsigned int n_extensions = 1;
#endif

#ifdef SUPPORT_EXEC_IN_BINARY_DIR
	/* If SUPPORT_EXEC_IN_BINARY_DIR is defined we try to load plugins from the
	 * build tree */

	/* Only true, when we are running in a subdir of GMT_BINARY_DIR_SRC_DEBUG: */
	bool running_in_bindir_src = !strncmp (GMT->init.runtime_bindir, GMT_BINARY_DIR_SRC_DEBUG, strlen(GMT_BINARY_DIR_SRC_DEBUG));
#endif

	if ((API->lib = gmt_M_memory (GMT, NULL, n_alloc, struct GMT_LIBINFO)) == NULL) return 0;

	/* 1. Load the GMT core library by default [unless libgmt is used externally] */
	/* Note: To extract symbols from the currently executing process we need to load it as a special library.
	 * This is done by passing NULL under Linux and by calling GetModuleHandleEx under Windows, hence we
	 * use the dlopen_special call which is defined in gmt_sharedlibs.c.  If the gmt core and supplemental
	 * libraries are being used by 3rd party externals then no library is special and they are all opened
	 * the first time we need access. */

	API->lib[0].name = strdup ("core");
	n_custom_libs = 1;	/* Always have at least one shared gmt library */
	if (API->external) {	/* Determine the path to this library */
		if (GMT->init.runtime_libdir) {	/* Successfully determined runtime dir for shared libs */
			sprintf (path, "%s/%s", GMT->init.runtime_libdir, GMT_CORE_LIB_NAME);
			API->lib[0].path = strdup (path);
		}
		else	/* Rely on the OS to find it */
			API->lib[0].path = strdup (GMT_CORE_LIB_NAME);
	}
	else {	/* The handling of the core library is only special when gmt.c is used. */
		API->lib[0].path = strdup (GMT_CORE_LIB_NAME);
		GMT_Report (API, GMT_MSG_DEBUG, "Loading core GMT shared library: %s\n", API->lib[0].path);
		if ((API->lib[0].handle = dlopen_special (API->lib[0].path)) == NULL) {
			GMT_Report (API, GMT_MSG_ERROR, "Failure while loading core GMT shared library (%s): %s\n", API->lib[0].path, dlerror());
			return -GMT_RUNTIME_ERROR;
		}
		dlerror (); /* Clear any existing error */
	}
	GMT_Report (API, GMT_MSG_DEBUG, "Shared Library # 0 (core). Path = %s\n", API->lib[0].path);

	/* 3. Add any plugins installed in <installdir>/lib/gmt/plugins */

	if (GMT->init.runtime_libdir) {	/* Successfully determined runtime dir for shared libs */
#ifdef SUPPORT_EXEC_IN_BINARY_DIR
		if ( running_in_bindir_src && access (GMT_BINARY_DIR_SRC_DEBUG "/plugins", R_OK|X_OK) == 0 ) {
			/* Running in build dir: search plugins in build-dir/src/plugins */
			strncat (plugindir, GMT_BINARY_DIR_SRC_DEBUG "/plugins", PATH_MAX-1);
#ifdef XCODER
			strcat (plugindir, "/Debug");	/* The Xcode plugin subdir path for Debug */
#endif
		}
		else
#endif
		{
		/* Set full path to the core library */
		snprintf (plugindir, PATH_MAX, "%s/%s", GMT->init.runtime_libdir, GMT_CORE_LIB_NAME);
		if (!GMT->init.runtime_library) GMT->init.runtime_library = strdup (plugindir);

#ifdef WIN32
			snprintf (plugindir, PATH_MAX, "%s/gmt_plugins", GMT->init.runtime_libdir);	/* Generate the Win standard plugins path */
#else
			snprintf (plugindir, PATH_MAX, "%s/gmt" GMT_INSTALL_NAME_SUFFIX "/plugins", GMT->init.runtime_libdir);	/* Generate the *nix standard plugins path */
#endif
		}
		if (!GMT->init.runtime_plugindir) GMT->init.runtime_plugindir = strdup (plugindir);
		if (!GMT->init.runtime_library) {	/* Last call, probably in Xcode */
			sprintf (path, "%s/%s", GMT->init.runtime_libdir, GMT_CORE_LIB_NAME);
			GMT->init.runtime_library = strdup (path);
		}
		GMT_Report (API, GMT_MSG_DEBUG, "Loading GMT plugins from: %s\n", plugindir);
		for (e = 0; e < n_extensions; e++) {	/* Handle case of more than one allowed shared library extension */
			if ((list = gmt_get_dir_list (GMT, plugindir, extension[e]))) {	/* Add these files to the libs */
				for (k = 0; list[k] && strncmp (list[k], GMT_SUPPL_LIB_NAME, strlen(GMT_SUPPL_LIB_NAME)); k++);	/* Look for official supplements */
				if (list[k] && k) gmt_M_charp_swap (list[0], list[k]);	/* Put official supplements first if not first already */
				k = 0;
				while (list[k]) {
					snprintf (path, PATH_MAX, "%s/%s", plugindir, list[k]);
					if (access (path, R_OK))
						GMT_Report (API, GMT_MSG_ERROR, "Shared Library %s cannot be found or read!\n", path);
					else {
						API->lib[n_custom_libs].name = gmtapi_lib_tag (list[k]);
						API->lib[n_custom_libs].path = strdup (path);
						GMT_Report (API, GMT_MSG_DEBUG, "Shared Library # %d (%s). Path = %s\n", n_custom_libs, API->lib[n_custom_libs].name, API->lib[n_custom_libs].path);
						n_custom_libs++;			/* Add up entries found */
						if (n_custom_libs == n_alloc) {		/* Allocate more memory for list */
							n_alloc <<= 1;
							if ((API->lib = gmt_M_memory (GMT, API->lib, n_alloc, struct GMT_LIBINFO)) == NULL) return 0;
						}
					}
					++k;
				}
				gmt_free_dir_list (GMT, &list);
			}
		}
	}

	/* 4. Add any custom GMT libraries to the list of libraries/plugins to consider, if specified.
	      We will find when trying to open if any of these are actually available. */

	if (GMT->session.CUSTOM_LIBS) {	/* We specified custom shared libraries */
		k = (unsigned int)strlen (GMT->session.CUSTOM_LIBS) - 1;	/* Index of last char in CUSTOM_LIBS */
		if (GMT->session.CUSTOM_LIBS[k] == '/' || GMT->session.CUSTOM_LIBS[k] == '\\') {	/* We gave CUSTOM_LIBS as a subdirectory, add all files found inside it to shared libs list */
			strcpy (plugindir, GMT->session.CUSTOM_LIBS);
			plugindir[k] = '\0';	/* Chop off trailing slash */
			GMT_Report (API, GMT_MSG_DEBUG, "Loading custom GMT plugins from: %s\n", plugindir);
			for (e = 0; e < n_extensions; e++) {
				if ((list = gmt_get_dir_list (GMT, plugindir, extension[e]))) {	/* Add these to the libs */
					k = 0;
					while (list[k]) {
						snprintf (path, PATH_MAX, "%s/%s", plugindir, list[k]);
						if (access (path, R_OK)) {
							GMT_Report (API, GMT_MSG_ERROR, "Shared Library %s cannot be found or read!\n", path);
							GMT_Report (API, GMT_MSG_ERROR, "Check that your GMT_CUSTOM_LIBS (in %s, perhaps) is correct\n", GMT_SETTINGS_FILE);
						}
						else if ((API->lib[n_custom_libs].name = gmtapi_lib_tag (list[k]))) {
							API->lib[n_custom_libs].path = strdup (path);
							GMT_Report (API, GMT_MSG_DEBUG, "Shared Library # %d (%s). Path = \n", n_custom_libs, API->lib[n_custom_libs].name, API->lib[n_custom_libs].path);
							n_custom_libs++;		/* Add up entries found */
							if (n_custom_libs == n_alloc) {	/* Allocate more memory for list */
								n_alloc <<= 1;
								if ((API->lib = gmt_M_memory (GMT, API->lib, n_alloc, struct GMT_LIBINFO)) == NULL) return 0;
							}
						}
						else
							GMT_Report (API, GMT_MSG_ERROR, "Shared Library %s has no extension! Ignored\n", list[k]);
						++k;
					}
					gmt_free_dir_list (GMT, &list);
				}
			}
		}
		else {	/* Just a list with one or more comma-separated library paths */
			unsigned int pos = 0, ok;
			while (gmt_strtok (GMT->session.CUSTOM_LIBS, ",", &pos, text)) {
				for (e = ok = 0; e < n_extensions; e++) {
					if (strstr (text, extension[e])) ok++;
				}
				if (ok != 1) {	/* Maybe did not append proper shared library extension */
					GMT_Report (API, GMT_MSG_ERROR, "Shared Library %s lacks proper extension\n", text);
					GMT_Report (API, GMT_MSG_ERROR, "Check that your GMT_CUSTOM_LIBS (in %s, perhaps) is correct; if a directory it must end in /\n", GMT_SETTINGS_FILE);
					continue;
				}
				libname = strdup (basename (text));		/* Last component from the pathname */
				if (access (text, R_OK)) {
					GMT_Report (API, GMT_MSG_ERROR, "Shared Library %s cannot be found or read!\n", text);
					GMT_Report (API, GMT_MSG_ERROR, "Check that your GMT_CUSTOM_LIBS (in %s, perhaps) is correct; if a directory it must end in /\n", GMT_SETTINGS_FILE);
				}
				else if ((API->lib[n_custom_libs].name = gmtapi_lib_tag (libname))) {
					API->lib[n_custom_libs].path = strdup (text);
					GMT_Report (API, GMT_MSG_DEBUG, "Shared Library # %d (%s). Path = \n", n_custom_libs, API->lib[n_custom_libs].name, API->lib[n_custom_libs].path);
					n_custom_libs++;		/* Add up entries found */
					if (n_custom_libs == n_alloc) {	/* Allocate more memory for list */
						n_alloc <<= 1;
						if ((API->lib = gmt_M_memory (GMT, API->lib, n_alloc, struct GMT_LIBINFO)) == NULL) return 0;
					}
				}
				else
					GMT_Report (API, GMT_MSG_ERROR, "Shared Library %s has no extension! Ignored\n", text);
				gmt_M_str_free (libname);
			}
		}
	}

	API->n_shared_libs = n_custom_libs;	/* Update total number of shared libraries loaded */
	API->lib = gmt_M_memory (GMT, API->lib, API->n_shared_libs, struct GMT_LIBINFO);

	return (GMT_NOERROR);
}

/*! Free items in the shared lib list except the first core library */
GMT_LOCAL void gmtapi_free_sharedlibs (struct GMTAPI_CTRL *API) {
	unsigned int k;
	for (k = 0; k < API->n_shared_libs; k++) {
		if (k > 0 && API->lib[k].handle && dlclose (API->lib[k].handle))
			GMT_Report (API, GMT_MSG_ERROR, "Failure while closing GMT %s shared library: %s\n", API->lib[k].name, dlerror());
		gmt_M_str_free (API->lib[k].name);
		gmt_M_str_free (API->lib[k].path);
	}
	gmt_M_free (API->GMT, API->lib);
	API->n_shared_libs = 0;
}

/* The basic gmtread|write module meat; used by external APIs only, such as the GMT/MATLAB, Julia, PyGMT API */

/*! Duplicate ifile on ofile.  Calling program is responsible to ensure correct args are passed */
int gmt_copy (struct GMTAPI_CTRL *API, enum GMT_enum_family family, unsigned int direction, char *ifile, char *ofile) {
	double *wesn = NULL;	/* For grid, image, cube subsets */
	struct GMT_DATASET *D = NULL;
	struct GMT_PALETTE *C = NULL;
	struct GMT_GRID *G = NULL;
	struct GMT_CUBE *U = NULL;
	struct GMT_POSTSCRIPT *P = NULL;
	struct GMT_IMAGE *I = NULL;
	struct GMT_MATRIX *M = NULL;
	struct GMT_VECTOR *V = NULL;
	struct GMT_CTRL *GMT = NULL;
	struct GMT_DATASET_HIDDEN *DH = NULL;

	if (API == NULL) return_error (API, GMT_NOT_A_SESSION);
	API->error = GMT_NOERROR;
	GMT_Report (API, GMT_MSG_INFORMATION, "Read %s from %s and write to %s\n", GMT_family[family], ifile, ofile);
	GMT = API->GMT;

	switch (family) {
		case GMT_IS_DATASET:
			if ((D = GMT_Read_Data (API, GMT_IS_DATASET, GMT_IS_FILE, GMT_IS_POINT, GMT_READ_NORMAL, NULL, ifile, NULL)) == NULL)
				return (API->error);
			DH = gmt_get_DD_hidden (D);
			if (GMT_Write_Data (API, GMT_IS_DATASET, GMT_IS_FILE, D->geometry, DH->io_mode | GMT_IO_RESET, NULL, ofile, D) != GMT_NOERROR)
				return (API->error);
			break;
		case GMT_IS_GRID:
			wesn = (direction == GMT_IN && GMT->common.R.active[RSET]) ? GMT->common.R.wesn : NULL;
			if ((G = GMT_Read_Data (API, GMT_IS_GRID, GMT_IS_FILE, GMT_IS_SURFACE, GMT_READ_NORMAL, wesn, ifile, NULL)) == NULL)
				return (API->error);
			wesn = (direction == GMT_OUT && GMT->common.R.active[RSET]) ? GMT->common.R.wesn : NULL;
			if (GMT_Write_Data (API, GMT_IS_GRID, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_AND_DATA | GMT_IO_RESET, wesn, ofile, G) != GMT_NOERROR)
				return (API->error);
			break;
		case GMT_IS_IMAGE:
			wesn = (direction == GMT_IN && GMT->common.R.active[RSET]) ? GMT->common.R.wesn : NULL;
			if ((I = GMT_Read_Data (API, GMT_IS_IMAGE, GMT_IS_FILE, GMT_IS_SURFACE, GMT_READ_NORMAL, wesn, ifile, NULL)) == NULL)
				return (API->error);
			wesn = (direction == GMT_OUT && GMT->common.R.active[RSET]) ? GMT->common.R.wesn : NULL;
			if (GMT_Write_Data (API, GMT_IS_IMAGE, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_AND_DATA | GMT_IO_RESET, wesn, ofile, I) != GMT_NOERROR)
				return (API->error);
			break;
		case GMT_IS_CUBE:
			wesn = (direction == GMT_IN && GMT->common.R.active[RSET]) ? GMT->common.R.wesn : NULL;
			if ((U = GMT_Read_Data (API, GMT_IS_CUBE, GMT_IS_FILE, GMT_IS_VOLUME, GMT_READ_NORMAL, wesn, ifile, NULL)) == NULL)
				return (API->error);
			wesn = (direction == GMT_OUT && GMT->common.R.active[RSET]) ? GMT->common.R.wesn : NULL;
			if (GMT_Write_Data (API, GMT_IS_CUBE, GMT_IS_FILE, GMT_IS_VOLUME, GMT_CONTAINER_AND_DATA | GMT_IO_RESET, wesn, ofile, U) != GMT_NOERROR)
				return (API->error);
			break;
		case GMT_IS_PALETTE:
			if ((C = GMT_Read_Data (API, GMT_IS_PALETTE, GMT_IS_FILE, GMT_IS_NONE, GMT_READ_NORMAL, NULL, ifile, NULL)) == NULL)
				return (API->error);
			if (GMT_Write_Data (API, GMT_IS_PALETTE, GMT_IS_FILE, GMT_IS_NONE, C->mode | GMT_IO_RESET, NULL, ofile, C) != GMT_NOERROR)
				return (API->error);
			break;
		case GMT_IS_POSTSCRIPT:
			if ((P = GMT_Read_Data (API, GMT_IS_POSTSCRIPT, GMT_IS_FILE, GMT_IS_NONE, GMT_READ_NORMAL, NULL, ifile, NULL)) == NULL)
				return (API->error);
			if (GMT_Write_Data (API, GMT_IS_POSTSCRIPT, GMT_IS_FILE, GMT_IS_NONE, GMT_IO_RESET, NULL, ofile, P) != GMT_NOERROR)
				return (API->error);
			break;
		case GMT_IS_MATRIX:
			if ((M = GMT_Read_Data (API, GMT_IS_MATRIX, GMT_IS_FILE, GMT_IS_NONE, GMT_READ_NORMAL, NULL, ifile, NULL)) == NULL)
				return (API->error);
			if (GMT_Write_Data (API, GMT_IS_MATRIX, GMT_IS_FILE, GMT_IS_NONE, GMT_IO_RESET, NULL, ofile, M) != GMT_NOERROR)
				return (API->error);
			break;
		case GMT_IS_VECTOR:
			if ((V = GMT_Read_Data (API, GMT_IS_VECTOR, GMT_IS_FILE, GMT_IS_NONE, GMT_READ_NORMAL, NULL, ifile, NULL)) == NULL)
				return (API->error);
			if (GMT_Write_Data (API, GMT_IS_VECTOR, GMT_IS_FILE, GMT_IS_NONE, GMT_IO_RESET, NULL, ofile, V) != GMT_NOERROR)
				return (API->error);
			break;
		case GMT_IS_COORD:
			GMT_Report (API, GMT_MSG_ERROR, "No external read or write support yet for object %s\n", GMT_family[family]);
			return_error(API, GMT_NOT_A_VALID_FAMILY);
			break;
		default:
			GMT_Report (API, GMT_MSG_ERROR, "Internal error, family = %d\n", family);
			return_error(API, GMT_NOT_A_VALID_FAMILY);
			break;
	}

	return (API->error);
}

/* Note: Many/all of these do not need to check if API == NULL since they are called from functions that already did it. */
/* Private static functions used by this library only.  These are not accessed outside this file. */

GMT_LOCAL unsigned int gmtapi_pick_out_col_number (struct GMT_CTRL *GMT, unsigned int col) {
	/* Return the next column to be reported on output */
	unsigned int col_pos;
	if (GMT->common.o.col.select)	/* -o has selected specific columns */
		col_pos = GMT->current.io.col[GMT_OUT][col].col;	/* Which data column to pick */
	else if (GMT->current.setting.io_lonlat_toggle[GMT_OUT] && col < GMT_Z)	/* Worry about -: for lon,lat */
		col_pos = 1 - col;	/* Write lat/lon instead of lon/lat */
	else
		col_pos = col;	/* Just goto the specified column */
	return (col_pos);
}

/*! . */
GMT_LOCAL double gmtapi_select_dataset_value (struct GMT_CTRL *GMT, struct GMT_DATASEGMENT *S, unsigned int row, unsigned int col) {
	/* For binary output of a data table segment via external matrix, we must select correct col entry and possibly make adjustments */
	double val;
	unsigned int col_pos = gmtapi_pick_out_col_number (GMT, col);
	val = (col_pos >= S->n_columns) ? GMT->session.d_NaN : S->data[col_pos][row];	/* If we request a column beyond length of array, return NaN */
	if (GMT->common.d.active[GMT_OUT] && gmt_M_is_dnan (val)) val = GMT->common.d.nan_proxy[GMT_OUT];	/* Write this value instead of NaNs */
	if (gmt_M_is_type (GMT, GMT_OUT, col_pos, GMT_IS_LON)) gmt_lon_range_adjust (GMT->current.io.geo.range, &val);	/* Set longitude range as required */
	return (val);
}

/*! . */
GMT_LOCAL double gmtapi_select_record_value (struct GMT_CTRL *GMT, double *record, unsigned int col, unsigned int n_colums) {
	/* For binary output of data record via external matrix, we must select correct col entry and possibly make adjustments */
	double val;
	unsigned int col_pos = gmtapi_pick_out_col_number (GMT, col);
	val = (col_pos >= n_colums) ? GMT->session.d_NaN : record[col_pos];	/* If we request a column beyond length of array, return NaN */
	if (GMT->common.d.active[GMT_OUT] && gmt_M_is_dnan (val)) val = GMT->common.d.nan_proxy[GMT_OUT];	/* Write this value instead of NaNs */
	if (gmt_M_is_type (GMT, GMT_OUT, col_pos, GMT_IS_LON)) gmt_lon_range_adjust (GMT->current.io.geo.range, &val);	/* Set longitude range as required */
	return (val);
}

unsigned int gmtlib_pick_in_col_number (struct GMT_CTRL *GMT, unsigned int col, unsigned int *col_pos_in) {
	/* If -i is not active then we simply return col_pos_out = col_pos_in = col.  Done!
	 *
	 * With -i, we must return correct value for both the in and out column positions based on the info in the structure.
	 * Here, col is a loop variable that goes from 0 to number of desired output columns.  However, that col number
	 * is likely neither the input(col) we wish to get nor the output(col) where we want to place it in the output array.
	 * Instead, we set these two different columns via col_pos_in and col_pos_out so that output(col_pos_out) = input(col_pos_in).
	 * col_pos_out will uniquely touch the same values as col loops over, but col_pos_in may be outside that range
	 * and even have repeated column values (e.g., due to -i0,1,2,2+s5). */
	unsigned int col_pos_out;
	if (GMT->common.i.col.select) {	/* -i has selected some columns */
		*col_pos_in = GMT->current.io.col[GMT_IN][col].col;   /* Which data column to take the value from input */
		col_pos_out = GMT->current.io.col[GMT_IN][col].order; /* Which data column to place it on output */
	}
	else
		col_pos_out = *col_pos_in = col;	/* They are all the same */
	return (col_pos_out);
}

/*! . */
GMT_LOCAL double gmtapi_get_record_value (struct GMT_CTRL *GMT, double *record, uint64_t col, uint64_t n_colums, unsigned int *col_pos_out) {
	/* For binary input of data record via external matrix, we must select correct col entry and possibly make adjustments */
	double val;
	unsigned int col_pos_in;
	*col_pos_out = gmtlib_pick_in_col_number (GMT, (unsigned int)col, &col_pos_in);
	val = (*col_pos_out >= n_colums) ? GMT->session.d_NaN : gmt_M_convert_col (GMT->current.io.col[GMT_IN][col], record[col_pos_in]);	/* If we request a column beyond length of array, return NaN */
	if (GMT->common.d.active[GMT_IN] && gmt_M_is_dnan (val)) val = GMT->common.d.nan_proxy[GMT_IN];	/* Write this value instead of NaNs */
	if (gmt_M_is_type (GMT, GMT_IN, *col_pos_out, GMT_IS_LON)) gmt_lon_range_adjust (GMT->current.io.geo.range, &val);	/* Set longitude range as required */
	return (val);
}

/*! . */
GMT_LOCAL int gmtapi_bin_input_memory (struct GMT_CTRL *GMT, uint64_t n, uint64_t n_use) {
	/* Read function which gets one record from the memory reference.
 	 * The current data record has already been read from wherever and is available in
 	 * GMT->current.io.curr_rec so that is where we operate from */
	unsigned int status;
	gmt_M_unused(n);

	GMT->current.io.status = GMT_IO_DATA_RECORD;	/* Default status we expect, but this may change below */
	GMT->current.io.rec_no++;			/* One more input record read */
	status = gmtlib_process_binary_input (GMT, n_use);	/* Check for segment headers */
	if (status == 1) return (GMTAPI_GOT_SEGHEADER);	/* A segment header was found and we are done here */
	if (gmtlib_gap_detected (GMT)) { gmtlib_set_gap (GMT); return (GMTAPI_GOT_SEGGAP); }	/* Gap forced a segment header to be issued and we get out */
	GMT->current.io.data_record_number_in_set[GMT_IN]++;	/* Actually got a valid data record */
	return (GMT_NOERROR);
}

/*! . */
GMT_LOCAL char * gmtapi_tictoc_string (struct GMTAPI_CTRL *API, unsigned int mode) {
	/* Optionally craft a leading timestamp.
	 * mode = 0:	No time stamp
	 * mode = 1:	Abs time stamp formatted via GMT_TIME_STAMP
	 * mode = 2:	Report elapsed time since last reset.
	 * mode = 4:	Reset elapsed time to 0, no time stamp.
	 * mode = 6:	Reset elapsed time and report it as well.
	 */
	time_t right_now;
	clock_t toc = 0;
	unsigned int H, M, S, milli;
	double T;
	static char stamp[GMT_LEN256] = {""};

	if (mode == 0) return NULL;		/* No timestamp requested */
	if (mode > 1) toc = clock ();		/* Elapsed time requested */
	if (mode & 4) API->GMT->current.time.tic = toc;	/* Reset previous timestamp to now */

	switch (mode) {	/* Form output timestamp string */
		case 1:	/* Absolute time stamp */
			right_now = time ((time_t *)0);
			strftime (stamp, sizeof(stamp), API->GMT->current.setting.format_time_stamp, localtime (&right_now));
			break;
		case 2:	/* Elapsed time stamps */
		case 6:
			T = (double)(toc - (clock_t)API->GMT->current.time.tic);	/* Elapsed time in ticks */
			T /= CLOCKS_PER_SEC;	/* Elapsed time in seconds */
			H = urint (floor (T * GMT_SEC2HR));	/* Hours */
			T -= H * GMT_HR2SEC_I;
			M = urint (floor (T * GMT_SEC2MIN));	/* Minutes */
			T -= M * GMT_MIN2SEC_I;
			S = urint (floor (T));			/* Seconds */
			T -= S;
			milli = urint (T*1000.0);	/* Residual milliseconds */
			snprintf (stamp, GMT_LEN256, "Elapsed time %2.2u:%2.2u:%2.2u.%3.3u", H, M, S, milli);
			break;
		default: break;
	}
	return (stamp);
}

/*! . */
GMT_LOCAL unsigned int gmtapi_add_existing (struct GMTAPI_CTRL *API, enum GMT_enum_family family, unsigned int geometry, unsigned int direction, int *first_ID) {
	/* In this mode, we find all registered resources of matching family,geometry,direction that are unused and turn variable selected to true. */
	unsigned int i, n, this_geo;

	*first_ID = GMT_NOTSET;	/* Not found yet */
	for (i = n = 0; i < API->n_objects; i++) {
		if (!API->object[i]) continue;	/* A freed object, skip */
		if (API->object[i]->direction != (enum GMT_enum_std)direction) continue; /* Wrong direction */
		if (API->object[i]->status    != GMT_IS_UNUSED) continue;  /* Already used */
		if (family != API->object[i]->family) continue;		   /* Wrong data type */
		//if (API->object[i]->geometry  != (enum GMT_enum_geometry)geometry) continue;  /* Wrong geometry */
		/* More careful check for geometry that allows the PLP (1+2+4) be match by any of those using logical and */
		this_geo = (unsigned int)API->object[i]->geometry;
		if (!(this_geo & geometry)) continue;  /* Wrong geometry */
		n++;	/* Found one that satisfied all our requirements */
		if (*first_ID == GMT_NOTSET) *first_ID = API->object[i]->ID;	/* Get the ID of the first that passed the test */
		API->object[i]->selected = true;	/* Make this an active object for the coming i/o operation */
	}
	return (n);
}

/* These functions are support functions for the API function GMT_Encode_Options:
 *	gmtapi_key_to_family
 *	gmtapi_process_keys
 *	gmtapi_get_key
 *	gmtapi_found_marker
 *
 * The "keys" refer to the contents of the THIS_MODULE_KEYS set in each module.
 */

/* Indices into the keys triple codes */
#define K_OPT			0
#define K_FAMILY		1
#define K_DIR			2
#define K_EQUAL			3
#define K_MODIFIER		4
#define GMT_FILE_NONE		0
#define GMT_FILE_EXPLICIT	1
#define GMT_FILE_IMPLICIT	2

#define K_PRIMARY		0
#define K_SECONDARY		1

#define K_OR			0
#define K_AND			1

#define API_PRIMARY_INPUT	'{'
#define API_PRIMARY_OUTPUT	'}'
#define API_SECONDARY_INPUT	'('
#define API_SECONDARY_OUTPUT	')'

GMT_LOCAL int gmtapi_key_to_family (void *API, char *key, int *family, int *geometry) {
	/* Assign direction, family, and geometry based on the key.
	   Note: No Vector or Matrix here since those always masquerade as DATASET in modules. */

	switch (key[K_FAMILY]) {	/* 2nd char contains the data type (family) code */
		case 'G':
			*family = GMT_IS_GRID;
			*geometry = GMT_IS_SURFACE;
			break;
		case 'P':
			*family = GMT_IS_DATASET;
			*geometry = GMT_IS_POLY;
			break;
		case 'L':
			*family = GMT_IS_DATASET;
			*geometry = GMT_IS_LINE;
			break;
		case 'D':
			*family = GMT_IS_DATASET;
			*geometry = GMT_IS_POINT;
			break;
		case 'C':
			*family = GMT_IS_PALETTE;
			*geometry = GMT_IS_NONE;
			break;
		case 'I':
			*family = GMT_IS_IMAGE;
			*geometry = GMT_IS_SURFACE;
			break;
		case 'U':
			*family = GMT_IS_CUBE;
			*geometry = GMT_IS_VOLUME;
			break;
		case 'X':
			*family = GMT_IS_POSTSCRIPT;
			*geometry = GMT_IS_NONE;
			break;
		case '-':
			*family = GMT_IS_NONE;
			*geometry = GMT_IS_NONE;
			break;
		default:
			GMT_Report (API, GMT_MSG_ERROR, "gmtapi_key_to_family: Key family (%c) not recognized\n", key[K_FAMILY]);
			return GMT_NOTSET;
			break;
	}

	/* Third key character contains the in/out code */
	return ((key[K_DIR] == API_SECONDARY_OUTPUT || key[K_DIR] == API_PRIMARY_OUTPUT) ? GMT_OUT : GMT_IN);	/* Return the direction of the i/o */
}

GMT_LOCAL char * gmtapi_prepare_keys (struct GMTAPI_CTRL *API, const char *string) {
	char *tmp = NULL, *c = NULL, *string_;
	string_ = strdup(string);	/* Have to make a copy because "string" is const and the c[0] = '\0' make it crash on Win for non-debug builds */
	if ((c = strchr (string_, '@'))) {	/* Split KEYS: classic@modern, must get the relevant half */
		c[0] = '\0';	/* Chop into two */
		tmp = (API->GMT->current.setting.run_mode == GMT_MODERN) ? strdup (&c[1]) : strdup (string_);
	}
	else	/* Only one set of KEYS */
		tmp = strdup (string);		/* Get a working copy of string */

	free(string_);
	return (tmp);
}

GMT_LOCAL char ** gmtapi_process_keys (void *V_API, const char *string, char type, struct GMT_OPTION *head, int *n_to_add, unsigned int *n_items) {
	/* Turn the comma-separated list of 3-char codes in string into an array of such codes.
 	 * In the process, replace any ?-types with the selected type if type is not 0.
	 * We return the array of strings and its number (n_items). */
	size_t len, k, kk, n;
	int o_id = GMT_NOTSET, family = GMT_NOTSET, geometry = GMT_NOTSET;
	bool change_type = false;
	char **s = NULL, *next = NULL, *tmp = NULL, magic = 0, revised[GMT_LEN128] = {""};
	struct GMT_OPTION *opt = NULL;
	struct GMTAPI_CTRL *API = gmtapi_get_api_ptr (V_API);

	*n_items = 0;	/* No keys yet */

	for (k = 0; k < GMT_N_FAMILIES; k++) n_to_add[k] = GMT_NOTSET;	/* Initially no input counts */
	if (!string) return NULL;	/* Got NULL, so just give up */
	tmp = gmtapi_prepare_keys (API, string);	/* Get the correct KEYS if there are separate ones for Classic and Modern mode */
	len = strlen (tmp);			/* Get the length of this item */
	if (len == 0) { 			/* Got no characters, so give up */
		gmt_M_str_free (tmp);
		return NULL;
	}
	/* Replace unknown types (marked as ?) in tmp with selected type give by input variable "type" */
	if (type) {	/* Got a nonzero type which we use to update any unspecified types */
		for (k = 0; k < strlen (tmp); k++)
			if (tmp[k] == '?') tmp[k] = type;
	}
	/* Count the number of items (start n at 1 since there are one less comma than items) */
	for (k = 0, n = 1; k < len; k++)
		if (tmp[k] == ',') n++;
	/* Allocate and populate the character array, then return it and n_items */
	s = (char **) calloc (n, sizeof (char *));
	k = 0;
	while ((next = strsep (&tmp, ",")) != NULL) {	/* Get each comma-separated key */
		if (strlen (next) < 3) {
			GMT_Report (API, GMT_MSG_WARNING,
			            "gmtapi_process_keys: Key %s contains less than 3 characters\n", next);
			continue;
		}
		if (strchr (next, '!')) {	/* Type did not get determined in GMT_Encode_Options so key is skipped */
			GMT_Report (API, GMT_MSG_DEBUG,
			            "gmtapi_process_keys: key %s contains type = ! so we skip it\n", next);
			n--;
			continue;
		}
		s[k] = strdup (next);
		if (next[K_DIR] == API_PRIMARY_OUTPUT) {	/* Identified primary output key */
			if (o_id >= 0)	/* Already had a primary output key */
				GMT_Report (API, GMT_MSG_WARNING,
				            "gmtapi_process_keys: Keys %s contain more than one primary output key\n", tmp);
			else
				o_id = (int)k;
		}
		k++;
	}

	/* While processing the array we also determine the key # for the primary output (if there is one) */
	for (k = 0; k < n; k++) {	/* Check for presence of any of the magic X,Y,Z keys */
		if (s[k][K_OPT] == '-') {	/* Key letter X missing: Means that option -Y, if given, changes the type of input|output */
			/* Must first determine which data type we are dealing with via -Y<type> */
			if ((opt = GMT_Find_Option (API, s[k][K_FAMILY], head))) {	/* A -Y<type> option was passed to the module */
				type = (char)toupper (opt->arg[0]);	/* Find type and replace any ? in keys with this type in uppercase (CDGIP) in gmtapi_process_keys below */
				if (type == 'T')	/* There is no longer a T type but we may honor T from GMT5.  The gmtread|write module will decide depending on compatibility level set */
					type = 'D';	/* opt->arg will still be 't' and is handled in the modules */
				if (!strchr ("CDGIPU", type)) {
					GMT_Report (API, GMT_MSG_ERROR, "gmtapi_process_keys: No or bad data type given to read|write (%c)\n", type);
					return_null (NULL, GMT_NOT_A_VALID_TYPE);	/* Unknown type */
				}
				if (type == 'P') type = 'X';	/* We use X for PostScript internally since P may stand for polygon... */
				for (kk = 0; kk < n; kk++) {	/* Do the substitution for all keys that matches ? */
					if (s[kk][K_FAMILY] == '?' && strchr ("-({", s[kk][K_DIR])) s[kk][K_FAMILY] = type;	/* Want input to handle this type of data */
					if (s[kk][K_FAMILY] == '?' && strchr ("-)}", s[kk][K_DIR])) s[kk][K_FAMILY] = type;	/* Want output to handle this type of data */
				}
			}
			else
				GMT_Report (API, GMT_MSG_WARNING,
				            "gmtapi_process_keys: Required runtime type-getting option (-%c) was not given\n", s[k][K_FAMILY]);
			gmt_M_str_free (s[k]);		/* Free the inactive key that has now served its purpose */
		}
		else if (s[k][K_FAMILY] == '-') {	/* Key letter Y missing: Means that -X, if given, changes primary input|output set by -Z to secondary (i.e., not required) */
			/* However, if +<mod> is appended then the primary input setting is left as is */
			if ((opt = GMT_Find_Option (API, s[k][K_OPT], head))) {	/* Got the option that removes the requirement of an input or output dataset */
				if (!(s[k][3] == '+' && strstr (opt->arg, &s[k][3]))) {	/* Got the option and no modifier to turn it off */
					for (kk = 0; kk < n; kk++) {	/* Change all primary input|output flags to secondary, depending on Z */
						if (!s[kk]) continue;		/* A previously processed/freed key */
						if (s[kk][K_OPT] != s[k][K_DIR]) continue;		/* Not the "-Z "option */
						if (s[kk][K_DIR] == API_PRIMARY_INPUT) s[kk][K_DIR] = API_SECONDARY_INPUT;		/* No longer an implicit input */
						else if (s[kk][K_DIR] == API_PRIMARY_OUTPUT) s[kk][K_DIR] = API_SECONDARY_OUTPUT;	/* No longer an implicit output */
					}
				}
			}
			gmt_M_str_free (s[k]);		/* Free the inactive key that has served its purpose */
		}
		else if (!strchr ("{}()-", s[k][K_DIR])) {	/* Key letter Z not in {|(|}|)|-: which means that option -Z, if given, changes the type of primary output to Y */
			/* E.g, pscoast has >DM and this turns >X} to >D} only when -M is used.  Also, modifiers may be involved.
			   e.g, gmtspatial : New key ">TN+r" means if -N+r is given then set >T}.  Just giving -N will not trigger the change.
			   e.g., pscoast ">TE+w-rR" means if -E is given with modifier +w _and_ one of +r or +R is then set to >T}.
			   If X is not - then we will find the other KEY with X and select that as the one to change; this could
			   be used to change the primary INPUT type.  For instance, grdimage expects grid input (<G{+) but with
			   magic sequence <ID we change <G{+ to <I{+.  */
			magic = s[k][K_DIR];
			if ((opt = GMT_Find_Option (API, magic, head))) {	/* Got the magic option that changes output type */
				char modifier[3] = {'+', '?', 0};	/* We will replace ? with an actual modifier */
				size_t this_k;
				if (o_id == GMT_NOTSET)
					GMT_Report (API, GMT_MSG_WARNING, "gmtapi_process_keys: No primary output identified but magic Z key present\n");
				/* Check if modifier(s) were given also and that one of them were selected */
				if (strlen (s[k]) > 3) {	/* Not enough to just find option, must examine the modifiers */
					/* Full syntax: XYZ+abc...-def...: We do the substitution of output type to Y only if
					 * 1. -Z is given
					 * 2. -Z contains ALL the modifiers +a, +b, +c, ... (if any "+"-list is given)
					 * 3. -Z contains AT LEAST ONE of the modifiers +d, +e, +f, ... (if any "-"=list is given)
					 * At least one item from 2 or 3 must be given.
					 */
					unsigned int kase = 0, count[2] = {0, 0}, given[2] = {0, 0};
					change_type = false;
					for (kk = 3; s[k][kk]; kk++) {	/* Examine characters in the modifier string */
						if (strchr ("-+", s[k][kk])) {	/* Start of all (+) versus at least one (-) */
							kase = (s[k][kk] == '-') ? K_OR : K_AND;	/* Set kase and go to next letter */
							continue;
						}
						count[kase]++;	/* How many AND and how many OR modifiers (depending on kase) */
						modifier[1] = s[k][kk];	/* Set current modifier */
						if (strstr (opt->arg, modifier)) given[kase]++;	/* Match found with given option */
					}
					/* Only change the key if we found all the AND modifiers and at least one of the OR modifiers (if any were given) */
					if ((count[K_OR] == 0 || (count[K_OR] && given[K_OR])) && count[K_AND] == given[K_AND]) change_type = true;
				}
				else	/* true since we found the option and no modifiers were given */
					change_type = true;
				if (s[k][K_OPT] != '-') {	/* Find the relevant option to change [primary output key] */
					char match = (s[k][K_OPT] == '<') ? API_PRIMARY_INPUT : API_PRIMARY_OUTPUT;
					for (kk = 0, this_k = n; kk < n; kk++) {
						if (kk == k || s[kk] == NULL) continue;
						if (s[kk][K_OPT] == s[k][K_OPT] && s[kk][K_DIR] == match)
							this_k = kk;
					}
					if (this_k == n) this_k = o_id;
				}
				else	/* Select the primary output key */
					this_k = o_id;
				if (change_type) {
					if (strchr ("{<", s[this_k][K_DIR])) {
						int new_family = 0, old_family = 0;
						(void)gmtapi_key_to_family (API, s[k], &new_family, &geometry);
						(void)gmtapi_key_to_family (API, s[this_k], &old_family, &geometry);
						if (new_family != old_family) gmt_M_int_swap (n_to_add[new_family], n_to_add[old_family]);	/* Must swap our counts */
					}
					s[this_k][K_FAMILY] = s[k][K_FAMILY];	/* Required input/output now implies this data type */
					s[this_k][K_OPT]    = s[k][K_OPT];	/* Required input/output now implies this option */
				}
			}
			gmt_M_str_free (s[k]);		/* Free the inactive key that has served its purpose */
		}
		else if (s[k][K_DIR] == API_PRIMARY_INPUT) {	/* Non-magic key: This one identified a primary input key */
			(void)gmtapi_key_to_family (API, s[k], &family, &geometry);	/* Get datatype, and geometry, then set how many are requested */
			if (family != GMT_NOTSET) {	/* Safeguard: If family not found then we don't want to crash below... */
				if (s[k][K_DIR+1])	/* Gave an argument: This is either a number (a specific count) or + (1 or more) */
					n_to_add[family] = (s[k][K_DIR+1] == '+') ? GMTAPI_UNLIMITED : atoi (&s[k][K_DIR+1]);
				else
					n_to_add[family] = (family == GMT_IS_DATASET) ? GMTAPI_UNLIMITED : 1;
			}
		}
	}
	/* Shuffle away any NULL entries as a result of magic key processing */
	for (k = kk = 0; k < n; k++) {
		if (s[k]) {	/* Must keep this guy */
			if (k > kk) s[kk] = s[k];
			kk++;
		}
	}
	n = kk;	/* May have lost some NULLs.  Make a revised string for debug output */
	for (k = 0; k < n; k++) {
		if (k) strcat (revised, ",");
		strncat (revised, s[k], GMT_LEN128-1);
	}
	if (revised[0]) GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_process_keys: Revised keys string is %s\n", revised);
	*n_items = (unsigned int)n;	/* Total number of remaining keys for this module */
	gmt_M_str_free (tmp);
	return s;	/* The array of remaining keys */
}

GMT_LOCAL int gmtapi_get_key (void *API, char option, char *keys[], int n_keys) {
	/* Returns the position in the keys array that matches this option, or GMT_NOTSET if not found */
	int k;
	if (n_keys && keys == NULL)
		GMT_Report (API, GMT_MSG_WARNING, "gmtapi_get_key: Keys array is NULL but n_keys = %d\n", n_keys);
	for (k = 0; keys && k < n_keys; k++) if (keys[k][K_OPT] == option) return (k);
	return (GMT_NOTSET);
}

GMT_LOCAL bool gmtapi_found_marker (char *text) {
	/* A single question mark and nothing else indicates a memory item marker */
	if (text[0] == '?' && text[1] == '\0') return true;
	return false;	/* Not found */
}

GMT_LOCAL unsigned int gmtapi_determine_dimension (struct GMTAPI_CTRL *API, char *text) {
	/* Examine greenspline's -R option to learn the dimensionality of the domain (1, 2, or 3) */
	unsigned int n_slashes = 0;
	size_t k;
	const size_t s_length = strlen(text);

	/* First catch the simple -R? which means a grid is passed by the API, hence dimension is 2 */
	if (text[0] == '?' && text[1] == '\0') return 2;	/* A marker means a grid only, so done */
	for (k = 0; k < s_length; k++)
		if (text[k] == '/') n_slashes++;			/* Count slashes just in case */
	if ((text[0] == 'g' || text[0] == 'd') && (text[1] == '\0' || text[1] == '/')) {	/* Got -Rg or -Rd, possibly with trailing /zmin/zmax */
		if (text[1] == '\0') return 2;	/* Got -Rg or -Rd and no more */
		if (n_slashes == 2) return 3;	/* Got -Rg/zmin/zmax or -Rd/zmin/zmax */
		GMT_Report (API, GMT_MSG_ERROR, "Option -R: Give 2, 4, or 6 coordinates, a gridfile, or use -Rd|g[/zmin/zmax]\n");
		return 0;
	}
	if (!gmt_access (API->GMT, text, R_OK))	/* Gave a readable file, we assume it is a grid since that is all that is allowed */
		return 2;
	/* Only get here if the above cases did not trip */
	if (!(n_slashes == 1 || n_slashes == 3 || n_slashes == 5)) {
		GMT_Report (API, GMT_MSG_ERROR, "Option -R: Give 2, 4, or 6 coordinates\n");
		return 0;
	}
	return ((n_slashes + 1) / 2);	/* Turns 1,3,5 into dimensions 1,2,3 */
}

/*! . */
GMT_LOCAL void * gmtapi_retrieve_data (void *V_API, int object_ID) {
	/* Function to return pointer to the container for a registered data set.
	 * Typically used when we wish a module to "write" its results to a memory
	 * location that we wish to access from the calling program.  The procedure
	 * is to use GMT_Register_IO with GMT_REF|COPY|READONLY and GMT_OUT but use
	 * NULL as the source/destination.  Data are "written" by GMT allocating a
	 * output container and updating the objects->resource pointer to this container.
	 * gmtapi_retrieve_data simply returns that pointer given the registered ID.
	 */

	int item;
	struct GMTAPI_CTRL *API = NULL;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;

	if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION);

	/* Determine the item in the object list that matches this object_ID */
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;
	if ((item = gmtlib_validate_id (API, GMT_NOTSET, object_ID, GMT_NOTSET, GMT_NOTSET)) == GMT_NOTSET) {
		return_null (API, API->error);
	}
	S_obj = API->object[item];	/* Short hand */
	/* Make sure the resource is present */
	if (S_obj->resource == NULL) {
		return_null (API, GMT_PTR_IS_NULL);
	}

#ifdef DEBUG
	//gmtapi_list_objects (API, "gmtapi_retrieve_data");
#endif
	return (S_obj->resource);	/* Return pointer to the resource container */
}

/*! . */
GMT_LOCAL int gmtapi_begin_io (struct GMTAPI_CTRL *API, unsigned int direction) {
	/* Initializes the i/o mechanism for either input or output (depends on direction).
	 * gmtapi_begin_io must be called before any bulk data i/o is allowed.
	 * direction:	Either GMT_IN or GMT_OUT.
	 * Returns:	false if successful, true if error.
	 */

	struct GMT_CTRL *GMT = NULL;
	if (API == NULL) return_error (API, GMT_NOT_A_SESSION);
	if (!(direction == GMT_IN || direction == GMT_OUT)) return_error (API, GMT_NOT_A_VALID_DIRECTION);
	API->error = GMT_NOERROR;
	if (!API->registered[direction])
		GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_begin_io: No %s resources registered\n", GMT_direction[direction]);
	/* Passed basic sanity checks */
	GMT = API->GMT;
	API->io_mode[direction] = GMTAPI_BY_SET;
	API->io_enabled[direction] = true;	/* OK to access resources */
	GMT->current.io.ogr = GMT_OGR_UNKNOWN;
	GMT->current.io.variable_in_columns = false;
	GMT->current.io.need_previous = (GMT->common.g.active || GMT->current.io.skip_duplicates);
	GMT->current.io.segment_header[0] = GMT->current.io.curr_text[0] = 0;
	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_begin_io: %s resource access is now enabled [container]\n", GMT_direction[direction]);

	return (GMT_NOERROR);	/* No error encountered */
}

/* Mapping of internal [row][col] indices to a single 1-D index.
 * Internally, row and col both starts at 0.  These will be accessed
 * via pointers to these functions, hence they are not macros.
 * They apply to plain GMT_MATRIX items, NOT grids/images with pads.
 */

/*! . */
GMT_LOCAL uint64_t gmtapi_2d_to_index_c_normal (uint64_t row, uint64_t col, uint64_t dim) {
	/* Maps (row,col) to 1-D index for C normal row-major grid */
	return ((row * dim) + col);	/* Normal scanline grid */
}

/*! . */
GMT_LOCAL uint64_t gmtapi_2d_to_index_c_cplx_real (uint64_t row, uint64_t col, uint64_t dim) {
	/* Maps (row,col) to 1-D index for C complex row-major grid, real component */
	return (2ULL*(row * dim) + col);	/* Complex scanline grid, real(1) component */
}

/*! . */
GMT_LOCAL uint64_t gmtapi_2d_to_index_c_cplx_imag (uint64_t row, uint64_t col, uint64_t dim) {
	/* Maps (row,col) to 1-D index for C complex row-major grid, imaginary component */
	return (2ULL*(row * dim) + col + 1ULL);	/* Complex grid, imag(2) component */
}

/*! . */
GMT_LOCAL uint64_t gmtapi_2d_to_index_f_normal (uint64_t row, uint64_t col, uint64_t dim) {
	/* Maps (row,col) to 1-D index for FORTRAN column-major grid */
	return ((col * dim) + row);
}

/*! . */
GMT_LOCAL uint64_t gmtapi_2d_to_index_f_cplx_real (uint64_t row, uint64_t col, uint64_t dim) {
	/* Maps (row,col) to 1-D index for FORTRAN complex column-major grid, real component */
	return (2ULL*(col * dim) + row);	/* Complex grid, real(1) */
}

/*! . */
GMT_LOCAL uint64_t gmtapi_2d_to_index_f_cplx_imag (uint64_t row, uint64_t col, uint64_t dim) {
	/* Maps (row,col) to 1-D index for FORTRAN complex column-major grid, imaginary component  */
	return (2ULL*(col * dim) + row + 1ULL);	/* Complex grid, imag(2) component */
}

/*! . */
GMT_LOCAL p_func_uint64_t gmtapi_get_2d_to_index (struct GMTAPI_CTRL *API, enum GMT_enum_fmt shape, unsigned int mode) {
	/* Return pointer to the required 2D-index function above for MATRIX.  Here
	 * shape is either GMT_IS_ROW_FORMAT (C) or GMT_IS_COL_FORMAT (FORTRAN);
	 * mode is either 0 (regular grid), GMT_GRID_IS_COMPLEX_REAL (complex real) or GMT_GRID_IS_COMPLEX_IMAG (complex imag)
	 */
	p_func_uint64_t p = NULL;

	switch (mode & GMT_GRID_IS_COMPLEX_MASK) {
		case GMT_GRID_IS_REAL:
			p = (shape == GMT_IS_ROW_FORMAT) ? gmtapi_2d_to_index_c_normal : gmtapi_2d_to_index_f_normal;
			break;
		case GMT_GRID_IS_COMPLEX_REAL:
			p = (shape == GMT_IS_ROW_FORMAT) ? gmtapi_2d_to_index_c_cplx_real : gmtapi_2d_to_index_f_cplx_real;
			break;
		case GMT_GRID_IS_COMPLEX_IMAG:
			p = (shape == GMT_IS_ROW_FORMAT) ? gmtapi_2d_to_index_c_cplx_imag : gmtapi_2d_to_index_f_cplx_imag;
			break;
		default:
			GMT_Report (API, GMT_MSG_ERROR, "gmtapi_get_2d_to_index: Illegal mode passed\n");
			return (NULL);
	}
	return (p);
}

#if 0	/* Unused at the present time (or redundant) so removed from compilation for now */
GMT_LOCAL void gmtapi_index_to_2d_c (int *row, int *col, size_t index, int dim, int mode) {
	/* Maps 1-D index to (row,col) for C */
	if (mode) index /= 2;
	*col = (index % dim);
	*row = (index / dim);
}

GMT_LOCAL void gmtapi_index_to_2d_f (int *row, int *col, size_t index, int dim, int mode) {
	/* Maps 1-D index to (row,col) for FORTRAN */
	if (mode) index /= 2;
	*col = (index / dim);
	*row = (index % dim);
}
#endif

/* Mapping of internal [row][col][layer] indices to a single 1-D index for images.
 * Internally, row and col starts at 0.  These will be accessed
 * via pointers to these functions, hence they are not macros.
 * Here we must consider the optional padding of the image.
 */

GMT_LOCAL inline uint64_t gmtapi_get_index_from_TRB (struct GMT_GRID_HEADER *h, uint64_t row, uint64_t col, uint64_t layer) {
	/* Get linear index of an array with a band-interleaved layout RRR...RGGG...GBBB...B */
	return (h->pad[XLO] + col) + ((row + h->pad[YHI]) * h->mx) + (layer * h->size);
}

GMT_LOCAL inline uint64_t gmtapi_get_index_from_TRP (struct GMT_GRID_HEADER *h, uint64_t row, uint64_t col, uint64_t layer) {
	/* Get linear index of an array with a pixel-interleaved layout RGBRGBRGB...*/
	return ((h->pad[XLO] + col) * h->n_bands) + layer + ((row + h->pad[YHI]) * h->mx * h->n_bands);
}

GMT_LOCAL inline uint64_t gmtapi_get_index_from_TRL (struct GMT_GRID_HEADER *h, uint64_t row, uint64_t col, uint64_t layer) {
	/* Get linear index of an array with a line-interleaved layout R...RG..GB...BR...RG...GB...B...*/
	return (h->pad[XLO] + col) + (layer * h->mx) + ((row + h->pad[YHI]) * h->mx * h->n_bands);
}

GMT_LOCAL inline uint64_t gmtapi_get_index_from_TRS (struct GMT_GRID_HEADER *h, uint64_t row, uint64_t col, uint64_t layer) {
	/* Get linear index of an default GMT grid */
	gmt_M_unused(layer);
	return (gmt_M_ijp (h, row, col));
}

GMT_LOCAL inline uint64_t gmtapi_get_index_from_TRR (struct GMT_GRID_HEADER *h, uint64_t row, uint64_t col, uint64_t layer) {
	/* Get linear index to the real component of an default complex GMT grid */
	gmt_M_unused(layer);
	return (2ULL*gmt_M_ijp (h, row, col));
}

GMT_LOCAL inline uint64_t gmtapi_get_index_from_TRI (struct GMT_GRID_HEADER *h, uint64_t row, uint64_t col, uint64_t layer) {
	/* Get linear index to the imag component of an default complex GMT grid */
	gmt_M_unused(layer);
	return (2ULL*gmt_M_ijp (h, row, col)+1ULL);
}

/*! . */
GMT_LOCAL unsigned int gmtapi_decode_layout (struct GMTAPI_CTRL *API, const char *code, enum GMT_enum_family *family) {
	/* Convert the 3-letter grid/image layout code to a single integer mode.
	 * Defaults are TRS for grids and TRB for images. */
	unsigned int bits = 0;	/* Default value */
	*family = GMT_IS_IMAGE;	/* Default value, may be changed later */
	switch (code[0]) {	/* Char 1: The Y direction */
		case 'T':	break;		 /* Top-to-bottom [Default] */
		case 'B':	bits = 1; break; /* Bottom-to-top */
		default:
			GMT_Report (API, GMT_MSG_ERROR, "Illegal code [%c] for y-direction grid/image layout.  Must be T or B\n", code[0]);
			break;
	}
	switch (code[1]) {	/* Char 2: The storage mode (rows vs columns) */
		case 'R':	break;		  /* rows, i.e., scanlines [Default] */
		case 'C':	bits |= 2; break; /* columns (e.g., FORTRAN) */
		default:
			GMT_Report (API, GMT_MSG_ERROR, "Illegal code [%c] for grid/image storage mode.  Must be R or C\n", code[1]);
			break;
	}
	switch (code[2]) {	/* Char 3: Grids: Single, complex-Real, complex-Imag.  Images: band interleaving mode B|L|P */
		case 'S':	*family = GMT_IS_GRID; break;	/* Single-valued grid [Default] */
		case 'R':	bits |= 4; *family = GMT_IS_GRID; break;	/* Real component of complex grid */
		case 'I':	bits |= 8; *family = GMT_IS_GRID; break;	/* Imaginary component of complex grid */
		case 'B':	break;			/* r/g/b separated into three bands (layers) */
		case 'L':	bits |= 4; break;	/* r/g/b separated into three lines */
		case 'P':	bits |= 8; break;	/* r/g/b separated into three values per pixel */
		default:
			GMT_Report (API, GMT_MSG_ERROR, "Illegal code [%c] for type of grid or image layout.  Must be SRI (grids) or BLP (images)\n", code[1]);
			break;
	}
	return (bits);
}

GMT_LOCAL int gmtapi_init_grdheader (struct GMT_CTRL *GMT, unsigned int direction, struct GMT_GRID_HEADER *header, struct GMT_OPTION *options,
                             uint64_t dim[], double wesn[], double inc[], unsigned int registration, unsigned int mode) {
	/* Convenient way of setting a header struct wesn, inc, and registration, then compute dimensions, etc. */
	double wesn_dup[4] = {0.0, 0.0, 0.0, 0.0}, inc_dup[2] = {0.0, 0.0};
	unsigned int n_layers = 1;
	static char *regtype[2] = {"gridline", "pixel"};
	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (header);
	gmt_M_unused(mode);

	if (registration & GMT_GRID_DEFAULT_REG) registration |= GMT->common.R.registration;	/* Set the default registration */
	registration = (registration & 1);	/* Knock off any GMT_GRID_DEFAULT_REG bit */
	if (dim && (wesn == NULL || (gmt_M_is_zero (wesn[XLO]) && gmt_M_is_zero (wesn[XHI]) && gmt_M_is_zero (wesn[YLO]) && gmt_M_is_zero (wesn[YHI]))) && (inc == NULL || (gmt_M_is_zero (inc[GMT_X]) && gmt_M_is_zero (inc[GMT_Y])))) {	/* Gave dimension instead, set range and inc (1/1) while considering registration */
		gmt_M_memset (wesn_dup, 4, double);
		wesn_dup[XHI] = (double)(dim[GMT_X]);
		wesn_dup[YHI] = (double)(dim[GMT_Y]);
		inc_dup[GMT_X] = inc_dup[GMT_Y] = 1.0;
		if (registration == GMT_GRID_NODE_REG) wesn_dup[XHI] -= 1.0, wesn_dup[YHI] -= 1.0;
		if (dim[GMT_Z] > 1) n_layers = (unsigned int)dim[GMT_Z];
		GMT_Report (GMT->parent, GMT_MSG_DEBUG, "Grid/Image dimensions imply w/e/s/n = 0/%g/0/%g, inc = 1/1, %s registration, n_layers = %u\n",
			wesn_dup[XHI], wesn_dup[YHI], regtype[registration], n_layers);
	}
	else {	/* Must infer dimension etc from wesn, inc, registration */
		if (wesn == NULL) {	/* Must select -R setting */
			if (!GMT->common.R.active[RSET]) {
				GMT_Report (GMT->parent, GMT_MSG_ERROR, "No w/e/s/n given and no -R in effect.  Cannot initialize new grid\n");
				return GMT_ARG_IS_NULL;
			}
		}
		else	/* In case user is passing header->wesn etc we must save them first as gmt_grd_init will clobber them */
			gmt_M_memcpy (wesn_dup, wesn, 4, double);
		if (inc == NULL) {	/* Must select -I setting */
			if (!GMT->common.R.active[ISET]) {
				GMT_Report (GMT->parent, GMT_MSG_ERROR, "No increment given and no -I in effect.  Cannot initialize new grid\n");
				return GMT_ARG_IS_NULL;
			}
		}
		else	/* In case user is passing header->inc etc we must save them first as gmt_grd_init will clobber them */
			gmt_M_memcpy (inc_dup, inc, 2, double);
		if (dim && dim[GMT_Z] > 1) n_layers = (unsigned int)dim[GMT_Z];
		if (inc != NULL) {
			GMT_Report (GMT->parent, GMT_MSG_DEBUG, "Grid/Image dimensions imply w/e/s/n = %g/%g/%g/%g, inc = %g/%g, %s registration, n_layers = %u\n",
			            wesn_dup[XLO], wesn_dup[XHI], wesn_dup[YLO], wesn_dup[YHI], inc[GMT_X], inc[GMT_Y], regtype[registration], n_layers);
		}
	}
	/* Clobber header and reset */
	gmt_grd_init (GMT, header, options, false);	/* This is for new grids only so update is always false */
	if (dim == NULL && wesn == NULL)
		gmt_M_memcpy (header->wesn, GMT->common.R.wesn, 4, double);
	else
		gmt_M_memcpy (header->wesn, wesn_dup, 4, double);
	if (dim == NULL && inc == NULL)
		gmt_M_memcpy (header->inc, GMT->common.R.inc, 2, double);
	else
		gmt_M_memcpy (header->inc, inc_dup, 2, double);
	header->registration = registration;
	/* Copy row-order from R.row_order, if set */
	if (GMT->common.R.row_order) HH->row_order = GMT->common.R.row_order;
	/* Mode may contain complex mode information */
	header->complex_mode = (mode & GMT_GRID_IS_COMPLEX_MASK);
	HH->grdtype = gmtlib_get_grdtype (GMT, direction, header);
	gmt_RI_prepare (GMT, header);	/* Ensure -R -I consistency and set n_columns, n_rows in case of meter units etc. */
	gmt_M_err_pass (GMT, gmt_grd_RI_verify (GMT, header, 1), "");
	gmt_M_grd_setpad (GMT, header, GMT->current.io.pad);	/* Assign default GMT pad */
	if (dim) header->n_bands = n_layers;
	gmt_set_grddim (GMT, header);	/* Set all dimensions before returning */
	gmtlib_grd_get_units (GMT, header);
	gmt_BC_init (GMT, header);	/* Initialize grid interpolation and boundary condition parameters */
	HH->grdtype = gmtlib_get_grdtype (GMT, direction, header);	/* Set grid type (i.e. periodicity for global grids) */
#ifdef DOUBLE_PRECISION_GRID
	header->type = GMT_GRID_IS_ND;
#else
	header->type = GMT_GRID_IS_NF;
#endif
	return (GMT_NOERROR);
}

/*! . */
GMT_LOCAL int gmtapi_init_grid (struct GMTAPI_CTRL *API, struct GMT_OPTION *opt, uint64_t dim[], double *range, double *inc, int registration, unsigned int mode, unsigned int direction, struct GMT_GRID *G) {
	if (direction == GMT_OUT) return (GMT_NOERROR);	/* OK for creating a blank container for output */
	return (gmtapi_init_grdheader (API->GMT, direction, G->header, opt, dim, range, inc, registration, mode));
}

/*! . */
GMT_LOCAL int gmtapi_init_image (struct GMTAPI_CTRL *API, struct GMT_OPTION *opt, uint64_t dim[], double *range, double *inc, int registration, unsigned int mode, unsigned int direction, struct GMT_IMAGE *I) {
	if (direction == GMT_OUT) return (GMT_NOERROR);	/* OK for creating blank container for output */
	return (gmtapi_init_grdheader (API->GMT, direction, I->header, opt, dim, range, inc, registration, mode));
}

/*! . */
GMT_LOCAL int gmtapi_init_matrix (struct GMTAPI_CTRL *API, uint64_t dim[], double *range, double *inc, int registration, unsigned int mode, unsigned int direction, struct GMT_MATRIX *M) {
	/* If range = inc = NULL then the dimensions are set via dim: ncols, nrow, nlayers, type.
	 * else, ncols,nrows is set via range and inc and registration. dim, if not null, sets dim[2] = nlayers [1] and dim[3] = type [double]
	 */
	int error;
	unsigned int dims = (M->n_layers > 1) ? 3 : 2;
	size_t size = 0;
	struct GMT_MATRIX_HIDDEN *MH = gmt_get_M_hidden (M);

	GMT_Report (API, GMT_MSG_DEBUG, "Initializing a matrix for handing external %s [mode = %u]\n", GMT_direction[direction], mode);
	if (direction == GMT_OUT) {	/* OK to create blank container for output unless dims or range/inc is also set */
		if (dim && dim[GMTAPI_DIM_ROW]) {	/* Dimensions are given when we are using external memory for the matrix and must specify the dimensions specifically */
			M->n_rows    = dim[GMTAPI_DIM_ROW];
			M->n_columns = dim[GMTAPI_DIM_COL];
			M->dim = (M->shape == GMT_IS_ROW_FORMAT) ? M->n_columns : M->n_rows;	/* Matrix layout order */
		}
		else if (range) {	/* Giving dimensions via range and inc when using external memory */
			if (!inc || (inc[GMT_X] == 0.0 && inc[GMT_Y] == 0.0)) return (GMT_VALUE_NOT_SET);
			gmt_M_memcpy (M->range, range, 2 * dims, double);
			gmt_M_memcpy (M->inc, inc, dims, double);
			M->n_rows    = gmt_M_get_n (API->GMT, range[YLO], range[YHI], inc[GMT_Y], registration);
			M->n_columns = gmt_M_get_n (API->GMT, range[XLO], range[XHI], inc[GMT_X], registration);
			M->dim = (M->shape == GMT_IS_ROW_FORMAT) ? M->n_columns : M->n_rows;	/* Matrix layout order */
		}
		return (GMT_NOERROR);
	}
	if (full_region (range) && (dims == 2 || (!range || range[ZLO] == range[ZHI]))) {	/* Not an equidistant vector arrangement, use dim */
		double dummy_range[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};	/* Flag vector as such */
		if (dim == NULL) return (GMT_VALUE_NOT_SET);
		gmt_M_memcpy (M->range, dummy_range, 2 * dims, double);
		gmt_M_memcpy (M->inc, dummy_range, dims, double);
		M->n_rows    = dim[GMTAPI_DIM_ROW];
		M->n_columns = dim[GMTAPI_DIM_COL];
	}
	else {	/* Was apparently given valid range and inc */
		if (!inc || (inc[GMT_X] == 0.0 && inc[GMT_Y] == 0.0)) return (GMT_VALUE_NOT_SET);
		gmt_M_memcpy (M->range, range, 2 * dims, double);
		gmt_M_memcpy (M->inc, inc, dims, double);
		M->n_rows    = gmt_M_get_n (API->GMT, range[YLO], range[YHI], inc[GMT_Y], registration);
		M->n_columns = gmt_M_get_n (API->GMT, range[XLO], range[XHI], inc[GMT_X], registration);
	}
	M->type = (dim == NULL) ? API->GMT->current.setting.export_type : dim[3];	/* Use selected data type for export or default to GMT setting */
	M->dim = (M->shape == GMT_IS_ROW_FORMAT) ? M->n_columns : M->n_rows;
	M->registration = registration;
	size = M->n_rows * M->n_columns * ((size_t)M->n_layers);	/* Size of the initial matrix allocation (number of elements) */
	MH->grdtype = gmtlib_get_matrixtype (API->GMT, direction, M);
	if ((mode & GMT_CONTAINER_ONLY) == 0) {	/* Must allocate data memory */
		if (size) {	/* Must allocate data matrix and possibly string array */
			if ((error = gmtlib_alloc_univector (API->GMT, &(M->data), M->type, size)) != GMT_NOERROR)
				return (error);
			if (mode & GMT_WITH_STRINGS) {	/* Must allocate text pointer array */
				if ((M->text = gmt_M_memory (API->GMT, NULL, M->n_rows, char *)) == NULL)
					return (GMT_MEMORY_ERROR);
			}
			MH->alloc_mode = GMT_ALLOC_INTERNALLY;
			MH->alloc_level = API->GMT->hidden.func_level;	/* Must be freed at this level. */
		}
	}
	return (GMT_NOERROR);
}

/*! . */
GMT_LOCAL uint64_t gmtapi_vector_nrows (uint64_t dim[], double *range, double *inc, int registration, unsigned int dir) {
	if (dim && dim[GMTAPI_DIM_ROW]) return dim[GMTAPI_DIM_ROW];	/* Gave the dimension directly */
	if (dir == GMT_IN && (!inc || inc[GMT_X] == 0.0)) return ((uint64_t)GMT_NOTSET);
	if (dir == GMT_IN && (!range || (range[XLO] == 0.0 && range[XHI] == 0.0))) return ((uint64_t)GMT_NOTSET);
	if (range && inc) return (gmt_M_get_n (API->GMT, range[XLO], range[XHI], inc[GMT_X], registration));
	return (0);	/* When dir == GMT_OUT */
}

/*! . */
GMT_LOCAL int64_t gmtapi_vector_ncols (uint64_t dim[], unsigned int dir) {
	if (dim) return (int64_t) dim[GMTAPI_DIM_COL];	/* Gave the dimension directly */
	if (dir == GMT_OUT) return (0);		/* Not set for output to be allocated later */
	return (GMT_NOTSET);	/* When dir == GMT_IN and we fail since we need to know */
}

/*! . */
GMT_LOCAL int gmtapi_init_vector (struct GMTAPI_CTRL *API, uint64_t dim[], double *range, double *inc, int registration, unsigned int mode, unsigned int direction, struct GMT_VECTOR *V) {
	/* If range = inc = NULL then add dimensions via dim: ncols, nrow, type.
	 * else, ncols,nrows is set via range and inc and registration. dim[2], if not null, sets type [double]
	 */
	int error;
	uint64_t col;
	struct GMT_VECTOR_HIDDEN *HV = gmt_get_V_hidden (V);

	GMT_Report (API, GMT_MSG_DEBUG, "Initializing a vector for handing external %s\n", GMT_direction[direction]);
	if (direction == GMT_OUT) {	/* OK for creating blank container for output, but sometimes there are dimensions */
		if (dim && dim[GMTAPI_DIM_ROW] && V->n_columns)
			V->n_rows = dim[GMTAPI_DIM_ROW];	/* Set n_rows in case when vectors will be hook on from external memory */
		else if (range && V->n_columns)	/* Giving dimensions via range and inc when using external memory */
			V->n_rows = gmtapi_vector_nrows (dim, range, inc, registration, direction);
		return (GMT_NOERROR);
	}
	else if (V->n_columns == 0)
		return (GMT_VALUE_NOT_SET);	/* Must know the number of columns to do this */
	if ((range && inc == NULL) || (range == NULL && inc)) {
		GMT_Report (API, GMT_MSG_ERROR, "Passed one of range, inc as NULL\n");
		return (GMT_VALUE_NOT_SET);
	}
	if ((range == NULL && inc == NULL) || (range[XLO] == range[XHI] && inc[GMT_X] == 0.0)) {	/* Not an equidistant vector arrangement, use dim */
		double dummy_range[2] = {0.0, 0.0};	/* Flag vector as such */
		V->n_rows = dim[GMTAPI_DIM_ROW];		/* If so, n_rows is passed via dim[GMTAPI_DIM_ROW], unless it is GMT_OUT when it is zero */
		gmt_M_memcpy (V->range, dummy_range, 2, double);
	}
	else {	/* Equidistant vector defined by dimension or range/inc */
		int64_t n = gmtapi_vector_nrows (dim, range, inc, registration, direction);
		if (n == GMT_NOTSET) return (GMT_VALUE_NOT_SET);
		V->n_rows = (uint64_t)n;
		gmt_M_memcpy (V->range, range, 2, double);
	}
	for (col = 0; col < V->n_columns; col++)	/* Set the same export data type for all vectors (or default to double) */
		V->type[col] = (dim == NULL) ? API->GMT->current.setting.export_type : dim[GMT_Z];
	if ((mode & GMT_CONTAINER_ONLY) == 0) {	/* Must allocate space */
		struct GMT_VECTOR_HIDDEN *VH = gmt_get_V_hidden (V);
		if (V->n_rows) {	/* Must allocate vector space and possibly strings */
			if ((error = gmtlib_alloc_vectors (API->GMT, V, V->n_rows)) != GMT_NOERROR)
				return (error);
			if (mode & GMT_WITH_STRINGS) {	/* Must allocate text pointer array */
				if ((V->text = gmt_M_memory (API->GMT, NULL, V->n_rows, char *)) == NULL)
					return (GMT_MEMORY_ERROR);
				VH->alloc_mode_text = GMT_ALLOC_INTERNALLY;
			}
		}
	}
	if (gmt_M_is_geographic (API->GMT, direction)) HV->geographic = 1;
	return (GMT_NOERROR);
}

/*! . */
GMT_LOCAL double * gmtapi_matrix_coord (struct GMTAPI_CTRL *API, int dim, struct GMT_MATRIX *M) {
	/* Allocate and compute coordinates along one dimension of a matrix */
	double *coord = NULL, off;
	unsigned int min, max;
	uint64_t k, n;

	if (M->n_layers <= 1 && dim == GMT_Z) return (NULL);	/* No z-dimension */
	n = (dim == GMT_X) ? M->n_columns : ((dim == GMT_Y) ? M->n_rows : M->n_layers);
	min = 2*dim, max = 2*dim + 1;	/* Indices into the min/max triplets in range */
	if ((coord = gmt_M_memory (API->GMT, NULL, n, double)) == NULL) return NULL;
	off = 0.5 * M->registration;
	/* Using gmt_M_col_to_x even though we might be computing latitudes, for instance */
	for (k = 0; k < n; k++) coord[k] = gmt_M_col_to_x (API->GMT, k, M->range[min], M->range[max], M->inc[dim], off, n);
	return (coord);
}

/*! . */
GMT_LOCAL double * gmtapi_vector_coord (struct GMTAPI_CTRL *API, int dim, struct GMT_VECTOR *V) {
	/* Allocate and compute coordinates for a vector, if equidistantly defined */
	unsigned int k;
	double *coord = NULL, off, inc;
	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_vector_coord called: dim = %d\n", dim);
	if (V->range[0] == 0.0 && V->range[1] == 0.0) return (NULL);	/* Not an equidistant vector */
	if ((coord = gmt_M_memory (API->GMT, NULL, V->n_rows, double)) == NULL) return NULL;
	off = 0.5 * V->registration;
	inc = gmt_M_get_inc (API->GMT, V->range[0], V->range[1], V->n_rows, V->registration);
	/* Using gmt_M_col_to_x even though we might be computing latitudes, for instance */
	for (k = 0; k < V->n_rows; k++) coord[k] = gmt_M_col_to_x (API->GMT, k, V->range[0], V->range[1], inc, off, V->n_rows);
	return (coord);
}

/*! . */
GMT_LOCAL void gmtapi_grdheader_to_matrixinfo (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *h, struct GMT_MATRIX *M_obj) {
	/* Packs the necessary items of the grid header into the matrix parameters */
	struct GMT_MATRIX_HIDDEN *MH = gmt_get_M_hidden (M_obj);
	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (h);
	gmt_M_unused(GMT);

	M_obj->n_columns = h->n_columns;
	M_obj->n_rows = h->n_rows;
	M_obj->registration = h->registration;
	gmt_M_memcpy (M_obj->range, h->wesn, 4, double);
	gmt_M_memcpy (M_obj->inc, h->inc, 2, double);
	MH->grdtype = HH->grdtype;	/* Pass whatever we know about being geographic or not */
}

/*! . */
GMT_LOCAL void gmtapi_matrixinfo_to_grdheader (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *h, struct GMT_MATRIX *M_obj) {
	/* Unpacks the necessary items into the grid header from the matrix parameters */
	struct GMT_MATRIX_HIDDEN *MH = gmt_get_M_hidden (M_obj);
	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (h);
	gmt_M_unused(GMT);

	h->n_columns = (unsigned int)M_obj->n_columns;
	h->n_rows = (unsigned int)M_obj->n_rows;
	h->registration = M_obj->registration;
	if (M_obj->range[XLO] == M_obj->range[XHI] && M_obj->range[YLO] == M_obj->range[YHI]) {	/* No range data given */
		h->wesn[XHI] = h->n_columns - 1.0;
		h->wesn[YHI] = h->n_rows - 1.0;
		h->inc[GMT_X] = h->inc[GMT_Y] = 1.0;
	}
	else {	/* Use what we got */
		gmt_M_memcpy (h->wesn, M_obj->range, 4, double);
		gmt_M_memcpy (h->inc, M_obj->inc, 2, double);
	}
	/* External matrices have no padding but the internal grid will */
	/* Compute xy_off  */
	h->xy_off = (h->registration == GMT_GRID_NODE_REG) ? 0.0 : 0.5;
	HH->grdtype = MH->grdtype;	/* Pass whatever we know about being geographic or not */
	gmt_set_grddim (GMT, h);
}

/*! . */
GMT_LOCAL bool gmtapi_adjust_grdpadding (struct GMT_GRID_HEADER *h, unsigned int *pad) {
	/* Compares current grid pad status to output pad requested.  If we need
	 * to adjust a pad we return true here, otherwise false. */
	unsigned int side;

	for (side = 0; side < 4; side++) if (h->pad[side] != pad[side]) return (true);
	return (false);
}

/*! . */
struct GMT_GRID_HEADER * gmt_get_header (struct GMT_CTRL *GMT) {
	struct GMT_GRID_HEADER *h = gmt_M_memory (GMT, NULL, 1, struct GMT_GRID_HEADER);
	if (h == NULL) return NULL;
	if ((h->hidden = gmt_M_memory (GMT, NULL, 1, struct GMT_GRID_HEADER_HIDDEN)) == NULL) return NULL;
	return (h);
}

/*! . */
GMT_LOCAL size_t gmtapi_set_grdarray_size (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *h, unsigned int mode, double *wesn) {
	/* Determines size of grid given grid spacing and grid domain in h.
 	 * However, if wesn is given and not empty we compute size using the sub-region instead.
 	 * Finally, the current pad is used when calculating the grid size.
	 * NOTE: This function leaves h unchanged by testing on a temporary header. */
	struct GMT_GRID_HEADER *h_tmp = NULL;
	size_t size;

	/* Must duplicate header and possibly reset wesn, then set pad and recalculate all dims */
	h_tmp = gmt_get_header (GMT);
	gmt_copy_gridheader (GMT, h_tmp, h);
	h_tmp->complex_mode = (mode & GMT_GRID_IS_COMPLEX_MASK);	/* Set the mode-to-be so that if complex the size is doubled */

	if (!full_region (wesn)) {
		gmt_M_memcpy (h_tmp->wesn, wesn, 4, double);    /* Use wesn instead of header info */
		gmt_adjust_loose_wesn (GMT, wesn, h);		/* Subset requested; make sure wesn matches header spacing */
		gmt_M_memcpy(h_tmp->wesn, wesn, 4, double);	/* And update the eventually adjusted wesn */
	}
	gmt_M_grd_setpad (GMT, h_tmp, GMT->current.io.pad);	/* Use the system pad setting by default */
	gmt_set_grddim (GMT, h_tmp);				/* Computes all integer parameters */
	size = h_tmp->size;					/* This is the size needed to hold grid + padding */
	gmt_M_free (GMT, h_tmp->hidden);
	gmt_M_free (GMT, h_tmp);
	return (size);
}

/*! . */
GMT_LOCAL int gmtapi_open_grd (struct GMT_CTRL *GMT, char *file, struct GMT_GRID *G, char mode, unsigned int access_mode) {
	/* Read or write the header structure and initialize row-by-row machinery for grids.
	 * We fill the GMT_GRID_ROWBYROW structure with all the required information.
	 * mode can be w or r.  Upper case W or R refers to headerless native grid files
	 * (not used in GMT typically).  The access_mode dictates if we automatically advance
	 * row counter to next row after read/write or if we use the rec_no to seek
	 * first.
	 */

	int r_w, err;
	bool header = true, magic = true, alloc = false;
	int cdf_mode[3] = { NC_NOWRITE, NC_WRITE, NC_WRITE};	/* These MUST be ints */
	char *bin_mode[3] = { "rb", "rb+", "wb"};
	char *fmt = NULL;
	struct GMT_GRID_HIDDEN *GH = gmt_get_G_hidden (G);
	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (G->header);
	struct GMT_GRID_ROWBYROW *R = gmtapi_get_rbr_ptr (GH->extra);	/* Shorthand to row-by-row book-keeping structure */

	if (mode == 'r' || mode == 'R') {	/* Open file for reading */
		if (mode == 'R') {	/* File has no header; can only work if G->header has been set already, somehow */
			header = false;
			if (G->header->n_columns == 0 || G->header->n_rows == 0) {
				GMT_Report (GMT->parent, GMT_MSG_ERROR, "Unable to read header-less grid file %s without a preset header structure\n", file);
				return (GMT_GRDIO_OPEN_FAILED);
			}
		}
		r_w = 0;	mode = 'r';
	}
	else if (mode == 'W') {	/* Write headerless grid */
		r_w = 2;	mode = 'w';
		header = magic = false;
	}
	else {	/* Regular writing of grid with header */
		r_w = 1;
		magic = false;
	}
	if (header) {
		if (mode == 'r' && !R->open)	/* First time reading the info */
			gmtlib_read_grd_info (GMT, file, G->header);
		else if (R->open)		/* Coming back to update the header */
			gmt_update_grd_info (GMT, file, G->header);
		else				/* First time writing the header */
			gmtlib_write_grd_info (GMT, file, G->header);
	}
	else /* Fallback to existing header */
		gmt_M_err_trap (gmt_grd_get_format (GMT, file, G->header, magic));
	if (R->open) return (GMT_NOERROR);	/* Already set the first time */
	fmt = GMT->session.grdformat[G->header->type];
	if (fmt[0] == 'c') {		/* Open netCDF file, old NC3 format */
		gmt_M_err_trap (gmt_nc_open (GMT, HH->name, cdf_mode[r_w], &R->fid));
		R->edge[0] = G->header->n_columns;
		R->start[0] = 0;
		R->start[1] = 0;
	}
	else if (fmt[0] == 'n') {	/* Open netCDF file, COARDS-compliant format */
		gmt_M_err_trap (gmt_nc_open (GMT, HH->name, cdf_mode[r_w], &R->fid));
		R->edge[0] = 1;
		R->edge[1] = G->header->n_columns;
		R->start[0] = HH->row_order == k_nc_start_north ? 0 : G->header->n_rows-1;
		R->start[1] = 0;
	}
	else {		/* Regular binary file with/w.o standard GMT header, or Sun rasterfile */
		if (r_w == 0) {	/* Open for plain reading */
			if ((R->fp = gmt_fopen (GMT, HH->name, bin_mode[0])) == NULL)
				return (GMT_GRDIO_OPEN_FAILED);
		}
		else if ((R->fp = gmt_fopen (GMT, HH->name, bin_mode[r_w])) == NULL)
			return (GMT_GRDIO_CREATE_FAILED);
		/* Seek past the grid header, unless there is none */
		if (header && fseek (R->fp, (off_t)GMT_GRID_HEADER_SIZE, SEEK_SET)) return (GMT_GRDIO_SEEK_FAILED);
		alloc = (fmt[1] != GMT_GRD_FORMAT);	/* Only need to allocate the v_row array if grid is not grdfloat */
#ifdef DEBUG
		R->pos = ftell (R->fp);	/* Where we are */
#endif
	}

	R->size = gmtlib_grd_data_size (GMT, G->header->type, &G->header->nan_value);
	R->check = !isnan (G->header->nan_value);
	R->open = true;

	if (fmt[1] == 'm')	/* Bit mask */
		R->n_byte = lrint (ceil (G->header->n_columns / 32.0)) * R->size;
	else if (fmt[0] == 'r' && fmt[1] == 'b')	/* Sun Raster uses multiple of 2 bytes */
		R->n_byte = lrint (ceil (G->header->n_columns / 2.0)) * 2 * R->size;
	else	/* All other */
		R->n_byte = G->header->n_columns * R->size;

	if (alloc && (R->v_row = gmt_M_memory (GMT, NULL, R->n_byte, char)) == NULL) return GMT_MEMORY_ERROR;

	R->row = 0;
	R->auto_advance = (access_mode & GMT_GRID_ROW_BY_ROW_MANUAL) ? false : true;	/* Read sequentially or random-access rows */
	return (GMT_NOERROR);
}

/*! . */
GMT_LOCAL void gmtapi_update_grd_item (struct GMTAPI_CTRL *API, unsigned int mode, void *arg, size_t length, struct GMT_GRID_HEADER *H) {
	/* Place desired text in string (fixed size array) which can hold up to length bytes in header but unlimited in the hidden structure */
	size_t lim = GMT_BUFSIZ - 1;
	static char buffer[GMT_BUFSIZ];
	char *txt = (mode & GMT_COMMENT_IS_OPTION) ? GMT_Create_Cmd (API, arg) : (char *)arg;

	gmt_M_memset (buffer, GMT_BUFSIZ, char);    /* Start with a clean slate */
	if (mode & GMT_COMMENT_IS_OPTION) { /* Must start with gmt and module name since it is not part of the option args */
		strncat (buffer, "gmt ", GMT_BUFSIZ);
		strncat (buffer, API->GMT->init.module_name, GMT_BUFSIZ-4);
		lim -= strlen (buffer) + 1; /* Remaining characters that we can use */
		strncat (buffer, " ", lim);
	}
	strncat (buffer, txt, lim);     /* Append new text */
	if (mode & GMT_COMMENT_IS_OPTION) gmt_M_free (API->GMT, txt);
	if (strlen (buffer) >= length) {    /* Must place full string versions in hidden structure */
		struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (H);
		if (mode & GMT_COMMENT_IS_TITLE) {    /* Place title */
			if (HH->title) gmt_M_str_free (HH->title);  /* Free previous string */
			HH->title = strdup (buffer);
			GMT_Report (API, GMT_MSG_INFORMATION,
				"Title string exceeds upper length of %d characters (will be truncated in non-netCDF grid files)\n", length);
		}
		if (mode & GMT_COMMENT_IS_COMMAND) {   /* Place command string */
			if (HH->command) gmt_M_str_free (HH->command);  /* Free previous string */
			HH->command = strdup (buffer);
			GMT_Report (API, GMT_MSG_INFORMATION,
				"Command string exceeds upper length of %d characters (will be truncated in non-netCDF grid files)\n", length);
		}
		if (mode & GMT_COMMENT_IS_REMARK) {   /* Place remark */
			if (HH->remark) gmt_M_str_free (HH->remark);  /* Free previous string */
			HH->remark = strdup (buffer);
			GMT_Report (API, GMT_MSG_INFORMATION,
				"Remark string exceeds upper length of %d characters (will be truncated in non-netCDF grid files)\n", length);
		}
	}
	/* Place possibly truncated item in the traditional grid/image header */
	if (mode & GMT_COMMENT_IS_TITLE) {    /* Place title */
		gmt_M_memset (H->title, length, char);    /* Wipe string completely */
		strncpy (H->title, buffer, length-1);   /* Only copy over max length-1 bytes so last byte is 0 */
	}
	if (mode & GMT_COMMENT_IS_COMMAND) {  /* Place command string */
		gmt_M_memset (H->command, length, char);    /* Wipe string completely */
		strncpy (H->command, buffer, length-1);   /* Only copy over max length-1 bytes so last byte is 0 */
	}
	if (mode & GMT_COMMENT_IS_REMARK) {  /* Place remark */
		gmt_M_memset (H->remark, length, char);    /* Wipe string completely */
		strncpy (H->remark, buffer, length-1);   /* Only copy over max length-1 bytes so last byte is 0 */
	}
}

/*! . */
GMT_LOCAL void gmtapi_update_txt_item (struct GMTAPI_CTRL *API, unsigned int mode, void *arg, size_t length, char string[]) {
    /* Place desired text in string (fixed size array) which can hold up to length bytes */
	size_t lim;
	static char buffer[GMT_BUFSIZ];
	char *txt = (mode & GMT_COMMENT_IS_OPTION) ? GMT_Create_Cmd (API, arg) : (char *)arg;
	gmt_M_memset (buffer, GMT_BUFSIZ, char);    /* Start with a clean slate */
	if ((mode & GMT_COMMENT_IS_OPTION) == 0 && (mode & GMT_COMMENT_IS_RESET) == 0 && string[0])
		strncat (buffer, string, length-1); /* Use old text if we are not resetting */
	lim = length - strlen (buffer) - 1; /* Remaining characters that we can use */
	if (mode & GMT_COMMENT_IS_OPTION) { /* Must start with gmt and module name since it is not part of the option args */
        strncat (buffer, "gmt ", lim);  lim -= 4;
        strncat (buffer, API->GMT->init.module_name, lim);
		lim = length - strlen (buffer) - 1; /* Remaining characters that we can use */
		strncat (buffer, " ", lim);
	}
	lim = length - strlen (buffer) - 1; /* Remaining characters that we can use */
	strncat (buffer, txt, lim);     /* Append new text */
	gmt_M_memset (string, length, char);    /* Wipe string completely */
	strncpy (string, buffer, length);   /* Only copy over max length bytes */
	if (mode & GMT_COMMENT_IS_OPTION) gmt_M_free (API->GMT, txt);
}

/*! . */
GMT_LOCAL void gmtapi_GI_comment (struct GMTAPI_CTRL *API, unsigned int mode, void *arg, struct GMT_GRID_HEADER *H) {
	/* Replace or append either command or remark field with text or command-line options */
	if (mode & GMT_COMMENT_IS_REMARK)       gmtapi_update_grd_item (API, mode, arg, GMT_GRID_REMARK_LEN160,  H);
	else if (mode & GMT_COMMENT_IS_COMMAND) gmtapi_update_grd_item (API, mode, arg, GMT_GRID_COMMAND_LEN320, H);
	else if (mode & GMT_COMMENT_IS_TITLE)   gmtapi_update_grd_item (API, mode, arg, GMT_GRID_TITLE_LEN80,    H);
	else if (mode & GMT_COMMENT_IS_NAME_X)  gmtapi_update_txt_item (API, mode, arg, GMT_GRID_UNIT_LEN80,     H->x_units);
	else if (mode & GMT_COMMENT_IS_NAME_Y)  gmtapi_update_txt_item (API, mode, arg, GMT_GRID_UNIT_LEN80,     H->y_units);
	else if (mode & GMT_COMMENT_IS_NAME_Z)  gmtapi_update_txt_item (API, mode, arg, GMT_GRID_UNIT_LEN80,     H->z_units);
}

/*! Replace or append either command or remark field with text or command-line options */
GMT_LOCAL void gmtapi_grid_comment (struct GMTAPI_CTRL *API, unsigned int mode, void *arg, struct GMT_GRID *G) {
	gmtapi_GI_comment (API, mode, arg, G->header);
}

GMT_LOCAL void gmtapi_cube_comment (struct GMTAPI_CTRL *API, unsigned int mode, void *arg, struct GMT_CUBE *U) {
	gmtapi_GI_comment (API, mode, arg, U->header);
}

/*! Update either command or remark field with text or command-line options */
GMT_LOCAL void gmtapi_image_comment (struct GMTAPI_CTRL *API, unsigned int mode, void *arg, struct GMT_IMAGE *I) {
	gmtapi_GI_comment (API, mode, arg, I->header);
}

/*! Update either command or remark field with text or command-line options */
GMT_LOCAL void gmtapi_vector_comment (struct GMTAPI_CTRL *API, unsigned int mode, void *arg, struct GMT_VECTOR *V) {
	if (mode & GMT_COMMENT_IS_REMARK)  gmtapi_update_txt_item (API, mode, arg, GMT_GRID_REMARK_LEN160,  V->remark);
	if (mode & GMT_COMMENT_IS_COMMAND) gmtapi_update_txt_item (API, mode, arg, GMT_GRID_COMMAND_LEN320, V->command);
}

/*! Update either command or remark field with text or command-line options */
GMT_LOCAL void gmtapi_matrix_comment (struct GMTAPI_CTRL *API, unsigned int mode, void *arg, struct GMT_MATRIX *M) {
	if (mode & GMT_COMMENT_IS_REMARK)  gmtapi_update_txt_item (API, mode, arg, GMT_GRID_REMARK_LEN160,  M->remark);
	if (mode & GMT_COMMENT_IS_COMMAND) gmtapi_update_txt_item (API, mode, arg, GMT_GRID_COMMAND_LEN320, M->command);
}

/*! Update common.h's various text items; return 1 if successful else 0 */
GMT_LOCAL int gmtapi_add_comment (struct GMTAPI_CTRL *API, unsigned int mode, char *txt) {
	unsigned int k = 0;
	struct GMT_COMMON *C = &API->GMT->common;	/* Short-hand to the common arg structs */

	if (mode & GMT_COMMENT_IS_TITLE)  { gmt_M_str_free (C->h.title); C->h.title = strdup (txt); k++; }
	if (mode & GMT_COMMENT_IS_REMARK) { gmt_M_str_free (C->h.remark); C->h.remark = strdup (txt); k++; }
    if (mode & GMT_COMMENT_IS_COLNAMES) { gmt_M_str_free (C->h.colnames); C->h.colnames = strdup (txt); k++; C->h.add_colnames = true; }
	return (k);	/* 1 if we did any of the three above; 0 otherwise */
}

/*! Append or replace data table headers with given text or command-line options */
GMT_LOCAL void gmtapi_dataset_comment (struct GMTAPI_CTRL *API, unsigned int mode, void *arg, struct GMT_DATASET *D) {
	unsigned int tbl, k;
	struct GMT_DATATABLE *T = NULL;
	char *txt = gmtlib_create_header_item (API, mode, arg);

	if (gmtapi_add_comment (API, mode, txt)) return;	/* Updated one -h item, or nothing */

	if (D->table == NULL) {
		GMT_Report (API, GMT_MSG_WARNING, "gmtapi_dataset_comment: Trying to access an empty D->table object\n");
		return;
	}

	/* Here we process free-form comments; these go into the dataset's header structures */
	for (tbl = 0; tbl < D->n_tables; tbl++) {	/* For each table in the dataset */
		T = D->table[tbl];	/* Short-hand for this table */
		if (mode & GMT_COMMENT_IS_RESET) {	/* Eliminate all existing headers */
			for (k = 0; k < T->n_headers; k++) gmt_M_str_free (T->header[k]);
			T->n_headers = 0;
		}
		if ((T->header = gmt_M_memory (API->GMT, T->header, T->n_headers + 1, char *)) == NULL) return;
		T->header[T->n_headers++] = strdup (txt);
	}
}

/*! Append or replace CPT headers with given text or command-line options */
GMT_LOCAL void gmtapi_cpt_comment (struct GMTAPI_CTRL *API, unsigned int mode, void *arg, struct GMT_PALETTE *P) {
	unsigned int k;
	char *txt = gmtlib_create_header_item (API, mode, arg);

	if (!gmtapi_add_comment (API, mode, txt)) return;	/* Updated one -h item or nothing */

	/* Here we process free-form comments; these go into the CPT's header structures */
	if (mode & GMT_COMMENT_IS_RESET) {	/* Eliminate all existing headers */
		for (k = 0; k < P->n_headers; k++) gmt_M_str_free (P->header[k]);
		P->n_headers = 0;
	}
	if ((P->header = gmt_M_memory (API->GMT, P->header, P->n_headers + 1, char *)) == NULL) return;
	P->header[P->n_headers++] = strdup (txt);
}

/*! Append or replace Postscript container headers with given text or command-line options */
GMT_LOCAL void gmtapi_ps_comment (struct GMTAPI_CTRL *API, unsigned int mode, void *arg, struct GMT_POSTSCRIPT *P) {
	unsigned int k;
	char *txt = gmtlib_create_header_item (API, mode, arg);

	if (!gmtapi_add_comment (API, mode, txt)) return;	/* Updated one -h item or nothing */

	/* Here we process free-form comments; these go into the CPT's header structures */
	if (mode & GMT_COMMENT_IS_RESET) {	/* Eliminate all existing headers */
		for (k = 0; k < P->n_headers; k++) gmt_M_str_free (P->header[k]);
		P->n_headers = 0;
	}
	if ((P->header = gmt_M_memory (API->GMT, P->header, P->n_headers + 1, char *)) == NULL) return;
	P->header[P->n_headers++] = strdup (txt);
}

GMT_LOCAL unsigned int gmtapi_set_method (struct GMTAPI_DATA_OBJECT *S) {
	/* Most objects have a one-to-one path but for vectors and matrices
	 * we need to set the bit that correspond to their type */
	unsigned int m;
	if (S->method < GMT_IS_DUPLICATE) return S->method;
	switch (S->actual_family) {
		case GMT_IS_VECTOR: m = S->method | GMT_VIA_VECTOR; break;
		case GMT_IS_MATRIX: m = S->method | GMT_VIA_MATRIX; break;
		default: m = S->method;
	}
	return m;
}

/*! . */
GMT_LOCAL int gmtapi_next_io_source (struct GMTAPI_CTRL *API, unsigned int direction) {
	/* Get ready for the next source/destination (open file, initialize counters, etc.).
	 * Note this is only a mechanism for data files where it is common
	 * to give many files on the command line (e.g., *.txt) and we do rec-by-rec processing.
	 * Not used by modules who read entire datasets in one go via GMT_{Read|Write}_Data,
	 * such as grids, images, cubes, palettes, postscript, but also datasets when
	 * GMT_Read_Data are used.  This section is strictly related to GMT_Get_Record. */

	int *fd = NULL;	/* !!! This MUST be int* due to nature of UNIX system function */
	unsigned int method, kind, first = 0;
	static const char *dir[2] = {"from", "to"};
	static const char *operation[3] = {"Reading", "Writing", "Appending"};
	char *mode = NULL;
	struct GMT_MATRIX *M_obj = NULL;
	struct GMT_VECTOR *V_obj = NULL;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMT_CTRL *GMT = API->GMT;

	S_obj = API->object[API->current_item[direction]];	/* For shorthand purposes only */
	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_next_io_source: Selected object %d\n", S_obj->ID);
	gmt_M_memset (GMT->current.io.curr_pos[direction], 4U, int64_t);	/* Reset file, seg, point, header counters */
	GMT->current.io.data_record_number_in_tbl[direction] = GMT->current.io.data_record_number_in_seg[direction] = 0;	/* Start at zero for new table */
	if (direction == GMT_IN) {	/* Set reading mode */
		mode = GMT->current.io.r_mode;
		GMT->current.io.curr_pos[GMT_IN][GMT_SEG] = GMT_NOTSET;	/* First segment of input is set to -1 until first segment header have been dealt with */
	}
	else	/* Set writing mode (but could be changed to append if GMT_IS_FILE and filename starts with >) */
		mode = GMT->current.io.w_mode;
	S_obj->close_file = false;	/* Do not want to close file pointers passed to us unless WE open them below */
	/* Either use binary n_columns settings or initialize to unknown if ascii input, i.e., GMT_MAX_COLUMNS */
	S_obj->n_expected_fields = (GMT->common.b.ncol[direction]) ? GMT->common.b.ncol[direction] : GMT_MAX_COLUMNS;

	method = gmtapi_set_method (S_obj);	/* Get the actual method to use since may be MATRIX or VECTOR masquerading as DATASET */
	switch (method) {	/* File, array, stream etc ? */
		case GMT_IS_FILE:	/* Filename given; we must open the file here */
			assert (S_obj->filename != NULL);
			if (S_obj->family == GMT_IS_GRID || S_obj->family == GMT_IS_IMAGE) return (gmtlib_report_error (API, GMT_NOT_A_VALID_FAMILY));	/* Grids or images not allowed here */
			if (direction == GMT_IN) {
				first = gmt_download_file_if_not_found (API->GMT, S_obj->filename, 0);	/* Deal with downloadable GMT data sets first */
			}
			if (direction == GMT_OUT && S_obj->filename[0] == '>') {
				mode = GMT->current.io.a_mode;	/* Must append to an existing file (we have already checked the file exists) */
				first = 1;
			}
			if ((S_obj->fp = gmt_fopen (GMT, &(S_obj->filename[first]), mode)) == NULL) {	/* Trouble opening file */
				GMT_Report (API, GMT_MSG_ERROR, "Unable to open file %s for %s\n", &(S_obj->filename[first]), GMT_direction[direction]);
				return (GMT_ERROR_ON_FOPEN);
			}
			S_obj->close_file = true;	/* We do want to close files we are opening, but later */
			strncpy (GMT->current.io.filename[direction], &(S_obj->filename[first]), PATH_MAX-1);
			GMT_Report (API, GMT_MSG_INFORMATION, "%s %s %s file %s\n",
				operation[direction+first], GMT_family[S_obj->family], dir[direction], &(S_obj->filename[first]));
			if (gmt_M_binary_header (GMT, direction)) {
				gmtlib_io_binary_header (GMT, S_obj->fp, direction);
				GMT_Report (API, GMT_MSG_INFORMATION, "%s %d bytes of header %s binary file %s\n",
					operation[direction], GMT->current.setting.io_n_header_items, dir[direction], &(S_obj->filename[first]));
			}
			break;

		case GMT_IS_STREAM:	/* Given a stream; no need to open (or close) anything */
#ifdef SET_IO_MODE
			if (S_obj->family == GMT_IS_DATASET && S_obj->fp == GMT->session.std[direction])
				gmt_setmode (GMT, (int)direction);	/* Windows may need to have its read mode changed from text to binary */
#endif
			kind = (S_obj->fp == GMT->session.std[direction]) ? 0 : 1;	/* For message only: 0 if stdin/out, 1 otherwise for user pointer */
			snprintf (GMT->current.io.filename[direction], PATH_MAX-1, "<%s %s>", GMT_stream[kind], GMT_direction[direction]);
			GMT_Report (API, GMT_MSG_INFORMATION, "%s %s %s %s %s stream\n",
				operation[direction], GMT_family[S_obj->family], dir[direction], GMT_stream[kind], GMT_direction[direction]);
			if (gmt_M_binary_header (GMT, direction)) {
				gmtlib_io_binary_header (GMT, S_obj->fp, direction);
				GMT_Report (API, GMT_MSG_INFORMATION, "%s %d bytes of header %s binary %s stream\n",
					operation[direction], GMT->current.setting.io_n_header_items, dir[direction], GMT_stream[kind]);
			}
			break;

		case GMT_IS_FDESC:	/* Given a pointer to a file handle; otherwise same as stream */
			fd = (int *)S_obj->fp;	/* Extract the file handle integer */
			if ((S_obj->fp = fdopen (*fd, mode)) == NULL) {	/* Reopen handle as stream */
				GMT_Report (API, GMT_MSG_ERROR, "Unable to open file descriptor %d for %s\n", *fd, GMT_direction[direction]);
				return (GMT_ERROR_ON_FDOPEN);
			}
			S_obj->method = S_obj->method - GMT_IS_FDESC + GMT_IS_STREAM;	/* Since fp now holds stream pointer an we have lost the handle */
			kind = (S_obj->fp == GMT->session.std[direction]) ? 0 : 1;	/* For message only: 0 if stdin/out, 1 otherwise for user pointer */
			snprintf (GMT->current.io.filename[direction], PATH_MAX-1, "<%s %s>", GMT_stream[kind], GMT_direction[direction]);
			GMT_Report (API, GMT_MSG_INFORMATION, "%s %s %s %s %s stream via supplied file descriptor\n",
				operation[direction], GMT_family[S_obj->family], dir[direction], GMT_stream[kind], GMT_direction[direction]);
			if (gmt_M_binary_header (GMT, direction)) {
				gmtlib_io_binary_header (GMT, S_obj->fp, direction);
				GMT_Report (API, GMT_MSG_INFORMATION, "%s %d bytes of header %s binary %s stream via supplied file descriptor\n",
					operation[direction], GMT->current.setting.io_n_header_items, dir[direction], GMT_stream[kind]);
			}
			break;

	 	case GMT_IS_DUPLICATE:	/* Copy, nothing to do [PW: not tested] */
			GMT_Report (API, GMT_MSG_INFORMATION, "%s %s %s memory copy supplied by pointer\n",
				operation[direction], GMT_family[S_obj->family], dir[direction]);
			break;

	 	case GMT_IS_REFERENCE:	/* Reference, nothing to do [PW: not tested] */
			GMT_Report (API, GMT_MSG_INFORMATION, "%s %s %s memory reference supplied by pointer\n",
				operation[direction], GMT_family[S_obj->family], dir[direction]);
			break;

	 	case GMT_IS_DUPLICATE|GMT_VIA_MATRIX:	/* These 2 mean reading or writing a dataset record-by-record via a user matrix */
		case GMT_IS_REFERENCE|GMT_VIA_MATRIX:
			if (!(S_obj->family == GMT_IS_DATASET)) return (gmtlib_report_error (API, GMT_NOT_A_VALID_FAMILY));
			GMT_Report (API, GMT_MSG_INFORMATION, "%s %s %s %s memory location via matrix\n",
				operation[direction], GMT_family[S_obj->family], dir[direction], GMT_direction[direction]);
			if (direction == GMT_IN) {	/* Hard-wired limits are passed in from calling program; for output we have nothing yet */
				if ((M_obj = S_obj->resource) == NULL) {
					GMT_Report (API, GMT_MSG_ERROR, "GMTAPI: Internal error: gmtapi_next_io_source got a matrix pointer that is NULL!!!\n");
					return GMT_NOERROR;
				}
				S_obj->n_rows    = M_obj->n_rows;
				S_obj->n_columns = M_obj->n_columns;
				S_obj->rec = 0;	/* Start of this "file" */
				GMT->common.b.ncol[direction] = M_obj->n_columns;	/* Basically doing binary i/o with specified number of columns */
			}
			GMT->common.b.active[direction] = true;	/* Basically, we are doing what GMT calls binary i/o since it is all in memory */
			strcpy (GMT->current.io.filename[direction], "<matrix memory>");
			break;

		 case GMT_IS_DUPLICATE|GMT_VIA_VECTOR:	/* These 2 mean reading or writing a dataset record-by-record via user vector arrays */
		 case GMT_IS_REFERENCE|GMT_VIA_VECTOR:
			if (S_obj->family != GMT_IS_DATASET) return (gmtlib_report_error (API, GMT_NOT_A_VALID_FAMILY));
			GMT_Report (API, GMT_MSG_INFORMATION, "%s %s %s %s memory location via vector\n",
					operation[direction], GMT_family[S_obj->family], dir[direction], GMT_direction[direction]);
			if (direction == GMT_IN) {	/* Hard-wired limits are passed in from calling program; for output we have nothing yet */
				if ((V_obj = S_obj->resource) == NULL) {
					GMT_Report (API, GMT_MSG_ERROR, "GMTAPI: Internal error: gmtapi_next_io_source got a vector pointer that is NULL!!!\n");
					return GMT_NOERROR;
				}
				S_obj->n_rows    = V_obj->n_rows;
				S_obj->n_columns = V_obj->n_columns;
				S_obj->rec = 0;	/* Start of this "file" */
				GMT->common.b.ncol[direction] = V_obj->n_columns;	/* Basically doing binary i/o with specified number of columns */
			}
			GMT->common.b.active[direction] = true;	/* Basically, we are doing what GMT calls binary i/o */
			strcpy (GMT->current.io.filename[direction], "<vector memory>");
			break;

		default:
			GMT_Report (API, GMT_MSG_ERROR, "GMTAPI: Internal error: gmtapi_next_io_source called with illegal method\n");
			break;
	}

	/* A few things pertaining only to data/text tables */
	GMT->current.io.rec_in_tbl_no = 0;	/* Start on new table */
	if (direction == GMT_IN) API->current_get_obj = S_obj;
	if (S_obj->geometry == GMT_IS_TEXT) {	/* Reading pure text, no coordinates */
		S_obj->import = &gmtlib_ascii_textinput;
		GMT->current.io.record.data = NULL;	/* Since there isn't any data */
	}
	else
		S_obj->import = GMT->current.io.input;	/* import may point to ASCII or binary (if -b) input functions */

	return (GMT_NOERROR);
}

/*! . */
GMT_LOCAL int gmtapi_next_data_object (struct GMTAPI_CTRL *API, enum GMT_enum_family family, enum GMT_enum_std direction) {
	/* Sets up current_item to be the next unused item of the required direction; or return EOF.
	 * When EOF is returned, API->current_item[direction] holds the last object ID used. */
	bool found = false;
	int item = API->current_item[direction] + 1;	/* Advance to next item, if it exists */
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	while (item < (int)API->n_objects && !found) {
		S_obj = API->object[item];	/* Current object in list */
		if (S_obj && S_obj->selected && S_obj->status == GMT_IS_UNUSED && S_obj->direction == direction && S_obj->family == family)
			found = true;	/* Got item that is selected and unused, has correct direction and family */
		else
			item++;	/* No, keep looking */
	}
	if (found) {	/* Update to use next item */
		API->current_item[direction] = item;	/* The next item */
		return (gmtapi_next_io_source (API, direction));	/* Initialize the next source/destination */
	}
	else
		return (EOF);	/* No more objects available for this direction; return EOF */
}

/*! Hook object to end of linked list and assign unique id (> 0) which is returned */
GMT_LOCAL int gmtapi_add_data_object (struct GMTAPI_CTRL *API, struct GMTAPI_DATA_OBJECT *object) {
	/* Find the first entry in the API->object array which is unoccupied, and if
	 * they are all occupied then reallocate the array to make more space.
	 * We thus find and return the lowest available ID. */
	API->error = GMT_NOERROR;
	API->n_objects++;		/* Must add one more entry to the tally */
	if (API->n_objects == API->n_objects_alloc) {	/* Must allocate more space to hold all data descriptors */
		size_t old_n_alloc = API->n_objects_alloc;
		API->n_objects_alloc <<= 1;	/* Double it */
		API->object = gmt_M_memory (API->GMT, API->object, API->n_objects_alloc, struct GMTAPI_DATA_OBJECT *);
		if (!(API->object)) {		/* Failed to allocate more memory */
			API->n_objects--;	/* Undo our premature increment */
			return_value (API, GMT_MEMORY_ERROR, GMT_NOTSET);
		}
		else	/* Set new ones to NULL */
			gmt_M_memset (&(API->object[old_n_alloc]), API->n_objects_alloc - old_n_alloc, struct GMTAPI_DATA_OBJECT *);
	}
	object->ID = API->unique_ID++;	/* Assign a unique object ID */
	API->object[API->n_objects-1] = object;		/* Hook the current object onto the end of the list */

	return (object->ID);
}

/*! Sanity check that geometry and family are compatible; note they may not be set (GMT_NOTSET) hence the use of signed ints */
GMT_LOCAL bool gmtapi_validate_geometry (struct GMTAPI_CTRL *API, int family, int geometry) {
	bool problem = false;
	gmt_M_unused(API);
	if (geometry == GMT_NOTSET || family == GMT_NOTSET) return false;	/* No errors if nothing to check yet */
	switch (family) {
		case GMT_IS_DATASET:     if (!(geometry == GMT_IS_NONE || geometry == GMT_IS_TEXT || (geometry & GMT_IS_PLP))) problem = true; break;	/* Datasets can hold many things... */
		case GMT_IS_GRID:        if (geometry != GMT_IS_SURFACE) problem = true;    break;	/* Only surface is valid */
		case GMT_IS_IMAGE:       if (geometry != GMT_IS_SURFACE) problem = true;    break;	/* Only surface is valid */
		case GMT_IS_PALETTE:     if (geometry != GMT_IS_NONE) problem = true;       break;	/* Only text is valid */
		case GMT_IS_POSTSCRIPT:  if (geometry != GMT_IS_NONE) problem = true;       break;	/* Only text is valid */
		case GMT_IS_CUBE:        if (geometry != GMT_IS_VOLUME) problem = true;     break;	/* Only volume is valid */
		case GMT_IS_VECTOR:      if ((geometry & GMT_IS_PLP) == 0) problem = true;  break; 	/* Must be one of those three */
		case GMT_IS_MATRIX:      if (geometry == GMT_IS_NONE) problem = true;       break;	/* Matrix can hold surfaces or DATASETs */
		case GMT_IS_COORD:       if (geometry != GMT_IS_NONE) problem = true;       break;	/* Only text is valid */
	}
	return (problem);
}

/*! . */
GMT_LOCAL int gmtapi_decode_id (const char *filename) {
	/* Checking if filename contains a name with embedded GMTAPI Object ID.
	 * If found we return the ID, otherwise we return GMT_NOTSET.
 	*/
	int object_ID = GMT_NOTSET;

	if (gmt_M_file_is_memory (filename)) {	/* Passing ID of a registered object */
		if (sscanf (&filename[GMTAPI_OBJECT_ID_START], "%d", &object_ID) != 1) return (GMT_NOTSET);	/* Get the object ID unless we fail scanning */
	}
	return (object_ID);	/* Returns GMT_NOTSET if no embedded ID was found */
}

/*! . */
bool gmtlib_data_is_geographic (struct GMTAPI_CTRL *API, const char *file) {
	/* Here file is a memory file. If dataset, grid, image, matrix, or vector we determine if geographic */
	bool geo = false;	/* Default is Cartesian */
	int object_ID, item;
	struct GMT_DATASET *D;
	struct GMT_GRID *G;
	struct GMT_IMAGE *I;
	struct GMT_CUBE *U;
	struct GMT_MATRIX *M;
	struct GMT_VECTOR *V;
	struct GMT_DATASET_HIDDEN *HD;
	struct GMT_GRID_HEADER_HIDDEN *HH;
	struct GMT_MATRIX_HIDDEN *HM;
	struct GMT_VECTOR_HIDDEN *HV;

	if (!gmt_M_file_is_memory (file)) return false;	/* Not a memory file */

	if (strchr ("DGIPMV", file[GMTAPI_OBJECT_FAMILY_START]) == NULL) return false;	/* Not geographic since not dataset, grid, image, cube, vector or matrix */

	/* Must get pointer to the hidden structure */
	if ((object_ID = gmtapi_decode_id (file)) == GMT_NOTSET)
		return false;	/* Should not happen but return as not geographic */
	if ((item = gmtlib_validate_id (API, GMT_NOTSET, object_ID, GMT_NOTSET, GMT_NOTSET)) == GMT_NOTSET)
		return false;	/* Should not happen but return as not geographic */

	switch (file[GMTAPI_OBJECT_FAMILY_START]) {
		case 'D':	/* Memory dataset */
			D = gmtapi_get_dataset_data (API->object[item]->resource);
			HD = gmt_get_DD_hidden (D);
			if (HD->geographic) geo = true;
			break;
		case 'G':	/* Memory grid */
			G = gmtapi_get_grid_data (API->object[item]->resource);
			HH = gmt_get_H_hidden (G->header);
			if (HH->grdtype > GMT_GRID_CARTESIAN) geo = true;
			break;
		case 'I':	/* Memory image */
			I = gmtapi_get_image_data (API->object[item]->resource);
			HH = gmt_get_H_hidden (I->header);
			if (HH->grdtype > GMT_GRID_CARTESIAN) geo = true;
			break;
		case 'U':	/* Memory cube */
			U = gmtapi_get_cube_data (API->object[item]->resource);
			HH = gmt_get_H_hidden (U->header);
			if (HH->grdtype > GMT_GRID_CARTESIAN) geo = true;
			break;
		case 'M':	/* Memory matrix */
			M = gmtapi_get_matrix_data (API->object[item]->resource);
			HM = gmt_get_M_hidden (M);
			if (HM->grdtype > GMT_GRID_CARTESIAN) geo = true;
			break;
		case 'V':	/* Memory vector */
			V = gmtapi_get_vector_data (API->object[item]->resource);
			HV = gmt_get_V_hidden (V);
			if (HV->geographic) geo = true;
			break;
		default: /* For Coverity mostly */
			break;
	}
	return (geo);
}

/*! . */
GMT_LOCAL unsigned int gmtapi_expand_headerpad (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *h, double *new_wesn, unsigned int *orig_pad, double *orig_wesn) {
	unsigned int tmp_pad[4] = {0, 0, 0, 0}, delta[4] = {0, 0, 0, 0}, k = 0;
	/* When using subset with memory grids we cannot actually cut the grid but instead
	 * must temporarily change the pad to match the desired inner region wesn.  This means
	 * the pads will change and can be quite large. */
	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (h);

	gmt_M_memcpy (tmp_pad, h->pad, 4, unsigned int);    /* Initialize new pad to the original pad */
	/* First determine which (and how many, k) of the 4 new boundaries are inside the original region and update the padding: */
	if (new_wesn[XLO] > h->wesn[XLO]) k++, tmp_pad[XLO] += urint ((new_wesn[XLO] - h->wesn[XLO]) * HH->r_inc[GMT_X]);
	if (new_wesn[XHI] < h->wesn[XHI]) k++, tmp_pad[XHI] += urint ((h->wesn[XHI] - new_wesn[XHI]) * HH->r_inc[GMT_X]);
	if (new_wesn[YLO] > h->wesn[YLO]) k++, tmp_pad[YLO] += urint ((new_wesn[YLO] - h->wesn[YLO]) * HH->r_inc[GMT_Y]);
	if (new_wesn[YHI] < h->wesn[YHI]) k++, tmp_pad[YHI] += urint ((h->wesn[YHI] - new_wesn[YHI]) * HH->r_inc[GMT_Y]);
	if (k) {    /* Yes, pad will change since region is different for k of the 4 sides */
		for (k = 0; k < 4; k++) delta[k] = tmp_pad[k] - h->pad[k];  /* Columns with data being passed as padding */
		gmt_M_memcpy (orig_pad, h->pad, 4, unsigned int);   /* Place the original grid pad in the provided array */
		gmt_M_memcpy (orig_wesn, h->wesn, 4, double);       /* Place the original grid wesn in the provided array */
		gmt_M_memcpy (h->pad, tmp_pad, 4, unsigned int);    /* Place the new pad in the grid header */
		gmt_M_memcpy (h->wesn, new_wesn, 4, double);        /* Place the new wesn in the grid header */
		gmt_set_grddim (GMT, h);    /* This recomputes n_columns|n_rows. */
		GMT_Report (GMT->parent, GMT_MSG_DEBUG, "gmtapi_expand_headerpad: %d pad sides changed. Now %u/%u/%u/%u\n",
			k, h->pad[XLO], h->pad[XHI], h->pad[YLO], h->pad[YHI]);
		for (k = 0; k < 4; k++) {   /* If pad now contains data then change the BC to reflect this */
			if (delta[k] >= orig_pad[k]) HH->BC[k] = GMT_BC_IS_DATA;
		}
	}
    else
        GMT_Report (GMT->parent, GMT_MSG_DEBUG, "gmtapi_expand_headerpad: No pad adjustment needed\n");
    return k;
}

/*! . */
GMT_LOCAL void gmtapi_update_grid_minmax (struct GMT_CTRL *GMT, struct GMT_GRID *G) {
	/* Update grid header z_min/z_max to reflect the range within the subset */
	uint64_t ij;
	openmp_int row, col;
	struct GMT_GRID_HEADER *h = G->header;
	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (h);
	gmt_M_unused (GMT);

	h->z_min = +DBL_MAX;    h->z_max = -DBL_MAX;    /* Reset the min/max before we search */
	HH->has_NaNs = GMT_GRID_NO_NANS;    /* We are about to check for NaNs and if none are found we retain 1, else 2 */
	gmt_M_grd_loop (GMT, G, row, col, ij) {
		if (gmt_M_is_fnan (G->data[ij]))
			HH->has_NaNs = GMT_GRID_HAS_NANS;
		else {
			h->z_min = MIN (h->z_min, G->data[ij]);
			h->z_max = MAX (h->z_max, G->data[ij]);
		}
	}
}

/*! . */
GMT_LOCAL void gmtapi_update_cube_minmax (struct GMT_CTRL *GMT, struct GMT_CUBE *U) {
	/* Update cube header z_min/z_max to reflect the range within the subset */
	uint64_t ij, node, here = 0;
	unsigned int k;
	openmp_int row, col;
	struct GMT_GRID_HEADER *h = U->header;
	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (h);
	gmt_M_unused (GMT);

	h->z_min = +DBL_MAX;    h->z_max = -DBL_MAX;    /* Reset the min/max before we search */
	HH->has_NaNs = GMT_GRID_NO_NANS;    /* We are about to check for NaNs and if none are found we retain 1, else 2 */
	for (k = 0; k < h->n_bands; k++) {
		gmt_M_grd_loop (GMT, U, row, col, ij) {
			node = ij + here;
			if (gmt_M_is_fnan (U->data[node]))
				HH->has_NaNs = GMT_GRID_HAS_NANS;
			else {
				h->z_min = MIN (h->z_min, U->data[node]);
				h->z_max = MAX (h->z_max, U->data[node]);
			}
		}
		here += h->size;
	}
}

/*! . */
GMT_LOCAL void gmtapi_contract_headerpad (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *h, unsigned int *orig_pad, double *orig_wesn) {
	/* When using subset with memory grids we must reset the pad back to the original setting when done */
	if (h == NULL) return;	/* Nothing for us to work with */
	gmt_M_memcpy (h->pad, orig_pad, 4, unsigned int);	/* Place the original pad in the grid header */
	gmt_M_memcpy (h->wesn, orig_wesn, 4, double);		/* Place the orig_pad wesn in the grid header */
	gmt_set_grddim (GMT, h);	/* This recomputes n_columns|n_rows. */
	GMT_Report (GMT->parent, GMT_MSG_DEBUG, "gmtapi_contract_headerpad: Pad and wesn reset to original values\n");
}

/*! . */
GMT_LOCAL void gmtapi_contract_pad (struct GMT_CTRL *GMT, void *object, int family, unsigned int *orig_pad, double *orig_wesn) {
	/* When using subset with memory grids we must reset the pad back to the original setting when done */
	struct GMT_GRID_HEADER *h = NULL;
	if (family == GMT_IS_GRID) {
		struct GMT_GRID *G = gmtapi_get_grid_data (object);
		if (G) h = G->header;
	}
	else if (family == GMT_IS_IMAGE) {
		struct GMT_IMAGE *I = gmtapi_get_image_data (object);
		if (I) h = I->header;
	}
	gmtapi_contract_headerpad (GMT, h, orig_pad, orig_wesn);
}

/*! . */
GMT_LOCAL int gmtapi_get_object (struct GMTAPI_CTRL *API, int sfamily, void *ptr) {
	/* Returns the ID of the first object whose resource pointer matches ptr.
	 * Unless family is GMT_NOTSET the object must be of the specified family.
	 */
	unsigned int item;
	enum GMT_enum_family family;
	int object_ID = GMT_NOTSET;	/* Not found yet */
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;

	if (sfamily != GMT_NOTSET) family = sfamily;
	for (item = 0; object_ID == GMT_NOTSET && item < API->n_objects; item++) {	/* Loop over all objects */
		if ((S_obj = API->object[item]) == NULL) continue;	/* Skip freed objects */
		if (S_obj->resource == NULL) continue;	/* No resource pointer */
		if (sfamily != GMT_NOTSET && S_obj->family != family) continue;	/* Not the right family */
		if (S_obj->resource == ptr && object_ID == GMT_NOTSET) object_ID = S_obj->ID;	/* Found a matching data pointer */
	}
	return (object_ID);	/* Return ID or GMT_NOTSET if not found */
}

/*! . */
GMT_LOCAL void * gmtapi_pass_object (struct GMTAPI_CTRL *API, struct GMTAPI_DATA_OBJECT *object, unsigned int family, unsigned int mode, double *wesn) {
	/* Passes back the input object pointer after possibly performing some minor adjustments to metadata.
	 * For grids and images we must worry about possible subset requests */
	void *data = object->resource;
	struct GMT_GRID    *G = NULL;
	struct GMT_IMAGE   *I = NULL;
	struct GMT_DATASET *D = NULL;
	struct GMT_GRID_HEADER_HIDDEN *HH = NULL;

	if (object->resource == NULL)
		GMT_Report (API, GMT_MSG_ERROR, "gmtapi_pass_object given a NULL resource!\n");

	switch (family) {	/* Do family-specific prepping before passing back the input object */
		case GMT_IS_PALETTE:	/* Make sure the hidden support arrays etc. have been initialized as external interfaces may not care */
			if (data) gmtlib_init_cpt (API->GMT, data);
			break;
		case GMT_IS_GRID:	/* Grids need to update the grdtype setting, possibly rotate geographic grids, and maybe deal with subsets */
			G = gmtapi_get_grid_data (data);	/* Get the right grid pointer */
			HH = gmt_get_H_hidden (G->header);
			gmtlib_grd_get_units (API->GMT, G->header);	/* Set the unit names */
			HH->grdtype = gmtlib_get_grdtype (API->GMT, GMT_IN, G->header);
			if (wesn && G->data) {	/* Subset or global rotation was requested */
				if (gmt_grd_is_global (API->GMT, G->header)) {	/* May have to rotate a geographic grid since we are not reading from file this time */
					double shift_amount = wesn[XLO] - G->header->wesn[XLO];
					if (fabs (shift_amount) >= G->header->inc[GMT_X]) {	/* Must do it */
						GMT_Report (API, GMT_MSG_DEBUG, "Shifting longitudes in grid by %g degrees to fit -R\n", shift_amount);
						gmt_grd_shift (API->GMT, G, shift_amount);	/* In-memory rotation */
					}
				}
				if (object->region) {	/* Possibly adjust the pad so inner region matches requested wesn */
					/* NOTE: This assumes the memory cannot be adjusted. Probably should distinguish between GMT_IS_REFERENCE and GMT_IS_DUPLICATE
					 * and offer different behavior.  As it is we assume read-only grids */
					if (object->reset_pad) {	/* First undo any prior sub-region used with this memory grid */
						gmtapi_contract_headerpad (API->GMT, G->header, object->orig_pad, object->orig_wesn);
						object->reset_pad = HH->reset_pad = 0;
					}
					/* Then apply the new pad adjustment.  Basically we cannot mess with the data so we change what constitute the pad */
					if (gmtapi_expand_headerpad (API->GMT, G->header, object->wesn, object->orig_pad, object->orig_wesn)) {
						object->reset_pad = HH->reset_pad = 1;
						gmtapi_update_grid_minmax (API->GMT, G);	/* Update z-range */
					}
				}
			}
			if (mode & GMT_CONTAINER_ONLY) break;	/* No grid yet */
			gmt_BC_init (API->GMT, G->header);	/* Initialize grid interpolation and boundary condition parameters */
			if (gmt_M_err_pass (API->GMT, gmt_grd_BC_set (API->GMT, G, GMT_IN), "Grid memory"))
				return_null (API, GMT_GRID_BC_ERROR);	/* Failed to set boundary conditions */
			break;
		case GMT_IS_IMAGE:	/* Images need to update the grdtype setting, possibly rotate geographic grids, and maybe deal with subsets */
			I = gmtapi_get_image_data (data);	/* Get the right image pointer */
			HH = gmt_get_H_hidden (I->header);
			gmtlib_grd_get_units (API->GMT, I->header);	/* Set the unit names */
			HH->grdtype = gmtlib_get_grdtype (API->GMT, GMT_IN, I->header);
			if (wesn && I->data) {	/* Subset or global rotation was requested */
				if (gmt_grd_is_global (API->GMT, I->header)) {	/* May have to rotate geographic grid since we are not reading from file here */
					double shift_amount = wesn[XLO] - I->header->wesn[XLO];
					if (fabs (shift_amount) >= I->header->inc[GMT_X]) {	/* Must do it */
						GMT_Report (API, GMT_MSG_WARNING, "Longitudinal roll for images not implemented yet\n");
#if 0
						GMT_Report (API, GMT_MSG_DEBUG, "Shifting longitudes in grid by %g degrees to fit -R\n", shift_amount);
						gmt_grd_shift (API->GMT, I, shift_amount);
#endif
					}
				}
				else if (object->region) {	/* Possibly adjust the pad so inner region matches wesn */
					/* NOTE: This assumes the memory cannot be adjusted. Probably should distinaguish between GMT_IS_REFERENCE and GMT_IS_DUPLICATE
					 * and offer different behavior.  As it is we assume read-only images */
					if (object->reset_pad) {	/* First undo a prior sub-region used with this memory grid */
						gmtapi_contract_headerpad (API->GMT, I->header, object->orig_pad, object->orig_wesn);
						object->reset_pad = HH->reset_pad = 0;
					}
					/* Then apply the new pad adjustment.  Basically we cannot mess with the data so we change what constitute the pad */
					if (gmtapi_expand_headerpad (API->GMT, I->header, object->wesn, object->orig_pad, object->orig_wesn))
						object->reset_pad = HH->reset_pad = 1;
				}
			}
			if (mode & GMT_CONTAINER_ONLY) break;	/* No image yet */
			gmt_BC_init (API->GMT, I->header);	/* Initialize image interpolation and boundary condition parameters */
			if (gmt_M_err_pass (API->GMT, gmtlib_image_BC_set (API->GMT, I), "Image memory"))
				return_null (API, GMT_IMAGE_BC_ERROR);	/* Set boundary conditions */
			break;
		case GMT_IS_DATASET:	/* Just make sure the min/max values are updated for tables and dataset  */
		 	D = gmtapi_get_dataset_data (data);	/* Get the right dataset pointer */
			gmt_set_dataset_minmax (API->GMT, D);	/* Set the min/max values for the entire dataset */
			break;
		default:	/* Nothing yet for other types */
			break;
	}
	return (data);
}

/*! . */
GMT_LOCAL int gmtapi_get_object_id_from_data_ptr (struct GMTAPI_CTRL *API, void *ptr) {
	/* Returns the ID of the first object whose data pointer matches *ptr.
 	 * This is necessary since many objects may have the same pointer
	 * but we only want to destroy the memory once.  This function is
	 * only used in GMT_Destroy_Data and gmt_is_an_object.
	 */
	unsigned int item;
	int object_ID = GMT_NOTSET;	/* Not found yet */
	void *data = NULL;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;

	for (item = 0; object_ID == GMT_NOTSET && item < API->n_objects; item++) {	/* Loop over all objects */
		if ((S_obj = API->object[item]) == NULL) continue;	/* Skip freed objects */
		data = gmtapi_return_address (ptr, S_obj->family);	/* Get void* pointer to resource of this family */
		/* Try to look for either data or resource pointers since Open_VirtualFile shuffles these two and Destroy needs to find them even if the
		 * function level test will tell it not to free anything */
		if (object_ID == GMT_NOTSET && S_obj->resource == data) object_ID = S_obj->ID;	/* Found a matching data pointer */
	}
	return (object_ID);	/* Return ID or GMT_NOTSET if not found */
}

/*! . */
GMT_LOCAL int gmtapi_destroy_grid (struct GMTAPI_CTRL *API, struct GMT_GRID **G_obj) {
	/* Delete the given grid resource. */
	struct GMT_GRID_HIDDEN *GH = NULL;
	if (!(*G_obj)) {	/* Probably not a good sign */
		GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_destroy_grid: Passed NULL pointer - skipped\n");
		return (GMT_PTR_IS_NULL);
	}
	GH = gmt_get_G_hidden (*G_obj);
	if ((*G_obj)->data && GH->alloc_level != API->GMT->hidden.func_level) return (GMT_FREE_WRONG_LEVEL);	/* Not the right level */

	gmt_free_grid (API->GMT, G_obj, true);
	return GMT_NOERROR;
}

/*! . */
GMT_LOCAL int gmtapi_destroy_cube (struct GMTAPI_CTRL *API, struct GMT_CUBE **U_obj) {
	/* Delete the given grid resource. */
	struct GMT_CUBE_HIDDEN *UH = NULL;
	if (!(*U_obj)) {	/* Probably not a good sign */
		GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_destroy_cube: Passed NULL pointer - skipped\n");
		return (GMT_PTR_IS_NULL);
	}
	UH = gmt_get_U_hidden (*U_obj);
	if (UH->alloc_level != API->GMT->hidden.func_level) return (GMT_FREE_WRONG_LEVEL);	/* Not the right level */

	gmtlib_free_cube (API->GMT, U_obj, true);
	return GMT_NOERROR;
}

/*! . */
GMT_LOCAL int gmtapi_destroy_dataset (struct GMTAPI_CTRL *API, struct GMT_DATASET **D_obj) {
	/* Delete the given dataset resource. */
	struct GMT_DATASET_HIDDEN *DH = NULL;

	if (!(*D_obj)) {	/* Probably not a good sign */
		GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_destroy_dataset: Passed NULL pointer - skipped\n");
		return (GMT_PTR_IS_NULL);
	}
	DH = gmt_get_DD_hidden (*D_obj);

	if (DH->alloc_level != API->GMT->hidden.func_level) return (GMT_FREE_WRONG_LEVEL);	/* Not the right level */

	gmt_free_dataset (API->GMT, D_obj);
	return GMT_NOERROR;
}

/*! . */
GMT_LOCAL int gmtapi_destroy_palette (struct GMTAPI_CTRL *API, struct GMT_PALETTE **P_obj) {
	/* Delete the given CPT resource. */
	struct GMT_PALETTE_HIDDEN *PH = NULL;

	if (!(*P_obj)) {	/* Probably not a good sign */
		GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_destroy_palette: Passed NULL pointer - skipped\n");
		return (GMT_PTR_IS_NULL);
	}
	PH = gmt_get_C_hidden (*P_obj);
	if (PH->alloc_level != API->GMT->hidden.func_level) return (GMT_FREE_WRONG_LEVEL);	/* Not the right level */

	gmtlib_free_palette (API->GMT, P_obj);
	return GMT_NOERROR;
}

/*! . */
GMT_LOCAL int gmtapi_destroy_postscript (struct GMTAPI_CTRL *API, struct GMT_POSTSCRIPT **P_obj) {
	/* Delete the given GMT_POSTSCRIPT resource. */
	struct GMT_POSTSCRIPT_HIDDEN *PH = NULL;
	if (!(*P_obj)) {	/* Probably not a good sign */
		GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_destroy_postscript: Passed NULL pointer - skipped\n");
		return (GMT_PTR_IS_NULL);
	}
	PH = gmt_get_P_hidden (*P_obj);
	if (PH->alloc_level != API->GMT->hidden.func_level) return (GMT_FREE_WRONG_LEVEL);	/* Not the right level */

	gmtlib_free_ps (API->GMT, P_obj);
	return GMT_NOERROR;
}

/*! . */
GMT_LOCAL int gmtapi_destroy_matrix (struct GMTAPI_CTRL *API, struct GMT_MATRIX **M_obj) {
	/* Delete the given Matrix resource. */
	struct GMT_MATRIX_HIDDEN *MH = NULL;
	if (!(*M_obj)) {	/* Probably not a good sign */
		GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_destroy_matrix: Passed NULL pointer - skipped\n");
		return (GMT_PTR_IS_NULL);
	}
	MH = gmt_get_M_hidden (*M_obj);
	if (MH->alloc_level != API->GMT->hidden.func_level) return (GMT_FREE_WRONG_LEVEL);	/* Not the right level */

	gmtlib_free_matrix (API->GMT, M_obj, true);
	return GMT_NOERROR;
}

/*! . */
GMT_LOCAL int gmtapi_destroy_vector (struct GMTAPI_CTRL *API, struct GMT_VECTOR **V_obj) {
	/* Delete the given Matrix resource. */
	struct GMT_VECTOR_HIDDEN *VH = NULL;
	if (!(*V_obj)) {	/* Probably not a good sign */
		GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_destroy_vector: Passed NULL pointer - skipped\n");
		return (GMT_PTR_IS_NULL);
	}
	VH = gmt_get_V_hidden (*V_obj);
	if (VH->alloc_level != API->GMT->hidden.func_level) return (GMT_FREE_WRONG_LEVEL);	/* Not the right level */

	gmt_free_vector (API->GMT, V_obj, true);
	return GMT_NOERROR;
}

/*! . */
bool gmt_is_an_object (struct GMT_CTRL *GMT, void *ptr) {
	/* Needed by g*math.c so exported as part of the gmt_dev library */
	return (gmtapi_get_object_id_from_data_ptr (GMT->parent, ptr) == GMT_NOTSET) ? false : true;
}

/*! Determine if resource is a filename that has already been registered */
#if 0
/* Unused code - probably can be removed but like unused DNA in-between the useful stuff */
GMT_LOCAL int gmtapi_memory_registered (struct GMTAPI_CTRL *API, enum GMT_enum_family family, unsigned int direction, void *resource) {
	int object_ID = 0, item;
	unsigned int module_input = (family & GMT_VIA_MODULE_INPUT);	/* Are we dealing with a resource that is a module input? */
	family -= module_input;

	if (family == GMT_IS_COORD) return (GMT_NOTSET);	/* Coordinate arrays cannot be a registered memory resource */
	if ((object_ID = gmtapi_decode_id (resource)) == GMT_NOTSET) return (GMT_NOTSET);	/* Not a registered resource */
	if ((item = gmtlib_validate_id (API, family, object_ID, direction, GMT_NOTSET)) == GMT_NOTSET) return (GMT_NOTSET);	/* Not the right attributes */
	if (module_input && direction == GMT_IN) API->object[item]->module_input = true;	/* Flag this object as a module input resource */
	return (object_ID);	/* resource is a registered and valid item */
}
#endif

/*! Determine if resource is a filename that has already been registered */
GMT_LOCAL int gmtapi_memory_registered (struct GMTAPI_CTRL *API, enum GMT_enum_family family, unsigned int direction, char *filename) {
	char SP, D, F, A, G, M;
	int k, object_ID;
	gmt_M_unused(family);
	if (!gmt_M_file_is_memory (filename)) return GMT_NOTSET;	/* If not a memory reference then there is no ID etc */
	/* Name template: @GMTAPI@-S-D-F-A-G-M-###### where # is the 6-digit integer object code.
	 * S stands for P(rimary) or S(econdary) input or output object (command line is primary, files via options are secondary).
	 * D stands for Direction and is either I(n) or O(ut).
	 * F stands for Family and is one of D(ataset), G(rid), I(mage), C(PT), X(PostScript), M(atrix), V(ector), U(cube), - (undefined).
	 * A stands for Actual Family and is one of D, G, I, C, X, M, V, and U as well.
	 *   Actual family may differ from family if a Dataset is actually passed as a Matrix, for instance.
	 * G stands for Geometry and is one of (poin)T, L(ine), P(olygon), C(Line|Polygon), A(POint|Line|Polygon), G(rid), V(olume), N(one), X(text), or -(undefined).
	 * M stands for Messenger and is either Y(es) or N(o).
	 * Limitation:  object_ID must be <= GMTAPI_MAX_ID */

	SP = D = F = A = G = M = 0;	/* Initialize */
	if ((k = sscanf (&filename[GMTAPI_PREFIX_LEN], "%c-%c-%c-%c-%c-%c-%d", &SP, &D, &F, &A, &G, &M, &object_ID)) != 7) {
		GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_memory_registered: Failed to decode memory name [%s], only %d conversions were successful.\n", filename, k);
		return (GMT_NOTSET);	/* Get the object ID unless we fail scanning */
	}
	if (direction == GMT_IN && D != 'I') return GMT_NOTSET;	/* Not the right direction */
	return object_ID;
}

/*! . */
GMT_LOCAL int gmtapi_is_registered (struct GMTAPI_CTRL *API, int family, int geometry, int direction, unsigned int mode, char *filename, void *resource) {
	/* Checks to see if the given data pointer has already been registered.
 	 * This can happen for grids which first gets registered reading the header
 	 * and then is registered again when reading the whole grid.  In those cases
	 * we don't want to register them twice.
	 */
	unsigned int i;
	int item;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;

	if (API->n_objects == 0) return (GMT_NOTSET);	/* There are no known resources yet */

	if ((item = gmtapi_memory_registered (API, family, direction, filename)) != GMT_NOTSET)
		return (item);	/* OK, return the object ID */

	 /* Search for the object in the active list.  However, if object_ID == GMT_NOTSET we instead pick the first in that direction */

	for (i = 0, item = GMT_NOTSET; item == GMT_NOTSET && i < API->n_objects; i++) {
		if (!(S_obj = API->object[i])) continue;	/* Skip empty objects */
		if (S_obj->status != GMT_IS_UNUSED) {	/* Has already been read - do we wish to reset this status ? */
			if (family == GMT_IS_GRID && (mode & GMT_DATA_ONLY)) {	/* Requesting data only means we already did the header so OK to reset status */
				if (mode & GMT_GRID_IS_COMPLEX_MASK) {	/* Complex grids are read in stages so handled separately */
					/* Check if complex grid already has one layer and that we are reading the next layer */
					struct GMT_GRID *G = gmtapi_get_grid_data (resource);	/* Get pointer to this grid */
					unsigned int cmplx = mode & GMT_GRID_IS_COMPLEX_MASK;
					if (G->header->complex_mode & GMT_GRID_IS_COMPLEX_MASK && G->header->complex_mode != cmplx && filename) {
						/* Apparently so, either had real and now getting imag, or vice versa. */
						gmt_M_str_free (S_obj->filename);	/* Free previous grid name and replace with current name */
						S_obj->filename = strdup (filename);
						mode |= GMT_IO_RESET;	/* Reset so we may read in the 2nd component grid */
					}
				}
				else	/* Just read the header earlier, do the reset */
					mode |= GMT_IO_RESET;	/* Reset so we may read in the grid data */
			}
			else if (family == GMT_IS_IMAGE && (mode & GMT_DATA_ONLY))	/* Requesting data only means we already did the header so OK to reset status */
				mode |= GMT_IO_RESET;	/* Reset so we may read in the image data */

			if (!(mode & GMT_IO_RESET)) continue;	/* No reset above so we refuse to do the work */
			S_obj->status = GMT_IS_UNUSED;	/* Reset so we may continue to read it */
		}
		if (direction != GMT_NOTSET && (int)S_obj->direction != direction) continue;	/* Wrong direction */
		if (family != GMT_NOTSET && (int)S_obj->family != family) continue;			/* Wrong family */
		if (geometry != GMT_NOTSET && (int)S_obj->geometry != geometry) continue;	/* Wrong geometry */
		if (resource && S_obj->resource == resource) item = S_obj->ID;	/* Yes: already registered. */
	}
	return (item);		/* The ID of the object (or GMT_NOTSET) */
}

/*! . */
GMT_LOCAL struct GMT_PALETTE * gmtapi_import_palette (struct GMTAPI_CTRL *API, int object_ID, unsigned int mode) {
	/* Does the actual work of loading in a CPT palette table.
 	 * The mode controls how the back-, fore-, NaN-color entries are handled.
	 * Note: Memory is allocated to hold the GMT_PALETTE structure except for method GMT_IS_REFERENCE.
	 */

	int item;
	unsigned int flag = 0, kind;
	char tmp_cptfile[GMT_LEN64] = {""};
	struct GMT_PALETTE *P_obj = NULL, *P_orig = NULL;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMT_CTRL *GMT = API->GMT;

	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_palette: Passed ID = %d and mode = %d\n", object_ID, mode);

	if (object_ID == GMT_NOTSET) return_null (API, GMT_NO_INPUT);	/* Need to know the ID to do anything */
	if ((item = gmtlib_validate_id (API, GMT_IS_PALETTE, object_ID, GMT_IN, GMTAPI_OPTION_INPUT)) == GMT_NOTSET)
		return_null (API, API->error);	/* Failed basic sanity check */

	S_obj = API->object[item];	/* Use S_obj as shorthand */
	if (S_obj->status != GMT_IS_UNUSED) { /* Already read this resource before; are we allowed to re-read? */
		if (S_obj->method == GMT_IS_STREAM || S_obj->method == GMT_IS_FDESC)
			return_null (API, GMT_READ_ONCE); /* Not allowed to re-read streams since they are gone */
		if (!(mode & GMT_IO_RESET)) return_null (API, GMT_READ_ONCE);	/* Not authorized to re-read */
	}

	/* OK, passed sanity and is allowed to read */

	switch (S_obj->method) {	/* From where are we getting the palette ? */
		case GMT_IS_FILE:
			/* gmtlib_read_cpt will report where it is reading from if level is GMT_MSG_INFORMATION */
			GMT_Report (API, GMT_MSG_INFORMATION, "Reading CPT from %s %s\n", gmtapi_method (S_obj->method), S_obj->filename);
			snprintf (tmp_cptfile, GMT_LEN64, "gmtapi_colors2cpt_%d.cpt", (int)getpid());
			if (!strcmp (tmp_cptfile, S_obj->filename))	/* This file was created when we gave "name" as red,blue,... instead */
			 	flag = GMT_CPT_TEMPORARY;	/* So we can take action later when we learn if user wanted a discrete or continuous CPT */
			if ((P_obj = gmtlib_read_cpt (GMT, S_obj->filename, S_obj->method, mode|flag)) == NULL)
				return_null (API, GMT_CPT_READ_ERROR);
			if (flag == GMT_CPT_TEMPORARY) {	/* Remove the temporary file */
				GMT_Report (API, GMT_MSG_DEBUG, "Remove temporary CPT %s\n", S_obj->filename);
				remove (tmp_cptfile);
			}
			S_obj->resource = P_obj;	/* Retain pointer to the allocated data so we use garbage collection later */
			break;
		case GMT_IS_STREAM:
 			/* gmtlib_read_cpt will report where it is reading from if level is GMT_MSG_INFORMATION */
			kind = (S_obj->fp == GMT->session.std[GMT_IN]) ? 0 : 1;	/* 0 if stdin, 1 otherwise for user pointer */
			GMT_Report (API, GMT_MSG_INFORMATION, "Reading CPT from %s %s stream\n", gmtapi_method (S_obj->method), GMT_stream[kind]);
			if ((P_obj = gmtlib_read_cpt (GMT, S_obj->fp, S_obj->method, mode)) == NULL)
				return_null (API, GMT_CPT_READ_ERROR);
			S_obj->resource = P_obj;	/* Retain pointer to the allocated data so we use garbage collection later */
			break;
		case GMT_IS_FDESC:
			/* gmtlib_read_cpt will report where it is reading from if level is GMT_MSG_INFORMATION */
			kind = (*((int *)S_obj->fp) == GMT_IN) ? 0 : 1;	/* 0 if stdin, 1 otherwise for user pointer */
			GMT_Report (API, GMT_MSG_INFORMATION, "Reading CPT from %s %s stream\n", gmtapi_method (S_obj->method), GMT_stream[kind]);
			if ((P_obj = gmtlib_read_cpt (GMT, S_obj->fp, S_obj->method, mode)) == NULL)
				return_null (API, GMT_CPT_READ_ERROR);
			S_obj->resource = P_obj;	/* Retain pointer to the allocated data so we use garbage collection later */
			break;
		case GMT_IS_DUPLICATE:	/* Duplicate the input CPT palette */
			GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating CPT from GMT_PALETTE memory location\n");
			if ((P_orig = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			if ((P_obj = GMT_Duplicate_Data (API, GMT_IS_PALETTE, mode, P_orig)) == NULL)
				return_null (API, GMT_MEMORY_ERROR);
			break;
		case GMT_IS_REFERENCE:	/* Just pass memory location, so nothing is allocated */
			GMT_Report (API, GMT_MSG_INFORMATION, "Referencing CPT from GMT_PALETTE memory location\n");
			if ((P_obj = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			gmtlib_init_cpt (GMT, P_obj);	/* Make sure derived quantities are set */
			break;
		default:	/* Barking up the wrong tree here... */
			GMT_Report (API, GMT_MSG_ERROR, "Wrong method used to import GMT_PALETTE\n");
			return_null (API, GMT_NOT_A_VALID_METHOD);
			break;
	}
	S_obj->status = GMT_IS_USED;	/* Mark as read */

	return (P_obj);	/* Pass back the palette */
}

/*! . */
GMT_LOCAL int gmtapi_export_palette (struct GMTAPI_CTRL *API, int object_ID, unsigned int mode, struct GMT_PALETTE *P_obj) {
	/* Does the actual work of writing out the specified CPT to a destination.
	 * The mode controls how the back, for, NaN color entries are handled.
	 */
	int item, error;
	unsigned int kind;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMT_PALETTE *P_copy = NULL;
	struct GMT_PALETTE_HIDDEN *PH = NULL;
	struct GMT_CTRL *GMT = API->GMT;

	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_export_palette: Passed ID = %d and mode = %d\n", object_ID, mode);

	if (object_ID == GMT_NOTSET) return (gmtlib_report_error (API, GMT_OUTPUT_NOT_SET));
	if ((item = gmtlib_validate_id (API, GMT_IS_PALETTE, object_ID, GMT_OUT, GMT_NOTSET)) == GMT_NOTSET) return (gmtlib_report_error (API, API->error));

	S_obj = API->object[item];	/* This is the API object for the output destination */
	if (S_obj->status != GMT_IS_UNUSED && !(mode & GMT_IO_RESET)) {	/* Only allow writing of a data set once, unless we override by resetting the mode */
		return (gmtlib_report_error (API, GMT_WRITTEN_ONCE));
	}
	if (mode & GMT_IO_RESET) mode -= GMT_IO_RESET;

	/* Passed sanity and allowed to write */

	/* If need to assign non-default BFN do it now so external interfaces can access that too */
	if (mode & GMT_CPT_EXTEND_BNF) {	/* Use low and high colors as back and foreground */
		gmt_M_rgb_copy (P_obj->bfn[GMT_BGD].rgb, P_obj->data[0].rgb_low);
		gmt_M_rgb_copy (P_obj->bfn[GMT_FGD].rgb, P_obj->data[P_obj->n_colors-1].rgb_high);
		gmt_M_rgb_copy (P_obj->bfn[GMT_BGD].hsv, P_obj->data[0].hsv_low);
		gmt_M_rgb_copy (P_obj->bfn[GMT_FGD].hsv, P_obj->data[P_obj->n_colors-1].hsv_high);
	}

	switch (S_obj->method) {	/* File, array, stream etc ? */
		case GMT_IS_FILE:
			/* gmtlib_write_cpt will report where it is writing from if level is GMT_MSG_INFORMATION */
			GMT_Report (API, GMT_MSG_INFORMATION, "Write CPT to %s %s\n", gmtapi_method (S_obj->method), S_obj->filename);
			if ((error = gmtlib_write_cpt (GMT, S_obj->filename, S_obj->method, mode, P_obj))) return (gmtlib_report_error (API, error));
			break;
	 	case GMT_IS_STREAM:
			/* gmtlib_write_cpt will report where it is writing from if level is GMT_MSG_INFORMATION */
			kind = (S_obj->fp == GMT->session.std[GMT_OUT]) ? 0 : 1;	/* 0 if stdout, 1 otherwise for user pointer */
			GMT_Report (API, GMT_MSG_INFORMATION, "Write CPT to %s %s output stream\n", gmtapi_method (S_obj->method), GMT_stream[kind]);
			if ((error = gmtlib_write_cpt (GMT, S_obj->fp, S_obj->method, mode, P_obj))) return (gmtlib_report_error (API, error));
			break;
	 	case GMT_IS_FDESC:
			/* gmtlib_write_cpt will report where it is writing from if level is GMT_MSG_INFORMATION */
			kind = (*((int *)S_obj->fp) == GMT_OUT) ? 0 : 1;	/* 0 if stdout, 1 otherwise for user pointer */
			GMT_Report (API, GMT_MSG_INFORMATION, "Write CPT to %s %s output stream\n", gmtapi_method (S_obj->method), GMT_stream[kind]);
			if ((error = gmtlib_write_cpt (GMT, S_obj->fp, S_obj->method, mode, P_obj))) return (gmtlib_report_error (API, error));
			break;
		case GMT_IS_DUPLICATE:	/* Duplicate the input cpt */
			if (S_obj->resource) return (gmtlib_report_error (API, GMT_PTR_NOT_NULL));	/* The output resource must be NULL */
			GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating CPT to GMT_PALETTE memory location\n");
			P_copy = gmtlib_create_palette (GMT, P_obj->n_colors);
			gmtlib_copy_palette (GMT, P_copy, P_obj);
			S_obj->resource = P_copy;	/* Set resource pointer from object to this palette */
			break;
		case GMT_IS_REFERENCE:	/* Just pass memory location */
			if (S_obj->resource) return (gmtlib_report_error (API, GMT_PTR_NOT_NULL));	/* The output resource must be NULL */
			GMT_Report (API, GMT_MSG_INFORMATION, "Referencing CPT to GMT_PALETTE memory location\n");
			PH = gmt_get_C_hidden (P_obj);
			PH->alloc_level = S_obj->alloc_level;	/* Since we are passing it up to the caller */
			S_obj->resource = P_obj;	/* Set resource pointer from object to this palette */
			break;
		default:
			GMT_Report (API, GMT_MSG_ERROR, "Wrong method used to export CPTs\n");
			return (gmtlib_report_error (API, GMT_NOT_A_VALID_METHOD));
			break;
	}
	S_obj->status = GMT_IS_USED;	/* Mark as written */

	return GMT_NOERROR;
}

/*! . */
GMT_LOCAL struct GMT_POSTSCRIPT * gmtapi_import_postscript (struct GMTAPI_CTRL *API, int object_ID, unsigned int mode) {
	/* Does the actual work of loading in a PS struct.
 	 * The mode is not used yet.
	 * Note: Memory is allocated to hold the GMT_POSTSCRIPT structure except for method GMT_IS_REFERENCE.
	 */

	int item;
	unsigned int kind;
	struct GMT_POSTSCRIPT *P_obj = NULL, *P_orig = NULL;
	struct GMT_POSTSCRIPT_HIDDEN *PH = NULL;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMT_CTRL *GMT = API->GMT;

	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_postscript: Passed ID = %d and mode = %d\n", object_ID, mode);

	if (object_ID == GMT_NOTSET) return_null (API, GMT_NO_INPUT);
	if ((item = gmtlib_validate_id (API, GMT_IS_POSTSCRIPT, object_ID, GMT_IN, GMTAPI_OPTION_INPUT)) == GMT_NOTSET)
		return_null (API, API->error);

	S_obj = API->object[item];	/* Use S_obj as shorthand */
	if (S_obj->status != GMT_IS_UNUSED) { /* Already read this resource before; are we allowed to re-read? */
		if (S_obj->method == GMT_IS_STREAM || S_obj->method == GMT_IS_FDESC) return_null (API, GMT_READ_ONCE); /* Not allowed to re-read streams */
		if (!(mode & GMT_IO_RESET)) return_null (API, GMT_READ_ONCE);	/* Not authorized to re-read */
	}

	/* Passed sanity and allowed to read */

	switch (S_obj->method) {	/* File, array, stream etc ? */
		case GMT_IS_FILE:
			/* gmtlib_read_ps will report where it is reading from if level is GMT_MSG_INFORMATION */
			GMT_Report (API, GMT_MSG_INFORMATION, "Reading PS from %s %s\n", gmtapi_method (S_obj->method), S_obj->filename);
			if ((P_obj = gmtlib_read_ps (GMT, S_obj->filename, S_obj->method, mode)) == NULL)
				return_null (API, GMT_CPT_READ_ERROR);
			S_obj->resource = P_obj;	/* Retain pointer to the allocated data so we use garbage collection later */
			break;
		case GMT_IS_STREAM:
 			/* gmtlib_read_ps will report where it is reading from if level is GMT_MSG_INFORMATION */
			kind = (S_obj->fp == GMT->session.std[GMT_IN]) ? 0 : 1;	/* 0 if stdin, 1 otherwise for user pointer */
			GMT_Report (API, GMT_MSG_INFORMATION, "Reading PS from %s %s stream\n", gmtapi_method (S_obj->method), GMT_stream[kind]);
			if ((P_obj = gmtlib_read_ps (GMT, S_obj->fp, S_obj->method, mode)) == NULL)
				return_null (API, GMT_CPT_READ_ERROR);
			S_obj->resource = P_obj;	/* Retain pointer to the allocated data so we use garbage collection later */
			break;
		case GMT_IS_FDESC:
			/* gmtlib_read_ps will report where it is reading from if level is GMT_MSG_INFORMATION */
			kind = (*((int *)S_obj->fp) == GMT_IN) ? 0 : 1;	/* 0 if stdin, 1 otherwise for user pointer */
			GMT_Report (API, GMT_MSG_INFORMATION, "Reading PS from %s %s stream\n", gmtapi_method (S_obj->method), GMT_stream[kind]);
			if ((P_obj = gmtlib_read_ps (GMT, S_obj->fp, S_obj->method, mode)) == NULL)
				return_null (API, GMT_CPT_READ_ERROR);
			S_obj->resource = P_obj;	/* Retain pointer to the allocated data so we use garbage collection later */
			break;
		case GMT_IS_DUPLICATE:	/* Duplicate the input CPT palette */
			GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating PS from GMT_POSTSCRIPT memory location\n");
			if ((P_orig = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			if ((P_obj = GMT_Duplicate_Data (API, GMT_IS_POSTSCRIPT, mode, P_orig)))
				return_null (API, GMT_MEMORY_ERROR);
			break;
		case GMT_IS_REFERENCE:	/* Just pass memory location, so nothing is allocated */
			GMT_Report (API, GMT_MSG_INFORMATION, "Referencing PS from GMT_POSTSCRIPT memory location\n");
			if ((P_obj = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			PH = gmt_get_P_hidden (P_obj);
			PH->alloc_level = S_obj->alloc_level;	/* Since we are passing it up to the caller */
			break;
		default:	/* Barking up the wrong tree here... */
			GMT_Report (API, GMT_MSG_ERROR, "Wrong method used to import PS\n");
			return_null (API, GMT_NOT_A_VALID_METHOD);
			break;
	}
	PH = gmt_get_P_hidden (P_obj);
	S_obj->alloc_mode = PH->alloc_mode;
	S_obj->status = GMT_IS_USED;	/* Mark as read */

	return (P_obj);	/* Pass back the PS */
}

/*! . */
GMT_LOCAL int gmtapi_export_postscript (struct GMTAPI_CTRL *API, int object_ID, unsigned int mode, struct GMT_POSTSCRIPT *P_obj) {
	/* Does the actual work of writing out the specified PS to a destination.
	 * The mode not used yet.
	 */
	int item, error;
	unsigned int kind;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMT_POSTSCRIPT *P_copy = NULL;
	struct GMT_POSTSCRIPT_HIDDEN *PH = NULL;
	struct GMT_CTRL *GMT = API->GMT;

	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_export_postscript: Passed ID = %d and mode = %d\n", object_ID, mode);

	if (object_ID == GMT_NOTSET) return (gmtlib_report_error (API, GMT_OUTPUT_NOT_SET));
	if ((item = gmtlib_validate_id (API, GMT_IS_POSTSCRIPT, object_ID, GMT_OUT, GMT_NOTSET)) == GMT_NOTSET) return (gmtlib_report_error (API, API->error));

	S_obj = API->object[item];	/* This is the API object for the output destination */
	if (S_obj->status != GMT_IS_UNUSED && !(mode & GMT_IO_RESET)) {	/* Only allow writing of a data set once, unless we override by resetting the mode */
		return (gmtlib_report_error (API, GMT_WRITTEN_ONCE));
	}
	if (mode & GMT_IO_RESET) mode -= GMT_IO_RESET;

	/* Passed sanity and allowed to write */

	switch (S_obj->method) {	/* File, array, stream etc ? */
		case GMT_IS_FILE:
			/* gmtlib_write_ps will report where it is writing from if level is GMT_MSG_INFORMATION */
			GMT_Report (API, GMT_MSG_INFORMATION, "Write PS to %s %s\n", gmtapi_method (S_obj->method), S_obj->filename);
			if ((error = gmtlib_write_ps (GMT, S_obj->filename, S_obj->method, mode, P_obj))) return (gmtlib_report_error (API, error));
			break;
	 	case GMT_IS_STREAM:
			/* gmtlib_write_ps will report where it is writing from if level is GMT_MSG_INFORMATION */
			kind = (S_obj->fp == GMT->session.std[GMT_OUT]) ? 0 : 1;	/* 0 if stdout, 1 otherwise for user pointer */
			GMT_Report (API, GMT_MSG_INFORMATION, "Write PS to %s %s output stream\n", gmtapi_method (S_obj->method), GMT_stream[kind]);
			if ((error = gmtlib_write_ps (GMT, S_obj->fp, S_obj->method, mode, P_obj))) return (gmtlib_report_error (API, error));
			break;
	 	case GMT_IS_FDESC:
			/* gmtlib_write_ps will report where it is writing from if level is GMT_MSG_INFORMATION */
			kind = (*((int *)S_obj->fp) == GMT_OUT) ? 0 : 1;	/* 0 if stdout, 1 otherwise for user pointer */
			GMT_Report (API, GMT_MSG_INFORMATION, "Write PS to %s %s output stream\n", gmtapi_method (S_obj->method), GMT_stream[kind]);
			if ((error = gmtlib_write_ps (GMT, S_obj->fp, S_obj->method, mode, P_obj))) return (gmtlib_report_error (API, error));
			break;
		case GMT_IS_DUPLICATE:		/* Duplicate the input cpt */
			if (S_obj->resource) return (gmtlib_report_error (API, GMT_PTR_NOT_NULL));	/* The output resource must be NULL */
			GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating PS to GMT_POSTSCRIPT memory location\n");
			if ((P_copy = GMT_Duplicate_Data (API, GMT_IS_POSTSCRIPT, mode, P_obj)))
				return (gmtlib_report_error (API, GMT_MEMORY_ERROR));
			S_obj->resource = P_copy;	/* Set resource pointer from object to this PS */
			break;
		case GMT_IS_REFERENCE:	/* Just pass memory location */
			if (S_obj->resource) return (gmtlib_report_error (API, GMT_PTR_NOT_NULL));	/* The output resource must be NULL */
			GMT_Report (API, GMT_MSG_INFORMATION, "Referencing PS to GMT_POSTSCRIPT memory location\n");
			PH = gmt_get_P_hidden (P_obj);
			PH->alloc_level = S_obj->alloc_level;	/* Since we are passing it up to the caller */
			S_obj->resource = P_obj;	/* Set resource pointer from object to this PS */
			break;
		default:
			GMT_Report (API, GMT_MSG_ERROR, "Wrong method used to export PS\n");
			return (gmtlib_report_error (API, GMT_NOT_A_VALID_METHOD));
			break;
	}
	S_obj->status = GMT_IS_USED;	/* Mark as written */

	return GMT_NOERROR;
}

#if 0
/* PW: I think this was commented out and not used as Florian wanted variable column files to be OK (?) */
GMT_LOCAL bool gmtapi_col_check (struct GMT_DATATABLE *T, uint64_t *n_cols) {
	uint64_t seg;
	/* Checks that all segments in this table has the correct number of columns.
	 * If *n_cols == 0 we set it to the number of columns found in the first segment. */

	for (seg = 0; seg < T->n_segments; seg++) {
		if ((*n_cols) == 0 && seg == 0) *n_cols = T->segment[seg]->n_columns;
		if (T->segment[seg]->n_columns != (*n_cols)) return (true);
	}
	return (false);	/* All is well */
}
#endif

/*! . */
GMT_LOCAL void gmtapi_increment_d (struct GMT_DATASET *D_obj, uint64_t n_rows, uint64_t n_columns, uint64_t n_seg) {
	/* Increment dimensions for this single dataset's single table's last segment */
	uint64_t last_seg = n_seg - 1;
	assert (n_seg > 0);
	D_obj->table[D_obj->n_tables]->segment[last_seg]->n_rows = n_rows;
	D_obj->table[D_obj->n_tables]->segment[last_seg]->n_columns = D_obj->table[D_obj->n_tables]->n_columns = n_columns;
	D_obj->table[D_obj->n_tables]->n_records += n_rows;
	D_obj->table[D_obj->n_tables]->n_segments = n_seg;
	D_obj->n_tables++;	/* Since we just read one table */
}

GMT_LOCAL void gmtapi_switch_cols (struct GMT_CTRL *GMT, struct GMT_DATASET *D, unsigned int direction) {
	uint64_t tbl, seg;
	struct GMT_DATASEGMENT *S = NULL;

	/* Implements the effect of -: when we are not writing to file */

	if (D->n_columns < 2 || !GMT->current.setting.io_lonlat_toggle[direction]) return;	/* Nothing to do */
	for (tbl = 0; tbl < D->n_tables; tbl++) {
		for (seg = 0; seg < D->table[tbl]->n_segments; seg++) {
			S = D->table[tbl]->segment[seg];
			gmt_M_doublep_swap (S->data[GMT_X], S->data[GMT_Y]);
		}
	}
}

GMT_LOCAL bool gmtapi_vector_data_must_be_duplicated (struct GMTAPI_CTRL *API, struct GMT_VECTOR *V) {
    /* Check if referenced vector data arrays must be scaled/offset and hence must be duplicated instead */
    for (unsigned int col = 0; col < V->n_columns; col++) {
        if (API->GMT->common.i.col.select && API->GMT->current.io.col[GMT_IN][col].convert) return (true); /* Cannot pass as read-only if it must be converted */
    }
    return false;    /* Seems OK */
}

/*! . */
GMT_LOCAL struct GMT_DATASET *gmtapi_import_dataset(struct GMTAPI_CTRL *API, int object_ID, unsigned int mode) {
	/* Does the actual work of loading in the entire virtual data set (possibly via many sources)
	 * If object_ID == GMT_NOTSET we get all registered input tables, otherwise we just get the one requested.
	 * Note: Memory is allocated for the Dataset except for method GMT_IS_REFERENCE.
	 */

	int item, first_item = 0, this_item = GMT_NOTSET, last_item, new_item, new_ID, status;
	unsigned int geometry = GMT_IS_PLP, n_used = 0, method, smode, type = GMT_READ_DATA, col_pos_out;
	bool allocate = false, update = false, diff_types, use_GMT_io, greenwich = true;
	bool via = false, got_data = false, check_col_switch = false, regit = false;
	size_t n_alloc, s_alloc = GMT_SMALL_CHUNK;
	uint64_t tbl = 0, tbl_in, row, seg, col, ij, n_records = 0, n_columns = 0, col_pos, n_use;
	p_func_uint64_t GMT_2D_to_index = NULL;
	GMT_getfunction api_get_val = NULL;
	struct GMT_DATASET *D_obj = NULL, *Din_obj = NULL;
	struct GMT_DATASEGMENT *S = NULL;
	struct GMT_MATRIX *M_obj = NULL;
	struct GMT_VECTOR *V_obj = NULL;
	struct GMT_DATASET_HIDDEN *DH = NULL, *DHi = NULL;
	struct GMT_DATATABLE_HIDDEN *TH = NULL;
	struct GMT_DATASEGMENT_HIDDEN *SH = NULL;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMT_CTRL *GMT = API->GMT;

	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_dataset: Passed ID = %d and mode = %d\n", object_ID, mode);

	if (object_ID == GMT_NOTSET) {	/* Means there is more than one source: Merge all registered data tables into a single virtual data set */
		last_item = API->n_objects - 1;	/* Must check all registered objects */
		allocate = true;
		n_alloc = GMT_TINY_CHUNK;	/* We don't expect that many files to be given initially */
	}
	else {		/* Requested a single, specific data table/file */
		int flag = (API->module_input) ? GMTAPI_MODULE_INPUT : GMTAPI_OPTION_INPUT;	/* Needed by Validate_ID */
		if ((first_item = gmtlib_validate_id (API, GMT_IS_DATASET, object_ID, GMT_IN, flag)) == GMT_NOTSET)
			return_null (API, API->error);
		last_item = first_item;
		n_alloc = 1;
	}

	/* Allocate a single data set and an initial allocated list of n_alloc tables */
	if ((D_obj = gmt_get_dataset (GMT)) == NULL) return NULL;
	DH = gmt_get_DD_hidden (D_obj);
	if ((D_obj->table = gmt_M_memory (GMT, NULL, n_alloc, struct GMT_DATATABLE *)) == NULL) return NULL;
	DH->alloc_mode = GMT_ALLOC_INTERNALLY;	/* So GMT_* modules can free this memory (may override below) */
	DH->alloc_level = GMT->hidden.func_level;	/* So GMT_* modules can free this memory (may override below) */
	use_GMT_io = !(mode & GMT_IO_ASCII);		/* false if we insist on ASCII reading */
	GMT->current.io.seg_no = GMT->current.io.rec_no = GMT->current.io.rec_in_tbl_no = GMT->current.io.data_record_number_in_tbl[GMT_IN] = GMT->current.io.data_record_number_in_seg[GMT_IN] = 0;	/* Reset for each new dataset */
	if (GMT->common.R.active[RSET] && GMT->common.R.wesn[XLO] < -180.0 && GMT->common.R.wesn[XHI] > -180.0) greenwich = false;

	for (item = first_item; item <= last_item; item++) {	/* Look through all sources for registered inputs (or just one) */
		S_obj = API->object[item];	/* S_obj is the current data object */
		if (!S_obj) {	/* Probably not a good sign. NOTE: Probably cannot happen since skipped in api_next_source, no? */
			GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_dataset: Skipped empty object (item = %d)\n", item);
			continue;
		}
		if (!S_obj->selected) continue;			/* Registered, but not selected */
		if (S_obj->direction == GMT_OUT) continue;	/* We're doing reading here, so skip output objects */
		if (S_obj->family != GMT_IS_DATASET) continue;	/* We're doing datasets here, so skip other data types */
		if (API->module_input && !S_obj->module_input) continue;	/* Do not mix module-inputs and option inputs if knowable */
		if (S_obj->status != GMT_IS_UNUSED) { 	/* Already read this resource before; are we allowed to re-read? */
			if (S_obj->method == GMT_IS_STREAM || S_obj->method == GMT_IS_FDESC) {
				gmt_M_free (GMT, D_obj->table);		gmt_M_free (GMT, D_obj);
				return_null (API, GMT_READ_ONCE);	/* Cannot re-read streams */
			}
			if (!(mode & GMT_IO_RESET)) {
				gmt_M_free (GMT, D_obj->table);		gmt_M_free (GMT, D_obj);
				return_null (API, GMT_READ_ONCE);	/* Not authorized to re-read */
			}
		}
		if (this_item == GMT_NOTSET) this_item = item;	/* First item that worked */
		via = false;
		geometry = (GMT->common.a.output) ? GMT->common.a.geometry : S_obj->geometry;	/* When reading GMT and writing OGR/GMT we must make sure we set this first */
		method = gmtapi_set_method (S_obj);	/* Get the actual method to use */
		/* At the time an external vector was created via GMT_Open_VirtualFile there is not yet any knowledge if this data
		 * will be passed to a module with options -i that could require scaling, offsetting, or taking the log of the data.
		 * If that is the case then we cannot pass via reference but must switch method to duplicate. */
		if (method == (GMT_IS_REFERENCE|GMT_VIA_VECTOR) && gmtapi_vector_data_must_be_duplicated (API, S_obj->resource))
			method = GMT_IS_DUPLICATE|GMT_VIA_VECTOR;   /* We need to adjust at least one vector due to -i+s+o+l so must duplicate input rather than reference */

		switch (method) {	/* File, array, stream, reference, etc ? */
	 		case GMT_IS_FILE:	/* Import all the segments, then count total number of records */
#ifdef SET_IO_MODE
				if (item == first_item) gmt_setmode (GMT, GMT_IN);	/* Windows may need to switch read mode from text to binary */
#endif
				/* gmtlib_read_table will report where it is reading from if level is GMT_MSG_INFORMATION */
				GMT->current.io.first_rec = true;
				if (GMT->current.io.ogr == GMT_OGR_TRUE && D_obj->n_tables > 0) {	/* Only single tables if GMT/OGR */
					gmt_M_free (GMT, D_obj->table);		gmt_M_free (GMT, D_obj);
					return_null (API, GMT_OGR_ONE_TABLE_ONLY);
				}
				GMT_Report (API, GMT_MSG_INFORMATION,
				            "Reading %s from %s %s\n", GMT_family[S_obj->family], gmtapi_method (S_obj->method), S_obj->filename);
				if ((D_obj->table[D_obj->n_tables] = gmtlib_read_table (GMT, S_obj->filename, S_obj->method, greenwich, &geometry, &type, use_GMT_io)) == NULL)
					continue;		/* Ran into an empty file (e.g., /dev/null or equivalent). Skip to next item, */
				TH = gmt_get_DT_hidden (D_obj->table[D_obj->n_tables]);
				TH->id = D_obj->n_tables;	/* Give sequential internal object_ID numbers to tables */
				D_obj->n_tables++;	/* Since we just read one */
				update = true;		/* Have reason to update min/max when done */
				break;

			case GMT_IS_STREAM:	/* Import all the segments, then count total number of records */
	 		case GMT_IS_FDESC:
				/* gmtlib_read_table will report where it is reading from if level is GMT_MSG_INFORMATION */
#ifdef SET_IO_MODE
				if (item == first_item) gmt_setmode (GMT, GMT_IN);	/* Windows may need to switch read mode from text to binary */
#endif
				GMT->current.io.first_rec = true;
				if (GMT->current.io.ogr == GMT_OGR_TRUE && D_obj->n_tables > 0)	{	/* Only single tables if GMT/OGR */
					gmt_M_free (GMT, D_obj);	return_null (API, GMT_OGR_ONE_TABLE_ONLY);
				}
				GMT_Report (API, GMT_MSG_INFORMATION, "Reading %s from %s %" PRIxS "\n", GMT_family[S_obj->family], gmtapi_method (S_obj->method), (size_t)S_obj->fp);
				if ((D_obj->table[D_obj->n_tables] = gmtlib_read_table (GMT, S_obj->fp, S_obj->method, greenwich, &geometry, &type, use_GMT_io)) == NULL) continue;		/* Ran into an empty file (e.g., /dev/null or equivalent). Skip to next item, */
				TH = gmt_get_DT_hidden (D_obj->table[D_obj->n_tables]);
				TH->id = D_obj->n_tables;	/* Give sequential internal object_ID numbers to tables */
				D_obj->n_tables++;	/* Since we just read one */
				update = true;		/* Have reason to update min/max when done */
				break;

			case GMT_IS_DUPLICATE:	/* Duplicate the input dataset */
				if (S_obj->resource == NULL) return_null (API, GMT_PTR_IS_NULL);
				if (GMT->common.q.mode == GMT_RANGE_ROW_IN || GMT->common.q.mode == GMT_RANGE_DATA_IN)
					GMT_Report (API, GMT_MSG_WARNING, "Row-selection via -qi is not implemented for GMT_IS_DUPLICATE with GMT_IS_DATASET external memory objects\n");
				GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating data table from GMT_DATASET memory location\n");
				gmt_set_dataset_verify (GMT, S_obj->resource);    /* Basic sanity checking of incoming dataset */
				Din_obj = gmt_duplicate_dataset (GMT, S_obj->resource, GMT_ALLOC_NORMAL|GMT_ALLOC_VIA_ICOLS, NULL);
				if ((tbl + Din_obj->n_tables) >= n_alloc) {	/* Need more space to hold these new tables */
					n_alloc += Din_obj->n_tables;
					if ((D_obj->table = gmt_M_memory (GMT, D_obj->table, n_alloc, struct GMT_DATATABLE *)) == NULL) return NULL;
				}
				for (tbl_in = 0; tbl_in < Din_obj->n_tables; tbl_in++, tbl++)	/* Pass over the pointers only */
					D_obj->table[tbl] = Din_obj->table[tbl_in];
				gmtlib_free_dataset_misc (GMT, Din_obj);	/* Free this object but not its tables */
				gmt_M_free (GMT, Din_obj);
				D_obj->n_tables = tbl;
				D_obj->geometry = S_obj->geometry;	/* Since provided when registered */
				check_col_switch = true;
				update = regit = via = true;		/* Have reason to update min/max as well as registering D_obj when done */
				break;

			case GMT_IS_REFERENCE:	/* Just pass memory locations to tables */
				if ((Din_obj = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
				gmt_set_dataset_verify (GMT, Din_obj);   /* Basic sanity checking of incoming dataset */
				if (GMT->common.q.mode == GMT_RANGE_ROW_IN || GMT->common.q.mode == GMT_RANGE_DATA_IN)
					GMT_Report (API, GMT_MSG_WARNING, "Row-selection via -qi is not implemented for GMT_IS_REFERENCE with GMT_IS_DATASET external memory objects\n");
				GMT_Report (API, GMT_MSG_INFORMATION, "Referencing data table from GMT_DATASET memory location\n");
				DHi = gmt_get_DD_hidden (Din_obj);
				if ((tbl + Din_obj->n_tables) >= n_alloc) {	/* Need more space to hold these new tables */
					n_alloc += Din_obj->n_tables;
					if ((D_obj->table = gmt_M_memory (GMT, D_obj->table, n_alloc, struct GMT_DATATABLE *)) == NULL) return NULL;
				}
				for (tbl_in = 0; tbl_in < Din_obj->n_tables; tbl_in++, tbl++) {	/* Pass over the pointers only */
					D_obj->table[tbl] = Din_obj->table[tbl_in];
					Din_obj->table[tbl_in] = NULL;	/* Since passed to D_obj */
				}
				Din_obj->n_tables = 0;	/* Only the husk remains of this fruit */
				D_obj->n_tables = tbl;
				D_obj->geometry = S_obj->geometry;	/* Since provided when registered */
				DH->alloc_mode = DHi->alloc_mode;	/* Must use whatever alloc_mode the input reference had */
				DH->alloc_level = DHi->alloc_level;	/* Must use whatever alloc_level the input reference had */
				check_col_switch = true;
				update = regit = via = true;		/* Have reason to update min/max as well as registering D_obj when done */
				break;

		 	case GMT_IS_DUPLICATE|GMT_VIA_MATRIX:	/* There is no difference since in both cases we must allocate dataset arrays */
		 	case GMT_IS_REFERENCE|GMT_VIA_MATRIX:
				/* Each matrix source becomes a separate table with a single segment unless there are NaN-records as segment headers */
				if ((M_obj = S_obj->resource) == NULL) {
					gmt_M_free (GMT, D_obj);
					return_null (API, GMT_PTR_IS_NULL);
				}
				GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating data table from user matrix location\n");
				if (GMT->common.q.mode == GMT_RANGE_ROW_IN || GMT->common.q.mode == GMT_RANGE_DATA_IN)
					GMT_Report (API, GMT_MSG_WARNING, "Row-selection via -qi is not implemented for [GMT_IS_DUPLICATE,GMT_IS_REFERENCE]|GMT_IS_MATRIX external memory objects\n");
				/* Allocate a table with a single segment given matrix dimensions, but if nan-record we may end up with more segments */
				smode = (M_obj->text) ? GMT_WITH_STRINGS : GMT_NO_STRINGS;
				if (smode) type = GMT_READ_MIXED;	/* If a matrix has text we have a mixed record */
				n_columns = (GMT->common.i.col.select) ? GMT->common.i.col.n_cols : M_obj->n_columns;
				if ((D_obj->table[D_obj->n_tables] = gmt_get_table (GMT)) == NULL) return NULL;
				if ((D_obj->table[D_obj->n_tables]->segment = gmt_M_memory (GMT, NULL, s_alloc, struct GMT_DATASEGMENT *)) == NULL) return NULL;
				S = D_obj->table[D_obj->n_tables]->segment[0] = GMT_Alloc_Segment (API, smode, M_obj->n_rows, n_columns, NULL, NULL);
				if (S == NULL) return NULL;
				if ((GMT_2D_to_index = gmtapi_get_2d_to_index (API, M_obj->shape, GMT_GRID_IS_REAL)) == NULL)
					return_null (API, GMT_WRONG_MATRIX_SHAPE);
				if ((api_get_val = gmtapi_select_get_function (API, M_obj->type)) == NULL)
					return_null (API, GMT_NOT_A_VALID_TYPE);

				n_use = gmtapi_n_cols_needed_for_gaps (GMT, M_obj->n_columns);	/* Number of input columns to process */
				for (row = seg = n_records = 0; row < M_obj->n_rows; row++) {	/* This loop may include NaN-records and data records */
					gmtapi_update_prev_rec (GMT, n_use);	/* Make last current record the previous record if it is required by gap checking */
					for (col = 0; col < M_obj->n_columns; col++) {	/* Extract cols for a single record and store result in curr_rec */
						ij = GMT_2D_to_index (row, col, M_obj->dim);	/* Index into the user data matrix depends on layout (M->shape) */
						api_get_val (&(M_obj->data), ij, &(GMT->current.io.curr_rec[col]));
					}
					/* Now process the current record */
					if ((status = gmtapi_bin_input_memory (GMT, M_obj->n_columns, n_use)) < 0) {	/* Segment header found, finish the segment we worked on and goto next */
						if (status == GMTAPI_GOT_SEGGAP) API->current_rec[GMT_IN]--;	/* Since we inserted a segment header we must revisit this record as the first in next segment */
						if (got_data) {	/* If first input segment has header then we already have that segment allocated */
							if (n_records == 0) {  /* Repeated NaN records we skip since no need to build empty segments */
								continue;
							}
							(void)GMT_Alloc_Segment (API, GMT_IS_DATASET, n_records, n_columns, NULL, S);	/* Reallocate to exact length */
							D_obj->table[D_obj->n_tables]->n_records += n_records;			/* Update record count for this table */
							seg++;	/* Increment number of segments */
							if (seg == s_alloc) {	/* Allocate more space for additional segments */
								s_alloc <<= 1;	/* Double current alloc limit for segments, then allocate space for more segments */
								if ((D_obj->table[D_obj->n_tables]->segment = gmt_M_memory (GMT, D_obj->table[D_obj->n_tables]->segment, s_alloc, struct GMT_DATASEGMENT *)) == NULL) return NULL;
							}
							/* Allocate next segment with initial size the remainder of the data, which is the maximum length possible */
							S = D_obj->table[D_obj->n_tables]->segment[seg] = GMT_Alloc_Segment (API, GMT_IS_DATASET, M_obj->n_rows-n_records, n_columns, NULL, NULL);
							n_records = 0;	/* This is number of recs in current segment so we reset it to zero */
						}
					}
					else {	/* Found a data record */
						for (col = 0; col < n_columns; col++) {	/* Place the record into the dataset segment structure */
							double val = gmtapi_get_record_value (GMT, GMT->current.io.curr_rec, col, M_obj->n_columns, &col_pos_out);
							S->data[col_pos_out][n_records] = val;
						}
						got_data = true;	/* No longer before first data record */
						if (smode) S->text[n_records] = strdup (M_obj->text[row]);
						n_records++;	/* Update count of records in current segment */
					}
				}
				if (seg)	/* Got more than one segment, so finalize the reallocation of last segment to exact record count */
					(void)GMT_Alloc_Segment (API, smode, n_records, n_columns, NULL, S);	/* Reallocate to exact length */
				seg++;	/* Now holds the total number of segments */
				/* Realloc this table's segment array to the actual length [i.e., seg] */
				D_obj->table[D_obj->n_tables]->segment = gmt_M_memory (GMT, D_obj->table[D_obj->n_tables]->segment, seg, struct GMT_DATASEGMENT *);
				gmtapi_increment_d (D_obj, n_records, n_columns, seg);	/* Update counters for D_obj's only table */
				new_ID = GMT_Register_IO (API, GMT_IS_DATASET, GMT_IS_DUPLICATE, geometry, GMT_IN, NULL, D_obj);	/* Register a new resource to hold D_obj */
				if ((new_item = gmtlib_validate_id (API, GMT_IS_DATASET, new_ID, GMT_IN, GMT_NOTSET)) == GMT_NOTSET)
					return_null (API, GMT_OBJECT_NOT_FOUND);	/* Some internal error... */
				API->object[new_item]->resource = D_obj;
				API->object[new_item]->status = GMT_IS_USED;	/* Mark as read */
				DH = gmt_get_DD_hidden (D_obj);
				DH->alloc_level = API->object[new_item]->alloc_level;	/* Since allocated here */
				D_obj->geometry = S_obj->geometry;	/* Since provided when registered */
				update = via = true;
				break;

	 		case GMT_IS_DUPLICATE|GMT_VIA_VECTOR:
				/* Each column array source becomes column arrays in a separate table with one (or more if NaN-records) segments */
				if ((V_obj = S_obj->resource) == NULL) {
					gmt_M_free (GMT, D_obj);
					return_null (API, GMT_PTR_IS_NULL);
				}
				GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating data table from user %" PRIu64 " column arrays of length %" PRIu64 "\n",
				            V_obj->n_columns, V_obj->n_rows);
				if (GMT->common.q.mode == GMT_RANGE_ROW_IN || GMT->common.q.mode == GMT_RANGE_DATA_IN)
					GMT_Report (API, GMT_MSG_WARNING, "Row-selection via -qi is not implemented for GMT_IS_DUPLICATE|GMT_VIA_VECTOR external memory objects\n");
				/* Allocate a single table with one segment - there may be more if there are nan-records */
				smode = (V_obj->text) ? GMT_WITH_STRINGS : GMT_NO_STRINGS;
				if (smode) type = GMT_READ_MIXED;	/* If a vector has text we have a mixed record */
				n_columns = (GMT->common.i.col.select) ? GMT->common.i.col.n_cols : V_obj->n_columns;
				if ((D_obj->table[D_obj->n_tables] = gmt_get_table (GMT)) == NULL) return NULL;
				if ((D_obj->table[D_obj->n_tables]->segment = gmt_M_memory (GMT, NULL, s_alloc, struct GMT_DATASEGMENT *)) == NULL) return NULL;
				S = D_obj->table[D_obj->n_tables]->segment[0] = GMT_Alloc_Segment (API, smode, V_obj->n_rows, n_columns, NULL, NULL);
				if (S == NULL) return NULL;
				for (col = 1, diff_types = false; !diff_types && col < V_obj->n_columns; col++) if (V_obj->type[col] != V_obj->type[col-1]) diff_types = true;
				if (!diff_types && (api_get_val = gmtapi_select_get_function (API, V_obj->type[0])) == NULL)
					return_null (API, GMT_NOT_A_VALID_TYPE);

				for (row = seg = n_records = 0; row < V_obj->n_rows; row++) {	/* This loop may include NaN-records and data records */
					n_use = gmtapi_n_cols_needed_for_gaps (GMT, V_obj->n_columns);
					gmtapi_update_prev_rec (GMT, n_use);
					for (col = 0; col < V_obj->n_columns; col++) {	/* Process a single record into curr_rec */
						if (diff_types && (api_get_val = gmtapi_select_get_function (API, V_obj->type[col])) == NULL)
							return_null (API, GMT_NOT_A_VALID_TYPE);
						api_get_val (&(V_obj->data[col]), row, &(GMT->current.io.curr_rec[col]));
					}
					if ((status = gmtapi_bin_input_memory (GMT, V_obj->n_columns, n_use)) < 0) {	/* Segment header found, finish the one we had and add more */
						if (status == GMTAPI_GOT_SEGGAP) API->current_rec[GMT_IN]--;	/* Since we inserted a segment header we must revisit this record as first in next segment */
						if (got_data) {	/* If first input segment has header then we already have a segment allocated */
							if (n_records == 0) {  /* Repeated NaN records we skip since no need to build empty segments */
								continue;
							}
							(void)GMT_Alloc_Segment (API, GMT_IS_DATASET, n_records, n_columns, NULL, S);
							D_obj->table[D_obj->n_tables]->n_records += n_records;
							seg++;	/* Increment number of segments */
							if (seg == s_alloc) {	/* Allocate more space for segments */
								s_alloc <<= 1;
								if ((D_obj->table[D_obj->n_tables]->segment = gmt_M_memory (GMT, D_obj->table[D_obj->n_tables]->segment, s_alloc, struct GMT_DATASEGMENT *)) == NULL) return NULL;
							}
							/* Allocate next segment with initial size the remainder of the data */
							S = D_obj->table[D_obj->n_tables]->segment[seg] = GMT_Alloc_Segment (API, GMT_IS_DATASET, V_obj->n_rows-n_records, n_columns, NULL, NULL);
							n_records = 0;	/* This is number of recs in current segment */
						}
					}
					else {	/* Data record */
						for (col = 0; col < n_columns; col++) {	/* Place the record into the structure */
							double val = gmtapi_get_record_value (GMT, GMT->current.io.curr_rec, col, V_obj->n_columns, &col_pos_out);
							S->data[col_pos_out][n_records] = val;
						}
						if (smode) S->text[n_records] = strdup (V_obj->text[row]);
						got_data = true;
						n_records++;
					}
				}
				if (seg)	/* Got more than one segment, finalize the realloc of last segment */
					(void)GMT_Alloc_Segment (API, smode, n_records, n_columns, NULL, S);	/* Reallocate to exact length */
				seg++;	/* Total number of segments */
				/* Realloc this table's segment array to the actual length [i.e., seg] */
				D_obj->table[D_obj->n_tables]->segment = gmt_M_memory (GMT, D_obj->table[D_obj->n_tables]->segment, seg, struct GMT_DATASEGMENT *);
				gmtapi_increment_d (D_obj, n_records, n_columns, seg);	/* Update counters for D_obj's only table */
				new_ID = GMT_Register_IO (API, GMT_IS_DATASET, GMT_IS_DUPLICATE, geometry, GMT_IN, NULL, D_obj);	/* Register a new resource to hold D_obj */
				if ((new_item = gmtlib_validate_id (API, GMT_IS_DATASET, new_ID, GMT_IN, GMT_NOTSET)) == GMT_NOTSET)
					return_null (API, GMT_OBJECT_NOT_FOUND);	/* Some internal error... */
				API->object[new_item]->resource = D_obj;
				API->object[new_item]->status = GMT_IS_USED;			/* Mark as read */
				DH = gmt_get_DD_hidden (D_obj);
				DH->alloc_level = API->object[new_item]->alloc_level;	/* Since allocated here */
				D_obj->geometry = S_obj->geometry;	/* Since provided when registered */
				update = via = true;
				break;

			case GMT_IS_REFERENCE|GMT_VIA_VECTOR:
				if ((V_obj = S_obj->resource) == NULL) {
					gmt_M_free (GMT, D_obj);
					return_null (API, GMT_PTR_IS_NULL);
				}
				for (col = 0; col < V_obj->n_columns; col++) {
					if (V_obj->type[col] != GMT_DOUBLE) {
						GMT_Report (API, GMT_MSG_ERROR, "Only double-precision vectors can be passed via reference to datasets\n");
						gmt_M_free (GMT, D_obj);
						return_null (API, GMT_NOT_A_VALID_TYPE);
					}
				}
				if (GMT->common.q.mode == GMT_RANGE_ROW_IN || GMT->common.q.mode == GMT_RANGE_DATA_IN)
					GMT_Report (API, GMT_MSG_WARNING, "Row-selection via -qi is not implemented for GMT_IS_REFERENCE|GMT_VIA_VECTOR external memory objects\n");
				/* Each column double array source becomes preallocated column arrays in a separate table with a single segment */
				smode = (V_obj->text) ? GMT_WITH_STRINGS : GMT_NO_STRINGS;
				if (smode) type = GMT_READ_MIXED;	/* If a matrix has text we have a mixed record */
				n_columns = (GMT->common.i.col.select) ? GMT->common.i.col.n_cols : V_obj->n_columns;
				GMT_Report (API, GMT_MSG_INFORMATION, "Referencing data table from user %" PRIu64 " column arrays of length %" PRIu64 "\n",
				            V_obj->n_columns, V_obj->n_rows);
				if ((D_obj->table[D_obj->n_tables] = gmt_get_table (GMT)) == NULL) return NULL;
				if ((D_obj->table[D_obj->n_tables]->segment = gmt_M_memory (GMT, NULL, 1, struct GMT_DATASEGMENT *)) == NULL) return NULL;
				S = D_obj->table[D_obj->n_tables]->segment[0] = GMT_Alloc_Segment (API, smode, 0, n_columns, NULL, NULL);
				if (S == NULL) return NULL;
				SH = gmt_get_DS_hidden (S);
				for (col = 0; col < V_obj->n_columns; col++) {
					if (GMT->common.i.col.select) {	/* -i has selected some columns */
						col_pos = GMT->current.io.col[GMT_IN][col].col;	/* Which data column to pick */
						col_pos_out = GMT->current.io.col[GMT_IN][col].order; /* Which data column to place it on output */
					}
					else if (GMT->current.setting.io_lonlat_toggle[GMT_IN] && col < GMT_Z) {	/* Worry about -: for lon,lat */
						col_pos = 1 - col;	/* Read lat/lon instead of lon/lat */
						col_pos_out = col;
					}
					else
						col_pos = col_pos_out = col;	/* Just goto that column */
					S->data[col_pos_out] = V_obj->data[col_pos].f8;
					SH->alloc_mode[col_pos_out] = GMT_ALLOC_EXTERNALLY;	/* Not this objects job to free what was passed in by reference */
				}
				SH->alloc_mode_text = GMT_ALLOC_EXTERNALLY; /* Not this object's job to free what was passed in by reference */
				DH = gmt_get_DD_hidden (D_obj);
				if (smode) S->text = V_obj->text;	/* Hook up trailing text array */
				gmtapi_increment_d (D_obj, V_obj->n_rows, n_columns, 1U);	/* Update counters for D_obj with 1 segment */
				DH->alloc_mode = GMT_ALLOC_EXTERNALLY;	/* Since we just hooked on the arrays */
				new_ID = GMT_Register_IO (API, GMT_IS_DATASET, GMT_IS_REFERENCE, geometry, GMT_IN, NULL, D_obj);	/* Register a new resource to hold D_obj */
				if ((new_item = gmtlib_validate_id (API, GMT_IS_DATASET, new_ID, GMT_IN, GMT_NOTSET)) == GMT_NOTSET)
					return_null (API, GMT_OBJECT_NOT_FOUND);	/* Some internal error... */
				API->object[new_item]->resource = D_obj;
				API->object[new_item]->status = GMT_IS_USED;	/* Mark as read */
				DH->alloc_level = API->object[new_item]->alloc_level;	/* Since allocated here */
				D_obj->geometry = S_obj->geometry;	/* Since provided when registered */
				S_obj->family = GMT_IS_VECTOR;	/* Done with the via business now */
				update = via = check_col_switch = true;
				break;

			default:	/* Barking up the wrong tree here... */
				GMT_Report (API, GMT_MSG_ERROR, "Wrong method used to import data tables\n");
				gmt_M_free (GMT, D_obj->table);
				gmt_M_free (GMT, D_obj);
				return_null (API, GMT_NOT_A_VALID_METHOD);
				break;
		}
		if (update) {	/* Means we got stuff and need to update the total dataset statistics so far */
			D_obj->n_segments += D_obj->table[D_obj->n_tables-1]->n_segments;	/* Sum up total number of segments in the entire data set */
			D_obj->n_records  += D_obj->table[D_obj->n_tables-1]->n_records;		/* Sum up total number of records in the entire data set */
			/* Update segment IDs so they are sequential across many tables (gmtlib_read_table sets the ids relative to current table). */
			if (D_obj->n_tables > 1) {
				for (seg = 0; seg < D_obj->table[D_obj->n_tables-1]->n_segments; seg++) {
					SH = gmt_get_DS_hidden (D_obj->table[D_obj->n_tables-1]->segment[seg]);
					SH->id += D_obj->table[D_obj->n_tables-2]->n_segments;
				}
			}
			if (allocate && D_obj->n_tables == n_alloc) {	/* Must allocate more space for additional tables */
				size_t old_n_alloc = n_alloc;
				n_alloc += GMT_TINY_CHUNK;
				if ((D_obj->table = gmt_M_memory (GMT, D_obj->table, n_alloc, struct GMT_DATATABLE *)) == NULL) return NULL;
				gmt_M_memset (&(D_obj->table[old_n_alloc]), n_alloc - old_n_alloc, struct GMT_DATATABLE *);	/* Set new memory to NULL */
			}
		}
		S_obj->alloc_mode = DH->alloc_mode;	/* Clarify allocation mode for this object */
#if 0
		if (gmtapi_col_check (D_obj->table[D_obj->n_tables-1], &n_cols)) {	/* Different tables have different number of columns, which is not good */
			return_null (API, GMT_N_COLS_VARY);
		}
#endif
		S_obj->status = GMT_IS_USED;	/* Mark input object as read */
		S_obj->n_expected_fields = GMT_MAX_COLUMNS;	/* Since need to start over if this object is used again */
		n_used++;	/* Number of items actually processed */
	}
	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_dataset processed %u resources\n", n_used);
	if (regit) {	/* Register the output */
		new_ID = GMT_Register_IO (API, GMT_IS_DATASET, GMT_IS_DUPLICATE, geometry, GMT_IN, NULL, D_obj);	/* Register a new resource to hold D_obj */
		if ((new_item = gmtlib_validate_id (API, GMT_IS_DATASET, new_ID, GMT_IN, GMT_NOTSET)) == GMT_NOTSET)
			return_null (API, GMT_OBJECT_NOT_FOUND);	/* Some internal error... */
		API->object[new_item]->resource = D_obj;
		API->object[new_item]->status = GMT_IS_USED;	/* Mark as read */
		API->object[new_item]->alloc_mode = DH->alloc_mode;	/* Clarify allocation mode for this object */
		API->object[new_item]->alloc_level = DH->alloc_level;
	}
	if (D_obj->n_tables == 0) {	/* Only found empty files (e.g., /dev/null) and we have nothing to show for our efforts.  Return an single empty table with no segments. */
		D_obj->table = gmt_M_memory (GMT, D_obj->table, 1, struct GMT_DATATABLE *);
		D_obj->table[0] = gmt_get_table (GMT);
		D_obj->n_tables = 1;	/* But we must indicate we found one (empty) table */
	}
	else {	/* Found one or more tables, finalize table allocation, set number of columns, and possibly allocate min/max arrays if not there already */
		if (allocate && D_obj->n_tables < n_alloc) D_obj->table = gmt_M_memory (GMT, D_obj->table, D_obj->n_tables, struct GMT_DATATABLE *);
		D_obj->n_columns = D_obj->table[0]->n_columns;
		if (D_obj->n_columns) { /* Has numerical columns */
			if (!D_obj->min) D_obj->min = gmt_M_memory (GMT, NULL, D_obj->n_columns, double);
			if (!D_obj->max) D_obj->max = gmt_M_memory (GMT, NULL, D_obj->n_columns, double);
			if (D_obj->min == NULL || D_obj->max == NULL) return NULL;
		}
	}
	D_obj->geometry = geometry;		/* Since gmtlib_read_table may have changed it */
	D_obj->type = type;			/* Since gmtlib_read_table may have changed it */
	if (check_col_switch) gmtapi_switch_cols (GMT, D_obj, GMT_IN);	/* Deals with -:, if it was selected */
	gmt_set_dataset_minmax (GMT, D_obj);	/* Set the min/max values for the entire dataset */
	if (!via) API->object[this_item]->resource = D_obj;	/* Retain pointer to the allocated data so we use garbage collection later */
	return (D_obj);
}

/*! . */
GMT_LOCAL int gmtapi_destroy_data_ptr (struct GMTAPI_CTRL *API, enum GMT_enum_family family, void *ptr) {
	/* Like GMT_Destroy_Data but takes pointer to data rather than address of pointer.
	 * We pass true to make sure we free the memory.  Some objects (grid, matrix, vector) may
	 * point to externally allocated memory so we return the alloc_mode for those items.
	 * This is mostly for information since the pointers to such external memory have now
	 * been set to NULL instead of being freed.
	 * The containers are always allocated by GMT so those are freed at the end.
	 */

	struct GMT_CTRL *GMT;
	if (API == NULL) return (GMT_NOT_A_SESSION);
	if (!ptr) return (GMT_NOERROR);	/* Null pointer */
	GMT = API->GMT;

	switch (family) {
		case GMT_IS_GRID:
			gmtlib_free_grid_ptr (GMT, ptr, true);
			break;
		case GMT_IS_DATASET:
			gmtlib_free_dataset_ptr (GMT, ptr);
			break;
		case GMT_IS_PALETTE:
			gmtlib_free_cpt_ptr (GMT, ptr);
			break;
		case GMT_IS_IMAGE:
			gmtlib_free_image_ptr (GMT, ptr, true);
			break;
		case GMT_IS_POSTSCRIPT:
			gmtlib_free_ps_ptr (GMT, ptr);
			break;
		case GMT_IS_CUBE:
			gmtlib_free_cube_ptr (GMT, ptr, true);
			break;
		case GMT_IS_COORD:
			/* Nothing to do as gmt_M_free below will do it */
			break;

		/* Also allow destroying of intermediate vector and matrix containers */
		case GMT_IS_MATRIX:
			gmtlib_free_matrix_ptr (GMT, ptr, true);
			break;
		case GMT_IS_VECTOR:
			gmtlib_free_vector_ptr (GMT, ptr, true);
			break;
		default:
			return (gmtlib_report_error (API, GMT_NOT_A_VALID_FAMILY));
			break;
	}
	gmt_M_free (GMT, ptr);	/* OK to free container */
	return (GMT_NOERROR);	/* Null pointer */
}

GMT_LOCAL void gmtapi_flip_vectors (struct GMT_CTRL *GMT, struct GMT_VECTOR *V, unsigned int direction) {
	enum GMT_enum_type etmp;
	union GMT_UNIVECTOR utmp;

	/* Implements the effect of -: on output via vectors */

	if (V->n_columns < 2 || !GMT->current.setting.io_lonlat_toggle[direction]) return;	/* Nothing to do */
	/* Flip first two vector pointers */
	etmp = V->type[GMT_X];	V->type[GMT_X] = V->type[GMT_Y];	V->type[GMT_Y] = etmp;
	utmp = V->data[GMT_X];	V->data[GMT_X] = V->data[GMT_Y];	V->data[GMT_Y] = utmp;
}

/*! . */
GMT_LOCAL int gmtapi_export_dataset (struct GMTAPI_CTRL *API, int object_ID, unsigned int mode, struct GMT_DATASET *D_obj) {
 	/* Does the actual work of writing out the specified data set to a single destination.
	 * If object_ID == GMT_NOTSET we use the first registered output destination, otherwise we just use the one specified.
	 * See the GMT API documentation for how mode is used to create multiple files from segments or tables of a dataset.
	 */
	int item, error, default_method;
	unsigned int method, hdr;
	uint64_t tbl, col, kol, row_out, row, seg, ij, n_columns, n_rows;
	bool save, diff_types = false, toggle;
	double value;
	p_func_uint64_t GMT_2D_to_index = NULL;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMT_DATASET *D_copy = NULL;
	struct GMT_MATRIX *M_obj = NULL;
	struct GMT_VECTOR *V_obj = NULL;
	struct GMT_MATRIX_HIDDEN *MH = NULL;
	struct GMT_VECTOR_HIDDEN *VH = NULL;
	struct GMT_DATASEGMENT *S = NULL;
	struct GMT_DATASET_HIDDEN *DH = NULL;
	struct GMT_CTRL *GMT = API->GMT;
	void *ptr = NULL;
	GMT_putfunction api_put_val = NULL;

	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_export_dataset: Passed ID = %d and mode = %d\n", object_ID, mode);

	if (object_ID == GMT_NOTSET) return (gmtlib_report_error (API, GMT_OUTPUT_NOT_SET));
	if ((item = gmtlib_validate_id (API, GMT_IS_DATASET, object_ID, GMT_OUT, GMT_NOTSET)) == GMT_NOTSET) return (gmtlib_report_error (API, API->error));

	S_obj = API->object[item];	/* S is the object whose data we will export */
	if (S_obj->family != GMT_IS_DATASET) return (gmtlib_report_error (API, GMT_NOT_A_VALID_FAMILY));	/* Called with wrong data type */
	if (S_obj->status != GMT_IS_UNUSED && !(mode & GMT_IO_RESET))	/* Only allow writing of a data set once unless overridden by mode */
		return (gmtlib_report_error (API, GMT_WRITTEN_ONCE));
	if (mode & GMT_IO_RESET) mode -= GMT_IO_RESET;	/* Remove the reset bit */
	if (mode >= GMT_WRITE_TABLE && !S_obj->filename) return (gmtlib_report_error (API, GMT_OUTPUT_NOT_SET));	/* Must have filename when segments are to be written */
	default_method = GMT_IS_FILE;
	if (S_obj->filename)	/* Write to this file */
		ptr = S_obj->filename;
	else {			/* No filename so we switch default method to writing to a stream or fdesc */
		default_method = (S_obj->method == GMT_IS_FILE) ? GMT_IS_STREAM : S_obj->method;
		ptr = S_obj->fp;
#ifdef SET_IO_MODE
		gmt_setmode (GMT, GMT_OUT);	/* Windows may need to switch write mode from text to binary */
#endif
	}
	gmt_set_dataset_minmax (GMT, D_obj);	/* Update all counters and min/max arrays */
	if (API->GMT->common.o.col.end || GMT->common.o.col.text)	/* Asked for unspecified last column on input (e.g., -i3,2,5:), supply the missing last column number */
		gmt_reparse_o_option (GMT, (GMT->common.o.col.text) ? 0 : D_obj->n_columns);
	toggle = (GMT->current.setting.io_lonlat_toggle[GMT_OUT] && D_obj->n_columns >= 2);
	GMT->current.io.data_record_number_in_tbl[GMT_OUT] = GMT->current.io.data_record_number_in_seg[GMT_OUT] = 0;
	DH = gmt_get_DD_hidden (D_obj);
	DH->io_mode = mode;	/* Handles if tables or segments should be written to separate files, according to mode */
	method = gmtapi_set_method (S_obj);	/* Get the actual method to use */
	switch (method) {	/* File, array, stream, etc. */
	 	case GMT_IS_STREAM:
#ifdef SET_IO_MODE
			gmt_setmode (GMT, GMT_OUT);	/* Windows may need to switch write mode from text to binary */
#endif
		case GMT_IS_FILE:
	 	case GMT_IS_FDESC:
			/* gmtlib_write_dataset (or lower) will report where it is reading from if level is GMT_MSG_INFORMATION */
			if ((error = gmtlib_write_dataset (GMT, ptr, default_method, D_obj, true, GMT_NOTSET))) return (gmtlib_report_error (API, error));
			break;

		case GMT_IS_DUPLICATE:		/* Duplicate the input dataset on output */
			if (S_obj->resource) return (gmtlib_report_error (API, GMT_PTR_NOT_NULL));	/* The output resource must be NULL */
			if (GMT->common.q.mode == GMT_RANGE_ROW_OUT || GMT->common.q.mode == GMT_RANGE_DATA_OUT)
				GMT_Report (API, GMT_MSG_WARNING, "Row-selection via -qo is not implemented for GMT_IS_DUPLICATE GMT_IS_DATASET external memory objects\n");
			GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating data table to GMT_DATASET memory location\n");
			D_copy = gmt_duplicate_dataset (GMT, D_obj, GMT_ALLOC_NORMAL, NULL);
			gmtlib_change_out_dataset (GMT, D_copy);	/* Deal with any -o settings */
			gmtapi_switch_cols (GMT, D_copy, GMT_OUT);	/* Deals with -:, if it was selected */
			S_obj->resource = D_copy;	/* Set resource pointer from object to this dataset */
			break;

		case GMT_IS_REFERENCE:	/* Just pass memory location */
			if (S_obj->resource) return (gmtlib_report_error (API, GMT_PTR_NOT_NULL));	/* The output resource must be NULL */
			if (GMT->common.q.mode == GMT_RANGE_ROW_OUT || GMT->common.q.mode == GMT_RANGE_DATA_OUT)
				GMT_Report (API, GMT_MSG_WARNING, "Row-selection via -qo is not implemented for GMT_IS_REFERENCE GMT_IS_DATASET external memory objects\n");
			GMT_Report (API, GMT_MSG_INFORMATION, "Referencing data table to GMT_DATASET memory location\n");
			gmtlib_change_out_dataset (GMT, D_obj);	/* Deal with any -o settings */
			gmtapi_switch_cols (GMT, D_obj, GMT_OUT);	/* Deals with -:, if it was selected */
			S_obj->resource = D_obj;		/* Set resource pointer from object to this dataset */
			DH->alloc_level = S_obj->alloc_level;	/* Since we are passing it up to the caller */
			break;

		case GMT_IS_DUPLICATE|GMT_VIA_MATRIX:
			GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating data table to user matrix location\n");
			if (GMT->common.q.mode == GMT_RANGE_ROW_OUT || GMT->common.q.mode == GMT_RANGE_DATA_OUT)
				GMT_Report (API, GMT_MSG_WARNING, "Row-selection via -qo is not implemented for GMT_IS_DUPLICATE|GMT_VIA_MATRIX external memory objects\n");
			save = GMT->current.io.multi_segments[GMT_OUT];
			if (GMT->current.io.skip_headers_on_outout) GMT->current.io.multi_segments[GMT_OUT] = false;
			n_rows = (GMT->current.io.multi_segments[GMT_OUT]) ? D_obj->n_records + D_obj->n_segments : D_obj->n_records;	/* Number of rows needed to hold the data [incl any segment headers] */
			n_columns = (GMT->common.o.col.select) ? GMT->common.o.col.n_cols : D_obj->n_columns;					/* Number of columns needed to hold the data records */
			if ((M_obj = S_obj->resource) == NULL) {	/* Must allocate suitable matrix */
				M_obj = gmtlib_create_matrix (GMT, 1U, 0);	/* 1-layer matrix (i.e., 2-D) */
				/* Allocate final output space since we now know all dimensions */
				MH = gmt_get_M_hidden (M_obj);
				M_obj->n_rows = n_rows;
				M_obj->n_columns = n_columns;
				M_obj->dim = (M_obj->shape == GMT_IS_ROW_FORMAT) ? M_obj->n_columns : M_obj->n_rows;						/* Matrix layout order */
				S_obj->n_alloc = M_obj->n_rows * M_obj->n_columns;	/* Get total number of elements as n_rows * n_columns */
				M_obj->type = S_obj->type;	/* Use selected data type for the export */
				/* Allocate output matrix space or die */
				if ((error = gmtlib_alloc_univector (GMT, &(M_obj->data), M_obj->type, S_obj->n_alloc)) != GMT_NOERROR) return (gmtlib_report_error (API, error));
				MH->alloc_mode = GMT_ALLOC_INTERNALLY;
				MH->alloc_level = GMT->hidden.func_level;   /* Must be freed at this level. */
				if (D_obj->type >= GMT_READ_TEXT) { /* Also has trailing text */
					if ((M_obj->text = gmt_M_memory (GMT, NULL, n_rows, char *)) == NULL) return (gmtlib_report_error (API, GMT_MEMORY_ERROR));
					MH->alloc_mode_text = GMT_ALLOC_INTERNALLY;
				}
			}
			else {	/* We passed in a matrix so must check it is big enough */
				if (M_obj->n_rows < n_rows || M_obj->n_columns < n_columns)
					return (gmtlib_report_error (API, GMT_DIM_TOO_SMALL));
				MH = gmt_get_M_hidden (M_obj);
			}
			/* Consider header records from first table only */
			if (D_obj->table[0]->n_headers) {
				if ((M_obj->header = gmt_M_memory (GMT, NULL, D_obj->table[0]->n_headers, char *)) == NULL) return (gmtlib_report_error (API, GMT_MEMORY_ERROR));
				for (hdr = M_obj->n_headers = 0; hdr < D_obj->table[0]->n_headers; hdr++)
					M_obj->header[M_obj->n_headers++] = strdup (D_obj->table[0]->header[hdr]);
			}

			/* Set up index and put-value functions for this matrix */
			if ((GMT_2D_to_index = gmtapi_get_2d_to_index (API, M_obj->shape, GMT_GRID_IS_REAL)) == NULL)
				return (gmtlib_report_error (API, GMT_WRONG_MATRIX_SHAPE));
			if ((api_put_val = gmtapi_select_put_function (API, M_obj->type)) == NULL)
				return (gmtlib_report_error (API, GMT_NOT_A_VALID_TYPE));

			for (tbl = row_out = 0; tbl < D_obj->n_tables; tbl++) {	/* Loop over tables and segments */
				for (seg = 0; seg < D_obj->table[tbl]->n_segments; seg++) {
					S = D_obj->table[tbl]->segment[seg];	/* Shorthand for the current segment */
					if (GMT->current.io.multi_segments[GMT_OUT]) {	/* Must write a NaN-segment record to indicate segment break */
						for (col = 0; col < M_obj->n_columns; col++) {
							ij = GMT_2D_to_index (row_out, col, M_obj->dim);
							api_put_val (&(M_obj->data), ij, GMT->session.d_NaN);
						}
						row_out++;	/* Due to the extra NaN-data header record we just wrote */
					}
					for (row = 0; row < S->n_rows; row++, row_out++) {	/* Write this segment's data records to the matrix */
						for (col = 0; col < M_obj->n_columns; col++) {
							if (col < 2 && toggle)	/* Deal with -: since we are writing to matrix memory and not file */
								kol = 1 - col;
							else
								kol = col;
							ij = GMT_2D_to_index (row_out, kol, M_obj->dim);
							value = gmtapi_select_dataset_value (GMT, S, (unsigned int)row, (unsigned int)col);
							api_put_val (&(M_obj->data), ij, value);
						}
						if (S->text) M_obj->text[row_out] = strdup (S->text[row]);
					}
				}
			}
			assert (M_obj->n_rows == row_out);	/* Sanity check */
			MH->alloc_level = S_obj->alloc_level;
			S_obj->resource = M_obj;		/* Set resource pointer from object to this matrix */
			GMT->current.io.multi_segments[GMT_OUT] = save;
			break;

		case GMT_IS_DUPLICATE|GMT_VIA_VECTOR:
			if (GMT->common.q.mode == GMT_RANGE_ROW_OUT || GMT->common.q.mode == GMT_RANGE_DATA_OUT)
				GMT_Report (API, GMT_MSG_WARNING, "Row-selection via -qo is not implemented for GMT_IS_DUPLICATE|GMT_VIA_VECTOR external memory objects\n");
			GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating data table to user column arrays location\n");
			save = GMT->current.io.multi_segments[GMT_OUT];
			if (GMT->current.io.skip_headers_on_outout) GMT->current.io.multi_segments[GMT_OUT] = false;
			n_columns = (GMT->common.o.col.select) ? GMT->common.o.col.n_cols : D_obj->n_columns;	/* Number of columns needed to hold the data records */
			n_rows = (GMT->current.io.multi_segments[GMT_OUT]) ? D_obj->n_records + D_obj->n_segments : D_obj->n_records;	/* Number of data records [and any segment headers] */
			if ((V_obj = S_obj->resource) == NULL) {	/* Must create output container given data dimensions */
				if ((V_obj = gmt_create_vector (GMT, n_columns, GMT_OUT)) == NULL)
					return (gmtlib_report_error (API, GMT_PTR_IS_NULL));
				for (col = 0; col < V_obj->n_columns; col++) V_obj->type[col] = S_obj->type;	/* Set same data type for all columns */
				V_obj->n_rows = n_rows;
				if ((error = gmtlib_alloc_vectors (GMT, V_obj, n_rows)) != GMT_NOERROR) return (gmtlib_report_error (API, error));	/* Allocate space for all columns */
				if (D_obj->type >= GMT_READ_TEXT) { /* Also has trailing text */
					struct GMT_VECTOR_HIDDEN *VH = gmt_get_V_hidden (V_obj);
					if ((V_obj->text = gmt_M_memory (GMT, NULL, n_rows, char *)) == NULL) return (gmtlib_report_error (API, GMT_MEMORY_ERROR));
					VH->alloc_mode_text = GMT_ALLOC_INTERNALLY;
				}
			}
			else {	/* Got a preallocated container */
				if (V_obj->n_rows < n_rows || V_obj->n_columns < n_columns)
					return (gmtlib_report_error (API, GMT_DIM_TOO_SMALL));
				for (col = 1, diff_types = false; !diff_types && col < V_obj->n_columns; col++) if (V_obj->type[col] != V_obj->type[col-1]) diff_types = true;
			}
			/* Consider header records from first table only */
			if (D_obj->table[0]->n_headers) {
				if ((V_obj->header = gmt_M_memory (GMT, NULL, D_obj->table[0]->n_headers, char *)) == NULL) return (gmtlib_report_error (API, GMT_MEMORY_ERROR));
				for (hdr = V_obj->n_headers = 0; hdr < D_obj->table[0]->n_headers; hdr++)
					V_obj->header[V_obj->n_headers++] = strdup (D_obj->table[0]->header[hdr]);
			}

			/* Set up put-value functions for this vector */
			if (!diff_types && (api_put_val = gmtapi_select_put_function (API, V_obj->type[0])) == NULL)	/* Get function to write 1st column (possibly all columns) */
				return (gmtlib_report_error (API, GMT_NOT_A_VALID_TYPE));
			for (tbl = row_out = 0; tbl < D_obj->n_tables; tbl++) {	/* Loop over all tables and segments */
				for (seg = 0; seg < D_obj->table[tbl]->n_segments; seg++) {
					S = D_obj->table[tbl]->segment[seg];	/* Shorthand for this segment */
					if (GMT->current.io.multi_segments[GMT_OUT]) {		/* Must write a NaN-segment record */
						for (col = 0; col < V_obj->n_columns; col++)
							api_put_val (&(V_obj->data[col]), row_out, GMT->session.d_NaN);
						row_out++;	/* Due to the extra NaN-data header */
					}
					for (row = 0; row < S->n_rows; row++, row_out++) {	/* Copy the data records */
						for (col = 0; col < V_obj->n_columns; col++) {
							if (diff_types && (api_put_val = gmtapi_select_put_function (API, V_obj->type[col])) == NULL)
								return (gmtlib_report_error (API, GMT_NOT_A_VALID_TYPE));
							value = gmtapi_select_dataset_value (GMT, S, (unsigned int)row, (unsigned int)col);
							api_put_val (&(V_obj->data[col]), row_out, value);
						}
						if (S->text) V_obj->text[row_out] = strdup (S->text[row]);
					}
				}
			}
			assert (V_obj->n_rows == row_out);	/* Sanity check */
			if (toggle) gmtapi_flip_vectors (GMT, V_obj, GMT_OUT);
			VH = gmt_get_V_hidden (V_obj);
			VH->alloc_level = S_obj->alloc_level;
			S_obj->resource = V_obj;
			GMT->current.io.multi_segments[GMT_OUT] = save;
			break;

		case GMT_IS_REFERENCE|GMT_VIA_VECTOR:
			if (GMT->common.q.mode == GMT_RANGE_ROW_OUT || GMT->common.q.mode == GMT_RANGE_DATA_OUT)
				GMT_Report (API, GMT_MSG_WARNING, "Row-selection via -qo is not implemented for GMT_IS_REFERENCE|GMT_VIA_VECTOR external memory objects\n");
			GMT_Report (API, GMT_MSG_DEBUG, "Referencing data table to users column-vector location\n");
			if (D_obj->n_tables > 1 || D_obj->n_segments > 1) {
				GMT_Report (API, GMT_MSG_WARNING, "Reference by vector requires a single segment!\n");
				GMT_Report (API, GMT_MSG_WARNING, "Output may be truncated or an error may occur!\n");
			}
			GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating data table to user column arrays location\n");
			n_columns = (GMT->common.o.col.select) ? GMT->common.o.col.n_cols : D_obj->n_columns;	/* Number of columns needed to hold the data records */
			n_rows = D_obj->n_records;	/* Number of data records */
			S = D_obj->table[0]->segment[0];	/* Shorthand for this single segment */
			if ((V_obj = S_obj->resource) == NULL) {	/* Must create output container given data dimensions */
				struct GMT_DATASEGMENT_HIDDEN *SH = gmt_get_DS_hidden (S);
				if ((V_obj = gmt_create_vector (GMT, n_columns, GMT_OUT)) == NULL)
					return (gmtlib_report_error (API, GMT_PTR_IS_NULL));
				VH = gmt_get_V_hidden (V_obj);
				for (col = 0; col < V_obj->n_columns; col++) {
					V_obj->type[col] = S_obj->type;	/* Set same data type for all columns */
					V_obj->data[col].f8 = S->data[col];	/* Set pointer only */
					VH->alloc_mode[col] = GMT_ALLOC_EXTERNALLY;	/* Since not duplicated, just pointed to */
					SH->alloc_mode[col] = GMT_ALLOC_EXTERNALLY;	/* To prevent freeing in D_obj */
				}
				if (S->text) {
					V_obj->text = S->text;
					VH->alloc_mode_text = SH->alloc_mode_text = GMT_ALLOC_EXTERNALLY;	/* Since not duplicated, just pointed to */
				}
				V_obj->n_rows = n_rows;
				VH->alloc_level = S_obj->alloc_level;	/* Otherwise D_obj will be freed before we get to use data */
				S_obj->alloc_mode = DH->alloc_mode;	/* Otherwise D_obj will be freed before we get to use data */
			}
			else {	/* Got a preallocated container */
				if (V_obj->n_rows < n_rows || V_obj->n_columns < n_columns)
					return (gmtlib_report_error (API, GMT_DIM_TOO_SMALL));
				for (col = 0; col < V_obj->n_columns; col++)
					gmt_M_memcpy (V_obj->data[col].f8, S->data[col], n_rows, double);	/* Duplicate data */
			}
			/* Consider header records from first table only and will set pointers only */
			if (D_obj->table[0]->n_headers) {
				if ((V_obj->header = gmt_M_memory (GMT, NULL, D_obj->table[0]->n_headers, char *)) == NULL) return (gmtlib_report_error (API, GMT_MEMORY_ERROR));
				for (hdr = V_obj->n_headers = 0; hdr < D_obj->table[0]->n_headers; hdr++)
					V_obj->header[V_obj->n_headers++] = strdup (D_obj->table[0]->header[hdr]);
			}
			if (toggle) gmtapi_flip_vectors (GMT, V_obj, GMT_OUT);
			S_obj->resource = V_obj;
			break;

		default:
			GMT_Report (API, GMT_MSG_ERROR, "Wrong method used to export data tables\n");
			return (gmtlib_report_error (API, GMT_NOT_A_VALID_METHOD));
			break;
	}
	S_obj->alloc_mode = DH->alloc_mode;	/* Clarify allocation mode for this entity */
	S_obj->status = GMT_IS_USED;	/* Mark as written */

	return GMT_NOERROR;
}

GMT_LOCAL int gmtapi_import_ppm_header (struct GMT_CTRL *GMT, char *fname, bool close, FILE **fp_ppm, struct GMT_IMAGE *I) {
	/* Reads a Portable Pixel Map (PPM) file header if fname extension is .ppm, else returns nonzero value.
	 * PPM is the only image format GMT can read natively. */
	char *ext = gmt_get_ext (fname), text[GMT_LEN128] = {""}, c;
	int k = 0, max, n;
	FILE *fp = NULL;
	if (ext == NULL || strcmp (ext, "ppm")) return GMT_NOT_A_VALID_FAMILY;	/* Not requesting a PPM file - return GMT_NOT_A_VALID_FAMILY and let GDAL take over */

	if ((fp = gmt_fopen (GMT, fname, GMT->current.io.r_mode)) == NULL) {	/* Return GMT_ERROR_ON_FOPEN to signify failure */
		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Cannot open file %s\n", fname);
		return GMT_ERROR_ON_FOPEN;
	}
	while ((c = fgetc (fp)) != '\n' && k < GMT_LEN128) text[k++] = c;	/* Get first record up to newline */
	text[MIN(k,GMT_LEN128-1)] = '\0';	/* Terminate line & check that we don't overflow */
	if (text[1] == '5') /* Used P5 for grayscale image */
		I->header->n_bands = 1;
	else if (text[1] == '6')	/* Used P6 for rgb image */
		I->header->n_bands = 3;
	else {
		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Cannot decode PPM magic key (%s) from file %s\n", text, fname);
		gmt_fclose (GMT, fp);
		return GMT_NOT_A_VALID_TYPE;
	}
	c = fgetc (fp);	/* Need to peak ahead to know what record we are dealing with.  PPM can have comments */
	while (c == '#') {	/* Wind past comment */
		while ((c = fgetc (fp)) != '\n' ) k++;	/* Ends when c is newline */
		c = fgetc (fp);	/* Peak ahead again */
	}
	/* Put back last read character to the stream */
	ungetc (c, fp);
	k = 0;
	while ((c = fgetc (fp)) != '\n' && k < GMT_LEN128) text[k++] = c;	/* Get next record up to newline */
	text[MIN(k,GMT_LEN128-1)] = '\0';	/* Terminate line & check that we don't overflow */
	n = sscanf (text, "%d %d %d", &I->header->n_rows, &I->header->n_columns, &max);
	if (n == 2) {	/* Must skip past a separate record with the max pixel value */
		while ((c = fgetc (fp)) != '\n' ) k++;
	}
	/* Any read now would start reading the image pixels; done in gmtapi_import_ppm */
	I->header->registration = GMT_GRID_PIXEL_REG;
	if (GMT->common.R.active[RSET]) {	/* Got -Rw/e/s/n, we use that as the region for this image */
		gmt_M_memcpy (I->header->wesn, GMT->common.R.wesn, 4, double);
		I->header->inc[GMT_X] = gmt_M_get_inc (GMT, I->header->wesn[XLO], I->header->wesn[XHI], I->header->n_columns, GMT_GRID_PIXEL_REG);
		I->header->inc[GMT_Y] = gmt_M_get_inc (GMT, I->header->wesn[YLO], I->header->wesn[YHI], I->header->n_rows, GMT_GRID_PIXEL_REG);
	}
	else {	/* Must just use dimensions to set a dummy -R -I */
		I->header->wesn[XLO] = I->header->wesn[YLO] = 0.0;
		I->header->wesn[XHI] = I->header->n_columns;
		I->header->wesn[YHI] = I->header->n_rows;
		I->header->inc[GMT_X] = I->header->inc[GMT_Y] = 1.0;
	}
	gmt_M_memset (I->header->pad, 4, unsigned int);
	gmt_set_grddim (GMT, I->header);	/* Update all header dimensions */
	strcpy (I->header->mem_layout, "TRP");	/* Layout use in all PPM files */
	if (close)	/* Close file, we only wanted the header information */
		gmt_fclose (GMT, fp);
	else	/* Pass back FILE pointers since we want to read the rest as well */
		*fp_ppm = fp;
	return GMT_NOERROR;
}

GMT_LOCAL int gmtapi_import_ppm (struct GMT_CTRL *GMT, char *fname, struct GMT_IMAGE *I) {
	/* Reads a Portable Pixel Map (PPM) file if fname extension is .ppm, else returns 1 */
	FILE *fp = NULL;
	size_t size;

	if (gmtapi_import_ppm_header (GMT, fname, false, &fp, I)) return GMT_NOT_A_VALID_FAMILY;	/* Not a PPM */
	/* Now read the image in scanline order, with each pixel as (R, G, B) or (gray) */
	size = I->header->nm * I->header->n_bands;
	if (fread (I->data, sizeof(char), size, fp) != size) {
		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Failed to read the image from %s\n", fname);
		gmt_fclose (GMT, fp);
		return GMT_IMAGE_READ_ERROR;
	}
	gmt_fclose (GMT, fp);
	return GMT_NOERROR;
}

GMT_LOCAL bool gmtapi_expand_index_image (struct GMT_CTRL *GMT, struct GMT_IMAGE *I_in, struct GMT_IMAGE **I_out) {
    /* In most situations we can use an input image given to a module as the dataset to
     * plot.  However, if the image is indexed then we must expand it to rgb since we may
     * need to interpolate the r/g/b planes due to projections. If the image is read-only
     * then we cannot reallocate the array and must duplicate, otherwise we reallocate the
     * image array and expand to rgb.  This function is called at the end of gmtapi_import_image
     * if the GMT_IMAGE_NO_INDEX bitflag is passed. The image layout honors the current setting
     * of API_IMAGE_LAYOUT. */
    bool new = false;
    unsigned char *data = NULL;
    uint64_t node, off[3];
    size_t n_colors, n_len;
    unsigned int c, index;
    struct GMT_IMAGE *I = NULL;
    struct GMT_IMAGE_HIDDEN *IH = gmt_get_I_hidden (I_in);
    struct GMT_GRID_HEADER *h = I_in->header;
    struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (h);
    char *layout = (h->n_bands == 4 || I_in->alpha) ? "TRBa" : "TRB";
    n_len = strlen (layout);    /* 3 or 4 */
    if (I_in->n_indexed_colors == 0) {  /* Regular gray or r/g/b image - use as is */
        (*I_out) = I_in;
        return (false);
    }
    /* Here we have an indexed image */
    if (IH->alloc_mode == GMT_ALLOC_EXTERNALLY) {   /* Cannot reallocate a non-GMT read-only input array */
        if ((I = GMT_Duplicate_Data (GMT->parent, GMT_IS_IMAGE, GMT_DUPLICATE_DATA, I_in)) == NULL) {
            GMT_Report (GMT->parent, GMT_MSG_ERROR, "Unable to duplicate external image! - this is not a good thing and may crash this module\n");
            (*I_out) = I_in;
        }
        else {
            struct GMT_IMAGE_HIDDEN *IH = gmt_get_I_hidden (I);
            IH->alloc_mode = GMT_ALLOC_INTERNALLY;
        }
        new = true;
    }
    else    /* Here we may overwrite the input image and just pass the pointer back */
        I = I_in;

    /* Here, I is an image we can reallocate the array when expanding the colors */

    h = I->header;
    if ((data = gmt_M_memory_aligned (GMT, NULL, h->size * 3, unsigned char)) == NULL) return false;    /* The new r,g,b image */

    n_colors = I->n_indexed_colors;
    if (n_colors > 2000)            /* If colormap is Mx4 or has encoded the alpha color */
        n_colors = (uint64_t)(floor(n_colors / 1000.0));
    if (GMT->parent->GMT->current.gdal_read_in.O.mem_layout[0] && strncmp (GMT->parent->GMT->current.gdal_read_in.O.mem_layout, "TRB", 3U) == 0) {  /* Band interleave */
        strncpy (h->mem_layout, layout, n_len); /* Fill out red, green, and blue bands */
        for (c = 0; c < 3; c++) off[c] = c * h->size;
        for (node = 0; node < h->size; node++) {    /* For all pixels, including the pad */
            index = I->data[node];  /* Pixel index into color table */
            for (c = 0; c < 3; c++) data[node+off[c]] = gmt_M_get_rgba (I->colormap, index, c, n_colors);   /* Place r,g,b in separate bands */
        }
    }
    else {  /* Pixel interleave */
        uint64_t k;
        strncpy (h->mem_layout, layout, n_len); /* Fill out red, green, and blue pixels */
        for (node = k = 0; node < h->size; node++) {    /* For all pixels, including the pad */
            index = I->data[node];  /* Pixel index into color table */
            for (c = 0; c < 3; c++, k++) data[k] = gmt_M_get_rgba (I->colormap, index, c, n_colors);    /* Place r,g,b in separate bands */
        }
        /* If neither TRB or TRP we call for a changed layout, which may or may not have been implemented */
        GMT_Change_Layout (GMT->parent, GMT_IS_IMAGE, GMT->parent->GMT->current.gdal_read_in.O.mem_layout, 0, I, NULL, NULL);
    }
    gmt_M_free_aligned (GMT, I->data);  /* Free previous aligned image memory */
    I->data = data; /* Pass the reallocated rgb TRB image back */
    /* Reset meta data to reflect a regular 3-band r,g,b image */
    h->n_bands = 3;
    I->n_indexed_colors = 0;
    if (!gmt_M_is_fnan (h->nan_value)) { /* Preserve the NaN color index */
        unsigned int k = rint (h->nan_value);
        for (c = 0; c < 3; c++) HH->nan_rgb[c] = gmt_M_get_rgba (I->colormap, k, c, n_colors);  /* Save NaN r,g,b */
        HH->has_NaN_rgb = 1;
    }
    gmt_M_free (GMT, I->colormap);  /* Free the colormap */
    I->color_interp = NULL;

    (*I_out) = I;
    return (new);
}

int gmtlib_ind2rgb (struct GMT_CTRL *GMT, struct GMT_IMAGE **I_in) {
	/* Convert an indexed image to RGB. Other than indirect calls to gmtapi_expand_index_image, e.g., the one
	   called by gmtapi_import_image, there are other cases when we need also to convert from indexed to RGB.
	   For example in grdimage when the image was sent in via an external wrapper. In this case the code flow goes
	   through gmtapi_get_image_data() (in GMT_Read_Data -> gmtapi_pass_object (API, S_obj, family, mode, wesn))
	   and deliver that Image object directly to the calling module and may thus have indexed pixels.
	*/
	struct GMT_IMAGE* Irgb = NULL;
	if ((*I_in)->header->n_bands == 1 && (*I_in)->n_indexed_colors > 0) {		/* Indexed image, convert to RGB */
		gmtapi_expand_index_image (GMT, *I_in, &Irgb);	/* true if we have a read-only indexed image and we had to allocate a new one */
		if (GMT_Destroy_Data (GMT->parent, I_in) != GMT_NOERROR) {
			gmtlib_report_error(GMT->parent, GMT->parent->error);
			return GMT->parent->error;
		}
		(*I_in) = Irgb;
	}
	return GMT_NOERROR;
}

void gmtlib_GDALDestroyDriverManager (struct GMTAPI_CTRL *API) {
    /* Cannot close connection to GDAL if calling environment expect it to be open */
	if (API->external < 2) GDALDestroyDriverManager();
}

/*! . */
GMT_LOCAL struct GMT_IMAGE *gmtapi_import_image (struct GMTAPI_CTRL *API, int object_ID, unsigned int mode, struct GMT_IMAGE *image) {
	/* Handles the reading of a 2-D image given in one of several ways.
	 * Get the entire image:
 	 * 	mode = GMT_CONTAINER_AND_DATA reads both header and image;
	 * Get a subset of the image:  Call gmtapi_import_image twice:
	 * 	1. first with mode = GMT_CONTAINER_ONLY which reads header only.  Then, pass
	 *	   the new S_obj-> wesn to match your desired subregion
	 *	2. 2nd with mode = GMT_DATA_ONLY, which reads image based on header's settings
	 * If the image->data array is NULL it will be allocated for you.
	 */

	int item, new_item, new_ID;
	bool done = true, via = false, must_be_image = true, no_index = false, bc_not_set = true, new = false, have_CRS = false;
	uint64_t ij, ij_orig;
	openmp_int row, col, i0, i1, j0, j1;
	unsigned int both_set = (GMT_CONTAINER_ONLY | GMT_DATA_ONLY);
	size_t size;
	double dx, dy, d;
	p_func_uint64_t GMT_2D_to_index = NULL;
	GMT_getfunction api_get_val = NULL;
	struct GMT_IMAGE *I_obj = NULL, *I_orig = NULL;
	struct GMT_MATRIX *M_obj = NULL;
	struct GMT_MATRIX_HIDDEN  *MH = NULL;
	struct GMT_IMAGE_HIDDEN *IH = NULL;
	struct GMT_GRID_HEADER_HIDDEN *HH = NULL;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMT_CTRL *GMT = API->GMT;
	struct GMT_IMAGE *Irgb = NULL;

	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_image: Passed ID = %d and mode = %d\n", object_ID, mode);

	if ((item = gmtlib_validate_id (API, GMT_IS_IMAGE, object_ID, GMT_IN, GMT_NOTSET)) == GMT_NOTSET) return_null (API, API->error);

	S_obj = API->object[item];		/* Current data object */
	if (S_obj->status != GMT_IS_UNUSED && !(mode & GMT_IO_RESET))
		return_null (API, GMT_READ_ONCE);	/* Already read this resources before, so fail unless overridden by mode */
	if ((mode & GMT_IMAGE_NO_INDEX)) no_index = true, mode -= GMT_IMAGE_NO_INDEX;	/* Must expand any index to rgb */
	if ((mode & both_set) == both_set) mode -= both_set;	/* Allow users to have set GMT_CONTAINER_ONLY | GMT_DATA_ONLY; reset to GMT_CONTAINER_AND_DATA */
	if ((mode & GMT_GRID_IS_IMAGE) == GMT_GRID_IS_IMAGE) {	/* Only allowed when fishing the image header and it may in fact be a grid */
		if (mode & GMT_DATA_ONLY) {
			GMT_Report (API, GMT_MSG_ERROR, "Cannot pass mode = GMT_GRID_IS_IMAGE when reading the image for file %s\n", S_obj->filename);
			return_null (API, GMT_IMAGE_READ_ERROR);
		}
		mode -= GMT_GRID_IS_IMAGE;
		must_be_image = false;
	}

	switch (S_obj->method) {
		case GMT_IS_FILE:	/* Name of an image file on disk */
			if (image == NULL) {	/* Only allocate image struct when not already allocated */
				if (mode & GMT_DATA_ONLY) return_null (API, GMT_NO_GRDHEADER);		/* For mode & GMT_DATA_ONLY image must already be allocated */
				I_obj = gmtlib_create_image (GMT);
				new = true;
			}
			else
				I_obj = image;	/* We are passing in an image already allocated */
			HH = gmt_get_H_hidden (I_obj->header);
			I_obj->header->complex_mode = (mode & GMT_GRID_IS_COMPLEX_MASK);		/* Pass on any bitflags */
			done = (mode & GMT_CONTAINER_ONLY) ? false : true;	/* Not done until we read image */
			if (!(mode & GMT_DATA_ONLY)) {		/* Must init header and read the header information from file */
				if (gmtapi_import_ppm_header (GMT, S_obj->filename, true, NULL, I_obj) == GMT_NOERROR)
					d = 0.0;	/* Placeholder */
				else if (gmt_M_err_pass (GMT, gmtlib_read_image_info (GMT, S_obj->filename, must_be_image, I_obj), S_obj->filename)) {
					if (new) gmtlib_free_image (GMT, &I_obj, false);
					return_null (API, GMT_IMAGE_READ_ERROR);
				}
				if (mode & GMT_CONTAINER_ONLY) break;	/* Just needed the header, get out of here */
			}
			have_CRS = (I_obj->header->ProjRefPROJ4 || I_obj->header->ProjRefWKT || I_obj->header->ProjRefEPSG != 0) ? true : false;
			/* Here we will read the image data themselves. To get a subset we use wesn that is not NULL or contain 0/0/0/0.
			   Otherwise we extract the entire file domain */
			/* BUT WE HAVE A MESS SOMEWHERE.
			   When doing a grdview drape (-G) in Modern mode GMT->common.R.active[RSET] arrives = true but it Classic it was false,
			   so I had to set it to true in grdview (~# 884). However, having set to true means that gdalread would read it
			   upside-down, so if image is not referenced GMT->common.R.active[RSET] is set to false.
			   This is all a damn hack, but I couldn't find any other cleaner way to make it work for all the combinations of:
			   Classic/Modern/Referenced/notReferenced.
			*/
			if (have_CRS && (GMT->common.R.active[RSET] && !S_obj->region && !GMT->common.R.oblique)) { /* subregion not passed to object yet */
				gmt_M_memcpy (S_obj->wesn, GMT->common.R.wesn, 4U, double);
				S_obj->region = true;
			}
			else if (!have_CRS)
				GMT->common.R.active[RSET] = false;

			size = gmtapi_set_grdarray_size (GMT, I_obj->header, mode, S_obj->wesn);    /* Get array dimension only, which includes padding. DANGER DANGER JL*/
			if (!I_obj->data) {	/* Array is not allocated yet, do so now. We only expect header (and possibly w/e/s/n subset) to have been set correctly */
				if (I_obj->type <= GMT_UCHAR)
					I_obj->data = gmt_M_memory (GMT, NULL, size * I_obj->header->n_bands, unsigned char);
				else if (I_obj->type <= GMT_USHORT)
					I_obj->data = gmt_M_memory (GMT, NULL, size * I_obj->header->n_bands, unsigned short);
				else if (I_obj->type <= GMT_UINT)
					I_obj->data = gmt_M_memory (GMT, NULL, size * I_obj->header->n_bands, unsigned int);
				else if (I_obj->type <= GMT_FLOAT)
					I_obj->data = gmt_M_memory (GMT, NULL, size * I_obj->header->n_bands, float);
				else if (I_obj->type <= GMT_DOUBLE)
					I_obj->data = gmt_M_memory (GMT, NULL, size * I_obj->header->n_bands, float);   /* Not doing double yet */
				else {
					GMT_Report (API, GMT_MSG_ERROR, "Unsupported image data type %d\n", I_obj->type);
					return_null (API, GMT_NOT_A_VALID_TYPE);
				}
				if (I_obj->data == NULL) return_null (API, GMT_MEMORY_ERROR);
			}
			else {	/* Already have allocated space; check that it is enough */
				if (size > I_obj->header->size) return_null (API, GMT_IMAGE_READ_ERROR);
			}
			GMT_Report (API, GMT_MSG_INFORMATION, "Reading image from file %s\n", S_obj->filename);
			if (gmtapi_import_ppm (GMT, S_obj->filename, I_obj) == GMT_NOERROR)
				d = 0.0;	/* Placeholder only */
			else if (gmt_M_err_pass (GMT, gmtlib_read_image (GMT, S_obj->filename, I_obj, S_obj->wesn, I_obj->header->pad, mode), S_obj->filename))
				return_null (API, GMT_IMAGE_READ_ERROR);
			if (I_obj->n_indexed_colors == 0) {	/* May set the BCs */
				if (gmt_M_err_pass (GMT, gmtlib_image_BC_set (GMT, I_obj), S_obj->filename))
					return_null (API, GMT_IMAGE_BC_ERROR);	/* Set boundary conditions */
				bc_not_set = false;
			}
			IH = gmt_get_I_hidden (I_obj);
			IH->alloc_mode = GMT_ALLOC_INTERNALLY;
			IH->alloc_level = GMT->hidden.func_level;   /* Must be freed at this level. */
			break;

	 	case GMT_IS_DUPLICATE:	/* GMT image and header in a GMT_IMAGE container object. */
			if ((I_orig = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			if (image == NULL) {	/* Only allocate when not already allocated */
				if (mode & GMT_DATA_ONLY) return_null (API, GMT_NO_GRDHEADER);		/* For mode & GMT_DATA_ONLY image must already be allocated */
				I_obj = gmtlib_create_image (GMT);
			}
			else
				I_obj = image;	/* We are passing in an image already */
			done = (mode & GMT_CONTAINER_ONLY) ? false : true;	/* Not done until we read image */
			if (! (mode & GMT_DATA_ONLY)) {	/* Must init header and copy the header information from the existing image */
				gmt_copy_gridheader (GMT, I_obj->header, I_orig->header);
				if (mode & GMT_CONTAINER_ONLY) break;	/* Just needed the header, get out of here */
			}
			/* Here we will read image data. */
			/* To get a subset we use wesn that is not NULL or contain 0/0/0/0.
			 * Otherwise we use everything passed in */
			if (!I_obj->data) {	/* Array is not allocated, do so now. We only expect header (and possibly subset w/e/s/n) to have been set correctly */
				I_obj->header->size = gmtapi_set_grdarray_size (GMT, I_obj->header, mode, S_obj->wesn);	/* Get array dimension only, which may include padding */
				if ((I_obj->data = gmt_M_memory (GMT, NULL, I_obj->header->size * I_obj->header->n_bands, unsigned char)) == NULL) return_null (API, GMT_MEMORY_ERROR);
				if (I_orig->alpha && (I_obj->alpha = gmt_M_memory (GMT, NULL, I_obj->header->size , unsigned char)) == NULL) return_null (API, GMT_MEMORY_ERROR);
			}
			IH = gmt_get_I_hidden (I_obj);
			IH->alloc_mode = GMT_ALLOC_INTERNALLY;
			IH->alloc_level = GMT->hidden.func_level;   /* Must be freed at this level. */
			if (!S_obj->region && gmt_grd_pad_status (GMT, I_obj->header, GMT->current.io.pad)) {	/* Want an exact copy with no subset and same padding */
				GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating image data from GMT_IMAGE memory location\n");
				gmt_M_memcpy (I_obj->data, I_orig->data, I_orig->header->size * I_orig->header->n_bands, char);
				if (I_orig->alpha) gmt_M_memcpy (I_obj->alpha, I_orig->alpha, I_orig->header->size, char);
				break;		/* Done with this image */
			}
			GMT_Report (API, GMT_MSG_INFORMATION, "Extracting subset image data from GMT_IMAGE memory location\n");
			/* Here we need to do more work: Either extract subset or add/change padding, or both. */
			/* Get start/stop row/cols for subset (or the entire domain) */
			/* dx,dy are needed when the image is pixel-registered as the w/e/s/n bounds are off by 0.5 {dx,dy} relative to node coordinates */
			dx = I_obj->header->inc[GMT_X] * I_obj->header->xy_off;	dy = I_obj->header->inc[GMT_Y] * I_obj->header->xy_off;
			j1 = (uint64_t) gmt_M_grd_y_to_row (GMT, I_obj->header->wesn[YLO]+dy, I_orig->header);
			j0 = (uint64_t) gmt_M_grd_y_to_row (GMT, I_obj->header->wesn[YHI]-dy, I_orig->header);
			i0 = (uint64_t) gmt_M_grd_x_to_col (GMT, I_obj->header->wesn[XLO]+dx, I_orig->header);
			i1 = (uint64_t) gmt_M_grd_x_to_col (GMT, I_obj->header->wesn[XHI]-dx, I_orig->header);
			gmt_M_memcpy (I_obj->header->pad, GMT->current.io.pad, 4, int);	/* Set desired padding */
			for (row = j0; row <= j1; row++) {
				for (col = i0; col <= i1; col++, ij++) {
					ij_orig = gmt_M_ijp (I_orig->header, row, col);	/* Position of this (row,col) in original image organization */
					ij = gmt_M_ijp (I_obj->header, row, col);		/* Position of this (row,col) in output image organization */
					I_obj->data[ij] = I_orig->data[ij_orig];
					if (I_orig->alpha) I_obj->alpha[ij] = I_orig->alpha[ij_orig];
				}
			}
			break;

	 	case GMT_IS_REFERENCE:	/* GMT image and header in a GMT_IMAGE container object by reference */
			if (S_obj->region) return_null (API, GMT_SUBSET_NOT_ALLOWED);
			GMT_Report (API, GMT_MSG_INFORMATION, "Referencing image data from GMT_IMAGE memory location\n");
			if ((I_obj = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			done = (mode & GMT_CONTAINER_ONLY) ? false : true;	/* Not done until we read image */
			GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_image: Change alloc mode\n");
			GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_image: Check pad\n");
			if (!gmtapi_adjust_grdpadding (I_obj->header, GMT->current.io.pad)) break;	/* Pad is correct so we are done */
			/* Here we extend G_obj->data to allow for padding, then rearrange rows, but only if item was allocated by GMT */
			IH = gmt_get_I_hidden (I_obj);
			if (IH->alloc_mode == GMT_ALLOC_EXTERNALLY) return_null (API, GMT_PADDING_NOT_ALLOWED);
			GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_image: Add pad\n");
#if 0
			gmt_grd_pad_on (GMT, image, GMT->current.io.pad);
#endif
			if (done && S_obj->region) {	/* Possibly adjust the pad so inner region matches wesn */
				HH = gmt_get_H_hidden (I_obj->header);
				if (S_obj->reset_pad) {	/* First undo a prior sub-region used with this memory image */
					gmtapi_contract_headerpad (GMT, I_obj->header, S_obj->orig_pad, S_obj->orig_wesn);
					S_obj->reset_pad = HH->reset_pad = 0;
				}
				if (gmtapi_expand_headerpad (GMT, I_obj->header, S_obj->wesn, S_obj->orig_pad, S_obj->orig_wesn))
					S_obj->reset_pad = HH->reset_pad = 1;
			}
			GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_image: Return from GMT_IS_REFERENCE\n");
			break;

	 	case GMT_IS_DUPLICATE|GMT_VIA_MATRIX:	/* The user's 2-D image array of some sort, + info in the args [NOT YET FULLY TESTED] */
			if ((M_obj = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			if (S_obj->region) return_null (API, GMT_SUBSET_NOT_ALLOWED);
			I_obj = (image == NULL) ? gmtlib_create_image (GMT) : image;	/* Only allocate when not already allocated */
			HH = gmt_get_H_hidden (I_obj->header);
			I_obj->header->complex_mode = (mode & GMT_GRID_IS_COMPLEX_MASK);	/* Set the complex mode */
			if (! (mode & GMT_DATA_ONLY)) {
				gmtapi_matrixinfo_to_grdheader (GMT, I_obj->header, M_obj);	/* Populate a GRD header structure */
				if (mode & GMT_CONTAINER_ONLY) break;	/* Just needed the header */
			}
			IH = gmt_get_I_hidden (I_obj);
			IH->alloc_mode = GMT_ALLOC_INTERNALLY;
			IH->alloc_level = GMT->hidden.func_level;   /* Must be freed at this level. */
			/* Must convert to new array */
			GMT_Report (API, GMT_MSG_INFORMATION, "Importing image data from user memory location\n");
			gmt_set_grddim (GMT, I_obj->header);	/* Set all dimensions */
			if ((I_obj->data = gmt_M_memory (GMT, NULL, I_obj->header->size, unsigned char)) == NULL) return_null (API, GMT_MEMORY_ERROR);
			if ((GMT_2D_to_index = gmtapi_get_2d_to_index (API, M_obj->shape, GMT_GRID_IS_REAL)) == NULL)
				return_null (API, GMT_WRONG_MATRIX_SHAPE);
			if ((api_get_val = gmtapi_select_get_function (API, M_obj->type)) == NULL)
				return_null (API, GMT_NOT_A_VALID_TYPE);
			gmt_M_grd_loop (GMT, I_obj, row, col, ij) {
				ij_orig = GMT_2D_to_index (row, col, M_obj->dim);
				api_get_val (&(M_obj->data), ij_orig, &d);
				I_obj->data[ij] = (char)d;
			}
			new_ID = GMT_Register_IO (API, GMT_IS_IMAGE, GMT_IS_DUPLICATE, S_obj->geometry, GMT_IN, NULL, I_obj);	/* Register a new resource to hold I_obj */
			if ((new_item = gmtlib_validate_id (API, GMT_IS_IMAGE, new_ID, GMT_IN, GMT_NOTSET)) == GMT_NOTSET)
				return_null (API, GMT_OBJECT_NOT_FOUND);	/* Some internal error... */
			API->object[new_item]->resource = I_obj;
			API->object[new_item]->status = GMT_IS_USED;	/* Mark as read */
			API->object[new_item]->method = S_obj->method;
			IH->alloc_level = API->object[new_item]->alloc_level;	/* Since allocated here */
			via = true;
			if (S_obj->region) {	/* Possibly adjust the pad so inner region matches wesn */
				if (S_obj->reset_pad) {	/* First undo a prior sub-region used with this memory image */
					gmtapi_contract_headerpad (GMT, I_obj->header, S_obj->orig_pad, S_obj->orig_wesn);
					S_obj->reset_pad = HH->reset_pad = 0;
				}
				if (gmtapi_expand_headerpad (GMT, I_obj->header, S_obj->wesn, S_obj->orig_pad, S_obj->orig_wesn))
					S_obj->reset_pad = HH->reset_pad = 1;
			}
			break;

	 	case GMT_IS_REFERENCE|GMT_VIA_MATRIX:	/* The user's 2-D image array of some sort, + info in the args [NOT YET FULLY TESTED] */
			if ((M_obj = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			if (S_obj->region) return_null (API, GMT_SUBSET_NOT_ALLOWED);
			I_obj = (image == NULL) ? gmtlib_create_image (GMT) : image;	/* Only allocate when not already allocated */
			HH = gmt_get_H_hidden (I_obj->header);
			I_obj->header->complex_mode = (mode & GMT_GRID_IS_COMPLEX_MASK);	/* Set the complex mode */
			if (! (mode & GMT_DATA_ONLY)) {
				gmtapi_matrixinfo_to_grdheader (GMT, I_obj->header, M_obj);	/* Populate a GRD header structure */
				if (mode & GMT_CONTAINER_ONLY) break;	/* Just needed the header */
			}
			MH = gmt_get_M_hidden (M_obj);
			if (!(M_obj->shape == GMT_IS_ROW_FORMAT && M_obj->type == GMT_FLOAT && MH->alloc_mode == GMT_ALLOC_EXTERNALLY && (mode & GMT_GRID_IS_COMPLEX_MASK))) {
				return_null (API, GMT_NOT_A_VALID_IO_ACCESS);
			}
			GMT_Report (API, GMT_MSG_INFORMATION, "Referencing image data from user memory location\n");
			IH = gmt_get_I_hidden (I_obj);
			I_obj->data = (unsigned char *)(M_obj->data.sc1);
			S_obj->alloc_mode = MH->alloc_mode;	/* Pass on allocation mode of matrix */
			IH->alloc_mode = MH->alloc_mode;
			if (!gmtapi_adjust_grdpadding (I_obj->header, GMT->current.io.pad)) break;	/* Pad is correct so we are done */
			if (IH->alloc_mode == GMT_ALLOC_EXTERNALLY) {
				return_null (API, GMT_PADDING_NOT_ALLOWED);
			}
			/* Here we extend I_obj->data to allow for padding, then rearrange rows */
			/* gmt_grd_pad_on (GMT, I, GMT->current.io.pad);*/
			new_ID = GMT_Register_IO (API, GMT_IS_IMAGE, GMT_IS_REFERENCE, S_obj->geometry, GMT_IN, NULL, I_obj);	/* Register a new resource to hold I_obj */
			if ((new_item = gmtlib_validate_id (API, GMT_IS_IMAGE, new_ID, GMT_IN, GMT_NOTSET)) == GMT_NOTSET)
				return_null (API, GMT_OBJECT_NOT_FOUND);	/* Some internal error... */
			API->object[new_item]->resource = I_obj;
			API->object[new_item]->status = GMT_IS_USED;	/* Mark as read */
			IH->alloc_level = API->object[new_item]->alloc_level;	/* Since allocated here */
			via = true;
			if (S_obj->region) {	/* Possibly adjust the pad so inner region matches wesn */
				if (S_obj->reset_pad) {	/* First undo a prior sub-region used with this memory image */
					gmtapi_contract_headerpad (GMT, I_obj->header, S_obj->orig_pad, S_obj->orig_wesn);
					S_obj->reset_pad = HH->reset_pad = 0;
				}
				if (gmtapi_expand_headerpad (GMT, I_obj->header, S_obj->wesn, S_obj->orig_pad, S_obj->orig_wesn))
					S_obj->reset_pad = HH->reset_pad = 1;
			}
			break;

		default:
			GMT_Report (API, GMT_MSG_ERROR, "Wrong method used to import image\n");
			return_null (API, GMT_NOT_A_VALID_METHOD);
			break;
	}
	if ((mode & GMT_CONTAINER_ONLY) == 0) {	/* Also allocate and initialize the x and y vectors */
		I_obj->x = gmtapi_image_coord (API, GMT_X, I_obj);	/* Get array of x coordinates */
		I_obj->y = gmtapi_image_coord (API, GMT_Y, I_obj);	/* Get array of y coordinates */
	}

	if (done) S_obj->status = GMT_IS_USED;	/* Mark as read (unless we just got the header) */

	if (no_index) {   /* true if we have an indexed image and we had to allocate a new one */
		if (gmtapi_expand_index_image (API->GMT, I_obj, &Irgb)) {   /* true if we have a read-only indexed image and we had to allocate a new one */
			if (GMT_Destroy_Data (API, &I_obj) != GMT_NOERROR) {
				return_null (API, API->error);
			}
			I_obj = Irgb;
		}
		/* If we were unable to set BCs earlier we must do it now */
		if (bc_not_set && gmt_M_err_pass (GMT, gmtlib_image_BC_set (GMT, I_obj), S_obj->filename))
			return_null (API, GMT_IMAGE_BC_ERROR);	/* Failed to set boundary conditions */
	}

	if (!via) S_obj->resource = I_obj;	/* Retain pointer to the allocated data so we use garbage collection later */

	return (I_obj);	/* Pass back out what we have so far */
}

GMT_LOCAL int gmtapi_export_ppm (struct GMT_CTRL *GMT, char *fname, struct GMT_IMAGE *I) {
	/* Write a Portable Pixel Map (PPM) file if fname extension is .ppm, else returns 1.
	 * We assume there is no pad, otherwise the pad will be part of the image on output. */
	//uint32_t row, col, band;
	static char *comment = "# Produced by GMT\n";
	char *ext = gmt_get_ext (fname), dim[GMT_LEN32] = {""};
	size_t n;
	FILE *fp = NULL;
	if (ext == NULL || strcmp (ext, "ppm")) return GMT_NOT_A_VALID_FAMILY;	/* Not requesting a PPM file - return GMT_NOT_A_VALID_FAMILY and let GDAL take over */

	if ((fp = gmt_fopen (GMT, fname, GMT->current.io.w_mode)) == NULL) {	/* Return GMT_ERROR_ON_FOPEN to signify failure */
		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Cannot create PPM file %s\n", fname);
		return GMT_ERROR_ON_FOPEN;
	}
	if (I->header->n_bands == 1) /* Use P5 for grayscale image */
		n = fwrite ("P5\n", sizeof (char), 3U, fp);	/* Write magic number, linefeed */
	else	/* Use P6 for rgb image */
		n = fwrite ("P6\n", sizeof (char), 3U, fp);	/* Write magic number, linefeed */
	if (n != 3U) {
		gmt_fclose (GMT, fp);
		return GMT_IMAGE_WRITE_ERROR;
	}
	n = strlen (comment);
	if (fwrite (comment, sizeof (char), n, fp) != n) {
		gmt_fclose (GMT, fp);
		return GMT_IMAGE_WRITE_ERROR;	/* Write comment and linefeed */
	}
	snprintf (dim, GMT_LEN32, "%d %d\n255\n", I->header->mx, I->header->my);
	n = strlen (dim);
	if (fwrite (dim, sizeof (char), n, fp) != n) return GMT_IMAGE_WRITE_ERROR;	/* Write dimensions and max color value + linefeeds */
	/* Now dump the image in scanline order, with each pixel as (R, G, B) */
	if (I->alpha)
		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Alpha-channel not supported by PPM format - ignored\n");
	n = I->header->size * I->header->n_bands;
	if (!strncmp (I->header->mem_layout, "TRP", 3U)) { /* Easy street! */
		if (fwrite (I->data, sizeof(char), n, fp) != n) {
			gmt_fclose (GMT, fp);
			return GMT_IMAGE_WRITE_ERROR;
		}
	}
	else {	/* Must change image layout first as PPM is strictly TRP */
		char *data = NULL;
		GMT_Report (GMT->parent, GMT_MSG_VERBOSE, "Must convert image from %s to TRP in order to write PPM file\n", I->header->mem_layout);
		if ((data = gmt_M_memory_aligned (GMT, NULL, n, char)) == NULL) {
			GMT_Report (GMT->parent, GMT_MSG_ERROR, "Unable to allocate image memory in gmtapi_export_ppm to force TRP format - written as is\n");
			if (fwrite (I->data, sizeof(char), n, fp) != n) {
				gmt_fclose (GMT, fp);
				return GMT_IMAGE_WRITE_ERROR;
			}
		}
		else {	/* Convert from TRB to TRP */
			GMT_Change_Layout (GMT->parent, GMT_IS_IMAGE, "TRP", 0, I, data, NULL);
			if (fwrite (data, sizeof(char), n, fp) != n) return GMT_IMAGE_WRITE_ERROR;
			gmt_M_free_aligned (GMT, data);
		}
	}
	gmt_fclose (GMT, fp);

	return GMT_NOERROR;
}

/*! Writes out a single image to destination */
GMT_LOCAL int gmtapi_export_image (struct GMTAPI_CTRL *API, int object_ID, unsigned int mode, struct GMT_IMAGE *I_obj) {
	int item, error;
	bool done = true;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMT_IMAGE *I_copy = NULL;
	struct GMT_IMAGE_HIDDEN *IH = gmt_get_I_hidden (I_obj);

	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_export_image: Passed ID = %d and mode = %d\n", object_ID, mode);

	if (object_ID == GMT_NOTSET) return (gmtlib_report_error (API, GMT_OUTPUT_NOT_SET));
	if (I_obj->data == NULL && !(mode & GMT_CONTAINER_ONLY)) return (gmtlib_report_error (API, GMT_PTR_IS_NULL));
	if ((item = gmtlib_validate_id (API, GMT_IS_IMAGE, object_ID, GMT_OUT, GMT_NOTSET)) == GMT_NOTSET) return (gmtlib_report_error (API, API->error));

	S_obj = API->object[item];	/* The current object whose data we will export */
	if (S_obj->status != GMT_IS_UNUSED && !(mode & GMT_IO_RESET))
		return (gmtlib_report_error (API, GMT_WRITTEN_ONCE));	/* Only allow writing of a data set once, unless overridden by mode */
	if (mode & GMT_IO_RESET) mode -= GMT_IO_RESET;
	switch (S_obj->method) {
		case GMT_IS_FILE:	/* Name of an image file on disk */
			GMT_Report (API, GMT_MSG_INFORMATION, "Writing image to file %s\n", S_obj->filename);
			if ((error = gmtapi_export_ppm (API->GMT, S_obj->filename, I_obj)) == GMT_NOERROR)
				break;	/* OK, wrote a PPM and we are done */
			else if (error == GMT_ERROR_ON_FOPEN) {	/* Failed to open file */
				GMT_Report (API, GMT_MSG_ERROR, "Unable to export image\n");
				return (gmtlib_report_error (API, GMT_ERROR_ON_FOPEN));
			}
			else if (gmt_M_err_pass (API->GMT, gmt_export_image (API->GMT, S_obj->filename, I_obj), S_obj->filename))
				return (gmtlib_report_error (API, GMT_IMAGE_WRITE_ERROR));
			break;

	 	case GMT_IS_DUPLICATE:	/* Duplicate GMT image to a new GMT_IMAGE container object */
			if (S_obj->resource) return (gmtlib_report_error (API, GMT_PTR_NOT_NULL));	/* The output resource pointer must be NULL */
			if (mode & GMT_CONTAINER_ONLY) return (gmtlib_report_error (API, GMT_NOT_A_VALID_MODE));
			GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating image data to GMT_IMAGE memory location\n");
			if ((I_copy = GMT_Duplicate_Data (API, GMT_IS_IMAGE, mode, I_obj)) == NULL)
				return (gmtlib_report_error (API, GMT_MEMORY_ERROR));
			S_obj->resource = I_copy;	/* Set resource pointer to the image */
			break;		/* Done with this image */

	 	case GMT_IS_REFERENCE:	/* GMT image and header in a GMT_IMAGE container object - just pass the reference */
			if (S_obj->region) return (gmtlib_report_error (API, GMT_SUBSET_NOT_ALLOWED));
			if (mode & GMT_CONTAINER_ONLY) return (gmtlib_report_error (API, GMT_NOT_A_VALID_MODE));
			GMT_Report (API, GMT_MSG_INFORMATION, "Referencing image data to GMT_IMAGE memory location\n");
			S_obj->resource = I_obj;	/* Set resource pointer to the image */
			IH->alloc_level = S_obj->alloc_level;	/* Since we are passing it up to the caller */
			break;

		default:
			GMT_Report (API, GMT_MSG_ERROR, "Wrong method used to export image\n");
			return (gmtlib_report_error (API, GMT_NOT_A_VALID_METHOD));
			break;
	}

	if (done) S_obj->status = GMT_IS_USED;	/* Mark as written (unless we only updated header) */

	return (GMT_NOERROR);
}

unsigned int gmt_whole_earth (struct GMT_CTRL *GMT, double we_in[], double we_out[]) {
	/* Determines if this is a global geographic grid and we want the whole world, regardless of central longitude */
	if (!gmt_M_is_geographic (GMT, GMT_IN)) return 0;
	if (!gmt_M_360_range (we_in[XLO],  we_in[XHI])) return 0;
	if (!gmt_M_360_range (we_out[XLO], we_out[XHI])) return 0;
	if (doubleAlmostEqualZero (we_in[XLO], we_out[XLO])) return 2;	/* Both regions are the same */
	return 1;	/* Different central meridians */
}

GMT_LOCAL unsigned int gmtapi_switch_method (struct GMTAPI_CTRL *API, struct GMTAPI_DATA_OBJECT *S, unsigned int *method, char *message) {
	/* Flip input method from GMT_IS_REFERENCE to GMT_IS_DUPLICATE */
	*method -= GMT_IS_REFERENCE;
	*method += GMT_IS_DUPLICATE;
	S->method -= GMT_IS_REFERENCE;
	S->method += GMT_IS_DUPLICATE;
	GMT_Report (API, GMT_MSG_INFORMATION, message);
	return GMT_IS_DUPLICATE;
}

/*! . */
GMT_LOCAL struct GMT_GRID *gmtapi_import_grid (struct GMTAPI_CTRL *API, int object_ID, unsigned int mode, struct GMT_GRID *grid) {
	/* Handles the reading of a 2-D grid given in one of several ways.
	 * Get the entire grid:
 	 * 	mode = GMT_CONTAINER_AND_DATA reads both header and grid;
	 * Get a subset of the grid:  Call gmtapi_import_grid twice:
	 * 	1. first with mode = GMT_CONTAINER_ONLY which reads header only.  Then, pass
	 *	   the new S_obj-> wesn to match your desired subregion
	 *	2. 2nd with mode = GMT_DATA_ONLY, which reads grid based on header's settings
	 * If the grid->data array is NULL it will be allocated for you.
	 */

	int item, new_item, new_ID, err;
	bool done = true, new = false, row_by_row;
	openmp_int row, col, kol, row_out, i0, i1, j0, j1;
	uint64_t ij, ij_orig;
	size_t size;
	unsigned int both_set = (GMT_CONTAINER_ONLY | GMT_DATA_ONLY);
	unsigned int method, start_over_method = 0;
	double dx, dy, d;
	p_func_uint64_t GMT_2D_to_index = NULL;
	struct GMT_GRID *G_obj = NULL, *G_orig = NULL;
	struct GMT_MATRIX *M_obj = NULL;
	struct GMT_MATRIX_HIDDEN *MH = NULL;
	struct GMT_GRID_HIDDEN *GH = NULL, *GH2 = NULL;
	struct GMT_GRID_HEADER_HIDDEN *HH = NULL;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMT_CTRL *GMT = API->GMT;
	GMT_getfunction api_get_val = NULL;

	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_grid: Passed ID = %d and mode = %d\n", object_ID, mode);

	if ((item = gmtlib_validate_id (API, GMT_IS_GRID, object_ID, GMT_IN, GMT_NOTSET)) == GMT_NOTSET) return_null (API, API->error);

	S_obj = API->object[item];		/* Current data object */
	if (S_obj->status != GMT_IS_UNUSED && S_obj->method == GMT_IS_FILE && !(mode & GMT_IO_RESET)) return_null (API, GMT_READ_ONCE);	/* Already read this file before, so fail unless overridden by mode */
	if ((mode & both_set) == both_set) mode -= both_set;	/* Allow users to have set GMT_CONTAINER_ONLY | GMT_DATA_ONLY; reset to GMT_CONTAINER_AND_DATA */
	row_by_row = ((mode & GMT_GRID_ROW_BY_ROW) || (mode & GMT_GRID_ROW_BY_ROW_MANUAL));
	if (row_by_row && S_obj->method != GMT_IS_FILE) {
		GMT_Report (API, GMT_MSG_ERROR, "Can only use method GMT_IS_FILE when row-by-row reading of grid is selected\n");
		return_null (API, GMT_NOT_A_VALID_METHOD);
	}
	if ((mode & GMT_CONTAINER_ONLY) && S_obj->region && S_obj->method == GMT_IS_FILE) {
		GMT_Report (API, GMT_MSG_ERROR, "Cannot request a subset when just inquiring about the grid header\n");
		return_null (API, GMT_SUBSET_NOT_ALLOWED);
	}

	if (S_obj->region && grid) {	/* See if this is really a subset or just the same region as the grid */
		if (grid->header->wesn[XLO] == S_obj->wesn[XLO] && grid->header->wesn[XHI] == S_obj->wesn[XHI] && grid->header->wesn[YLO] == S_obj->wesn[YLO] && grid->header->wesn[YHI] == S_obj->wesn[YHI]) S_obj->region = false;
	}
	method = gmtapi_set_method (S_obj);	/* Get the actual method to use since may be MATRIX or VECTOR masquerading as GRID */

start_over_import_grid:		/* We may get here if we cannot honor a GMT_IS_REFERENCE from below */

	switch (method) {
		/* Status: This case is fully tested and operational */
		case GMT_IS_FILE:	/* Name of a grid file on disk */
			if (gmt_file_is_tiled_list (API, S_obj->filename, NULL, NULL, NULL)) {	/* Special list file of individual grid tiles */
				if (grid == NULL) {	/* Only allocate grid struct when not already allocated */
					if (mode & GMT_DATA_ONLY) return_null (API, GMT_NO_GRDHEADER);		/* For mode & GMT_DATA_ONLY the grid must already be allocated */
					if (API->got_remote_wesn) { /* Use the tile information */
						unsigned g_reg = (API->tile_reg == 'g') ? GMT_GRID_NODE_REG : GMT_GRID_PIXEL_REG;
						double g_inc[2] = {API->tile_inc, API->tile_inc}; /* Must duplicate due to syntax below */
						if (!full_region (API->tile_wesn)) {	/* Ensure any -R via DCW is rounded by these known increments */
							API->tile_wesn[XLO] = floor ((API->tile_wesn[XLO] / g_inc[GMT_X]) + GMT_CONV8_LIMIT) * g_inc[GMT_X];
							API->tile_wesn[XHI] = ceil  ((API->tile_wesn[XHI] / g_inc[GMT_X]) - GMT_CONV8_LIMIT) * g_inc[GMT_X];
							API->tile_wesn[YLO] = floor ((API->tile_wesn[YLO] / g_inc[GMT_Y]) + GMT_CONV8_LIMIT) * g_inc[GMT_Y];
							API->tile_wesn[YHI] = ceil  ((API->tile_wesn[YHI] / g_inc[GMT_Y]) - GMT_CONV8_LIMIT) * g_inc[GMT_Y];
						}
	 					if ((G_obj = gmt_create_grid (API->GMT)) == NULL)
	 						return_null (API, GMT_MEMORY_ERROR);	/* Allocation error */
						if (gmtapi_init_grid (API, NULL, NULL, API->tile_wesn, g_inc, g_reg, GMT_CONTAINER_ONLY, GMT_IN, G_obj))
	 						return_null (API, GMT_MEMORY_ERROR);	/* Allocation error */
						S_obj->resource = grid = G_obj;	/* Set resource pointer to the grid */
					}
					else {  /* Should not (cannot) happen */
						GMT_Report (API, GMT_MSG_ERROR, "No w/e/s/n dx/dy reg found for this tiled dataset? Internal error\n");
						return_null (API, GMT_RUNTIME_ERROR);    /* Allocation error */
					}
					if (mode & GMT_CONTAINER_ONLY) break;	/* Just needed the header, get out of here */
				}
				/* Here we must assemble to grid from the list of tiles */
				if ((G_obj = gmtlib_assemble_tiles (API, NULL, S_obj->filename)) == NULL)
					return_null (API, GMT_GRID_READ_ERROR);
				if (gmt_M_err_pass (GMT, gmt_grd_BC_set (GMT, G_obj, GMT_IN), S_obj->filename))
					return_null (API, GMT_GRID_BC_ERROR);	/* Set boundary conditions */
				/* Sneaky internal recycling of the contents of the grid structure based on final results in G_obj */
				GH  = gmt_get_G_hidden (grid);
				GH2 = gmt_get_G_hidden (G_obj);
				/* Copy over the hidden grid settings obtained in grdblend */
				GH->alloc_mode  = GH2->alloc_mode;
				GH->alloc_level = GH2->alloc_level;
				GH->xy_alloc_mode[GMT_X] = GH2->xy_alloc_mode[GMT_X];
				GH->xy_alloc_mode[GMT_Y] = GH2->xy_alloc_mode[GMT_Y];
				gmt_copy_gridheader (API->GMT, grid->header, G_obj->header);	/* Update the header with more info, such as z_min/z_max */
				grid->data = G_obj->data;	/* Pass long the data pointer... */
				grid->x = G_obj->x;	/* ...and the x and y arrays */
				grid->y = G_obj->y;
				G_obj->x = G_obj->y = NULL;	/* Wipe them from the G_obj structure so we can free G_obj without clobbering the arrays now pointed to by grid */
				G_obj->data = NULL;
				GMT_Destroy_Data (API, &G_obj);	/* Destroy this registered object which has nothing of value anymore */
				G_obj = grid;	/* Refresh the G_obj to be the original pointer so it can be returned */
				break;	/* DOne with all operations involving reading a tiled dataset */
			}

			/* When source is an actual file we place the grid container into the S_obj->resource slot; no new object required */
			if (grid == NULL) {	/* Only allocate grid struct when not already allocated */
				if (mode & GMT_DATA_ONLY) return_null (API, GMT_NO_GRDHEADER);		/* For mode & GMT_DATA_ONLY grid must already be allocated */
				G_obj = gmt_create_grid (GMT);
				new = true;
			}
			else
				G_obj = grid;	/* We are working on a grid already allocated */
			S_obj->resource = G_obj;	/* Set resource pointer to the grid */
			done = (mode & GMT_CONTAINER_ONLY) ? false : true;	/* Not done until we read grid */
			GH = gmt_get_G_hidden (G_obj);
			if (! (mode & GMT_DATA_ONLY)) {		/* Must init header and read the header information from file */
				if (row_by_row) {	/* Special row-by-row processing mode */
					char r_mode = (mode & GMT_GRID_NO_HEADER) ? 'R' : 'r';
					/* If we get here more than once we only allocate extra once */
					if (GH->extra == NULL) GH->extra = gmt_M_memory (GMT, NULL, 1, struct GMT_GRID_ROWBYROW);
					if (GH->extra == NULL) {
						return_null (API, GMT_MEMORY_ERROR);
					}
					if (gmtapi_open_grd (GMT, S_obj->filename, G_obj, r_mode, mode)) {	/* Open the grid for incremental row reading */
						if (new) gmt_free_grid (GMT, &G_obj, false);
						return_null (API, GMT_GRID_READ_ERROR);
					}
				}
				else if (gmt_M_err_pass (GMT, gmtlib_read_grd_info (GMT, S_obj->filename, G_obj->header), S_obj->filename)) {
					if (new) gmt_free_grid (GMT, &G_obj, false);
					return_null (API, GMT_GRID_READ_ERROR);
				}
				if (mode & GMT_CONTAINER_ONLY) break;	/* Just needed the header, get out of here */
			}
			/* Here we will read the grid data themselves. */
			/* To get a subset we use wesn that is not NULL or contain 0/0/0/0.
			 * Otherwise we extract the entire file domain */
			HH = gmt_get_H_hidden (G_obj->header);
			/* Ensure a region set via DCW is properly rounded */
			if (!full_region (S_obj->wesn) && (err = gmt_M_err_fail (GMT, gmt_adjust_loose_wesn (GMT, S_obj->wesn, G_obj->header), "")))
				return_null (API, GMT_RUNTIME_ERROR);
			size = gmtapi_set_grdarray_size (GMT, G_obj->header, mode, S_obj->wesn);	/* Get array dimension only, which includes padding */
			if (!G_obj->data) {	/* Array is not allocated yet, do so now. We only expect header (and possibly w/e/s/n subset) to have been set correctly */
				G_obj->header->size = size;
				if ((G_obj->data = gmt_M_memory_aligned (GMT, NULL, G_obj->header->size, gmt_grdfloat)) == NULL) return_null (API, GMT_MEMORY_ERROR);
			}
			else {	/* Already have allocated space; check that it is enough */
				if (size > G_obj->header->size) return_null (API, GMT_GRID_READ_ERROR);
			}
			GMT_Report (API, GMT_MSG_INFORMATION, "Reading grid from file %s\n", S_obj->filename);
			if (gmt_M_err_pass (GMT, gmtlib_read_grd (GMT, S_obj->filename, G_obj->header, G_obj->data, S_obj->wesn,
							GMT->current.io.pad, mode), S_obj->filename))
				return_null (API, GMT_GRID_READ_ERROR);
			if (gmt_M_err_pass (GMT, gmt_grd_BC_set (GMT, G_obj, GMT_IN), S_obj->filename))
				return_null (API, GMT_GRID_BC_ERROR);	/* Set boundary conditions */
			GH->alloc_mode = GMT_ALLOC_INTERNALLY;
			GH->alloc_level = GMT->hidden.func_level;   /* Must be freed at this level. */
			break;

	 	case GMT_IS_DUPLICATE:	/* GMT grid and header in a GMT_GRID container object. */
			/* Must duplicate the grid container from S_obj->resource and hence a new object is required */
			if ((G_orig = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			if (grid == NULL) {	/* Only allocate when not already allocated */
				if (mode & GMT_DATA_ONLY) return_null (API, GMT_NO_GRDHEADER);		/* For mode & GMT_DATA_ONLY grid must already be allocated */
				if ((G_obj = GMT_Duplicate_Data (API, GMT_IS_GRID, GMT_DUPLICATE_NONE, G_orig)) == NULL)
					return_null (API, GMT_MEMORY_ERROR);
			}
			else
				G_obj = grid;	/* We are passing in a grid already */
			done = (mode & GMT_CONTAINER_ONLY) ? false : true;	/* Not done until we read grid */
			if (mode & GMT_CONTAINER_ONLY) break;	/* Just needed the header, get out of here */
			/* Here we will read grid data. */
			/* To get a subset we use wesn that is not NULL or contain 0/0/0/0.
			 * Otherwise we use everything passed in */
			GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating grid data from GMT_GRID memory location\n");
			if (!G_obj->data) {	/* Array is not allocated, do so now. We only expect header (and possibly subset w/e/s/n) to have been set correctly */
				G_obj->header->size = gmtapi_set_grdarray_size (GMT, G_obj->header, mode, S_obj->wesn);	/* Get array dimension only, which may include padding */
				if ((G_obj->data = gmt_M_memory_aligned (GMT, NULL, G_obj->header->size, gmt_grdfloat)) == NULL) return_null (API, GMT_MEMORY_ERROR);
			}
			GH = gmt_get_G_hidden (G_obj);
			GH->alloc_mode = GMT_ALLOC_INTERNALLY;
			GH->alloc_level = GMT->hidden.func_level;   /* Must be freed at this level. */
			HH = gmt_get_H_hidden (G_obj->header);
			if (HH->grdtype > GMT_GRID_CARTESIAN) gmt_set_geographic (GMT, GMT_IN);
			if (!S_obj->region && gmt_grd_pad_status (GMT, G_obj->header, GMT->current.io.pad)) {	/* Want an exact copy with no subset and same padding */
				gmt_M_memcpy (G_obj->data, G_orig->data, G_orig->header->size, gmt_grdfloat);
				gmt_BC_init (GMT, G_obj->header);	/* Initialize grid interpolation and boundary condition parameters */
				if (gmt_M_err_pass (GMT, gmt_grd_BC_set (GMT, G_obj, GMT_IN), "Grid memory"))
					return_null (API, GMT_GRID_BC_ERROR);	/* Set boundary conditions */
				break;		/* Done with this grid */
			}
			/* Here we need to do more work: Either extract subset or add/change padding, or both. */
			/* Get start/stop row/cols for subset (or the entire domain) */
			/* dx,dy are needed when the grid is pixel-registered as the w/e/s/n bounds are off by 0.5 {dx,dy} relative to node coordinates */
			dx = G_obj->header->inc[GMT_X] * G_obj->header->xy_off;	dy = G_obj->header->inc[GMT_Y] * G_obj->header->xy_off;
			j1 = (unsigned int)gmt_M_grd_y_to_row (GMT, S_obj->wesn[YLO]+dy, G_orig->header);
			j0 = (unsigned int)gmt_M_grd_y_to_row (GMT, S_obj->wesn[YHI]-dy, G_orig->header);
			i0 = (unsigned int)gmt_M_grd_x_to_col (GMT, S_obj->wesn[XLO]+dx, G_orig->header);
			i1 = (unsigned int)gmt_M_grd_x_to_col (GMT, S_obj->wesn[XHI]-dx, G_orig->header);
			gmt_M_memcpy (G_obj->header->pad, GMT->current.io.pad, 4, int);	/* Set desired padding */
			gmt_M_memcpy (G_obj->header->wesn, S_obj->wesn, 4U, double);	/* Update the grid header region to match subset request */
			gmt_set_grddim (GMT, G_obj->header);	/* Adjust all dimensions accordingly before accessing the grid for output */
			/* get stats */
			HH = gmt_get_H_hidden (G_obj->header);
			G_obj->header->z_min = DBL_MAX;
			G_obj->header->z_max = -DBL_MAX;
			HH->has_NaNs = GMT_GRID_NO_NANS;	/* We are about to check for NaNs and if none are found we retain 1, else 2 */
			for (row = j0, row_out = 0; row <= j1; row++, row_out++) {
				ij = gmt_M_ijp (G_obj->header, row_out, 0);	/* Position in output grid at start of current row */
				for (col = i0; col <= i1; col++, ij++) {
					kol = col % G_orig->header->n_columns;
					ij_orig = gmt_M_ijp (G_orig->header, row, kol);	/* Position of this (row,col) in original grid organization */
					G_obj->data[ij] = G_orig->data[ij_orig];
					if (gmt_M_is_fnan (G_obj->data[ij]))
						HH->has_NaNs = GMT_GRID_HAS_NANS;
					else {
						G_obj->header->z_min = MIN (G_obj->header->z_min, G_obj->data[ij]);
						G_obj->header->z_max = MAX (G_obj->header->z_max, G_obj->data[ij]);
					}
				}
			}
			gmt_BC_init (GMT, G_obj->header);	/* Initialize grid interpolation and boundary condition parameters */
			if (gmt_M_err_pass (GMT, gmt_grd_BC_set (GMT, G_obj, GMT_IN), "Grid memory"))
				return_null (API, GMT_GRID_BC_ERROR);	/* Set boundary conditions */
			break;

	 	case GMT_IS_REFERENCE:	/* GMT grid and header in a GMT_GRID container object by reference [NOT SURE ABOUT THIS] */
			if (S_obj->region) return_null (API, GMT_SUBSET_NOT_ALLOWED);
			GMT_Report (API, GMT_MSG_INFORMATION, "Referencing grid data from GMT_GRID memory location\n");
			if ((G_obj = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			done = (mode & GMT_CONTAINER_ONLY) ? false : true;	/* Not done until we read grid */
			GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_grid: Change alloc mode\n");
			GH = gmt_get_G_hidden (G_obj);
			S_obj->alloc_mode = GH->alloc_mode;
			HH = gmt_get_H_hidden (G_obj->header);
			if (HH->grdtype > GMT_GRID_CARTESIAN) gmt_set_geographic (GMT, GMT_IN);
			GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_grid: Check pad\n");
			gmt_BC_init (GMT, G_obj->header);	/* Initialize grid interpolation and boundary condition parameters */
			if (gmt_M_err_pass (GMT, gmt_grd_BC_set (GMT, G_obj, GMT_IN), "Grid memory"))
				return_null (API, GMT_GRID_BC_ERROR);	/* Set boundary conditions */
			if (!gmtapi_adjust_grdpadding (G_obj->header, GMT->current.io.pad)) break;	/* Pad is correct so we are done */
			/* Here we extend G_obj->data to allow for padding, then rearrange rows */
			if (GH->alloc_mode == GMT_ALLOC_EXTERNALLY) return_null (API, GMT_PADDING_NOT_ALLOWED);
			GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_grid: Add pad\n");
			gmt_grd_pad_on (GMT, G_obj, GMT->current.io.pad);
			if (done && S_obj->region) {	/* Possibly adjust the pad so inner region matches wesn */
				HH = gmt_get_H_hidden (G_obj->header);
				if (S_obj->reset_pad) {	/* First undo a prior sub-region used with this memory grid */
					gmtapi_contract_headerpad (GMT, G_obj->header, S_obj->orig_pad, S_obj->orig_wesn);
					S_obj->reset_pad = HH->reset_pad = 0;
				}
				if (gmtapi_expand_headerpad (GMT, G_obj->header, S_obj->wesn, S_obj->orig_pad, S_obj->orig_wesn)) {
					S_obj->reset_pad = HH->reset_pad = 1;
					gmtapi_update_grid_minmax (API->GMT, G_obj);	/* Update z-range */
				}
			}
			GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_grid: Return from GMT_IS_REFERENCE\n");
			break;

		/* Status: This case is fully tested and operational */
	 	case GMT_IS_DUPLICATE|GMT_VIA_MATRIX:	/* The user's 2-D grid array of some sort, + info in the matrix header */
			/* Must create a grid container from matrix info S_obj->resource and hence a new object is required */
			if ((M_obj = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			if (grid == NULL) {	/* Only allocate when not already allocated, and only allocate container */
				uint64_t dim[3] = {M_obj->n_columns, M_obj->n_rows, 1};
				if ((G_obj = GMT_Create_Data (API, GMT_IS_GRID, GMT_IS_SURFACE, GMT_CONTAINER_ONLY, dim, M_obj->range, M_obj->inc, M_obj->registration, GMT_NOTSET, NULL)) == NULL)
					return_null (API, GMT_MEMORY_ERROR);
			}
			else
				G_obj = grid;
			if ((new_ID = gmtapi_get_object (API, GMT_IS_GRID, G_obj)) == GMT_NOTSET)
				return_null (API, GMT_OBJECT_NOT_FOUND);
			if ((new_item = gmtlib_validate_id (API, GMT_IS_GRID, new_ID, GMT_IN, GMT_NOTSET)) == GMT_NOTSET)
				return_null (API, GMT_OBJECT_NOT_FOUND);
			API->object[new_item]->method = S_obj->method;
			GH = gmt_get_G_hidden (G_obj);
			HH = gmt_get_H_hidden (G_obj->header);
			G_obj->header->complex_mode = (mode & GMT_GRID_IS_COMPLEX_MASK);	/* Set the complex mode */
			GH->alloc_mode = GMT_ALLOC_INTERNALLY;
			done = (mode & GMT_CONTAINER_ONLY) ? false : true;	/* Not done until we read grid */
			if ((GMT_2D_to_index = gmtapi_get_2d_to_index (API, M_obj->shape, GMT_GRID_IS_REAL)) == NULL)
				return_null (API, GMT_WRONG_MATRIX_SHAPE);
			if ((api_get_val = gmtapi_select_get_function (API, M_obj->type)) == NULL)
				return_null (API, GMT_NOT_A_VALID_TYPE);
			HH = gmt_get_H_hidden (G_obj->header);

			if (! (mode & GMT_DATA_ONLY)) {	/* Must first init header and copy the header information from the matrix header */
				gmtapi_matrixinfo_to_grdheader (GMT, G_obj->header, M_obj);	/* Populate a GRD header structure */
				if (HH->grdtype > GMT_GRID_CARTESIAN) gmt_set_geographic (GMT, GMT_IN);
				/* Must get the full zmin/max range since not provided by the matrix header */
					G_obj->header->z_min = +DBL_MAX;
					G_obj->header->z_max = -DBL_MAX;
					HH->has_NaNs = GMT_GRID_NO_NANS;	/* We are about to check for NaNs and if none are found we retain 1, else 2 */
					gmt_M_grd_loop (GMT, G_obj, row, col, ij) {
					ij_orig = GMT_2D_to_index (row, col, M_obj->dim);
					api_get_val (&(M_obj->data), ij_orig, &d);
					if (gmt_M_is_dnan (d))
						HH->has_NaNs = GMT_GRID_HAS_NANS;
					else {
						G_obj->header->z_min = MIN (G_obj->header->z_min, (gmt_grdfloat)d);
						G_obj->header->z_max = MAX (G_obj->header->z_max, (gmt_grdfloat)d);
					}
				}
				if (mode & GMT_CONTAINER_ONLY)	/* Just needed the header */
					break;	/* Done for now */
			}

			GMT_Report (API, GMT_MSG_INFORMATION, "Importing grid data from user matrix memory location\n");

			/* Get start/stop row/cols for subset (or the entire domain) */
			/* dx,dy are needed when the grid is pixel-registered as the w/e/s/n bounds are off by 0.5 {dx,dy} relative to node coordinates */
			if (!S_obj->region || gmt_whole_earth (GMT, M_obj->range, S_obj->wesn)) {	/* Easy, get the whole enchilada */
				j0 = i0 = 0;
				j1 = G_obj->header->n_rows    - 1;	/* Minus 1 since we loop up to and including below */
				i1 = G_obj->header->n_columns - 1;
			}
			else {	/* Want a subset */
				/* Use dx/dy which will be nonzero for pixel grids */
				dx = G_obj->header->inc[GMT_X] * G_obj->header->xy_off;	dy = G_obj->header->inc[GMT_Y] * G_obj->header->xy_off;
				if (gmt_M_is_geographic (GMT, GMT_IN)) { /* Must first wrap S_obj->wesn to fit the data if necessary */
					if (S_obj->wesn[XLO] > G_obj->header->wesn[XHI]) { /* Must first wrap G_obj->header->wesn west to fit the data */
						G_obj->header->wesn[XLO] += 360.0;	G_obj->header->wesn[XHI] += 360.0;
					}
					else if (S_obj->wesn[XHI] < G_obj->header->wesn[XLO]) { /* Must first wrap G_obj->header->wesn east to fit the data */
						G_obj->header->wesn[XLO] -= 360.0;	G_obj->header->wesn[XHI] -= 360.0;
					}
					if (S_obj->wesn[XLO] < G_obj->header->wesn[XLO]) {
						/* Must wrap G_obj->header.wesn so the left bound in S_obj is larger than that in G_obj, otherwise i0 is negative (but it's defined as unsigned int */
						G_obj->header->wesn[XLO] -= 360.0;	G_obj->header->wesn[XHI] -= 360.0;
					}
				}
				j1 = (unsigned int)gmt_M_grd_y_to_row (GMT, S_obj->wesn[YLO]+dy, G_obj->header);
				j0 = (unsigned int)gmt_M_grd_y_to_row (GMT, S_obj->wesn[YHI]-dy, G_obj->header);
				i0 = (unsigned int)gmt_M_grd_x_to_col (GMT, S_obj->wesn[XLO]+dx, G_obj->header);
				i1 = (unsigned int)gmt_M_grd_x_to_col (GMT, S_obj->wesn[XHI]-dx, G_obj->header);
				gmt_M_memcpy (G_obj->header->wesn, S_obj->wesn, 4U, double);	/* Update the grid header region to match subset request */
				gmt_set_grddim (GMT, G_obj->header);	/* Adjust all dimensions accordingly before allocating space */
			}
			if (G_obj->data) {	/* This is an error - there cannot be a data pointer yet */
				GMT_Report (API, GMT_MSG_ERROR, "G->data is not NULL when memory allocation is about to happen\n");
				return_null (API, GMT_PTR_IS_NULL);
			}
			else if ((G_obj->data = gmt_M_memory_aligned (GMT, NULL, G_obj->header->size, gmt_grdfloat)) == NULL)
				return_null (API, GMT_MEMORY_ERROR);
			G_obj->header->z_min = DBL_MAX;
			G_obj->header->z_max = -DBL_MAX;
			for (row = j0, row_out = 0; row <= j1; row++, row_out++) {
				ij = gmt_M_ijp (G_obj->header, row_out, 0);	/* Position in output grid at start of current row */
				for (col = i0; col <= i1; col++, ij++) {
					kol = col % M_obj->n_columns;
					ij_orig = GMT_2D_to_index (row, kol, M_obj->dim);	/* Position of this (row,col) in input matrix organization */
					api_get_val (&(M_obj->data), ij_orig, &d);	/* Get the next item from the matrix */
					G_obj->data[ij] = (gmt_grdfloat)d;
					if (gmt_M_is_dnan (d))
						HH->has_NaNs = GMT_GRID_HAS_NANS;
					else {
						G_obj->header->z_min = MIN (G_obj->header->z_min, (gmt_grdfloat)d);
						G_obj->header->z_max = MAX (G_obj->header->z_max, (gmt_grdfloat)d);
					}
				}
			}
			if (gmt_whole_earth (GMT, M_obj->range, S_obj->wesn)) {
				/* Global grids passed via matrix are not rotated to fit the desired global region, so we need to correct the wesn for this grid to match the matrix */
				gmt_M_memcpy (G_obj->header->wesn, M_obj->range, 4U, double);
			}
			gmt_BC_init (GMT, G_obj->header);	/* Initialize grid interpolation and boundary condition parameters */
			if (gmt_M_err_pass (GMT, gmt_grd_BC_set (GMT, G_obj, GMT_IN), "Grid memory"))
				return_null (API, GMT_GRID_BC_ERROR);	/* Set boundary conditions */
			API->object[new_item]->status = GMT_IS_USED;	/* Mark as read */
			API->object[new_item]->actual_family = GMT_IS_GRID;	/* Done reading from matrix */
			if (start_over_method) API->object[new_item]->method = start_over_method;	/* We changed our mind from reference to duplicate due to region */
			GH->alloc_level = API->object[new_item]->alloc_level;	/* Since allocated here */
			break;

	 	case GMT_IS_REFERENCE|GMT_VIA_MATRIX:	/* The user's 2-D grid array of some sort, + info in the args [NOT YET FULLY TESTED] */
			/* Getting a matrix info S_obj->resource. Create grid header and then pass the grid pointer via the matrix pointer */
			if ((M_obj = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			/* If passed by reference and the module requires a pad then we must switch to duplication */
			if (mode & GMT_GRID_NEEDS_PAD1 || mode & GMT_GRID_NEEDS_PAD2) {	/* Cannot do this by reference, switch to duplication */
				start_over_method = gmtapi_switch_method (API, S_obj, &method, "Grid via a user matrix requires method GMT_IS_DUPLICATE instead of GMT_IS_REFERENCE due to a padding requirement - method has been switched\n");
				goto start_over_import_grid;
			}
			/* This method requires the input data to be a GMT_GRD_FORMAT matrix - otherwise we should be DUPLICATING */
			if (!(M_obj->shape == GMT_IS_ROW_FORMAT && M_obj->type == GMT_GRDFLOAT && (mode & GMT_GRID_IS_COMPLEX_MASK) == 0)) {
				start_over_method = gmtapi_switch_method (API, S_obj, &method, "Grid via a user matrix requires method GMT_IS_DUPLICATE instead of GMT_IS_REFERENCE due to incompatible data type for a grid - method has been switched\n");
				goto start_over_import_grid;
			}
			/* Determine if it is possible to use the matrix given the region selected and the fact we chose GMT_IS_REFERENCE. This test will
			 * only kick in after we allocate the G_obj and come back the second time (after getting header) since otherwise S_obj->wesn is not set yet */
			if (!(!S_obj->region ||
				(S_obj->wesn[XLO] >= M_obj->range[XLO] && S_obj->wesn[XHI] <= M_obj->range[XHI] && S_obj->wesn[YLO] >= M_obj->range[YLO] && S_obj->wesn[YHI] <= M_obj->range[YHI]) ||
				gmt_whole_earth (GMT, M_obj->range, S_obj->wesn))) {	/* Cannot do this by reference, switch to duplication */
				start_over_method = gmtapi_switch_method (API, S_obj, &method, "Subset selection for grid via a user matrix requires method GMT_IS_DUPLICATE instead of GMT_IS_REFERENCE - method has been switched\n");
				goto start_over_import_grid;
			}

			if (grid == NULL) {	/* Only allocate when not already allocated, and only get container.  Note: Cannot have pad since input matrix won't have one */
				uint64_t dim[3] = {M_obj->n_rows, M_obj->n_columns, 1};
				if ((G_obj = GMT_Create_Data (API, GMT_IS_GRID, GMT_IS_SURFACE, GMT_CONTAINER_ONLY, dim, M_obj->range, M_obj->inc, M_obj->registration, 0, NULL)) == NULL)
					return_null (API, GMT_MEMORY_ERROR);
			}
			else
				G_obj = grid;
			HH = gmt_get_H_hidden (G_obj->header);
			G_obj->header->complex_mode = (mode & GMT_GRID_IS_COMPLEX_MASK);	/* Set the complex mode */
			done = (mode & GMT_CONTAINER_ONLY) ? false : true;	/* Not done until we read grid */
			if (! (mode & GMT_DATA_ONLY)) {
				gmtapi_matrixinfo_to_grdheader (GMT, G_obj->header, M_obj);	/* Populate a GRD header structure */
				if (HH->grdtype > GMT_GRID_CARTESIAN) gmt_set_geographic (GMT, GMT_IN);
				/* Temporarily set data pointer for convenience; removed later */
#ifdef DOUBLE_PRECISION_GRID
				G_obj->data = M_obj->data.f8;
#else
				G_obj->data = M_obj->data.f4;
#endif
				G_obj->header->z_min = +DBL_MAX;
				G_obj->header->z_max = -DBL_MAX;
				HH->has_NaNs = GMT_GRID_NO_NANS;	/* We are about to check for NaNs and if none are found we retain 1, else 2 */
				gmt_M_grd_loop (GMT, G_obj, row, col, ij) {
					if (gmt_M_is_fnan (G_obj->data[ij]))
						HH->has_NaNs = GMT_GRID_HAS_NANS;
					else {
						G_obj->header->z_min = MIN (G_obj->header->z_min, G_obj->data[ij]);
						G_obj->header->z_max = MAX (G_obj->header->z_max, G_obj->data[ij]);
					}
				}
				G_obj->data = NULL;	/* Since data are not requested yet */
				if (mode & GMT_CONTAINER_ONLY)	/* Just needed the header but had to set zmin/zmax first */
					break;
			}
			if ((new_ID = gmtapi_get_object (API, GMT_IS_GRID, G_obj)) == GMT_NOTSET)
				return_null (API, GMT_OBJECT_NOT_FOUND);
			if ((new_item = gmtlib_validate_id (API, GMT_IS_GRID, new_ID, GMT_IN, GMT_NOTSET)) == GMT_NOTSET)
				return_null (API, GMT_OBJECT_NOT_FOUND);
			GMT_Report (API, GMT_MSG_INFORMATION, "Referencing grid data from user memory location\n");
#ifdef DOUBLE_PRECISION_GRID
			if ((mode & GMT_DATA_ONLY) && G_obj->data && G_obj->data != M_obj->data.f8)
				gmt_M_memcpy (G_obj->data, M_obj->data.f8, G_obj->header->size, gmt_grdfloat);
			else
				G_obj->data = M_obj->data.f8;
#else
			if ((mode & GMT_DATA_ONLY) && G_obj->data && G_obj->data != M_obj->data.f4) {
				struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (G_obj->header);
				size_t stride = HH->stride ? HH->stride : G_obj->header->n_columns;
				float *dest = G_obj->data + HH->data_offset;
				for (size_t row = 0; row < M_obj->n_rows; row++)
					gmt_M_memcpy (dest + row * stride, M_obj->data.f4 + row * M_obj->n_columns, M_obj->n_columns, gmt_grdfloat);
			} else
				G_obj->data = M_obj->data.f4;
#endif
			GH = gmt_get_G_hidden (G_obj);
			MH = gmt_get_M_hidden (M_obj);
			S_obj->alloc_mode = MH->alloc_mode;	/* Pass on alloc_mode of matrix */
			GH->alloc_mode = GMT_ALLOC_EXTERNALLY;	/* Since we cannot have both M and G try to free */
			API->object[new_item]->resource = G_obj;
			API->object[new_item]->status = GMT_IS_USED;	/* Mark as read */
			GH->alloc_level = API->object[new_item]->alloc_level;	/* Since allocated here */
			if (gmt_whole_earth (GMT, M_obj->range, S_obj->wesn)) {
				/* Global grids passed via matrix are not rotated to fit the desired global region, so we need to correct the wesn for this grid to match the matrix */
				gmt_M_memcpy (G_obj->header->wesn, M_obj->range, 4U, double);
			}
			else if (S_obj->region) {	/* Possibly adjust the pad so inner region matches wesn */
				if (S_obj->reset_pad) {	/* First undo a prior sub-region used with this memory grid */
					gmtapi_contract_headerpad (GMT, G_obj->header, S_obj->orig_pad, S_obj->orig_wesn);
					S_obj->reset_pad = 0;
				}
				if (gmtapi_expand_headerpad (GMT, G_obj->header, S_obj->wesn, S_obj->orig_pad, S_obj->orig_wesn)) {
					S_obj->reset_pad = 1;
					gmtapi_update_grid_minmax (GMT, G_obj);	/* Update z-range */
				}
			}
			break;

		default:
			GMT_Report (API, GMT_MSG_ERROR, "Wrong method used to import grid\n");
			return_null (API, GMT_NOT_A_VALID_METHOD);
			break;
	}

	if ((mode & GMT_CONTAINER_ONLY) == 0) {	/* Also allocate and initialize the x and y vectors unless already present  */
		if (G_obj->x == NULL) {
			GH->xy_alloc_mode[GMT_X] = GMT_ALLOC_INTERNALLY;
			if (GMT->current.io.nc_xarray)	/* Got variable x-array and asked to used this instead */
				G_obj->x = GMT->current.io.nc_xarray, GMT->current.io.nc_xarray = NULL;
			else
				G_obj->x = gmtapi_grid_coord (API, GMT_X, G_obj);	/* Get array of x coordinates */
		}
		if (G_obj->y == NULL) {
			GH->xy_alloc_mode[GMT_Y] = GMT_ALLOC_INTERNALLY;
			if (GMT->current.io.nc_yarray)	/* Got variable y-array and asked to used this instead */
				G_obj->y = GMT->current.io.nc_yarray, GMT->current.io.nc_yarray = NULL;
			else
				G_obj->y = gmtapi_grid_coord (API, GMT_Y, G_obj);	/* Get array of y coordinates */
		}
	}

	if (done) S_obj->status = GMT_IS_USED;	/* Mark as read (unless we just got the header) */

	/* Some grids are geographic and others are Cartesian, but the user may wish to flip this fact
	 * via -f. Here we catch such deviousness and set the desired input geo/cart mode imposed by -f. */
	if (GMT->common.f.is_geo[GMT_IN])
		gmt_set_geographic (GMT, GMT_IN);
	else if (GMT->common.f.is_cart[GMT_IN])
		gmt_set_cartesian (GMT, GMT_IN);

	return (G_obj);	/* Pass back out what we have so far */
}

GMT_LOCAL void gmtapi_cube_set_units (struct GMT_CTRL *GMT, struct GMT_CUBE *U) {
	/* Set unit strings for cube coordinates x, y and z based on
	   output data types for columns 0, 1, and 2.
	*/
	unsigned int i;
	char *string[4] = {NULL, NULL, NULL, NULL}, unit[GMT_GRID_UNIT_LEN80] = {""};
	char date[GMT_LEN16] = {""}, clock[GMT_LEN16] = {""};
	struct GMT_GRID_HEADER *header = U->header;
	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (header);

	/* Copy pointers to unit strings */
	string[0] = header->x_units;
	string[1] = header->y_units;
	string[2] = U->units;
	string[3] = header->z_units;

	/* Use input data type as backup for output data type */
	for (i = 0; i < 4; i++)
		if (gmt_M_type (GMT, GMT_OUT, i) == GMT_IS_UNKNOWN) GMT->current.io.col_type[GMT_OUT][i] = GMT->current.io.col_type[GMT_IN][i];

	/* Catch some anomalies */
	if (gmt_M_type (GMT, GMT_OUT, GMT_X) == GMT_IS_LAT && gmt_M_type (GMT, GMT_OUT, GMT_Y) == GMT_IS_LAT) {
		GMT_Report (GMT->parent, GMT_MSG_WARNING, "Output type for X-coordinate of grid %s is LAT. Replaced by LON.\n", HH->name);
		gmt_set_column_type (GMT, GMT_OUT, GMT_X, GMT_IS_LON);

	}
	if (gmt_M_type (GMT, GMT_OUT, GMT_Y) == GMT_IS_LON && gmt_M_type (GMT, GMT_OUT, GMT_X) == GMT_IS_LON) {
		GMT_Report (GMT->parent, GMT_MSG_WARNING, "Output type for Y-coordinate of grid %s is LON. Replaced by LAT.\n", HH->name);
		gmt_set_column_type (GMT, GMT_OUT, GMT_Y, GMT_IS_LAT);
	}

	/* Set unit strings one by one based on output type - or fall through if Cartesian floats */
	for (i = 0; i < 4; i++) {
		switch (gmt_M_type (GMT, GMT_OUT, i)) {
			case GMT_IS_LON:
				strcpy (string[i], "longitude [degrees_east]"); break;
			case GMT_IS_LAT:
				strcpy (string[i], "latitude [degrees_north]"); break;
			case GMT_IS_ABSTIME:
			case GMT_IS_RELTIME:
			case GMT_IS_RATIME:
				/* Determine time unit */
				switch (GMT->current.setting.time_system.unit) {
					case 'y':
						strcpy (unit, "years"); break;
					case 'o':
						strcpy (unit, "months"); break;
					case 'd':
						strcpy (unit, "days"); break;
					case 'h':
						strcpy (unit, "hours"); break;
					case 'm':
						strcpy (unit, "minutes"); break;
					default:
						strcpy (unit, "seconds"); break;
				}
				gmt_format_calendar (GMT, date, clock, &GMT->current.io.date_output, &GMT->current.io.clock_output, false, 1, 0.0);
				snprintf (string[i], GMT_GRID_UNIT_LEN80, "time [%s since %s %s]", unit, date, clock);
				/* Warning for non-double cubes */
				if (i == 3 && GMT->session.grdformat[header->type][1] != 'd')
					GMT_Report (GMT->parent, GMT_MSG_WARNING, "Use double precision output cube to avoid loss of significance of time coordinate.\n");
				break;
		}
	}
}

/*! Writes out a single grid to destination */
GMT_LOCAL int gmtapi_export_grid (struct GMTAPI_CTRL *API, int object_ID, unsigned int mode, struct GMT_GRID *G_obj) {
	int item, error;
	bool done = true, row_by_row;
	unsigned int method;
	openmp_int row, col, i0, i1, j0, j1;
	uint64_t ij, ijp, ij_orig;
	size_t size;
	double dx, dy;
	p_func_uint64_t GMT_2D_to_index = NULL;
	GMT_putfunction api_put_val = NULL;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMT_GRID *G_copy = NULL;
	struct GMT_MATRIX *M_obj = NULL;
	struct GMT_MATRIX_HIDDEN *MH = NULL;
	struct GMT_GRID_HIDDEN *GH = gmt_get_G_hidden (G_obj), *GH2 = NULL;
	struct GMT_CTRL *GMT = API->GMT;

	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_export_grid: Passed ID = %d and mode = %d\n", object_ID, mode);

	if (object_ID == GMT_NOTSET) return (gmtlib_report_error (API, GMT_OUTPUT_NOT_SET));
	if (G_obj->data == NULL && !(mode & GMT_CONTAINER_ONLY)) return (gmtlib_report_error (API, GMT_PTR_IS_NULL));
	if ((item = gmtlib_validate_id (API, GMT_IS_GRID, object_ID, GMT_OUT, GMT_NOTSET)) == GMT_NOTSET) return (gmtlib_report_error (API, API->error));

	S_obj = API->object[item];	/* The current object whose data we will export */
	if (S_obj->status != GMT_IS_UNUSED && !(mode & GMT_IO_RESET))
		return (gmtlib_report_error (API, GMT_WRITTEN_ONCE));	/* Only allow writing of a data set once, unless overridden by mode */
	if (mode & GMT_IO_RESET) mode -= GMT_IO_RESET;
	row_by_row = ((mode & GMT_GRID_ROW_BY_ROW) || (mode & GMT_GRID_ROW_BY_ROW_MANUAL));
	if (row_by_row && S_obj->method != GMT_IS_FILE) {
		GMT_Report (API, GMT_MSG_ERROR, "Can only use method GMT_IS_FILE when row-by-row writing of grid is selected\n");
		return (gmtlib_report_error (API, GMT_NOT_A_VALID_METHOD));
	}
	if (S_obj->region) {	/* See if this is really a subset or just the same region as the grid */
		if (G_obj->header->wesn[XLO] == S_obj->wesn[XLO] && G_obj->header->wesn[XHI] == S_obj->wesn[XHI] && G_obj->header->wesn[YLO] == S_obj->wesn[YLO] && G_obj->header->wesn[YHI] == S_obj->wesn[YHI]) S_obj->region = false;
	}
	if (mode & GMT_DATA_IS_GEO)	/* From API to tell grid is geographic */
		gmt_set_geographic (GMT, GMT_OUT);
	else {
		/* Some grids are geographic and others are Cartesian, but the user may wish to flip this fact
		 * via -f. Here we catch such deviousness and set the desired output geo/cart mode imposed by -f. */
		if (GMT->common.f.is_geo[GMT_OUT])
			gmt_set_geographic (GMT, GMT_OUT);
		else if (GMT->common.f.is_cart[GMT_OUT])
			gmt_set_cartesian (GMT, GMT_OUT);
	}

	gmtlib_grd_set_units (GMT, G_obj->header);	/* Ensure unit strings are set, regardless of destination */
	method = gmtapi_set_method (S_obj);	/* Get the actual method to use since may be MATRIX or VECTOR masquerading as GRID */
	switch (method) {
		case GMT_IS_FILE:	/* Name of a grid file on disk */
			if (mode & GMT_CONTAINER_ONLY) {	/* Update header structure only */
				GMT_Report (API, GMT_MSG_INFORMATION, "Updating grid header for file %s\n", S_obj->filename);
				if (row_by_row) {	/* Special row-by-row processing mode */
					char w_mode = (mode & GMT_GRID_NO_HEADER) ? 'W' : 'w';
					/* Since we may get here twice (initial write; later update) we only allocate extra if NULL */
					if (GH->extra == NULL) GH->extra = gmt_M_memory (GMT, NULL, 1, struct GMT_GRID_ROWBYROW);
					if (GH->extra == NULL) {
						return (gmtlib_report_error (API, GMT_MEMORY_ERROR));
					}
					if (gmtapi_open_grd (GMT, S_obj->filename, G_obj, w_mode, mode))	/* Open the grid for incremental row writing */
						return (gmtlib_report_error (API, GMT_GRID_WRITE_ERROR));
				}
				else if (gmt_update_grd_info (GMT, NULL, G_obj->header))
					return (gmtlib_report_error (API, GMT_GRID_WRITE_ERROR));
				done = false;	/* Since we are not done with writing */
			}
			else {
				GMT_Report (API, GMT_MSG_INFORMATION, "Writing grid to file %s\n", S_obj->filename);
				if (gmt_M_err_pass (GMT, gmtlib_write_grd (GMT, S_obj->filename, G_obj->header, G_obj->data, S_obj->wesn, G_obj->header->pad, mode), S_obj->filename)) return (gmtlib_report_error (API, GMT_GRID_WRITE_ERROR));
			}
			break;

	 	case GMT_IS_DUPLICATE:	/* Duplicate GMT grid and header to a GMT_GRID container object. Subset allowed */
			if (S_obj->resource) return (gmtlib_report_error (API, GMT_PTR_NOT_NULL));	/* The output resource pointer must be NULL */
			if (mode & GMT_CONTAINER_ONLY) return (gmtlib_report_error (API, GMT_NOT_A_VALID_MODE));
			GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating grid data to GMT_GRID memory location\n");
			if (!S_obj->region) {	/* No subset, possibly same padding */
				G_copy = gmt_duplicate_grid (API->GMT, G_obj, GMT_DUPLICATE_DATA);
				GH2 = gmt_get_G_hidden (G_copy);
				GH2->alloc_level = S_obj->alloc_level;	/* Since we are passing it up to the caller */
				if (gmtapi_adjust_grdpadding (G_copy->header, GMT->current.io.pad))
					gmt_grd_pad_on (GMT, G_copy, GMT->current.io.pad);
				gmt_BC_init (GMT, G_copy->header);	/* Initialize grid interpolation and boundary condition parameters */
				if (gmt_M_err_pass (GMT, gmt_grd_BC_set (GMT, G_copy, GMT_OUT), "Grid memory")) return (gmtlib_report_error (API, GMT_GRID_BC_ERROR));	/* Set boundary conditions */
				S_obj->resource = G_copy;	/* Set resource pointer to the grid */
				break;		/* Done with this grid */
			}
			/* Here we need to extract subset, and possibly change padding. */
			/* Get start/stop row/cols for subset (or the entire domain) */
			G_copy = gmt_create_grid (GMT);
			GH2 = gmt_get_G_hidden (G_copy);
			GH2->alloc_level = S_obj->alloc_level;	/* Since we are passing it up to the caller */
			gmt_copy_gridheader (GMT, G_copy->header, G_obj->header);
			gmt_M_memcpy (G_copy->header->wesn, S_obj->wesn, 4, double);
			/* dx,dy are needed when the grid is pixel-registered as the w/e/s/n bounds are off by 0.5 {dx,dy} relative to node coordinates */
			dx = G_obj->header->inc[GMT_X] * G_obj->header->xy_off;	dy = G_obj->header->inc[GMT_Y] * G_obj->header->xy_off;
			j1 = (unsigned int) gmt_M_grd_y_to_row (GMT, G_obj->header->wesn[YLO]+dy, G_obj->header);
			j0 = (unsigned int) gmt_M_grd_y_to_row (GMT, G_obj->header->wesn[YHI]-dy, G_obj->header);
			i0 = (unsigned int) gmt_M_grd_x_to_col (GMT, G_obj->header->wesn[XLO]+dx, G_obj->header);
			i1 = (unsigned int) gmt_M_grd_x_to_col (GMT, G_obj->header->wesn[XHI]-dx, G_obj->header);
			gmt_M_memcpy (G_obj->header->pad, GMT->current.io.pad, 4, int);		/* Set desired padding */
			G_copy->header->size = gmtapi_set_grdarray_size (GMT, G_obj->header, mode, S_obj->wesn);	/* Get array dimension only, which may include padding */
			if ((G_copy->data = gmt_M_memory_aligned (GMT, NULL, G_copy->header->size, gmt_grdfloat)) == NULL) return (gmtlib_report_error (API, GMT_MEMORY_ERROR));
			G_copy->header->z_min = DBL_MAX;	G_copy->header->z_max = -DBL_MAX;	/* Must set zmin/zmax since we are not writing to file */
			for (row = j0; row <= j1; row++) {
				for (col = i0; col <= i1; col++, ij++) {
					ij_orig = gmt_M_ijp (G_obj->header, row, col);	/* Position of this (row,col) in original grid organization */
					ij = gmt_M_ijp (G_copy->header, row, col);	/* Position of this (row,col) in output grid organization */
					G_copy->data[ij] = G_obj->data[ij_orig];
					if (gmt_M_is_fnan (G_copy->data[ij])) continue;
					/* Update z_min, z_max */
					G_copy->header->z_min = MIN (G_copy->header->z_min, (double)G_copy->data[ij]);
					G_copy->header->z_max = MAX (G_copy->header->z_max, (double)G_copy->data[ij]);
				}
			}
			S_obj->resource = G_copy;	/* Set resource pointer to the grid */
			break;

	 	case GMT_IS_REFERENCE:	/* GMT grid and header in a GMT_GRID container object - just pass the reference */
			if (S_obj->region) return (gmtlib_report_error (API, GMT_SUBSET_NOT_ALLOWED));
			if (mode & GMT_CONTAINER_ONLY) return (gmtlib_report_error (API, GMT_NOT_A_VALID_MODE));
			GMT_Report(API, GMT_MSG_DEBUG, "Referencing grid data to GMT_GRID memory location\n");
			gmt_grd_zminmax (GMT, G_obj->header, G_obj->data);	/* Must set zmin/zmax since we are not writing to file */
			gmt_BC_init (GMT, G_obj->header);	/* Initialize grid interpolation and boundary condition parameters */
			if (gmt_M_err_pass (GMT, gmt_grd_BC_set (GMT, G_obj, GMT_OUT), "Grid memory")) return (gmtlib_report_error (API, GMT_GRID_BC_ERROR));	/* Set boundary conditions */
			S_obj->resource = G_obj;	/* Set resource pointer to the grid */
			GH->alloc_level = S_obj->alloc_level;	/* Since we are passing it up to the caller */
			break;

	 	case GMT_IS_DUPLICATE|GMT_VIA_MATRIX:	/* The user's 2-D grid array of some sort, + info in the args [NOT FULLY TESTED] */
			if (mode & GMT_CONTAINER_ONLY) return (gmtlib_report_error (API, GMT_NOT_A_VALID_MODE));
			if (S_obj->resource) {	/* The output resource pointer already exist for matrix */
				M_obj = gmtapi_get_matrix_data (S_obj->resource);
				if (M_obj->n_rows < G_obj->header->n_rows || M_obj->n_columns < G_obj->header->n_columns)
					return (gmtlib_report_error (API, GMT_DIM_TOO_SMALL));
			}
			else {	/* Must allocate stuff */
		 		M_obj = gmtlib_create_matrix (API->GMT, 1, 0);
				M_obj->type = S_obj->type;
			}
			MH = gmt_get_M_hidden (M_obj);
			gmtapi_grdheader_to_matrixinfo (GMT, G_obj->header, M_obj);	/* Populate an array with GRD header information */
			M_obj->dim = (M_obj->shape == GMT_IS_ROW_FORMAT) ? M_obj->n_columns : M_obj->n_rows;	/* Matrix layout order */
			GMT_Report (API, GMT_MSG_INFORMATION, "Exporting grid data to user memory location\n");
			if (S_obj->resource == NULL) {	/* Must allocate output */
				size = gmt_M_get_nm (GMT, G_obj->header->n_columns, G_obj->header->n_rows);
				if ((error = gmtlib_alloc_univector (GMT, &(M_obj->data), M_obj->type, size)) != GMT_NOERROR) return (error);
				MH->alloc_mode = GMT_ALLOC_INTERNALLY;
			}
			if ((GMT_2D_to_index = gmtapi_get_2d_to_index (API, M_obj->shape, GMT_GRID_IS_REAL)) == NULL)
				return (gmtlib_report_error (API, GMT_WRONG_MATRIX_SHAPE));
			if ((api_put_val = gmtapi_select_put_function (API, M_obj->type)) == NULL)
				return (gmtlib_report_error (API, GMT_NOT_A_VALID_TYPE));
			gmt_M_grd_loop (GMT, G_obj, row, col, ijp) {
				ij = GMT_2D_to_index (row, col, M_obj->dim);
				api_put_val (&(M_obj->data), ij, (double)G_obj->data[ijp]);
			}
			MH->alloc_level = S_obj->alloc_level;	/* Since we are passing it up to the caller */
			S_obj->resource = M_obj;	/* Set resource pointer to the matrix */
			break;

	 	case GMT_IS_REFERENCE|GMT_VIA_MATRIX:	/* Write to a user matrix of type gmt_grdfloat */
			if (mode & GMT_CONTAINER_ONLY) return (gmtlib_report_error (API, GMT_NOT_A_VALID_MODE));
			if (mode & GMT_GRID_IS_COMPLEX_MASK)	/* Cannot do a complex grid this way */
				return (gmtlib_report_error (API, GMT_NOT_A_VALID_IO_ACCESS));
			if (S_obj->resource) {	/* The output resource pointer already exist for matrix */
				M_obj = gmtapi_get_matrix_data (S_obj->resource);
				if (M_obj->n_rows < G_obj->header->n_rows || M_obj->n_columns < G_obj->header->n_columns)
					return (gmtlib_report_error (API, GMT_DIM_TOO_SMALL));
				assert (M_obj->type == GMT_GRDFLOAT);	/* That is the whole point of getting here, no? */
			}
			else {	/* Must allocate stuff */
		 		M_obj = gmtlib_create_matrix (API->GMT, 1, 1);
				M_obj->type = GMT_GRDFLOAT;	/* A grid is always gmt_grdfloat */
			}
			MH = gmt_get_M_hidden (M_obj);
			if (gmtapi_adjust_grdpadding (G_obj->header, GMT_no_pad))
				gmt_grd_pad_on (GMT, G_obj, GMT_no_pad);	/* Adjust pad */
			/* This method requires the output data to be a gmt_grdfloat matrix - otherwise we should be DUPLICATING.
			   This distinction is set in GMT_Open_VirtualFile */
			gmtapi_grdheader_to_matrixinfo (GMT, G_obj->header, M_obj);	/* Populate an array with GRD header information */
			M_obj->shape = GMT_IS_ROW_FORMAT;	/* Because it is a direct GMT gmt_grdfloat grid */
			if (S_obj->resource) {
				GMT_Report (API, GMT_MSG_INFORMATION, "Memcpy grid data to user memory location\n");
#ifdef DOUBLE_PRECISION_GRID
				gmt_M_memcpy (M_obj->data.f8, G_obj->data, G_obj->header->nm, double);
#else
				gmt_M_memcpy (M_obj->data.f4, G_obj->data, G_obj->header->nm, float);
#endif
			}
			else {
				GMT_Report (API, GMT_MSG_INFORMATION, "Referencing grid data to user memory location\n");
#ifdef DOUBLE_PRECISION_GRID
				M_obj->data.f8 = G_obj->data;
#else
				M_obj->data.f4 = G_obj->data;
#endif
			}
			MH->alloc_level = S_obj->alloc_level;	/* Since we are passing it up to the caller */
			S_obj->resource = M_obj;	/* Set resource pointer to the matrix */
			break;

		default:
			GMT_Report (API, GMT_MSG_ERROR, "Wrong method used to export grids\n");
			return (gmtlib_report_error (API, GMT_NOT_A_VALID_METHOD));
			break;
	}

	if (done) S_obj->status = GMT_IS_USED;	/* Mark as written (unless we only updated header) */

	return (GMT_NOERROR);
}

int GMT_Put_Levels (void *V_API, struct GMT_CUBE *C, double *levels, uint64_t n_levels) {
	/* Duplicate and assign a level array to the cube for its 3rd dimension coordinates */
	struct GMT_CUBE_HIDDEN *CU;
	struct GMTAPI_CTRL *API = NULL;

	/* Check for NULL and void arguments */
	if (V_API == NULL) return_error (API, GMT_NOT_A_SESSION);
	if (levels == NULL) return_error (API, GMT_PTR_IS_NULL);
	if (n_levels == 0) return_error (API, GMT_DIM_TOO_SMALL);
	if (C == NULL) return_error (API, GMT_PTR_IS_NULL);
	if (C->z) return_error (API, GMT_PTR_NOT_NULL);
	if (C->header == NULL) return_error (API, GMT_PTR_IS_NULL);
	if (C->header->n_bands > 0) {	/* If set then it better match */
		if ((uint64_t)C->header->n_bands < n_levels) return_error (API, GMT_DIM_TOO_SMALL);
		if ((uint64_t)C->header->n_bands > n_levels) return_error (API, GMT_DIM_TOO_LARGE);
	}
	if ((CU = gmt_get_U_hidden (C)) == NULL) return_error (API, GMT_PTR_IS_NULL);
	API = gmtapi_get_api_ptr (V_API);
	if ((C->z = gmt_duplicate_array (API->GMT, levels, n_levels)) == NULL) return_error (API, GMT_MEMORY_ERROR);
	CU->xyz_alloc_mode[GMT_Z] = GMT_ALLOC_INTERNALLY;	/* Since allocated by GMT */
	C->header->n_bands = (uint32_t)n_levels;

	return (GMT_NOERROR);
}

#ifdef FORTRAN_API
int GMT_Put_Levels_ (struct GMT_CUBE *C, double *level, uint64_t *n) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Put_Levels (GMT_FORTRAN, C, level, *n));
}
#endif

/*! . */
GMT_LOCAL struct GMT_CUBE * gmtapi_import_cube (struct GMTAPI_CTRL *API, int object_ID, unsigned int mode, struct GMT_CUBE *cube) {
	/* Handles the reading of a 3-D data cube given in one of several ways.
	 * Get the entire cube:
 	 * 	mode = GMT_CONTAINER_AND_DATA reads both header and cube;
	 * Get a subset of the cube:  Call gmtapi_import_cube twice:
	 * 	1. first with mode = GMT_CONTAINER_ONLY which reads header only.  Then, pass
	 *	   the new S_obj-> wesn to match your desired subregion
	 *	2. 2nd with mode = GMT_DATA_ONLY, which reads cube based on header's settings
	 * If the cube->data array is NULL it will be allocated for you.
	 */

	char file[PATH_MAX] = {""};
	int item, new_item, new_ID;
	bool done = true;
	openmp_int row, col, kol, row_out, i0, i1, j0, j1;
	uint64_t k0, k1, ij, ij_orig, n_layers = 0, k, n_layers_used, here;
	unsigned int both_set = (GMT_CONTAINER_ONLY | GMT_DATA_ONLY);
	unsigned int method, start_over_method = 0;
	double dx, dy, d;
	double *level = NULL, z_min, z_max, w_range[2] = {0.0, 0.0};
	p_func_uint64_t GMT_2D_to_index = NULL;
	struct GMT_CUBE *U_obj = NULL, *U_orig = NULL;
	struct GMT_GRID *G = NULL;
	struct GMT_MATRIX *M_obj = NULL;
	struct GMT_MATRIX_HIDDEN *MH = NULL;
	struct GMT_CUBE_HIDDEN *UH = NULL;
	struct GMT_GRID_HEADER_HIDDEN *HH = NULL;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMT_CTRL *GMT = API->GMT;
	GMT_getfunction api_get_val = NULL;

	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_cube: Passed ID = %d and mode = %d\n", object_ID, mode);

	if ((item = gmtlib_validate_id (API, GMT_IS_CUBE, object_ID, GMT_IN, GMT_NOTSET)) == GMT_NOTSET) return_null (API, API->error);

	S_obj = API->object[item];		/* Current data object */
	if (S_obj->status != GMT_IS_UNUSED && S_obj->method == GMT_IS_FILE && !(mode & GMT_IO_RESET)) return_null (API, GMT_READ_ONCE);	/* Already read this file before, so fail unless overridden by mode */
	if ((mode & both_set) == both_set) mode -= both_set;	/* Allow users to have set GMT_CONTAINER_ONLY | GMT_DATA_ONLY; reset to GMT_CONTAINER_AND_DATA */
	if ((mode & GMT_CONTAINER_ONLY) && S_obj->region && S_obj->method == GMT_IS_FILE) {
		GMT_Report (API, GMT_MSG_ERROR, "Cannot request a subset when just inquiring about the cube header\n");
		return_null (API, GMT_SUBSET_NOT_ALLOWED);
	}

	if (S_obj->region && cube) {	/* See if this is really a subset or just the same region as the cube */
		if (cube->header->wesn[XLO] == S_obj->wesn[XLO] && cube->header->wesn[XHI] == S_obj->wesn[XHI] && \
			cube->header->wesn[YLO] == S_obj->wesn[YLO] && cube->header->wesn[YHI] == S_obj->wesn[YHI] && \
			cube->z_range[0] == S_obj->wesn[ZLO] && cube->z_range[1] == S_obj->wesn[ZHI])
				S_obj->region = false;
	}
	method = gmtapi_set_method (S_obj);	/* Get the actual method to use since may be MATRIX or VECTOR masquerading as GRID */

start_over_import_cube:		/* We may get here if we cannot honor a GMT_IS_REFERENCE from below */

	switch (method) {
		/* Status: This case is fully tested and operational */
		case GMT_IS_FILE:	/* Name of a cube file on disk */
			/* When source is an actual file we place the cube container into the S_obj->resource slot; no new object required */
			/* If we need to read the header etc then we based this on the first layer in the cube */
			if ((mode & GMT_DATA_ONLY) == 0) {	/* Get the cube header information */
				char cube_layer[GMT_LEN64] = {""}, z_name[GMT_GRID_UNIT_LEN80] = {""}, *nc_z_named = NULL, *the_file = NULL;
				/* Got a single 3-D cube netCDF name, possibly selecting a specific variable via ?<name> */
				the_file = strdup (S_obj->filename);		/* Duplicate filename since we may change it */
				nc_z_named = strchr (the_file, '?');	/* Maybe given a specific variable? */
				if (nc_z_named) {	/* Gave a specific layer. Keep variable name and truncate the filename */
					strcpy (cube_layer, &nc_z_named[1]);	/* Place variable name in cube_layer string */
					nc_z_named[0] = '\0';	/* Chop off layer name for now */
				}
				if (gmt_nc_read_cube_info (GMT, the_file, w_range, &n_layers, &level, z_name)) {	/* Learn the basics about the cube */
					GMT_Report (API, GMT_MSG_ERROR, "gmtapi_import_cube: Unable to examine cube %s.\n", the_file);
					gmt_M_str_free (the_file);
					return_null (API, GMT_RUNTIME_ERROR);
				}
				sprintf (file, "%s?%s[0]", the_file, cube_layer);	/* Read cube header from the first layer in the cube */
				/* Read the first layer grid */
				if ((G = GMT_Read_Data (API, GMT_IS_GRID, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_ONLY, NULL, file, NULL)) == NULL) {
					gmt_M_str_free (the_file);
					return_null (API, GMT_RUNTIME_ERROR);
				}
				/* Allocate a data cube structure and fill in the file information */
				U_obj = gmtlib_create_cube (GMT);
				gmt_copy_gridheader (GMT, U_obj->header, G->header);
				if (level) {	/* Got an array of levels from the 3-D grid */
					U_obj->z_range[0] = level[0];	U_obj->z_range[1] = level[n_layers-1];
					if (n_layers < 3 || !gmtlib_var_inc (level, n_layers))	/* Equidistant layering */
						U_obj->z_inc = level[1] - level[0];	/* Since they are all the same */
					U_obj->z = level;	/* Let C be the owner of this array from now on */
					UH = gmt_get_U_hidden (U_obj);
					UH->xyz_alloc_mode[GMT_Z] = GMT_ALLOC_INTERNALLY;
				}
				U_obj->header->n_bands = n_layers;
				U_obj->mode = mode;
				if (nc_z_named) strncpy (U_obj->name, cube_layer, GMT_GRID_VARNAME_LEN80);	/* Remember this name if given */
				strncpy (U_obj->units, z_name, GMT_GRID_UNIT_LEN80);	/* Place the cube's z-unit (for z-dimension) */
				HH = gmt_get_H_hidden (U_obj->header);
				strncpy (HH->name, the_file, GMT_GRID_NAME_LEN256);	/* Filename minus any specified variable */
				if (nc_z_named) nc_z_named[0] = '?';	/* Restore layer name in file name */
				gmt_M_str_free (the_file);
				if (GMT_Destroy_Data (API, &G)) {	/* Must use GMT_Destroy_Data since G was registered in GMT_Read_Data */
					gmtlib_free_cube (GMT, &U_obj, true);
					return_null (API, GMT_RUNTIME_ERROR);
				}
			}
			else
				U_obj = cube;	/* We are working on a cube already allocated */
			S_obj->resource = U_obj;	/* Set resource pointer to the cube */
			done = (mode & GMT_CONTAINER_ONLY) ? false : true;	/* Not done until we read cube */
			if (mode & GMT_CONTAINER_ONLY) break;	/* Just needed the header, get out of here */

			/* Here we have the cube header and possibly the levels */
			HH = gmt_get_H_hidden (U_obj->header);

			/* Determine which layers we want to read */
			k0 = 0;	k1 = U_obj->header->n_bands - 1;	/* All layers selected */
			if (S_obj->region && S_obj->wesn[ZHI] > S_obj->wesn[ZLO]) {	/* Want a subset of layers */
				if (U_obj->z == NULL) {
					GMT_Report (API, GMT_MSG_ERROR, "gmtapi_import_cube: No layer level array available in GMT_IS_CUBE.\n");
					gmtlib_free_cube (GMT, &U_obj, true);
					return_null (API, GMT_PTR_IS_NULL);
				}
				else if (gmt_get_active_layers (GMT, U_obj, &(S_obj->wesn[ZLO]), &k0, &k1) == 0) {
					gmtlib_free_cube (GMT, &U_obj, true);
					return_null (API, GMT_PTR_IS_NULL);
				}
			}
			n_layers_used = k1 - k0 + 1;	/* Total number of layers actually to be read */
			if (n_layers_used == 0) {
				GMT_Report (API, GMT_MSG_ERROR, "gmtapi_import_cube: No layers selected from GMT_IS_CUBE.\n");
				gmtlib_free_cube (GMT, &U_obj, true);
				return_null (API, GMT_DIM_TOO_SMALL);
			}

			GMT_Report (API, GMT_MSG_INFORMATION, "Reading cube from file %s\n", S_obj->filename);
			for (k = k0; k <= k1; k++) {	/* Read the required layers into individual grid structures */
				/* Get the k'th layer from 3D cube possibly via a selected variable name */
				sprintf (file, "%s?%s[%" PRIu64 "]", HH->name, U_obj->name, k);
				/* Read in the layer as a temporary grid */
				if ((G = GMT_Read_Data (API, GMT_IS_GRID, GMT_IS_FILE, GMT_IS_SURFACE, GMT_GRID_ALL, S_obj->wesn, file, NULL)) == NULL) {
					GMT_Report (API, GMT_MSG_ERROR, "gmtapi_import_cube: Unable to read layer %" PRIu64 " from file %s.\n", k, file);
					gmtlib_free_cube (GMT, &U_obj, true);
					return_null (API, GMT_GRID_READ_ERROR);
				}
				if (U_obj->data == NULL) {	/* Update grid header (due to possible subsets) and allocate cube the first time */
					n_layers = U_obj->header->n_bands;	/* Remember full number of layers before overwriting in the next line */
					if (S_obj->region) gmt_copy_gridheader (GMT, U_obj->header, G->header);	/* Since subset can have changed dims and ranges */
					U_obj->header->n_bands = n_layers_used;	/* New number of layers */
					U_obj->z_range[0] = U_obj->z[k0];
					U_obj->z_range[1] = U_obj->z[k1];
					if (k0) {	/* Eliminate levels not included and shrink length of array */
						memmove (U_obj->z, &U_obj->z[k0], n_layers_used * sizeof(double));
						gmt_M_memset (&U_obj->z[n_layers_used], n_layers-n_layers_used, double);
						U_obj->z = gmt_M_memory (API->GMT, U_obj->z, n_layers_used, double);
					}
					/* Allocate cube data (note: each layer has padding) */
					if ((U_obj->data = gmt_M_memory_aligned (API->GMT, NULL, U_obj->header->size * n_layers_used, gmt_grdfloat)) == NULL) return_null (API, GMT_MEMORY_ERROR);
					z_min = U_obj->header->z_min;	/* Initialize cube min/max values based on this first layer */
					z_max = U_obj->header->z_max;
					here = 0;	/* Initialize offset into k'th layer */
					UH = gmt_get_U_hidden (U_obj);
					UH->alloc_mode = GMT_ALLOC_INTERNALLY;
				}
				else {	/* Here we update min/max for subsequent layers read */
					if (G->header->z_min < z_min) z_min = G->header->z_min;
					if (G->header->z_max > z_max) z_max = G->header->z_max;
				}
				/* Place this layer in the cube */
				gmt_M_memcpy (&U_obj->data[here], G->data, U_obj->header->size, gmt_grdfloat);
				here += U_obj->header->size;	/* Advance the offset */
				if (GMT_Destroy_Data (API, &G)) {	/* Must eliminate this registered resource */
					gmtlib_free_cube (GMT, &U_obj, true);
					return_null (API, GMT_RUNTIME_ERROR);
				}
			}
			/* Update cube min/max */
			U_obj->header->z_min = z_min;
			U_obj->header->z_max = z_max;
			/* Set BCs per layer */
			gmt_BC_init (GMT, U_obj->header);	/* Initialize cube interpolation and boundary condition parameters */
			if (gmt_M_err_pass (GMT, gmt_cube_BC_set (GMT, U_obj, GMT_IN), "Cube memory"))
				return_null (API, GMT_GRID_BC_ERROR);	/* Set boundary conditions */
			break;

	 	case GMT_IS_DUPLICATE:	/* GMT cube and header in a GMT_CUBE container object. */
			/* Must duplicate the cube container from S_obj->resource and hence a new object is required */
			if ((U_orig = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			if (cube == NULL) {	/* Only allocate when not already allocated */
				if (mode & GMT_DATA_ONLY) return_null (API, GMT_NO_GRDHEADER);		/* For mode & GMT_DATA_ONLY cube must already be allocated */
				if ((U_obj = GMT_Duplicate_Data (API, GMT_IS_GRID, GMT_DUPLICATE_NONE, U_orig)) == NULL)
					return_null (API, GMT_MEMORY_ERROR);
			}
			else
				U_obj = cube;	/* We are passing in a cube already */
			done = (mode & GMT_CONTAINER_ONLY) ? false : true;	/* Not done until we read cube */
			if (mode & GMT_CONTAINER_ONLY) break;	/* Just needed the header, get out of here */
			/* Here we will read cube data. */
			/* To get a subset we use region that is not NULL or contain 0/0/0/0/0/0.
			 * Otherwise we use everything passed in */
			GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating cube data from GMT_CUBE memory location\n");
			if (!U_obj->data) {	/* Array is not allocated, do so now. We only expect header (and possibly subset w/e/s/n/z0/z1) to have been set correctly */
				U_obj->header->size = gmtapi_set_grdarray_size (GMT, U_obj->header, mode, S_obj->wesn);	/* Get x/y array dimension only, which may include padding */
				if ((U_obj->data = gmt_M_memory_aligned (GMT, NULL, U_obj->header->size * U_obj->header->n_bands, gmt_grdfloat)) == NULL) return_null (API, GMT_MEMORY_ERROR);
			}
			UH = gmt_get_U_hidden (U_obj);
			UH->alloc_mode = GMT_ALLOC_INTERNALLY;
			UH->alloc_level = GMT->hidden.func_level;   /* Must be freed at this level. */
			if (!S_obj->region && gmt_grd_pad_status (GMT, U_obj->header, GMT->current.io.pad)) {	/* Want an exact copy with no subset and same padding */
				gmt_M_memcpy (U_obj->data, U_orig->data, U_orig->header->size * U_orig->header->n_bands, gmt_grdfloat);
				gmt_BC_init (GMT, U_obj->header);	/* Initialize cube interpolation and boundary condition parameters */
				if (gmt_M_err_pass (GMT, gmt_cube_BC_set (GMT, U_obj, GMT_IN), "Cube memory"))
					return_null (API, GMT_GRID_BC_ERROR);	/* Set boundary conditions */
				break;		/* Done with this cube */
			}
			/* Here we need to do more work: Either extract subset or add/change padding, or both. */
			/* Get start/stop row/cols for subset (or the entire domain) */
			/* dx,dy are needed when the cube is pixel-registered as the w/e/s/n bounds are off by 0.5 {dx,dy} relative to node coordinates */
			dx = U_obj->header->inc[GMT_X] * U_obj->header->xy_off;	dy = U_obj->header->inc[GMT_Y] * U_obj->header->xy_off;
			j1 = (unsigned int)gmt_M_grd_y_to_row (GMT, S_obj->wesn[YLO]+dy, U_orig->header);
			j0 = (unsigned int)gmt_M_grd_y_to_row (GMT, S_obj->wesn[YHI]-dy, U_orig->header);
			i0 = (unsigned int)gmt_M_grd_x_to_col (GMT, S_obj->wesn[XLO]+dx, U_orig->header);
			i1 = (unsigned int)gmt_M_grd_x_to_col (GMT, S_obj->wesn[XHI]-dx, U_orig->header);
			(void) gmt_get_active_layers (GMT, U_orig, &(S_obj->wesn[ZLO]), &k0, &k1);
			gmt_M_memcpy (U_obj->header->pad, GMT->current.io.pad, 4, int);	/* Set desired padding */
			gmt_M_memcpy (U_obj->header->wesn, S_obj->wesn, 4U, double);	/* Update the cube header region to match subset request */
			gmt_M_memcpy (U_obj->z_range, &(S_obj->wesn[ZLO]), 2U, double);	/* Update the cube range to match subset request */
			gmt_set_grddim (GMT, U_obj->header);	/* Adjust all dimensions accordingly before accessing the cube for output */
			U_obj->header->n_bands = k1 - k0 + 1;
			/* get stats */
			HH = gmt_get_H_hidden (U_obj->header);
			U_obj->header->z_min = DBL_MAX;
			U_obj->header->z_max = -DBL_MAX;
			HH->has_NaNs = GMT_GRID_NO_NANS;	/* We are about to check for NaNs and if none are found we retain 1, else 2 */
			for (k = k0; k <= k1; k++) {
				for (row = j0, row_out = 0; row <= j1; row++, row_out++) {
					ij = gmt_M_ijp (U_obj->header, row_out, 0) + (k - k0) * U_obj->header->size;	/* Position in output cube at start of current row */
					for (col = i0; col <= i1; col++, ij++) {
						kol = col % U_orig->header->n_columns;
						ij_orig = gmt_M_ijp (U_orig->header, row, kol) + k * U_orig->header->size;	/* Position of this (row,col) in original cube organization */
						U_obj->data[ij] = U_orig->data[ij_orig];
						if (gmt_M_is_fnan (U_obj->data[ij]))
							HH->has_NaNs = GMT_GRID_HAS_NANS;
						else {
							U_obj->header->z_min = MIN (U_obj->header->z_min, U_obj->data[ij]);
							U_obj->header->z_max = MAX (U_obj->header->z_max, U_obj->data[ij]);
						}
					}
				}
			}
			gmt_BC_init (GMT, U_obj->header);	/* Initialize cube interpolation and boundary condition parameters */
			if (gmt_M_err_pass (GMT, gmt_cube_BC_set (GMT, U_obj, GMT_IN), "Cube memory"))
				return_null (API, GMT_GRID_BC_ERROR);	/* Set boundary conditions */
			break;

	 	case GMT_IS_REFERENCE:	/* GMT cube and header in a GMT_CUBE container object by reference [NOT SURE ABOUT THIS] */
			if (S_obj->region) return_null (API, GMT_SUBSET_NOT_ALLOWED);
			GMT_Report (API, GMT_MSG_INFORMATION, "Referencing cube data from GMT_CUBE memory location\n");
			if ((U_obj = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			done = (mode & GMT_CONTAINER_ONLY) ? false : true;	/* Not done until we read cube */
			GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_cube: Change alloc mode\n");
			UH = gmt_get_U_hidden (U_obj);
			S_obj->alloc_mode = UH->alloc_mode;
			GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_cube: Check pad\n");
			gmt_BC_init (GMT, U_obj->header);	/* Initialize cube interpolation and boundary condition parameters */
			if (gmt_M_err_pass (GMT, gmt_cube_BC_set (GMT, U_obj, GMT_IN), "Cube memory"))
				return_null (API, GMT_GRID_BC_ERROR);	/* Set boundary conditions */
			if (gmtapi_adjust_grdpadding (U_obj->header, GMT->current.io.pad)) {
				GMT_Report (API, GMT_MSG_INFORMATION, "Reference cube must have standard padding\n");
				return_null (API, GMT_PADDING_NOT_ALLOWED);	/* Set boundary conditions */
			}
			GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_cube: Return from GMT_IS_REFERENCE\n");
			break;

		/* Status: This case is fully tested and operational */
	 	case GMT_IS_DUPLICATE|GMT_VIA_MATRIX:	/* The user's 3-D cube matrix of some sort, + info in the matrix header */
			/* Must create a cube container from matrix info S_obj->resource and hence a new object is required */
			if ((M_obj = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			if (cube == NULL) {	/* Only allocate when not already allocated, and only get container */
				uint64_t dim[3] = {M_obj->n_columns, M_obj->n_rows, M_obj->n_layers};
				if ((U_obj = GMT_Create_Data (API, GMT_IS_CUBE, GMT_IS_VOLUME, GMT_CONTAINER_ONLY, dim, M_obj->range, M_obj->inc, M_obj->registration, GMT_NOTSET, NULL)) == NULL)
					return_null (API, GMT_MEMORY_ERROR);
			}
			else
				U_obj = cube;
			if ((new_ID = gmtapi_get_object (API, GMT_IS_CUBE, U_obj)) == GMT_NOTSET)
				return_null (API, GMT_OBJECT_NOT_FOUND);
			if ((new_item = gmtlib_validate_id (API, GMT_IS_CUBE, new_ID, GMT_IN, GMT_NOTSET)) == GMT_NOTSET)
				return_null (API, GMT_OBJECT_NOT_FOUND);
			API->object[new_item]->method = S_obj->method;
			UH = gmt_get_U_hidden (U_obj);
			HH = gmt_get_H_hidden (U_obj->header);
			U_obj->header->complex_mode = (mode & GMT_GRID_IS_COMPLEX_MASK);	/* Set the complex mode */
			UH->alloc_mode = GMT_ALLOC_INTERNALLY;
			done = (mode & GMT_CONTAINER_ONLY) ? false : true;	/* Not done until we read cube */
			if ((GMT_2D_to_index = gmtapi_get_2d_to_index (API, M_obj->shape, GMT_GRID_IS_REAL)) == NULL)
				return_null (API, GMT_WRONG_MATRIX_SHAPE);
			if ((api_get_val = gmtapi_select_get_function (API, M_obj->type)) == NULL)
				return_null (API, GMT_NOT_A_VALID_TYPE);
			HH = gmt_get_H_hidden (U_obj->header);

			if (! (mode & GMT_DATA_ONLY)) {	/* Must first init header and copy the header information from the matrix header */
				gmtapi_matrixinfo_to_grdheader (GMT, U_obj->header, M_obj);	/* Populate a GRD header structure */
				/* Must get the full zmin/max range since not provided by the matrix header */
					U_obj->header->z_min = +DBL_MAX;
					U_obj->header->z_max = -DBL_MAX;
					HH->has_NaNs = GMT_GRID_NO_NANS;	/* We are about to check for NaNs and if none are found we retain 1, else 2 */
					for (k = 0; k < M_obj->n_layers; k++) {
						for (row = 0; row < (openmp_int)M_obj->n_rows; row++) {
							for (col = 0; col <(openmp_int) M_obj->n_columns; col++) {
							ij_orig = GMT_2D_to_index (row, col, M_obj->dim) + k * M_obj->size;
							ij_orig = GMT_2D_to_index (row, col, M_obj->dim);
							api_get_val (&(M_obj->data), ij_orig, &d);
							if (gmt_M_is_dnan (d))
								HH->has_NaNs = GMT_GRID_HAS_NANS;
							else {
								U_obj->header->z_min = MIN (U_obj->header->z_min, (gmt_grdfloat)d);
								U_obj->header->z_max = MAX (U_obj->header->z_max, (gmt_grdfloat)d);
							}
						}
					}
				}
				if (mode & GMT_CONTAINER_ONLY)	/* Just needed the header */
					break;	/* Done for now */
			}

			GMT_Report (API, GMT_MSG_INFORMATION, "Importing cube data from user matrix memory location\n");

			/* Get start/stop row/cols for subset (or the entire domain) */
			/* dx,dy are needed when the cube is pixel-registered as the w/e/s/n bounds are off by 0.5 {dx,dy} relative to node coordinates */
			k0 = 0;	k1 = M_obj->n_layers - 1;
			if (!S_obj->region || gmt_whole_earth (GMT, M_obj->range, S_obj->wesn)) {	/* Easy, get the whole enchilada */
				j0 = i0 = 0;
				j1 = U_obj->header->n_rows    - 1;	/* Minus 1 since we loop up to and including below */
				i1 = U_obj->header->n_columns - 1;
			}
			else {	/* Want a subset */
				dx = U_obj->header->inc[GMT_X] * U_obj->header->xy_off;	dy = U_obj->header->inc[GMT_Y] * U_obj->header->xy_off;
				if (gmt_M_is_geographic (GMT, GMT_IN)) { /* Must first wrap S_obj->wesn to fit the data if necessary */
					if (S_obj->wesn[XLO] > U_obj->header->wesn[XHI]) { /* Must first wrap U_obj->header->wesn west to fit the data */
						U_obj->header->wesn[XLO] += 360.0;	U_obj->header->wesn[XHI] += 360.0;
					}
					else if (S_obj->wesn[XHI] < U_obj->header->wesn[XLO]) { /* Must first wrap U_obj->header->wesn east to fit the data */
						U_obj->header->wesn[XLO] -= 360.0;	U_obj->header->wesn[XHI] -= 360.0;
					}
					if (S_obj->wesn[XLO] < U_obj->header->wesn[XLO]) {
						/* Must wrap U_obj->header.wesn so the left bound in S_obj is larger than that in U_obj, otherwise i0 is negative (but it's defined as unsigned int */
						U_obj->header->wesn[XLO] -= 360.0;	U_obj->header->wesn[XHI] -= 360.0;
					}
				}
				j1 = (unsigned int)gmt_M_grd_y_to_row (GMT, S_obj->wesn[YLO]+dy, U_obj->header);
				j0 = (unsigned int)gmt_M_grd_y_to_row (GMT, S_obj->wesn[YHI]-dy, U_obj->header);
				i0 = (unsigned int)gmt_M_grd_x_to_col (GMT, S_obj->wesn[XLO]+dx, U_obj->header);
				i1 = (unsigned int)gmt_M_grd_x_to_col (GMT, S_obj->wesn[XHI]-dx, U_obj->header);
				while (k0 < k1 && S_obj->wesn[ZLO] > (M_obj->range[ZLO] + k0 * M_obj->inc[GMT_Z])) k0++;	/* Set first layer */
				while (k1 && S_obj->wesn[ZHI] > (M_obj->range[ZHI] - (M_obj->n_layers-k1 - 1) * M_obj->inc[GMT_Z])) k1++;	/* Set last layer */
				gmt_M_memcpy (U_obj->header->wesn, S_obj->wesn, 4U, double);	/* Update the cube header region to match subset request */
				gmt_set_grddim (GMT, U_obj->header);	/* Adjust all dimensions accordingly before allocating space */
				gmt_M_memcpy (U_obj->z_range, &(S_obj->wesn[ZLO]), 2U, double);	/* Update the cube header range to match subset request */
				U_obj->header->n_bands = k1 - k0 + 1;
			}
			if (U_obj->data) {	/* This is an error - there cannot be a data pointer yet */
				GMT_Report (API, GMT_MSG_ERROR, "U->data is not NULL when memory allocation is about to happen\n");
				return_null (API, GMT_PTR_IS_NULL);
			}
			else if ((U_obj->data = gmt_M_memory_aligned (GMT, NULL, U_obj->header->size * U_obj->header->n_bands, gmt_grdfloat)) == NULL)
				return_null (API, GMT_MEMORY_ERROR);
			here = 0;
			for (k = k0; k <= k1; k++) {
				for (row = j0, row_out = 0; row <= j1; row++, row_out++) {
					ij = gmt_M_ijp (U_obj->header, row_out, 0) + here;	/* Position in output cube at start of current row */
					for (col = i0; col <= i1; col++, ij++) {
						kol = col % M_obj->n_columns;
						ij_orig = GMT_2D_to_index (row, kol, M_obj->dim) + k * M_obj->size;	/* Position of this (row,col) in input matrix organization */
						api_get_val (&(M_obj->data), ij_orig, &d);	/* Get the next item from the matrix */
						U_obj->data[ij] = (gmt_grdfloat)d;
						if (gmt_M_is_dnan (d))
							HH->has_NaNs = GMT_GRID_HAS_NANS;
						else {
							U_obj->header->z_min = MIN (U_obj->header->z_min, (gmt_grdfloat)d);
							U_obj->header->z_max = MAX (U_obj->header->z_max, (gmt_grdfloat)d);
						}
					}
				}
				here += U_obj->header->size;
			}
			if (gmt_whole_earth (GMT, M_obj->range, S_obj->wesn)) {
				/* Global cubes passed via matrix are not rotated to fit the desired global region, so we need to correct the wesn for this cube to match the matrix */
				gmt_M_memcpy (U_obj->header->wesn, M_obj->range, 4U, double);
			}
			gmt_BC_init (GMT, U_obj->header);	/* Initialize cube interpolation and boundary condition parameters */
			if (gmt_M_err_pass (GMT, gmt_cube_BC_set (GMT, U_obj, GMT_IN), "Cube memory"))
				return_null (API, GMT_GRID_BC_ERROR);	/* Set boundary conditions */
			API->object[new_item]->status = GMT_IS_USED;	/* Mark as read */
			API->object[new_item]->actual_family = GMT_IS_CUBE;	/* Done reading from matrix */
			if (start_over_method) API->object[new_item]->method = start_over_method;	/* We changed our mind from reference to duplicate due to region */
			UH->alloc_level = API->object[new_item]->alloc_level;	/* Since allocated here */
			break;

	 	case GMT_IS_REFERENCE|GMT_VIA_MATRIX:	/* The user's 3-D matrix of some sort, + info in the args [NOT YET FULLY TESTED] */
			/* Getting a matrix info S_obj->resource. Create cube header and then pass the cube pointer via the matrix pointer */
			if ((M_obj = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			if (mode & GMT_GRID_NEEDS_PAD1 || mode & GMT_GRID_NEEDS_PAD2) {	/* Cannot do this by reference, switch to duplication */
				start_over_method = gmtapi_switch_method (API, S_obj, &method, "Cube via a user matrix requires method GMT_IS_DUPLICATE instead of GMT_IS_REFERENCE due to a padding requirement - method has been switched\n");
				goto start_over_import_cube;
			}
			/* Determine if it is possible to use the matrix given the region selected and the fact we chose GMT_IS_REFERENCE. This test will
			 * only kick in after we allocate the U_obj and come back the second time (after getting header) since otherwise S_obj->wesn is not set yet */
			if (!(!S_obj->region ||
				(S_obj->wesn[XLO] >= M_obj->range[XLO] && S_obj->wesn[XHI] <= M_obj->range[XHI] && S_obj->wesn[YLO] >= M_obj->range[YLO] && S_obj->wesn[YHI] <= M_obj->range[YHI]) ||
				gmt_whole_earth (GMT, M_obj->range, S_obj->wesn))) {	/* Cannot do this by reference, switch to duplication */
				start_over_method = gmtapi_switch_method (API, S_obj, &method, "Cube subset selection via a user matrix requires method GMT_IS_DUPLICATE instead of GMT_IS_REFERENCE - method has been switched\n");
				goto start_over_import_cube;
			}
			/* This method requires the input data to be a GMT_GRD_FORMAT matrix - otherwise we should be DUPLICATING */
			if (!(M_obj->shape == GMT_IS_ROW_FORMAT && M_obj->type == GMT_GRDFLOAT && (mode & GMT_GRID_IS_COMPLEX_MASK) == 0)) {
				start_over_method = gmtapi_switch_method (API, S_obj, &method, "Cube via a user matrix requires method GMT_IS_DUPLICATE instead of GMT_IS_REFERENCE due to incompatible data type for a cube - method has been switched\n");
				goto start_over_import_cube;
			}

			if (cube == NULL) {	/* Only allocate when not already allocated.  Note cannot have pad since input matrix wont have one */
				uint64_t dim[3] = {M_obj->n_rows, M_obj->n_columns, M_obj->n_layers};
				if ((U_obj = GMT_Create_Data (API, GMT_IS_CUBE, GMT_IS_VOLUME, GMT_CONTAINER_ONLY, dim, M_obj->range, M_obj->inc, M_obj->registration, 0, NULL)) == NULL)
					return_null (API, GMT_MEMORY_ERROR);
			}
			else
				U_obj = cube;
			HH = gmt_get_H_hidden (U_obj->header);
			U_obj->header->complex_mode = (mode & GMT_GRID_IS_COMPLEX_MASK);	/* Set the complex mode */
			done = (mode & GMT_CONTAINER_ONLY) ? false : true;	/* Not done until we read cube */
			if (! (mode & GMT_DATA_ONLY)) {
				gmtapi_matrixinfo_to_grdheader (GMT, U_obj->header, M_obj);	/* Populate a GRD header structure */
				/* Temporarily set data pointer for convenience; removed later */
#ifdef DOUBLE_PRECISION_GRID
				U_obj->data = M_obj->data.f8;
#else
				U_obj->data = M_obj->data.f4;
#endif
				U_obj->header->z_min = +DBL_MAX;
				U_obj->header->z_max = -DBL_MAX;
				HH->has_NaNs = GMT_GRID_NO_NANS;	/* We are about to check for NaNs and if none are found we retain 1, else 2 */
				here = 0;
				for (k = 0; k < M_obj->n_layers; k++) {
					for (row = 0; row < (openmp_int)M_obj->n_rows; row++) {
						ij = gmt_M_ijp (U_obj->header, row, 0) + here;
						for (col = 0; col < (openmp_int)M_obj->n_columns; col++, ij++) {
							if (gmt_M_is_fnan (U_obj->data[ij]))
								HH->has_NaNs = GMT_GRID_HAS_NANS;
							else {
								U_obj->header->z_min = MIN (U_obj->header->z_min, U_obj->data[ij]);
								U_obj->header->z_max = MAX (U_obj->header->z_max, U_obj->data[ij]);
							}
						}
					}
					here += U_obj->header->size;
				}
				U_obj->data = NULL;	/* Since data are not requested yet */
				if (mode & GMT_CONTAINER_ONLY)	/* Just needed the header but had to set zmin/zmax first */
					break;
			}
			if ((new_ID = gmtapi_get_object (API, GMT_IS_CUBE, U_obj)) == GMT_NOTSET)
				return_null (API, GMT_OBJECT_NOT_FOUND);
			if ((new_item = gmtlib_validate_id (API, GMT_IS_CUBE, new_ID, GMT_IN, GMT_NOTSET)) == GMT_NOTSET)
				return_null (API, GMT_OBJECT_NOT_FOUND);
			GMT_Report (API, GMT_MSG_INFORMATION, "Referencing cube data from user memory location\n");
#ifdef DOUBLE_PRECISION_GRID
			U_obj->data = M_obj->data.f8;
#else
			U_obj->data = M_obj->data.f4;
#endif
			UH = gmt_get_U_hidden (U_obj);
			MH = gmt_get_M_hidden (M_obj);
			S_obj->alloc_mode = MH->alloc_mode;	/* Pass on alloc_mode of matrix */
			UH->alloc_mode = GMT_ALLOC_EXTERNALLY;	/* Since we cannot have both M and G try to free */
			API->object[new_item]->resource = U_obj;
			API->object[new_item]->status = GMT_IS_USED;	/* Mark as read */
			UH->alloc_level = API->object[new_item]->alloc_level;	/* Since allocated here */
			if (gmt_whole_earth (GMT, M_obj->range, S_obj->wesn)) {
				/* Global cubes passed via matrix are not rotated to fit the desired global region, so we need to correct the wesn for this cube to match the matrix */
				gmt_M_memcpy (U_obj->header->wesn, M_obj->range, 4U, double);
			}
			else if (S_obj->region) {	/* Possibly adjust the pad so inner region matches wesn */
				if (S_obj->reset_pad) {	/* First undo a prior sub-region used with this memory cube */
					gmtapi_contract_headerpad (GMT, U_obj->header, S_obj->orig_pad, S_obj->orig_wesn);
					S_obj->reset_pad = 0;
				}
				if (gmtapi_expand_headerpad (GMT, U_obj->header, S_obj->wesn, S_obj->orig_pad, S_obj->orig_wesn)) {
					S_obj->reset_pad = 1;
					gmtapi_update_cube_minmax (API->GMT, U_obj);	/* Update z-range */
				}
			}
			break;

		default:
			GMT_Report (API, GMT_MSG_ERROR, "Wrong method used to import cube\n");
			return_null (API, GMT_NOT_A_VALID_METHOD);
			break;
	}

	if ((mode & GMT_CONTAINER_ONLY) == 0) {	/* Also allocate and initialize the x and y vectors unless already present  */
		struct GMT_CUBE_HIDDEN *GU = gmt_get_U_hidden (U_obj);
		if (U_obj->x == NULL) {
			GU->xyz_alloc_mode[GMT_X] = GMT_ALLOC_INTERNALLY;
			if (GMT->current.io.nc_xarray)	/* Got variable x-array and asked to used this instead */
				U_obj->x = GMT->current.io.nc_xarray, GMT->current.io.nc_xarray = NULL;
			else
				U_obj->x = gmtapi_cube_coord (API, GMT_X, U_obj);	/* Get array of x coordinates */
		}
		if (U_obj->y == NULL) {
			GU->xyz_alloc_mode[GMT_Y] = GMT_ALLOC_INTERNALLY;
			if (GMT->current.io.nc_yarray)	/* Got variable y-array and asked to used this instead */
				U_obj->y = GMT->current.io.nc_yarray, GMT->current.io.nc_yarray = NULL;
			else
				U_obj->y = gmtapi_cube_coord (API, GMT_Y, U_obj);	/* Get array of y coordinates */
		}
	}

	if (done) S_obj->status = GMT_IS_USED;	/* Mark as read (unless we just got the header) */

	return (U_obj);	/* Pass back out what we have so far */
}

/*! Writes out a single cube to destination */
GMT_LOCAL int gmtapi_export_cube (struct GMTAPI_CTRL *API, int object_ID, unsigned int mode, struct GMT_CUBE *U_obj) {
	int item, error;
	bool done = true;
	unsigned int method;
	openmp_int row, col, i0, i1, j0, j1;
	uint64_t k0, k1, ij, ijp, ij_orig, k, here = 0;
	size_t size;
	double dx, dy;
	p_func_uint64_t GMT_2D_to_index = NULL;
	GMT_putfunction api_put_val = NULL;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMT_CUBE *U_copy = NULL;
	struct GMT_MATRIX *M_obj = NULL;
	struct GMT_MATRIX_HIDDEN *MH = NULL;
	struct GMT_CUBE_HIDDEN *UH = gmt_get_U_hidden (U_obj), *UH2 = NULL;
	struct GMT_CTRL *GMT = API->GMT;

	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_export_cube: Passed ID = %d and mode = %d\n", object_ID, mode);

	if (object_ID == GMT_NOTSET) return (gmtlib_report_error (API, GMT_OUTPUT_NOT_SET));
	if (U_obj->data == NULL && !(mode & GMT_CONTAINER_ONLY)) return (gmtlib_report_error (API, GMT_PTR_IS_NULL));
	if ((item = gmtlib_validate_id (API, GMT_IS_CUBE, object_ID, GMT_OUT, GMT_NOTSET)) == GMT_NOTSET) return (gmtlib_report_error (API, API->error));

	S_obj = API->object[item];	/* The current object whose data we will export */
	if (S_obj->status != GMT_IS_UNUSED && !(mode & GMT_IO_RESET))
		return (gmtlib_report_error (API, GMT_WRITTEN_ONCE));	/* Only allow writing of a data set once, unless overridden by mode */
	if (mode & GMT_IO_RESET) mode -= GMT_IO_RESET;
	if (S_obj->region) {	/* See if this is really a subset or just the same region as the cube */
		if (U_obj->header->wesn[XLO] == S_obj->wesn[XLO] && U_obj->header->wesn[XHI] == S_obj->wesn[XHI] && \
			U_obj->header->wesn[YLO] == S_obj->wesn[YLO] && U_obj->header->wesn[YHI] == S_obj->wesn[YHI] && \
			U_obj->z_range[0] == S_obj->wesn[ZLO] && U_obj->z_range[1] == S_obj->wesn[ZHI])
				S_obj->region = false;
	}
	if (mode & GMT_DATA_IS_GEO) gmt_set_geographic (GMT, GMT_OUT);	/* From API to tell cube is geographic */
	gmtapi_cube_set_units (GMT, U_obj);	/* Ensure unit strings are set, regardless of destination */

	method = gmtapi_set_method (S_obj);	/* Get the actual method to use since may be MATRIX or VECTOR masquerading as GRID */
	switch (method) {
		case GMT_IS_FILE:	/* Name of a cube file to write to disk */
			if (mode & GMT_CONTAINER_ONLY) {	/* Update header structure only */
				GMT_Report (API, GMT_MSG_INFORMATION, "Updating cube header for file %s not implemented\n", S_obj->filename);
				return (gmtlib_report_error (API, GMT_RUNTIME_ERROR));
			}
			else {
				GMT_Report (API, GMT_MSG_INFORMATION, "Writing cube to file %s\n", S_obj->filename);
				if (gmt_nc_write_cube (GMT, U_obj, S_obj->wesn, S_obj->filename) != GMT_NOERROR)
					return (gmtlib_report_error (API, API->error));
				done = true;
			}
			break;

	 	case GMT_IS_DUPLICATE:	/* Duplicate GMT cube and header to a GMT_CUBE container object. Subset allowed */
			if (S_obj->resource) return (gmtlib_report_error (API, GMT_PTR_NOT_NULL));	/* The output resource pointer must be NULL */
			if (mode & GMT_CONTAINER_ONLY) return (gmtlib_report_error (API, GMT_NOT_A_VALID_MODE));
			if (gmtapi_adjust_grdpadding (U_obj->header, GMT->current.io.pad)) {
				GMT_Report (API, GMT_MSG_INFORMATION, "Reference cube must have standard padding\n");
				gmtlib_report_error (API, GMT_PADDING_NOT_ALLOWED);
			}
			GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating cube data to GMT_GRID memory location\n");
			if (!S_obj->region) {	/* No subset, possibly same padding */
				U_copy = gmtlib_duplicate_cube (API->GMT, U_obj, GMT_DUPLICATE_DATA);
				UH2 = gmt_get_U_hidden (U_copy);
				UH2->alloc_level = S_obj->alloc_level;	/* Since we are passing it up to the caller */
				gmt_BC_init (GMT, U_copy->header);	/* Initialize cube interpolation and boundary condition parameters */
				if (gmt_M_err_pass (GMT, gmt_cube_BC_set (GMT, U_copy, GMT_OUT), "Cube memory")) return (gmtlib_report_error (API, GMT_GRID_BC_ERROR));	/* Set boundary conditions */
				S_obj->resource = U_copy;	/* Set resource pointer to the cube */
				break;		/* Done with this cube */
			}
			/* Here we need to extract subset, and possibly change padding. */
			/* Get start/stop row/cols for subset (or the entire domain) */
			U_copy = gmtlib_create_cube (GMT);
			UH2 = gmt_get_U_hidden (U_copy);
			UH2->alloc_level = S_obj->alloc_level;	/* Since we are passing it up to the caller */
			gmt_copy_gridheader (GMT, U_copy->header, U_obj->header);
			gmt_M_memcpy (U_copy->header->wesn, S_obj->wesn, 4, double);
			gmt_M_memcpy (U_copy->z_range, &(S_obj->wesn[ZLO]), 2U, double);	/* Update the cube range to match subset request */
			/* dx,dy are needed when the cube is pixel-registered as the w/e/s/n bounds are off by 0.5 {dx,dy} relative to node coordinates */
			dx = U_obj->header->inc[GMT_X] * U_obj->header->xy_off;	dy = U_obj->header->inc[GMT_Y] * U_obj->header->xy_off;
			j1 = (unsigned int) gmt_M_grd_y_to_row (GMT, U_obj->header->wesn[YLO]+dy, U_obj->header);
			j0 = (unsigned int) gmt_M_grd_y_to_row (GMT, U_obj->header->wesn[YHI]-dy, U_obj->header);
			i0 = (unsigned int) gmt_M_grd_x_to_col (GMT, U_obj->header->wesn[XLO]+dx, U_obj->header);
			i1 = (unsigned int) gmt_M_grd_x_to_col (GMT, U_obj->header->wesn[XHI]-dx, U_obj->header);
			(void) gmt_get_active_layers (GMT, U_obj, &(S_obj->wesn[ZLO]), &k0, &k1);
			gmt_M_memcpy (U_obj->header->pad, GMT->current.io.pad, 4, int);		/* Set desired padding */
			U_copy->header->size = gmtapi_set_grdarray_size (GMT, U_obj->header, mode, S_obj->wesn);	/* Get array dimension only, which may include padding */
			if ((U_copy->data = gmt_M_memory_aligned (GMT, NULL, U_copy->header->size, gmt_grdfloat)) == NULL) return (gmtlib_report_error (API, GMT_MEMORY_ERROR));
			U_copy->header->z_min = DBL_MAX;	U_copy->header->z_max = -DBL_MAX;	/* Must set vmin/vmax since we are not writing to file */
			U_copy->header->n_bands = k1 - k0 + 1;
			for (k = k0; k <= k1; k++) {
				for (row = j0; row <= j1; row++) {
					for (col = i0; col <= i1; col++, ij++) {
						ij_orig = gmt_M_ijp (U_obj->header, row, col) + (k - k0) * U_obj->header->size;	/* Position of this (row,col) in original cube organization */
						ij = gmt_M_ijp (U_copy->header, row, col) + k * U_copy->header->size;	/* Position of this (row,col) in output cube organization */
						U_copy->data[ij] = U_obj->data[ij_orig];
						if (gmt_M_is_fnan (U_copy->data[ij])) continue;
						/* Update z_min, z_max */
						U_copy->header->z_min = MIN (U_copy->header->z_min, (double)U_copy->data[ij]);
						U_copy->header->z_max = MAX (U_copy->header->z_max, (double)U_copy->data[ij]);
					}
				}
				}
			S_obj->resource = U_copy;	/* Set resource pointer to the cube */
			break;

	 	case GMT_IS_REFERENCE:	/* GMT cube and header in a GMT_CUBE container object - just pass the reference */
			if (S_obj->region) return (gmtlib_report_error (API, GMT_SUBSET_NOT_ALLOWED));
			if (mode & GMT_CONTAINER_ONLY) return (gmtlib_report_error (API, GMT_NOT_A_VALID_MODE));
			GMT_Report (API, GMT_MSG_INFORMATION, "Referencing cube data to GMT_CUBE memory location\n");
			gmt_cube_vminmax (GMT, U_obj->header, U_obj->data);	/* Must set cube's vmin/vmax since we are not writing to file */
			gmt_BC_init (GMT, U_obj->header);	/* Initialize cube interpolation and boundary condition parameters */
			if (gmt_M_err_pass (GMT, gmt_cube_BC_set (GMT, U_obj, GMT_OUT), "Cube memory")) return (gmtlib_report_error (API, GMT_GRID_BC_ERROR));	/* Set boundary conditions */
			S_obj->resource = U_obj;	/* Set resource pointer to the cube */
			UH->alloc_level = S_obj->alloc_level;	/* Since we are passing it up to the caller */
			break;

	 	case GMT_IS_DUPLICATE|GMT_VIA_MATRIX:	/* The user's 3-D matrix of some sort, + info in the args [NOT FULLY TESTED] */
			if (mode & GMT_CONTAINER_ONLY) return (gmtlib_report_error (API, GMT_NOT_A_VALID_MODE));
			if (S_obj->resource) {	/* The output resource pointer already exist for matrix */
				M_obj = gmtapi_get_matrix_data (S_obj->resource);
				if (M_obj->n_rows < U_obj->header->n_rows || M_obj->n_columns < U_obj->header->n_columns || M_obj->n_layers < U_obj->header->n_bands)
					return (gmtlib_report_error (API, GMT_DIM_TOO_SMALL));
			}
			else {	/* Must allocate stuff */
		 		M_obj = gmtlib_create_matrix (API->GMT, U_obj->header->n_bands, 0);
				M_obj->type = S_obj->type;
			}
			MH = gmt_get_M_hidden (M_obj);
			gmtapi_grdheader_to_matrixinfo (GMT, U_obj->header, M_obj);	/* Populate an array with GRD header information */
			gmt_M_memcpy (&(M_obj->range[ZLO]), U_obj->z_range, 2U, double);	/* Update the cube range to match subset request */
			M_obj->inc[GMT_Z] = U_obj->z_inc;
			M_obj->dim = (M_obj->shape == GMT_IS_ROW_FORMAT) ? M_obj->n_columns : M_obj->n_rows;	/* Matrix layout order */
			GMT_Report (API, GMT_MSG_INFORMATION, "Exporting cube data to user memory location\n");
			if (S_obj->resource == NULL) {	/* Must allocate output */
				size = gmt_M_get_nm (GMT, U_obj->header->n_columns, U_obj->header->n_rows) * M_obj->n_layers;
				if ((error = gmtlib_alloc_univector (GMT, &(M_obj->data), M_obj->type, size)) != GMT_NOERROR) return (error);
				MH->alloc_mode = GMT_ALLOC_INTERNALLY;
				MH->alloc_level = GMT->hidden.func_level;   /* Must be freed at this level. */
			}
			if ((GMT_2D_to_index = gmtapi_get_2d_to_index (API, M_obj->shape, GMT_GRID_IS_REAL)) == NULL)
				return (gmtlib_report_error (API, GMT_WRONG_MATRIX_SHAPE));
			if ((api_put_val = gmtapi_select_put_function (API, M_obj->type)) == NULL)
				return (gmtlib_report_error (API, GMT_NOT_A_VALID_TYPE));
			size = gmt_M_get_nm (GMT, M_obj->n_columns, M_obj->n_rows);
			for (k = 0; k < U_obj->header->n_bands; k++) {
				gmt_M_grd_loop (GMT, U_obj, row, col, ijp) {
					ij = GMT_2D_to_index (row, col, M_obj->dim) + k * size;
					api_put_val (&(M_obj->data), ij, (double)U_obj->data[ijp+here]);
				}
				here += U_obj->header->size;
			}
			MH->alloc_level = S_obj->alloc_level;	/* Since we are passing it up to the caller */
			S_obj->resource = M_obj;	/* Set resource pointer to the matrix */
			break;

	 	case GMT_IS_REFERENCE|GMT_VIA_MATRIX:	/* Write to a user matrix of type gmt_grdfloat */
			if (mode & GMT_CONTAINER_ONLY) return (gmtlib_report_error (API, GMT_NOT_A_VALID_MODE));
			if (mode & GMT_GRID_IS_COMPLEX_MASK)	/* Cannot do a complex cube this way */
				return (gmtlib_report_error (API, GMT_NOT_A_VALID_IO_ACCESS));
			if (S_obj->resource) {	/* The output resource pointer already exist for matrix */
				M_obj = gmtapi_get_matrix_data (S_obj->resource);
				if (M_obj->n_rows < U_obj->header->n_rows || M_obj->n_columns < U_obj->header->n_columns || M_obj->n_layers < U_obj->header->n_bands)
					return (gmtlib_report_error (API, GMT_DIM_TOO_SMALL));
				assert (M_obj->type == GMT_GRDFLOAT);	/* That is the whole point of getting here, no? */
			}
			else {	/* Must allocate stuff */
		 		M_obj = gmtlib_create_matrix (API->GMT, U_obj->header->n_bands, 1);
				M_obj->type = GMT_GRDFLOAT;	/* A cube is always gmt_grdfloat */
			}
			MH = gmt_get_M_hidden (M_obj);
			if (gmtapi_adjust_grdpadding (U_obj->header, GMT_no_pad))
				gmt_cube_pad_off (GMT, U_obj);	/* Remove pad */
			/* This method requires the output data to be a gmt_grdfloat matrix - otherwise we should be DUPLICATING.
			   This distinction is set in GMT_Open_VirtualFile */
			gmtapi_grdheader_to_matrixinfo (GMT, U_obj->header, M_obj);	/* Populate an array with GRD header information */
			gmt_M_memcpy (&(M_obj->range[ZLO]), U_obj->z_range, 2U, double);	/* Update the cube range to match subset request */
			M_obj->inc[GMT_Z] = U_obj->z_inc;
			M_obj->shape = GMT_IS_ROW_FORMAT;	/* Because it is a direct GMT gmt_grdfloat cube */
			if (S_obj->resource) {
				GMT_Report (API, GMT_MSG_INFORMATION, "Memcpy cube data to user memory location\n");
#ifdef DOUBLE_PRECISION_GRID
				gmt_M_memcpy (M_obj->data.f8, U_obj->data, U_obj->header->nm * U_obj->header->n_bands, double);
#else
				gmt_M_memcpy (M_obj->data.f4, U_obj->data, U_obj->header->nm * U_obj->header->n_bands, float);
#endif
			}
			else {
				GMT_Report (API, GMT_MSG_INFORMATION, "Referencing cube data to user memory location\n");
#ifdef DOUBLE_PRECISION_GRID
				M_obj->data.f8 = U_obj->data;
#else
				M_obj->data.f4 = U_obj->data;
#endif
			}
			MH->alloc_level = S_obj->alloc_level;	/* Since we are passing it up to the caller */
			S_obj->resource = M_obj;	/* Set resource pointer to the matrix */
			break;

		default:
			GMT_Report (API, GMT_MSG_ERROR, "Wrong method used to export cubes\n");
			return (gmtlib_report_error (API, GMT_NOT_A_VALID_METHOD));
			break;
	}

	if (done) S_obj->status = GMT_IS_USED;	/* Mark as written (unless we only updated header) */

	return (GMT_NOERROR);
}


GMT_LOCAL struct GMT_MATRIX * gmtapi_read_matrix (struct GMT_CTRL *GMT, void *source, unsigned int src_type, unsigned int mode) {
	/* We read the MATRIX from fp [or stdin].
	 * src_type can be GMT_IS_[FILE|STREAM|FDESC]
	 * Notes: mode is not used yet.  We only do ascii file for now - later need to deal with -b, if needed.
	 */

	bool close_file = false, first = true, add_first_segheader = false, in_header_section = true;
	unsigned int pos;
	int error = 0;
	uint64_t row = 0, col, ij, n_col, nt_alloc = 0, nh_alloc = 0, n_headers = 0, dim[4] = {0, 0, 0, GMT->current.setting.export_type};
	char M_file[PATH_MAX] = {""}, line[GMT_BUFSIZ] = {""};
	char **text = NULL, **header = NULL;
	FILE *fp = NULL;
	struct GMT_MATRIX *M = NULL;
	GMT_putfunction api_put_val = NULL;
	p_func_uint64_t GMT_2D_to_index = NULL;
	gmt_M_unused(mode);

	if (GMT->common.b.active[GMT_IN]) {
		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Opening Matrix file %s in binary mode not yet supported\n");
		return_null (GMT->parent, GMT_ERROR_ON_FOPEN);
	}

	if (src_type == GMT_IS_FILE && !source) src_type = GMT_IS_STREAM;	/* No filename given, default to stdin */

	if (src_type == GMT_IS_FILE) {	/* dest is a file name */
		strncpy (M_file, source, PATH_MAX-1);
		if ((fp = gmt_fopen (GMT, M_file, GMT->current.io.r_mode)) == NULL) {
			GMT_Report (GMT->parent, GMT_MSG_ERROR, "Cannot open Matrix file %s\n", M_file);
			return_null (GMT->parent, GMT_ERROR_ON_FOPEN);
		}
		close_file = true;	/* We only close files we have opened here */
	}
	else if (src_type == GMT_IS_STREAM) {	/* Open file pointer given, just copy */
		fp = (FILE *)source;
		if (fp == NULL) fp = GMT->session.std[GMT_IN];	/* Default destination */
		if (fp == GMT->session.std[GMT_IN])
			strcpy (M_file, "<stdin>");
		else
			strcpy (M_file, "<input stream>");
	}
	else if (src_type == GMT_IS_FDESC) {		/* Open file descriptor given, just convert to file pointer */
		int *fd = source;
		if (fd && (fp = fdopen (*fd, "r")) == NULL) {
			GMT_Report (GMT->parent, GMT_MSG_ERROR, "Cannot convert Matrix file descriptor %d to stream in gmtapi_read_matrix\n", *fd);
			return_null (GMT->parent, GMT_ERROR_ON_FDOPEN);
		}
		if (fd == NULL) fp = GMT->session.std[GMT_IN];	/* Default destination */
		if (fp == GMT->session.std[GMT_IN])
			strcpy (M_file, "<stdin>");
		else
			strcpy (M_file, "<input file descriptor>");
		close_file = true;	/* since fdopen allocates space */
	}
	else {
		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Unrecognized source type %d in gmtapi_read_matrix\n", src_type);
		return_null (GMT->parent, GMT_NOT_A_VALID_METHOD);
	}
	GMT_Report (GMT->parent, GMT_MSG_DEBUG, "Read Matrix from %s\n", M_file);

	while (!error && fgets (line, GMT_BUFSIZ, fp)) {
		gmt_chop (line);	/* Remove linefeeds */
		if (strchr (GMT->current.setting.io_head_marker_in, line[0])) {
			if (in_header_section) {
				if (nh_alloc <= n_headers && (header = gmt_M_memory (GMT, NULL, nh_alloc += GMT_TINY_CHUNK, char *)) == NULL) return_null (GMT->parent, GMT_MEMORY_ERROR);
				header[n_headers++] = strdup (line);
			}
			continue;
		}
		in_header_section = false;
		if (line[0] == '>') {
			if (first) {	/* Have not allocated yet so just skip that row for now and deal with it later */
				first = false;
				add_first_segheader = true;
			}
			else {	/* Already allocated so place NaNs as segment header */
				gmt_prep_tmp_arrays (GMT, GMT_IN, row, dim[0]);	/* Init or reallocate tmp vectors */
				for (col = 0; col < dim[0]; col++) GMT->hidden.mem_coord[col][row] = GMT->session.d_NaN;
			}
		}
		else {	/* Regular data record */
			if (dim[0] == 0)	/* First time we must establish how many columns */
				dim[0] = gmtlib_conv_text2datarec (GMT, line, GMT_BUFSIZ, GMT->current.io.curr_rec, &pos);
			if ((n_col = gmtlib_conv_text2datarec (GMT, line, dim[0], GMT->current.io.curr_rec, &pos)) != dim[0]) {
				GMT_Report (GMT->parent, GMT_MSG_ERROR, "Matrix record %" PRIu64 " only had %" PRIu64 " columns but %" PRIu64 " was expected.  Record skipped\n", row, n_col, dim[0]);
				continue;
			}
			gmt_prep_tmp_arrays (GMT, GMT_IN, row, dim[0]);	/* Init or reallocate tmp vectors */
			for (col = 0; col < dim[0]; col++) GMT->hidden.mem_coord[col][row] = GMT->current.io.curr_rec[col];
			if (line[pos]) {	/* Deal with trailing text */
				if (nt_alloc <= row && (text = gmt_M_memory (GMT, NULL, nt_alloc += GMT_INITIAL_MEM_ROW_ALLOC, char **)) == NULL) return_null (GMT->parent, GMT_MEMORY_ERROR);
				text[row] = strdup (&line[pos]);
			}
		}
		row++;
	}
	/* Possibly restore the missing first segment header */
	if (add_first_segheader) for (col = 0; col < dim[0]; col++) GMT->hidden.mem_coord[col][0] = GMT->session.d_NaN;
	dim[1] = row;	/* Allocate all vectors using current type setting in the defaults [GMT_DOUBLE] */
	if ((M = GMT_Create_Data (GMT->parent, GMT_IS_MATRIX, GMT_IS_POINT, 0, dim, NULL, NULL, 0, 0, NULL)) == NULL) {
		if (close_file) fclose (fp);
		return_null (GMT->parent, GMT_MEMORY_ERROR);
	}
	if ((api_put_val = gmtapi_select_put_function (GMT->parent, M->type)) == NULL)	/* Get correct put function given data type */
		return_null (GMT->parent, GMT_NOT_A_VALID_TYPE);
	if ((GMT_2D_to_index = gmtapi_get_2d_to_index (GMT->parent, M->shape, GMT_GRID_IS_REAL)) == NULL)	/* Get ij index function */
		return_null (GMT->parent, GMT_WRONG_MATRIX_SHAPE);
	for (col = 0; col < M->n_columns; col++) {
		for (row = 0; row < M->n_rows; row++) {
			ij = GMT_2D_to_index (row, col, M->dim);	/* Index into the user data matrix depends on layout (M->shape) */
			api_put_val (&(M->data), ij, GMT->hidden.mem_coord[col][row]);
		}
	}
	M->size = dim[GMT_X] * dim[GMT_Y];
	/* Set Default range and inc to reflect dim, with inc = 1 */
	M->range[XHI] = dim[GMT_X] - 1.0;
	M->range[YHI] = dim[GMT_Y] - 1.0;
	M->inc[GMT_X] = M->inc[GMT_Y] = 1.0;

	if (text) {	/* Attach the trailing text to the vector */
		struct GMT_MATRIX_HIDDEN *MH = gmt_get_M_hidden (M);
		if (nt_alloc > row) text = gmt_M_memory (GMT, text, row, char **);
		GMT_Put_Strings (GMT->parent, GMT_IS_MATRIX, M, text);
		MH->alloc_mode_text = GMT_ALLOC_INTERNALLY;	/* Override since it is allocated internally in GMT */
	}
	if (n_headers) {	/* Pass out the header records as well */
		if (nh_alloc > n_headers) header = gmt_M_memory (GMT, header, n_headers, char *);
		M->header = header;
		M->n_headers = n_headers;
	}

	if (close_file) gmt_fclose (GMT, fp);
	return (M);
}

GMT_LOCAL void *gmtapi_grid2matrix (struct GMTAPI_CTRL *API, struct GMT_GRID *In, struct GMT_MATRIX *Out) {
	bool alloc = (Out == NULL);
	openmp_int row, col;
	uint64_t ij, ij_M;
	double d;
	GMT_putfunction api_put_val = NULL;
	p_func_uint64_t GMT_2D_to_index = NULL;

	if (alloc) Out = gmtlib_create_matrix (API->GMT, 1U, 0);

	gmtapi_grdheader_to_matrixinfo (API->GMT, In->header, Out);
	if (alloc) {	/* Allocate the matrix itself */
		int error;
		Out->type = API->GMT->current.setting.export_type;
		Out->registration = In->header->registration;
		Out->shape = GMT_IS_ROW_FORMAT;	/* For now */
		Out->dim = (Out->shape == GMT_IS_ROW_FORMAT) ? Out->n_columns : Out->n_rows;	/* Matrix layout order */

		if ((error = gmtlib_alloc_univector (API->GMT, &(Out->data), Out->type, Out->n_rows * Out->n_columns)) != GMT_NOERROR) {
			gmt_M_free (API->GMT, Out);
			return_null (API, error);
		}
	}
	if ((GMT_2D_to_index = gmtapi_get_2d_to_index (API, Out->shape, GMT_GRID_IS_REAL)) == NULL) {
		if (alloc) gmt_M_free (API->GMT, Out);
		return_null (API, GMT_WRONG_MATRIX_SHAPE);
	}
	if ((api_put_val = gmtapi_select_put_function (API, Out->type)) == NULL) {
		if (alloc) gmt_M_free (API->GMT, Out);
		return_null (API, GMT_NOT_A_VALID_TYPE);
	}

	gmt_M_grd_loop (API->GMT, In, row, col, ij) {
		d = In->data[ij];
		ij_M = GMT_2D_to_index (row, col, Out->dim);
		api_put_val (&(Out->data), ij_M, d);
	}

	return Out;
}

GMT_LOCAL void *gmtapi_matrix2grid (struct GMTAPI_CTRL *API, struct GMT_MATRIX *In, struct GMT_GRID *Out) {
	bool alloc = (Out == NULL);
	openmp_int row, col;
	uint64_t ij, ij_M;
	double d;
	GMT_getfunction api_get_val = NULL;
	p_func_uint64_t GMT_2D_to_index = NULL;
	struct GMT_GRID_HEADER_HIDDEN *HH = NULL;

	if (alloc) Out = gmt_create_grid (API->GMT);

	gmtapi_matrixinfo_to_grdheader (API->GMT, Out->header, In);
	if (alloc) {	/* Allocate the grid itself */
		int error;
		gmt_set_grddim (API->GMT, Out->header);	/* Set all dimensions */
		if ((Out->data = gmt_M_memory (API->GMT, NULL, Out->header->size, gmt_grdfloat)) == NULL) {
			gmt_M_free (API->GMT, Out);
			return_null (API, API->error);
		}
		if ((error = gmtapi_alloc_grid_xy (API, Out)) != GMT_NOERROR) {
			gmt_M_free (API->GMT, Out);
			return_null (API, error);	/* Allocation error */
		}
	}
	if ((GMT_2D_to_index = gmtapi_get_2d_to_index (API, In->shape, GMT_GRID_IS_REAL)) == NULL) {
		if (alloc) gmt_M_free (API->GMT, Out);
		return_null (API, GMT_WRONG_MATRIX_SHAPE);
	}
	if ((api_get_val = gmtapi_select_get_function (API, In->type)) == NULL) {
		if (alloc) gmt_M_free (API->GMT, Out);
		return_null (API, GMT_NOT_A_VALID_TYPE);
	}

	HH = gmt_get_H_hidden (Out->header);
	Out->header->z_min = DBL_MAX;
	Out->header->z_max = -DBL_MAX;
	HH->has_NaNs = GMT_GRID_NO_NANS;	/* We are about to check for NaNs and if none are found we retain 1, else 2 */
	gmt_M_grd_loop (API->GMT, Out, row, col, ij) {
		ij_M = GMT_2D_to_index (row, col, In->dim);
		api_get_val (&(In->data), ij_M, &d);
		Out->data[ij] = (gmt_grdfloat)d;
		if (gmt_M_is_fnan (Out->data[ij]))
			HH->has_NaNs = GMT_GRID_HAS_NANS;
		else {
			Out->header->z_min = MIN (Out->header->z_min, Out->data[ij]);
			Out->header->z_max = MAX (Out->header->z_max, Out->data[ij]);
		}
	}

	return Out;
}

/*! . */
GMT_LOCAL struct GMT_MATRIX *gmtapi_import_matrix (struct GMTAPI_CTRL *API, int object_ID, unsigned int mode) {
	/* Does the actual work of loading in a GMT matrix. This could either be from a grid file or a 2-D table. */
	int item;
	unsigned int kind;
	struct GMT_MATRIX *M_obj = NULL, *M_orig = NULL;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMT_CTRL *GMT = API->GMT;

	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_matrix: Passed ID = %d and mode = %d\n", object_ID, mode);

	if (object_ID == GMT_NOTSET) return_null (API, GMT_NO_INPUT);
	if ((item = gmtlib_validate_id (API, GMT_IS_MATRIX, object_ID, GMT_IN, GMTAPI_OPTION_INPUT)) == GMT_NOTSET)
		return_null (API, API->error);

	S_obj = API->object[item];	/* Use S_obj as shorthand */
	if (S_obj->status != GMT_IS_UNUSED) { /* Already read this resource before; are we allowed to re-read? */
		if (S_obj->method == GMT_IS_STREAM || S_obj->method == GMT_IS_FDESC)
			return_null (API, GMT_READ_ONCE); /* Not allowed to re-read streams */
		if (!(mode & GMT_IO_RESET)) return_null (API, GMT_READ_ONCE);	/* Not authorized to re-read */
	}

	/* Passed sanity and allowed to read */

	switch (S_obj->method) {	/* File, array, stream etc ? */
		case GMT_IS_FILE:
			/* gmtapi_read_matrix will report where it is reading from if level is GMT_MSG_INFORMATION */
			GMT_Report (API, GMT_MSG_INFORMATION, "Reading MATRIX from %s %s\n", gmtapi_method (S_obj->method), S_obj->filename);
			if (S_obj->geometry == GMT_IS_SURFACE) {	/* Read a grid file and convert to MATRIX */
				struct GMT_GRID *G = NULL;
				if ((G = gmtapi_import_grid (API, object_ID, mode, NULL)) == NULL)
					return_null (API, GMT_DATA_READ_ERROR);
				M_obj = gmtapi_grid2matrix (API, G, NULL);	/* Convert the grid to a matrix */
				if (gmtapi_destroy_grid (API, &G))
					return_null (API, GMT_DATA_READ_ERROR);
			}
			else if ((M_obj = gmtapi_read_matrix (GMT, S_obj->filename, S_obj->method, mode)) == NULL)	/* Read a 2-D table */
				return_null (API, GMT_DATA_READ_ERROR);
			S_obj->resource = M_obj;		/* Retain pointer to the allocated data so we use garbage collection later */
			break;
		case GMT_IS_STREAM:
 			/* gmtapi_read_matrix will report where it is reading from if level is GMT_MSG_INFORMATION */
			kind = (S_obj->fp == GMT->session.std[GMT_IN]) ? 0 : 1;	/* Used for message: 0 if stdin, 1 otherwise for user pointer */
			GMT_Report (API, GMT_MSG_INFORMATION, "Reading MATRIX from %s %s stream\n", gmtapi_method (S_obj->method), GMT_stream[kind]);
			if ((M_obj = gmtapi_read_matrix (GMT, S_obj->fp, S_obj->method, mode)) == NULL)
				return_null (API, GMT_DATA_READ_ERROR);
			S_obj->resource = M_obj;		/* Retain pointer to the allocated data so we use garbage collection later */
			break;
		case GMT_IS_FDESC:
			/* gmtapi_read_matrix will report where it is reading from if level is GMT_MSG_INFORMATION */
			kind = (*((int *)S_obj->fp) == GMT_IN) ? 0 : 1;	/* Used for message: 0 if stdin, 1 otherwise for user pointer */
			GMT_Report (API, GMT_MSG_INFORMATION, "Reading MATRIX from %s %s stream\n", gmtapi_method (S_obj->method), GMT_stream[kind]);
			if ((M_obj = gmtapi_read_matrix (GMT, S_obj->fp, S_obj->method, mode)) == NULL)
				return_null (API, GMT_CPT_READ_ERROR);
			S_obj->resource = M_obj;		/* Retain pointer to the allocated data so we use garbage collection later */
			break;
		case GMT_IS_DUPLICATE:	/* Duplicate the input MATRIX */
			GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating MATRIX from MATRIX memory location\n");
			if ((M_orig = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			if ((M_obj = GMT_Duplicate_Data (API, GMT_IS_MATRIX, mode, M_orig)))
				return_null (API, GMT_MEMORY_ERROR);
			break;
		case GMT_IS_REFERENCE:	/* Just pass memory location, so nothing is allocated */
			GMT_Report (API, GMT_MSG_INFORMATION, "Referencing MATRIX from MATRIX memory location\n");
			if ((M_obj = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			break;
		default:	/* Barking up the wrong tree here... */
			GMT_Report (API, GMT_MSG_ERROR, "Wrong method used to import MATRIX\n");
			return_null (API, GMT_NOT_A_VALID_METHOD);
			break;
	}
	S_obj->status = GMT_IS_USED;	/* Mark as read */
	return (M_obj);	/* Pass back the vector */
}

GMT_LOCAL int gmtapi_write_matrix (struct GMT_CTRL *GMT, void *dest, unsigned int dest_type, unsigned int mode, struct GMT_MATRIX *M) {
	/* We write the MATRIX to fp [or stdout].
	 * dest_type can be GMT_IS_[FILE|STREAM|FDESC]
	 * mode is not used yet.
	 */

	bool close_file = false, append = false, was;
	uint64_t row, col, ij;
	unsigned int hdr;
	char M_file[PATH_MAX] = {""};
	static char *msg1[2] = {"Writing", "Appending"};
	FILE *fp = NULL;
	p_func_uint64_t GMT_2D_to_index = NULL;
	GMT_getfunction api_get_val = NULL;
	gmt_M_unused(mode);

	if (dest_type == GMT_IS_FILE && !dest) dest_type = GMT_IS_STREAM;	/* No filename given, default to stdout */

	if (dest_type == GMT_IS_FILE) {	/* dest is a file name */
		static char *msg2[2] = {"create", "append to"};
		strncpy (M_file, dest, PATH_MAX-1);
		append = (M_file[0] == '>');	/* Want to append to existing file */
		if ((fp = fopen (&M_file[append], (append) ? "a" : "w")) == NULL) {
			GMT_Report (GMT->parent, GMT_MSG_ERROR, "Cannot %s Matrix file %s\n", msg2[append], &M_file[append]);
			return (GMT_ERROR_ON_FOPEN);
		}
		close_file = true;	/* We only close files we have opened here */
	}
	else if (dest_type == GMT_IS_STREAM) {	/* Open file pointer given, just copy */
		fp = (FILE *)dest;
		if (fp == NULL) fp = GMT->session.std[GMT_OUT];	/* Default destination */
		if (fp == GMT->session.std[GMT_OUT])
			strcpy (M_file, "<stdout>");
		else
			strcpy (M_file, "<output stream>");
	}
	else if (dest_type == GMT_IS_FDESC) {		/* Open file descriptor given, just convert to file pointer */
		int *fd = dest;
		if (fd && (fp = fdopen (*fd, "w")) == NULL) {
			GMT_Report (GMT->parent, GMT_MSG_ERROR, "Cannot convert Matrix file descriptor %d to stream in gmtapi_write_matrix\n", *fd);
			return (GMT_ERROR_ON_FDOPEN);
		}
		if (fd == NULL) fp = GMT->session.std[GMT_OUT];	/* Default destination */
		if (fp == GMT->session.std[GMT_OUT])
			strcpy (M_file, "<stdout>");
		else
			strcpy (M_file, "<output file descriptor>");
		close_file = true;	/* since fdopen allocates space */
	}
	else {
		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Unrecognized source type %d in gmtapi_write_matrix\n", dest_type);
		return (GMT_NOT_A_VALID_METHOD);
	}
	GMT_Report (GMT->parent, GMT_MSG_DEBUG, "%s Matrix to %s\n", msg1[append], &M_file[append]);

	/* Set index and put-value functions */
	if ((GMT_2D_to_index = gmtapi_get_2d_to_index (GMT->parent, M->shape, GMT_GRID_IS_REAL)) == NULL) {
		if (close_file) fclose (fp);
		return (GMT_WRONG_MATRIX_SHAPE);
	}
	if ((api_get_val = gmtapi_select_get_function (GMT->parent, M->type)) == NULL) {
		if (close_file) fclose (fp);
		return (GMT_NOT_A_VALID_TYPE);
	}


	/* Start writing Matrix to fp */

	if (M->n_headers) {	/* Make sure we enable header records to be written */
		was = GMT->current.setting.io_header[GMT_OUT];
		GMT->current.setting.io_header[GMT_OUT] = true;
	}
	for (hdr = 0; hdr < M->n_headers; hdr++)
		gmtlib_write_tableheader (GMT, fp, M->header[hdr]);

	for (row = 0; row < M->n_rows; row++) {
		for (col = 0; col < M->n_columns; col++) {
			ij = GMT_2D_to_index (row, col, M->dim);	/* Index into the user data matrix depends on layout (M->shape) */
			api_get_val (&(M->data), ij, &(GMT->current.io.curr_rec[col]));
		}
		if (gmtapi_bin_input_memory (GMT, M->n_columns, M->n_columns) < 0)	/* Segment header found, finish the segment we worked on and goto next */
			gmt_write_segmentheader (GMT, fp, M->n_columns);
		else {	/* Format an ASCII output record */
			fprintf (fp, GMT->current.setting.format_float_out, GMT->current.io.curr_rec[0]);
			for (col = 1; col < M->n_columns; col++) {
				fprintf (fp, "%s", GMT->current.setting.io_col_separator);
				fprintf (fp, GMT->current.setting.format_float_out, GMT->current.io.curr_rec[col]);
			}
			if (M->text && M->text[row])
				fprintf (fp, "%s%s", GMT->current.setting.io_col_separator, M->text[row]);
			fprintf (fp, "\n");
		}
	}
	if (M->n_headers) GMT->current.setting.io_header[GMT_OUT] = was;  /* Revert to the original setting */

	if (close_file) fclose (fp);
	return (GMT_NOERROR);
}

/*! . */
GMT_LOCAL int gmtapi_export_matrix (struct GMTAPI_CTRL *API, int object_ID, unsigned int mode, struct GMT_MATRIX *M_obj) {
	/* Does the actual work of writing out the specified Matrix to a destination.  Only FILE supported for testing.
	 */
	int item, error;
	unsigned int kind;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMT_CTRL *GMT = API->GMT;

	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_export_matrix: Passed ID = %d and mode = %d\n", object_ID, mode);

	if (object_ID == GMT_NOTSET) return (gmtlib_report_error (API, GMT_OUTPUT_NOT_SET));
	if ((item = gmtlib_validate_id (API, GMT_IS_MATRIX, object_ID, GMT_OUT, GMT_NOTSET)) == GMT_NOTSET) return (gmtlib_report_error (API, API->error));

	S_obj = API->object[item];	/* This is the API object for the output destination */
	if (S_obj->status != GMT_IS_UNUSED && !(mode & GMT_IO_RESET)) {	/* Only allow writing of a data set once, unless we override by resetting the mode */
		return (gmtlib_report_error (API, GMT_WRITTEN_ONCE));
	}
	if (mode & GMT_IO_RESET) mode -= GMT_IO_RESET;

	/* Passed sanity and allowed to write */

	switch (S_obj->method) {	/* File, array, stream etc ? */
		case GMT_IS_FILE:
			/* gmtapi_write_matrix will report where it is writing from if level is GMT_MSG_INFORMATION */
			GMT_Report (API, GMT_MSG_INFORMATION, "Write MATRIX to %s %s\n", gmtapi_method (S_obj->method), S_obj->filename);
			if (S_obj->geometry == GMT_IS_SURFACE) {	/* Must convert matrix to grid then write to file */
				struct GMT_GRID *G;
				G = gmtapi_matrix2grid (API, M_obj, NULL);	/* Convert the matrix to a grid */
				error = gmtapi_export_grid (API, object_ID, mode, G);
				if (gmtapi_destroy_grid (API, &G))
					return (gmtlib_report_error (API, GMT_DATA_READ_ERROR));
			}
			else if ((error = gmtapi_write_matrix (GMT, S_obj->filename, S_obj->method, mode, M_obj))) return (gmtlib_report_error (API, error));
			break;
	 	case GMT_IS_STREAM:
			/* gmtapi_write_matrix will report where it is writing from if level is GMT_MSG_INFORMATION */
			kind = (S_obj->fp == GMT->session.std[GMT_OUT]) ? 0 : 1;	/* For message only: 0 if stdout, 1 otherwise for user pointer */
			GMT_Report (API, GMT_MSG_INFORMATION, "Write MATRIX to %s %s output stream\n", gmtapi_method (S_obj->method), GMT_stream[kind]);
			if ((error = gmtapi_write_matrix (GMT, S_obj->fp, S_obj->method, mode, M_obj))) return (gmtlib_report_error (API, error));
			break;
	 	case GMT_IS_FDESC:
			/* gmtapi_write_matrix will report where it is writing from if level is GMT_MSG_INFORMATION */
			kind = (*((int *)S_obj->fp) == GMT_OUT) ? 0 : 1;	/* For message only: 0 if stdout, 1 otherwise for user pointer */
			GMT_Report (API, GMT_MSG_INFORMATION, "Write MATRIX to %s %s output stream\n", gmtapi_method (S_obj->method), GMT_stream[kind]);
			if ((error = gmtapi_write_matrix (GMT, S_obj->fp, S_obj->method, mode, M_obj))) return (gmtlib_report_error (API, error));
			break;
		default:
			GMT_Report (API, GMT_MSG_ERROR, "Wrong method used to export MATRIX\n");
			return (gmtlib_report_error (API, GMT_NOT_A_VALID_METHOD));
			break;
	}
	S_obj->status = GMT_IS_USED;	/* Mark as written */

	return GMT_NOERROR;
}

GMT_LOCAL int gmtapi_write_vector (struct GMT_CTRL *GMT, void *dest, unsigned int dest_type, unsigned int mode, struct GMT_VECTOR *V) {
	/* We write the VECTOR to fp [or stdout].
	 * dest_type can be GMT_IS_[FILE|STREAM|FDESC]
	 * mode is not used yet.
	 */

	bool close_file = false, append = false, was;
	uint64_t row, col;
	unsigned int hdr;
	char V_file[PATH_MAX] = {""};
	static char *msg1[2] = {"Writing", "Appending"};
	FILE *fp = NULL;
	GMT_getfunction *api_get_val = NULL;
	gmt_M_unused(mode);

	if (V == NULL) {
		GMT_Report(GMT->parent, GMT_MSG_ERROR, "GMTAPI: gmtapi_write_vector passed a NULL pointer *V\n");
		return GMT_NOTSET;
	}
	if (dest_type == GMT_IS_FILE && !dest) dest_type = GMT_IS_STREAM;	/* No filename given, default to stdout */

	if (dest_type == GMT_IS_FILE) {	/* dest is a file name */
		static char *msg2[2] = {"create", "append to"};
		strncpy (V_file, dest, PATH_MAX-1);
		append = (V_file[0] == '>');	/* Want to append to existing file */
		if ((fp = fopen (&V_file[append], (append) ? "a" : "w")) == NULL) {
			GMT_Report (GMT->parent, GMT_MSG_ERROR, "Cannot %s Vector file %s\n", msg2[append], &V_file[append]);
			return (GMT_ERROR_ON_FOPEN);
		}
		close_file = true;	/* We only close files we have opened here */
	}
	else if (dest_type == GMT_IS_STREAM) {	/* Open file pointer given, just copy */
		fp = (FILE *)dest;
		if (fp == NULL) fp = GMT->session.std[GMT_OUT];	/* Default destination */
		if (fp == GMT->session.std[GMT_OUT])
			strcpy (V_file, "<stdout>");
		else
			strcpy (V_file, "<output stream>");
	}
	else if (dest_type == GMT_IS_FDESC) {		/* Open file descriptor given, just convert to file pointer */
		int *fd = dest;
		if (fd && (fp = fdopen (*fd, "a")) == NULL) {
			GMT_Report (GMT->parent, GMT_MSG_ERROR, "Cannot convert Vector file descriptor %d to stream in gmtapi_write_vector\n", *fd);
			return (GMT_ERROR_ON_FDOPEN);
		}
		if (fd == NULL) fp = GMT->session.std[GMT_OUT];	/* Default destination */
		if (fp == GMT->session.std[GMT_OUT])
			strcpy (V_file, "<stdout>");
		else
			strcpy (V_file, "<output file descriptor>");
		close_file = true;	/* since fdopen allocates space */
	}
	else {
		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Unrecognized source type %d in gmtapi_write_vector\n", dest_type);
		return (GMT_NOT_A_VALID_METHOD);
	}
	GMT_Report (GMT->parent, GMT_MSG_DEBUG, "%s Vector to %s\n", msg1[append], &V_file[append]);

	/* Set get function per vector column */
	if ((api_get_val = gmt_M_memory (GMT, NULL, V->n_columns, GMT_getfunction)) == NULL) return GMT_MEMORY_ERROR;
	for (col = 0; col < V->n_columns; col++) {
		if ((api_get_val[col] = gmtapi_select_get_function (GMT->parent, V->type[col])) == NULL) {
			gmt_M_free (GMT, api_get_val);
			return (GMT_NOT_A_VALID_TYPE);
		}
	}

	/* Start writing vector to fp */

	if (V->n_headers) {	/* Make sure we enable header records to be written */
		was = GMT->current.setting.io_header[GMT_OUT];
		GMT->current.setting.io_header[GMT_OUT] = true;
	}
	for (hdr = 0; hdr < V->n_headers; hdr++)
		gmtlib_write_tableheader (GMT, fp, V->header[hdr]);

	for (row = 0; row < V->n_rows; row++) {
		for (col = 0; col < V->n_columns; col++)
			api_get_val[col] (&(V->data[col]), row, &(GMT->current.io.curr_rec[col]));
		if (gmtapi_bin_input_memory (GMT, V->n_columns, V->n_columns) < 0)	/* Segment header found, finish the segment we worked on and goto next */
			gmt_write_segmentheader (GMT, fp, V->n_columns);
		else {	/* Format an ASCII record for output */
			gmt_ascii_output_col (GMT, fp, GMT->current.io.curr_rec[0], 0);
			for (col = 1; col < V->n_columns; col++) {
				fprintf (fp, "%s", GMT->current.setting.io_col_separator);
				gmt_ascii_output_col (GMT, fp, GMT->current.io.curr_rec[col], col);
			}
			if (V->text && V->text[row])
				fprintf (fp, "%s%s", GMT->current.setting.io_col_separator, V->text[row]);
			fprintf (fp, "\n");
		}
	}
	gmt_M_free (GMT, api_get_val);

	if (close_file) fclose (fp);
	if (V->n_headers) GMT->current.setting.io_header[GMT_OUT] = was;  /* Revert to the original setting */

	return (GMT_NOERROR);
}

/*! . */
GMT_LOCAL int gmtapi_export_vector (struct GMTAPI_CTRL *API, int object_ID, unsigned int mode, struct GMT_VECTOR *V_obj) {
	/* Does the actual work of writing out the specified Matrix to a destination.  Only FILE supported for testing.
	 */
	int item, error;
	unsigned int kind;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMT_CTRL *GMT = API->GMT;

	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_export_vector: Passed ID = %d and mode = %d\n", object_ID, mode);

	if (object_ID == GMT_NOTSET) return (gmtlib_report_error (API, GMT_OUTPUT_NOT_SET));
	if ((item = gmtlib_validate_id (API, GMT_IS_VECTOR, object_ID, GMT_OUT, GMT_NOTSET)) == GMT_NOTSET) return (gmtlib_report_error (API, API->error));

	S_obj = API->object[item];	/* This is the API object for the output destination */
	if (S_obj->status != GMT_IS_UNUSED && !(mode & GMT_IO_RESET)) {	/* Only allow writing of a data set once, unless we override by resetting the mode */
		return (gmtlib_report_error (API, GMT_WRITTEN_ONCE));
	}
	if (mode & GMT_IO_RESET) mode -= GMT_IO_RESET;

	/* Passed sanity and allowed to write */

	switch (S_obj->method) {	/* File, array, stream etc ? */
		case GMT_IS_FILE:
			/* gmtapi_write_vector will report where it is writing from if level is GMT_MSG_INFORMATION */
			GMT_Report (API, GMT_MSG_INFORMATION, "Write VECTOR to %s %s\n", gmtapi_method (S_obj->method), S_obj->filename);
			if ((error = gmtapi_write_vector (GMT, S_obj->filename, S_obj->method, mode, V_obj))) return (gmtlib_report_error (API, error));
			break;
	 	case GMT_IS_STREAM:
			/* gmtapi_write_vector will report where it is writing from if level is GMT_MSG_INFORMATION */
			kind = (S_obj->fp == GMT->session.std[GMT_OUT]) ? 0 : 1;	/* For message only: 0 if stdout, 1 otherwise for user pointer */
			GMT_Report (API, GMT_MSG_INFORMATION, "Write VECTOR to %s %s output stream\n", gmtapi_method (S_obj->method), GMT_stream[kind]);
			if ((error = gmtapi_write_vector (GMT, S_obj->fp, S_obj->method, mode, V_obj))) return (gmtlib_report_error (API, error));
			break;
	 	case GMT_IS_FDESC:
			/* gmtapi_write_vector will report where it is writing from if level is GMT_MSG_INFORMATION */
			kind = (*((int *)S_obj->fp) == GMT_OUT) ? 0 : 1;	/* For message only: 0 if stdout, 1 otherwise for user pointer */
			GMT_Report (API, GMT_MSG_INFORMATION, "Write VECTOR to %s %s output stream\n", gmtapi_method (S_obj->method), GMT_stream[kind]);
			if ((error = gmtapi_write_vector (GMT, S_obj->fp, S_obj->method, mode, V_obj))) return (gmtlib_report_error (API, error));
			break;
		default:
			GMT_Report (API, GMT_MSG_ERROR, "Wrong method used to export VECTOR\n");
			return (gmtlib_report_error (API, GMT_NOT_A_VALID_METHOD));
			break;
	}
	S_obj->status = GMT_IS_USED;	/* Mark as written */

	return GMT_NOERROR;
}

GMT_LOCAL struct GMT_VECTOR *gmtapi_read_vector (struct GMT_CTRL *GMT, void *source, unsigned int src_type, unsigned int mode) {
	/* We read the VECTOR from fp [or stdin].
	 * src_type can be GMT_IS_[FILE|STREAM|FDESC]
	 * mode is not used yet.  We only do ascii file for now - later need to deal with -b
	 */

	bool close_file = false, first = true, add_first_segheader = false, in_header_section = true;
	unsigned int pos;
	uint64_t nt_alloc = 0, nh_alloc = 0, n_headers = 0, row = 0, n_col, col, dim[GMT_DIM_SIZE] = {0, 0, GMT->current.setting.export_type, 0};
	char V_file[PATH_MAX] = {""};
	char line[GMT_BUFSIZ] = {""};
	char **text = NULL, **header = NULL;
	FILE *fp = NULL;
	struct GMT_VECTOR *V = NULL;
	GMT_putfunction api_put_val = NULL;
	gmt_M_unused(mode);

	if (GMT->common.b.active[GMT_IN]) {
		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Opening Vector file %s in binary mode not yet supported\n");
		return_null (GMT->parent, GMT_ERROR_ON_FOPEN);
	}

	if (src_type == GMT_IS_FILE && !source) src_type = GMT_IS_STREAM;	/* No filename given, default to stdin */

	if (src_type == GMT_IS_FILE) {	/* dest is a file name */
		strncpy (V_file, source, PATH_MAX-1);
		if ((fp = gmt_fopen (GMT, V_file, "r")) == NULL) {
			GMT_Report (GMT->parent, GMT_MSG_ERROR, "Cannot open Vector file %s\n", V_file);
			return_null (GMT->parent, GMT_ERROR_ON_FOPEN);
		}
		close_file = true;	/* We only close files we have opened here */
	}
	else if (src_type == GMT_IS_STREAM) {	/* Open file pointer given, just copy */
		fp = (FILE *)source;
		if (fp == NULL) fp = GMT->session.std[GMT_IN];	/* Default destination */
		if (fp == GMT->session.std[GMT_IN])
			strcpy (V_file, "<stdin>");
		else
			strcpy (V_file, "<input stream>");
	}
	else if (src_type == GMT_IS_FDESC) {		/* Open file descriptor given, just convert to file pointer */
		int *fd = source;
		if (fd && (fp = fdopen (*fd, "r")) == NULL) {
			GMT_Report (GMT->parent, GMT_MSG_ERROR, "Cannot convert Vector file descriptor %d to stream in gmtapi_read_vector\n", *fd);
			return_null (GMT->parent, GMT_ERROR_ON_FDOPEN);
		}
		if (fd == NULL) fp = GMT->session.std[GMT_IN];	/* Default destination */
		if (fp == GMT->session.std[GMT_IN])
			strcpy (V_file, "<stdin>");
		else
			strcpy (V_file, "<input file descriptor>");
		close_file = true;	/* since fdopen allocates space */
	}
	else {
		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Unrecognized source type %d in gmtapi_read_vector\n", src_type);
		return_null (GMT->parent, GMT_NOT_A_VALID_METHOD);
	}
	GMT_Report (GMT->parent, GMT_MSG_DEBUG, "Read Vector from %s\n", V_file);

	while (fgets (line, GMT_BUFSIZ, fp)) {
		gmt_chop (line);	/* Remove linefeeds */
		if (strchr (GMT->current.setting.io_head_marker_in, line[0])) {
			if (in_header_section) {
				if (nh_alloc <= n_headers && (header = gmt_M_memory (GMT, NULL, nh_alloc += GMT_TINY_CHUNK, char *)) == NULL) return_null (GMT->parent, GMT_MEMORY_ERROR);
				header[n_headers++] = strdup (line);
			}
			continue;
		}
		in_header_section = false;
		if (line[0] == '>') {
			if (first) {	/* Have not allocated yet so just skip that row for now */
				first = false;
				add_first_segheader = true;
			}
			else {	/* Already allocated so place NaNs */
				gmt_prep_tmp_arrays (GMT, GMT_IN, row, dim[0]);	/* Init or reallocate tmp vectors */
				for (col = 0; col < dim[0]; col++) GMT->hidden.mem_coord[col][row] = GMT->session.d_NaN;
			}
		}
		else {	/* Regular data record */
			if (dim[0] == 0)	/* First time we must extablish how many columns */
				dim[0] = gmtlib_conv_text2datarec (GMT, line, GMT_BUFSIZ, GMT->current.io.curr_rec, &pos);
			if ((n_col = gmtlib_conv_text2datarec (GMT, line, dim[0], GMT->current.io.curr_rec, &pos)) != dim[0]) {
				GMT_Report (GMT->parent, GMT_MSG_ERROR, "Vector record %" PRIu64 " only had %" PRIu64 " columns but %" PRIu64 " was expected.  Record skipped\n", row, n_col, dim[0]);
				continue;
			}
			gmt_prep_tmp_arrays (GMT, GMT_IN, row, dim[0]);	/* Init or reallocate tmp vectors */
			for (col = 0; col < dim[0]; col++) GMT->hidden.mem_coord[col][row] = GMT->current.io.curr_rec[col];
			if (line[pos]) {	/* Deal with trailing text */
				if (nt_alloc <= row && (text = gmt_M_memory (GMT, NULL, nt_alloc += GMT_INITIAL_MEM_ROW_ALLOC, char **)) == NULL) return_null (GMT->parent, GMT_MEMORY_ERROR);
				text[row] = strdup (&line[pos]);
			}
		}
		row++;
	}
	if (add_first_segheader) for (col = 0; col < dim[0]; col++) GMT->hidden.mem_coord[col][0] = GMT->session.d_NaN;
	dim[1] = row;	/* Allocate all vectors using current type setting in the defaults [GMT_DOUBLE] */
	if ((V = GMT_Create_Data (GMT->parent, GMT_IS_VECTOR, GMT_IS_POINT, 0, dim, NULL, NULL, 0, 0, NULL)) == NULL) {
		if (close_file) gmt_fclose (GMT, fp);
		return_null (GMT->parent, GMT_MEMORY_ERROR);
	}
	for (col = 0; col < V->n_columns; col++) {
		if ((api_put_val = gmtapi_select_put_function (GMT->parent, V->type[col])) == NULL)
			return_null (GMT->parent, GMT_NOT_A_VALID_TYPE);
		for (row = 0; row < V->n_rows; row++)
			api_put_val (&(V->data[col]), row, GMT->hidden.mem_coord[col][row]);
	}

	if (text) {	/* Attach the trailing text to the vector */
		struct GMT_VECTOR_HIDDEN *VH = gmt_get_V_hidden (V);
		if (nt_alloc > row) text = gmt_M_memory (GMT, text, row, char **);
		GMT_Put_Strings (GMT->parent, GMT_IS_VECTOR, V, text);
		VH->alloc_mode_text = GMT_ALLOC_INTERNALLY;	/* Override since it is allocated internally in GMT */
	}
	if (n_headers) {	/* Pass out the header records as well */
		if (nh_alloc > n_headers) header = gmt_M_memory (GMT, header, n_headers, char *);
		V->header = header;
		V->n_headers = n_headers;
	}

	if (close_file) gmt_fclose (GMT, fp);
	return (V);
}

/*! . */
GMT_LOCAL struct GMT_VECTOR *gmtapi_import_vector (struct GMTAPI_CTRL *API, int object_ID, unsigned int mode) {
	/* Does the actual work of loading in a GMT vector table. */
	int item;
	unsigned int kind;
	struct GMT_VECTOR *V_obj = NULL, *V_orig = NULL;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMT_CTRL *GMT = API->GMT;

	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_import_vector: Passed ID = %d and mode = %d\n", object_ID, mode);

	if (object_ID == GMT_NOTSET) return_null (API, GMT_NO_INPUT);
	if ((item = gmtlib_validate_id (API, GMT_IS_VECTOR, object_ID, GMT_IN, GMTAPI_OPTION_INPUT)) == GMT_NOTSET)
		return_null (API, API->error);

	S_obj = API->object[item];	/* Use S_obj as shorthand */
	if (S_obj->status != GMT_IS_UNUSED) { /* Already read this resource before; are we allowed to re-read? */
		if (S_obj->method == GMT_IS_STREAM || S_obj->method == GMT_IS_FDESC)
			return_null (API, GMT_READ_ONCE); /* Not allowed to re-read streams */
		if (!(mode & GMT_IO_RESET)) return_null (API, GMT_READ_ONCE);	/* Not authorized to re-read */
	}

	/* Passed sanity and allowed to read */

	switch (S_obj->method) {	/* File, array, stream etc ? */
		case GMT_IS_FILE:
			/* gmtapi_read_vector will report where it is reading from if level is GMT_MSG_INFORMATION */
			GMT_Report (API, GMT_MSG_INFORMATION, "Reading VECTOR from %s %s\n", gmtapi_method (S_obj->method), S_obj->filename);
			if ((V_obj = gmtapi_read_vector (GMT, S_obj->filename, S_obj->method, mode)) == NULL)
				return_null (API, GMT_DATA_READ_ERROR);
			S_obj->resource = V_obj;	/* Retain pointer to the allocated data so we use garbage collection later */
			break;
		case GMT_IS_STREAM:
 			/* gmtapi_read_vector will report where it is reading from if level is GMT_MSG_INFORMATION */
			kind = (S_obj->fp == GMT->session.std[GMT_IN]) ? 0 : 1;	/* For message only: 0 if stdin, 1 otherwise for user pointer */
			GMT_Report (API, GMT_MSG_INFORMATION, "Reading VECTOR from %s %s stream\n", gmtapi_method (S_obj->method), GMT_stream[kind]);
			if ((V_obj = gmtapi_read_vector (GMT, S_obj->fp, S_obj->method, mode)) == NULL)
				return_null (API, GMT_DATA_READ_ERROR);
			S_obj->resource = V_obj;	/* Retain pointer to the allocated data so we use garbage collection later */
			break;
		case GMT_IS_FDESC:
			/* gmtapi_read_vector will report where it is reading from if level is GMT_MSG_INFORMATION */
			kind = (*((int *)S_obj->fp) == GMT_IN) ? 0 : 1;	/* For message only: 0 if stdin, 1 otherwise for user pointer */
			GMT_Report (API, GMT_MSG_INFORMATION, "Reading VECTOR from %s %s stream\n", gmtapi_method (S_obj->method), GMT_stream[kind]);
			if ((V_obj = gmtapi_read_vector (GMT, S_obj->fp, S_obj->method, mode)) == NULL)
				return_null (API, GMT_CPT_READ_ERROR);
			S_obj->resource = V_obj;	/* Retain pointer to the allocated data so we use garbage collection later */
			break;
		case GMT_IS_DUPLICATE:	/* Duplicate the input VECTOR */
			GMT_Report (API, GMT_MSG_INFORMATION, "Duplicating VECTOR from VECTOR memory location\n");
			if ((V_orig = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			if ((V_obj = GMT_Duplicate_Data (API, GMT_IS_VECTOR, mode, V_orig)))
				return_null (API, GMT_MEMORY_ERROR);
			break;
		case GMT_IS_REFERENCE:	/* Just pass memory location, so nothing is allocated */
			GMT_Report (API, GMT_MSG_INFORMATION, "Referencing VECTOR from VECTOR memory location\n");
			if ((V_obj = S_obj->resource) == NULL) return_null (API, GMT_PTR_IS_NULL);
			break;
		default:	/* Barking up the wrong tree here... */
			GMT_Report (API, GMT_MSG_ERROR, "Wrong method used to import VECTOR\n");
			return_null (API, GMT_NOT_A_VALID_METHOD);
			break;
	}
	S_obj->status = GMT_IS_USED;	/* Mark as read */
	return (V_obj);	/* Pass back the vector */
}

/*! . */
GMT_LOCAL void *gmtapi_import_data (struct GMTAPI_CTRL *API, enum GMT_enum_family family, int object_ID, unsigned int mode, void *data) {

	/* Function that will import the data object referred to by the object_ID (or all registered inputs if object_ID == GMT_NOTSET).
	 * This is a wrapper functions for CPT, Dataset, Grid, Image and PostScript imports; see the specific functions
	 * for details on the arguments, in particular the mode setting (or see the GMT API documentation).
	 */
	int item, flag = GMT_NOTSET;
	void *new_obj = NULL;

	if (API == NULL) return_null (API, GMT_NOT_A_SESSION);			/* GMT_Create_Session has not been called */
	if (!API->registered[GMT_IN]) return_null (API, GMT_NO_INPUT);		/* No sources registered yet */

	/* Get information about this resource first */
	if (multiple_files_ok (family)) {
		flag = (API->module_input) ? GMTAPI_MODULE_INPUT : GMTAPI_OPTION_INPUT;
	}
	if ((item = gmtlib_validate_id (API, family, object_ID, GMT_IN, flag)) == GMT_NOTSET) return_null (API, API->error);

	switch (family) {
		case GMT_IS_PALETTE:
			new_obj = gmtapi_import_palette (API, object_ID, mode);		/* Try to import a CPT */
			break;
		case GMT_IS_DATASET:
			new_obj = gmtapi_import_dataset (API, object_ID, mode);		/* Try to import data tables */
			break;
		case GMT_IS_GRID:
			new_obj = gmtapi_import_grid (API, object_ID, mode, data);	/* Try to import a grid */
			break;
		case GMT_IS_IMAGE:
			new_obj = gmtapi_import_image (API, object_ID, mode, data);	/* Try to import an image */
			break;
		case GMT_IS_CUBE:
			new_obj = gmtapi_import_cube (API, object_ID, mode, data);	/* Try to import a 3-D cube */
			break;
		case GMT_IS_MATRIX:
			new_obj = gmtapi_import_matrix (API, object_ID, mode);		/* Try to import a matrix */
			break;
		case GMT_IS_VECTOR:
			new_obj = gmtapi_import_vector (API, object_ID, mode);		/* Try to import a vector */
			break;
		case GMT_IS_POSTSCRIPT:
			new_obj = gmtapi_import_postscript (API, object_ID, mode);	/* Try to import PS */
			break;
		default:
			API->error = GMT_NOT_A_VALID_FAMILY;
			break;
	}
	if (new_obj == NULL) return_null (API, API->error);	/* Return NULL as something went wrong */
	return (new_obj);	/* Successful, return pointer */
}

/*! . */
GMT_LOCAL void *gmtapi_get_data (void *V_API, int object_ID, unsigned int mode, void *data) {
	/* Function to import registered data sources directly into program memory as a set (not record-by-record).
	 * data is pointer to an existing grid container when we read a grid in two steps, otherwise use NULL.
	 * ID is the registered resource from which to import.
	 * Return: Pointer to data container, or NULL if there were errors (passed back via API->error).
	 */
	int item, family, flag = GMT_NOTSET;
	bool was_enabled;
	void *new_obj = NULL;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION);

	/* Determine the item in the object list that matches this ID and direction */
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;
	if (object_ID == GMT_NOTSET) {	/* Must pick up the family from the shelf */
		family = API->shelf;
		API->shelf = GMT_NOTSET;
		if (multiple_files_ok(family)) flag = (API->module_input) ? GMTAPI_MODULE_INPUT : GMTAPI_OPTION_INPUT;
	}
	else
		family = GMT_NOTSET;
	if ((item = gmtlib_validate_id (API, family, object_ID, GMT_IN, flag)) == GMT_NOTSET) {
		return_null (API, API->error);
	}

	was_enabled = API->io_enabled[GMT_IN];
	if (!was_enabled && gmtapi_begin_io (API, GMT_IN) != GMT_NOERROR) {	/* Enables data input if not already set and sets access mode */
		return_null (API, API->error);
	}
	S_obj = API->object[item];	/* Short-hand */
	S_obj->selected = true;	/* Make sure it the requested data set is selected */

	/* OK, try to do the importing */
	if ((new_obj = gmtapi_import_data (API, S_obj->family, object_ID, mode, data)) == NULL) {
		return_null (API, API->error);
	}

	if (!was_enabled && GMT_End_IO (API, GMT_IN, 0) != GMT_NOERROR) {	/* Disables data input if we had to set it in this function */
		return_null (API, API->error);
	}
#ifdef DEBUG
	gmtapi_set_object (API, S_obj);
	//gmtapi_list_objects (API, "gmtapi_get_data");
#endif

	/* 4GMT.jl For start, copy only 64 columns instead of the GMT_MAX_COLUMNS
	   Don't change anything here without consulting with GMT.jl side
	*/
	gmt_M_memcpy (&API->jl_pocket.col_type[0], &API->GMT->current.io.col_type[0], 64, int);
	gmt_M_memcpy (&API->jl_pocket.col_type[1], &API->GMT->current.io.col_type[1], 64, int);

	return (new_obj);		/* Return pointer to the data container */
}

GMT_LOCAL void gmtapi_reconsider_messenger (struct GMTAPI_CTRL *API, struct GMTAPI_DATA_OBJECT *S_obj) {
	/* A messenger is a dummy container with no memory allocated that is there to tell a
	 * module that it can be deleted to make space for an actual container with output data.
	 * However, for GMT_IS_MATRIX and GMT_IS_VECTOR output we will need to check if user supplied
	 * actual output memory.  For this to be true we need (a) non-NULL vectors/matrix and (b) known
	 * dimension(s).  If we pass those tests then we set the messenger flag to false.
	 */
	gmt_M_unused(API);
	if (S_obj->messenger == false) return;	/* Nothing to ponder */
	if (S_obj->actual_family == GMT_IS_VECTOR) {	/* Examine a vector container */
		struct GMT_VECTOR *V = S_obj->resource;
		if (V == NULL) return;
		if (V->n_rows == 0) return;
		for (unsigned int col = 0; col < V->n_columns; col++)
			if (V->data[col].f8 == NULL) return;	/* Any of the actual members could be used here */
	}
	else if (S_obj->actual_family == GMT_IS_MATRIX) {	/* Examine a matrix container */
		struct GMT_MATRIX *M = S_obj->resource;
		if (M == NULL) return;
		if (M->n_rows == 0 || M->n_columns == 0) return;
		if (M->data.f8 == NULL) return;	/* Any of the actual members could be used here */
	}
	else	/* Wrong container */
		return;
	/* Here we need to shoot the messenger */
	S_obj->messenger = false;
}

/*! . */
GMT_LOCAL int gmtapi_export_data (struct GMTAPI_CTRL *API, enum GMT_enum_family family, int object_ID, unsigned int mode, void *data) {
	/* Function that will export the single data object referred to by the object_ID as registered by GMT_Register_IO.
	 */
	int error, item;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;

	if (API == NULL) return (GMT_NOT_A_SESSION);	/* GMT_Create_Session has not been called */
	if (data == NULL) return (GMT_PTR_IS_NULL);		/* Got a NULL data pointer */
	if (!API->registered[GMT_OUT]) return (gmtlib_report_error (API, GMT_NO_OUTPUT));		/* No destination registered yet */

	/* Get information about this resource first */
	if ((item = gmtlib_validate_id (API, family, object_ID, GMT_OUT, GMT_NOTSET)) == GMT_NOTSET) return (gmtlib_report_error (API, API->error));

	S_obj = API->object[item];	/* The current object we are trying to export */
	/* The case where object_ID is not set but a virtual (memory) file is found is a special case: we must supply the correct object_ID */
	if (object_ID == GMT_NOTSET && item && S_obj->method != GMT_IS_FILE)
		object_ID = S_obj->ID;	/* Found virtual file; set actual object_ID */

	/* Check if this is a container passed from the outside to capture output */
	gmtapi_reconsider_messenger (API, S_obj);	/* This may set S_obj->messenger to false in some cases */
	if (S_obj->messenger && S_obj->resource) {	/* Need to destroy the dummy container before passing data out */
		error = gmtapi_destroy_data_ptr (API, S_obj->actual_family, S_obj->resource);	/* Do the dirty deed */
		if (error) return error;
		GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_export_data: Messenger dummy output container for object %d [item %d] freed and set resource=data=NULL\n", S_obj->ID, item);
		S_obj->resource  = NULL;	/* Since we now have nothing */
		S_obj->messenger = false;	/* OK, now clean for output */
	}

#ifdef DEBUG
	//gmtapi_list_objects (API, "gmtapi_export_data-in");
#endif
	/* PW Note: Important that any exporter needing to create memory to hold an output
	 * that will be returned to the caller: Never create/duplicate with the API functions
	 * as these add memory registrations and thus leads to duplicate entries in the objects
	 * table.  Symptoms of this are memory junk back in the calling program because two objects
	 * have a pointer to the same memory and one of them is destroyed, messing up the other.
	 * Use the gmtlib functions like gmt_duplicate_grid, etc for these purposes herein. */

	switch (family) {
		case GMT_IS_PALETTE:	/* Export a CPT */
			error = gmtapi_export_palette (API, object_ID, mode, data);
			break;
		case GMT_IS_DATASET:	/* Export a Data set */
			error = gmtapi_export_dataset (API, object_ID, mode, data);
			break;
		case GMT_IS_GRID:	/* Export a GMT grid */
			error = gmtapi_export_grid (API, object_ID, mode, data);
			break;
		case GMT_IS_IMAGE:	/* Export a GMT image */
			error = gmtapi_export_image (API, object_ID, mode, data);
			break;
		case GMT_IS_CUBE:	/* Export a GMT cube */
			error = gmtapi_export_cube (API, object_ID, mode, data);
			break;
		case GMT_IS_POSTSCRIPT:	/* Export PS */
			error = gmtapi_export_postscript (API, object_ID, mode, data);
			break;
		case GMT_IS_MATRIX:	/* Export MATRIX */
			error = gmtapi_export_matrix (API, object_ID, mode, data);
			break;
		case GMT_IS_VECTOR:	/* Export VECTOR */
			error = gmtapi_export_vector (API, object_ID, mode, data);
			break;
		default:
			error = GMT_NOT_A_VALID_FAMILY;
			break;
	}
#ifdef DEBUG
	//gmtapi_list_objects (API, "gmtapi_export_data-out");
#endif
	return (gmtlib_report_error (API, error));	/* Return status */
}

/*! . */
GMT_LOCAL int gmtapi_put_data (void *V_API, int object_ID, unsigned int mode, void *data) {
	/* Function to write data directly from program memory as a set (not record-by-record).
	 * We can combine the <register resource - export resource > sequence in
	 * one combined function.  See GMT_Register_IO for details on arguments.
	 * Here, *data is the pointer to the data object to save (CPT, dataset, Grid)
	 * ID is the registered destination.
	 * While only one output destination is allowed, for DATASETS one can
	 * have the tables and even segments be written to individual files (see the mode
	 * description in the documentation for how to enable this feature.)
	 * Return: false if all is well, true if there was an error (and set API->error).
	 */
	int item;
	bool was_enabled;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (data == NULL) return_error (V_API, GMT_PTR_IS_NULL);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;

	/* Determine the item in the object list that matches this ID and direction */
	if ((item = gmtlib_validate_id (API, GMT_NOTSET, object_ID, GMT_OUT, GMT_NOTSET)) == GMT_NOTSET) return_error (API, API->error);

	was_enabled = API->io_enabled[GMT_OUT];
	if (!was_enabled && gmtapi_begin_io (API, GMT_OUT) != GMT_NOERROR) {	/* Enables data output if not already set and sets access mode */
		return_error (API, API->error);
	}
	S_obj = API->object[item];	/* The current object we are trying to export */
	if (gmtapi_export_data (API, S_obj->family, object_ID, mode, data) != GMT_NOERROR) return_error (API, API->error);

	if (!was_enabled && GMT_End_IO (API, GMT_OUT, 0) != GMT_NOERROR) {	/* Disables data output if we had to set it in this function */
		return_error (API, API->error);
	}
#ifdef DEBUG
	gmtapi_set_object (API, S_obj);
	//gmtapi_list_objects (API, "gmtapi_put_data");
#endif
	return (GMT_NOERROR);	/* No error encountered */
}

/*! See if this file has already been registered and used.  If so, do not add it again */
GMT_LOCAL bool gmtapi_not_used (struct GMTAPI_CTRL *API, char *name) {
	unsigned int item = 0;
	bool not_used = true;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	while (item < API->n_objects && not_used) {
		if ((S_obj = API->object[item]) == NULL) continue;	/* Skip NULLs */
			if (S_obj->direction == GMT_IN && S_obj->status != GMT_IS_UNUSED && S_obj->filename && !strcmp (S_obj->filename, name))
			/* Used resource with same name */
			not_used = false;	/* Got item with same name, but used */
		else
			item++;	/* No, keep looking */
	}
	return (not_used);
}

/*! . */
GMT_LOCAL int gmtapi_init_import (struct GMTAPI_CTRL *API, enum GMT_enum_family family, unsigned int geometry, unsigned int mode, struct GMT_OPTION *head) {
	/* Handle registration of data files given with option arguments and/or stdin as input sources.
	 * These are the possible actions taken:
	 * 1. If (mode | GMT_ADD_FILES_IF_NONE) is true and NO resources have previously been registered, then we scan the option list for files (option == '<' (input)).
	 *    For each file found we register the item as a resource.
	 * 2. If (mode | GMT_ADD_FILES_ALWAYS) is true then we always scan the option list for files (option == '<' (input)).
	 *    For each file found we register the item as a resource.
	 * 3. If (mode & GMT_ADD_STDIO_IF_NONE) is true we will register stdin as an input source only if there are NO input items registered.
	 * 4. If (mode & GMT_ADD_STDIO_ALWAYS) is true we will register stdin as an input source, regardless of other items already registered.
	 */

	int object_ID, first_ID = GMT_NOTSET, item;
 	unsigned int n_reg = 0;
	struct GMT_OPTION *current = NULL;
	double *wesn = NULL;

	API->error = GMT_NOERROR;

	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_init_import: Passed family = %s and geometry = %s\n", GMT_family[family], GMT_geometry[gmtapi_gmtry(geometry)]);

	if (mode & GMT_ADD_EXISTING)
		n_reg = gmtapi_add_existing (API, family, geometry, GMT_IN, &first_ID);

	if ((mode & GMT_ADD_FILES_ALWAYS) || ((mode & GMT_ADD_FILES_IF_NONE))) {	/* Wish to register all command-line file args as sources */
		current = head;
		while (current) {	/* Loop over the list and look for input files */
			if (current->option == GMT_OPT_INFILE && gmtapi_not_used (API, current->arg)) {	/* File given, register it if has not already been used */
				if (geometry == GMT_IS_SURFACE) {	/* Grids and images may require a subset */
					if (API->GMT->common.R.active[RSET]) {	/* Global subset may have been specified (it might also match the grid/image domain) */
						wesn = gmt_M_memory (API->GMT, NULL, 4U, double);
						gmt_M_memcpy (wesn, API->GMT->common.R.wesn, 4U, double);
					}
				}
				if ((object_ID = GMT_Register_IO (API, family|GMT_VIA_MODULE_INPUT, GMT_IS_FILE, geometry, GMT_IN, wesn, current->arg)) == GMT_NOTSET) {
					gmt_M_free (API->GMT, wesn);
					return_value (API, API->error, GMT_NOTSET);	/* Failure to register */
				}
				n_reg++;	/* Count of new items registered */
				if (API->GMT->common.R.active[RSET]) gmt_M_free (API->GMT, wesn);
				if (first_ID == GMT_NOTSET) first_ID = object_ID;	/* Found our first ID */
				if ((item = gmtlib_validate_id (API, family, object_ID, GMT_IN, GMTAPI_MODULE_INPUT)) == GMT_NOTSET)
					return_value (API, API->error, GMT_NOTSET);	/* Some internal error... */
				API->object[item]->selected = true;	/* We will use this variable to find the files to read later */
			}
			current = current->next;	/* Go to next option */
		}
		GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_init_import: Added %d new sources\n", n_reg);
	}

	/* Note that n_reg can have changed if we added file args above */

	if ((mode & GMT_ADD_STDIO_ALWAYS) || ((mode & GMT_ADD_STDIO_IF_NONE) && n_reg == 0)) {	/* Wish to register stdin pointer as a source */
		if ((object_ID = GMT_Register_IO (API, family|GMT_VIA_MODULE_INPUT, GMT_IS_STREAM, geometry, GMT_IN, NULL, API->GMT->session.std[GMT_IN])) == GMT_NOTSET)
			return_value (API, API->error, GMT_NOTSET);	/* Failure to register stdin */
		n_reg++;		/* Add the single item */
		if (first_ID == GMT_NOTSET) first_ID = object_ID;	/* Found our first ID */
		if ((item = gmtlib_validate_id (API, family, object_ID, GMT_IN, GMTAPI_MODULE_INPUT)) == GMT_NOTSET)
			return_value (API, API->error, GMT_NOTSET);	/* Some internal error... */
		API->object[item]->selected = true;	/* We will use this variable to find stdin to read from later */
		GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_init_import: Added stdin to registered sources\n");
	}
	if (geometry == GMT_IS_TEXT)
		API->GMT->current.io.trailing_text[GMT_IN] = true;
	return (first_ID);
}

/*! . */
GMT_LOCAL int gmtapi_init_export (struct GMTAPI_CTRL *API, enum GMT_enum_family family, unsigned int geometry, unsigned int mode, struct GMT_OPTION *head) {
	/* Handle registration of output file given with option arguments and/or stdout as output destinations.
	 * Only a single output may be considered.  These are the possible actions taken:
	 * 1. If (mode | GMT_ADD_FILES_IF_NONE) is true and NO destinations have previously been registered,
	 *    then we scan the option list for files (option == '>' (output)).
	 *    Only one file can be registered as a destination; finding more than one results in an error.
	 * 2. If (mode | GMT_ADD_FILES_ALWAYS) is true then we always scan the option list for files (option == '>' (output)).
	 *    Only one file can be registered as a destination; finding more than one results in an error.
	 * 3. If (mode & GMT_ADD_STDIO_IF_NONE) is true we will register stdout as the only destination if there is NO output item registered.
	 * 4. If (mode & GMT_ADD_STDIO_ALWAYS) is true we will register stdout as an destination,
	 *    and give error if other output items have already been registered.
	 */

	unsigned int n_reg = 0;
	int object_ID = GMT_NOTSET, item;
	struct GMT_OPTION *current = NULL, *out_item = NULL;

	API->error = GMT_NOERROR;

	GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_init_export: Passed family = %s and geometry = %s\n", GMT_family[family], GMT_geometry[gmtapi_gmtry(geometry)]);

	if (mode & GMT_ADD_EXISTING)
		n_reg = gmtapi_add_existing (API, family, geometry, GMT_OUT, &object_ID);
	if (n_reg > 1) return_value (API, GMT_ONLY_ONE_ALLOWED, GMT_NOTSET);	/* Only one output allowed */

	if ((mode & GMT_ADD_FILES_ALWAYS) || (mode & GMT_ADD_FILES_IF_NONE)) {	/* Wish to register a single output file arg as destination */
		current = head;
		while (current) {	/* Loop over the list and look for output files */
			if (current->option == GMT_OPT_OUTFILE) {	/* Output file given */
				n_reg++;	/* Count it */
				out_item = current;	/* Remember which one it was for later */
			}
			current = current->next;				/* Go to next option */
		}
		if (n_reg > 1) return_value (API, GMT_ONLY_ONE_ALLOWED, GMT_NOTSET);	/* Only one output allowed */

		if (n_reg == 1 && out_item) {	/* Register the single output file found above */
			if ((object_ID = GMT_Register_IO (API, family, GMT_IS_FILE, geometry, GMT_OUT, NULL, out_item->arg)) == GMT_NOTSET)
				return_value (API, API->error, GMT_NOTSET);	/* Failure to register */
			if ((item = gmtlib_validate_id (API, family, object_ID, GMT_OUT, GMT_NOTSET)) == GMT_NOTSET)
				return_value (API, API->error, GMT_NOTSET);	/* Some internal error... */
			API->object[item]->selected = true;
			GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_init_export: Added 1 new destination\n");
		}
	}
	/* Note that n_reg may have changed if we added file arg */

	if ((mode & GMT_ADD_STDIO_ALWAYS) && n_reg == 1)
		return_value (API, GMT_ONLY_ONE_ALLOWED, GMT_NOTSET);	/* Only one output destination allowed at once */

	if (n_reg == 0 && ((mode & GMT_ADD_STDIO_ALWAYS) || (mode & GMT_ADD_STDIO_IF_NONE))) {	/* Wish to register stdout pointer as a destination */
		if ((object_ID = GMT_Register_IO (API, family, GMT_IS_STREAM, geometry, GMT_OUT, NULL, API->GMT->session.std[GMT_OUT])) == GMT_NOTSET)
			return_value (API, API->error, GMT_NOTSET);	/* Failure to register stdout?*/
		if ((item = gmtlib_validate_id (API, family, object_ID, GMT_OUT, GMT_NOTSET)) == GMT_NOTSET)
			return_value (API, API->error, GMT_NOTSET);	/* Some internal error... */
		API->object[item]->selected = true;
		GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_init_export: Added stdout to registered destinations\n");
		n_reg = 1;	/* Only have one item */
	}
	if (n_reg == 0) return_value (API, GMT_OUTPUT_NOT_SET, GMT_NOTSET);	/* No output set */
	return (object_ID);
}

/*! . */
GMT_LOCAL int gmtapi_destroy_image (struct GMTAPI_CTRL *API, struct GMT_IMAGE **I_obj) {
	/* Delete the given image resource */
	struct GMT_IMAGE_HIDDEN  *IH = NULL;
	if (!(*I_obj)) {	/* Probably not a good sign */
		GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_destroy_image: Passed NULL pointer - skipped\n");
		return (GMT_PTR_IS_NULL);
	}
	IH = gmt_get_I_hidden (*I_obj);
	if (IH->alloc_level != API->GMT->hidden.func_level) return (GMT_FREE_WRONG_LEVEL);	/* Not the right level */

	gmtlib_free_image (API->GMT, I_obj, true);
	return GMT_NOERROR;
}

/*! . */
GMT_LOCAL struct GMTAPI_DATA_OBJECT * gmtapi_make_dataobject (struct GMTAPI_CTRL *API, enum GMT_enum_family family, unsigned int method, unsigned int geometry, void *resource, unsigned int direction) {
	/* Simply the creation and initialization of this DATA_OBJECT structure */
	struct GMTAPI_DATA_OBJECT *S_obj = gmt_M_memory (API->GMT, NULL, 1, struct GMTAPI_DATA_OBJECT);

	if (S_obj == NULL) return NULL;
	S_obj->family    = S_obj->actual_family = family;	/* At creation we are all equal */
	S_obj->method    = method;
	S_obj->geometry  = geometry;
	S_obj->resource  = resource;
	S_obj->direction = direction;

	return (S_obj);
}

/*! . */
GMT_LOCAL int gmtapi_colors2cpt (struct GMTAPI_CTRL *API, char **str, unsigned int *mode) {
	/* Take comma-separated color entries given in lieu of a file and build a linear, discrete CPT.
	 * This may be converted to a continuous CPT if -Z is used by makecpt/grd2cpt.
	 * We check if a color is valid then write the given entries verbatim to the temp file.
	 * If a soft hinge has been requested then we must add flag to temp file and pass hinge on via filename.
	 * Returns GMT_NOTSET on error, 0 if no CPT is created (str presumably held a CPT name) and 1 otherwise.
	*/
	unsigned int pos = 0;
	int z = 0, n;
	char *pch = NULL, color[GMT_LEN256] = {""}, tmp_file[GMT_LEN64] = "", *h = NULL;
	double rgb[4] = {0.0, 0.0, 0.0, 0.0};
	FILE *fp = NULL;

	if (!(pch = strchr (*str, ','))) {	/* No comma so presumably a regular CPT name, but check for single color entry */
		bool gray = true;
		size_t k;
		const size_t s_length = strlen(*str);
		 /* Since "gray" is both a master CPT and a shade we must let the CPT take precedence */
		if (!strcmp (*str, "gray"))
			return (0);
		/* Because gmtlib_is_color cannot uniquely determine what a single number is, check for that separately first. */
		for (k = 0; gray && k < s_length; k++)
			if (!isdigit ((*str)[k])) gray = false;	/* Not just a bunch of integers */
		if (gray) {	/* Must also rule out temporary files like 14334.cpt since the GMT_CPT_EXTENSION is not present */
			snprintf (tmp_file, GMT_LEN64, "%s%s", *str, GMT_CPT_EXTENSION);	/* Try this as a filename */
			if (!gmt_access (API->GMT, tmp_file, F_OK))
				return 0;	/* Probably a process id temp file like 13223.cpt */
		}
		if (!gray && !gmtlib_is_color (API->GMT, *str))	/* Not a single color/shade, skip */
			return (0);
	}

	/* OK, here we need to create the temporary palette file */
	snprintf (tmp_file, GMT_LEN64, "gmtapi_colors2cpt_%d.cpt", (int)getpid());
	if ((fp = fopen (tmp_file, "w")) == NULL) {
		GMT_Report (API, GMT_MSG_ERROR, "Unable to open file %s file for writing\n", tmp_file);
		return (GMT_NOTSET);
	}
	fprintf (fp, "# COLOR_LIST\n");	/* Flag that we are building a CPT from a list of discrete colors */

	if ((h = strstr (*str, "+h"))) /* See if we got a hinge modifier */
		h[0] = '\0'; /* Hide hinge modifier */

	n = gmt_count_char (API->GMT, *str, ',') + 1;	/* Number of colors */
	if (n && (*str)[strlen(*str)-1] == ',') n--;	/* Stray trailing comma with no color ignored */
	if (h) {	/* Check if number of colors is odd when a hinge is specified */
		if (n%2) {	/* Should be OK */
			fprintf (fp, "# SOFT_HINGE\n");	/* Flag that we are building a CPT with a soft hinge */
			z = -(n - 1)/2;	/* Start z value for CPT centered on 0 */
		}
		else {
			GMT_Report (API, GMT_MSG_ERROR, "Cannot build soft-hinge CPT from an even number of colors\n");
			return (GMT_NOTSET);
		}
	}

	if ((*mode) & GMT_CPT_CONTINUOUS) {	/* Make a continuous cpt from the colors */
		char last_color[GMT_LEN256] = {""};
		if (!gmt_strtok (*str, ",", &pos, last_color)) {	/* Get 1st color entry */
			GMT_Report (API, GMT_MSG_ERROR, "Unable to find 1st color entry in: %s\n", *str);
			fclose (fp);
			return (GMT_NOTSET);
		}
		if (gmt_getrgb (API->GMT, last_color, rgb)) {
			GMT_Report (API, GMT_MSG_ERROR, "Badly formatted color entry: %s\n", color);
			fclose (fp);
			return (GMT_NOTSET);
		}
		while (gmt_strtok (*str, ",", &pos, color) && color[0]) {	/* Get color entries */
			if (gmt_getrgb (API->GMT, color, rgb)) {
				GMT_Report (API, GMT_MSG_ERROR, "Badly formatted color entry: %s\n", color);
				fclose (fp);
				return (GMT_NOTSET);
			}
			fprintf (fp, "%d\t%s\t%d\t%s\n", z, last_color, z+1, color);
			strncpy (last_color, color, GMT_LEN256-1);
			z++;	/* Increment z-slice values */
		}
		*mode -= GMT_CPT_CONTINUOUS;	/* Served its purpose */
		if (n == 1) {	/* Needed at least two colors to specify a ramp */
			GMT_Report (API, GMT_MSG_ERROR, "Cannot make a continuous color ramp from a single color: %s\n", *str);
			fclose (fp);
			return (GMT_NOTSET);
		}
	}
	else {
		while (gmt_strtok (*str, ",", &pos, color) && color[0]) {	/* Get color entries */
			if (gmt_getrgb (API->GMT, color, rgb)) {
				GMT_Report (API, GMT_MSG_ERROR, "Badly formatted color entry: %s\n", color);
				fclose (fp);
				return (GMT_NOTSET);
			}
			fprintf (fp, "%d\t%s\t%d\t%s\n", z, color, z+1, color);
			z++;	/* Increment z-slice values */
		}
	}
	fclose (fp);

	GMT_Report (API, GMT_MSG_DEBUG, "Converted %s to CPT %s\n", *str, tmp_file);

	if (h) { /* Restore hinge modifier and append it to filename */
		h[0] = '+';
		strcat (tmp_file, h);
	}
	gmt_M_str_free (*str);		/* Because it was allocated with strdup */
	*str = strdup (tmp_file);	/* Pass out the temp file name instead */

	return (1);	/* We replaced the name */
}

/*! . */
GMT_LOCAL int gmtapi_destroy_coord (struct GMTAPI_CTRL *API, double **ptr) {
	gmt_M_free (API->GMT, *ptr);
	return GMT_NOERROR;
}

/*! Also called in gmt_init.c and prototyped in gmt_internals.h: */
void gmtlib_garbage_collection (struct GMTAPI_CTRL *API, int level) {
	/* gmtlib_garbage_collection frees all registered memory associated with the
	 * current module level or for the entire session if level == GMT_NOTSET (-1). */

	unsigned int i, j, n_free = 0, u_level = 0;
	int error = GMT_NOERROR;
	void *address = NULL;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;

	if (API->n_objects == 0) return;	/* Nothing to do */

#ifdef DEBUG
	gmtapi_list_objects (API, "GMTAPI_Garbage_Collection entry");
#endif
	/* Free memory allocated during data registration (e.g., via GMT_Get|Put_Data).
	 * Because gmtlib_unregister_io will delete an object and shuffle
	 * the API->object array, reducing API->n_objects by one we must
	 * be aware that API->n_objects changes in the loop below, hence the while loop */

	i = n_free = 0;
	if (level != GMT_NOTSET) u_level = level;
	while (i < API->n_objects) {	/* While there are more objects to consider */
		S_obj = API->object[i];	/* Shorthand for the current object */
		if (!S_obj) {		/* Skip empty object [NOTE: Should not happen?] */
			GMT_Report (API, GMT_MSG_WARNING, "gmtlib_garbage_collection found empty object number %d [Bug?]\n", i++);
			continue;
		}
		if (!(level == GMT_NOTSET || S_obj->alloc_level == u_level)) {	/* Not the right module level (or not end of session yet) */
			if (S_obj->reset_pad && S_obj->no_longer_owner == false) {	/* Temporarily changed pad to access a sub-region of a memory grid - now reset this if still the owner */
				address = S_obj->resource;	/* Try to get the data object */
				gmtapi_contract_pad (API->GMT, address, S_obj->actual_family, S_obj->orig_pad, S_obj->orig_wesn);
				S_obj->reset_pad = 0;
			}
			i++;	continue;
		}
		if (S_obj->resource == NULL) {	/* No memory to free (probably freed earlier); handle trashing of empty object after this loop */
			i++;	continue;
		}
		if (level != GMT_NOTSET && S_obj->no_longer_owner) {	/* No memory to free since we passed it on; just NULL the pointers */
			S_obj->resource = NULL;				/* Since other objects own the data now */
			S_obj->alloc_level = u_level;			/* To ensure it will be Unregistered below */
			S_obj->alloc_mode = GMT_ALLOC_INTERNALLY;	/* To ensure it will be Unregistered below */
			i++;	continue;
		}
		/* Here we will try to free the memory pointed to by S_obj->resource|data */
		GMT_Report (API, GMT_MSG_DEBUG, "gmtlib_garbage_collection: Destroying object: C=%d A=%d ID=%d W=%s F=%s M=%s S=%s P=%" PRIxS " N=%s\n",
			S_obj->close_file, S_obj->alloc_mode, S_obj->ID, GMT_direction[S_obj->direction],
			GMT_family[S_obj->family], gmtapi_method (S_obj->method), GMT_status[S_obj->status&2],
			(size_t)S_obj->resource, S_obj->filename);
		if (S_obj->resource) {
			address = S_obj->resource;	/* Keep a record of what the address was (since S_obj->resource will be set to NULL when freed) */
			error = gmtapi_destroy_data_ptr (API, S_obj->actual_family, API->object[i]->resource);	/* Do the dirty deed */
		}

		if (error < 0) {	/* Failed to destroy this memory; that cannot be a good thing... */
			GMT_Report (API, GMT_MSG_WARNING, "gmtlib_garbage_collection failed to destroy memory for object % d [Bug?]\n", i++);
			/* Skip it for now; but this is possibly a fatal error [Bug]? */
		}
		else  {	/* Successfully freed.  See if this address occurs more than once (e.g., both for in and output); if so just set repeated data pointer to NULL */
			S_obj->resource = NULL;
			for (j = i; j < API->n_objects; j++) {
				if (API->object[j]->resource == address)
					API->object[j]->resource = NULL;	/* Yes, set to NULL so we don't try to free twice */
			}
			n_free++;	/* Number of freed n_objects; do not increment i since GMT_Destroy_Data shuffled the array */
		}
		i++;	/* Go to next object */
	}
 	if (n_free) GMT_Report (API, GMT_MSG_DEBUG, "GMTAPI_Garbage_Collection freed %d memory objects\n", n_free);

	/* Deallocate all remaining objects associated with NULL pointers (e.g., rec-by-rec i/o or those set to NULL above) set during this module (or session) */
	i = 0;
	while (i < API->n_objects) {	/* While there are more objects to consider */
		S_obj = API->object[i];	/* Shorthand for the current object */
		if (S_obj && (level == GMT_NOTSET || (S_obj->alloc_level == u_level)))	/* Yes, this object was added at this level, get rid of it; do not increment i */
			gmtlib_unregister_io (API, (int)S_obj->ID, (unsigned int)GMT_NOTSET);	/* This shuffles the object array and reduces n_objects */
		else
			i++;	/* Was allocated higher up, leave alone and go to next */
	}
#ifdef DEBUG
	gmtapi_list_objects (API, "GMTAPI_Garbage_Collection exit");
#endif
}

/*! Determine if file contains a netCDF directive to a specific variable, e.g., table.nc?time[2] */
GMT_LOCAL bool gmtapi_file_with_netcdf_directive (struct GMTAPI_CTRL *API, const char *file) {
	char *duplicate = NULL, *c = NULL;
	if (!strchr (file, '?')) return false;  /* No question mark found */
	duplicate = strdup (file);              /* Found a ?, duplicate this const char string and chop off the end */
	c = strchr (duplicate, '?');            /* Locate the location of ? again */
	if (c) c[0] = '\0';                     /* Chop off text from ? onwards */
	if (gmt_access (API->GMT, duplicate, F_OK)) {	/* No such file, presumably */
		free (duplicate);
		return false;
	}
	else {	/* Since the file exist we know it is a netCDF directive */
		free (duplicate);
		return true;
	}
}

/* Several lower-level API function are needed in a few other gmt_*.c library codes and are thus NOT local.
 * They are listed here and declared via EXTERN_MSC where they occur:
 *   gmtlib_report_error
 *   gmtlib_validate_id
 *   gmtlib_unregister_io
 *   gmtlib_count_objects
 *   gmtlib_close_grd
 *   gmtlib_create_header_item
 * If DEBUG is defined then these two are also accessible:
 *   gmtapi_list_objects
 *   gmtapi_set_object
 */

/*! ===>  Error message reporting */

int gmtlib_report_error (void *V_API, int error) {
	/* Write error message to log or stderr, then return error code back.
 	 * All functions can call this, even if API has not been initialized. */
	FILE *fp = NULL;
	bool report;
	char message[GMT_LEN256];
	struct GMTAPI_CTRL *API = gmtapi_get_api_ptr (V_API);

	report = (API) ? API->error != API->last_error : true;
	if (report && error != GMT_NOERROR) {	/* Report error */
		if (!API || !API->GMT || (fp = API->GMT->session.std[GMT_ERR]) == NULL) fp = stderr;
		if (API && API->session_tag) {
			snprintf (message, GMT_LEN256, "[Session %s (%d)]: Error returned from GMT API: %s (%d)\n",
				API->session_tag, API->session_ID, gmt_api_error_string[error], error);
			GMT_Message (API, GMT_TIME_NONE, message);
			if (API->log_level) fflush (fp);	/* Flush the latest message to file in case of crash */
		}
		else
			fprintf (fp, "Error returned from GMT API: %s (%d)\n", gmt_api_error_string[error], error);
	}
	if (API) API->last_error = API->error, API->error = error;	/* Update API error value if API exists */
	return (error);
}

/*! . */
int gmtlib_validate_id (struct GMTAPI_CTRL *API, int family, int object_ID, int direction, int module_input) {
	/* Checks to see if the given object_ID is listed and of the right direction.  If so
 	 * we return the item number; otherwise return GMT_NOTSET and set API->error to the error code.
	 * Note: int arguments MAY be GMT_NOTSET, hence we use signed ints.  If object_ID == GMT_NOTSET
	 * then we only look for DATASETS.  Note: module_input controls if we are being very specific
	 * about the type of input resource.  There are module inputs and option inputs. We have:
	 * module_input = GMT_NOTSET [-1]:	Do not use the resource's module_input status in determining the next ID.
	 * module_input = GMTAPI_OPTION_INPUT [0]:	Only validate resources with module_input = false.
	 * module_input = GMTAPI_MODULE_INPUT [1]:	Only validate resources with module_input = true.
	 * Finally, since we allow vectors and matrices to masquerade as DATASETs we check for this and
	 * re-baptize such objects to become GMT_IS_DATASETs. */
	unsigned int i;
	int item;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;

	API->error = GMT_NOERROR;

	 /* Search for the object in the active list.  However, if object_ID == GMT_NOTSET we pick the first in that direction */

	for (i = 0, item = GMT_NOTSET; item == GMT_NOTSET && i < API->n_objects; i++) {
		S_obj = API->object[i];	/* Shorthand only */
		if (!S_obj) continue;	/* Empty object, skip */
		if (direction != GMT_NOTSET && (int)S_obj->direction != direction) continue;	/* Not the requested direction */
		if (direction == GMT_IN && S_obj->status != GMT_IS_UNUSED && object_ID == GMT_NOTSET) continue;	/* Already used this input object once */
		/* Preliminary checks passed, no look at family */
		//if (!(family == GMT_NOTSET || (int)S_obj->family == family)) {		/* Not the required data type; check for exceptions... */
		if (family != GMT_NOTSET) {		/* Was specific about the family. */
			if (family == GMT_IS_GRID && S_obj->actual_family == GMT_IS_MATRIX && S_obj->family != GMT_IS_DATASET)
				S_obj->family = GMT_IS_GRID;	/* Matrix masquerading as grids is valid. Change the family here. */
			else if (family == GMT_IS_DATASET && (S_obj->actual_family == GMT_IS_VECTOR || S_obj->actual_family == GMT_IS_MATRIX) && !(S_obj->family == GMT_IS_GRID || S_obj->family == GMT_IS_IMAGE))
				S_obj->family = GMT_IS_DATASET;	/* Vectors or Matrix masquerading as dataset are valid. Change their family here. */
			else if (family != (int)S_obj->family)	/* We don't like your kind */
				continue;
		}
		if (object_ID == GMT_NOTSET && (int)S_obj->direction == direction) item = i;	/* Pick the first object with the specified direction */
		if (object_ID == GMT_NOTSET && !(S_obj->family == GMT_IS_DATASET)) continue;	/* Must be data/text-set */
		else if (direction == GMT_NOTSET && (int)S_obj->ID == object_ID) item = i;	/* Pick the requested object regardless of direction */
		else if ((int)S_obj->ID == object_ID) item = i;					/* Pick the requested object */
		if (item != GMT_NOTSET && direction == GMT_IN && module_input != GMT_NOTSET) {		/* Must check that object's module_input status matches */
			bool status = (module_input == GMTAPI_MODULE_INPUT) ? true : false;
			if (status != S_obj->module_input) item = GMT_NOTSET;	/* Not the right type of input resource */
		}
	}
	if (item == GMT_NOTSET) return_value (API, GMT_NOT_A_VALID_ID, GMT_NOTSET);		/* No such object found */

	/* OK, we found the object; is it the right kind (input or output)? */
	if (direction != GMT_NOTSET && (int)(API->object[item]->direction) != direction) {
		/* Passing an input object but it is listed as output, or vice versa */
		if (direction == GMT_IN)  return_value (API, GMT_NOT_INPUT_OBJECT, GMT_NOTSET);
		if (direction == GMT_OUT) return_value (API, GMT_NOT_OUTPUT_OBJECT, GMT_NOTSET);
	}
	/* Here we have been successful in finding the right object */
	return (item);
}

/*! . */
int gmtlib_unregister_io (struct GMTAPI_CTRL *API, int object_ID, unsigned int direction) {
	/* Remove specified object ID from active list of objects */
	int s_item;
	unsigned item;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;

	if (API == NULL) return (GMT_NOT_A_SESSION);		/* GMT_Create_Session has not been called */
	if (API->n_objects == 0) return (gmtlib_report_error (API, GMT_NO_RESOURCES));	/* There are no known resources yet */

	/* Check if this is a valid ID and matches the direction */
	if ((s_item = gmtlib_validate_id (API, GMT_NOTSET, object_ID, direction, GMT_NOTSET)) == GMT_NOTSET) return (gmtlib_report_error (API, API->error));

	/* OK, now it is safe to remove the object; item >= 0 */

	item = s_item;
	S_obj = API->object[item];	/* Short-hand */
	GMT_Report (API, GMT_MSG_DEBUG, "gmtlib_unregister_io: Unregistering object no %d [n_objects = %d]\n", S_obj->ID, API->n_objects-1);
 	if (S_obj->resource) GMT_Report (API, GMT_MSG_DEBUG, "gmtlib_unregister_io: Object no %d has non-NULL resource pointer\n", S_obj->ID);

	if (S_obj->method == GMT_IS_FILE) gmt_M_str_free (S_obj->filename);	/* Free any strdup-allocated filenames */
	gmt_M_free (API->GMT, S_obj);		/* Free the current data object */
	API->n_objects--;				/* Tally of how many data sets are left */
	while (item < API->n_objects) {
		API->object[item] = API->object[item+1];	/* Shuffle pointers down one entry */
		item++;
	}

	/* All active resources are found consecutively from 0 to (API->n_objects-1); those with status == 0 (GMT_IS_UNUSED) are available for use. */
	return GMT_NOERROR;
}

/*! . */
unsigned int gmtlib_count_objects (struct GMTAPI_CTRL *API, enum GMT_enum_family family, unsigned int geometry, unsigned int direction, int *first_ID) {
	/* Count how many data sets of the given family are currently registered and unused for the given direction (GMT_IN|GMT_OUT).
 	 * Also return the ID of the first unused data object for the given direction, geometry, and family (GMT_NOTSET if not found).
	 */
	unsigned int i, n;

	*first_ID = GMT_NOTSET;	/* Not found yet */
	for (i = n = 0; i < API->n_objects; i++) {
		if (!API->object[i]) continue;	 /* A freed object, skip it */
		if (API->object[i]->direction != (enum GMT_enum_std)direction) continue;	  /* Wrong direction */
		if (API->object[i]->geometry  != (enum GMT_enum_geometry)geometry) continue;	  /* Wrong geometry */
		if (API->object[i]->status    != GMT_IS_UNUSED) continue; /* Already used */
		if (family != API->object[i]->family) continue;		  /* Wrong data type */
		n++;	/* Found one that satisfied requirements */
		if (*first_ID == GMT_NOTSET) *first_ID = API->object[i]->ID;
	}
	return (n);
}

/*! . */
char * gmtlib_create_header_item (struct GMTAPI_CTRL *API, unsigned int mode, void *arg) {
	size_t lim;
	char *txt = (mode & GMT_COMMENT_IS_OPTION) ? GMT_Create_Cmd (API, arg) : (char *)arg;
	static char buffer[GMT_BUFSIZ];
	gmt_M_memset (buffer, GMT_BUFSIZ, char);
	if (mode & GMT_COMMENT_IS_TITLE) strcat (buffer, "  Title :");
	if (mode & GMT_COMMENT_IS_COMMAND) {
		strcat (buffer, " Command : gmt ");
		if (strlen(API->GMT->init.module_name) < 500)		/* 500, just to shut up a Coverity issue */
			strcat (buffer, API->GMT->init.module_name);
		strcat (buffer, " ");
	}
	if (mode & GMT_COMMENT_IS_REMARK) strcat (buffer, " Remark : ");
	if (mode & GMT_COMMENT_IS_MULTISEG) strcat (buffer, "> ");
	lim = GMT_BUFSIZ - strlen (buffer) - 1;	/* Max characters left */
	strncat (buffer, txt, lim);
	if (mode & GMT_COMMENT_IS_OPTION) gmt_M_free (API->GMT, txt);
	return (buffer);
}

/*! . */
void gmtlib_close_grd (struct GMT_CTRL *GMT, struct GMT_GRID *G) {
	struct GMT_GRID_HIDDEN *GH = gmt_get_G_hidden (G);
	struct GMT_GRID_ROWBYROW *R = gmtapi_get_rbr_ptr (GH->extra);	/* Shorthand to row-by-row book-keeping structure */
	gmt_M_free (GMT, R->v_row);
	if (GMT->session.grdformat[G->header->type][0] == 'c' || GMT->session.grdformat[G->header->type][0] == 'n')
		gmt_nc_close (GMT, R->fid);
	else
		gmt_fclose (GMT, R->fp);
	gmt_M_free (GMT, GH->extra);
}

/*========================================================================================================
 *          HERE ARE THE PUBLIC GMT API UTILITY FUNCTIONS, WITH THEIR FORTRAN BINDINGS
 *========================================================================================================
 */

/*! ===>  Create a new GMT Session */

void * GMT_Create_Session (const char *session, unsigned int pad, unsigned int mode, int (*print_func) (FILE *, const char *)) {
	/* Initializes the GMT API for a new session. This is typically called once in a program,
	 * but programs that manage many threads might call it several times to create as many
	 * sessions as needed. [Note: There is of yet no thread support built into the GMT API
	 * but you could still manage many sessions at once].
	 * The session argument is a textstring used when reporting errors or messages from activity
	 *   originating within this session.
	 * Pad sets the default number or rows/cols used for grid padding.  GMT uses 2; users of
	 *   the API may wish to use 0 if they have no need for BCs, etc.
	 * The mode argument is a bitflag that controls a few things [0, or GMT_SESSION_NORMAL]:
	 *   bit 1 (GMT_SESSION_NOEXIT)   means call return and not exit when returning from an error.
	 *   bit 2 (GMT_SESSION_EXTERNAL) means we are called by an external API (e.g., MATLAB, Python).
	 *   bit 3 (GMT_SESSION_COLMAJOR) means the external API uses column-major format (e.g., MATLAB, FORTRAN) [Default is row-major, i.e., C/C++, Python]
	 *   bit 4 (GMT_SESSION_LOGERRORS) means we redirect stderr to a log file whose name is the session string + log.
	 *   We reserve the right to add future flags.
	 * We return the pointer to the allocated API structure.
	 * If any error occurs we report the error, set the error code via API->error, and return NULL.
	 * We terminate each session with a call to GMT_Destroy_Session.
	 */

	struct GMTAPI_CTRL *API = NULL;
	size_t len;
	static char *unknown = "unknown";
	char *dir = NULL;
	/* Determine the width of the current terminal */
#ifdef WIN32
	CONSOLE_SCREEN_BUFFER_INFO csbi;
	GetConsoleScreenBufferInfo (GetStdHandle(STD_ERROR_HANDLE), &csbi);
	int n_columns = csbi.srWindow.Right;
#else
	struct winsize w;
	int err = 0, n_columns;
	if ((err = ioctl (STDERR_FILENO, TIOCGWINSZ, &w))) {
 		GMT_Report (API, GMT_MSG_DEBUG, "GMT_Create_Session: Unable to get terminal width via ioctl, err = %d\n", err);
	}
	n_columns = w.ws_col;
#endif

	if ((API = calloc (1, sizeof (struct GMTAPI_CTRL))) == NULL) return_null (NULL, GMT_MEMORY_ERROR);	/* Failed to allocate the structure */
	API->verbose = (mode >> GMT_MSG_BITSHIFT);	/* Pick up any -V settings from gmt.c */
	API->remote_id = GMT_NOTSET;     /* Not read a remote grid yet */
	API->pad = pad;     /* Preserve the default pad value for this session */
	API->print_func = (print_func == NULL) ? gmtapi_print_func : print_func;	/* Pointer to the print function to use in GMT_Message|Report */
	API->do_not_exit = mode & GMT_SESSION_NOEXIT;	/* Deprecated, we no longer call exit anywhere in the API (gmt_api.c) */
	API->external = (mode & GMT_SESSION_EXTERNAL) ? 1 : 0;  /* if false|0 then we don't list read and write as modules */
	if (API->external && mode & GMT_SESSION_NOGDALCLOSE) API->external = 2;  /* Avoid calling GDALDestroyDriverManager */
	API->shape = (mode & GMT_SESSION_COLMAJOR) ? GMT_IS_COL_FORMAT : GMT_IS_ROW_FORMAT;		/* if set then we must use column-major format [row-major] */
	API->runmode = mode & GMT_SESSION_RUNMODE;		/* If nonzero we set up modern GMT run-mode, else classic */
	API->no_history = mode & GMT_SESSION_NOHISTORY;		/* If nonzero we disable the gmt.history mechanism (shorthands) entirely */
	if (API->internal) API->leave_grid_scaled = 1;	/* Do NOT undo grid scaling after write since modules do not reuse grids we save some CPU */
    API->n_numerical_columns = GMT_NOTSET;
	if (session) {	/* Pick up a tag for this session */
		char *tmptag = strdup (session);
		API->session_tag = strdup (basename (tmptag));	/* Only used in reporting and error messages */
		gmt_M_str_free (tmptag);
	}

	if ((API->message = calloc (GMT_MSGSIZ, sizeof (char))) == NULL) {	/* Failed to allocate the message string */
	 	gmt_M_str_free (API);	/* Not gmt_M_free since this item was allocated before GMT was initialized */
		return_null (NULL, GMT_MEMORY_ERROR);
	}
	/* Ensure Windows terminal can handle UTF-8 characters */
#ifdef WIN32
	SetConsoleOutputCP (CP_UTF8);
#endif
	API->terminal_width = (n_columns > 24) ? n_columns : 100;	/* Character width of current terminal [100] */
	GMT_Report (API, GMT_MSG_DEBUG, "GMT_Create_Session: Terminal width = %d\n", API->terminal_width);
	/* Set temp directory used by GMT */

#ifdef WIN32
	if ((dir = getenv ("TEMP")))	/* Standard Windows temp directory designation */
		API->tmp_dir = strdup (dir);
	/* If not found we leave it NULL */
#else
	if ((dir = getenv ("TMPDIR")))	/* Alternate tmp dir for *nix */
		API->tmp_dir = strdup (dir);
	else	/* Set standard temporary directory under *nix */
		API->tmp_dir = strdup ("/tmp");
#endif
	if ((len = strlen (API->tmp_dir)) > 2 && API->tmp_dir[len-1] == '/') API->tmp_dir[len-1] = '\0';	/* Chop off trailing slash */
	API->session_name = gmtapi_get_ppid (API);		/* Save session name for the rest of the session */

	/* gmt_begin initializes, among other things, the settings in the user's (or the system's) gmt.conf file */
	if (gmt_begin (API, session, pad) == NULL) {		/* Initializing GMT and PSL machinery failed */
		gmt_M_str_free (API);	/* Free API */
		return_null (API, GMT_MEMORY_ERROR);
	}
	GMT_Report (API, GMT_MSG_DEBUG, "GMT_Create_Session initialized GMT structure\n");

	if (API->external && (mode & GMT_SESSION_BIN2LIBDIR)) {	/* Reset bindir to libdir. This is to be used from externals only */
		free(API->GMT->init.runtime_bindir);				/* where the bindir was set to the bin of launcher. In Julia it's julia/bin */
		API->GMT->init.runtime_bindir = strdup(API->GMT->init.runtime_libdir);	/* but the libdir is correctly set to the GMT shared lib dir */
	}

	if (mode & GMT_SESSION_LOGERRORS) {	/* Want to redirect errors to a log file */
		char file[PATH_MAX] = {""};
		FILE *fp = NULL;
		if (API->session_tag == NULL) {
			GMT_Report (API, GMT_MSG_DEBUG, "Must pass a session tag to be used for error log file name\n");
			return_null (API, GMT_ARG_IS_NULL);
		}
		snprintf (file, PATH_MAX, "%s.log", API->session_tag);
		if ((fp = fopen (file, "w")) == NULL) {
			GMT_Report (API, GMT_MSG_DEBUG, "Unable to open error log file %s\n", file);
			return_null (API, GMT_ERROR_ON_FOPEN);
		}
		API->GMT->session.std[GMT_ERR] = fp;	/* Set the error fp pointer */
		API->log_level = GMT_LOG_SET;
	}

	API->n_cores = gmtlib_get_num_processors();	/* Get number of available CPU cores */
	GMTAPI_index_function = gmtapi_get_index_from_TRS;	/* Default grid node lookup */

	/* Allocate memory to keep track of registered data resources */

	API->n_objects_alloc = GMT_SMALL_CHUNK;	/* Start small; this may grow as more resources are registered */
	if ((API->object = gmt_M_memory (API->GMT, NULL, API->n_objects_alloc, struct GMTAPI_DATA_OBJECT *)) == NULL) return NULL;

	/* Set the unique Session parameters */

	API->session_ID = GMTAPI_session_counter++;		/* Guarantees each session ID will be unique and sequential from 0 up */
	if (session)
		API->GMT->init.module_name = API->session_tag;	/* So non-modules can report name of program, */
	else
		API->GMT->init.module_name = unknown; /* or unknown */

	if (gmtapi_init_sharedlibs (API) < 0)				/* Count how many shared libraries we should know about, and get their names and paths */
		return_null (API, GMT_RUNTIME_ERROR);

	return (API);	/* Pass the structure back out */
}

#ifdef FORTRAN_API
/* FORTRAN binding [THESE MAY CHANGE ONCE WE ACTUALLY TRY TO USE THESE] */
struct GMTAPI_CTRL * GMT_Create_Session_ (const char *tag, unsigned int *pad, unsigned int *mode, void *print, int len) {
	/* FORTRAN version: We pass the hidden global GMT_FORTRAN structure */
	return (GMT_Create_Session (tag, *pad, *mode, print));
}
#endif

/*! ===>  Destroy a registered GMT Session */

int GMT_Destroy_Session (void *V_API) {
	/* GMT_Destroy_Session terminates the information for the specified session and frees all memory.
	 * Returns false if all is well and true if there were errors. */

	unsigned int i;
	char *module = NULL;
	struct GMTAPI_CTRL *API = gmtapi_get_api_ptr (V_API);

	if (API == NULL) return_error (API, GMT_NOT_A_SESSION);	/* GMT_Create_Session has not been called */
	API->error = GMT_NOERROR;

	GMT_Report (API, GMT_MSG_DEBUG, "Entering GMT_Destroy_Session\n");
	module = strdup (API->GMT->init.module_name);	/* Need a copy as the pointer to static memory in library will close soon */
	gmtlib_garbage_collection (API, GMT_NOTSET);	/* Free any remaining memory from data registration during the session */
	gmtapi_free_sharedlibs (API);			/* Close shared libraries and free list */
	API->GMT->init.module_name = module;		/* So GMT_Report will function after GMT_SUPPL_LIB_NAME.so shut down */

	/* Deallocate all remaining objects associated with NULL pointers (e.g., rec-by-rec i/o) */
	for (i = 0; i < API->n_objects; i++) gmtlib_unregister_io (API, (int)API->object[i]->ID, (unsigned int)GMT_NOTSET);
	gmt_M_free (API->GMT, API->object);
	if (API->GMT->session.std[GMT_ERR] != stderr)	/* Close the error log fp pointer */
		fclose (API->GMT->session.std[GMT_ERR]);
	gmt_end (API->GMT);	/* Terminate GMT machinery */
	gmt_M_str_free (API->session_tag);
	gmt_M_str_free (API->session_name);
	gmt_M_str_free (API->tmp_dir);
	gmt_M_str_free (API->session_dir);
	gmt_M_str_free (API->message);
	gmt_M_memset (API, 1U, struct GMTAPI_CTRL);	/* Wipe it clean first */
 	gmt_M_str_free (API);	/* Not gmt_M_free since this item was allocated before GMT was initialized */
 	gmt_M_str_free (module);

	return (GMT_NOERROR);
}

#ifdef FORTRAN_API
int GMT_Destroy_Session_ () {
	/* FORTRAN version: We pass the hidden global GMT_FORTRAN structure */
	return (GMT_Destroy_Session (GMT_FORTRAN));
}
#endif

/*! . */
GMT_LOCAL char gmtapi_debug_geometry_code (unsigned int geometry) {
	char c;
	switch (geometry) {
		case GMT_IS_POINT:	c = 'T'; break;
		case GMT_IS_LINE:	c = 'L'; break;
		case GMT_IS_POLY:	c = 'P'; break;
		case GMT_IS_LP:		c = 'C'; break;
		case GMT_IS_PLP:	c = 'A'; break;
		case GMT_IS_SURFACE: 	c = 'G'; break;
		case GMT_IS_VOLUME:	c = 'U'; break;
		case GMT_IS_NONE:	c = 'N'; break;
		case GMT_IS_TEXT:	c = 'X'; break;
		default:	 	c = '-'; break;
	}
	return c;
}

/*! . */
GMT_LOCAL int gmtapi_encode_id (struct GMTAPI_CTRL *API, unsigned int module_input, unsigned int direction, unsigned int family, unsigned int actual_family, unsigned int geometry, unsigned int messenger, int object_ID, char *filename) {
	/* Creates a virtual filename with the embedded object information .  Space for up to GMT_VF_LEN characters in filename must exist.
	 * Name template: @GMTAPI@-S-D-F-A-G-M-###### where # is the 6-digit integer object code.  Total length is 27 chars (GMTAPI_MEMFILE_LEN)
	 * S stands for P(rimary) or S(econdary) input or output object (command line is primary, files via options are secondary).
	 * D stands for Direction and is either I(n) or O(ut).
	 * F stands for Family and is one of D(ataset), G(rid), I(mage), C(PT), X(PostScript), M(atrix), V(ector), U(cube), -(undefined).
	 * A stands for Actual Family and is one of D, G, I, C, X, M, V, and U as well.
	 *   Actual family may differ from family if a Dataset is actually passed as a Matrix, for instance.
	 * G stands for Geometry and is one of (poin)T, L(ine), P(olygon), C(Line|Polygon), A(Point|Line|Polygon), G(rid), V(olume), N(one), X(text), or -(undefined).
	 * M stands for Messenger and is either Y(es) or N(o).
	 * Limitation:  object_ID must be <= GMTAPI_MAX_ID */

	if (API == NULL) return_error (API, GMT_NOT_A_SESSION);	/* GMT_Create_Session has not been called */
	if (!filename) return_error (API, GMT_MEMORY_ERROR);		/* Oops, cannot write to that variable */
	if (object_ID <= GMT_NOTSET) return_error (API, GMT_NOT_A_VALID_ID);	/* ID is not set yet */
	if (object_ID > GMTAPI_MAX_ID) return_error (API, GMT_ID_TOO_LARGE);	/* ID is too large to fit in %06d format below */
	if (!(direction == GMT_IN || direction == GMT_OUT)) return_error (API, GMT_NOT_A_VALID_DIRECTION);
	if (!gmtapi_valid_input_family (family))  return_error (API, GMT_NOT_A_VALID_FAMILY);
	if (!gmtapi_valid_actual_family (actual_family))  return_error (API, GMT_NOT_A_VALID_FAMILY);
	if (gmtapi_validate_geometry (API, family, geometry)) return_error (API, GMT_BAD_GEOMETRY);
	if (!(messenger == 0 || messenger == 1)) return_error (API, GMT_RUNTIME_ERROR);
	if (module_input) module_input = 1;	/* It may be GMT_VIA_MODULE_INPUT but here we want just 0 or 1 */

	gmt_M_memset (filename, GMT_VF_LEN, char);	/* Wipe any trace of previous text */
	sprintf (filename, "@GMTAPI@-%c-%c-%s-%s-%c-%c-%06d", (module_input) ? 'P' : 'S', (direction == GMT_IN) ? 'I' : 'O', GMT_family_abbrev[family], GMT_family_abbrev[actual_family], gmtapi_debug_geometry_code (geometry), (messenger) ? 'Y' : 'N', object_ID);
	GMT_Report (API, GMT_MSG_DEBUG, "VirtualFile name created: %s\n", filename);

	return_error (API, GMT_NOERROR);	/* No error encountered */
}

/* Data registration: The main reason for data registration is the following:
 * Unlike GMT 4, GMT 5 may be used as modules by another calling program.  In
 * that case, the input data file may not be a file but a memory location (i.e.,
 * a data array).  To allow the program to pass such information we needed a
 * way to abstract things so that the modules have no idea of where things are
 * coming from (and were output is going as well).
 * The API session maintains a single linked linear list of data objects; these
 * objects contain information about all the data resources (sources and destinations)
 * that it has been told about.  Because GMT programs (hence the GMT modules) must
 * be able to find data from stdin, command line files, and command options (e.g.,
 * -Gmyfile.txt) we must be flexible in how things are done.
 *
 * Source registration is done in one of several ways:
 *  1. Call GMT_Register_IO directly and specify the source.  The specifics about the
 *     source will be stored in a new data object which is added to the linked list.
 *     This is what top-level programs must do to allow a GMT module to read via a
 *     memory location.
 *  2. Give file names via the option list (this is what happens when stand-alone
 *     GMT programs process the command line argv list).  Depending on the GMT module,
 *     the module will call GMT_Init_IO to scan for such option arguments and then add
 *     each file found as a new data object.
 *  3. Again, depending on the GMT module, if no unused resources are found, the module
 *     will, via GMT_Init_IO, add stdin as an input resource.  This can be in addition
 *     to any other registered sources, but most often it is added because no other
 *     sources were found.
 *
 * The lower-level GMT i/o machinery will handle complications such as 0 (stdin), 1, or
 * many data files so that the modules themselves simply read the next record with
 * GMT_Get_Record until EOF (as if there is only one input source).  Modules that need
 * to store all the data in memory for further processing will call gmtapi_get_data instead,
 * which will return a single entity (grid, dataset, cpt, etc).
 *
 * Destination registration is done in the same way, with the exception that for most
 * modules (those processing data tables, at least), only one output destination (e.g., file)
 * can be specified.  However, data sets such as tables with segments can, via mode
 * options, be specified to be written to separate table files or even segment files.
 * The actual writing is done by lower-level functions so that the GMT modules are simply
 * calling gmtapi_put_data (all in one go).  For record-by-record output the modules will use
 * GMT_Put_Record.  This keeps data i/o in the modules uniform and simple across GMT.
 */

 /*! . */
int GMT_Register_IO (void *V_API, unsigned int family, unsigned int method, unsigned int geometry, unsigned int direction, double wesn[], void *resource) {
	/* Adds a new data object to the list of registered objects and returns a unique object ID.
	 * Arguments are as listed for api_Register_Im|Export (); see those for details.
	 * During the registration we make sure files exist and are readable.
	 *
	 * if direction == GMT_IN:
	 * A program uses this routine to pass information about input data to GMT.
	 * family:	Specifies the data type we are trying to import; select one of 6 families:
	 *   GMT_IS_PALETTE:	A GMT_PALETTE structure:
	 *   GMT_IS_DATASET:	A GMT_DATASET structure:
	 *   GMT_IS_GRID:	A GMT_GRID structure:
	 *   GMT_IS_IMAGE:	A GMT_IMAGE structure:
	 *   GMT_IS_CUBE:	A GMT_CUBE structure:
	 *   GMT_IS_POSTSCRIPT:	A GMT_POSTSCRIPT structure:
	 * method:	Specifies by what method we will import this data set:
	 *   GMT_IS_FILE:	A file name is given via input.  The program will read data from this file
	 *   GMT_IS_STREAM:	A file pointer to an open file is passed via input. --"--
	 *   GMT_IS_FDESC:	A file descriptor to an open file is passed via input. --"--
	 *   GMT_IS_DUPLICATE:	A pointer to a data set to be copied
	 *   GMT_IS_REFERENCE:	A pointer to a data set to be passed as is [we may reallocate sizes only if GMT-allocated]
	 * The following approaches can be added to the method for all but CPT:
	 *   GMT_VIA_MATRIX:	A 2-D user matrix is passed via input as a source for copying.
	 *			The GMT_MATRIX structure must have parameters filled out.
	 *   GMT_VIA_VECTOR:	An array of user column vectors is passed via input as a source for copying.
	 *			The GMT_VECTOR structure must have parameters filled out.
	 * geometry:	One of GMT_IS_{TEXT|POINT|LINE|POLY|SURF} (the last for GMT grids)
	 * input:	Pointer to the source filename, stream, handle, array position, etc.
	 * wesn:	Grid subset defined by 4 doubles (or 6 for cubes); otherwise use NULL
	 *
	 * RETURNED:	Unique ID assigned to this input resource, or GMT_NOTSET (-1) if error.
	 *
	 * An error status is returned if problems are encountered via API->error [GMT_NOERROR].
	 *
	 * GMT_IS_GRID & GMT_VIA_MATRIX: Since GMT internally uses floats in C arrangement, anything else will be converted to gmt_grdfloat.
	 * GMT_IS_DATASET & GMT_VIA_MATRIX: Since GMT internally uses doubles in C arrangement, anything else will be converted to double.
	 *
	 * api_Register_Import will allocate and populate a GMTAPI_DATA_OBJECT structure which
	 * is appended to the data list maintained by the GMTAPI_CTRL API structure.
	 *
	 * if direction == GMT_OUT:
	 * The main program uses this routine to pass information about output data from GMT.
	 * family:	Specifies the data type we are trying to export; select one of:
	 *   GMT_IS_PALETTE:	A GMT_PALETTE structure:
	 *   GMT_IS_DATASET:	A GMT_DATASET structure:
	 *   GMT_IS_IMAGE:	A GMT_IMAGE structure:
	 *   GMT_IS_GRID:	A GMT_GRID structure:
	 *   GMT_IS_CUBE:	A GMT_CUBE structure:
	 *   GMT_IS_POSTSCRIPT:	A GMT_POSTSCRIPT structure:
	 * method:	Specifies by what method we will export this data set:
	 *   GMT_IS_FILE:	A file name is given via output.  The program will write data to this file
	 *   GMT_IS_STREAM:	A file pointer to an open file is passed via output. --"--
	 *   GMT_IS_FDESC:	A file descriptor to an open file is passed via output. --"--
	 *   GMT_IS_DUPLICATE:	A pointer to a data set to be copied
	 *   GMT_IS_REFERENCE:	A pointer to a data set to be passed as is [we may reallocate sizes only if GMT-allocated]
	 * geometry:	One of GMT_IS_{TEXT|POINT|LINE|POLY|SURFACE|VOLUME} (the last for GMT grids and cubes)
	 * output:	Pointer to the destination filename, stream, handle, array position, etc.
	 * wesn:	Grid/volume subset defined by 4/6 doubles; otherwise use NULL
	 *
	 * RETURNED:	Unique ID assigned to this output resource, or GMT_NOTSET (-1) if error.
	 *
	 * An error status is returned if problems are encountered via API->error [GMT_NOERROR].
	 *
	 * api_Register_Export will allocate and populate a GMTAPI_DATA_OBJECT structure which
	 * is appended to the data list maintained by the GMTAPI_CTRL API structure.
	 */
	int item, object_ID;
	unsigned int module_input, mode = method & GMT_IO_RESET;	/* In case we wish to reuse this resource */
	unsigned int first = 0;
	char message[GMT_LEN256], *file = NULL;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMTAPI_CTRL *API = NULL;
	struct GMT_CTRL *GMT = NULL;

	if (V_API == NULL) return_value (V_API, GMT_NOT_A_SESSION, GMT_NOTSET);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;	/* Reset in case it has some previous error */
	module_input = (family & GMT_VIA_MODULE_INPUT);	/* Are we registering a resource that is a module input? */
	family -= module_input;
	if (gmtapi_validate_geometry (API, family, geometry)) return_value (API, GMT_BAD_GEOMETRY, GMT_NOTSET);

	if ((object_ID = gmtapi_is_registered (API, family, geometry, direction, mode, resource, resource)) != GMT_NOTSET) {	/* Registered before */
		if ((item = gmtlib_validate_id (API, GMT_NOTSET, object_ID, direction, GMT_NOTSET)) == GMT_NOTSET) return_value (API, API->error, GMT_NOTSET);
		S_obj = API->object[item];	/* Use S as shorthand */
		if (module_input) S_obj->module_input = true;
		if ((grid_or_image (family) || matrix_is_a_surface(family,geometry)) && !full_region (wesn)) {	/* Update the subset region if given (for grids/images only) */
			gmt_M_memcpy (S_obj->wesn, wesn, 4, double);
			S_obj->region = true;
		}
		else if (family == GMT_IS_CUBE && (!full_region (wesn) || (wesn && wesn[ZLO] != wesn[ZHI]))) {
			gmt_M_memcpy (S_obj->wesn, wesn, 6, double);
			S_obj->region = true;
		}
		return (object_ID);	/* Already registered so we are done */
	}
	method -= mode;	/* Remove GMT_IO_RESET if it was passed */
	GMT = API->GMT;

	switch (method) {	/* Consider CPT, data, text, and grids, accessed via a variety of methods */
		case GMT_IS_FILE:	/* Registration via a single file name */
			/* No, so presumably it is a regular file name */
			file = strdup (resource);
			if (direction == GMT_IN) {	/* For input we can check if the file exists and can be read. */
				char *p = NULL;
				bool not_url = true, is_plus_sign = false;
				if (grid_or_image_or_cube (family) && !gmtlib_remote_file_is_tiled (API, file, NULL) && (p = strchr (file, '='))) *p = '\0';	/* Chop off any =<stuff> for grids and images so access can work */
				else if (family == GMT_IS_IMAGE && (p = strchr (file, '+'))) {
					char *c = strchr (file, '.');	/* The period before an extension */
					 /* PW 1/30/2014: Protect images with band requests, e.g., my_image.jpg+b2 */
					if (c && c < p && p[1] == 'b' && isdigit (p[2])) {
						GMT_Report (API, GMT_MSG_DEBUG, "Truncating +b modifier for image filename %s\n", file);
						*p = '\0';	/* Chop off any +b<band> for images at end of extension so access can work */
						is_plus_sign = true;
					}
					else	/* Make sure p is NULL so we don't restore a character below */
						p = NULL;
				}
				if (grid_or_image (family))	/* Only grid and images can be URLs so far */
					not_url = !(gmtlib_found_url_for_gdal (file) || gmt_M_file_is_url (file));	/* true if neither special GDAL-remote files nor regular URLs */
				first = gmt_download_file_if_not_found (API->GMT, file, 0);	/* Deal with downloadable GMT data sets first */
				if (gmt_access (GMT, &file[first], F_OK) && not_url) {	/* For input we can check if the file exists (except if via Web) */
					GMT_Report (API, GMT_MSG_ERROR, "File %s not found\n", &file[first]);
					gmt_M_str_free (file);
					return_value (API, GMT_FILE_NOT_FOUND, GMT_NOTSET);
				}
				if (gmt_access (GMT, &file[first], R_OK) && not_url) {	/* Found it but we cannot read. */
					GMT_Report (API, GMT_MSG_ERROR, "Not permitted to read file %s\n", &file[first]);
					gmt_M_str_free (file);
					return_value (API, GMT_BAD_PERMISSION, GMT_NOTSET);
				}
				if (p) p[0] = (is_plus_sign) ? '+' : '=';	/* Restore the extensions */
			}
			else if (resource == NULL) {	/* No file given [should this mean stdin/stdout?] */
				gmt_M_str_free (file);
				return_value (API, GMT_OUTPUT_NOT_SET, GMT_NOTSET);
			}
			/* Create a new data object and initialize variables */
			if ((S_obj = gmtapi_make_dataobject (API, family, method, geometry, NULL, direction)) == NULL) {
				gmt_M_str_free (file);
				return_value (API, GMT_MEMORY_ERROR, GMT_NOTSET);	/* No more memory */
			}
			if (strlen (resource))	/* Strip off any beginning of the name */
				S_obj->filename = strdup (&file[first]);
			gmt_M_str_free (file);
			snprintf (message, GMT_LEN256, "Object ID %%d : Registered %s %s %s as an %s resource with geometry %s [n_objects = %%d]\n", GMT_family[family], gmtapi_method (method), S_obj->filename, GMT_direction[direction], GMT_geometry[gmtapi_gmtry(geometry)]);
			break;

		case GMT_IS_STREAM:	/* Methods that indirectly involve a file */
		case GMT_IS_FDESC:
			if (resource == NULL) {	/* No file given [should this mean stdin/stdout?] */
				return_value (API, GMT_OUTPUT_NOT_SET, GMT_NOTSET);
			}
			if ((S_obj = gmtapi_make_dataobject (API, family, method, geometry, NULL, direction)) == NULL) {
				return_value (API, GMT_MEMORY_ERROR, GMT_NOTSET);	/* No more memory */
			}
			S_obj->fp = resource;	/* Pass the stream of fdesc onward */
			snprintf (message, GMT_LEN256, "Object ID %%d : Registered %s %s %" PRIxS " as an %s resource with geometry %s [n_objects = %%d]\n", GMT_family[family], gmtapi_method (method), (size_t)resource, GMT_direction[direction], GMT_geometry[gmtapi_gmtry(geometry)]);
			break;

		case GMT_IS_DUPLICATE:
		case GMT_IS_REFERENCE:
			if (direction == GMT_IN && resource == NULL) {
				return_value (API, GMT_PTR_IS_NULL, GMT_NOTSET);	/* Input registration of memory takes a resource */
			}
			if ((S_obj = gmtapi_make_dataobject (API, family, method, geometry, resource, direction)) == NULL) {
				return_value (API, GMT_MEMORY_ERROR, GMT_NOTSET);	/* No more memory */
			}
			snprintf (message, GMT_LEN256, "Object ID %%d : Registered %s %s %" PRIxS " as an %s resource with geometry %s [n_objects = %%d]\n", GMT_family[family], gmtapi_method (method), (size_t)resource, GMT_direction[direction], GMT_geometry[gmtapi_gmtry(geometry)]);
			break;

		case GMT_IS_COORD:	/* Internal registration of coordinate arrays so that GMT_Destroy_Data can free them */
			if ((S_obj = gmtapi_make_dataobject (API, family, method, geometry, resource, direction)) == NULL) {
				return_value (API, GMT_MEMORY_ERROR, GMT_NOTSET);	/* No more memory */
			}
			snprintf (message, GMT_LEN256, "Object ID %%d : Registered double array %" PRIxS " as an %s resource [n_objects = %%d]\n", (size_t)resource, GMT_direction[direction]);
			break;
		default:
			GMT_Report (API, GMT_MSG_ERROR, "Failure in GMT_Register_IO (%s): Unrecognized method %d\n", GMT_direction[direction], method);
			return_value (API, GMT_NOT_A_VALID_METHOD, GMT_NOTSET);
			break;
	}

	if ((grid_or_image (family) || matrix_is_a_surface(family,geometry)) && !full_region (wesn)) {	/* Copy the subset region if it was given (for grids) */
		gmt_M_memcpy (S_obj->wesn, wesn, 4, double);
		S_obj->region = true;
	}
	else if (family == GMT_IS_CUBE && (!full_region (wesn) || (wesn && wesn[ZLO] != wesn[ZHI]))) {
		gmt_M_memcpy (S_obj->wesn, wesn, 6, double);
		S_obj->region = true;
	}
	else if (family == GMT_IS_DATASET && S_obj->actual_family == GMT_IS_DATASET) 	/* All datasets are double (this is informational only) */
		S_obj->type = GMT_DOUBLE;
	S_obj->alloc_level = GMT->hidden.func_level;	/* Object was allocated at this module nesting level */
	if (module_input) S_obj->module_input = true;

	/* Here S is not NULL and no errors have occurred (yet) */

	if (direction == GMT_OUT && resource == NULL) S_obj->messenger = true;	/* Output messenger */
	if (method != GMT_IS_COORD) API->registered[direction] = true;	/* We have at least registered one item */
	object_ID = gmtapi_add_data_object (API, S_obj);
	GMT_Report (API, GMT_MSG_DEBUG, message, object_ID, API->n_objects);
#ifdef DEBUG
	//gmtapi_list_objects (API, "GMT_Register_IO");
#endif
	return_value (API, API->error, object_ID);
}

#ifdef FORTRAN_API
int GMT_Register_IO_ (unsigned int *family, unsigned int *method, unsigned int *geometry, unsigned int *direction, double wesn[], void *input) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Register_IO (GMT_FORTRAN, *family, *method, *geometry, *direction, wesn, input));
}
#endif

 /*! . */
int GMT_Init_IO (void *V_API, unsigned int family, unsigned int geometry, unsigned int direction, unsigned int mode, unsigned int n_args, void *args) {
	/* Registers program option file arguments as sources/destinations for the current module.
	 * All modules planning to use std* and/or command-line file args must call GMT_Init_IO to register these resources.
	 * family:	The kind of data (GMT_IS_DATASET|CPT|GRID|IMAGE|CUBE|PS)
	 * geometry:	Either GMT_IS_NONE|TEXT|POINT|LINE|POLYGON|SURFACE|VOLUME
	 * direction:	Either GMT_IN or GMT_OUT
	 * mode:	Bitflags composed of 1 = add command line (option) files, 2 = add std* if no other input/output,
	 *		4 = add std* regardless.  mode must be > 0.
	 * n_args:	Either 0 if we pass linked option structs or argc if we pass argv[]
	 * args:	Either linked list of program option arguments (n_args == 0) or char *argv[].
	 *
	 * Returns:	false if successful, true if error.
	 */
	int object_ID;	/* ID of first object [only for debug purposes - not used in this function; ignore -Wunused-but-set-variable warning */
	struct GMT_OPTION *head = NULL;
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;	/* Reset in case it has some previous error */
	if (gmtapi_validate_geometry (API, family, geometry)) return_error (API, GMT_BAD_GEOMETRY);
	if (!(direction == GMT_IN || direction == GMT_OUT)) return_error (API, GMT_NOT_A_VALID_DIRECTION);
	if (!((mode & GMT_ADD_FILES_IF_NONE) || (mode & GMT_ADD_FILES_ALWAYS) || (mode & GMT_ADD_STDIO_IF_NONE) || (mode & GMT_ADD_STDIO_ALWAYS) || (mode & GMT_ADD_EXISTING))) return_error (API, GMT_NOT_A_VALID_MODE);

	if (n_args == 0) /* Passed the head of linked option structures */
		head = args;
	else		/* Passed argc, argv, likely from FORTRAN */
		head = GMT_Create_Options (API, n_args, args);
	gmtlib_io_banner (API->GMT, direction);	/* Message for binary i/o */
	if (direction == GMT_IN)
		object_ID = gmtapi_init_import (API, family, geometry, mode, head);
	else
		object_ID = gmtapi_init_export (API, family, geometry, mode, head);
	GMT_Report (API, GMT_MSG_DEBUG, "GMT_Init_IO: Returned first %s object ID = %d\n", GMT_direction[direction], object_ID);
	return (API->error);
}

#ifdef FORTRAN_API
int GMT_Init_IO_ (unsigned int *family, unsigned int *geometry, unsigned int *direction, unsigned int *mode, unsigned int *n_args, void *args) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Init_IO (GMT_FORTRAN, *family, *geometry, *direction, *mode, *n_args, args));
}
#endif

GMT_LOCAL int gmtapi_end_io_dataset (struct GMTAPI_CTRL *API, struct GMTAPI_DATA_OBJECT *S, unsigned int *item) {
	/* These are the steps we must take to finalize a GMT_DATASET that was written to
	 * record-by-record via GMT_Put_Record.  It needs to set number of records and set
	 * the min/max per segments and table */
	int check, object_ID;
	int64_t *count = API->GMT->current.io.curr_pos[GMT_OUT];		/* Short-hand for counts of tbl, seg, rows */
	struct GMT_DATASET *D = S->resource;
	struct GMT_DATATABLE *T = NULL;
	struct GMT_DATASET_HIDDEN *DH = NULL;
	struct GMT_DATATABLE_HIDDEN *TH = NULL;
	if (D == NULL) {	/* No output records produced by module; just return an empty dataset with no rows instead of NULL */
		unsigned int smode = (API->GMT->current.io.record_type[GMT_OUT] & GMT_WRITE_TEXT) ? GMT_WITH_STRINGS : GMT_NO_STRINGS;
		D = gmtlib_create_dataset (API->GMT, 1, 1, 0, 0, S->geometry, smode, true);	/* 1 table, 1 segment; no cols or rows yet */
		S->resource = D;
	}

	T = D->table[0];	/* Shorthand to the only table */
	DH = gmt_get_DD_hidden (D);
	TH = gmt_get_DT_hidden (T);
	if (count[GMT_SEG] >= 0) {	/* Finalize segment allocations */
		if (!T->segment[count[GMT_SEG]]) T->segment[count[GMT_SEG]] = gmt_get_segment (API->GMT, T->n_columns);
		gmtlib_assign_segment (API->GMT, GMT_OUT, T->segment[count[GMT_SEG]], count[GMT_ROW], T->n_columns);	/* Allocate and place arrays into segment */
		count[GMT_SEG]++;	/* Set final number of segments */
		T->n_segments++;
	}
	if (count[GMT_SEG] < (int64_t)TH->n_alloc) {	/* Reallocate final number of segments */
		uint64_t s;
		for (s = T->n_segments; s < TH->n_alloc; s++) {	/* Free the extra structures */
			if (T->segment[s] == NULL) continue;
			gmt_M_free (API->GMT, T->segment[s]->hidden);
			gmt_M_free (API->GMT, T->segment[s]);
		}
		T->segment = gmt_M_memory (API->GMT, T->segment, T->n_segments, struct GMT_DATASEGMENT *);	/* Finalize pointer array */
		TH->n_alloc = T->n_segments;	/* Update allocation count */
	}
	if (S->h_delay) {	/* Must do the first table headers now since we finally have allocated the table */
		T->header = API->tmp_header;
		T->n_headers = API->n_tmp_headers;
		API->n_tmp_headers = 0;
		API->tmp_header = NULL;
		S->h_delay = false;
	}
	if (S->s_delay) {	/* Must do the first segment header now since we finally have allocated the table */
		T->segment[0]->header = API->tmp_segmentheader;
		API->tmp_segmentheader = NULL;
		S->s_delay = false;
	}
	D->n_segments = T->n_segments;
	gmt_set_dataset_minmax (API->GMT, D);	/* Update the min/max values for this dataset */
	D->n_records = T->n_records = count[GMT_ROW];
	DH->alloc_level = S->alloc_level;	/* Since we are passing it up to the caller */
	/* Register this resource */
	if ((object_ID = GMT_Register_IO (API, GMT_IS_DATASET, GMT_IS_REFERENCE, D->geometry, GMT_OUT, NULL, D)) == GMT_NOTSET)
		return_error (API, API->error);		/* Failure to register */
	if ((check = gmtlib_validate_id (API, GMT_IS_DATASET, object_ID, GMT_OUT, GMT_NOTSET)) == GMT_NOTSET)
		return_error (API, API->error);		/* Failure to validate */
	*item = (unsigned int) check;
	return (GMT_NOERROR);
}

GMT_LOCAL int gmtapi_end_io_matrix (struct GMTAPI_CTRL *API, struct GMTAPI_DATA_OBJECT *S, unsigned int *item) {
	/* These are the steps we must take to finalize a GMT_MATRIX that was written to
	 * record-by-record vai GMT_Put_Record.  It needs to set number of rows and possibly
	 * add leading NaN-record(s) if there were segment headers at the beginning of file. */
	int error = 0, check, object_ID;
	struct GMT_MATRIX *M = S->resource;
	struct GMT_MATRIX_HIDDEN *MH = gmt_get_M_hidden (M);
	if (S->alloc_mode != GMT_ALLOC_EXTERNALLY && S->n_alloc != S->rec) {	/* Must finalize matrix memory */
		size_t size = S->n_alloc = S->rec;
		size *= M->n_columns;
		if ((error = gmtlib_alloc_univector (API->GMT, &(M->data), M->type, size)) != GMT_NOERROR)
			return_error (API, error);
	}
	MH->alloc_level = S->alloc_level;	/* Since we are passing it up to the caller */
	if (S->h_delay) {	/* Must do the first table headers now since we finally have allocated the table */
		M->header = API->tmp_header;
		M->n_headers = API->n_tmp_headers;
		API->n_tmp_headers = 0;
		S->h_delay = false;
	}
	if (S->delay) {	/* Must place delayed NaN record(s) signifying segment header(s) */
		p_func_uint64_t GMT_2D_to_index = NULL;
		uint64_t col, ij;
		GMT_putfunction api_put_val = gmtapi_select_put_function (API, M->type);
		if (api_put_val == NULL) return_error (API, GMT_NOT_A_VALID_TYPE);
		if ((GMT_2D_to_index = gmtapi_get_2d_to_index (API, GMT_IS_ROW_FORMAT, GMT_GRID_IS_REAL)) == NULL)	/* Can only do row-format until end of this function */
			return_error (API, GMT_WRONG_MATRIX_SHAPE);
		while (S->delay) {	/* Place delayed NaN-rows(s) up front */
			S->delay--;
			for (col = 0; col < M->n_columns; col++) {	/* Place the output items */
				ij = GMT_2D_to_index (S->delay, col, M->dim);
				api_put_val (&(M->data), ij, API->GMT->session.d_NaN);
			}
		}
	}
	if (M->shape == GMT_IS_COL_FORMAT) {	/* Oh no, must do a transpose in place */
		GMT_Report (API, GMT_MSG_DEBUG, "gmtapi_end_io_matrix: Must transpose union matrix to GMT_IS_COL_FORMAT arrangement\n");
		gmtlib_union_transpose (API->GMT, &(M->data), M->n_rows, M->n_columns, M->type);
		M->dim = M->n_rows;	/* Since now it is in FORTRAN column format */
	}
	/* Register this resource */
	if ((object_ID = GMT_Register_IO (API, GMT_IS_MATRIX, GMT_IS_REFERENCE, GMT_IS_SURFACE, GMT_OUT, NULL, M)) == GMT_NOTSET)
		return_error (API, API->error);	/* Failure to register */
	if ((check = gmtlib_validate_id (API, GMT_IS_MATRIX, object_ID, GMT_OUT, GMT_NOTSET)) == GMT_NOTSET)
		return_error (API, API->error);	/* Failure to validate */
	*item = (unsigned int) check;
	return (GMT_NOERROR);
}

GMT_LOCAL int gmtapi_end_io_vector (struct GMTAPI_CTRL *API, struct GMTAPI_DATA_OBJECT *S, unsigned int *item) {
	/* These are the steps we must take to finalize a GMT_VECTOR that was written to
	 * record-by-record via GMT_Put_Record.  It needs to set number of rows and possibly
	 * add leading NaN-record(s) if there were segment headers at the beginning of file. */
	int error = 0, check, object_ID;
	struct GMT_VECTOR *V = S->resource;
	struct GMT_VECTOR_HIDDEN *VH = gmt_get_V_hidden (V);
	if (S->alloc_mode != GMT_ALLOC_EXTERNALLY && S->n_alloc != S->rec) {	/* Must finalize memory */
		S->n_alloc = S->rec;
		if ((error = gmtlib_alloc_vectors (API->GMT, V, S->n_alloc)) != GMT_NOERROR)
			return_error (API, error);
	}
	if ((object_ID = GMT_Register_IO (API, GMT_IS_VECTOR, GMT_IS_REFERENCE, S->geometry, GMT_OUT, NULL, V)) == GMT_NOTSET)
		return_error (API, API->error);	/* Failure to register */
	if ((check = gmtlib_validate_id (API, GMT_IS_VECTOR, object_ID, GMT_OUT, GMT_NOTSET)) == GMT_NOTSET)
		return_error (API, API->error);	/* Failure to validate */
	VH->alloc_level = S->alloc_level;	/* Since we are passing it up to the caller */
	if (S->h_delay) {	/* Must do the first table headers now since we finally have allocated the table */
		V->header = API->tmp_header;
		V->n_headers = API->n_tmp_headers;
		API->n_tmp_headers = 0;
		S->h_delay = false;
	}
	if (S->delay) {	/* Must place delayed NaN record(s) signifying segment header(s) */
		uint64_t col;
		while (S->delay) {	/* Place delayed NaN-record(s) as leading rows */
			S->delay--;
			V->n_rows++;	/* Since could not be incremented before V was created */
			for (col = 0; col < V->n_columns; col++)
				API->current_put_V_val[col] (&(V->data[col]), S->delay, API->GMT->session.d_NaN);
		}
	}
	gmt_M_free (API->GMT, API->current_put_V_val);
	*item = (unsigned int) check;
	return (GMT_NOERROR);
}

/*! . */
int GMT_End_IO (void *V_API, unsigned int direction, unsigned int mode) {
	/* Terminates the i/o mechanism for either input or output (given by direction).
	 * GMT_End_IO must be called after all data i/o is completed.
	 * direction:	Either GMT_IN or GMT_OUT
	 * mode:	Either GMT_IO_DONE (nothing), GMT_IO_RESET (let all resources be accessible again), or GMT_IO_UNREG (unreg all accessed resources).
	 * NOTE: 	Mode not yet implemented until we see a use.
	 * Returns:	false if successful, true if error.
	 * For memory output we finalized the container, register it, sets the alloc_level to the calling entity
	 * and pass the resource upwards.
	 */
	unsigned int item = 0;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (!(direction == GMT_IN || direction == GMT_OUT)) return_error (V_API, GMT_NOT_A_VALID_DIRECTION);
	if (mode > GMT_IO_UNREG) return_error (V_API, GMT_NOT_A_VALID_IO_MODE);

	API = gmtapi_get_api_ptr (V_API);
	gmtlib_free_ogr (API->GMT, &(API->GMT->current.io.OGR), 0);	/* Free segment-related array */
	if (direction == GMT_OUT && API->io_mode[GMT_OUT] == GMTAPI_BY_REC) {
		/* Finalize record-by-record output object dimensions */
		S_obj = API->object[API->current_item[GMT_OUT]];	/* Shorthand for the data object we are working on */
		if (S_obj) {
			S_obj->status = GMT_IS_USED;	/* Done "writing" to this destination */
			if ((S_obj->method == GMT_IS_DUPLICATE || S_obj->method == GMT_IS_REFERENCE)) {	/* Used GMT_Put_Record: Must now realloc dimensions given known sizes */
				int error = GMT_NOERROR;	/* If all goes well */
				if (S_obj->actual_family == GMT_IS_DATASET)			/* Dataset type */
					error = gmtapi_end_io_dataset (API, S_obj, &item);
				else if (S_obj->actual_family == GMT_IS_MATRIX)		/* Matrix type */
					error = gmtapi_end_io_matrix (API, S_obj, &item);
				else if (S_obj->actual_family == GMT_IS_VECTOR)		/* Vector type */
					error = gmtapi_end_io_vector (API, S_obj, &item);
				else	/* Should not get here... */
					error = GMT_NOT_A_VALID_FAMILY;
				if (error) return_error (API, error);	/* Failure to finalize */
				API->object[item]->no_longer_owner = true;	/* Since we passed it via S_obj */
			}
			if (S_obj->close_file) {	/* Close any file that we opened earlier */
				gmt_fclose (API->GMT, S_obj->fp);
				S_obj->close_file = false;
			}
		}
	}
	else {	/* Input files were closed when we tried to go to next item */
		if (API->current_get_V_val) gmt_M_free (API->GMT, API->current_get_V_val);
	}
	API->is_file = true;
	API->io_enabled[direction] = false;	/* No longer OK to access resources or destinations */
	API->current_rec[direction] = 0;	/* Reset count for next time */
	for (item = 0; item < API->n_objects; item++) {	/* Deselect the used resources */
		if (!API->object[item]) continue;	/* Skip empty object */
		if (API->object[item]->direction != (enum GMT_enum_std)direction) continue;	/* Not the required direction */
		if (API->object[item]->selected) API->object[item]->selected = false;	/* No longer a selected resource */
	}

	GMT_Report (API, GMT_MSG_DEBUG, "GMT_End_IO: %s resource access is now disabled\n", GMT_direction[direction]);

	return_error (V_API, GMT_NOERROR);	/* No error encountered */
}

#ifdef FORTRAN_API
int GMT_End_IO_ (unsigned int *direction, unsigned int *mode) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_End_IO (GMT_FORTRAN, *direction, *mode));
}
#endif

/*! . */
int GMT_Get_Status (void *V_API, unsigned int mode) {
	/* Returns nonzero (true) or 0 (false) if the current io status
	 * associated with record-by-record reading matches the
	 * specified mode.  The modes are:
	 * GMT_IO_TABLE_HEADER		: Is current record a table header?
	 * GMT_IO_SEGMENT_HEADER	: Is current record a segment header?
	 * GMT_IO_ANY_HEADER		: Is current record a header or segment header?
	 * GMT_IO_MISMATCH		: Did current record result in a parsing error?
	 * GMT_IO_EOF			: Did we reach end-of-file for entire data set(EOF)?
	 * GMT_IO_NAN			: Did we encounter any NaNs in current record?
	 * GMT_IO_GAP			: Did current record indicate a data gap?
	 * GMT_IO_NEW_SEGMENT		: Is current record the start of a new segment (gap or header)
	 * GMT_IO_LINE_BREAK		: Any sort of new line break (gap, headers, nan)
	 * GMT_IO_FILE_BREAK		: Did we reach end-of-file for a single table (EOF)?
	 * GMT_IO_DATA			: Is current record a data record (including nans)?
	 */

	struct GMTAPI_CTRL *API = NULL;
	struct GMT_IO *IO = NULL;

	if (V_API == NULL) return_value (V_API, GMT_NOT_A_SESSION, GMT_NOTSET);

	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;
	IO = &(API->GMT->current.io);	/* Pointer to the GMT IO structure */
	if (mode == GMT_IO_DATA_RECORD) return (IO->status == 0 || IO->status == GMT_IO_NAN);
	return (IO->status & mode);
}

#ifdef FORTRAN_API
int GMT_Get_Status_ (unsigned int *mode) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Get_Status (GMT_FORTRAN, *mode));
}
#endif

/*! . */
GMT_LOCAL int gmtapi_get_id (void *V_API, unsigned int family, unsigned int direction, void *resource) {
	unsigned int i;
	int item;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);	/* GMT_Create_Session has not been called */
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;
	for (i = 0, item = GMT_NOTSET; item == GMT_NOTSET && i < API->n_objects; i++) {
		if ((S_obj = API->object[i]) == NULL) continue;	/* Empty object */
		if (!S_obj->resource) continue;		/* Empty resource */
		if (S_obj->family != (enum GMT_enum_family)family) {		/* Not the required data type, but check for exceptions */
			if (family == GMT_IS_DATASET && (S_obj->family == GMT_IS_MATRIX || S_obj->family == GMT_IS_VECTOR))
				S_obj->family = GMT_IS_DATASET;	/* Vectors or Matrix masquerading as dataset are valid. Change their family here. */
			else
				continue;
		}
		if (S_obj->direction != (enum GMT_enum_std)direction) continue;	/* Not the required direction */
		if (S_obj->resource == resource) item = i;	/* Pick the requested object regardless of direction */
	}
	if (item == GMT_NOTSET) return_value (API, GMT_NOT_A_VALID_ID, GMT_NOTSET);	/* No such resource found */
	return (S_obj->ID);
}

GMT_LOCAL bool gmtapi_matrix_data_conforms_to_grid (struct GMT_MATRIX *M) {
	/* Check if a matrix data array matches the form of a GMT grid (row-oriented floats) */
	if (M->shape == GMT_IS_COL_FORMAT) return (false);	/* Must transpose */
	if (M->data.f4 == NULL) return (false);	/* Having nothing means we must allocate */
	return (M->type == GMT_GRDFLOAT);	/* Having gmt_grdfloat means we can use as is */
}

GMT_LOCAL bool gmtapi_matrix_data_conforms_to_dataset (struct GMT_MATRIX *M) {
	/* Check if a matrix data array matches the form of a GMT dataset (columns of doubles) */
	if (M->shape == GMT_IS_ROW_FORMAT) return (false);	/* Must transpose */
	if (M->data.f8 == NULL) return (false);	/* Having nothing means we must allocate */
	return (M->type == GMT_DOUBLE);		/* Having double means we can use as is */
}

GMT_LOCAL bool gmtapi_vector_data_conforms_to_dataset (struct GMTAPI_CTRL *API, struct GMT_VECTOR *V, enum GMT_enum_type type) {
	gmt_M_unused(API);
	/* Check if the vector data arrays matches the form of a GMT dataset (columns of doubles) */
	if (type != GMT_DOUBLE) {	/* Only doubles can be passed or memcpy directly */
		if (V->n_columns == 0) return (false);	/* Having nothing yet means we must duplicate */
		if (V->type == NULL) return (false);	/* Having nothing yet means we must duplicate */
		if (V->data == NULL) return (false);	/* Having nothing yet means we must duplicate */
	}
	for (unsigned int col = 0; col < V->n_columns; col++) {
		if (V->data[col].f8 == NULL) return (false);	/* Having nothing means we must duplicate */
		if (V->type[col] != GMT_DOUBLE) return (false);	/* Not having double means must duplicate */
	}
	return true;	/* Seems OK */
}

 /*! . */
GMT_LOCAL unsigned int gmtapi_separate_families (unsigned int *family) {
	unsigned int actual_family;
	if ((*family) & GMT_VIA_VECTOR) {	/* Must allocate a GMT_VECTOR despite family being something else (like DATASET) */
		actual_family = GMT_IS_VECTOR;
		(*family) -=  GMT_VIA_VECTOR;
	}
	else if ((*family) & GMT_VIA_MATRIX) {	/* Must allocate a GMT_MATRIX despite family being something else (like GRID) */
		actual_family = GMT_IS_MATRIX;
		(*family) -=  GMT_VIA_MATRIX;
	}
	else
		actual_family = (*family);	/* It is what it says it is */
	return actual_family;
}

GMT_LOCAL void gmtapi_maybe_change_method_to_duplicate (struct GMTAPI_CTRL *API, struct GMTAPI_DATA_OBJECT *S_obj) {
	/* We want to pass a matrix or set of vectors from the outside as a grid or as a dataset.
	 * grid: If it is a float matrix in row-order layout then we can, else we must duplicate
	 * dataset: If matrix or vector are in columns and they are all doubles then we can, else we must duplicate */
	if (S_obj->actual_family == GMT_IS_MATRIX && S_obj->family == GMT_IS_GRID && !gmtapi_matrix_data_conforms_to_grid (S_obj->resource)) {
		S_obj->method = GMT_IS_DUPLICATE;	/* Must duplicate this resource */
		GMT_Report (API, GMT_MSG_INFORMATION, "GMT_Open_VirtualFile: Switch method to GMT_IS_DUPLICATE as input matrix is not compatible with a GMT gmt_grdfloat grid\n");
	}
	else if (S_obj->actual_family == GMT_IS_MATRIX && S_obj->family == GMT_IS_DATASET && !gmtapi_matrix_data_conforms_to_dataset (S_obj->resource)) {
		S_obj->method = GMT_IS_DUPLICATE;	/* Must duplicate this resource */
		GMT_Report (API, GMT_MSG_INFORMATION, "GMT_Open_VirtualFile: Switch method to GMT_IS_DUPLICATE as input matrix is not compatible with a GMT dataset\n");
	}
	else if (S_obj->actual_family == GMT_IS_VECTOR && S_obj->family == GMT_IS_GRID) {
		S_obj->method = GMT_IS_DUPLICATE;	/* Must duplicate this resource */
		GMT_Report (API, GMT_MSG_INFORMATION, "GMT_Open_VirtualFile: Switch method to GMT_IS_DUPLICATE as vectors are not compatible with a GMT grid\n");
	}
    else if (S_obj->actual_family == GMT_IS_VECTOR && S_obj->family == GMT_IS_DATASET && !gmtapi_vector_data_conforms_to_dataset (API, S_obj->resource, S_obj->type)) {
        S_obj->method = GMT_IS_DUPLICATE;   /* Must duplicate this resource */
        GMT_Report (API, GMT_MSG_INFORMATION, "GMT_Open_VirtualFile: Switch method to GMT_IS_DUPLICATE as input vectors are not compatible with a GMT dataset\n");
    }
    else if (S_obj->actual_family == GMT_IS_DATASET && S_obj->family == GMT_IS_DATASET && API->GMT->common.i.col.select) {
        S_obj->method = GMT_IS_DUPLICATE;   /* Must duplicate this resource */
        GMT_Report (API, GMT_MSG_INFORMATION, "GMT_Open_VirtualFile: Switch method to GMT_IS_DUPLICATE as input dataset needs to be operated on to become output GMT dataset\n");
    }
}

 /*! . */
int GMT_Open_VirtualFile (void *V_API, unsigned int family, unsigned int geometry, unsigned int direction, void *data, char *name) {
	/* Associate a virtual file with a data object for either reading or writing.
	 * Family and geometry specifies the nature of the data to be read or written.
	 * Direction is either GMT_IN or GMT_OUT and determines if we read or write.
	 * Reading: data must point to a data container we wish to read from via a module.
	 * Writing: data is either an existing output data container that the user created
	 *  beforehand or it is NULL and we create an expanding output resource.
	 * name is the name given to the virtual file and is returned. */
	int object_ID = GMT_NOTSET, item_s = 0;
	unsigned int item, orig_family, actual_family = 0, via_type = 0, messenger = 0, module_input, the_mode = GMT_IS_DUPLICATE;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMTAPI_CTRL *API = NULL;
	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	module_input = (family & GMT_VIA_MODULE_INPUT);	/* Are we registering a resource that is a module input? */
	family -= module_input;
	if (direction & GMT_IS_REFERENCE) {	/* Treat this memory as read-only */
		direction -= GMT_IS_REFERENCE;
		the_mode = GMT_IS_REFERENCE;
	}
	else if (direction & GMT_IS_DUPLICATE)	/* This is the default - just remove the mode flag */
		direction -= GMT_IS_DUPLICATE;
	if (!(direction == GMT_IN || direction == GMT_OUT)) return GMT_NOT_A_VALID_DIRECTION;
	if (direction == GMT_IN && data == NULL) return GMT_PTR_IS_NULL;
	if (name == NULL) return_error (V_API, GMT_PTR_IS_NULL);
	orig_family = family;	/* In case we will call GMT_Create_Data later */
	actual_family = gmtapi_separate_families (&family);	/* In case via have a VIA situation */
	if (geometry >= GMT_VIA_CHAR) {
		via_type = (geometry / 100);	/* via_type is 1 higher than GMT_CHAR */
		geometry %= 100;
	}
	if (direction == GMT_IN  && !gmtapi_valid_input_family (family))  return GMT_NOT_A_VALID_FAMILY;
	if (direction == GMT_OUT && !gmtapi_valid_output_family (family)) return GMT_NOT_A_VALID_FAMILY;
	if (via_type && data && !gmtapi_valid_type (via_type-1)) return GMT_NOT_A_VALID_TYPE;	/* via type only valid if not passing any data but want vector or matrix */
	if (actual_family != family && !gmtapi_valid_via_family (actual_family)) return GMT_NOT_A_VALID_FAMILY;
	API = gmtapi_get_api_ptr (V_API);

	if (data) {	/* Data container provided, see if registered */
		for (item = 0; object_ID == GMT_NOTSET && item < API->n_objects; item++) {	/* Loop over all objects */
			if (!API->object[item]) continue;	/* Skip freed objects */
			if (API->object[item]->resource == data) object_ID = API->object[item]->ID;	/* Found a matching data pointer */
		}
		if (object_ID != GMT_NOTSET && (item_s = gmtapi_get_item (API, family, data)) == GMT_NOTSET) {	/* Not found in list */
			return_error (API, GMT_OBJECT_NOT_FOUND);	/* Could not find that item in the array despite finding its ID? */
		}
		S_obj = API->object[item_s];	/* Short-hand for later */
		if (!(S_obj->family == family && S_obj->actual_family == actual_family))
			 return GMT_NOT_A_VALID_FAMILY;
	}
	if (direction == GMT_IN) {	/* Set things up for reading */
		/* See if this one is known to us already */
		if (object_ID == GMT_NOTSET) {	/* Register data as a new object for reading [GMT_IN] and reset its status to unread */
			if ((object_ID = GMT_Register_IO (API, family, the_mode|GMT_IO_RESET, geometry, GMT_IN, NULL, data)) == GMT_NOTSET)
				return (API->error);
			if ((item_s = gmtapi_get_item (API, family, data)) == GMT_NOTSET) {	/* Not found in list */
				return_error (API, GMT_OBJECT_NOT_FOUND);	/* Could not find that item in the array despite finding its ID? */
			}
			S_obj = API->object[item_s];	/* Short-hand for later */
		}
		else {	/* Found the object earlier; recycle the address and ensure it is a readable object */
			if (S_obj->family != family || S_obj->actual_family != actual_family)
				return_error (API, GMT_WRONG_FAMILY);	/* Mixup between what was created and what was passed in */
			S_obj->status = 0;					/* Open for business */
			S_obj->method = the_mode;			/* Now a memory resource */
			S_obj->direction = GMT_IN;			/* Make sure it now is flagged for reading */
		}
		/* If the input a container masquerading as another then we may have to replace method GMT_IS_REFERENCE by GMT_IS_DUPLICATE if REFERENCE was specified */
		if (S_obj->method == GMT_IS_REFERENCE) gmtapi_maybe_change_method_to_duplicate (API, S_obj);
	}
	else {	/* Set things up for writing */
		if (data) {	/* Was provided an object to use */
			if (object_ID == GMT_NOTSET) {	/* Register a new object for writing [GMT_OUT] and reset its status to unread */
				if ((object_ID = GMT_Register_IO (API, orig_family, the_mode|GMT_IO_RESET, geometry, GMT_OUT, NULL, data)) == GMT_NOTSET)
					return (API->error);
				if ((item_s = gmtapi_get_item (API, family, data)) == GMT_NOTSET) {	/* Not found in list */
					return_error (API, GMT_OBJECT_NOT_FOUND);	/* Could not find that item in the array despite finding its ID? */
				}
				S_obj = API->object[item_s];	/* Short-hand for later */
			}
			else {	/* Here we have the item and can recycle the address */
				S_obj->status = 0;				/* Open for business */
				S_obj->method = the_mode;		/* Now a memory resource */
				S_obj->direction = GMT_OUT;		/* Make sure it now is flagged for writing */
			}
		}
		else {	/* New expanding output resource */
			void *object = NULL;
			/* GMT_Create_Data may return error code if there are issues with the values of family, or geometry */
			/* Creating an empty object with mode & GMT_IS_OUTPUT means it is intended to hold output [GMT_OUT] from a module */
			if ((object = GMT_Create_Data (API, orig_family, geometry, GMT_IS_OUTPUT, NULL, NULL, NULL, 0, 0, NULL)) == NULL)
				return (API->error);
			/* Obtain the object's ID */
			if ((object_ID = gmtapi_get_id (API, family, GMT_OUT, object)) == GMT_NOTSET)
				return (API->error);
			if ((item_s = gmtapi_get_item (API, family, object)) == GMT_NOTSET) {	/* Not found in list */
				return_error (API, GMT_OBJECT_NOT_FOUND);	/* Could not find that item in the array despite finding its ID? */
			}
			S_obj = API->object[item_s];	/* Short-hand for later */
			S_obj->type = (via_type) ? via_type - 1 : API->GMT->current.setting.export_type;	/* Remember what output type we want */
			S_obj->method = the_mode;	/* Now a memory resource */
			messenger = 1;
		}
		/* If the output is a matrix masquerading as grid then it must be GMT_FLOAT, otherwise change to DUPLICATE if REFERENCE was specified */
		if (S_obj->method == GMT_IS_REFERENCE) gmtapi_maybe_change_method_to_duplicate (API, S_obj);
	}
	S_obj->region = false;	/* No subset of anything is being considered here */
	gmt_M_memset (S_obj->wesn, 6U, double);
	/* Obtain the unique VirtualFile name */
	if (gmtapi_encode_id (API, module_input, direction, family, actual_family, geometry, messenger, object_ID, name) != GMT_NOERROR)
		return (API->error);
	return GMT_NOERROR;
}

#ifdef FORTRAN_API
int GMT_Open_VirtualFile_ (unsigned int *family, unsigned int *geometry, unsigned int *direction, void *data, char *string, int len) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Open_VirtualFile (GMT_FORTRAN, *family, *geometry, *direction, data, string));
}
#endif

int GMT_Close_VirtualFile (void *V_API, const char *string) {
	/* Given a VirtualFile name, close it */
	int object_ID, item;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMTAPI_CTRL *API = NULL;
	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (string == NULL) return_error (V_API, GMT_PTR_IS_NULL);
	if ((object_ID = gmtapi_decode_id (string)) == GMT_NOTSET)
		return_error (V_API, GMT_OBJECT_NOT_FOUND);
	API = gmtapi_get_api_ptr (V_API);
	if ((item = gmtlib_validate_id (API, GMT_NOTSET, object_ID, GMT_NOTSET, GMT_NOTSET)) == GMT_NOTSET)
		return_error (API, GMT_OBJECT_NOT_FOUND);
	S_obj = API->object[item];	/* Short-hand */
	if (S_obj->family != S_obj->actual_family)	/* Reset the un-masquerading that GMT_Open_VirtualFile did */
		S_obj->family = S_obj->actual_family;
	return GMT_NOERROR;
}

#ifdef FORTRAN_API
int GMT_Close_VirtualFile_ (unsigned int *family, char *string, int len) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Close_VirtualFile (GMT_FORTRAN, string));
}
#endif

void * GMT_Read_VirtualFile (void *V_API, const char *string) {
	/* Given a VirtualFile name, retrieve the resulting object */
	int object_ID;
	void *object = NULL;
	if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION);
	if (string == NULL) return_null (V_API, GMT_PTR_IS_NULL);
	if ((object_ID = gmtapi_decode_id (string)) == GMT_NOTSET)
		return_null (V_API, GMT_OBJECT_NOT_FOUND);
	if ((object = gmtapi_retrieve_data (V_API, object_ID)) == NULL)
		return_null (V_API, GMT_OBJECT_NOT_FOUND);
	return object;
}

#ifdef FORTRAN_API
void * GMT_Read_VirtualFile_ (char *string, int len) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Read_VirtualFile (GMT_FORTRAN, string));
}
#endif

int GMT_Inquire_VirtualFile (void *V_API, const char *string) {
	/* Given a VirtualFile name, retrieve the family of the resulting object */
	int object_ID, item;
	struct GMTAPI_CTRL *API = NULL;
	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (string == NULL) return_error (V_API, GMT_PTR_IS_NULL);
	if ((object_ID = gmtapi_decode_id (string)) == GMT_NOTSET)
		return_error (V_API, GMT_OBJECT_NOT_FOUND);
	if ((item = gmtlib_validate_id (V_API, GMT_NOTSET, object_ID, GMT_NOTSET, GMT_NOTSET)) == GMT_NOTSET)
		return_error (API, GMT_OBJECT_NOT_FOUND);
	API = gmtapi_get_api_ptr (V_API);
	return API->object[item]->family;
}

#ifdef FORTRAN_API
int GMT_Inquire_VirtualFile_ (char *string, int len) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Inquire_VirtualFile (GMT_FORTRAN, string));
}
#endif

 /*! . */
int GMT_Init_VirtualFile (void *V_API, unsigned int mode, const char *name) {
	/* Reset a virtual file back to its original configuration so that it can be
	 * repurposed for reading or writing again.
	 */
	int object_ID = GMT_NOTSET, item;
	struct GMTAPI_DATA_OBJECT *S = NULL;
	struct GMTAPI_CTRL *API = NULL;
	gmt_M_unused (mode);

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (name == NULL) return_error (V_API, GMT_PTR_IS_NULL);
	API = gmtapi_get_api_ptr (V_API);
	if ((object_ID = gmtapi_decode_id (name)) == GMT_NOTSET) return (GMT_OBJECT_NOT_FOUND);	/* Not a registered resource */
	if ((item = gmtlib_validate_id (API, GMT_NOTSET, object_ID, GMT_NOTSET, GMT_NOTSET)) == GMT_NOTSET)
		return_error (API, GMT_OBJECT_NOT_FOUND);
	S = API->object[item];	/* Short-hand pointer */
	S->rec = 0;	/* Start at first record */
	S->delay = 0;	/* No Nan-fuckery yet */
	S->s_delay = S->h_delay = false;	/* No header issues yet */
	S->status = GMT_IS_UNUSED;
	S->selected = true;
	return GMT_NOERROR;
}

#ifdef FORTRAN_API
int GMT_Init_VirtualFile_ (unsigned int mode, char *string, int len) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Init_VirtualFile (GMT_FORTRAN, mode, string));
}
#endif

GMT_LOCAL bool gmtapi_is_passable (struct GMTAPI_DATA_OBJECT *S_obj, unsigned int family) {
	if (family != (unsigned int)S_obj->actual_family) return false;	/* Cannot deal with masquerading containers */
	if (S_obj->resource == NULL) return false;	/* Certainly cannot pass this guy */
	if (S_obj->method != GMT_IS_REFERENCE) return false;	/* Only references can be passed */
	if (S_obj->family == GMT_IS_GRID) {
		struct GMT_GRID *G = gmtapi_get_grid_data (S_obj->resource);
		return (G->data == NULL) ? false : true;
	}
	if (S_obj->family == GMT_IS_IMAGE) {
		struct GMT_IMAGE *I = gmtapi_get_image_data (S_obj->resource);
		return (I->data == NULL) ? false : true;
	}
	return true; /* True to its word, otherwise we fall through and read the data */
}

/* Simple macro to tell us if this file (which we know is a memory file when called) is an output file */
#define gmtapi_M_is_output(file) (file[GMTAPI_OBJECT_DIR_START] == 'O')

/*! . */
void * GMT_Read_Data (void *V_API, unsigned int family, unsigned int method, unsigned int geometry, unsigned int mode, double wesn[], const char *infile, void *data) {
	/* Function to read data files directly into program memory as a set (not record-by-record).
	 * We can combine the <register resource - import resource > sequence in
	 * one combined function.  See GMT_Register_IO for details on arguments.
	 * data is pointer to an existing grid container when we read a grid in two steps, otherwise it must be NULL.
	 * Case 1: infile != NULL: Register input as the source and import data.
	 * Case 2: infile == NULL: Register stdin as the source and import data.
	 * Case 3: geometry == 0: Loop over all previously registered AND unread sources and combine as virtual dataset [DATASET only]
	 * Case 4: family is GRID|IMAGE and method = GMT_DATA_ONLY: Just find already registered resource
	 * Return: Pointer to data container, or NULL if there were errors (passed back via API->error).
	 */
	int in_ID = GMT_NOTSET, item = GMT_NOTSET;
	unsigned int module_input = 0;
	bool just_get_data, reset, reg_here = false;
	void *new_obj = NULL;
	char *input = NULL;
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;
	if (infile) input = strdup (infile);
	just_get_data = (gmt_M_file_is_memory (input));     /* A regular GMT resource passed via memory */
	if (just_get_data && gmtapi_M_is_output (input)) {  /* A virtual output file created elsewhere, retrieve and we are done */
		gmt_M_str_free (input);
		return (GMT_Read_VirtualFile (API, infile));
	}
	reset = (mode & GMT_IO_RESET);	/* We want to reset resource as unread after reading it */
	if (reset) mode -= GMT_IO_RESET;
	module_input = (family & GMT_VIA_MODULE_INPUT);	/* Are we reading a resource that should be considered a module input? */
	family -= module_input;
	API->module_input = (module_input) ? true : false;
	if (grid_or_image_or_cube (family)) {	/* Further checks on the data argument */
		if ((mode & GMT_DATA_ONLY) && data == NULL) {
			free (input);
			return_null (V_API, GMT_PTR_IS_NULL);
		}
		if ((mode & GMT_CONTAINER_ONLY) && data != NULL) {
			free (input);
			return_null (V_API, GMT_PTR_NOT_NULL);
		}
	}

	if (!gmt_M_file_is_remote (infile) && !gmt_M_file_is_url(infile) && infile && !API->external && strpbrk (infile, "*?[]") && !gmtapi_file_with_netcdf_directive (API, infile)) {
		/* Gave a wildcard filename */
		uint64_t n_files;
		unsigned int k;
		char **filelist = NULL;
		if (!multiple_files_ok (family)) {
			GMT_Report (API, GMT_MSG_ERROR, "GMT_Read_Data: Wildcards only allowed for DATASET. "
			                                 "Use GMT_Read_Group to read groups of other data types\n");
			free (input);
			return_null (API, GMT_ONLY_ONE_ALLOWED);
		}
		if ((n_files = gmtlib_glob_list (API->GMT, infile, &filelist)) == 0) {
			GMT_Report (API, GMT_MSG_ERROR, "GMT_Read_Data: Expansion of \"%s\" gave no results\n", infile);
			free (input);
			return_null (API, GMT_OBJECT_NOT_FOUND);
		}
		API->shelf = family;	/* Save which one it is so we know in gmtapi_get_data */
		API->module_input = true;	/* Since we are passing NULL as file name we must loop over registered resources */
		for (k = 0; k < n_files; k++) {
			if ((in_ID = GMT_Register_IO (API, family|GMT_VIA_MODULE_INPUT, GMT_IS_FILE, geometry, GMT_IN, NULL, filelist[k])) == GMT_NOTSET) {
				GMT_Report (API, GMT_MSG_ERROR, "GMT_Read_Data: Could not register file for input: \n", filelist[k]);
				gmt_M_str_free (input);
				gmt_free_list (API->GMT, filelist, n_files);	/* Free the file list */
				return_null (API, API->error);
			}
			if ((item = gmtlib_validate_id (API, family, in_ID, GMT_IN, GMTAPI_MODULE_INPUT)) == GMT_NOTSET) {
				gmt_M_str_free (input);
				gmt_free_list (API->GMT, filelist, n_files);	/* Free the file list */
				return_null (API, API->error);	/* Some internal error... */
			}
			API->object[item]->selected = true;
		}
		gmt_free_list (API->GMT, filelist, n_files);	/* Free the file list */
		in_ID = GMT_NOTSET;
	}
	else if (grid_or_image (family) && (mode & GMT_DATA_ONLY)) {	/* Case 4: Already registered when we obtained header, find object ID */
		if ((in_ID = gmtapi_is_registered (API, family, geometry, GMT_IN, mode, input, data)) == GMT_NOTSET) {
			if (input) gmt_M_str_free (input);
			return_null (API, GMT_OBJECT_NOT_FOUND);	/* Could not find it */
		}
		if (!full_region (wesn) || (family == GMT_IS_CUBE && wesn && wesn[ZLO] != wesn[ZHI])) {	/* Must update subset selection */
			int item;
			if ((item = gmtlib_validate_id (API, family, in_ID, GMT_IN, GMT_NOTSET)) == GMT_NOTSET) {
				if (input) gmt_M_str_free (input);
				return_null (API, API->error);
			}
			if (family == GMT_IS_CUBE)
				gmt_M_memcpy (API->object[item]->wesn, wesn, 6, double);
			else
				gmt_M_memcpy (API->object[item]->wesn, wesn, 4, double);
			API->object[item]->region = true;
		}
	}
	else if (input) {	/* Case 1: Load from a single input, given source. Register it first. */
		unsigned int first = 0;
		/* Must handle special case when a list of colors are given instead of a CPT name.  We make a temp CPT from the colors */
		if (family == GMT_IS_PALETTE && !just_get_data) { /* CPTs must be handled differently since the master files live in share/cpt and filename is missing .cpt */
			int c_err = 0;
			char CPT_file[PATH_MAX] = {""}, *file = NULL, *m = NULL, *f = NULL;
			if (input[0] == '@') first = gmt_download_file_if_not_found (API->GMT, input, 0);	/* Deal with downloadable CPTs */
			file = strdup (&input[first]);
			if ((c_err = gmtapi_colors2cpt (API, &file, &mode)) < 0) { /* Maybe converted colors to new CPT */
				gmt_M_str_free (input);
				gmt_M_str_free (file);
				return_null (API, GMT_CPT_READ_ERROR);	/* Failed in the conversion */
			}
			else if (c_err == 0) {	/* Regular cpt (master or local), append .cpt and set path */
				char *cpt_master = gmt_is_cpt_master (API->GMT, file), *q = NULL;

				/* Need to check for CPT filename modifiers */
				if ((f = gmt_strrstr (file, GMT_CPT_EXTENSION)))
					m = gmtlib_last_valid_file_modifier (API, f, GMT_CPTFILE_MODIFIERS);
				else
					m = gmtlib_last_valid_file_modifier (API, file, GMT_CPTFILE_MODIFIERS);

				if (m) {	/* Got one or more valid CPT file modifiers */
					if ((q = gmtlib_cptfile_unitscale (API, m))) q[0] = '\0';    /* Truncate modifier after processing the unit */
					if (m[0] && (q = strstr (m, "+h"))) q[0] = '\0';    /* Truncate +h modifier (checking for m[0] since the line above could leave it blank) */
				}
				if (cpt_master)	/* Master: Append extension and supply path */
					gmt_getsharepath (API->GMT, "cpt", cpt_master, GMT_CPT_EXTENSION, CPT_file, R_OK);
				else if (!gmt_getdatapath (API->GMT, file, CPT_file, R_OK)) {	/* Use name.cpt as is but look for it */
					GMT_Report (API, GMT_MSG_ERROR, "GMT_Read_Data: File not found: %s\n", file);
					gmt_M_str_free (input);
					return_null (API, GMT_FILE_NOT_FOUND);	/* Failed to find the file anywhere */
				}
				if (m && q) {q[0] = '+'; strncat (CPT_file, q, PATH_MAX-1);}	/* Add back the z modifiers */
			}
			else	/* Got color list, now a temp CPT instead */
				strncpy (CPT_file, file, PATH_MAX-1);
			gmt_M_str_free (file);	/* Free temp CPT name */
			if ((in_ID = GMT_Register_IO (API, family, method, geometry, GMT_IN, wesn, CPT_file)) == GMT_NOTSET) {
				gmt_M_str_free (input);
				return_null (API, API->error);
			}
		}
		else {	/* Not a CPT file but could be remote */
			int k_data;
			char file[PATH_MAX] = {""};
			if (API->remote_info == NULL && !API->GMT->current.io.internet_error && input[0] == '@' && !gmt_M_file_is_memory (input)) {
				/* Maybe using the API without a module call first so server has not been refreshed yet */
				gmt_refresh_server (API);
			}
			if (gmt_set_unspecified_remote_registration (API, &input))   /* If argument is a remote file name then this handles any missing registration _p|_g */
				GMT_Report (API, GMT_MSG_DEBUG, "Revised remote file name to %s\n", input);
			first = gmt_download_file_if_not_found (API->GMT, input, 0);	/* Deal with downloadable GMT data sets first */
			strncpy (file, &input[first], PATH_MAX-1);
			if ((k_data = gmt_remote_no_extension (API, input)) != GMT_NOTSET)	/* A remote @earth_relief_xxm|s grid without extension */
				strcat (file, API->remote_info[k_data].ext);	/* Must supply the .extension */
			if ((in_ID = GMT_Register_IO (API, family|module_input, method, geometry, GMT_IN, wesn, file)) == GMT_NOTSET) {
				gmt_M_str_free (input);
				return_null (API, API->error);
			}
		}
		reg_here = true;
	}
	else if (input == NULL && geometry) {	/* Case 2: Load from stdin.  Register stdin first */
		if ((in_ID = GMT_Register_IO (API, family|module_input, GMT_IS_STREAM, geometry, GMT_IN, wesn, API->GMT->session.std[GMT_IN])) == GMT_NOTSET) {
			gmt_M_str_free (input);
			return_null (API, API->error);	/* Failure to register std??? */
		}
		reg_here = true;
	}
	else {	/* Case 3: input == NULL && geometry == 0, so use all previously registered sources (unless already used). */
		if (!multiple_files_ok (family)) {
			GMT_Report (API, GMT_MSG_ERROR, "GMT_Read_Data: Multiple input resources only allowed for DATASET.");
			return_null (API, GMT_ONLY_ONE_ALLOWED);	/* Virtual source only applies to data and text tables */
		}
		API->shelf = family;	/* Save which one it is so we know in gmtapi_get_data */
		API->module_input = true;	/* Since we are passing NULL as file name we must loop over registered resources */
	}
	if (just_get_data) {
		struct GMTAPI_DATA_OBJECT *S_obj = NULL;
		if ((item = gmtlib_validate_id (API, GMT_NOTSET, in_ID, GMT_NOTSET, GMT_NOTSET)) == GMT_NOTSET) {
			gmt_M_str_free (input);
			return_null (API, API->error);
		}
		S_obj = API->object[item];	/* Current object */
		/* Try to catch a matrix or vector masquerading as dataset by examining the object's actual family  */
		if (gmtapi_is_passable (S_obj, family)) {	/* True to its word, otherwise we fall through and read the data */
#ifdef DEBUG
			gmtapi_set_object (API, S_obj);
#endif
			if (reset) S_obj->status = 0;	/* Reset  to unread */
			return (gmtapi_pass_object (API, S_obj, family, mode, wesn));
		}
	}

	/* OK, try to do the importing */
	if (in_ID != GMT_NOTSET) {	/* Make sure we select the item we just registered */
		if ((item = gmtlib_validate_id (API, GMT_NOTSET, in_ID, GMT_NOTSET, GMT_NOTSET)) == GMT_NOTSET) {
			gmt_M_str_free (input);
			return_null (API, API->error);
		}
		API->object[item]->selected = true;	/* Make sure the item we want is now selected */
	}
	if ((new_obj = gmtapi_get_data (API, in_ID, mode, data)) == NULL) {
		if (reg_here) gmtlib_unregister_io (API, in_ID, GMT_IN);	/* Since reading failed */
		gmt_M_str_free (input);	/* Done with this variable) */
		return_null (API, API->error);
	}
	if (reset) API->object[item]->status = 0;	/* Reset to unread */
	gmt_M_str_free (input);	/* Done with this variable) */
	API->module_input = false;	/* Reset to normal */

#ifdef DEBUG
	gmtapi_list_objects (API, "GMT_Read_Data");
#endif

	return (new_obj);		/* Return pointer to the data container */
}

#ifdef FORTRAN_API
void * GMT_Read_Data_ (unsigned int *family, unsigned int *method, unsigned int *geometry, unsigned int *mode, double *wesn, char *input, void *data, int len) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Read_Data (GMT_FORTRAN, *family, *method, *geometry, *mode, wesn, input, data));
}
#endif

/*! . */
void * GMT_Read_Group (void *V_API, unsigned int family, unsigned int method, unsigned int geometry, unsigned int mode, double wesn[], void *sources, unsigned int *n_items, void *data) {
	/* Function to read a group of data files directly into program memory given an array of objects.
	 * data is pointer to an existing array of grid container when we read a grid in two steps, otherwise use NULL.
	 * *n_items = 0: sources is a character string with wildcard-specification for file names.
	 * *n_items > 0: sources is an array of *n_items character strings with filenames.
	 * If n_items == NULL then it means 0 but we do not return back the number of items.
	 * Note: For DATASET you can also use wildcard expressions in GMT_Read_Data but there we combine then into one data|test-set.
	 * Return: Pointer to array of data container, or NULL if there were errors (passed back via API->error).
	 */
	bool free_list = false;
	unsigned int n_files, k;
	char **file = NULL, *pattern = NULL;
	struct GMTAPI_CTRL *API = NULL;
	void **object = NULL;
	if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION);

	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;

	if (data && !grid_or_image (family)) {
		GMT_Report (API, GMT_MSG_ERROR, "GMT_Read_Group: data pointer must be NULL except for GRID and IMAGE\n");
		return_null (API, GMT_PTR_NOT_NULL);
	}
	if (n_items && *n_items > 0) {	/* Gave list of files */
		n_files = *n_items;
		file = (char **)sources;
	}
	else {	/* Gave wildcard expression(s) */
		pattern = (void *)sources;
		if ((n_files = (unsigned int)gmtlib_glob_list (API->GMT, pattern, &file)) == 0) {
			GMT_Report (API, GMT_MSG_ERROR, "GMT_Read_Group: Expansion of \"%s\" gave no results\n", pattern);
			return_null (API, GMT_OBJECT_NOT_FOUND);
		}
		free_list = true;
	}
	/* Reuse data or allocate empty array of containers */
	object = (data == NULL) ? gmtapi_alloc_object_array (API, n_files, family) : data;
	for (k = 0; k < n_files; k++) {
		if ((object[k] = GMT_Read_Data (API, family, method, geometry, mode, wesn, file[k], object[k])) == NULL)
			GMT_Report (API, GMT_MSG_ERROR, "GMT_Read_Group: Reading of %s failed, returning NULL\n", file[k]);
	}
	if (free_list) {	/* Free the file list we created above and optionally return back how many we found */
		gmt_free_list (API->GMT, file, n_files);
		if (n_items) *n_items = n_files;	/* Return how many items we allocated, if n_items is not NULL */
	}
	return (object);	/* Return pointer to the data containers */
}

#ifdef FORTRAN_API
void * GMT_Read_Group_ (unsigned int *family, unsigned int *method, unsigned int *geometry, unsigned int *mode, double *wesn, void *sources, unsigned int *n_items, void *data) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Read_Group (GMT_FORTRAN, *family, *method, *geometry, *mode, wesn, sources, n_items, data));
}
#endif

/*! . */
void * GMT_Duplicate_Data (void *V_API, unsigned int family, unsigned int mode, void *data) {
	/* Create an duplicate container of the requested kind and optionally allocate space
	 * or duplicate content.
	 * The known families are GMT_IS_{DATASET,GRID,PALETTE,IMAGE,POSTSCRIPT}.
 	 * Pass mode as one of GMT_DUPLICATE_{NONE|ALLOC|DATA} to just duplicate the
	 * container and header structures, allocate space of same dimensions as original,
	 * or allocate space and duplicate contents.  For GMT_IS_{DATA|TEXT}SET you may add
	 * the modifiers GMT_ALLOC_VERTICAL or GMT_ALLOC_HORIZONTAL. Also, for GMT_IS_DATASET
	 * you can manipulate the incoming data->dim to overwrite the number of items allocated.
	 * [By default we follow the dimensions of the incoming data].
	 *
	 * Return: Pointer to new resource, or NULL if an error (set via API->error).
	 */

	int object_ID, item;
	unsigned int geometry = 0U, pmode = 0U;
	void *new_obj = NULL;
	struct GMTAPI_CTRL *API = NULL;
	struct GMT_CTRL *GMT = NULL;

	if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION);
	if (data == NULL)  return_null (V_API, GMT_PTR_IS_NULL);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;
	GMT = API->GMT;

	switch (family) {	/* dataset, cpt, text, grid , image, vector, matrix */
		case GMT_IS_GRID:	/* GMT grid, allocate header and possibly data array */
			new_obj = gmt_duplicate_grid (GMT, data, mode);
			geometry = GMT_IS_SURFACE;
			break;
		case GMT_IS_IMAGE:	/* GMT image, allocate header but not data array */
			new_obj = gmtlib_duplicate_image (GMT, data, mode);
			geometry = GMT_IS_SURFACE;
			break;
		case GMT_IS_DATASET:	/* GMT dataset, allocate the requested tables, segments, rows, and columns */
			pmode = (mode & (GMT_ALLOC_VERTICAL + GMT_ALLOC_HORIZONTAL));	/* Just isolate any special allocation modes */
			mode -= pmode;	/* Remove the hor/ver flags from the rest of mode */
			if (mode == GMT_DUPLICATE_DATA)
				new_obj = gmt_duplicate_dataset (GMT, data, pmode, &geometry);
			else if (mode == GMT_DUPLICATE_ALLOC) {	/* Allocate data set of same size, possibly modulated by Din->dim (of > 0) and pmode */
				struct GMT_DATASET *Din = gmtapi_get_dataset_data (data);	/* We know this is a GMT_DATASET pointer */
				struct GMT_DATASET_HIDDEN *DH = gmt_get_DD_hidden (Din);
				new_obj = gmt_alloc_dataset (GMT, data, DH->dim[GMT_ROW], DH->dim[GMT_COL], pmode);
				geometry = Din->geometry;
				gmt_M_memset (DH->dim, 4U, uint64_t);	/* Reset alloc dimensions */
			}
			else {	/* Just want a dataset structure */
				struct GMT_DATASET *Din = gmtapi_get_dataset_data (data);	/* We know this is a GMT_DATASET pointer */
				new_obj = gmt_get_dataset (GMT);
				geometry = Din->geometry;
			}
			break;
		case GMT_IS_PALETTE:	/* GMT CPT, allocate one with space for dim[0] color entries */
			new_obj = gmtlib_duplicate_palette (GMT, data, 0);
			geometry = GMT_IS_NONE;
			break;
		case GMT_IS_POSTSCRIPT:	/* GMT PS, allocate one with space for the original */
			new_obj = gmtlib_duplicate_ps (GMT, data, 0);
			geometry = GMT_IS_NONE;
			break;
		case GMT_IS_CUBE:	/* GMT cube, allocate header and possibly data array */
			new_obj = gmtlib_duplicate_cube (GMT, data, mode);
			geometry = GMT_IS_VOLUME;
			break;
		case GMT_IS_MATRIX:	/* GMT MATRIX */
			new_obj = gmtlib_duplicate_matrix (GMT, data, mode);
			geometry = GMT_IS_NONE;
			break;
		case GMT_IS_VECTOR:	/* GMT VECTOR */
			new_obj = gmtlib_duplicate_vector (GMT, data, mode);
			geometry = GMT_IS_NONE;
			break;
		default:
			API->error = GMT_NOT_A_VALID_FAMILY;
			break;
	}
	if (API->error) return_null (API, API->error);

	/* Now register this data set so it can be deleted by GMT_Destroy_Data */
	if ((object_ID = GMT_Register_IO (API, family, GMT_IS_REFERENCE, geometry, GMT_IN, NULL, new_obj)) == GMT_NOTSET)
		return_null (API, API->error);	/* Failure to register */
	if ((item = gmtlib_validate_id (API, family, object_ID, GMT_IN, GMT_NOTSET)) == GMT_NOTSET)
		return_null (API, API->error);
	API->object[item]->geometry = geometry;	/* Ensure same geometry */
	API->object[item]->resource = new_obj;	/* Retain pointer to the allocated data so we use garbage collection later */

	GMT_Report (API, GMT_MSG_DEBUG, "Successfully duplicated a %s\n", GMT_family[family]);
#ifdef DEBUG
	gmtapi_list_objects (API, "GMT_Duplicate_Data");
#endif

	return (new_obj);
}

#ifdef FORTRAN_API
void * GMT_Duplicate_Data_ (unsigned int *family,  unsigned int *mode, void *data) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Duplicate_Data (GMT_FORTRAN, *family, *mode, data));
}
#endif

/*! . */
int GMT_Write_Data (void *V_API, unsigned int family, unsigned int method, unsigned int geometry, unsigned int mode, double wesn[], const char *outfile, void *data) {
	/* Function to write data directly from program memory as a set (not record-by-record).
	 * We can combine the <register resource - export resource > sequence in
	 * one combined function.  See GMT_Register_IO for details on arguments.
	 * Here, *data is the pointer to the data object to save (CPT, dataset, Grid)
	 * Case 1: outfile != NULL: Register this as the destination and export data.
	 * Case 2: outfile == NULL: Register stdout as the destination and export data.
	 * Case 3: geometry == 0: Use a previously registered single destination.
	 * While only one output destination is allowed, for DATASETS one can
	 * have the tables and even segments be written to individual files (see the mode
	 * description in the documentation for how to enable this feature.)
	 * Return: false if all is well, true if there was an error (and set API->error).
	 */
	unsigned int n_reg;
	int out_ID;
	char *output = NULL;
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (data == NULL) return_error (V_API, GMT_PTR_IS_NULL);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;
	if (outfile) output = strdup (outfile);

	if (output) {	/* Case 1: Save to a single specified destination (file or memory).  Register it first. */
		if ((out_ID = gmtapi_memory_registered (API, family, GMT_OUT, output)) != GMT_NOTSET) {
			/* Output is a memory resource, passed via a @GMTAPI@-###### file name, and ###### is the out_ID.
			   In this case we must make some further checks.  We need to find the API object that holds data.
			   We do this below and get in_ID (the id of the data to write), while out_ID is the id of where
			   things go (the output "memory").  Having the in_ID we get the array index in_item that matches
			   this ID and of the correct family.  We set direction to GMT_NOTSET since otherwise we may be
			   denied a hit as we don't really know what the direction is for in_ID.  Once in_item has been
			   secured we transfer ownership of this data from the in_ID object to the out_ID object.  That
			   way we avoid accidental premature freeing of the data object via the in_ID object since it now
			   will live on via out_ID and outlive the current module.
			    */
			int in_ID = GMT_NOTSET,  in_item = GMT_NOTSET;
			in_ID = gmtapi_get_object (API, family, data);	/* Get the object ID of the input source */
			if (in_ID != GMT_NOTSET) in_item = gmtlib_validate_id (API, family, in_ID, GMT_NOTSET, GMT_NOTSET);	/* Get the item in the API array; pass dir = GMT_NOTSET to bypass status check */
			if (in_item != GMT_NOTSET) {
				int out_item = gmtlib_validate_id (API, GMT_NOTSET, out_ID, GMT_OUT, GMT_NOTSET);	/* Get the item in the API array; pass family = GMT_NOTSET to bypass status check */
				GMT_Report (API, GMT_MSG_DEBUG, "GMT_Write_Data: Writing %s to memory object %d from object %d which transfers ownership\n", GMT_family[family], out_ID, in_ID);
				if (API->object[out_item]->method == GMT_IS_REFERENCE) API->object[in_item]->no_longer_owner = true;	/* Since we have passed the content onto an output object */
				if (!API->object[out_item]->filename) API->object[out_item]->filename = strdup (output);
			}
		}	/* else it is a regular file and we just register it and get the new out_ID needed below */
		else if ((out_ID = GMT_Register_IO (API, family, method, geometry, GMT_OUT, wesn, output)) == GMT_NOTSET) {
			gmt_M_str_free (output);	/* Done with this variable */
			return_error (API, API->error);
		}
	}
	else if (output == NULL && geometry) {	/* Case 2: Save to stdout.  Register stdout first. */
		if (family == GMT_IS_GRID) return_error (API, GMT_STREAM_NOT_ALLOWED);	/* Cannot write grids to stream */
		if ((out_ID = GMT_Register_IO (API, family, GMT_IS_STREAM, geometry, GMT_OUT, wesn, API->GMT->session.std[GMT_OUT])) == GMT_NOTSET) return_error (API, API->error);	/* Failure to register std??? */
	}
	else {	/* Case 3: output == NULL && geometry == 0, so use the previously registered destination */
		if ((n_reg = gmtlib_count_objects (API, family, geometry, GMT_OUT, &out_ID)) != 1) {
			gmt_M_str_free (output);	/* Done with this variable */
			return_error (API, GMT_NO_OUTPUT);	/* There is no registered output */
		}
	}
	gmt_M_str_free (output);	/* Done with this variable */
	/* With out_ID in hand we can now put the data where it should go */
	if (gmtapi_put_data (API, out_ID, mode, data) != GMT_NOERROR)
		return_error (API, API->error);

#ifdef DEBUG
	gmtapi_list_objects (API, "GMT_Write_Data");
#endif
	return (GMT_NOERROR);	/* No error encountered */
}

#ifdef FORTRAN_API
int GMT_Write_Data_ (unsigned int *family, unsigned int *method, unsigned int *geometry, unsigned int *mode, double *wesn, char *output, void *data, int len) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Write_Data (GMT_FORTRAN, *family, *method, *geometry, *mode, wesn, output, data));
}
#endif

static inline int gmtapi_wind_to_next_datarecord (int64_t *count, struct GMT_DATASET *D, unsigned int mode) {
	/* Increment row, seg, tbl to next record and return current record status */
	if (count[GMT_SEG] == GMT_NOTSET) {	/* Special flag to processes table header(s) */
		if (count[GMTAPI_HDR_POS] < D->table[count[GMT_TBL]]->n_headers) {	/* Must first handle table headers */
			count[GMTAPI_HDR_POS]++;	/* Increment counter for each one we return until done */
			return GMT_IO_TABLE_HEADER;
		}
		/* Must be out of table headers - time for the segment header */
		count[GMT_SEG] = count[GMT_ROW] = 0;
		return GMT_IO_SEGMENT_HEADER;
	}
	if (count[GMT_ROW] == (int64_t)D->table[count[GMT_TBL]]->segment[count[GMT_SEG]]->n_rows) {	/* Previous record was last in segment, go to next */
		count[GMT_SEG]++;	/* Next segment number */
		count[GMT_ROW] = 0;
		if (count[GMT_SEG] == (int64_t)D->table[count[GMT_TBL]]->n_segments) {		/* Also the end of a table ("file") */
			count[GMT_TBL]++;	/* Next table number */
			count[GMT_SEG] = GMT_NOTSET;	/* Reset to start at first segment in this table */
			count[GMTAPI_HDR_POS] = 0;	/* Ready to process headers from next table */
			if (count[GMT_TBL] == (int64_t)D->n_tables)	/* End of entire data set */
				return GMT_IO_EOF;
			/* Just end of a file */
			if (mode & GMT_READ_FILEBREAK)	/* Return empty handed to indicate a break between files */
				return GMT_IO_NEXT_FILE;
		}
		return GMT_IO_SEGMENT_HEADER;
	}
	/* No drama, here we have a data record just go to next row */
	return GMT_IO_DATA_RECORD;
}

/*! . */
int GMT_Set_Geometry (void *V_API, unsigned int direction, unsigned int geometry) {
	/* Sets the geometry of direction resource for record-by-record i/o.
	 * This currently only applies to external interfaces receiving data via rec-by-rec writing.
	 */
	unsigned int method;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	API = gmtapi_get_api_ptr (V_API);
	if (!API->io_enabled[GMT_OUT]) return_error (API, GMT_ACCESS_NOT_ENABLED);
	API->error = GMT_NOERROR;

	S_obj = API->object[API->current_item[direction]];	/* Shorthand for the data source we are working on */
	if (S_obj == NULL) return_error (API, GMT_OBJECT_NOT_FOUND);	/* No such object */
	method = gmtapi_set_method (S_obj);	/* Get the actual method to use */
	switch (method) {	/* File, array, stream etc ? */
		case GMT_IS_DUPLICATE:
		case GMT_IS_REFERENCE:
			if (S_obj->family == GMT_IS_DATASET) {
				struct GMT_DATASET *D_obj = gmtapi_get_dataset_data (S_obj->resource);
				if (!D_obj)	/* Not allocated yet? */
					GMT_Report (API, GMT_MSG_DEBUG, "GMTAPI: GMT_Set_Geometry called but no object available\n");
				else
					D_obj->geometry = geometry;
			}
			break;
		default:	/* For all others there is no geometry requirement, so quietly skip */
			break;
	}
	return GMT_NOERROR;
}

#ifdef FORTRAN_API
int GMT_Set_Geometry_ (unsigned int *direction, unsigned int *geometry) {	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Set_Geometry (GMT_FORTRAN, *direction, *geometry));
}
#endif

GMT_LOCAL void * gmtapi_get_record_fp_sub (struct GMTAPI_CTRL *API, unsigned int mode, int *n_fields, struct GMTAPI_DATA_OBJECT **S_obj) {
	/* Gets next data record from current open stream */
	int status;
	struct GMTAPI_DATA_OBJECT *S = API->current_get_obj;
	struct GMT_CTRL *GMT = API->GMT;
	void *record = S->import (GMT, S->fp, &(S->n_expected_fields), &status);	/* Get that next record */
	*n_fields = status;	/* Number of fields read */
	S->n_columns = (uint64_t)status;	/* Number of fields read */

	if (GMT->current.io.status & GMT_IO_EOF) {	/* Hit end-of-file in current file (but there may be many files in queue) */
		S->status = GMT_IS_USED;	/* Mark this file object as read */
		if (S->close_file) {	/* Close if it was a file that we opened earlier */
			gmt_fclose (GMT, S->fp);
			S->close_file = false;
		}
		/* Move on to next data source, if any */
		if (gmtapi_next_data_object (API, S->family, GMT_IN) == EOF)	/* That was the last source, we are done */
			*n_fields = EOF;				/* EOF is ONLY returned when we reach the end of the LAST data file */
		else if (mode & GMT_READ_FILEBREAK) {			/* Return empty handed to indicate a break between files */
			*n_fields = GMT_IO_NEXT_FILE;			/* We flag this situation with a special return value */
			GMT->current.io.status = GMT_IO_NEXT_FILE;	/* And set the status accordingly */
		}
		else {	/* Get ready to read the next data file */
			S = API->current_get_obj = API->object[API->current_item[GMT_IN]];	/* Shorthand for the next data source to work on */
			API->get_next_record = true;			/* Since we haven't read the next record yet */
			gmtapi_get_record_init (API);			/* Perform init steps on the new resource */
		}
		GMT->current.io.tbl_no++;				/* Update number of tables we have processed */
	}
	else
		S->status = GMT_IS_USING;				/* Mark current object as being read */
	*S_obj = S;
	return record;
}

GMT_LOCAL struct GMT_RECORD *gmtapi_get_record_fp (struct GMTAPI_CTRL *API, unsigned int mode, int *n_fields) {
	/* Gets other data record from current open stream */
	struct GMTAPI_DATA_OBJECT *S;
	return (gmtapi_get_record_fp_sub (API, mode, n_fields, &S));
}

GMT_LOCAL struct GMT_RECORD *gmtapi_get_record_fp_first (struct GMTAPI_CTRL *API, unsigned int mode, int *n_fields) {
	/* Gets first data record from current open stream */
	struct GMTAPI_DATA_OBJECT *S = NULL;
	struct GMT_CTRL *GMT = API->GMT;
	void *record = gmtapi_get_record_fp_sub (API, mode, n_fields, &S);

	if (gmt_M_rec_is_data (GMT) && S->n_expected_fields != GMT_MAX_COLUMNS) {	/* Set the actual column count */
		GMT->common.b.ncol[GMT_IN] = S->n_expected_fields;
		API->api_get_record = gmtapi_get_record_fp;	/* From now on we can read just the record */
	}
	return record;
}

GMT_LOCAL struct GMT_RECORD *gmtapi_get_record_matrix (struct GMTAPI_CTRL *API, unsigned int mode, int *n_fields) {
	/* Gets next data record from current matrix */
	struct GMTAPI_DATA_OBJECT *S = API->current_get_obj;
	struct GMT_CTRL *GMT = API->GMT;
	struct GMT_RECORD *record;

	if (S->rec >= S->n_rows) {	/* Our only way of knowing we are done is to quit when we reach the number of rows that was registered */
		S->status = (API->allow_reuse) ? GMT_IS_UNUSED : GMT_IS_USED;	/* Mark as finished reading this guy unless we may reuse */
		if (gmtapi_next_data_object (API, S->family, GMT_IN) == EOF) {	/* That was the last source, return */
			*n_fields = EOF;				/* EOF is ONLY returned when we reach the end of the LAST data file */
			GMT->current.io.status = GMT_IO_EOF;
		}
		else if (mode & GMT_READ_FILEBREAK) {			/* Return empty handed to indicate a break between files */
			*n_fields = GMT_IO_NEXT_FILE;			/* We flag this situation with a special return value */
			GMT->current.io.status = GMT_IO_NEXT_FILE;
		}
		else {	/* Get ready to read the next data file */
			S = API->current_get_obj = API->object[API->current_item[GMT_IN]];	/* Shorthand for the next data source to work on */
			API->get_next_record = true;	/* Since we haven't read the next record yet */
		}
		API->current_get_M = gmtapi_get_matrix_data (S->resource);
		API->current_get_n_columns = (GMT->common.i.col.select) ? GMT->common.i.col.n_cols : S->n_columns;
		if ((API->current_get_M_index = gmtapi_get_2d_to_index (API, API->current_get_M->shape, GMT_GRID_IS_REAL)) == NULL)
			return NULL;
		if ((API->current_get_M_val = gmtapi_select_get_function (API, API->current_get_M->type)) == NULL)
			return NULL;
		record = NULL;
	}
	else {	/* Read from the current resource */
		struct GMT_MATRIX *M = API->current_get_M;
		unsigned int col, n_use, col_pos_out, col_pos_in;
		uint64_t ij;
		int status;
		S->status = GMT_IS_USING;				/* Mark as being read */
		n_use = gmtapi_n_cols_needed_for_gaps (GMT, S->n_columns);
		gmtapi_update_prev_rec (GMT, n_use);

		for (col = 0; col < API->current_get_n_columns; col++) {
			col_pos_out = gmtlib_pick_in_col_number (GMT, (unsigned int)col, &col_pos_in);
			ij = API->current_get_M_index (S->rec, col_pos_in, M->dim);
			API->current_get_M_val (&(M->data), ij, &(GMT->current.io.curr_rec[col_pos_out]));
			GMT->current.io.curr_rec[col_pos_out] = gmt_M_convert_col (GMT->current.io.col[GMT_IN][col], GMT->current.io.curr_rec[col_pos_out]);
		}
		S->rec++;
		if ((status = gmtapi_bin_input_memory (GMT, S->n_columns, n_use)) < 0) {	/* Process the data record */
			if (status == GMTAPI_GOT_SEGGAP)	 /* Since we inserted a segment header we must revisit this record as first in next segment */
				S->rec--, API->current_rec[GMT_IN]--;
			record = NULL;
		}
		else {	/* Valid data record */
			if (M->text)	/* Also have text as part of record */
				strncpy (GMT->current.io.curr_trailing_text, M->text[S->rec-1], GMT_BUFSIZ-1);
			record = &GMT->current.io.record;
			*n_fields = (int)API->current_get_n_columns;
		}
	}
	return (record);
}

GMT_LOCAL struct GMT_RECORD *gmtapi_get_record_vector (struct GMTAPI_CTRL *API, unsigned int mode, int *n_fields) {
	/* Gets next data record from current vector */
	struct GMTAPI_DATA_OBJECT *S = API->current_get_obj;
	struct GMT_CTRL *GMT = API->GMT;
	struct GMT_RECORD *record;
	uint64_t col;

	if (S->rec == S->n_rows) {	/* Our only way of knowing we are done is to quit when we reach the number of rows that was registered */
		S->status = (API->allow_reuse) ? GMT_IS_UNUSED : GMT_IS_USED;	/* Mark as finished reading this guy unless we may reuse */
		if (gmtapi_next_data_object (API, S->family, GMT_IN) == EOF) {	/* That was the last source, return */
			*n_fields = EOF;				/* EOF is ONLY returned when we reach the end of the LAST data file */
			GMT->current.io.status = GMT_IO_EOF;
		}
		else if (mode & GMT_READ_FILEBREAK) {			/* Return empty handed to indicate a break between files */
			*n_fields = GMT_IO_NEXT_FILE;			/* We flag this situation with a special return value */
			GMT->current.io.status = GMT_IO_NEXT_FILE;
		}
		else {	/* Get ready to read the next data file */
			S = API->current_get_obj = API->object[API->current_item[GMT_IN]];	/* Shorthand for the next data source to work on */
			API->get_next_record = true;	/* Since we haven't read the next record yet */
		}
		API->current_get_V = gmtapi_get_vector_data (S->resource);
		API->current_get_n_columns = (GMT->common.i.col.select) ? GMT->common.i.col.n_cols : S->n_columns;
		API->current_get_V_val = gmt_M_memory (GMT, API->current_get_V_val, API->current_get_V->n_columns, GMT_getfunction);	/* Array of functions */
		for (col = 0; col < API->current_get_V->n_columns; col++)	/* We know the number of columns from registration */
			API->current_get_V_val[col] = gmtapi_select_get_function (API, API->current_get_V->type[col]);
		record = NULL;
	}
	else {	/* Read from this resource */
		struct GMT_VECTOR *V = API->current_get_V;
		unsigned int n_use, col_pos_out, col_pos_in;
		int status;
		S->status = GMT_IS_USING;				/* Mark as being read */
		n_use = gmtapi_n_cols_needed_for_gaps (GMT, S->n_columns);
		gmtapi_update_prev_rec (GMT, n_use);

		for (col = 0; col < API->current_get_n_columns; col++) {
			col_pos_out = gmtlib_pick_in_col_number (GMT, (unsigned int)col, &col_pos_in);
			API->current_get_V_val[col_pos_in] (&(V->data[col_pos_in]), S->rec, &(GMT->current.io.curr_rec[col_pos_out]));
			GMT->current.io.curr_rec[col_pos_out] = gmt_M_convert_col (GMT->current.io.col[GMT_IN][col], GMT->current.io.curr_rec[col_pos_out]);
		}

		S->rec++;
		if ((status = gmtapi_bin_input_memory (GMT, S->n_columns, n_use)) < 0) {	/* Process the data record */
			if (status == GMTAPI_GOT_SEGGAP)	 /* Since we inserted a segment header we must revisit this record as first in next segment */
				S->rec--, API->current_rec[GMT_IN]--;
			record = NULL;
		}
		else {	/* Valid data record */
			if (V->text)	/* Also have text as part of record */
				strncpy (GMT->current.io.curr_trailing_text, V->text[S->rec-1], GMT_BUFSIZ-1);
			record = &GMT->current.io.record;
			*n_fields = (int)API->current_get_n_columns;
		}
	}
	return record;
}

GMT_LOCAL struct GMT_RECORD * gmtapi_get_record_dataset (struct GMTAPI_CTRL *API, unsigned int mode, int *n_fields) {
	/* Gets next data record from current dataset */
	struct GMTAPI_DATA_OBJECT *S = API->current_get_obj;
	struct GMT_CTRL *GMT = API->GMT;
	struct GMT_DATASET *D = API->current_get_D_set;	/* Get the current dataset */
	struct GMT_RECORD *record = NULL;
	int64_t *count = GMT->current.io.curr_pos[GMT_IN];	/* Shorthand used below */
	unsigned int col, col_pos_in, col_pos_out;
	int status = gmtapi_wind_to_next_datarecord (count, D, mode);	/* Get current record status and wind counters if needed */

	switch (status) {
		case GMT_IO_DATA_RECORD:	/* Got a data record */
			S->status = GMT_IS_USING;		/* Mark this resource as currently being read */
			for (col = 0; col < API->current_get_n_columns; col++) {	/* Copy from row to curr_rec */
				col_pos_out = gmtlib_pick_in_col_number (GMT, (unsigned int)col, &col_pos_in);
				GMT->current.io.curr_rec[col_pos_out] = D->table[count[GMT_TBL]]->segment[count[GMT_SEG]]->data[col_pos_in][count[GMT_ROW]];
			}
			if (D->table[count[GMT_TBL]]->segment[count[GMT_SEG]]->text && D->table[count[GMT_TBL]]->segment[count[GMT_SEG]]->text[count[GMT_ROW]])
				strncpy (GMT->current.io.curr_trailing_text, D->table[count[GMT_TBL]]->segment[count[GMT_SEG]]->text[count[GMT_ROW]], GMT_BUFSIZ-1);
			if (GMT->current.setting.io_lonlat_toggle[GMT_IN] && API->current_get_n_columns >= 2) {
				gmt_M_double_swap (GMT->current.io.curr_rec[GMT_X], GMT->current.io.curr_rec[GMT_Y]);	/* Got lat/lon instead of lon/lat */
			}
			record = &GMT->current.io.record;
			GMT->common.b.ncol[GMT_IN] = API->current_get_n_columns;
			*n_fields = (int)API->current_get_n_columns;
			count[GMT_ROW]++;	/* Advance to next row for next time GMT_Get_Record is called */
			break;
		case GMT_IO_SEGMENT_HEADER:	/* Segment break */
			if (D->table[count[GMT_TBL]]->segment[count[GMT_SEG]]->header)
				strncpy (GMT->current.io.segment_header, D->table[count[GMT_TBL]]->segment[count[GMT_SEG]]->header, GMT_BUFSIZ-1);
			else
				GMT->current.io.segment_header[0] = '\0';	/* No header for this segment */
			record = NULL;	/* No data record to return */
			*n_fields = 0;
			break;
		case GMT_IO_TABLE_HEADER:	/* Table header(s) */
			strncpy (GMT->current.io.curr_text, D->table[count[GMT_TBL]]->header[count[GMTAPI_HDR_POS]-1], GMT_BUFSIZ-1);
			record = NULL;	/* No data record to return */
			*n_fields = 0;
			break;
		case GMT_IO_NEXT_FILE:	/* End of a table but more tables to come */
			record = NULL;	/* No data record to return */
			*n_fields = GMT_IO_NEXT_FILE;
			break;
		case GMT_IO_EOF:	/* End of entire data set */
			S->status = (API->allow_reuse) ? GMT_IS_UNUSED : GMT_IS_USED;	/* Mark as finished reading this guy unless we may reuse */
			record = NULL;	/* No more to return anyway */
			*n_fields = EOF;
			break;
	}
	GMT->current.io.status = status;
	return record;
}

/*! . */
GMT_LOCAL void gmtapi_get_record_init (struct GMTAPI_CTRL *API) {
	/* Initializes reading from current source. We must redo this after
	 * selecting a new source since there is no guarantee that the sources
	 * are all of the same kind. */

	unsigned int method;
	uint64_t col;
	struct GMTAPI_DATA_OBJECT *S;
	struct GMT_CTRL *GMT;

	if (!API->io_enabled[GMT_IN]) {
		API->error = GMT_ACCESS_NOT_ENABLED;
		return;
	}
	API->error = GMT_NOERROR;
	API->is_file = false;
	S = API->current_get_obj;	/* Shorthand for the current data source we are working on */
	GMT = API->GMT;			/* Shorthand for GMT access */
	/* Reset to default association for current record's data and text pointers */
	GMT->current.io.record.text = GMT->current.io.curr_trailing_text;
	GMT->current.io.record.data = GMT->current.io.curr_rec;

	method = gmtapi_set_method (S);	/* Get the actual method to use */
	GMT->current.io.status = 0;	/* Initialize status to OK */
	S->status = GMT_IS_USING;				/* Mark as being read */
	switch (method) {
		case GMT_IS_FILE:	/* File, stream, and fd are all the same for us, regardless of data or text input */
	 	case GMT_IS_STREAM:
	 	case GMT_IS_FDESC:
			API->api_get_record = gmtapi_get_record_fp_first;
			GMT->current.io.first_rec = true;
			gmtlib_reset_input (GMT);	/* Go back to being agnostic about number of columns, etc. */
			API->is_file = true;
			break;

		case GMT_IS_DUPLICATE|GMT_VIA_MATRIX:	/* Here we copy/read from a user memory location which is a matrix */
		case GMT_IS_REFERENCE|GMT_VIA_MATRIX:
			API->current_get_M = gmtapi_get_matrix_data (S->resource);
			API->current_get_n_columns = (GMT->common.i.col.select) ? GMT->common.i.col.n_cols : S->n_columns;
			if ((API->current_get_M_index = gmtapi_get_2d_to_index (API, API->current_get_M->shape, GMT_GRID_IS_REAL)) == NULL) {
				GMT_Report (API, GMT_MSG_ERROR, "GMTAPI: Internal error: gmtapi_get_record_init called gmtapi_get_2d_to_index with wring shape\n");
			}
			API->current_get_M_val = gmtapi_select_get_function (API, API->current_get_M->type);
			if (API->current_get_M->text == NULL) GMT->current.io.record.text = NULL;
			API->api_get_record = gmtapi_get_record_matrix;
			break;

		 case GMT_IS_DUPLICATE|GMT_VIA_VECTOR:	/* Here we copy from a user memory location that points to an array of column vectors */
		 case GMT_IS_REFERENCE|GMT_VIA_VECTOR:
			API->current_get_n_columns = (GMT->common.i.col.select) ? GMT->common.i.col.n_cols : S->n_columns;
			API->current_get_V = gmtapi_get_vector_data (S->resource);
			API->current_get_V_val = gmt_M_memory (GMT, NULL, API->current_get_V->n_columns, GMT_getfunction);	/* Array of functions */
			for (col = 0; col < API->current_get_V->n_columns; col++)	/* We know the number of columns from registration */
				API->current_get_V_val[col] = gmtapi_select_get_function (API, API->current_get_V->type[col]);
			API->api_get_record = gmtapi_get_record_vector;
			if (API->current_get_V->text == NULL) GMT->current.io.record.text = NULL;
			break;

		case GMT_IS_DUPLICATE:	/* Only for datasets */
		case GMT_IS_REFERENCE:	/* Only for datasets */
			API->current_get_D_set = gmtapi_get_dataset_data (S->resource);	/* Get the right dataset */
			gmt_set_dataset_verify (GMT, API->current_get_D_set);    /* Basic sanity checking of incoming dataset */
			API->current_get_n_columns = (GMT->common.i.col.select) ? GMT->common.i.col.n_cols : API->current_get_D_set->n_columns;
			API->api_get_record = gmtapi_get_record_dataset;
			if (!(API->current_get_D_set->type & GMT_READ_TEXT)) GMT->current.io.record.text = NULL;
			break;
		default:
			GMT_Report (API, GMT_MSG_ERROR, "GMTAPI: Internal error: gmtapi_get_record_init called with illegal method\n");
			break;
	}
}

void *GMT_Get_Record(void *V_API, unsigned int mode, int *retval) {
	/* Retrieves the next data record from the virtual input source and
	 * returns the number of columns found via *retval (unless retval == NULL).
	 * If current record is a segment header then we return 0.
	 * If we reach EOF then we return EOF.
	 * mode is either GMT_READ_DATA (data columns), GMT_READ_TEXT (text string) or
	 *	GMT_READ_MIXED (expect data but tolerate read errors).
	 * Also, if (mode | GMT_READ_FILEBREAK) is true then we will return empty-handed
	 *	when we get to the end of a file except the final file (which is EOF).
	 *	The calling module can then take actions appropriate between data files.
	 * The double array OR text string is returned via the pointer *record.
	 * If not a data record we return NULL, and pass status via API->GMT->current.io.status.
	 */

	int n_fields;
	struct GMTAPI_CTRL *API;
	struct GMT_CTRL *GMT;
	void *record;

	/* Top level check of active session */
	if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION);
	/* Various initializations before reading */
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;
	if (retval) *retval = 0;
	GMT = API->GMT;	/* Shorthand for GMT access */

	do {	/* We do this until we can secure the next record or we run out of records (and return EOF) */
		API->get_next_record = false;	/* We expect to read one data record and return */
		GMT->current.io.status = 0;	/* Initialize status to OK */
		record = API->api_get_record (API, mode, &n_fields);
	} while (API->get_next_record);

	if (!(n_fields == EOF || n_fields == GMT_IO_NEXT_FILE)) {	/* Increase record count, unless EOF */
		API->current_rec[GMT_IN]++;
		if (GMT->current.io.variable_in_columns) GMT->current.io.n_numerical_cols = (unsigned int)n_fields;	/* Keep track of this */
	}

	if (retval) *retval = n_fields;	/* Requested we return the number of fields found */
	return (record);		/* Return pointer to current record */
}

#ifdef FORTRAN_API
void * GMT_Get_Record_ (unsigned int *mode, int *status) {	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Get_Record (GMT_FORTRAN, *mode, status));
}
#endif

GMT_LOCAL int gmtapi_put_record_fp (struct GMTAPI_CTRL *API, unsigned int mode, struct GMT_RECORD *record) {
	/* Function to use for rec-by-rec output to stream */
	int error = GMT_NOERROR;
	char *s;
	struct GMT_CTRL *GMT = API->GMT;		/* Short hand */
	switch (mode) {
		case GMT_WRITE_TABLE_HEADER:	/* Export a table header record; skip if binary */
			s = (record) ? (char*) (record) : GMT->current.io.curr_text;	/* Default to last input record if NULL */
			gmtlib_write_tableheader (GMT, API->current_fp, s);	error = 1;	/* Write one item */
			break;
		case GMT_WRITE_SEGMENT_HEADER:	/* Export a segment header record; write NaNs if binary  */
			if (record) strncpy (GMT->current.io.segment_header, (char*) (record), GMT_BUFSIZ-1);	/* Default to last segment record if NULL */
			gmt_write_segmentheader (GMT, API->current_fp, GMT->common.b.ncol[GMT_OUT]);	error = 1;	/* Write one item */
			break;
		case GMT_WRITE_DATA:		/* Export either a formatted ASCII data record or a binary record */
			if (GMT->common.b.ncol[GMT_OUT] == UINT_MAX) GMT->common.b.ncol[GMT_OUT] = GMT->common.b.ncol[GMT_IN];
			error = GMT->current.io.output (GMT, API->current_fp, GMT->common.b.ncol[GMT_OUT], record->data, record->text);
			break;
		case GMT_WRITE_TABLE_START:	/* Write title and command to start of file; skip if binary */
			gmtlib_write_newheaders (GMT, API->current_fp, API->current_put_n_columns);	error = 1;	/* Write one item */
			break;
		default:
			GMT_Report (API, GMT_MSG_ERROR, "GMTAPI: Internal error: GMT_Put_Record called with illegal mode %u\n", mode);
			return_error (API, GMT_NOT_A_VALID_IO_MODE);
			break;
	}
	return ((error) ? GMT_NOTSET : 0);
}

GMT_LOCAL int gmtapi_put_record_dataset (struct GMTAPI_CTRL *API, unsigned int mode, struct GMT_RECORD *record) {
	/* Function to use for rec-by-rec output to a memory dataset */
	char *s = NULL;
	double value;
	struct GMT_DATATABLE *T = API->current_put_D_table;	/* Short hand */
	struct GMT_DATATABLE_HIDDEN *TH = gmt_get_DT_hidden (T);
	struct GMT_CTRL *GMT = API->GMT;		/* Short hand */
	int64_t *count = GMT->current.io.curr_pos[GMT_OUT];	/* Short hand to counters for table (not used as == 0), segment, row */
	uint64_t col;
	switch (mode) {
		case GMT_WRITE_TABLE_HEADER:	/* Export a table header record; skip if binary */
			s = (record) ? (char *)record : GMT->current.io.curr_text;	/* Default to last input record if NULL */
			/* Hook into table header list */
			if (count[GMT_SEG] == GMT_NOTSET && strlen(s)) {	/* Only allow headers for first segment in a table */
				T->header = gmt_M_memory (GMT, T->header, T->n_headers+1, char *);
				T->header[T->n_headers++] = strdup (s);
			}
			break;
		case GMT_WRITE_SEGMENT_HEADER:	/* Export a segment header record; write NaNs if binary  */
			count[GMT_SEG]++;	/* Start of new segment */
			if (count[GMT_SEG]) {	/* Must first copy over records for the previous segments; last (or only) segment will be done by GMT_End_IO */
				if (!T->segment[count[GMT_SEG]-1]) T->segment[count[GMT_SEG]-1] = gmt_get_segment (API->GMT, T->n_columns);
				gmtlib_assign_segment (GMT, GMT_OUT, T->segment[count[GMT_SEG]-1], count[GMT_ROW], T->n_columns);	/* Allocate and place arrays into previous segment */
				count[GMT_ROW] = 0;	/* Reset for next segment */
				T->n_segments++;
			}
			if (count[GMT_SEG] == (int64_t)TH->n_alloc) {	/* Extend but set new members to NULL */
				size_t was = TH->n_alloc;
				T->segment = gmt_M_malloc (GMT, T->segment, count[GMT_SEG], &TH->n_alloc, struct GMT_DATASEGMENT *);
				gmt_M_memset (&T->segment[was], TH->n_alloc - was, struct GMT_DATASEGMENT *);
			}
			if (!T->segment[count[GMT_SEG]]) T->segment[count[GMT_SEG]] = gmt_get_segment (GMT, T->n_columns);
			s = (record) ? (char *)record : GMT->current.io.segment_header;	/* Default to last segment header record if NULL */
			if (s && strlen(s)) {	/* Found a segment header */
				if (T->segment[count[GMT_SEG]]->header) gmt_M_str_free (T->segment[count[GMT_SEG]]->header);	/* Hm, better free the old guy before strdup'ing a new one */
				T->segment[count[GMT_SEG]]->header = strdup (s);
			}
			break;
		case GMT_WRITE_DATA:		/* Export a segment row */
			if (gmt_skip_output (GMT, record->data, T->n_columns))	/* Record was skipped via -s[a|r] */
				break;
			if (count[GMT_SEG] == GMT_NOTSET) {	/* Most likely a file with one segment but no segment header */
				GMT_Report (API, GMT_MSG_DEBUG, "GMTAPI: GMT_Put_Record (double) called before any segments declared\n");
				count[GMT_SEG] = 0;
			}
			gmt_prep_tmp_arrays (GMT, GMT_OUT, count[GMT_ROW], T->n_columns);	/* Init or reallocate tmp read vectors */
			for (col = 0; col < T->n_columns; col++) {
				value = gmtapi_select_record_value (GMT, record->data, (unsigned int)col, (unsigned int)GMT->common.b.ncol[GMT_OUT]);
				if (GMT->current.io.col_type[GMT_OUT][col] & GMT_IS_LON) gmt_lon_range_adjust (GMT->current.io.geo.range, &value);
				GMT->hidden.mem_coord[col][count[GMT_ROW]] = value;
			}
			if (GMT->current.setting.io_lonlat_toggle[GMT_OUT] && T->n_columns >= 2) {
				gmt_M_double_swap (GMT->hidden.mem_coord[GMT_X][count[GMT_ROW]], GMT->hidden.mem_coord[GMT_Y][count[GMT_ROW]]);	/* Got lat/lon instead of lon/lat */
			}

			if (record->text && record->text[0])	/* Also write trailing text */
				GMT->hidden.mem_txt[count[GMT_ROW]] = strdup (record->text);
			count[GMT_ROW]++;	/* Increment rows in this segment */
			break;
		case GMT_WRITE_TABLE_START:	/* Write title and command to start of file; skip if binary */
			break;	/* Ignore for this method */
		default:
			GMT_Report (API, GMT_MSG_ERROR, "GMTAPI: Internal error: GMT_Put_Record called with illegal mode %u\n", mode);
			return_error (API, GMT_NOT_A_VALID_IO_MODE);
			break;
	}
	return GMT_NOERROR;
}

GMT_LOCAL int gmtapi_put_record_matrix (struct GMTAPI_CTRL *API, unsigned int mode, struct GMT_RECORD *record) {
	/* Function to use for rec-by-rec output to a memory matrix */
	int error = GMT_NOERROR;
	struct GMT_MATRIX *M = API->current_put_M;
	struct GMT_CTRL *GMT = API->GMT;		/* Short hand */
	uint64_t col, kol, ij;
	char *s = NULL;

	switch (mode) {
		case GMT_WRITE_TABLE_HEADER:	/* Export a table header record; skip if binary */
			s = (record) ? (char *)record : GMT->current.io.curr_text;	/* Default to last input record if NULL */
			/* Hook into matrix header list */
			if (strlen(s)) {	/* Only allow headers for first segment in a table */
				M->header = gmt_M_memory (GMT, M->header, M->n_headers+1, char *);
				M->header[M->n_headers++] = strdup (s);
			}
			break;
		case GMT_WRITE_SEGMENT_HEADER:	/* Segment header */
			if (GMT->current.io.multi_segments[GMT_OUT]) {	/* Flag in data as NaNs in current_record (d) */
				for (col = 0; col < M->n_columns; col++) {	/* Place the output items */
					ij = API->current_put_M_index (API->current_put_obj->rec, col, M->dim);
					API->current_put_M_val (&(M->data), ij, GMT->session.d_NaN);
				}
				M->n_rows++;			/* Since the NaN-record becomes an actual data record that encodes a segment break */
			}
			break;
		case GMT_WRITE_DATA:	/* Data record */
			if (!record) {
				GMT_Report (API, GMT_MSG_ERROR, "GMTAPI: gmtapi_put_record_matrix got a NULL data pointer for method GMT_WRITE_DATA\n");
				error = GMT_NOTSET;
			}
			else {
				if (gmt_skip_output (GMT, record->data, M->n_columns))	/* Record was skipped via -s[a|r] */
					error = GMT_NOTSET;
				else {
					double value;
					bool toggle = (GMT->current.setting.io_lonlat_toggle[GMT_OUT] && M->n_columns >= 2);

					for (col = 0; col < M->n_columns; col++) {	/* Place the output items */
						if (col < 2 && toggle)	/* Deal with -: since we are writing to matrix memory and not file */
							kol = 1 - col;
						else
							kol = col;
						ij = API->current_put_M_index (API->current_put_obj->rec, kol, M->dim);
						value = gmtapi_select_record_value (GMT, record->data, (unsigned int)col, (unsigned int)GMT->common.b.ncol[GMT_OUT]);
						API->current_put_M_val (&(M->data), ij, value);
					}
					if (record->text)
						M->text[API->current_put_obj->rec] = strdup (record->text);
					M->n_rows++;
				}
			}
			break;
		default:
			GMT_Report (API, GMT_MSG_ERROR, "GMTAPI: Internal error: gmtapi_put_record_matrix called with illegal mode %u\n", mode);
			return_error (API, GMT_NOT_A_VALID_IO_MODE);
			break;
	}
	if (!error) {	/* Only increment if we placed a record on the output */
		API->current_rec[GMT_OUT]++;
		API->current_put_obj->rec++;
	}

	if (API->current_put_obj->n_alloc && API->current_put_obj->rec == API->current_put_obj->n_alloc) {	/* Must allocate more memory for vectors or matrices */
		API->current_put_obj->n_alloc <<= 1;
		if ((API->current_put_obj->method == GMT_IS_DUPLICATE || API->current_put_obj->method == GMT_IS_REFERENCE) && API->current_put_obj->actual_family == GMT_IS_MATRIX) {
			size_t size = API->current_put_obj->n_alloc * M->n_columns;	/* Only one layer in this context */
			if ((error = gmtlib_alloc_univector (API->GMT, &(M->data), M->type, size)) != GMT_NOERROR) return (error);
			if (M->text) M->text = gmt_M_memory (API->GMT, M->text, API->current_put_obj->n_alloc, char *);
		}
	}
	return error;
}

GMT_LOCAL int gmtapi_put_record_vector (struct GMTAPI_CTRL *API, unsigned int mode, struct GMT_RECORD *record) {
	/* Function to use for rec-by-rec output to a memory vector */
	int error = GMT_NOERROR;
	struct GMT_VECTOR *V = API->current_put_V;
	struct GMT_CTRL *GMT = API->GMT;		/* Short hand */
	uint64_t col, kol;
	char *s = NULL;

	switch (mode) {
		case GMT_WRITE_TABLE_HEADER:	/* Export a table header record; skip if binary */
			s = (record) ? (char *)record : GMT->current.io.curr_text;	/* Default to last input record if NULL */
			/* Hook into vector header list */
			if (strlen(s)) {	/* Only allow headers for first segment in a table */
				V->header = gmt_M_memory (GMT, V->header, V->n_headers+1, char *);
				V->header[V->n_headers++] = strdup (s);
			}
			break;
		case GMT_WRITE_SEGMENT_HEADER: /* Segment header */
			if (GMT->current.io.multi_segments[GMT_OUT]) {	/* Segment header - flag in data as NaNs */
				for (col = 0; col < V->n_columns; col++)	/* Place the output items */
					API->current_put_V_val[col] (&(V->data[col]), API->current_put_obj->rec, GMT->session.d_NaN);
				V->n_rows++;		/* Same */
			}
			break;
		case GMT_WRITE_DATA:	/* Data record */
			if (!record) {
				GMT_Report (API, GMT_MSG_ERROR, "GMT_Put_Record passed a NULL data pointer for method GMT_IS_DATASET|VECTOR\n");
				error = GMT_NOTSET;
			}
			else {
				double value;
				if (gmt_skip_output (GMT, record->data, V->n_columns))	/* Record was skipped via -s[a|r] */
					error = GMT_NOTSET;
				else {
					bool toggle = (GMT->current.setting.io_lonlat_toggle[GMT_OUT] && V->n_columns >= 2);
					for (col = 0; col < V->n_columns; col++) {	/* Place the output items */
						if (col < 2 && toggle)	/* Deal with -: since we are writing to matrix memory and not file */
							kol = 1 - col;
						else
							kol = col;
						value = gmtapi_select_record_value (GMT, record->data, (unsigned int)col, (unsigned int)GMT->common.b.ncol[GMT_OUT]);
						API->current_put_V_val[kol] (&(V->data[kol]), API->current_put_obj->rec, value);
					}
					if (record->text)
						V->text[API->current_put_obj->rec] = strdup (record->text);
					V->n_rows++;	/* Note that API->current_rec[GMT_OUT] and API->current_put_obj->rec are incremented separately at end of function */
				}
			}
			break;
		default:
			GMT_Report (API, GMT_MSG_ERROR, "GMTAPI: Internal error: gmtapi_put_record_vector called with illegal mode %u\n", mode);
			return_error (API, GMT_NOT_A_VALID_IO_MODE);
			break;
	}

	if (!error) {	/* Only increment if we placed a record on the output */
		API->current_rec[GMT_OUT]++;
		API->current_put_obj->rec++;
	}

	if (API->current_put_obj->n_alloc && API->current_put_obj->rec == API->current_put_obj->n_alloc) {	/* Must allocate more memory for vectors or matrices */
		API->current_put_obj->n_alloc <<= 1;
		if ((error = gmtlib_alloc_vectors (GMT, V, API->current_put_obj->n_alloc)) != GMT_NOERROR) return (error);
		if (V->text) V->text = gmt_M_memory (API->GMT, V->text, API->current_put_obj->n_alloc, char *);
	}
	return error;
}

/*! . */
GMT_LOCAL int gmtapi_put_record_init (struct GMTAPI_CTRL *API, unsigned int mode, struct GMT_RECORD *record) {
	/* Writes a single data record to destimation.
	 * We use mode to signal the kind of record:
	 *   GMT_WRITE_TABLE_HEADER: Write an ASCII table header
	 *   GMT_WRITE_SEGMENT_HEADER: Write an ASCII or binary segment header
	 *   GMT_WRITE_DATA:    Write an data record
	 * For text: If record == NULL use internal current record or header.
	 * Returns 0 if a record was written successfully (See what -s[r] can do).
	 * If an error occurs we return GMT_NOTSET and set API->error.
	 */
	int error = 0;
	unsigned int method;
	uint64_t col;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMT_MATRIX *M_obj  = NULL;
	struct GMT_VECTOR *V_obj  = NULL;
	struct GMT_DATASET *D_obj = NULL;
	struct GMT_MATRIX_HIDDEN *MH = NULL;
	struct GMT_CTRL *GMT;

	if (API == NULL) return_error (API, GMT_NOT_A_SESSION);
	GMT = API->GMT;		/* Short hand */
	if (!API->io_enabled[GMT_OUT]) return_error (API, GMT_ACCESS_NOT_ENABLED);
	API->error = GMT_NOERROR;

	S_obj = API->object[API->current_item[GMT_OUT]];	/* Shorthand for the data source we are working on */
	if (S_obj->status == GMT_IS_USED) return_error (API, GMT_WRITTEN_ONCE);	/* Only allow writing of a data set once [unless we reset status] */
	method = gmtapi_set_method (S_obj);	/* Get the actual method to use */
	API->current_put_obj = S_obj;

	switch (method) {	/* File, array, stream etc ? */
		case GMT_IS_FILE:
	 	case GMT_IS_STREAM:
	 	case GMT_IS_FDESC:
			API->api_put_record = gmtapi_put_record_fp;
			API->current_fp = S_obj->fp;
			API->current_put_n_columns = S_obj->n_columns;
			if (API->GMT->common.o.col.end || API->GMT->common.o.col.text)	/* Asked for unspecified last column on input (e.g., -i3,2,5:), supply the missing last column number */
				gmt_reparse_o_option (API->GMT, (API->GMT->common.o.col.text) ? 0 : S_obj->n_columns);
			error = gmtapi_put_record_fp (API, mode, record);
			break;

		case GMT_IS_DUPLICATE:	/* Fill in a DATASET structure with one table only */
		case GMT_IS_REFERENCE:
			D_obj = gmtapi_get_dataset_data (S_obj->resource);
			if (!D_obj) {	/* First time allocation of the single output table */
				unsigned int smode;
				if (mode == GMT_WRITE_TABLE_HEADER) {	/* Cannot do this yet since we don't know sizes. Delay */
					API->tmp_header = gmt_M_memory (GMT, API->tmp_header, API->n_tmp_headers+1, char *);
					if (record) strncpy (GMT->current.io.curr_text, (char*) (record), GMT_BUFSIZ-1);	/* Default to last segment record if NULL */
					API->tmp_header[API->n_tmp_headers++] = strdup (GMT->current.io.curr_text);
					S_obj->h_delay = true;
					S_obj->status = GMT_IS_USING;	/* Have started writing to this destination */
					return GMT_NOERROR;
				}
				else if (mode == GMT_WRITE_SEGMENT_HEADER) {	/* Cannot do this yet since we don't know sizes. Delay */
					if (API->tmp_segmentheader) gmt_M_str_free (API->tmp_segmentheader);	/* Can happen if empty segment is written */
					if (record) strncpy (GMT->current.io.segment_header, (char*) (record), GMT_BUFSIZ-1);	/* Default to last segment record if NULL */
					API->tmp_segmentheader = strdup (GMT->current.io.segment_header);
					S_obj->s_delay = true;
					S_obj->status = GMT_IS_USING;	/* Have started writing to this destination */
					return GMT_NOERROR;
				}
				else if (record->data == NULL && record->text == NULL) {
					GMT_Report (API, GMT_MSG_ERROR, "GMT_Put_Record give NULL record? - Must skip\n");
					return GMT_NOERROR;
				}
				/* Ensure record_type[GMT_OUT] is set correctly given we now have a data record to examine */
				if (record->text == NULL)
					GMT->current.io.record_type[GMT_OUT] = GMT_WRITE_DATA;
				else if (record->data == NULL)
					GMT->current.io.record_type[GMT_OUT] = GMT_WRITE_TEXT;
				else
					GMT->current.io.record_type[GMT_OUT] = GMT_WRITE_MIXED;
				smode = (GMT->current.io.record_type[GMT_OUT] & GMT_WRITE_TEXT) ? GMT_WITH_STRINGS : GMT_NO_STRINGS;
				D_obj = gmtlib_create_dataset (GMT, 1, GMT_TINY_CHUNK, 0, 0, S_obj->geometry, smode, true);	/* 1 table, alloc segments array; no cols or rows yet */
				S_obj->resource = D_obj;	/* Save this pointer for next time we call GMT_Put_Record */
				GMT->current.io.curr_pos[GMT_OUT][GMT_SEG] = GMT_NOTSET;	/* Start at seg = -1 and increment at first segment header */
				col = (GMT->common.o.col.select) ? GMT->common.o.col.n_cols : GMT->common.b.ncol[GMT_OUT];	/* Number of columns needed to hold the data records */
				if ((GMT->current.io.record_type[GMT_OUT] & GMT_WRITE_DATA) && col == 0) {	/* Still don't know # of columns */
					if (GMT->common.b.ncol[GMT_IN] < GMT_MAX_COLUMNS) {	/* Hail Mary pass to input columns */
						col = GMT->common.b.ncol[GMT_IN];	/* Set output cols to equal input cols since not set */
						GMT_Report (API, GMT_MSG_DEBUG, "GMTAPI: GMT_Put_Record does not know the number of output columns - set to equal input at %d\n", (int)GMT->common.b.ncol[GMT_IN]);
					}
					else {
						GMT_Report (API, GMT_MSG_DEBUG, "GMT_Put_Record does not know the number of columns - must abort!\n");
						return_error (API, GMT_N_COLS_NOT_SET);
					}
				}
				if (GMT->current.io.record_type[GMT_OUT] == GMT_WRITE_TEXT) col = 0;	/* Just to be safe rather than fucked */
				D_obj->n_columns = D_obj->table[0]->n_columns = col;	/* The final actual output column number */
				if (GMT->common.b.ncol[GMT_OUT] == 0) GMT->common.b.ncol[GMT_OUT] = col;
			}
			API->current_put_D_table = D_obj->table[0];	/* GMT_Put_Record only writes one table with one or more segments */
			API->api_put_record = gmtapi_put_record_dataset;
			error = gmtapi_put_record_dataset (API, mode, record);
			break;

		case GMT_IS_DUPLICATE|GMT_VIA_MATRIX:	/* Data matrix only */
		case GMT_IS_REFERENCE|GMT_VIA_MATRIX:
			/* At the first output record the output matrix has not been allocated.
			 * So first we do that, then later we can increment its size when needed.
			 * The realloc to final size takes place in GMT_End_IO. */
			if (mode == GMT_WRITE_TABLE_HEADER) {	/* Cannot do this yet since we don't know sizes. Delay */
				API->tmp_header = gmt_M_memory (GMT, API->tmp_header, API->n_tmp_headers+1, char *);
				if (record) strncpy (GMT->current.io.curr_text, (char*) (record), GMT_BUFSIZ-1);	/* Default to last segment record if NULL */
				API->tmp_header[API->n_tmp_headers++] = strdup (GMT->current.io.curr_text);
				S_obj->h_delay = true;
				S_obj->status = GMT_IS_USING;	/* Have started writing to this destination */
				return GMT_NOERROR;
			}
			if (S_obj->n_rows && S_obj->rec >= S_obj->n_rows)
				GMT_Report (API, GMT_MSG_WARNING, "GMTAPI: GMT_Put_Record exceeding limits on rows(?) - possible bug\n");
			if (S_obj->resource == NULL) {	/* First time allocating space; S_obj->n_rows == S_obj->n_alloc == 0 */
				size_t size;
				col = (GMT->common.o.col.select) ? GMT->common.o.col.n_cols : GMT->common.b.ncol[GMT_OUT];	/* Number of columns needed to hold the data records */
				if (col == 0 && mode == GMT_WRITE_SEGMENT_HEADER && GMT->current.io.multi_segments[GMT_OUT]) {
					/* Cannot place the NaN records since we don't know the number of columns yet */
					S_obj->delay++;
					S_obj->rec++;					/* Since the NaN-record is an actual data record that encodes a segment break */
					API->current_rec[GMT_OUT]++;	/* Since the NaN-record is an actual data record that encodes a segment break */
					break;
				}
				size = S_obj->n_alloc = GMT_CHUNK;
				M_obj = gmtlib_create_matrix (GMT, 1U, 0);
				M_obj->type = S_obj->type;	/* Use selected data type for export */
				M_obj->dim = M_obj->n_columns = col;	/* If COL_FORMAT the dim will change in end_io_matrix after transpose */
				size *= M_obj->n_columns;	/* Size in bytes of the initial matrix allocation */
				if ((error = gmtlib_alloc_univector (GMT, &(M_obj->data), M_obj->type, size)) != GMT_NOERROR) return (gmtlib_report_error (API, error));
				if (record->text && (M_obj->text = gmt_M_memory (GMT, NULL, S_obj->n_alloc, char *)) == NULL) return (gmtlib_report_error (API, GMT_MEMORY_ERROR));
				MH = gmt_get_M_hidden (M_obj);
				S_obj->alloc_mode = MH->alloc_mode = GMT_ALLOC_INTERNALLY;
				MH->alloc_level = GMT->hidden.func_level;   /* Must be freed at this level. */
				S_obj->resource = M_obj;	/* Save so we can get it next time */
				M_obj->n_rows = S_obj->rec;	/* So we start on the same output record due to the delayed NaNs */
			}
			/* Place current matrix parameters in API */
			API->current_put_M = M_obj;
			if ((API->current_put_M_index = gmtapi_get_2d_to_index (API, GMT_IS_ROW_FORMAT, GMT_GRID_IS_REAL)) == NULL)	/* Since we cannot do col_format without knowing dimension - see end_io_matrix */
				return GMT_WRONG_MATRIX_SHAPE;
			if ((API->current_put_M_val = gmtapi_select_put_function (API, M_obj->type)) == NULL)
				return GMT_NOT_A_VALID_TYPE;
			API->api_put_record = gmtapi_put_record_matrix;
			error = gmtapi_put_record_matrix (API, mode, record);
			break;

		case GMT_IS_DUPLICATE|GMT_VIA_VECTOR:	/* List of column arrays */
		case GMT_IS_REFERENCE|GMT_VIA_VECTOR:
			if (mode == GMT_WRITE_TABLE_HEADER) {	/* Cannot do this yet since we don't know sizes. Delay. */
				API->tmp_header = gmt_M_memory (GMT, API->tmp_header, API->n_tmp_headers+1, char *);
				if (record) strncpy (GMT->current.io.curr_text, (char*) (record), GMT_BUFSIZ-1);	/* Default to last segment record if NULL */
				API->tmp_header[API->n_tmp_headers++] = strdup (GMT->current.io.curr_text);
				S_obj->h_delay = true;
				S_obj->status = GMT_IS_USING;	/* Have started writing to this destination */
				return GMT_NOERROR;
			}
			if (S_obj->n_rows && S_obj->rec >= S_obj->n_rows)
				GMT_Report (API, GMT_MSG_WARNING, "GMTAPI: GMT_Put_Record exceeding limits on rows(?) - possible bug\n");
			if ((V_obj = S_obj->resource) == NULL) {	/* First time allocating space; S_obj->n_rows == S_obj->n_alloc == 0 */
				col = (GMT->common.o.col.select) ? GMT->common.o.col.n_cols : GMT->common.b.ncol[GMT_OUT];	/* Number of columns needed to hold the data records */
				if (col == 0 && mode == GMT_WRITE_SEGMENT_HEADER && GMT->current.io.multi_segments[GMT_OUT]) {
					/* Cannot place the NaN records since we don't know the number of columns yet */
					S_obj->delay++;
					S_obj->rec++;					/* Since the NaN-record is an actual data record that encodes a segment break */
					API->current_rec[GMT_OUT]++;	/* Since the NaN-record is an actual data record that encodes a segment break */
					break;
				}
				S_obj->n_alloc = GMT_CHUNK;	/* Size in bytes of the initial matrix allocation */
				if ((V_obj = gmt_create_vector (GMT, col, GMT_OUT)) == NULL)
					return_error (API, GMT_MEMORY_ERROR);
				for (col = 0; col < V_obj->n_columns; col++)	/* Set same export data type for all vectors */
					V_obj->type[col] = GMT->current.setting.export_type;
				if ((error = gmtlib_alloc_vectors (GMT, V_obj, S_obj->n_alloc)) != GMT_NOERROR) {
					/* Have to free V_obj here */
					return (gmtlib_report_error (API, error));
				}
				if (record->text) V_obj->text = gmt_M_memory (GMT, NULL, S_obj->n_alloc, char *);
				S_obj->resource = V_obj;	/* Save so we can get it next time */
			}
			/* Place current vector parameters in API */
			API->current_put_V = V_obj;
			API->current_put_V_val = gmt_M_memory (GMT, NULL, V_obj->n_columns, GMT_putfunction);	/* Array of functions */
			for (col = 0; col < V_obj->n_columns; col++) {	/* Assign the functions */
				if ((API->current_put_V_val[col] = gmtapi_select_put_function (API, V_obj->type[col])) == NULL)
					return GMT_NOT_A_VALID_TYPE;
			}
			API->api_put_record = gmtapi_put_record_vector;
			error = gmtapi_put_record_vector (API, mode, record);
			break;

		default:
			GMT_Report (API, GMT_MSG_ERROR, "GMT_Put_Record called with illegal method\n");
			return_error (API, GMT_NOT_A_VALID_METHOD);
			break;
	}

	S_obj->status = GMT_IS_USING;	/* Have started writing to this destination */

	return ((error) ? GMT_NOTSET : 0);
}

/*! . */
int GMT_Put_Record (void *V_API, unsigned int mode, void *record) {
	/* Writes a single data record to destination.
	 * We use mode to signal the kind of record:
	 *   GMT_WRITE_TABLE_HEADER:   Write an ASCII table header
	 *   GMT_WRITE_SEGMENT_HEADER: Write an ASCII or binary segment header
	 *   GMT_WRITE_DATA:           Write an data record
	 * For text: If record == NULL we use internal current record or header.
	 * Returns GMT_NOERROR if a record was written successfully (See what -s[r] can do).
	 * If an error occurs we return GMT_NOTSET and set API->error.
	 *
	 * GMT_Put_Record calls api_put_record is a pointer to various container-specific
	 * output functions.  It is initialized to gmtapi_put_record_init by GMT_Begin_IO.
	 * gmtapi_put_record_init initializes the machinery and assigns api_put_record. */

	struct GMTAPI_CTRL *API = gmtapi_get_api_ptr (V_API);
	return (API->api_put_record (API, mode, record));
}

#ifdef FORTRAN_API
int GMT_Put_Record_ (unsigned int *mode, void *record) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Put_Record (GMT_FORTRAN, *mode, record));
}
#endif

 /*! . */
int GMT_Begin_IO (void *V_API, unsigned int family, unsigned int direction, unsigned int mode) {
	/* Initializes the rec-by-rec i/o mechanism for either input or output (given by direction).
	 * GMT_Begin_IO must be called before any data i/o is allowed.
	 * family:	The family of data must be GMT_IS_DATASET.
	 * direction:	Either GMT_IN or GMT_OUT.
	 * mode:	Currently unused
	 * Returns:	false if successful, true if error.
	 */
	int error, item;
	struct GMTAPI_DATA_OBJECT *S_obj = NULL;
	struct GMTAPI_CTRL *API = NULL;
	struct GMT_CTRL *GMT = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (!(direction == GMT_IN || direction == GMT_OUT)) return_error (V_API, GMT_NOT_A_VALID_DIRECTION);
	if (!multiple_files_ok (family)) return_error (V_API, GMT_NOT_A_VALID_IO_ACCESS);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;	/* Reset in case it has some previous error */
	if (!API->registered[direction]) GMT_Report (API, GMT_MSG_DEBUG, "GMT_Begin_IO: No %s resources registered\n", GMT_direction[direction]);
	if (mode) GMT_Report (API, GMT_MSG_DEBUG, "GMT_Begin_IO: Mode value %u not considered (ignored)\n", mode);

	GMT = API->GMT;
	/* Must initialize record-by-record machinery for dataset */
	GMT_Report (API, GMT_MSG_DEBUG, "GMT_Begin_IO: Initialize record-by-record access for %s\n", GMT_direction[direction]);
	API->current_item[direction] = GMT_NOTSET;	/* gmtapi_next_data_object (below) will wind it to the first item >= 0 */
	if ((error = gmtapi_next_data_object (API, family, direction))) return_error (API, GMT_NO_RESOURCES);	/* Something went bad */
	item = API->current_item[direction];	/* Next item */
	S_obj = API->object[item];	/* Short-hand for next object */
	API->io_mode[direction] = GMTAPI_BY_REC;
	API->io_enabled[direction] = true;	/* OK to access resources */
	GMT->current.io.need_previous = (GMT->common.g.active || GMT->current.io.skip_duplicates);
	GMT->current.io.ogr = GMT_OGR_UNKNOWN;
	GMT->current.io.segment_header[0] = GMT->current.io.curr_text[0] = 0;
	GMT->current.io.first_rec = true;
	if (direction == GMT_OUT) {	/* Special checks for output */
		if (S_obj->messenger && S_obj->resource) {	/* Need to destroy the dummy container before passing data out */
			if ((error = gmtapi_destroy_data_ptr (API, S_obj->actual_family, S_obj->resource)))	/* Do the dirty deed */
				return_error (API,error);
			S_obj->resource  = NULL;	/* Since we now have nothing */
			S_obj->messenger = false;	/* OK, now clean for output */
		}
		API->current_put_obj = S_obj;
		API->api_put_record = gmtapi_put_record_init;
		API->GMT->current.io.record_type[GMT_OUT] = API->GMT->current.io.record_type[GMT_IN];	/* Can be overruled by GMT_Set_Columns */
		//if (header == GMT_HEADER_ON && !GMT->common.b.active[GMT_OUT]) GMT_Put_Record (API, GMT_WRITE_TABLE_START, NULL);	/* Write standard ASCII header block */
		if (!GMT->common.o.active) GMT->current.io.trailing_text[GMT_OUT] = true;	/* Default reads and writes any trailing text */
	}
	else {	/* Special checks for input */
		API->current_get_obj = S_obj;
		if (!GMT->common.i.active) GMT->current.io.trailing_text[GMT_IN] = true;	/* Default reads and writes any trailing text */
		gmtapi_get_record_init (API);
	}
	GMT_Report (API, GMT_MSG_DEBUG, "GMT_Begin_IO: %s resource access is now enabled [record-by-record]\n", GMT_direction[direction]);

	return_error (V_API, GMT_NOERROR);	/* No error encountered */
}

#ifdef FORTRAN_API
int GMT_Begin_IO_ (unsigned int *family, unsigned int *direction, unsigned int *mode) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Begin_IO (GMT_FORTRAN, *family, *direction, *mode));
}
#endif

/*! . */
int GMT_Get_Row (void *V_API, int row_no, struct GMT_GRID *G, gmt_grdfloat *row) {
	/* Reads the entire row vector from the grdfile.
	 * If row_no is NEGATIVE it is interpreted to mean that we just want to
	 * fseek to the start of the abs(row_no) record and no reading takes place.
	 * If R->auto_advance is false we must set R->start explicitly to row_no.
	 * If R->auto_advance is true it reads the current row and advances R->row++.
	 * In this case row_no is not actually used.
	 */
	unsigned int err;
 	unsigned int col;
	struct GMTAPI_CTRL *API = NULL;
	char *fmt = NULL;
	struct GMT_GRID_HIDDEN *GH = NULL;
	struct GMT_GRID_ROWBYROW *R = NULL;
	struct GMT_GRID_HEADER_HIDDEN *HH = NULL;
	struct GMT_CTRL *GMT = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;
	GMT = API->GMT;
	GH = gmt_get_G_hidden (G);
	R = gmtapi_get_rbr_ptr (GH->extra);
	HH = gmt_get_H_hidden (G->header);
	fmt = GMT->session.grdformat[G->header->type];
	if (fmt[0] == 'c') {		/* Get one NetCDF row, old format */
		if (row_no < 0) {	/* Special seek instruction, then return */
			R->row = abs (row_no);
			R->start[0] = R->row * R->edge[0];
			return (GMT_NOERROR);
		}
		else if (!R->auto_advance) {	/* Go to specified row and read it */
			R->row = row_no;
			R->start[0] = R->row * R->edge[0];
		}
		gmt_M_err_trap (gmt_nc_get_vara_grdfloat (R->fid, HH->z_id, R->start, R->edge, row));
		if (R->auto_advance) R->start[0] += R->edge[0];	/* Advance to next row if auto */
	}
	else if (fmt[0] == 'n') {	/* Get one NetCDF row, COARDS-compliant format */
		if (row_no < 0) {	/* Special seek instruction */
			R->row = abs (row_no);
			R->start[0] = HH->row_order == k_nc_start_north ? R->row : G->header->n_rows - 1 - R->row;
			return (GMT_NOERROR);
		}
		else if (!R->auto_advance) {
			R->row = row_no;
			R->start[0] = HH->row_order == k_nc_start_north ? R->row : G->header->n_rows - 1 - R->row;
		}
		gmt_M_err_trap (gmt_nc_get_vara_grdfloat (R->fid, HH->z_id, R->start, R->edge, row));
		if (R->auto_advance) R->start[0] -= HH->row_order;	/* Advance to next row if auto */
	}
	else {			/* Get a native binary row */
		size_t n_items;
		if (row_no < 0) {	/* Special seek instruction */
			R->row = abs (row_no);
			if (fseek (R->fp, (off_t)(GMT_GRID_HEADER_SIZE + R->row * R->n_byte), SEEK_SET)) return (GMT_GRDIO_SEEK_FAILED);
			return (GMT_NOERROR);
		}
		R->row = row_no;
		if (!R->auto_advance && fseek (R->fp, (off_t)(GMT_GRID_HEADER_SIZE + R->row * R->n_byte), SEEK_SET)) return (GMT_GRDIO_SEEK_FAILED);

		n_items = G->header->n_columns;
		if (fmt[1] == GMT_GRD_FORMAT) {	/* Binary gmt_grdfloat, no need to mess with decoding */
			if (gmt_M_fread (row, R->size, n_items, R->fp) != n_items) return (GMT_GRDIO_READ_FAILED);	/* Get one row */
		}
		else {
			if (gmt_M_fread (R->v_row, R->size, n_items, R->fp) != n_items) return (GMT_GRDIO_READ_FAILED);	/* Get one row */
			for (col = 0; col < G->header->n_columns; col++)
				row[col] = gmtlib_decode (GMT, R->v_row, col, fmt[1]);	/* Convert whatever to gmt_grdfloat */
		}
#ifdef DEBUG
		R->pos = ftell (R->fp);	/* Update where we are */
#endif
	}
	if (R->check) {	/* Replace NaN-marker with actual NaN */
		for (col = 0; col < G->header->n_columns; col++)
			if (row[col] == G->header->nan_value)
				row[col] = GMT->session.f_NaN;
	}
	gmt_scale_and_offset_f (GMT, row, G->header->n_columns, G->header->z_scale_factor, G->header->z_add_offset);
	if (R->auto_advance) R->row++;
	return (GMT_NOERROR);
}

#ifdef FORTRAN_API
int GMT_Get_Row_ (int *rec_no, struct GMT_GRID *G, gmt_grdfloat *row) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Get_Row (GMT_FORTRAN, *rec_no, G, row));
}
#endif

/*! . */
int GMT_Put_Row (void *V_API, int rec_no, struct GMT_GRID *G, gmt_grdfloat *row) {
	/* Writes the entire row vector to the grdfile.
	 * If row_no is NEGATIVE it is interpreted to mean that we just want to
	 * fseek to the start of the abs(row_no) record and no reading takes place.
	 * If R->auto_advance is false we must set R->start explicitly to row_no.
	 * If R->auto_advance is true it writes at the current row and advances R->row++.
	 * In this case row_no is not actually used.
	 */

	unsigned int err;	/* Required by gmt_M_err_trap */
	unsigned int col;
	size_t n_items;
	struct GMTAPI_CTRL *API = NULL;
	char *fmt = NULL;
	struct GMT_GRID_ROWBYROW *R = NULL;
	struct GMT_GRID_HIDDEN *GH = NULL;
	struct GMT_GRID_HEADER_HIDDEN *HH = NULL;
	struct GMT_CTRL *GMT = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;
	GMT = API->GMT;
	GH = gmt_get_G_hidden (G);
	R = gmtapi_get_rbr_ptr (GH->extra);
	HH = gmt_get_H_hidden (G->header);
	gmt_scale_and_offset_f (GMT, row, G->header->n_columns, G->header->z_scale_factor, G->header->z_add_offset);
	if (R->check) {	/* Replace NaNs with special value */
		for (col = 0; col < G->header->n_columns; col++)
			if (gmt_M_is_fnan (row[col]))
				row[col] = G->header->nan_value;
	}

	fmt = GMT->session.grdformat[G->header->type];
	switch (fmt[0]) {
		case 'c':
			if (!R->auto_advance) R->start[0] = rec_no * R->edge[0];
			gmt_M_err_trap (gmt_nc_put_vara_grdfloat (R->fid, HH->z_id, R->start, R->edge, row));
			if (R->auto_advance) R->start[0] += R->edge[0];
			break;
		case 'n':
			if (!R->auto_advance) {
				HH->row_order = k_nc_start_north ? rec_no : (int)G->header->n_rows - 1 - rec_no;
				R->start[0] = (size_t)HH->row_order;
			}
			gmt_M_err_trap (gmt_nc_put_vara_grdfloat (R->fid, HH->z_id, R->start, R->edge, row));
			if (R->auto_advance) R->start[0] -= HH->row_order;
			break;
		default:
			if (!R->auto_advance && fseek (R->fp, (off_t)(GMT_GRID_HEADER_SIZE + rec_no * R->n_byte), SEEK_SET)) return (GMT_GRDIO_SEEK_FAILED);
			n_items = G->header->n_columns;
			if (fmt[1] == GMT_GRD_FORMAT) {	/* Regular grdfloats */
				if (gmt_M_fwrite (row, R->size, n_items, R->fp) < n_items) return (GMT_GRDIO_WRITE_FAILED);
			}
			else {
				for (col = 0; col < G->header->n_columns; col++) gmtlib_encode (GMT, R->v_row, col, row[col], fmt[1]);
				if (gmt_M_fwrite (R->v_row, R->size, n_items, R->fp) < n_items) return (GMT_GRDIO_WRITE_FAILED);
			}
			break;
	}
	if (R->auto_advance) R->row++;

	return (GMT_NOERROR);
}

#ifdef FORTRAN_API
int GMT_Put_Row_ (int *rec_no, struct GMT_GRID *G, gmt_grdfloat *row) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Put_Row (GMT_FORTRAN, *rec_no, G, row));
}
#endif

GMT_LOCAL char * gmtapi_ptrvoid (char ** p) { 	/* Handle as char ** just to determine if address is of a NULL pointer */
	return *p;
}

/*! . */
int GMT_Destroy_Data (void *V_API, void *object) {
	/* Destroy a resource that is no longer needed.
	 * Returns the error code.  If passed an object allocated at a higher level then
	 * we quietly return no error.
	 */
	int error, item, object_ID = GMT_NOTSET;
	enum GMT_enum_family family;
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);	/* This is a cardinal sin */
	if (object == NULL) return_error (API, GMT_NOERROR);	/* Null address, quietly skip */
	if (!gmtapi_ptrvoid(object)) return_error (API, GMT_NOERROR);	/* Null pointer, quietly skip */
	API = gmtapi_get_api_ptr (V_API);		/* Now we need to get that API pointer to check further */
	if ((object_ID = gmtapi_get_object_id_from_data_ptr (API, object)) == GMT_NOTSET) return_error (API, GMT_OBJECT_NOT_FOUND);	/* Could not find the object in the list */
	if ((item = gmtlib_validate_id (API, GMT_NOTSET, object_ID, GMT_NOTSET, GMT_NOTSET)) == GMT_NOTSET) return_error (API, API->error);	/* Could not find that item */
	family = API->object[item]->actual_family;

	switch (family) {	/* Standard 6 families, plus matrix/vector and coordinates */
		case GMT_IS_GRID:	/* GMT grid */
			error = gmtapi_destroy_grid (API, object);
			break;
		case GMT_IS_DATASET:
			error = gmtapi_destroy_dataset (API, object);
			break;
		case GMT_IS_PALETTE:
			error = gmtapi_destroy_palette (API, object);
			break;
		case GMT_IS_IMAGE:
			error = gmtapi_destroy_image (API, object);
			break;
		case GMT_IS_POSTSCRIPT:
			error = gmtapi_destroy_postscript (API, object);
			break;
		case GMT_IS_CUBE:
			error = gmtapi_destroy_cube (API, object);
			break;

		/* Also allow destroying of intermediate vector and matrix containers */
		case GMT_IS_MATRIX:
			error = gmtapi_destroy_matrix (API, object);
			break;
		case GMT_IS_VECTOR:
			error = gmtapi_destroy_vector (API, object);
			break;
		case GMT_IS_COORD:
			error = gmtapi_destroy_coord (API, object);
			break;
		default:
			return_error (API, GMT_NOT_A_VALID_FAMILY);
			break;
	}
	if (error == GMT_NOERROR) {	/* We successfully freed the items, now remove from IO list */
		unsigned int j;
		void *address = API->object[item]->resource;
		GMT_Report (API, GMT_MSG_DEBUG, "GMT_Destroy_Data: freed memory for a %s for object %d\n", GMT_family[family], object_ID);
		if ((error = gmtlib_unregister_io (API, object_ID, (unsigned int)GMT_NOTSET))) return_error (API, error);	/* Did not find object */
		for (j = 0; j < API->n_objects; j++) {
			if (API->object[j]->resource == address) API->object[j]->resource = NULL;	/* Set matching resources to NULL so we don't try to read from there again either */
		}
#ifdef DEBUG
		gmtapi_list_objects (API, "GMT_Destroy_Data");
#endif

	}
	else if (error != GMT_FREE_WRONG_LEVEL) {
		/* Quietly ignore these errors: GMT_PTR_IS_NULL, GMT_FREE_EXTERNAL_NOT_ALLOWED as they are not considered errors here. */
		GMT_Report (API, GMT_MSG_DEBUG, "GMT_Destroy_Data: Ignored warning %d for object %d\n", error, object_ID);
	}
	else
		GMT_Report (API, GMT_MSG_DEBUG, "GMT_Destroy_Data: Skipped due to wrong level for object %d\n", object_ID);
	return_error (API, GMT_NOERROR);
}

#ifdef FORTRAN_API
int GMT_Destroy_Data_ (void *object) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Destroy_Data (GMT_FORTRAN, object));
}
#endif

/*! . */
int GMT_Free (void *V_API, void *ptr) {
	struct GMTAPI_CTRL *API = NULL;
	void *address = NULL;
	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);	/* This is a cardinal sin */
	if (ptr == NULL) return_error (V_API, GMT_NOERROR);	/* Null address, quietly skip */
	if ((address = gmtapi_ptrvoid(ptr)) == NULL) return_error (V_API, GMT_NOERROR);	/* Null pointer, quietly skip */
	API = gmtapi_get_api_ptr (V_API);		/* Now we need to get that API pointer to check further */
	gmt_M_free (API->GMT, address);
	return_error (API, GMT_NOERROR);
}

#ifdef FORTRAN_API
int GMT_Free_ (void *ptr) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Free (GMT_FORTRAN, ptr));
}
#endif

GMT_LOCAL int gmtapi_destroy_grids (struct GMTAPI_CTRL *API, struct GMT_GRID ***obj, unsigned int n_items) {
	/* Used to destroy a group of grids read via GMT_Read_Group */
	unsigned int k;
	int error;
	struct GMT_GRID **G = *obj;
	for (k = 0; k < n_items; k++) if ((error = GMT_Destroy_Data (API, &G[k]))) return_error (API, error);
	gmt_M_free (API->GMT, G);	*obj = NULL;
	return_error (API, GMT_NOERROR);
}

GMT_LOCAL int gmtapi_destroy_datasets (struct GMTAPI_CTRL *API, struct GMT_DATASET ***obj, unsigned int n_items) {
	/* Used to destroy a group of datasets read via GMT_Read_Group */
	unsigned int k;
	int error;
	struct GMT_DATASET **D = *obj;
	for (k = 0; k < n_items; k++) if ((error = GMT_Destroy_Data (API, &D[k]))) return_error (API, error);
	gmt_M_free (API->GMT, D);	*obj = NULL;
	return_error (API, GMT_NOERROR);
}

GMT_LOCAL int gmtapi_destroy_images (struct GMTAPI_CTRL *API, struct GMT_IMAGE ***obj, unsigned int n_items) {
	/* Used to destroy a group of images read via GMT_Read_Group */
	unsigned int k;
	int error;
	struct GMT_IMAGE **I = *obj;
	for (k = 0; k < n_items; k++) if ((error = GMT_Destroy_Data (API, &I[k]))) return_error (API, error);
	gmt_M_free (API->GMT, I);	*obj = NULL;
	return_error (API, GMT_NOERROR);
}

GMT_LOCAL int gmtapi_destroy_palettes (struct GMTAPI_CTRL *API, struct GMT_PALETTE ***obj, unsigned int n_items) {
	unsigned int k;
	int error;
	struct GMT_PALETTE **C = *obj;
	for (k = 0; k < n_items; k++) if ((error = GMT_Destroy_Data (API, &C[k]))) return_error (API, error);
	gmt_M_free (API->GMT, C);	*obj = NULL;
	return_error (API, GMT_NOERROR);
}

GMT_LOCAL int gmtapi_destroy_postscripts (struct GMTAPI_CTRL *API, struct GMT_POSTSCRIPT ***obj, unsigned int n_items) {
	/* Used to destroy a group of palettes read via GMT_Read_Group */
	unsigned int k;
	int error;
	struct GMT_POSTSCRIPT **P = *obj;
	for (k = 0; k < n_items; k++) if ((error = GMT_Destroy_Data (API, &P[k]))) return_error (API, error);
	gmt_M_free (API->GMT, P);	*obj = NULL;
	return_error (API, GMT_NOERROR);
}

GMT_LOCAL int gmtapi_destroy_cubes (struct GMTAPI_CTRL *API, struct GMT_CUBE ***obj, unsigned int n_items) {
	/* Used to destroy a group of grids read via GMT_Read_Group */
	unsigned int k;
	int error;
	struct GMT_CUBE **U = *obj;
	for (k = 0; k < n_items; k++) if ((error = GMT_Destroy_Data (API, &U[k]))) return_error (API, error);
	gmt_M_free (API->GMT, U);	*obj = NULL;
	return_error (API, GMT_NOERROR);
}

GMT_LOCAL int gmtapi_destroy_matrices (struct GMTAPI_CTRL *API, struct GMT_MATRIX ***obj, unsigned int n_items) {
	/* Used to destroy a group of matrices read via GMT_Read_Group */
	unsigned int k;
	int error;
	struct GMT_MATRIX **M = *obj;
	for (k = 0; k < n_items; k++) if ((error = GMT_Destroy_Data (API, &M[k]))) return_error (API, error);
	gmt_M_free (API->GMT, M);	*obj = NULL;
	return_error (API, GMT_NOERROR);
}

GMT_LOCAL int gmtapi_destroy_vectors (struct GMTAPI_CTRL *API, struct GMT_VECTOR ***obj, unsigned int n_items) {
	/* Used to destroy a group of vectors read via GMT_Read_Group */
	unsigned int k;
	int error;
	struct GMT_VECTOR **V = *obj;
	for (k = 0; k < n_items; k++) if ((error = GMT_Destroy_Data (API, &V[k]))) return_error (API, error);
	gmt_M_free (API->GMT, V);	*obj = NULL;
	return_error (API, GMT_NOERROR);
}

GMT_LOCAL void ** gmtapi_void3_to_void2 (void ***p) { return (*p); }	/* To avoid warnings and troubles */

/*! . */
int GMT_Destroy_Group (void *V_API, void *object, unsigned int n_items) {
	/* Destroy an array of resources that are no longer needed.
	 * Returns the error code.
	 */
	int error, object_ID, item;
	void **ptr = NULL;
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);	/* This is a cardinal sin */
	if (object == NULL) return (false);	/* Null address, quietly skip */
	API = gmtapi_get_api_ptr (V_API);		/* Now we need to get that API pointer to check further */
	ptr = gmtapi_void3_to_void2 (object);		/* Get the array of pointers */
	if ((object_ID = gmtapi_get_object_id_from_data_ptr (API, ptr)) == GMT_NOTSET) return_error (API, GMT_OBJECT_NOT_FOUND);	/* Could not find the object in the list */
	if ((item = gmtlib_validate_id (API, GMT_NOTSET, object_ID, GMT_NOTSET, GMT_NOTSET)) == GMT_NOTSET) return_error (API, API->error);	/* Could not find that item */
	switch (API->object[item]->actual_family) {
		case GMT_IS_GRID:       error = gmtapi_destroy_grids       (API, object, n_items); break;
		case GMT_IS_DATASET:    error = gmtapi_destroy_datasets    (API, object, n_items); break;
		case GMT_IS_IMAGE:      error = gmtapi_destroy_images      (API, object, n_items); break;
		case GMT_IS_PALETTE:    error = gmtapi_destroy_palettes    (API, object, n_items); break;
		case GMT_IS_CUBE:       error = gmtapi_destroy_cubes       (API, object, n_items); break;
		case GMT_IS_POSTSCRIPT: error = gmtapi_destroy_postscripts (API, object, n_items); break;
		case GMT_IS_MATRIX:     error = gmtapi_destroy_matrices    (API, object, n_items); break;
		case GMT_IS_VECTOR:     error = gmtapi_destroy_vectors     (API, object, n_items); break;
		default: return_error (API, GMT_NOT_A_VALID_FAMILY); break;
	}
	return_error (API, error);
}

#ifdef FORTRAN_API
int GMT_Destroy_Group_ (void *object, unsigned int *n_items) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Destroy_Group (GMT_FORTRAN, object, *n_items));
}
#endif

/*! . */
void * GMT_Create_Data (void *V_API, unsigned int family, unsigned int geometry, unsigned int mode, uint64_t dim[], double *range, double *inc, unsigned int registration, int pad, void *data) {
	/* Create an empty container of the requested kind and allocate space for content.
	 * The known families are GMT_IS_{DATASET,GRID,PALETTE,IMAGE,POSTSCRIPT}, but we
	 * also allow for creation of the containers for GMT_IS_{VECTOR,MATRIX}. Note
	 * that for VECTOR|MATRIX we don't allocate space to hold data as it is the users
	 * responsibility to hook their data pointers in.  The VECTOR allocates the array
	 * of column vector type and data pointers.
	 * Geometry should reflect the resource, e.g. GMT_IS_SURFACE for grid, etc.
	 * There are two ways to define the dimensions needed to actually allocate memory:
	 * (A) Via uint64_t dim[]:
	 *     The dim array contains up to 4 dimensions for:
	 *	   0: dim[GMT_TBL] = number of tables,
	 *	   1: dim[GMT_SEG] = number of segments per table
	 *	   2: dim[GMT_ROW] = number of rows per segment.
	 *	   3: dim[GMT_COL] = number of columns per row.
	 *     The dim array is ignored for CPTs.
	 *     For GMT_IS_IMAGE & GMT_IS_MATRIX, par[GMT_Z] = GMT[2] holds the number of bands or layers (dim == NULL means just 1).
	 *     For GMT_IS_GRID, GMT_IS_IMAGE, & GMT_IS_MATRIX: dim[0] holds the number of columns and dim[1] holds the number
	 *         of rows; this implies that wesn = 0-<dim-1>, inc = 1, and registration is pixel-registration.
	 *     For GMT_IS_VECTOR, dim[0] holds the number of columns, optionally dim[1] holds number of rows, if known, or 0.
	 *	   dim[2] can hold the data type (GMT_DOUBLE, etc). If dim[1] > 0 then we allocate the rows.
	 * (B) Via range, inc, registration:
	 *     Convert user domain range, increments, and registration into dimensions
	 *     for the container.  For grids and images we fill out the GMT_GRID_HEADER;
	 *     for vectors and matrices we fill out their internal parameters.
	 *     For complex grids pass registration + GMT_GRID_IS_COMPLEX_{REAL|IMAG}
	 *     For GMT_IS_MATRIX and GMT_IS_IMAGE, dim[GMT_Z] = holds the number of layers or bands (dim == NULL means just 1),
	 *     and dim[3] holds the data type (dim == NULL means GMT_DOUBLE).
	 *     For GMT_IS_VECTOR, dim[GMT_Z] holds the data type (dim == NULL means GMT_DOUBLE).
	 * pad sets the padding for grids and images, while for matrices it can be
	 *     0 for the default row/col orientation
	 *     1 for row-major format (C)
	 *     2 for column major format (FORTRAN)
	   pad is ignored for other resources.
	 * Some default actions for grids:
	 * range = NULL: Select current -R setting if present.
	 * registration = GMT_NOTSET: Gridline unless -r is in effect.
	 * Give -1 (GMT_NOTSET) to accept GMT default padding [2].
	 *
	 * For creating grids and images you can do it in one or two steps:
 	 * (A) Pass mode = GMT_CONTAINER_AND_DATA; this creates both header and allocates grid|image;
	 * (B) Call GMT_Create_Data twice:
	 * 	1. First with mode = GMT_CONTAINER_ONLY which creates header only
	 *	   and computes the dimensions based on the other arguments.
	 *	2. 2nd with mode = GMT_DATA_ONLY, which allocates the grid|image array
	 *	   based on the dimensions already set.  This time you pass NULL/0
	 *	   for dim, wesn, inc, registration, pad but let data be your grid|image
	 *	   returned to you after step 1.
	 *
	 * By default, the created resource is consider an input resource (direction == GMT_IN).
	 * However, for the interface containers GMT_VECTOR and GMT_MATRIX they will have their
	 * direction set to GMT_OUT if the row-dimension is not set.
	 *
	 * For containers GMT_IS_DATASET, GMT_IS_MATRIX and GMT_IS VECTOR: If you add the constant
	 * GMT_WITH_STRINGS to the mode it will allocate the corresponding arrays of string pointers.
	 * You can then add actual strings in addition to data values.  Note: GMT will assume
	 * the individual strings was allocated using functions like malloc or strdup and will
	 * free them when the container goes out of scope.  If you don't want that to happen then
	 * you must set those pointers to NULL beforehand.
	 *
	 * Return: Pointer to resource, or NULL if an error (set via API->error).
	 */

	int error = GMT_NOERROR;
	int def_direction = GMT_IN;	/* Default direction is GMT_IN  */
	unsigned int module_input, actual_family, i_mode;
	uint64_t n_layers = 0, zero_dim[4] = {0, 0, 0, 0}, *this_dim = dim;
	int64_t n_cols = 0;
	bool already_registered = false, has_ID = false;
	struct GMT_CUBE *C = NULL;
	struct GMT_CUBE_HIDDEN *HU = NULL;
	struct GMT_GRID *G = NULL;
	void *new_obj = NULL;
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;
	i_mode = (family & GMT_IMAGE_ALPHA_LAYER);
	family -= i_mode;
	module_input = (family & GMT_VIA_MODULE_INPUT);	/* Are we creating a resource that is a module input? */
	family -= module_input;
	actual_family = gmtapi_separate_families (&family);

	if (mode & GMT_IS_OUTPUT) {	/* Flagged to be an output container */
		def_direction = GMT_OUT;	/* Set output as default direction*/
		if (data) return_null (API, GMT_PTR_NOT_NULL);	/* Error if data pointer is not NULL */
		if (dim && !(actual_family == GMT_IS_MATRIX || actual_family == GMT_IS_VECTOR))
			return_null (API, GMT_PTR_NOT_NULL);	/* Error if dim pointer is not NULL except for matrix and vector */
		if (this_dim == NULL) this_dim = zero_dim;	/* Provide dimensions set to zero */
	}

	if (mode & GMT_DATA_IS_GEO) gmt_set_geographic (API->GMT, def_direction);	/* From API to tell the data are geographic */

	/* Below, data can only be non-NULL for Grids or Images passing back G or I to allocate the data array */

	switch (actual_family) {	/* dataset, cpt, text, grid , image, vector, matrix */
		case GMT_IS_GRID:	/* GMT grid, allocate header but not data array */
			if (mode & GMT_WITH_STRINGS) return_null (API, GMT_NO_STRINGS_ALLOWED);	/* Error if given unsuitable mode */
			if (mode & GMT_IS_OUTPUT || (mode & GMT_DATA_ONLY) == 0) {	/* Create new grid unless we only ask for data only */
				if (data) return_null (API, GMT_PTR_NOT_NULL);	/* Error if data pointer is not NULL */
	 			if ((new_obj = gmt_create_grid (API->GMT)) == NULL)
	 				return_null (API, GMT_MEMORY_ERROR);	/* Allocation error */
				if (pad != GMT_NOTSET) gmt_set_pad (API->GMT, pad);	/* Change the default pad; give -1 to leave as is */
				if ((error = gmtapi_init_grid (API, NULL, this_dim, range, inc, registration, mode, def_direction, new_obj)))
					return_null (API, error);
				if (pad != GMT_NOTSET) gmt_set_pad (API->GMT, API->pad);	/* Reset to the default pad */
			}
			else {	/* Already registered so has_ID must be false */
				if (has_ID || (new_obj = data) == NULL)
					return_null (API, GMT_PTR_IS_NULL);	/* Error if data pointer is NULL */
				already_registered = true;
			}
			if (def_direction == GMT_IN && (mode & GMT_CONTAINER_ONLY) == 0) {	/* Allocate the grid array unless we asked for header only */
				if ((error = gmtapi_alloc_grid (API->GMT, new_obj)) != GMT_NOERROR)
					return_null (API, error);	/* Allocation error */
				/* Also allocate and populate the x,y vectors */
				if ((error = gmtapi_alloc_grid_xy (API, new_obj)) != GMT_NOERROR)
					return_null (API, error);	/* Allocation error */
			}
			break;
		case GMT_IS_IMAGE:	/* GMT image, allocate header but not data array */
			if (mode & GMT_WITH_STRINGS) return_null (API, GMT_NO_STRINGS_ALLOWED);	/* Error if given unsuitable mode */
			if (mode & GMT_IS_OUTPUT || (mode & GMT_DATA_ONLY) == 0) {	/* Create new image unless we only ask for data only */
				if (data) return_null (API, GMT_PTR_NOT_NULL);	/* Error if data is not NULL */
	 			if ((new_obj = gmtlib_create_image (API->GMT)) == NULL)
	 				return_null (API, GMT_MEMORY_ERROR);	/* Allocation error */
				if (pad != GMT_NOTSET) gmt_set_pad (API->GMT, pad);	/* Change the default pad; give -1 to leave as is */
				if ((error = gmtapi_init_image (API, NULL, this_dim, range, inc, registration, mode, def_direction, new_obj)))
					return_null (API, error);
				if (pad != GMT_NOTSET) gmt_set_pad (API->GMT, API->pad);	/* Reset to the default pad */
			}
			else {
				if ((new_obj = data) == NULL)
					return_null (API, GMT_PTR_IS_NULL);	/* Error if data is NULL */
				already_registered = true;
			}
			if (def_direction == GMT_IN && (mode & GMT_CONTAINER_ONLY) == 0) {	/* Allocate the image array unless we asked for header only */
				if ((error = gmtapi_alloc_image (API->GMT, dim, i_mode, new_obj)) != GMT_NOERROR)
					return_null (API, error);	/* Allocation error */
					/* Also allocate and populate the image x,y vectors */
				if ((error = gmtapi_alloc_image_xy (API, new_obj)) != GMT_NOERROR)
					return_null (API, error);	/* Allocation error */
			}
			break;
		case GMT_IS_DATASET:	/* GMT dataset, allocate the requested tables, segments, rows, and columns */
			if (data) return_null (API, GMT_PTR_NOT_NULL);	/* Error if data is not NULL */
			if (this_dim[GMT_TBL] > UINT_MAX || this_dim[GMT_ROW] > UINT_MAX)
				return_null (API, GMT_DIM_TOO_LARGE);
			/* We basically create a blank slate(s), with n_tables, n_segments, and n_columns set [unless 0], but all n_rows == 0 (even if known; S->n_alloc has the lengths) */
			if ((new_obj = gmtlib_create_dataset (API->GMT, this_dim[GMT_TBL], this_dim[GMT_SEG], this_dim[GMT_ROW], this_dim[GMT_COL], geometry, mode & GMT_WITH_STRINGS, false)) == NULL) {
				return_null (API, GMT_MEMORY_ERROR);	/* Allocation error */
			}
			else if (gmt_M_is_geographic (API->GMT, def_direction)) {	/* Got a geographic data set, set hidden flag */
				struct GMT_DATASET *D = gmtapi_get_dataset_data (new_obj);
				struct GMT_DATASET_HIDDEN *DH = gmt_get_DD_hidden (D);
				DH->geographic = 1;
			}
			break;
		case GMT_IS_PALETTE:	/* GMT CPT, allocate one with space for dim[0] color entries */
			if (mode & GMT_WITH_STRINGS) return_null (API, GMT_NO_STRINGS_ALLOWED);	/* Error if given unsuitable mode */
			if (data) return_null (API, GMT_PTR_NOT_NULL);	/* Error if data is not NULL */
			/* If dim is NULL then we ask for 0 color entries as direction here is GMT_OUT for return to an external API */
		 	if ((new_obj = gmtlib_create_palette (API->GMT, this_dim[0])) == NULL)
		 		return_null (API, GMT_MEMORY_ERROR);	/* Allocation error */
			break;
		case GMT_IS_POSTSCRIPT:	/* GMT PS struct, allocate one struct */
			if (mode & GMT_WITH_STRINGS) return_null (API, GMT_NO_STRINGS_ALLOWED);	/* Error if given unsuitable mode */
			if (data) return_null (API, GMT_PTR_NOT_NULL);	/* Error if data is not NULL */
		 	if ((new_obj = gmtlib_create_ps (API->GMT, this_dim[0])) == NULL)
		 		return_null (API, GMT_MEMORY_ERROR);	/* Allocation error */
			break;
		case GMT_IS_MATRIX:	/* GMT matrix container, allocate one with the requested number of layers, rows & columns */
			if (data) return_null (API, GMT_PTR_NOT_NULL);	/* Error if data is not NULL */
			n_layers = (this_dim == NULL || (this_dim[GMTAPI_DIM_COL] == 0 && this_dim[GMTAPI_DIM_ROW] == 0)) ? 1U : this_dim[GMT_Z];	/* Only by specifying nx,ny dimension might there be > 1 layer */
		 	new_obj = gmtlib_create_matrix (API->GMT, n_layers, pad);
			if ((API->error = gmtapi_init_matrix (API, this_dim, range, inc, registration, mode, def_direction, new_obj))) {	/* Failure, must free the object */
				struct GMT_MATRIX *M = gmtapi_return_address (new_obj, GMT_IS_MATRIX);	/* Get pointer to resource */
				gmtlib_free_matrix (API->GMT, &M, true);
		 		return_null (API, GMT_MEMORY_ERROR);	/* Allocation error */
			}
			break;
		case GMT_IS_VECTOR:	/* GMT vector container, allocate one with the requested number of columns & rows */
			if (data) return_null (API, GMT_PTR_NOT_NULL);	/* Error if data is not NULL */
			n_cols = gmtapi_vector_ncols (dim, def_direction);
			if (n_cols == GMT_NOTSET) return_null (API, GMT_N_COLS_NOT_SET);
	 		new_obj = gmt_create_vector (API->GMT, n_cols, def_direction);
			if (pad) GMT_Report (API, GMT_MSG_DEBUG, "Pad argument (%d) ignored in initialization of %s\n", pad, GMT_family[family]);
			if ((API->error = gmtapi_init_vector (API, this_dim, range, inc, registration, mode, def_direction, new_obj))) {	/* Failure, must free the object */
				struct GMT_VECTOR *V = gmtapi_return_address (new_obj, GMT_IS_VECTOR);	/* Get pointer to resource */
				gmt_free_vector (API->GMT, &V, true);
		 		return_null (API, GMT_MEMORY_ERROR);	/* Allocation error */
			}
			break;
		case GMT_IS_CUBE:
			if (mode & GMT_WITH_STRINGS) return_null (API, GMT_NO_STRINGS_ALLOWED);	/* Error if given unsuitable mode */
			if (mode & GMT_IS_OUTPUT || (mode & GMT_DATA_ONLY) == 0) {	/* Create new cube unless we only ask for data only */
				if (data) return_null (API, GMT_PTR_NOT_NULL);	/* Error if data pointer is not NULL */
	 			if ((C = gmtlib_create_cube (API->GMT)) == NULL)
	 				return_null (API, GMT_MEMORY_ERROR);	/* Allocation error */
				if (def_direction == GMT_IN) {  /* Need to supply header information */
					if (range == NULL) return_null (API, GMT_PTR_IS_NULL);	/* Need at least the z-range for cubes */
					if (pad != GMT_NOTSET) gmt_set_pad (API->GMT, pad); /* Change the default pad; give -1 to leave as is */
					if ((G = gmt_create_grid (API->GMT)) == NULL)   /* Create a temporary helper grid */
						return_null (API, GMT_MEMORY_ERROR);    /* Allocation error */
					if ((error = gmtapi_init_grid (API, NULL, this_dim, range, inc, registration, mode, def_direction, G)))
						return_null (API, error);
					if (pad != GMT_NOTSET) gmt_set_pad (API->GMT, API->pad);    /* Reset to the default pad */
					gmt_copy_gridheader (API->GMT, C->header, G->header);
					C->z_range[0] = range[ZLO];	C->z_range[1] = range[ZHI];
					if (inc && inc[GMT_Z] > 0.0) {	/* Must make equidistant array, else we leave it as NULL to be set by calling module */
						HU = gmt_get_U_hidden (C);
						C->header->n_bands = gmt_make_equidistant_array (API->GMT, range[ZLO], range[ZHI], inc[GMT_Z], &(C->z));
						C->z_inc = inc[GMT_Z];
						HU->xyz_alloc_mode[GMT_Z] = GMT_ALLOC_INTERNALLY;
					}
				}
			}
			else {	/* Already registered so has_ID must be false */
				if (has_ID || (C = data) == NULL)
					return_null (API, GMT_PTR_IS_NULL);	/* Error if data pointer is NULL */
				already_registered = true;
			}
			if (def_direction == GMT_IN && (mode & GMT_CONTAINER_ONLY) == 0) {	/* Allocate the grid array unless we asked for header only */
				size_t chunk = C->header->size * ((size_t)C->header->n_bands);	/* Total memory needed for the entire cube */
				if ((C->data = gmt_M_memory_aligned (API->GMT, NULL, chunk, gmt_grdfloat)) == NULL)
					return_null (API, error);	/* Allocation error */
				/* Also allocate and populate the x,y vectors */
				if ((error = gmtapi_alloc_grid_xy (API, G)) != GMT_NOERROR)
					return_null (API, error);	/* Allocation error */
				C->x = G->x;	C->y = G->y;	/* Let these be the cube's arrays from now on */
				G->x = G->y = NULL;	/* No longer anything to do with G */
				HU = gmt_get_U_hidden (C);
				HU->xyz_alloc_mode[GMT_X] = HU->xyz_alloc_mode[GMT_Y] = HU->xyz_alloc_mode[GMT_Z] = HU->alloc_mode = GMT_ALLOC_INTERNALLY;
				HU->alloc_level = API->GMT->hidden.func_level;   /* Must be freed at this level. */
				if (gmtapi_destroy_grid (API, &G))	/* Use this instead of GMT_Destroy_Data since G was local and never registered */
		 			return_null (API, GMT_MEMORY_ERROR);	/* Allocation error */
			}
			new_obj = C;	/* Finally assign to new_obj */
			break;

		default:
 			return_null (API, GMT_NOT_A_VALID_FAMILY);
			break;
	}
	assert (API->error == GMT_NOERROR);	/* All errors were dealt with so why this? */

	if (!already_registered) {	/* Register this object so it can be deleted by GMT_Destroy_Data or gmtlib_garbage_collection */
		enum GMT_enum_method method = (mode & GMT_IS_OUTPUT) ? GMT_IS_DUPLICATE : GMT_IS_REFERENCE;	/* Since it is a memory object */
		int item = GMT_NOTSET, object_ID = GMT_NOTSET;
		struct GMTAPI_DATA_OBJECT *S_obj = NULL;
		if ((object_ID = GMT_Register_IO (API, actual_family|module_input, method, geometry, def_direction, range, new_obj)) == GMT_NOTSET)
			return_null (API, API->error);	/* Failure to register */
		if ((item = gmtlib_validate_id (API, actual_family, object_ID, def_direction, GMT_NOTSET)) == GMT_NOTSET)
			return_null (API, API->error);
		S_obj = API->object[item];		/* Short-hand notation */
		API->object[item]->resource = new_obj;	/* Retain pointer to the allocated data so we use garbage collection later */
		S_obj->actual_family = actual_family;
		S_obj->family = family;
		if (def_direction == GMT_OUT) S_obj->messenger = true;	/* We are passing a dummy container that should be destroyed before returning actual data */
		if (family == actual_family)
			GMT_Report (API, GMT_MSG_DEBUG, "Successfully created a new %s container\n", GMT_family[actual_family]);
		else
			GMT_Report (API, GMT_MSG_DEBUG, "Successfully created a new %s container to represent a %s\n", GMT_family[actual_family], GMT_family[family]);
#ifdef DEBUG
		gmtapi_set_object (API, S_obj);
#endif
	}
	else
		GMT_Report (API, GMT_MSG_DEBUG, "Successfully added data array to previously registered %s container\n", GMT_family[family]);
#ifdef DEBUG
	gmtapi_list_objects (API, "GMT_Create_Data");
#endif

	return (new_obj);
}

#ifdef FORTRAN_API
void * GMT_Create_Data_ (unsigned int *family, unsigned int *geometry, unsigned int *mode, uint64_t *dim, double *range, double *inc, unsigned int *registration, int *pad, void *container) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Create_Data (GMT_FORTRAN, *family, *geometry, *mode, dim, range, inc, *registration, *pad, container));
}
#endif

int GMT_Get_Info (void *V_API, unsigned int family, void *data, unsigned int *geometry, uint64_t dim[], double *range, double *inc, unsigned int *registration, int *pad) {
	/* Return information for this object identified by family and data pointer.
	 * The known families are GMT_IS_{DATASET,GRID,PALETTE,IMAGE,POSTSCRIPT,GMT_IS_CUBE,GMT_IS_{VECTOR,MATRIX}.
	 * Not all output args may be set as it depends on the family, and any output argument that
	 * is NULL is always skipped.  This function is mostly useful for applications where the containers
	 * are not easily inspected directly, e.g., we are calling from another programming language.
	 */

	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (data == NULL) return_error (API, GMT_PTR_IS_NULL);	/* Error if data is NULL */
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;

	switch (family) {	/* dataset, cpt, text, grid , image, vector, matrix */
		case GMT_IS_GRID:	/* GMT grid, allocate header but not data array */
			{	/* Deal with a local grid pointer */
				struct GMT_GRID *G = gmtapi_get_grid_data (data);
				if (dim) { dim[GMT_X] = G->header->n_columns; dim[GMT_Y] = G->header->n_rows; }
				if (range) gmt_M_memcpy (range, G->header->wesn, 4U, double);
				if (inc) gmt_M_memcpy (inc, G->header->inc, 2U, double);
				if (geometry) *geometry = GMT_IS_SURFACE;
				if (registration) *registration = G->header->registration;
				if (pad) {	/* Need to check they are all the same, if not return undefined or something */
					unsigned int bad = 0, k;
					for (k = XHI; k <= YHI; k++) if (G->header->pad[k] != G->header->pad[XLO]) bad++;
					if (bad) {
						GMT_Report (API, GMT_MSG_WARNING, "Grid sides have different padding, return pad as not set [-1]\n");
						*pad = GMT_NOTSET;
					}
					else
						*pad = G->header->pad[XLO];
				}
			}
			break;
		case GMT_IS_IMAGE:	/* GMT image, allocate header but not data array */
			{	/* Deal with a local image pointer */
				struct GMT_IMAGE *I = gmtapi_get_image_data (data);
				if (dim) { dim[GMT_X] = I->header->n_columns; dim[GMT_Y] = I->header->n_rows; dim[GMT_Z] = I->header->n_bands; }
				if (range) gmt_M_memcpy (range, I->header->wesn, 4U, double);
				if (inc) gmt_M_memcpy (inc, I->header->inc, 2U, double);
				if (geometry) *geometry = GMT_IS_IMAGE;
				if (registration) *registration = I->header->registration;
				if (pad) {	/* Need to check they are all the same, if not return undefined or something */
					unsigned int bad = 0, k;
					for (k = XHI; k <= YHI; k++) if (I->header->pad[k] != I->header->pad[XLO]) bad++;
					if (bad) {
						GMT_Report (API, GMT_MSG_WARNING, "Image sides have different padding, return pad as not set [-1]\n");
						*pad = GMT_NOTSET;
					}
					else
						*pad = I->header->pad[XLO];
				}
			}
			break;
		case GMT_IS_DATASET:	/* GMT dataset, allocate the requested tables, segments, rows, and columns */
			{	/* Deal with a local image pointer */
				struct GMT_DATASET *D = gmtapi_get_dataset_data (data);
				if (dim) { dim[GMT_TBL] = D->n_tables; dim[GMT_SEG] = D->n_segments; dim[GMT_ROW] = D->n_records;  dim[GMT_COL] = D->n_columns; }
				if (geometry) *geometry = D->geometry;
			}
			break;
		case GMT_IS_PALETTE:	/* GMT CPT, allocate one with space for dim[0] color entries */
			{	/* Deal with a local palette pointer */
				struct GMT_PALETTE *P = gmtapi_get_palette_data (data);
				if (dim) dim[0] = P->n_colors;
				if (range) gmt_M_memcpy (range, P->minmax, 2U, double);
				if (geometry) *geometry = GMT_IS_NONE;
			}
			break;
		case GMT_IS_POSTSCRIPT:	/* GMT PS struct, allocate one struct */
			{	/* Deal with a local PostScript pointer */
				struct GMT_POSTSCRIPT *X = gmtapi_get_postscript_data (data);
				if (dim) dim[0] = X->n_bytes;
				if (geometry) *geometry = GMT_IS_NONE;
			}
			break;
		case GMT_IS_CUBE:	/* GMT cube, allocate header but not data array */
			{	/* Deal with a local grid pointer */
				struct GMT_CUBE *U = gmtapi_get_cube_data (data);
				if (dim) { dim[GMT_X] = U->header->n_columns; dim[GMT_Y] = U->header->n_rows; dim[GMT_Z] = U->header->n_bands; }
				if (range) {
					gmt_M_memcpy (range, U->header->wesn, 4U, double);
					gmt_M_memcpy (&range[4], U->z_range, 2U, double);
				}
				if (inc) {
					gmt_M_memcpy (inc, U->header->inc, 2U, double);
					inc[GMT_Z] = U->z_inc;
				}
				if (geometry) *geometry = GMT_IS_VOLUME;
				if (registration) *registration = U->header->registration;
				if (pad) {	/* Need to check they are all the same, if not return undefined or something */
					unsigned int bad = 0, k;
					for (k = XHI; k <= YHI; k++) if (U->header->pad[k] != U->header->pad[XLO]) bad++;
					if (bad) {
						GMT_Report (API, GMT_MSG_WARNING, "Cube x/y sides have different padding, return pad as not set [-1]\n");
						*pad = GMT_NOTSET;
					}
					else
						*pad = U->header->pad[XLO];
				}
			}
			break;
		case GMT_IS_MATRIX:	/* GMT matrix container, allocate one with the requested number of layers, rows & columns */
			{	/* Deal with a local matrix pointer */
				struct GMT_MATRIX *M = gmtapi_get_matrix_data (data);
				if (dim) { dim[GMT_X] = M->n_columns; dim[GMT_Y] = M->n_rows; dim[GMT_Z] = M->n_layers; }
				if (range) gmt_M_memcpy (range, M->range, (M->n_layers > 1) ? 6U : 4U, double);
				if (inc) gmt_M_memcpy (inc, M->inc, (M->n_layers > 1) ? 3U : 2U, double);
				if (registration) *registration = M->registration;
				if (geometry) *geometry = GMT_IS_SURFACE;
			}
			break;
		case GMT_IS_VECTOR:	/* GMT vector container, allocate one with the requested number of columns & rows */
			{	/* Deal with a local image pointer */
				struct GMT_VECTOR *V = gmtapi_get_vector_data (data);
				if (dim) { dim[GMT_X] = V->n_columns; dim[GMT_Y] = V->n_rows; }
				if (range) gmt_M_memcpy (range, V->range, 2U, double);
				if (registration) *registration = V->registration;
				if (geometry) *geometry = GMT_IS_PLP;
			}
			break;
		default:
 			return_error (API, GMT_NOT_A_VALID_FAMILY);
			break;
	}

	return (API->error);
}

#ifdef FORTRAN_API
int GMT_Get_Info_ (unsigned int *family, void *container, unsigned int *geometry, uint64_t *dim, double *range, double *inc, unsigned int *registration, int *pad) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Get_Info (GMT_FORTRAN, *family, container, geometry, dim, range, inc, registration, pad));
}
#endif

/*! Convenience function to get grid or image node */
uint64_t GMT_Get_Index (void *V_API, struct GMT_GRID_HEADER *header, int row, int col) {
	/* V_API not used but all API functions take V_API so no exceptions! */
	gmt_M_unused(V_API);
	return (GMTAPI_index_function (header, row, col, 0));
}

#ifdef FORTRAN_API
uint64_t GMT_Get_Index_ (void *h, int *row, int *col) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Get_Index (GMT_FORTRAN, h, *row, *col));
}
#endif

/*! Convenience function to get image layer node */
uint64_t GMT_Get_Pixel (void *V_API, struct GMT_GRID_HEADER *header, int row, int col, int layer) {
	/* V_API not used but all API functions take V_API so no exceptions! */
	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (header);
	gmt_M_unused(V_API);
	return (HH->index_function (header, row, col, layer));
}

#ifdef FORTRAN_API
uint64_t GMT_Get_Pixel_ (void *h, int *row, int *col, int *layer) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Get_Pixel (GMT_FORTRAN, h, *row, *col, *layer));
}
#endif

/*! Convenience function to get cube node */
uint64_t GMT_Get_Index3 (void *V_API, struct GMT_GRID_HEADER *header, int row, int col, int layer) {
	/* V_API not used but all API functions take V_API so no exceptions! */
	gmt_M_unused(V_API);
	return (GMTAPI_index_function (header, row, col, 0) + layer * header->size);
}

#ifdef FORTRAN_API
uint64_t GMT_Get_Index3_ (void *h, int *row, int *col, int *layer) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Get_Index3 (GMT_FORTRAN, h, *row, *col, *layer));
}
#endif

/*! Specify image memory layout */
int GMT_Set_Index (void *V_API, struct GMT_GRID_HEADER *header, char *code) {
	struct GMTAPI_CTRL *API = NULL;
	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (header);
	enum GMT_enum_family family;
	unsigned int mode;
	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;
	mode = gmtapi_decode_layout (API, code, &family);
	switch (family) {
		case GMT_IS_GRID:
			switch (mode) {
				case 0:	/* Default scanline C grid */
					HH->index_function = gmtapi_get_index_from_TRS;
					break;
				case 4:	/* Same for real component in complex grid */
					HH->index_function = gmtapi_get_index_from_TRR;
					break;
				case 8:	/* Same for imag component in complex grid */
					HH->index_function = gmtapi_get_index_from_TRI;
					break;
				default:
					GMT_Report (API, GMT_MSG_ERROR, "Unrecognized mode for grid layout [%u]\n", mode);
					API->error = GMT_NOT_A_VALID_MODULE;
					break;
			}
			break;
		case GMT_IS_IMAGE:
			switch (mode) {
				case 0:	/* band-interleaved layout */
					HH->index_function = gmtapi_get_index_from_TRB;
					break;
				case 4:	/* pixel-interleaved layout */
					HH->index_function = gmtapi_get_index_from_TRP;
					break;
				case 8:	/* line-interleaved layout */
					HH->index_function = gmtapi_get_index_from_TRL;
					break;
				default:
					GMT_Report (API, GMT_MSG_ERROR, "Unrecognized mode for image layout [%u]\n", mode);
					API->error = GMT_NOT_A_VALID_MODULE;
					break;
			}
			break;
		default:
			GMT_Report (API, GMT_MSG_ERROR, "Unrecognized family for gmtapi_decode_layout [%s]\n", code);
			API->error = GMT_NOT_A_VALID_FAMILY;
			break;
	}
	GMTAPI_index_function = HH->index_function;
	return API->error;
}

#ifdef FORTRAN_API
int GMT_Set_Index_ (void *h, char *code, int len) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Set_Index (GMT_FORTRAN, h, code));
}
#endif

/*! . */
double * GMT_Get_Coord (void *V_API, unsigned int family, unsigned int dim, void *container) {
	/* Return an array of coordinates for the nodes along the specified dimension.
	 * For GMT_GRID and GMT_IMAGE, dim is either 0 (GMT_X) or 1 (GMT_Y) while for
	 * GMT_MATRIX it may be 2 (GMT_Z), provided the matrix has more than 1 layer.
	 * For GMT_VECTOR that was registered as equidistant it will return coordinates
	 * along the single dimension.
	 * Cannot be used on other resources (GMT_DATASET, GMT_PALETTE).
	 */
	int object_ID, item;
	double *coord = NULL;
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION);
	if (container == NULL) return_null (V_API, GMT_ARG_IS_NULL);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;

	switch (family) {	/* grid, image, or matrix */
		case GMT_IS_GRID:	/* GMT grid */
			if (dim > GMT_Y) return_null (API, GMT_DIM_TOO_LARGE);
			coord = gmtapi_grid_coord (API, dim, container);
			break;
		case GMT_IS_IMAGE:	/* GMT image */
			if (dim > GMT_Y) return_null (API, GMT_DIM_TOO_LARGE);
			coord = gmtapi_image_coord (API, dim, container);
			break;
		case GMT_IS_VECTOR:	/* GMT vector */
			if (dim != GMT_Y) return_null (API, GMT_DIM_TOO_LARGE);
			coord = gmtapi_vector_coord (API, dim, container);
			break;
		case GMT_IS_MATRIX:	/* GMT matrix */
			if (dim > GMT_Z) return_null (API, GMT_DIM_TOO_LARGE);
			coord = gmtapi_matrix_coord (API, dim, container);
			break;
		default:
			return_null (API, GMT_NOT_A_VALID_FAMILY);
			break;
	}
	/* We register the coordinate array so that GMT_Destroy_Data can free them later */
	if ((object_ID = GMT_Register_IO (V_API, GMT_IS_COORD, GMT_IS_COORD, GMT_IS_NONE, GMT_IN, NULL, coord)) == GMT_NOTSET)
		return_null (API, API->error);
	if ((item = gmtlib_validate_id (API, GMT_IS_COORD, object_ID, GMT_IN, GMT_NOTSET)) == GMT_NOTSET)
		return_null (API, API->error);
	API->object[item]->resource = coord;	/* Retain pointer to the allocated data so we use garbage collection later */
	GMT_Report (API, GMT_MSG_DEBUG, "Successfully created a new coordinate array for %s\n", GMT_family[family]);

	return (coord);
}

#ifdef FORTRAN_API
double * GMT_Get_Coord_ (unsigned int *family, unsigned int *dim, void *container) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Get_Coord (GMT_FORTRAN, *family, *dim, container));
}
#endif

/*! . */
int GMT_Set_Comment (void *V_API, unsigned int family, unsigned int mode, void *arg, void *container) {
	/* Set new header comment or grid command|remark to container */

	int error = GMT_NOERROR;
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (container == NULL) return_error (V_API, GMT_ARG_IS_NULL);
	if (arg == NULL) return_error (V_API, GMT_ARG_IS_NULL);
	API = gmtapi_get_api_ptr (V_API);

	switch (family) {	/* grid, image, dataset, cpt, PS or matrix */
		case GMT_IS_GRID:	/* GMT grid */
			gmtapi_grid_comment (API, mode, arg, container);
			break;
		case GMT_IS_IMAGE:	/* GMT image */
			gmtapi_image_comment (API, mode, arg, container);
			break;
		case GMT_IS_DATASET:	/* GMT dataset */
			gmtapi_dataset_comment (API, mode, arg, container);
			break;
		case GMT_IS_PALETTE:	/* GMT CPT */
			gmtapi_cpt_comment (API, mode, arg, container);
			break;
		case GMT_IS_POSTSCRIPT:	/* GMT PS */
			gmtapi_ps_comment (API, mode, arg, container);
			break;
		case GMT_IS_CUBE:	/* GMT cube */
			gmtapi_cube_comment (API, mode, arg, container);
			break;
		case GMT_IS_VECTOR:	/* GMT Vector [PW: Why do we need these?]*/
			gmtapi_vector_comment (API, mode, arg, container);
			break;
		case GMT_IS_MATRIX:	/* GMT Vector */
			gmtapi_matrix_comment (API, mode, arg, container);
			break;
		default:
			error = GMT_NOT_A_VALID_FAMILY;
			break;
	}
	return_error (API, error);
}

/* FFT Extension: Functions available to do FFT work within the API */

/*! . */
unsigned int GMT_FFT_Option (void *V_API, char option, unsigned int dim, const char *string) {
	/* For programs that needs to do either 1-D or 2-D FFT work */
	unsigned int d1 = dim - 1;	/* Index into the info text strings below for 1-D (0) and 2-D (1) case */
	char *data_type[2] = {"table", "grid"}, *dim_name[2] = {"<n_columns>", "<n_columns>/<n_rows>"}, *trend_type[2] = {"line", "plane"};
	char *dim_ref[2] = {"dimension", "dimensions"}, *linear_type[2] = {"linear", "planar"}, *d_msg, *h_msg;
	struct GMTAPI_CTRL *API = NULL;
	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (dim > 2) return_error (V_API, GMT_DIM_TOO_LARGE);
	if (dim == 0) return_error (V_API, GMT_DIM_TOO_SMALL);
	API = gmtapi_get_api_ptr (V_API);
	if (API->parker_fft_default) {	/* +h is default here as a special case */
		d_msg = "";
		h_msg = " [Default]";
	}
	else {	/* +d is default */
		d_msg = " [Default]";
		h_msg = "";
	}
	if (string && string[0] == ' ') GMT_Report (V_API, GMT_MSG_ERROR, "Option -%c parsing failure.  Correct syntax:\n", option);
	GMT_Usage (API, 1, "\n-%c%s", option, GMT_FFT_OPT);
	GMT_Usage (API, -2, "Choose or inquire about suitable %s %s for %u-D FFT, and set modifiers.", data_type[d1], dim_ref[d1], dim);
	GMT_Usage (API, -2, "%s Setting the FFT %s. Append a directive:", GMT_LINE_BULLET, dim_ref[d1]);
	GMT_Usage (API, 3, "a: Select %s promising the most accurate results.", dim_ref[d1]);
	GMT_Usage (API, 3, "f: Force the FFT to use the %s of the %s.", dim_ref[d1], data_type[d1]);
	GMT_Usage (API, 3, "m: Select %s using the least work storage.", dim_ref[d1]);
	GMT_Usage (API, 3, "r: Select %s promising the most rapid calculation.", dim_ref[d1]);
	GMT_Usage (API, 3, "s: List Singleton's [1967] recommended %s, then exit.", dim_ref[d1]);
	GMT_Usage (API, -2, "Alternatively, append %s to do FFT on array size %s (Must be >= %s size) "
	   "[Default chooses %s >= %s %s to optimize speed and accuracy of the FFT.]", dim_name[d1], dim_name[d1], data_type[d1], dim_ref[d1], data_type[d1], dim_ref[d1]);
	GMT_Usage (API, -2, "%s Append modifiers for removing a %s trend:", GMT_LINE_BULLET, linear_type[d1]);
	GMT_Usage (API, 3, "+d Detrend data, i.e., remove best-fitting %s%s.", trend_type[d1], d_msg);
	GMT_Usage (API, 3, "+a Only remove mean value.");
	GMT_Usage (API, 3, "+h Only remove mid value, i.e., 0.5 * (max + min)%s.", h_msg);
	GMT_Usage (API, 3, "+l Leave data alone.");
	GMT_Usage (API, -2, "%s If FFT %s > %s %s, data are extended via edge point symmetry "
	   "and tapered to zero.  Several modifiers can be set to change this behavior:", GMT_LINE_BULLET, dim_ref[d1], data_type[d1], dim_ref[d1]);
	GMT_Usage (API, 3, "+e Extend data via edge point symmetry [Default].");
	GMT_Usage (API, 3, "+m Extend data via edge mirror symmetry.");
	GMT_Usage (API, 3, "+n Do NOT extend data.");
	GMT_Usage (API, 3, "+t Limit tapering to <width> %% of the extended margins [100]. "
	   "If +n is also set then +t instead sets the boundary width of the interior "
	  "%s margin to be tapered [0].", data_type[d1]);
	GMT_Usage (API, -2, "%s Append modifiers for saving modified %s before or after the %u-D FFT is called:", GMT_LINE_BULLET, data_type[d1], dim);
	GMT_Usage (API, 3, "+w Write the intermediate %s passed to FFT after detrending/extension/tapering. "
	  "File name will have _<suffix> [tapered] inserted before file extension.", data_type[d1]);
	GMT_Usage (API, 3, "+z Write raw complex spectrum to two separate %s files. "
	   "File name will have _real/_imag inserted before the file extensions. "
	   "Alternatively, append p to store polar forms, using _mag/_phase instead.", data_type[d1]);
	GMT_Usage (API, -2, "%s Append modifiers for messages:", GMT_LINE_BULLET);
	GMT_Usage (API, 3, "+v Report all suitable dimensions (except when -Nf is selected).");

	return_error (V_API, GMT_NOERROR);
}

#ifdef FORTRAN_API
unsigned int GMT_FFT_Option_ (char *option, unsigned int *dim, const char *string, int *length) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_FFT_Option (GMT_FORTRAN, *option, *dim, string));
}
#endif

/* first 2 cols from table III of Singleton's paper on fft.... */
#define N_SINGLETON_LIST	117
static int Singleton_list[N_SINGLETON_LIST] = {
	64,72,75,80,81,90,96,100,108,120,125,128,135,144,150,160,162,180,192,200,
	216,225,240,243,250,256,270,288,300,320,324,360,375,384,400,405,432,450,480,
	486,500,512,540,576,600,625,640,648,675,720,729,750,768,800,810,864,900,960,
	972,1000,1024,1080,1125,1152,1200,1215,1250,1280,1296,1350,1440,1458,1500,
	1536,1600,1620,1728,1800,1875,1920,1944,2000,2025,2048,2160,2187,2250,2304,
	2400,2430,2500,2560,2592,2700,2880,2916,3000,3072,3125,3200,3240,3375,3456,
	3600,3645,3750,3840,3888,4000,4096,4320,4374,4500,4608,4800,4860,5000};

GMT_LOCAL void gmtapi_fft_Singleton_list (struct GMTAPI_CTRL *API) {
	unsigned int k;
	char message[GMT_LEN16] = {""};
	GMT_Message (API, GMT_TIME_NONE, "\t\"Good\" numbers for FFT dimensions [Singleton, 1967]:\n");
	for (k = 0; k < N_SINGLETON_LIST; k++) {
		snprintf (message, GMT_LEN16, "\t%d", Singleton_list[k]);
		if ((k+1) % 10 == 0 || k == (N_SINGLETON_LIST-1)) strcat (message, "\n");
		GMT_Message (API, GMT_TIME_NONE, message);
	}
}

/*! . */
void * GMT_FFT_Parse (void *V_API, char option, unsigned int dim, const char *args) {
	/* Parse the 1-D or 2-D FFT options such as -N in grdfft */
	unsigned int n_errors = 0, pos = 0;
	char p[GMT_BUFSIZ] = {""}, *c = NULL;
	struct GMT_FFT_INFO *info = NULL;
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION);
	if (args == NULL) return_null (V_API, GMT_ARG_IS_NULL);
	if (dim == 0) return_null (V_API, GMT_DIM_TOO_SMALL);
	if (dim > 2) return_null (V_API, GMT_DIM_TOO_LARGE);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;
	if ((info = gmt_M_memory (API->GMT, NULL, 1, struct GMT_FFT_INFO)) == NULL) return_null (V_API, GMT_MEMORY_ERROR);
	info->taper_width = -1.0;				/* Not set yet */
	info->taper_mode = GMT_FFT_EXTEND_NOT_SET;		/* Not set yet */
	info->trend_mode = GMT_FFT_REMOVE_NOT_SET;		/* Not set yet */
	info->info_mode = GMT_FFT_UNSPECIFIED;			/* Not set yet */
	info->suggest = GMT_FFT_N_SUGGEST;			/* Not yet set */

	if ((c = strchr (args, '+'))) {	/* Handle modifiers */
		while ((gmt_strtok (c, "+", &pos, p))) {
			switch (p[0]) {
				/* Detrending modifiers */
				case 'a':  info->trend_mode = GMT_FFT_REMOVE_MEAN;  break;
				case 'd':  info->trend_mode = GMT_FFT_REMOVE_TREND; break;
				case 'h':  info->trend_mode = GMT_FFT_REMOVE_MID;   break;
				case 'l':  info->trend_mode = GMT_FFT_REMOVE_NOTHING;  break;
				/* Taper modifiers */
				case 'e':  info->taper_mode = GMT_FFT_EXTEND_POINT_SYMMETRY; break;
				case 'n':  info->taper_mode = GMT_FFT_EXTEND_NONE; break;
				case 'm':  info->taper_mode = GMT_FFT_EXTEND_MIRROR_SYMMETRY; break;
				case 't':	/* Set taper width */
					if ((info->taper_width = atof (&p[1])) < 0.0) {
						GMT_Report (API, GMT_MSG_ERROR, "Option -%c: Negative taper width given\n", option);
						n_errors++;
					}
					break;
				/* i/o modifiers */
				case 'w':	/* Save FFT input; optionally append file suffix */
					info->save[GMT_IN] = true;
					if (p[1]) strncpy (info->suffix, &p[1], GMT_LEN64-1);
					break;
				case 'v':  info->verbose = true; break;	/* Report FFT suggestions */
				case 'z': 	/* Save FFT output in two files; append p for polar form */
					info->save[GMT_OUT] = true;
					if (p[1] == 'p') info->polar = true;
					break;
				default:
					GMT_Report (API, GMT_MSG_ERROR, "Option -%c: Unrecognized modifier +%s.\n", option, p);
					n_errors++;
					break;
			}
		}
	}
	if (info->taper_mode == GMT_FFT_EXTEND_NOT_SET)
		info->taper_mode = GMT_FFT_EXTEND_POINT_SYMMETRY;	/* Default action is edge-point symmetry */
	if (info->taper_mode == GMT_FFT_EXTEND_NONE) {
		if (info->taper_width < 0.0) info->taper_width = 0.0;	/* No tapering unless specified */
	}
	if (info->taper_width < 0.0)
		info->taper_width = 100.0;		/* Taper over entire margin strip by default */

	switch (args[0]) {
		case '\0': info->suggest = GMT_FFT_N_SUGGEST;  break;	/* Pick dimensions for the "best" solution */
		case 'a': info->suggest = GMT_FFT_ACCURATE;  break;	/* Pick dimensions for most accurate solution */
		case 'f': info->info_mode = GMT_FFT_FORCE; break;	/* Default is force actual grid dimensions */
		case 'm': info->suggest = GMT_FFT_STORAGE;  break;	/* Pick dimensions for minimum storage */
		case 'q': info->verbose = true; break;	/* No longer a mode.  Backwards compatibility; see +v instead */
		case 'r': info->suggest = GMT_FFT_FAST;  break;	/* Pick dimensions for most rapid solution */
		case 's': info->info_mode = GMT_FFT_LIST;  break;
		default:
			if (dim == 2U) {	/* 2-D */
				pos = sscanf (args, "%d/%d", &info->n_columns, &info->n_rows);
				if (pos == 1) info->n_rows = info->n_columns;
			}
			else {	/* 1-D */
				pos = sscanf (args, "%d", &info->n_columns);
				info->n_rows = 0;
			}
			if (pos) info->info_mode = GMT_FFT_SET;
	}
	if (info->suffix[0] == '\0') strncpy (info->suffix, "tapered", GMT_LEN64-1);	/* Default suffix */
	info->set = true;	/* We parsed this option */
	if (info->info_mode == GMT_FFT_SET) {
		if (dim == 2U && (info->n_columns <= 0 || info->n_rows <= 0)) {
			GMT_Report (API, GMT_MSG_ERROR, "Option -%c: n_columns and/or n_rows are <= 0\n", option);
			n_errors++;
		}
		else if (dim == 1U && info->n_columns <= 0) {
			GMT_Report (API, GMT_MSG_ERROR, "Option -%c: n_columns is <= 0\n", option);
			n_errors++;
		}
	}
	if (info->taper_mode == GMT_FFT_EXTEND_NONE && info->taper_width == 100.0) {
		GMT_Report (API, GMT_MSG_ERROR, "Option -%c: +n requires +t with width << 100!\n", option);
		n_errors++;
	}
	if (info->info_mode == GMT_FFT_LIST) {
		gmtapi_fft_Singleton_list (API);
	}
	if (n_errors) {
		gmt_M_free (API->GMT, info);
		info = NULL;
	}
	return (info);
}

#ifdef FORTRAN_API
void * GMT_FFT_Parse_ (char *option, unsigned int *dim, char *args, int *length) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_FFT_Parse (GMT_FORTRAN, *option, *dim, args));
}
#endif

/*! . */
GMT_LOCAL struct GMT_FFT_WAVENUMBER * gmtapi_fft_init_1d (struct GMTAPI_CTRL *API, struct GMT_DATASET *D, unsigned int mode, void *v_info) {
	struct GMT_FFT_WAVENUMBER *K = NULL;
	gmt_M_unused(API); gmt_M_unused(D); gmt_M_unused(mode); gmt_M_unused(v_info);

#if 0	/* Have not finalized 1-D FFT usage in general; this will probably happen when we add gmtfft [1-D FFT equivalent to grdfft] */
	unsigned n_cols = 1;
	struct GMT_FFT_INFO *F = gmtapi_get_fftinfo_ptr (v_info);
	/* Determine number of columns in [t] x [y] input */
	if (mode & GMT_FFT_CROSS_SPEC) n_cols++;
	if (Din->n_columns < n_cols) {
		GMT_report (API, GMT_MSG_ERROR, "2 columns needed but only 1 provided\n");
		return NULL;
	}
	cross = (n_cols == 2);
	if (mode & GMT_FFT_DELTA) n_cols++;
	delta_t = (mode & GMT_FFT_DELTA) ? F->delta_t : D->table[0]->segment[0]->data[0][1] - D->table[0]->segment[0]->data[0][0];
	K->delta_kx = 2.0 * M_PI / (F->n_columns * delta_t);

	GMT_table_detrend (C, D, F->trend_mode, K->coeff);	/* Detrend data, if requested */
	gmt_table_taper (C, G, F);				/* Taper data, if requested */
	K->dim = 1;	/* 1-D FFT */
#endif
	return (K);
}

/*! . */
GMT_LOCAL void gmtapi_fft_taper2d (struct GMT_CTRL *GMT, struct GMT_GRID *Grid, struct GMT_FFT_INFO *F) {
	/* mode sets if and how tapering will be performed [see GMT_FFT_EXTEND_* constants].
	 * width is relative width in percent of the margin that will be tapered [100]. */
	int il1, ir1, il2, ir2, jb1, jb2, jt1, jt2, im, jm, j, end_i, end_j, min_i, min_j, one;
	int i, i_data_start, j_data_start, mx, i_width, j_width, width_percent;
	unsigned int ju, start_component = 0, stop_component = 0, component;
	uint64_t off;
	char *method[2] = {"edge-point", "mirror"}, *comp[2] = {"real", "imaginary"};
	gmt_grdfloat *datac = Grid->data, scale, cos_wt;
	double width;
	struct GMT_GRID_HEADER *h = Grid->header;	/* For shorthand */
	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (h);

	width_percent = irint (F->taper_width);

	if ((Grid->header->n_columns == F->n_columns && Grid->header->n_rows == F->n_rows) || F->taper_mode == GMT_FFT_EXTEND_NONE) {
		GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Data and FFT dimensions are equal - no data extension will take place\n");
		/* But there may still be interior tapering */
		if (F->taper_mode != GMT_FFT_EXTEND_NONE) {	/* Nothing to do since no outside pad */
			GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Data and FFT dimensions are equal - no tapering will be performed\n");
			return;
		}
		if (F->taper_mode == GMT_FFT_EXTEND_NONE && width_percent == 100) {	/* No interior taper specified */
			GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "No interior tapering will be performed\n");
			return;
		}
	}

	if (HH->arrangement == GMT_GRID_IS_INTERLEAVED) {
		GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Demultiplexing complex grid before tapering can take place.\n");
		gmt_grd_mux_demux (GMT, Grid->header, Grid->data, GMT_GRID_IS_SERIAL);
	}

	/* Note that if nx2 = nx+1 and ny2 = n_rows + 1, then this routine
	 * will do nothing; thus a single row/column of zeros may be
	 * added to the bottom/right of the input array and it cannot
	 * be tapered.  But when (nx2 - nx)%2 == 1 or ditto for y,
	 * this is zero anyway.  */

	i_data_start = GMT->current.io.pad[XLO];	/* Some shorthands for readability */
	j_data_start = GMT->current.io.pad[YHI];
	mx = h->mx;
	one = (F->taper_mode == GMT_FFT_EXTEND_NONE) ? 0 : 1;	/* 0 is the boundary point which we want to taper to 0 for the interior taper */

	if (width_percent == 0) {
		GMT_Report (GMT->parent, GMT_MSG_WARNING, "Tapering has been disabled via +t0\n");
	}
	if (width_percent == 100 && F->taper_mode == GMT_FFT_EXTEND_NONE) {	/* Means user set +n but did not specify +t<taper> as 100% is unreasonable for interior */
		width_percent = 0;
		width = 0.0;
	}
	else
		width = F->taper_width / 100.0;	/* Was percent, now fraction */

	if (F->taper_mode == GMT_FFT_EXTEND_NONE) {	/* No extension, just tapering inside the data grid */
		i_width = irint (Grid->header->n_columns * width);	/* Interior columns over which tapering will take place */
		j_width = irint (Grid->header->n_rows * width);	/* Extended rows over which tapering will take place */
	}
	else {	/* We wish to extend data into the margin pads between FFT grid and data grid */
		i_width = irint (i_data_start * width);	/* Extended columns over which tapering will take place */
		j_width = irint (j_data_start * width);	/* Extended rows over which tapering will take place */
	}
	if (i_width == 0 && j_width == 0) one = 1;	/* So we do nothing further down */

	/* Determine how many complex components (1 or 2) to taper, and which one(s) */
	start_component = (h->complex_mode & GMT_GRID_IS_COMPLEX_REAL) ? 0 : 1;
	stop_component  = (h->complex_mode & GMT_GRID_IS_COMPLEX_IMAG) ? 1 : 0;

	for (component = start_component; component <= stop_component; component++) {	/* Loop over 1 or 2 components */
		off = component * h->size / 2;	/* offset to start of this component in grid */

		/* First reflect about xmin and xmax, either point symmetric about edge point OR mirror symmetric */

		if (F->taper_mode != GMT_FFT_EXTEND_NONE) {
			for (im = 1; im <= i_width; im++) {
				il1 = -im;	/* Outside xmin; left of edge 1  */
				ir1 = im;	/* Inside xmin; right of edge 1  */
				il2 = il1 + h->n_columns - 1;	/* Inside xmax; left of edge 2  */
				ir2 = ir1 + h->n_columns - 1;	/* Outside xmax; right of edge 2  */
				for (ju = 0; ju < h->n_rows; ju++) {
					if (F->taper_mode == GMT_FFT_EXTEND_POINT_SYMMETRY) {
						datac[gmt_M_ijp(h,ju,il1)+off] = 2.0f * datac[gmt_M_ijp(h,ju,0)+off]       - datac[gmt_M_ijp(h,ju,ir1)+off];
						datac[gmt_M_ijp(h,ju,ir2)+off] = 2.0f * datac[gmt_M_ijp(h,ju,h->n_columns-1)+off] - datac[gmt_M_ijp(h,ju,il2)+off];
					}
					else {	/* Mirroring */
						datac[gmt_M_ijp(h,ju,il1)+off] = datac[gmt_M_ijp(h,ju,ir1)+off];
						datac[gmt_M_ijp(h,ju,ir2)+off] = datac[gmt_M_ijp(h,ju,il2)+off];
					}
				}
			}
		}

		/* Next, reflect about ymin and ymax.
		 * At the same time, since x has been reflected,
		 * we can use these vals and taper on y edges */

		scale = (gmt_grdfloat)(M_PI / (j_width + 1));	/* Full 2*pi over y taper range */
		min_i = (F->taper_mode == GMT_FFT_EXTEND_NONE) ? 0 : -i_width;
		end_i = (F->taper_mode == GMT_FFT_EXTEND_NONE) ? (int)Grid->header->n_columns : mx - i_width;
		for (jm = one; jm <= j_width; jm++) {	/* Loop over width of strip to taper */
			jb1 = -jm;	/* Outside ymin; bottom side of edge 1  */
			jt1 = jm;	/* Inside ymin; top side of edge 1  */
			jb2 = jb1 + h->n_rows - 1;	/* Inside ymax; bottom side of edge 2  */
			jt2 = jt1 + h->n_rows - 1;	/* Outside ymax; bottom side of edge 2  */
			cos_wt = 0.5f * (1.0f + cosf (jm * scale));
			if (F->taper_mode == GMT_FFT_EXTEND_NONE) cos_wt = 1.0f - cos_wt;	/* Reverse weights for the interior */
			for (i = min_i; i < end_i; i++) {
				if (F->taper_mode == GMT_FFT_EXTEND_POINT_SYMMETRY) {
					datac[gmt_M_ijp(h,jb1,i)+off] = cos_wt * (2.0f * datac[gmt_M_ijp(h,0,i)+off]       - datac[gmt_M_ijp(h,jt1,i)+off]);
					datac[gmt_M_ijp(h,jt2,i)+off] = cos_wt * (2.0f * datac[gmt_M_ijp(h,h->n_rows-1,i)+off] - datac[gmt_M_ijp(h,jb2,i)+off]);
				}
				else if (F->taper_mode == GMT_FFT_EXTEND_MIRROR_SYMMETRY) {
					datac[gmt_M_ijp(h,jb1,i)+off] = cos_wt * datac[gmt_M_ijp(h,jt1,i)+off];
					datac[gmt_M_ijp(h,jt2,i)+off] = cos_wt * datac[gmt_M_ijp(h,jb2,i)+off];
				}
				else {	/* Interior tapering only */
					datac[gmt_M_ijp(h,jt1,i)+off] *= cos_wt;
					datac[gmt_M_ijp(h,jb2,i)+off] *= cos_wt;
				}
			}
		}
		/* Now, cos taper the x edges */
		scale = (gmt_grdfloat)(M_PI / (i_width + 1));	/* Full 2*pi over x taper range */
		end_j = (F->taper_mode == GMT_FFT_EXTEND_NONE) ? h->n_rows : h->my - j_data_start;
		min_j = (F->taper_mode == GMT_FFT_EXTEND_NONE) ? 0 : -j_width;
		for (im = one; im <= i_width; im++) {
			il1 = -im;
			ir1 = im;
			il2 = il1 + h->n_columns - 1;
			ir2 = ir1 + h->n_columns - 1;
			cos_wt = (gmt_grdfloat)(0.5f * (1.0f + cosf (im * scale)));
			if (F->taper_mode == GMT_FFT_EXTEND_NONE) cos_wt = 1.0f - cos_wt;	/* Switch to weights for the interior */
			for (j = min_j; j < end_j; j++) {
				if (F->taper_mode == GMT_FFT_EXTEND_NONE) {
					datac[gmt_M_ijp(h,j,ir1)+off] *= cos_wt;
					datac[gmt_M_ijp(h,j,il2)+off] *= cos_wt;
				}
				else {
					datac[gmt_M_ijp(h,j,il1)+off] *= cos_wt;
					datac[gmt_M_ijp(h,j,ir2)+off] *= cos_wt;
				}
			}
		}

		if (F->taper_mode == GMT_FFT_EXTEND_NONE)
			GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Grid margin (%s component) tapered to zero over %d %% of data width and height\n", comp[component], width_percent);
		else
			GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Grid (%s component) extended via %s symmetry at all edges, then tapered to zero over %d %% of extended area\n", comp[component], method[F->taper_mode], width_percent);
	}
}

/*! . */
GMT_LOCAL struct GMT_FFT_WAVENUMBER * gmtapi_fft_init_2d (struct GMTAPI_CTRL *API, struct GMT_GRID *G, unsigned int mode, void *v_info) {
	/* Initialize grid dimensions for FFT machinery and set up wavenumbers */
	unsigned int k, factors[32];
	uint64_t node;
	size_t worksize;
	bool stop;
	double tdummy, edummy;
	struct GMT_FFT_SUGGESTION fft_sug[GMT_FFT_N_SUGGEST];
	struct GMT_FFT_INFO *F = NULL, *F_in = gmtapi_get_fftinfo_ptr (v_info);
	struct GMT_FFT_WAVENUMBER *K = NULL;
	struct GMT_GRID_HEADER_HIDDEN *HH;
	struct GMT_CTRL *GMT = NULL;

	if (API == NULL) return_null (API, GMT_NOT_A_SESSION);
	if (G == NULL) return_null (API, GMT_ARG_IS_NULL);
	HH = gmt_get_H_hidden (G->header);
	GMT = API->GMT;
	if ((K = gmt_M_memory (GMT, NULL, 1, struct GMT_FFT_WAVENUMBER)) == NULL) return_null (API, GMT_MEMORY_ERROR);

	if ((F = gmt_M_memory (GMT, NULL, 1, struct GMT_FFT_INFO)) == NULL) return_null (API, GMT_MEMORY_ERROR);
	if (F_in) {	/* User specified -N so default settings should take effect */
		gmt_M_memcpy (F, F_in, 1, struct GMT_FFT_INFO);
		if (F->K) GMT_Report (API, GMT_MSG_DEBUG, "F->K already set; investigate.\n");
	}
	if (!F->set || F->info_mode == GMT_FFT_UNSPECIFIED) {	/* User is accepting the default values of extend via edge-point symmetry over 100% of margin */
		F->info_mode = GMT_FFT_EXTEND_POINT_SYMMETRY;
		F->taper_width = 100.0;
		if (!F->set) F->suggest = GMT_FFT_N_SUGGEST;
		F->set = true;
	}

	/* Get dimensions as may be appropriate */
	if (F->info_mode == GMT_FFT_SET) {	/* User specified the n_columns/n_rows dimensions */
		if (F->n_columns < G->header->n_columns || F->n_rows < G->header->n_rows) {
			GMT_Report (API, GMT_MSG_WARNING, "You specified a FFT n_columns/n_rows smaller than input grid.  Ignored.\n");
			F->info_mode = GMT_FFT_EXTEND;
		}
	}

	if (F->info_mode != GMT_FFT_SET) {	/* Either adjust, force, inquiery */
		if (F->info_mode == GMT_FFT_FORCE) {
			F->n_columns = G->header->n_columns;
			F->n_rows = G->header->n_rows;
			GMT_Report (API, GMT_MSG_INFORMATION, "Selected FFT dimensions == Grid dimensions.\n");
		}
		else {	/* Determine best FFT dimensions */
			unsigned int pick;
			char *mode[GMT_FFT_N_SUGGEST] = {"fastest", "most accurate", "least storage"};
			gmtlib_suggest_fft_dim (GMT, G->header->n_columns, G->header->n_rows, fft_sug, (gmt_M_is_verbose (GMT, GMT_MSG_WARNING) || F->verbose));
			if (F->suggest == GMT_FFT_N_SUGGEST) {	/* Must choose smallest of accurate and fast */
				pick = (fft_sug[GMT_FFT_ACCURATE].totalbytes < fft_sug[GMT_FFT_FAST].totalbytes) ? GMT_FFT_ACCURATE : GMT_FFT_FAST;
				GMT_Report (API, GMT_MSG_INFORMATION, "Selected FFT dimensions for the overall best solution (%s).\n", mode[pick]);
			}
			else {	/* Pick the one we selected up front */
				pick = F->suggest;
				GMT_Report (API, GMT_MSG_INFORMATION, "Selected FFT dimensions for the %s solution.\n", mode[pick]);
			}
			F->n_columns = fft_sug[pick].n_columns;
			F->n_rows    = fft_sug[pick].n_rows;
		}
	}

	/* Because we taper and reflect below we DO NOT want any BCs set since that code expects 2 BC rows/cols */
	for (k = 0; k < 4; k++) HH->BC[k] = GMT_BC_IS_DATA;

	/* Get here when F->n_columns and F->n_rows are set to the values we will use.  */

	gmtlib_fourt_stats (GMT, F->n_columns, F->n_rows, factors, &edummy, &worksize, &tdummy);
	GMT_Report (API, GMT_MSG_INFORMATION, "Grid dimensions (n_rows by n_columns): %d x %d\tFFT dimensions: %d x %d\n", G->header->n_rows, G->header->n_columns, F->n_rows, F->n_columns);

	/* Put the data in the middle of the padded array */

	GMT->current.io.pad[XLO] = (F->n_columns - G->header->n_columns) / 2;	/* zero if n_columns < G->header->n_columns+1  */
	GMT->current.io.pad[YHI] = (F->n_rows - G->header->n_rows) / 2;
	GMT->current.io.pad[XHI] = F->n_columns - G->header->n_columns - GMT->current.io.pad[XLO];
	GMT->current.io.pad[YLO] = F->n_rows - G->header->n_rows - GMT->current.io.pad[YHI];

	/* Precompute wavenumber increments and initialize the GMT_FFT machinery */

	K->delta_kx = 2.0 * M_PI / (F->n_columns * G->header->inc[GMT_X]);
	K->delta_ky = 2.0 * M_PI / (F->n_rows * G->header->inc[GMT_Y]);
	K->nx2 = F->n_columns;	K->ny2 = F->n_rows;

	if (gmt_M_is_geographic (GMT, GMT_IN)) {	/* Give delta_kx, delta_ky units of 2pi/meters via Flat Earth assumption  */
		K->delta_kx /= (GMT->current.proj.DIST_M_PR_DEG * cosd (0.5 * (G->header->wesn[YLO] + G->header->wesn[YHI])));
		K->delta_ky /= GMT->current.proj.DIST_M_PR_DEG;
	}

	gmt_fft_set_wave (GMT, GMT_FFT_K_IS_KR, K);	/* Initialize for use with radial wavenumbers */

	F->K = K;	/* So that F can access information in K later */
	K->info = F;	/* So K can have access to information in F later */

	/* Read in the data or change pad to match the nx2/ny2 determined */

	if (G->data) {	/* User already read the data, check padding and possibly extend it */
		if (G->header->complex_mode == 0) {	/* Grid was not read in interleaved, must do so now */
			/* Because of no realloc for aligned memory we must do it the hard way */
			gmt_grdfloat *f = NULL;
			size_t new_size = 2 * G->header->size;
			GMT_Report (API, GMT_MSG_INFORMATION, "Must double memory and multiplex external grid before we can do FFT\n", HH->name);
			GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Extend grid via copy onto larger memory-aligned grid\n");
			if ((f = gmt_M_memory_aligned (GMT, NULL, new_size, gmt_grdfloat)) == NULL) return_null (GMT->parent, GMT_MEMORY_ERROR);	/* New, larger grid size */
			gmt_M_memcpy (f, G->data, G->header->size, gmt_grdfloat);	/* Copy over previous grid values */
			gmt_M_free_aligned (GMT, G->data);			/* Free previous aligned grid memory */
			G->data = f;						/* Attach the new, larger aligned memory */
			G->header->complex_mode = GMT_GRID_IS_COMPLEX_REAL;	/* Flag as complex grid with real components only */
			G->header->size = new_size;					/* Update the size of complex grid */
		}
		if (!(G->header->mx == F->n_columns && G->header->my == F->n_rows)) {	/* Must re-pad, possibly re-allocate the grid */
			gmt_grd_pad_on (GMT, G, GMT->current.io.pad);
		}
	}
	else {	/* Read the data into a grid of approved dimension */
		G->header->mx = G->header->n_columns;	G->header->my = G->header->n_rows;	/* Undo misleading padding since we have not read the data yet and GMT pad has changed above */
		if (GMT_Read_Data (GMT->parent, GMT_IS_GRID, GMT_IS_FILE, GMT_IS_SURFACE, GMT_DATA_ONLY | mode, NULL, HH->name, G) == NULL)	/* Get data only */
			return (NULL);
	}
#ifdef DEBUG
	gmt_grd_dump (G->header, G->data, mode & GMT_GRID_IS_COMPLEX_MASK, "Read in FFT_Create");
#endif
	/* Make sure there are no NaNs in the grid - that is a fatal flaw */

	for (node = 0, stop = false; !stop && node < G->header->size; node++) stop = gmt_M_is_fnan (G->data[node]);
	if (stop) {
		GMT_Report (API, GMT_MSG_ERROR, "Input grid %s contain NaNs, cannot do FFT!\n", HH->name);
		return (NULL);
	}

	if (F->trend_mode == GMT_FFT_REMOVE_NOT_SET) F->trend_mode = GMT_FFT_REMOVE_NOTHING;	/* Delayed default */
	gmt_grd_detrend (GMT, G, F->trend_mode, K->coeff);	/* Detrend data, if requested */
#ifdef DEBUG
	gmt_grd_dump (G->header, G->data, mode & GMT_GRID_IS_COMPLEX_MASK, "After detrend");
#endif
	gmtapi_fft_taper2d (GMT, G, F);				/* Taper data, if requested */
#ifdef DEBUG
	gmt_grd_dump (G->header, G->data, mode & GMT_GRID_IS_COMPLEX_MASK, "After Taper");
#endif
	K->dim = 2;	/* 2-D FFT */
	return (K);
}

/*! . */
void * GMT_FFT_Create (void *V_API, void *X, unsigned int dim, unsigned int mode, void *v_info) {
	/* Initialize 1-D or 2-D FFT machinery and set up wavenumbers */
	if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION);
	if (dim == 1) return (gmtapi_fft_init_1d (V_API, X, mode, v_info));
	if (dim == 2) return (gmtapi_fft_init_2d (V_API, X, mode, v_info));
	GMT_Report (V_API, GMT_MSG_ERROR, "GMT FFT only supports dimensions 1 and 2, not %u\n", dim);
	return_null (V_API, (dim == 0) ? GMT_DIM_TOO_SMALL : GMT_DIM_TOO_LARGE);
}

#ifdef FORTRAN_API
void * GMT_FFT_Create_ (void *X, unsigned int *dim, unsigned int *mode, void *v_info) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_FFT_Create (GMT_FORTRAN, X, *dim, *mode, v_info));
}
#endif

/*! . */
GMT_LOCAL int gmtapi_fft_end_1d (void *V_API, struct GMT_DATASET *D, unsigned int mode) {
	gmt_M_unused (D);
	gmt_M_unused (mode);
	GMT_Report (V_API, GMT_MSG_ERROR, "GMT_FFT_End only implemented for dim = 2\n");
	return (GMT_NOERROR);
}

/*! . */
GMT_LOCAL int gmtapi_fft_end_2d (void *V_API, struct GMT_GRID *G, unsigned int mode) {
	/* Need to demultiplex the complex grid, wipe and free unneeded imaginary space memory,
	 * and adjust complex header flags */

	int error = GMT_NOERROR;
	struct GMTAPI_CTRL *API = gmtapi_get_api_ptr (V_API);
	struct GMT_GRID_HIDDEN *HG = gmt_get_G_hidden (G);
	gmt_grdfloat *data = NULL;
	gmt_M_unused (mode);

	if (G->header->complex_mode == 0) return (GMT_NOERROR);  /* Nothing to do */

	/* The following call may change layout but does not affect memory size */
	if ((error = gmt_grd_layout (API->GMT, G->header, G->data, G->header->complex_mode, GMT_OUT)))  /* Undo complex layout */
		return (gmtlib_report_error (API, error));
	G->header->complex_mode = 0;	/* Remove any non-complex flags */
	if (HG->alloc_mode == GMT_ALLOC_EXTERNALLY) return (GMT_NOERROR);	/* Cannot touch the memory further */
	/* For internally allocated memory we can realloc to half size */
	G->header->size /= 2;	/* Since we do not need the imaginary (now blank) half */
	if ((data = gmt_M_memory_aligned (API->GMT, NULL, G->header->size, gmt_grdfloat)) == NULL) return GMT_MEMORY_ERROR;
	gmt_M_memcpy (data, G->data, G->header->size, gmt_grdfloat);	/* Copy over the real part */
	gmt_M_free_aligned (API->GMT, G->data);	/* Free original complex grid */
	G->data = data;	/* Pass out new memory at half the size */
	return (GMT_NOERROR);
}

/*! . */
GMT_LOCAL int gmtapi_fft_1d (struct GMTAPI_CTRL *API, struct GMT_DATASET *D, int direction, unsigned int mode, struct GMT_FFT_WAVENUMBER *K) {
	/* The 1-D FFT operating on DATASET segments */
	int status = 0;
	uint64_t seg, row, tbl, last = 0, col = 0;
	gmt_grdfloat *data = NULL;
	struct GMT_DATASEGMENT *S = NULL;
	gmt_M_unused(K);
	if (API == NULL) return_error (API, GMT_NOT_A_SESSION);
	/* Not at all finished; will require gmtfft.c to be developed and tested */
	for (tbl = 0; tbl < D->n_tables; tbl++) {
		for (seg = 0; seg < D->table[tbl]->n_segments; seg++) {
			S = D->table[tbl]->segment[seg];
			if (S->n_rows > last) {	/* Extend array */
				if ((data = gmt_M_memory (API->GMT, data, S->n_rows, gmt_grdfloat)) == NULL) return_error (API, GMT_MEMORY_ERROR);
				last = S->n_rows;
			}
			for (row = 0; S->n_rows; row++) data[row] = (gmt_grdfloat)S->data[col][row];
			status = GMT_FFT_1D (API, data, S->n_rows, direction, mode);
			for (row = 0; S->n_rows; row++) S->data[col][row] = data[row];
		}
	}
	gmt_M_free (API->GMT, data);
	if (direction == GMT_FFT_INV && (mode & GMT_FFT_NO_DEMUX) == 0) {	/* Do the demux now */
		status = gmtapi_fft_end_1d (API, D, mode);
	}
	return (status);
}

GMT_LOCAL char * gmtapi_fft_file_name_with_suffix (struct GMT_CTRL *GMT, char *name, char *suffix) {
	static char file[PATH_MAX] = {""};
	uint64_t i, j;
	size_t len;

	if ((len = strlen (name)) == 0) {	/* Grids that are being created have no filename yet */
		snprintf (file, PATH_MAX, "tmpgrid_%s.grd", suffix);
		GMT_Report (GMT->parent, GMT_MSG_WARNING, "Created grid has no name to derive new names from; choose %s\n", file);
		return (file);
	}
	for (i = len; i > 0 && !(name[i] == '/' || name[i] == '\\'); i--);	/* i points to 1st char in name after slash, or 0 if no leading dirs */
	if (i) i++;	/* Move to 1st char after / */
	for (j = len; j > 0 && name[j] != '.'; j--);	/* j points to period before extension, or it is 0 if no extension */
	len = strlen (&name[i]);
	strncpy (file, &name[i], PATH_MAX-1);		/* Make a full copy of filename without leading directories */
	for (i = len; i > 0 && file[i] != '.'; i--);	/* i now points to period before extension in file, or it is 0 if no extension */
	if (i) file[i] = '\0';	/* Truncate at the extension */
	/* Determine length of new filename and make sure it fits */
	len = strlen (file);
	if (j) len += strlen (&name[j]);
	len += (1 + strlen(suffix));
	if ((GMT_BUFSIZ - len) > 0) {	/* Have enough space */
		strcat (file, "_");
		strcat (file, suffix);
		if (j) strncat (file, &name[j], PATH_MAX-1);
	}
	else
		GMT_Report (GMT->parent, GMT_MSG_WARNING, "File name [ %s] way too long - trouble in gmtapi_fft_file_name_with_suffix\n", file);
	return (file);
}

GMT_LOCAL void gmtapi_fft_grd_save_taper (struct GMT_CTRL *GMT, struct GMT_GRID *Grid, char *suffix) {
	/* Write the intermediate grid that will be passed to the FFT to file.
	 * This grid may have been a mean, mid-value, or plane removed, may
	 * have data filled into an extended margin, and may have been taperer.
	 * Normally, the complex grid will be in serial layout, but just in case
	 * we check and add a demux step if required.  The FFT will also check
	 * and multiplex the grid (again) if needed.
	 */
	unsigned int pad[4];
	struct GMT_GRID_HEADER *save = gmt_get_header (GMT);
	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (Grid->header);
	char *file = NULL;

	if (HH->arrangement == GMT_GRID_IS_INTERLEAVED) {
		GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Demultiplexing complex grid before saving can take place.\n");
		gmt_grd_mux_demux (GMT, Grid->header, Grid->data, GMT_GRID_IS_SERIAL);
	}
	gmt_copy_gridheader (GMT, save, Grid->header);
	gmt_M_memcpy (pad, Grid->header->pad, 4U, unsigned int);		/* Save current pad, then set pad to zero */
	/* Extend w/e/s/n to what it would be if the pad was not present */
	Grid->header->wesn[XLO] -= Grid->header->pad[XLO] * Grid->header->inc[GMT_X];
	Grid->header->wesn[XHI] += Grid->header->pad[XHI] * Grid->header->inc[GMT_X];
	Grid->header->wesn[YLO] -= Grid->header->pad[YLO] * Grid->header->inc[GMT_Y];
	Grid->header->wesn[YHI] += Grid->header->pad[YHI] * Grid->header->inc[GMT_Y];
	gmt_M_memset (Grid->header->pad,   4U, unsigned int);	/* Set header pad to {0,0,0,0} */
	gmt_M_memset (GMT->current.io.pad, 4U, unsigned int);	/* set GMT default pad to {0,0,0,0} */
	gmt_set_grddim (GMT, Grid->header);	/* Recompute all dimensions */
	if ((file = gmtapi_fft_file_name_with_suffix (GMT, HH->name, suffix)) == NULL) {
		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Unable to get file name for file %s\n", HH->name);
		return;
	}

	if (GMT_Write_Data (GMT->parent, GMT_IS_GRID, GMT_IS_FILE, GMT_IS_SURFACE, GMT_DATA_ONLY | GMT_GRID_IS_COMPLEX_REAL, NULL, file, Grid) != GMT_NOERROR)
		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Intermediate detrended, extended, and tapered grid could not be written to %s\n", file);
	else
		GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Intermediate detrended, extended, and tapered grid written to %s\n", file);

	gmt_copy_gridheader (GMT, Grid->header, save);	/* Restore original, including the original pad */
	gmt_M_memcpy (GMT->current.io.pad, pad, 4U, unsigned int);	/* Restore GMT default pad */
	gmt_M_free (GMT, save->hidden);
	gmt_M_free (GMT, save);
}

GMT_LOCAL void gmtapi_fft_grd_save_fft (struct GMT_CTRL *GMT, struct GMT_GRID *G, struct GMT_FFT_INFO *F) {
	/* Save the raw spectrum as two files (real,imag) or (mag,phase), depending on mode.
	 * We must first do an "fftshift" operation as in Matlab, to put the 0 frequency
	 * value in the center of the grid. */
	uint64_t i_ij, o_ij,  offset;
	int row_in, col_in, row_out, col_out, nx_2, ny_2;
	size_t len;
	unsigned int k, pad[4], mode, wmode[2] = {GMT_GRID_IS_COMPLEX_REAL, GMT_GRID_IS_COMPLEX_IMAG};
	double wesn[6], inc[2];
	gmt_grdfloat re, im, i_scale;
	char *file = NULL, *suffix[2][2] = {{"real", "imag"}, {"mag", "phase"}};
	struct GMT_GRID *Out = NULL;
	struct GMT_GRID_HEADER_HIDDEN *HH = gmt_get_H_hidden (G->header);
	struct GMT_FFT_WAVENUMBER *K = F->K;

	if (K == NULL) return;

	mode = (F->polar) ? 1 : 0;

	GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Write components of complex raw spectrum with file suffix %s and %s\n", suffix[mode][0], suffix[mode][1]);

	if (HH->arrangement == GMT_GRID_IS_SERIAL) {
		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Cannot save complex grid unless it is interleaved.\n");
		return;
	}
	/* Prepare wavenumber domain limits and increments */
	nx_2 = K->nx2 / 2;	ny_2 = K->ny2 / 2;
	wesn[XLO] = -K->delta_kx * nx_2;	wesn[XHI] =  K->delta_kx * (nx_2 - 1);
	wesn[YLO] = -K->delta_ky * (ny_2 - 1);	wesn[YHI] =  K->delta_ky * ny_2;
	inc[GMT_X] = K->delta_kx;		inc[GMT_Y] = K->delta_ky;
	gmt_M_memcpy (pad, GMT->current.io.pad, 4U, unsigned int);	/* Save current GMT pad */
	for (k = 0; k < 4; k++) GMT->current.io.pad[k] = 0;		/* No pad is what we need for this application */

	/* Set up and allocate the temporary grid which is always gridline registered. */
	if ((Out = GMT_Create_Data (GMT->parent, GMT_IS_GRID, GMT_IS_SURFACE, GMT_CONTAINER_AND_DATA | GMT_GRID_IS_COMPLEX_MASK,
	                            NULL, wesn, inc, GMT_GRID_NODE_REG, 0, NULL)) == NULL) {	/* Note: 0 for pad since no BC work needed for this temporary grid */
		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Unable to create complex output grid for %s\n", HH->name);
		return;
	}

	strcpy (Out->header->x_units, "xunit^(-1)");	strcpy (Out->header->y_units, "yunit^(-1)");
	strcpy (Out->header->z_units, G->header->z_units);
	strcpy (Out->header->remark, "Applied fftshift: kx,ky = (0,0) now at (n_columns/2 + 1,n_rows/2");

	offset = Out->header->size / 2;
	i_scale = (gmt_grdfloat)(1.0  / Out->header->nm);
	for (row_in = 0; row_in < K->ny2; row_in++) {
		row_out = (row_in + ny_2) % K->ny2;
		for (col_in = 0; col_in < K->nx2; col_in++) {
			col_out = (col_in + nx_2) % K->nx2;
			o_ij = gmt_M_ij0 (Out->header, row_out, col_out);
			i_ij = 2 * gmt_M_ij0 (G->header,   row_in,  col_in);
			re = G->data[i_ij] * i_scale; im = G->data[i_ij+1] * i_scale;
			if (F->polar) {	/* Want magnitude and phase */
				Out->data[o_ij]   = (gmt_grdfloat)hypot (re, im);
				Out->data[o_ij+offset] = (gmt_grdfloat)d_atan2 (im, re);
			}
			else {		/* Retain real and imag components as is */
				Out->data[o_ij] = re;	Out->data[o_ij+offset] = im;
			}
		}
	}
	for (k = 0; k < 2; k++) {	/* Write the two grids */
		if ((file = gmtapi_fft_file_name_with_suffix (GMT, HH->name, suffix[mode][k])) == NULL) {
			GMT_Report (GMT->parent, GMT_MSG_ERROR, "Unable to get file name for file %s\n", HH->name);
			return;
		}
		Out->header->complex_mode = wmode[k];
		for (len = strlen (HH->name); len > 0 && !(HH->name[len-1] == '/' || HH->name[len-1] == '\\'); len--);	/* Find start of file name minus any leading directories */
		snprintf (Out->header->title, GMT_GRID_TITLE_LEN80, "The %s part of FFT transformed grid %s", suffix[mode][k], &HH->name[len]);
		if (k == 1 && mode) strcpy (Out->header->z_units, "radians");
		if (GMT_Write_Data (GMT->parent, GMT_IS_GRID, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_AND_DATA | wmode[k], NULL, file, Out) != GMT_NOERROR) {
			GMT_Report (GMT->parent, GMT_MSG_ERROR, "%s could not be written\n", file);
			return;
		}
	}
	if (GMT_Destroy_Data (GMT->parent, &Out) != GMT_NOERROR) {
		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Failure while freeing temporary grid\n");
	}

	gmt_M_memcpy (GMT->current.io.pad, pad, 4U, unsigned int);	/* Restore GMT pad */
}

GMT_LOCAL void gmtapi_fft_save2d (struct GMT_CTRL *GMT, struct GMT_GRID *G, unsigned int direction, struct GMT_FFT_WAVENUMBER *K) {
	/* Handle the writing of the grid going into the FFT and comping out of the FFT, per F settings */

	if (G == NULL || (K == NULL ||  K->info == NULL)) return;
	if (direction == GMT_IN  && K->info->save[GMT_IN])  gmtapi_fft_grd_save_taper (GMT, G, K->info->suffix);
	if (direction == GMT_OUT && K->info->save[GMT_OUT]) gmtapi_fft_grd_save_fft (GMT, G, K->info);
}

/*! . */
GMT_LOCAL int gmtapi_fft_2d (struct GMTAPI_CTRL *API, struct GMT_GRID *G, int direction, unsigned int mode, struct GMT_FFT_WAVENUMBER *K) {
	/* The 2-D FFT operating on GMT_GRID arrays */
	int status;
	if (K && direction == GMT_FFT_FWD) gmtapi_fft_save2d (API->GMT, G, GMT_IN, K);	/* Save intermediate grid, if requested, before interleaving */
	gmt_grd_mux_demux (API->GMT, G->header, G->data, GMT_GRID_IS_INTERLEAVED);
#ifdef DEBUG
	gmt_grd_dump (G->header, G->data, true, "After demux");
#endif
	status = GMT_FFT_2D (API, G->data, G->header->mx, G->header->my, direction, mode);
#ifdef DEBUG
	gmt_grd_dump (G->header, G->data, true, "After FFT");
#endif
	if (K && direction == GMT_FFT_FWD) gmtapi_fft_save2d (API->GMT, G, GMT_OUT, K);	/* Save complex grid, if requested */

	if (direction == GMT_FFT_INV && (mode & GMT_FFT_NO_DEMUX) == 0) {	/* Do the demux now */
		status = gmtapi_fft_end_2d (API, G, mode);
	}
	return (status);
}

/*! . */
int GMT_FFT_Reset (void *V_API, void *X, unsigned int dim, unsigned int mode) {
	/* Do the demux in the case when it was not done in GMT_FFT */
	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (dim == 1) return (gmtapi_fft_end_1d (V_API, X, mode));
	if (dim == 2) return (gmtapi_fft_end_2d (V_API, X, mode));
	GMT_Report (V_API, GMT_MSG_ERROR, "GMT FFT only supports dimensions 1 and 2, not %u\n", dim);
	return_error (V_API, (dim == 0) ? GMT_DIM_TOO_SMALL : GMT_DIM_TOO_LARGE);
}

#ifdef FORTRAN_API
int GMT_FFT_Reset_ (void *X, int *direction, unsigned int *mode) {
    /* FORTRAN version: We pass the global GMT_FORTRAN structure */
    return (GMT_FFT_Reset (GMT_FORTRAN, X, *direction, *mode));
}
#endif

/*! . */
int GMT_FFT (void *V_API, void *X, int direction, unsigned int mode, void *v_K) {
	/* The 1-D or 2-D FFT operating on GMT_DATASET or GMT_GRID arrays */
	struct GMT_FFT_WAVENUMBER *K = gmtapi_get_fftwave_ptr (v_K);
	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (K->dim == 1) return (gmtapi_fft_1d (V_API, X, direction, mode, K));
	if (K->dim == 2) return (gmtapi_fft_2d (V_API, X, direction, mode, K));
	GMT_Report (V_API, GMT_MSG_ERROR, "GMT FFT only supports dimensions 1 and 2, not %u\n", K->dim);
	return_error (V_API, (K->dim == 0) ? GMT_DIM_TOO_SMALL : GMT_DIM_TOO_LARGE);
}

#ifdef FORTRAN_API
int GMT_FFT_ (void *X, int *direction, unsigned int *mode, void *v_K) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_FFT (GMT_FORTRAN, X, *direction, *mode, v_K));
}
#endif

/*! . */
int GMT_FFT_Destroy (void *V_API, void *v_info) {
	/* Perform any final duties, perhaps report.  For now just free */
	struct GMT_FFT_WAVENUMBER **K = NULL;
	struct GMTAPI_CTRL *API = NULL;
	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	API = gmtapi_get_api_ptr (V_API);
	K = gmtapi_get_fftwave_addr (v_info);
	gmt_M_free (API->GMT, (*K)->info);
	gmt_M_free (API->GMT, (*K));
	return_error (V_API, GMT_NOERROR);
}

#ifdef FORTRAN_API
int GMT_FFT_Destroy_ (void *v_K) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_FFT_Destroy (GMT_FORTRAN, v_K));
}
#endif

/*! Pretty print core module names and purposes */
const char * gmt_show_name_and_purpose (void *V_API, const char *component, const char *name, const char *purpose) {
	char message[GMT_LEN256] = {""};
	static char full_name[GMT_LEN32] = {""};
	const char *lib = NULL, *mode_name = name;
	static char *core = "core";
	struct GMTAPI_CTRL *API = NULL;
	assert (V_API != NULL);
	assert (name != NULL);
	assert (purpose != NULL);
	API = gmtapi_get_api_ptr (V_API);
	mode_name = gmtlib_get_active_name (API, name);
	lib = (component) ? component : core;
	snprintf (full_name, GMT_LEN32, "gmt %s", mode_name);
	snprintf (message, GMT_LEN256, "%s [%s] %s - %s\n", full_name, lib, GMT_version(), purpose);
	GMT_Usage (V_API, 0, message);
	gmtlib_set_KOP_strings (API);
	return full_name;
}

/* Module Extension: Allow listing and calling modules by name */

/*! . */
GMT_LOCAL void * gmtapi_get_shared_module_func (struct GMTAPI_CTRL *API, const char *module, unsigned int lib_no) {
	/* Function that returns a pointer to the function named module in specified shared library lib_no, or NULL if not found  */
	void *p_func = NULL;       /* function pointer */
	if (API->lib[lib_no].skip) return (NULL);	/* Tried to open this shared library before and it was not available */
	if (API->lib[lib_no].handle == NULL && (API->lib[lib_no].handle = dlopen (API->lib[lib_no].path, RTLD_LAZY)) == NULL) {	/* Not opened this shared library yet */
		GMT_Report (API, GMT_MSG_ERROR, "Unable to open GMT shared %s library: %s\n", API->lib[lib_no].name, dlerror());
		API->lib[lib_no].skip = true;	/* Not bother the next time... */
		return (NULL);			/* ...and obviously no function would be found */
	}
	/* Here the library handle is available; try to get pointer to specified module */
	*(void **) (&p_func) = dlsym (API->lib[lib_no].handle, module);
	return (p_func);
}

/*! . */
GMT_LOCAL void * gmtapi_get_module_func (struct GMTAPI_CTRL *API, const char *module, unsigned int lib_no) {
	return (gmtapi_get_shared_module_func (API, module, lib_no));
}

/*! . */
int GMT_Call_Module (void *V_API, const char *module, int mode, void *args) {
	/* Call the specified shared module and pass it the mode and args.
 	 * mode can be one of the following:
	 * GMT_MODULE_CLASSIC [-7]:		As GMT_MODULE_PURPOSE, but only lists the classic modules.
	 * GMT_MODULE_LIST [-6]:		As GMT_MODULE_PURPOSE, but only lists the modern modules.
	 * GMT_MODULE_CLASSIC_CORE [-5]:	As GMT_MODULE_PURPOSE, but only lists the classic modules (core only).
	 * GMT_MODULE_LIST_CORE [-4]:		As GMT_MODULE_PURPOSE, but only lists the modern modules (core only).
	 * GMT_MODULE_EXIST [-3]:		Return GMT_NOERROR (0) if module exists, GMT_NOT_A_VALID_MODULE otherwise.
	 * GMT_MODULE_PURPOSE [-2]:		As GMT_MODULE_EXIST, but also print the module purpose.
	 * GMT_MODULE_OPT [-1]:			Args is a linked list of option structures.
	 * GMT_MODULE_CMD [0]:			Args is a single textstring with multiple options
	 * mode > 0:				Args is an array of text strings (e.g., argv[]).
	 */
	int status = GMT_NOERROR;
	unsigned int lib;
	struct GMTAPI_CTRL *API = NULL;
	char gmt_module[GMT_LEN64] = "GMT_";
	int (*p_func)(void*, int, void*) = NULL;       /* function pointer */

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (module == NULL && !(mode == GMT_MODULE_LIST || mode == GMT_MODULE_LIST_CORE || mode == GMT_MODULE_CLASSIC || mode == GMT_MODULE_CLASSIC_CORE || mode == GMT_MODULE_PURPOSE))
		return_error (V_API, GMT_ARG_IS_NULL);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;

	if (module == NULL) {	/* Did not specify any specific module, so list purpose of all modules in all shared libs */
		char gmt_module[GMT_LEN256] = {""};	/* To form name of gmt_<lib>_module_show|list_all function */
		char *listfunc = (mode == GMT_MODULE_LIST || mode == GMT_MODULE_LIST_CORE) ? "list" : ( (mode == GMT_MODULE_CLASSIC || mode == GMT_MODULE_CLASSIC_CORE) ? "classic" : "show");
		void (*l_func)(void*);       /* function pointer to gmt_<lib>_module_show|list_all which takes one arg (the API) */
		unsigned int n_libs = (mode == GMT_MODULE_LIST_CORE || mode == GMT_MODULE_CLASSIC_CORE) ? 1 : API->n_shared_libs;
		/* Here we list purpose of all the available modules in each shared library */
		for (lib = 0; lib < n_libs; lib++) {
			snprintf (gmt_module, GMT_LEN64, "%s_module_%s_all", API->lib[lib].name, listfunc);
			*(void **) (&l_func) = gmtapi_get_module_func (API, gmt_module, lib);
			if (l_func == NULL) continue;	/* Not found in this shared library */
			(*l_func) (V_API);	/* Run this function */
		}
		return (status);
	}

	/* Here we call a named module */

	strncat (gmt_module, module, GMT_LEN64-5);		/* Concatenate GMT_-prefix and module name to get function name */
	for (lib = 0; lib < API->n_shared_libs; lib++) {	/* Look for gmt_module in any of the shared libs */
		*(void **) (&p_func) = gmtapi_get_module_func (API, gmt_module, lib);
		if (p_func) break;	/* Found it in this shared library */
	}
	if (p_func == NULL) {	/* Not in any of the shared libraries */
		status = GMT_NOT_A_VALID_MODULE;	/* Most likely, but we will try again: */
		if (strncasecmp (module, "gmt", 3)) {	/* For any module not already starting with "gmt..." */
			char gmt_module[GMT_LEN64] = "gmt";
			strncat (gmt_module, module, GMT_LEN64-4);	/* Prepend "gmt" to module and try again */
			status = GMT_Call_Module (V_API, gmt_module, mode, args);	/* Recursive call to try with the 'gmt' prefix this time */
		}
	}
	else if (mode == GMT_MODULE_EXIST)	/* Just wanted to know it is a valid module */
		return (GMT_NOERROR);
	else {	/* Call the function and pass back its return value */
		gmt_manage_workflow (API, GMT_USE_WORKFLOW, NULL);		/* First detect and set modern mode if modern mode session dir is found */
		if ((status = gmtapi_check_for_modern_oneliner (API, module, mode, args))) return status;	/* If a modern mode one-liner we must switch run--mode here, or fail if an error */
		if (API->external && gmt_M_is_verbose (API->GMT, GMT_MSG_DEBUG)) {
			/* For externals only, print the equivalent command-line string under -Vd */
			char *text;
			if (mode == GMT_MODULE_OPT) text = GMT_Create_Cmd (API, args);
			else if (mode == GMT_MODULE_CMD) text = args;
			else text = gmt_argv2str (NULL, mode, args);
			GMT_Report (API, GMT_MSG_DEBUG, "GMT_Call_Command string: gmt %s %s\n", module, text);
			if (mode == GMT_MODULE_OPT) GMT_Destroy_Cmd (API, &text);
		}
		status = (*p_func) (V_API, mode, args);				/* Call the module in peace */
	}
	return (status);
}

#ifdef FORTRAN_API
int GMT_Call_Module_ (const char *module, int *mode, void *args, int *length) {
	return (GMT_Call_Module (GMT_FORTRAN, module, *mode, args));
}
#endif

/*! . */
GMT_LOCAL const char *gmtapi_get_shared_module_keys (struct GMTAPI_CTRL *API, char *module, unsigned int lib_no) {
	/* Function that returns a pointer to the module keys in specified shared library lib_no, or NULL if not found  */
	/* DO not rename this function */
	char function[GMT_LEN64] = {""};
	const char *keys = NULL;       /* char pointer to module keys */
	const char *(*func)(void*, char*) = NULL;       /* function pointer */
	if (API->lib[lib_no].skip) return (NULL);	/* Tried to open this shared library before and it was not available */
	if (API->lib[lib_no].handle == NULL && (API->lib[lib_no].handle = dlopen (API->lib[lib_no].path, RTLD_LAZY)) == NULL) {	/* Not opened this shared library yet */
		GMT_Report (API, GMT_MSG_ERROR, "Unable to open GMT shared %s library: %s\n", API->lib[lib_no].name, dlerror());
		API->lib[lib_no].skip = true;	/* Not bother the next time... */
		return (NULL);			/* ...and obviously no keys would be found */
	}
	snprintf (function, GMT_LEN64, "%s_module_keys", API->lib[lib_no].name);
	/* Here the library handle is available; try to get pointer to specified module */
	*(void **) (&func) = dlsym (API->lib[lib_no].handle, function);
	if (func) keys = (*func) (API, module);
	return (keys);
}

/*! . */
GMT_LOCAL const char *gmtapi_get_shared_module_group (struct GMTAPI_CTRL *API, char *module, unsigned int lib_no) {
	/* Function that returns a pointer to the module group string in specified shared library lib_no, or NULL if not found  */
	/* DO not rename this function */
	char function[GMT_LEN64] = {""};
	const char *group = NULL;       /* char pointer to module group */
	const char * (*func)(void*, char*) = NULL;       /* function pointer */
	if (API->lib[lib_no].skip) return (NULL);	/* Tried to open this shared library before and it was not available */
	if (API->lib[lib_no].handle == NULL && (API->lib[lib_no].handle = dlopen (API->lib[lib_no].path, RTLD_LAZY)) == NULL) {	/* Not opened this shared library yet */
		GMT_Report (API, GMT_MSG_ERROR, "Unable to open GMT shared %s library: %s\n", API->lib[lib_no].name, dlerror());
		API->lib[lib_no].skip = true;	/* Not bother the next time... */
		return (NULL);			/* ...and obviously no keys would be found */
	}
	snprintf (function, GMT_LEN64, "%s_module_group", API->lib[lib_no].name);
	/* Here the library handle is available; try to get pointer to specified module */
	*(void **) (&func) = dlsym (API->lib[lib_no].handle, function);
	if (func) group = (*func) (API, module);
	return (group);
}

/*! . */
const char *gmt_get_module_group (void *V_API, char *module) {
	/* Call the specified shared module and retrieve the group of the module.
 	 * This function, while in the API, is only for API developers and thus has a
	 * "undocumented" status in the API documentation.
	 */
	unsigned int lib;
	struct GMTAPI_CTRL *API = NULL;
	char gmt_module[GMT_LEN32] = "gmt";
	const char *group = NULL;

	if (V_API == NULL) return_null (NULL, GMT_NOT_A_SESSION);
	if (module == NULL) return_null (V_API, GMT_ARG_IS_NULL);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;

	for (lib = 0; lib < API->n_shared_libs; lib++) {	/* Look for module in any of the shared libs */
		group = gmtapi_get_shared_module_group (API, module, lib);
		if (group) return (group);	/* Found it in this shared library, return the group */
	}
	/* If we get here we did not found it.  Try to prefix module with gmt */
	strncat (gmt_module, module, GMT_LEN32-4);		/* Concatenate gmt and module name to get function name */
	for (lib = 0; lib < API->n_shared_libs; lib++) {	/* Look for gmt_module in any of the shared libs */
		group = gmtapi_get_shared_module_group (API, gmt_module, lib);
		if (group) {	/* Found it in this shared library, adjust module name and return the group */
			strncpy (module, gmt_module, strlen(gmt_module));	/* Rewrite module name to contain prefix of gmt */
			return (group);
		}
	}
	/* Not in any of the shared libraries */
	GMT_Report (API, GMT_MSG_ERROR, "Shared GMT module not found: %s \n", module);
	return_null (V_API, GMT_NOT_A_VALID_MODULE);
}

/*! . */
GMT_LOCAL const char *gmtapi_retrieve_module_keys (void *V_API, char *module) {
	/* Call the specified shared module and retrieve the API developer options keys.
	 */
	unsigned int lib;
	struct GMTAPI_CTRL *API = NULL;
	char gmt_module[GMT_LEN32] = "gmt";
	const char *keys = NULL;

	if (V_API == NULL) return_null (NULL, GMT_NOT_A_SESSION);
	if (module == NULL) return_null (V_API, GMT_ARG_IS_NULL);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;

	for (lib = 0; lib < API->n_shared_libs; lib++) {	/* Look for module in any of the shared libs */
		keys = gmtapi_get_shared_module_keys (API, module, lib);
		if (keys) return (keys);	/* Found it in this shared library, return the keys */
	}
	/* If we get here we did not found it.  Try to prefix module with gmt */
	strncat (gmt_module, module, GMT_LEN32-4);		/* Concatenate gmt and module name to get function name */
	for (lib = 0; lib < API->n_shared_libs; lib++) {	/* Look for gmt_module in any of the shared libs */
		keys = gmtapi_get_shared_module_keys (API, gmt_module, lib);
		if (keys) {	/* Found it in this shared library, adjust module name and return the keys */
			strncpy (module, gmt_module, strlen(gmt_module));	/* Rewrite module name to contain prefix of gmt */
			return (keys);
		}
	}
	/* Not in any of the shared libraries */
	GMT_Report (API, GMT_MSG_ERROR, "Shared GMT module not found: %s \n", module);
	return_null (V_API, GMT_NOT_A_VALID_MODULE);
}

GMT_LOCAL int gmtapi_extract_argument (char *optarg, char *argument, char **key, int k, bool colon, char colon_opt, int *n_pre, enum GMTAPI_enum_takes_mod *takes_mod) {
	/* A few separate actions:
	 * 1) If key ends with "=" then we pull out the option argument after stripping off +<stuff>.
	 * 2) If key ends with "=q" then we see if +q is given and return pos to this modifiers argument.
	 * 3) Else we just copy input to output.
	 * We also set n_pre which is the number of characters to skip after the -X option
	 *   before looking for an argument.
	 * If colon_opt is not 0 then we know it is either -S[~q][fx] from psxy[z] or -G[fx] from the contour programs.
	 *   If colon is also true we also strip argument from the first colon onward.
	 * If this all sounds messy it is because the GMT command-line syntax of some modules are
	 *   messy and predate the whole API business...
	 */
	char *c = NULL, *inarg = strdup (optarg);  /* Local duplicate we can mess with */
	unsigned int pos = 0;
	size_t L;
	*n_pre = 0;
	*takes_mod = GMTAPI_MOD_NOTSET;

	L = (k >= 0) ? strlen (key[k]) : 0;
	if (colon_opt) {    /* Either -S (from psxy[z]) or -G (from grdcontour or pscontour) */
		if (colon && (c = strchr (inarg, ':')))  /* Chop off :<more arguments> from quoted/decorated lines or contour setups **/
			c[0] = '\0';
		/* We know that what remains is simply -S[~q][fx] or -G[fx] followed by a filename (or not) */
		strcpy (argument, inarg);	/* Any colon-string will be added back in GMT_Encode_Options */
		gmt_M_str_free (inarg);
		/* Also return 1 or 2, depending on -G vs -S */
		*n_pre = (k >= 0 && L > K_MODIFIER && key[k][K_MODIFIER] > 0 && isdigit (key[k][K_MODIFIER])) ? (int)(key[k][K_MODIFIER]-'0') : 0;
		return (0);
	}

	if (k >= 0 && key[k][K_EQUAL] == '=') {	/* Special handling */
		*takes_mod = GMTAPI_MOD_SPECIAL;	/* Flag that KEY was special */
		*n_pre = (L > K_MODIFIER && key[k][K_MODIFIER] && isdigit (key[k][K_MODIFIER])) ? (int)(key[k][K_MODIFIER]-'0') : 0;
		if ((*n_pre || key[k][K_MODIFIER] == 0) && (c = strchr (inarg, '+'))) {	/* Strip off trailing +<modifiers> */
			c[0] = 0;
			strcpy (argument, inarg);
			c[0] = '+';
			if (!argument[0]) *takes_mod = GMTAPI_MOD_NORMAL;	/* Flag that option is missing the arg and needs it later */
		}
		else if (L > K_MODIFIER && key[k][K_MODIFIER]) {	/* Look for a specific +<mod> in the option */
			char code[3] = {"+?"};
			code[1] = key[k][K_MODIFIER];
			if ((c = strstr (inarg, code))) {	/* Found +<modifier> */
				strcpy (argument, inarg);
				if (!c[2] || c[2] == '+') *takes_mod = GMTAPI_MOD_NORMAL;	/* Flag that option had no argument that KEY was looking for */
				pos = (unsigned int) (c - inarg + 2);	/* Position of this modifier's argument. E.g., -E+f<file> will have pos = 2 as start of <file> */
			}
			else	/* No modifier involved */
				strcpy (argument, inarg);
		}
		else
			strcpy (argument, inarg);
		if (!argument[0]) *takes_mod = GMTAPI_MOD_NORMAL;   /* Flag that option is missing the arg and needs it later */
	}
	else
		strcpy (argument, inarg);

	gmt_M_str_free (inarg);

	return pos;
}

GMT_LOCAL int gmtapi_B_custom_annotations (struct GMT_OPTION *opt) {
	/* Replace -B[p|s][x|y|z]c with -B[p|s][x|y|z]c? */
	unsigned int k = 0;
	if (opt->option != 'B') return 0;	/* Not the right option */
	if (strchr ("ps", opt->arg[k])) k++;	/* Skip a leading p|s for primary|secondary axis */
	if (strchr ("xyz", opt->arg[k])) k++;	/* Skip optional x|y|z for specific axis */
	if (opt->arg[k] != 'c') return 0;	/* Not a custom annotation request */
	if (opt->arg[k+1]) return 0;		/* Should be empty if we are to add ? */
	opt->arg = realloc (opt->arg, strlen (opt->arg)+2);	/* Make space for ? */
	strcat (opt->arg, "?");
	return 1;
}

GMT_LOCAL bool gmtapi_operator_takes_dataset (struct GMT_OPTION *opt, int *geometry) {
	/* Check if the sequence ? OPERATOR is one that requires reading a dataset instead of a grid.
	 * Here, opt is an input argument with value ? and we inquire about the next option (the operator).
	 * geometry is already set to GMT_IS_GRID */
	if (opt == NULL) return false;	/* Just being paranoid */
	if (opt->next == NULL) return false;	/* Just being extra paranoid */
	if (opt->next->option != GMT_OPT_INFILE) return false;	/* Cannot be an operator */
	if (!opt->next->arg[0]) return false;	/* No argument given */
	if (!strncmp (opt->next->arg, "INSIDE", 6U)) {	/* Are nodes inside/outside a polygon */
		*geometry = GMT_IS_POLY;
		return true;
	}
	if (!strncmp (opt->next->arg, "POINT", 5U) || !strncmp (opt->next->arg, "PDIST", 5U)) {
		*geometry = GMT_IS_POINT;
		return true;	/* One of the dataset-requiring operators */
	}
	if (!strncmp (opt->next->arg, "LDIST", 5U)) {	/* Distance to lines of some sort */
		*geometry = GMT_IS_LINE;
		return true;	/* One of the dataset-requiring operators */
	}
	return false;	/* No, something else */
}

GMT_LOCAL char gmtapi_grdinterpolate_type (struct GMTAPI_CTRL *API, struct GMT_OPTION *options) {
	char type;
	struct GMT_OPTION *opt = NULL, *E = NULL, *S = NULL;
	gmt_M_unused (API);

	for (opt = options; opt; opt = opt->next) {	/* Look for -E and -S */
		if (opt->option == 'E') E = opt;
		else if (opt->option == 'S') S = opt;
	}
	/* Now determine the various cases that yield different return types */
	if (S)	/* Sample down lines and returning result via a dataset */
		type = 'D';
	else if (E)	/* Return a vertical slice via a grid */
		type = 'G';
	else	/* Interpolating onto a single or multilayer cube */
		type = 'U';

	return (type);
}

#define api_is_required_IO(key) (key == API_PRIMARY_INPUT || key == API_PRIMARY_OUTPUT)			/* Returns true if this is a primary input or output item */
#define api_not_required_io(key) ((key == API_PRIMARY_INPUT || key == API_SECONDARY_INPUT) ? API_SECONDARY_INPUT : API_SECONDARY_OUTPUT)	/* Returns the optional input or output flag */

/*! . */
struct GMT_RESOURCE *GMT_Encode_Options(void *V_API, const char *module_name, int n_in, struct GMT_OPTION **head, unsigned int *n) {
	/* This function determines which input sources and output destinations are required given the module options.
	 * It is only used to assist developers of external APIs, such as the MATLAB, Julia, Python, R, and others.
	 * "Keys" referred to below is the unique combination given near the top of every module via the macro
	 * THIS_MODULE_KEYS.  For instance, here are the keys for grdtrack:
	 *
	 * #define THIS_MODULE_KEYS        "<D{,DD),GG(,>D}"
	 *
	 * Here are the GMT_Encode_Options arguments:
	 *   API	Controls all things within GMT.
	 *   module	Name of the GMT module.
	 *   n_in	Known number of objects given as input resources (-1 if not known).
	 *   head	Linked list of GMT options passed for this module. We may hook on 1-2 additional options.
	 *   *n		Number of structures returned by the function. Struct GMT_RESOURCE is defined in gmt.h
	 *
	 * We also return an array of structures with information about registered resources going to/from GMT.
	 * Basically, given the module we look up the keys for that module, which tells us which options provide
	 * the input and output selections and which ones are required and which ones are optional.  We then
	 * scan the given options and if file arguments to the options listed in the keys are missing we are
	 * to insert ? as the filename. Some options may already have the question mark. After scanning
	 * the options we examine the keys for any required input or output argument that have yet to be specified
	 * explicitly. If so we create the missing options, with filename = ?, and append them to the end of
	 * the option list (head). The API developers can then use this array of encoded options in concert with
	 * the information passed back via the structure list to attach actual resources.
	 *
	 * For each option that may take a file we need to know what kind of file and if it is input or output.
	 * We encode this information in a 3-character word XYZ, explained below.  Note that each module may
	 * need several comma-separated XYZ words and these are returned as one string via GMT_Get_Moduleinfo.
	 * The X, Y, and Z letter position represent different information, as discussed below:
	 *
	 * X stands for the specific program OPTION (e.g., L for -L, F for -F). For tables or grids read from files or
	 * tables processed via standard input we use '<', while '>' is used for standard (table) output.
	 * Y stands for data TYPE (C => CPT, D => Dataset/Point,Line,Text,
	 *    G => Grid, I => Image, X => PostScript, ? => type specified via a module option [more later]),
	 *    while a hyphen (-) means there is NO data when this option is set (see Z for whether this is for in- or output).
	 * Z stands for PRIMARY inputs '{', primary output '}' OR SECONDARY input '(', or secondary output ')'.
	 *   Primary inputs and outputs MUST be assigned, and if not explicitly given will result in
	 *   a syntax error. However, external APIs (MEX, Julia, Python) can override this and supply the missing items
	 *   via any given left- and right-hand side arguments to supply inputs or accept outputs.
	 *   Secondary inputs means they are only assigned if an option is actually given. If the in|out designation
	 *   is irrelevant for an option we use '-'.
	 *
	 * There are a few special cases where X, Y, or Z take on "magic" behavior:
	 *
	 *   A few modules will have X = - (hyphen). This means the primary input or output (determined by Z)
	 *   has a data type that is not known until runtime. A module option will tells us which type it is, and this
	 *   option is encoded in Y. So a -Y<type> option is _required_ and that is how we can update the primary
	 *   data type.  Example: gmtread can read any GMT object but requires -T<type>. It thus has the keys
	 *   "-T-,<?{,>?}". Hence, we examine -T<type> and replace ? with the dataset implied by <type> both for input
	 *   AND output (since Z was indeterminate).  Use i|o if only input or output should have this treatment.
	 *
	 *   A few modules will have Y = - which is another magic key: If the -X option is given then either the input
	 *   or output (depending on what Z is) will NOT be required. As an example of this behavior, consider psxy
	 *   which has a -T option that means "read no input, just write trailer". So the key "T-<" in psxy means that
	 *   when -T is used then NO input is required. This means the primary input key "<D{" is changed to "<D(" (secondary)
	 *   and no attempt is made to connect external input to the psxy input. If Z is none of () then we expect Z to
	 *   be one of the options with required input (or output) and we change that option to option input (or output).
	 *   Example: grdtrack has two required inputs (the grid(s) and the track/point file. However, if -E is set then
	 *   the track/point file is not expected so we need to change it to secondary. We thus add E-< which then will
	 *   change <D{ to <D(. A modifier is also possible.
	 *
	 *   A few modules will specify Z as some letter not in {|(|}|)|-, which means that normally these modules
	 *   will expect/produce whatever input/output is specified by the primary setting, but if the "-Z" option is given the primary
	 *   input/output will be changed to the given type Y. Also, modifiers may be involved. The full syntax for this is
	 *   XYZ[+abc...][-def...]: We do the substitution of output type to Y only if
	 *      1. -Z is given on the command line
	 *      2. -Z contains ALL the modifiers from the first "+"-list: +a, +b, +c, ... [optional]
	 *      3. -Z contains AT LEAST ONE of the modifiers from the second "-"-list: +d, +e, +f. [optional]
	 *   At least one case from 2 or 3 must be specified.
	 *   The Z magic is a bit confusing so here is some examples:
	 *   1. grdcontour normally writes PostScript but grdcontour -D will instead export data to std (or a file set by -D), so its key
	 *      contains the entry "DDD": When -D is active then the PostScript key ">X}" morphs into "DD}" and
	 *      thus allows for a data set export instead.
	 *   2. pscoast normally plots PostScript but pscoast -E+l only want to return a text listing of countries. We allow for this
	 *      switch by using the key >DE-lL so that if -E with either +l or +L are used we change primary output to D.
	 *
     * There can also be complications when an option either takes a file but may also accept optional modifiers, or the
     * file in question is itself given via an optional modifier. These cases are encoded this way:
     *
     *   A) If an input|output file argument to an option may be followed by optional modifiers we append "=" to that key.
     *        Example: The dataset given to gmtselect via -C (syntax -C<ptfile>|<lon>/<lat>+d<dist>) needs CD(=.
     *   B) If an input|output file is given via an option's modifier +x, then we append =x to that key.
     *        Example: The output dataset file given to grd2cpt via -E (syntax -E[<nlevels>][+c][+f<file>]) needs code ED)=f.
     *
     * Both psxy[z] and the contour functions may specify options that may accept file arguments. These are special cases:
     *
     *  A) psxy[z] lists KEY S?(=2. ? means unknown type, but this is replaced by D (dataset) only if -S requests either the
     *     quoted or decorated lines symbols since these require crossing or fixed-point dataset files. If any other symbol
     *     is specified then the type is set to ! which means we skip the processing of this key.
     *  B) The contour modules have option -G to set annotation placements. The key is G?(=1 and only if -Gf|x is given will we
     *     need to read a dataset (so type becomes D), else type is set to ! to skip the processing of this key.
     *
     *  The optional integer that may follow the '=' character indicates how many characters must we skip following the option
     *  switch before we reach the start of the filename.  Since -Gxfile or -Gffile has that initial x or f we use =1. For the
     *  psxy[z] command we used =2 since -Sqffile, -Sqxfile, -S~ffile, -S~xfile all imply file starts after 2 leading characters.
     *  If not given then the default is 0 (i.e., the filename immediately follows the option switch, like in -Adatafile).
     *
	 *   After processing, all magic key sequences are set to "---" to render them inactive.
	 */

	unsigned int n_keys, direction = 0, kind, pos = 0, n_items = 0, ku, n_out = 0, nn[2][2];
	unsigned int output_pos = 0, input_pos = 0, mod_pos;
	enum GMTAPI_enum_takes_mod takes_mod;
	int family = GMT_NOTSET;	/* -1, or one of GMT_IS_DATASET, GMT_IS_GRID, GMT_IS_PALETTE, GMT_IS_IMAGE */
	int geometry = GMT_NOTSET;	/* -1, or one of GMT_IS_NONE, GMT_IS_TEXT, GMT_IS_POINT, GMT_IS_LINE, GMT_IS_POLY, GMT_IS_SURFACE */
	int sdir, k = 0, n_in_added = 0, n_to_add, e, n_pre_arg, n_per_family[GMT_N_FAMILIES];
	bool deactivate_output = false, deactivate_input = false, strip_colon = false, strip = false, is_grdmath = false;
	size_t n_alloc, len;
	const char *keys = NULL;	/* This module's option keys */
	char **key = NULL;		/* Array of items in keys */
	char *text = NULL, *LR[2] = {"rhs", "lhs"}, *S[2] = {" IN", "OUT"}, txt[GMT_LEN256] = {""}, type = 0;
	char module[GMT_LEN32] = {""}, argument[GMT_BUFSIZ] = {""}, strip_colon_opt = 0;
	char *special_text[3] = {" [satisfies required input]", " [satisfies required output]", ""}, *satisfy = NULL;
	struct GMT_OPTION *opt = NULL, *new_ptr = NULL;	/* Pointer to a GMT option structure */
	struct GMT_RESOURCE *info = NULL;	/* Our return array of n_items info structures */
	struct GMTAPI_CTRL *API = NULL;

	*n = 0;	/* Initialize counter to zero in case we return prematurely */

	if (V_API == NULL) return_null (NULL, GMT_NOT_A_SESSION);
	if (module_name == NULL) return_null (V_API, GMT_ARG_IS_NULL);

	if ((*head) && ((*head)->option == GMT_OPT_USAGE || (*head)->option == GMT_OPT_SYNOPSIS)) {	/* Nothing to do */
		*n = UINT_MAX;
		return NULL;
	}
	API = gmtapi_get_api_ptr(V_API);
	API->error = GMT_NOERROR;
	(void) gmt_current_name (module_name, module);
	gmt_manage_workflow (API, GMT_USE_WORKFLOW, NULL);		/* Detect and set modern mode if modern mode session dir is found */
	/* 0. Get the keys for the module, possibly prepend "gmt" to module if required, or list modules and return NULL if unknown module */
	if ((keys = gmtapi_retrieve_module_keys (V_API, module)) == NULL) {	/* Gave an unknown module */
		if (GMT_Call_Module (V_API, NULL, GMT_MODULE_PURPOSE, NULL))	/* List the available modules */
			return_null (NULL, GMT_NOT_A_VALID_MODULE);
		return_null (NULL, GMT_NOT_A_VALID_MODULE);	/* Unknown module code */
	}

	/* First some special checks related to unusual GMT syntax or hidden modules */

	/* 1a. Check if this is the pscoast module, where output type is either PostScript or Dataset */
	if (!strncmp (module, "pscoast", 7U)) {
		/* Potential problem under modern mode: No -J -R set but will be provided later, and we are doing -E for coloring or lines */
		if (GMT_Find_Option (API, 'M', *head)) type = 'D';	/* -M means dataset dump */
		else if (GMT_Find_Option (API, 'C', *head) || GMT_Find_Option (API, 'G', *head) || GMT_Find_Option (API, 'I', *head) || GMT_Find_Option (API, 'N', *head) || GMT_Find_Option (API, 'W', *head)) type = 'X';	/* Clearly plotting GSHHG */
		else if ((opt = GMT_Find_Option (API, 'E', *head)) && gmt_found_modifier (API->GMT, opt->arg, "cCgp")) type = 'X';	/* Clearly plotting DCW polygons */
		else if ((opt = GMT_Find_Option (API, 'E', *head)) && gmt_found_modifier (API->GMT, opt->arg, "lL")) type = 'D';	/* Clearly requesting listing of DCW countries/states */
		else if (!GMT_Find_Option (API, 'J', *head)) type = 'D';	/* No -M and no -J means -Rstring as dataset */
		else type = 'X';	/* Otherwise we are still most likely plotting PostScript */
	}
	/* 1b. Check if this is psxy or psxyz modules with quoted or decorated lines. For any other -S~|q? flavor we kill the key with ! */
	else if ((!strncmp (module, "psxy", 4U) || !strncmp (module, "psxyz", 5U)) && (opt = GMT_Find_Option (API, 'S', *head))) {
		/* Found the -S option, check if we requested quoted or decorated lines via fixed or crossing lines */
		/* If not f|x then we don't want this at all and set type = ! */
		type = (!strchr ("~q", opt->arg[0]) || !strchr ("fx", opt->arg[1])) ? '!' : 'D';  /* Only -S[~q][fx] will yield D */
		strip_colon = (gmtlib_colon_pos (API->GMT, opt->arg) != GMT_NOTSET); /* true if optional arguments beginning with colon, otherwise false */
		strip_colon_opt = opt->option;    /* An option with (a possible) colon argument (if strip_colon is true, of course) */
		if (strip_colon)
			GMT_Report (API, GMT_MSG_DEBUG, "GMT_Encode_Options: Got quoted or decorate line and must strip argument %s from colon to end\n", opt->arg);
	}
	/* 1c. Check if this is either gmtmath or grdmath which both use the special = outfile syntax and replace that by -=<outfile> */
	else if (!strncmp (module, "gmtmath", 7U) || (is_grdmath = (strncmp (module, "grdmath", 7U) == 0))) {
		struct GMT_OPTION *delete = NULL;
		for (opt = *head; opt && opt->next; opt = opt->next) {	/* Here opt will end up being the last option */
			if (!strcmp (opt->arg, "=")) {
				if (opt->next) {	/* Combine the previous = and <whatever> options into a single -=<whatever> option, then delete the former */
					opt->next->option = '=';
					delete = opt;
				}
			}
		}
		GMT_Delete_Option (API, delete, head);
	}
	/* 1d. Check if this is the write special module, which has flagged its output file as input... */
	else if (!strncmp (module, "gmtwrite", 8U) && (opt = GMT_Find_Option (API, GMT_OPT_INFILE, *head))) {
		/* Found a -<"file" option; this is actually the output file so we simply change the option to output */
		opt->option = GMT_OPT_OUTFILE;
		deactivate_output = true;	/* Remember to turn off implicit output option since we got one */
	}
	/* 1e. Check if this is the grdconvert module, which uses the syntax "infile outfile" without any option flags */
	else if (!strncmp (module, "grdconvert", 10U) && (opt = GMT_Find_Option (API, GMT_OPT_INFILE, *head))) {
		/* Found a -<"file" option; this is indeed the input file but the 2nd "input" is actually output */
		if (opt->next && (opt = GMT_Find_Option (API, GMT_OPT_INFILE, opt->next)))	/* Found the next input file option */
			opt->option = GMT_OPT_OUTFILE;	/* Switch it to an output option */
	}
	/* 1f. Check if this is the greenspline module, where output type is grid for dimension == 2 else it is dataset */
	else if (!strncmp (module, "greenspline", 11U) && (opt = GMT_Find_Option (API, 'R', *head))) {
		/* Found the -R"domain" option; determine the dimensionality of the output */
		unsigned dim = gmtapi_determine_dimension (API, opt->arg);
		switch (dim) {	/* Determine if output is D, G, or U */
			case 1: type = 'D'; break;	/* 1-D is a data table */
			case 2: type = 'G'; break;	/* 2-D is always a grid */
			default:	/* 3-D, but can be dataset or cube */
				type = ((opt = GMT_Find_Option (API, 'G', *head))) ? 'U' : 'D';
				break;
		}
	}
	/* 1g. Check if this is the triangulate module, where primary dataset output should be turned off if -G given without -M,N,Q,S */
	else if (!strncmp (module, "triangulate", 11U) && (opt = GMT_Find_Option (API, 'G', *head))) {
		/* Found the -G<grid> option; determine if any of -M,N,Q,S are also set */
		if (!((opt = GMT_Find_Option (API, 'M', *head)) || (opt = GMT_Find_Option (API, 'N', *head))
			|| (opt = GMT_Find_Option (API, 'Q', *head)) || (opt = GMT_Find_Option (API, 'S', *head))))
				deactivate_output = true;	/* Turn off implicit output since none is in effect */
	}
	/* 1h. Check if this is a *contour modules with -Gf|x given. For any other -G? flavor we kill the key with ! */
	else if ((!strncmp (module, "grdcontour", 10U) || !strncmp (module, "pscontour", 9U)) && (opt = GMT_Find_Option (API, 'G', *head))) {
		/* Found the -G option, check if any strings are requested */
		/* If not -Gf|x then we don't want this at all and set type = ! */
		type = (opt->arg[0] == 'f' || opt->arg[0] == 'x') ? 'D' : '!';
		strip_colon_opt = opt->option;    /* An option with (a possible) colon argument */
	}
	/* 1i. Check if this is the talwani3d module, where output type is grid except with -N it is dataset */
	else if (!strncmp (module, "talwani3d", 9U)) {
		/* If we find the -N option, we set type to D, else G */
		type = (GMT_Find_Option (API, 'N', *head)) ? 'D' : 'G';
	}
	/* 1j. Check if this is a blockm* module using -A to set n output grids */
	else if (!strncmp (module, "block", 5U) && (opt = GMT_Find_Option (API, 'A', *head))) {
		/* Below, k is the number of under=the-hood -G? options we must add for returning grids to externals */
		k = 0;	/* Make sure we initialize this first */
		if (opt->arg[0]) {	/* Gave -A: Determine how many output grids are requested */
			for (k = 1, len = 0; len < strlen (opt->arg); len++) if (opt->arg[len] == ',') k++;
		}
		/* Here, k is still 0 or it is the number of fields selected via -A */
		if ((opt = GMT_Find_Option (API, 'G', *head))) {	/* This is a problem unless -G actually sent in a file name */
			if (opt->arg[0] == '\0') {	/* Cannot just give -G here */
				GMT_Report (API, GMT_MSG_ERROR, "GMT_Encode_Options: %s cannot set -G when called externally\n", module);
				return_null (NULL, GMT_NOT_A_VALID_OPTION);
			}
			else	/* Gave a (presumably) file argument, no need to add -G? separately since the user did it */
				k = 0;
		}
		else if (k == 0)	/* No -A or -G; default is to just add the z grid via -G?  */
			k = 1;
		while (k) {	/* Add -G? option k times */
			new_ptr = GMT_Make_Option (API, 'G', "?");	/* Create new output grid option(s) with filename "?" */
			*head = GMT_Append_Option (API, new_ptr, *head);
			k--;
		}
		deactivate_output = true;	/* Turn off implicit table output since only secondary -G output(s) is in effect */
	}
	/* 1k. Check if this is the earthtide module requesting output grids */
	else if (!strncmp (module, "earthtide", 9U) && !GMT_Find_Option (API, 'L', *head) && !GMT_Find_Option (API, 'S', *head)) {
		/* Below, k is the number of under=the-hood -G? options we must add for returning grids to externals */
		k = 0;	/* Make sure we initialize this first */
		if ((opt = GMT_Find_Option (API, 'C', *head))) {	/* Gave -C: Determine how many output grids are requested */
			for (k = 1, len = 0; len < strlen (opt->arg); len++) if (opt->arg[len] == ',') k++;
		}
		if ((opt = GMT_Find_Option(API, 'G', *head))) {	/* This is a problem unless -G actually sent in a file name */
			if (opt->arg[0] == '\0') {	/* Cannot just give -G here */
				GMT_Report (API, GMT_MSG_ERROR, "GMT_Encode_Options: %s cannot set -G (with no argument) when called externally\n", module);
				return_null (NULL, GMT_NOT_A_VALID_OPTION);
			}
			else	/* Gave a (presumably) file argument, no need to add -G? */
				k = 0;
		}
		else if (k == 0) 	/* No -C or -G; default is to just add the -Gz grid via -G? */
			k = 1;
		while (k) {	/* Add -G? option k times */
			new_ptr = GMT_Make_Option (API, 'G', "?");	/* Create new output grid option(s) with filename "?" */
			*head = GMT_Append_Option (API, new_ptr, *head);
			k--;
		}
		deactivate_output = true;	/* Turn off implicit table output since only secondary -G output(s) is in effect */
	}
	/* 1l. Check if this is makecpt using -E or -S with no args */
	else if (!strncmp (module, "makecpt", 7U)) {
		if (((opt = GMT_Find_Option (API, 'E', *head)) || (opt = GMT_Find_Option (API, 'S', *head)))) {
			if (opt->arg[0] == '\0') {	/* Found the -E or -S option without arguments */
				gmt_M_str_free (opt->arg);
				if (opt->option == 'E')	/* Gave -E but we need to pass -E0 */
					opt->arg = strdup ("0");
				else	/* Replace -S with -Sr */
					opt->arg = strdup ("r");
			}
			/* Then add implicit ? if no input file found */
			if ((opt = GMT_Find_Option (API, GMT_OPT_INFILE, *head)) == NULL) {	/* Must assume implicit input file is available */
				new_ptr = GMT_Make_Option (API, GMT_OPT_INFILE, "?");
				*head = GMT_Append_Option (API, new_ptr, *head);
			}
		}
		else if (API->GMT->current.setting.run_mode == GMT_MODERN && (opt = GMT_Find_Option (API, 'H', *head)) == NULL) {	/* Modern mode, no -H */
			deactivate_output = true;	/* Turn off implicit output since none is in effect */
		}
	}
	/* 1m. Check if this is the grdgradient module, where primary dataset output should be turned off if -Qc and no -G is set */
	else if (!strncmp (module, "grdgradient", 11U) && (opt = GMT_Find_Option (API, 'G', *head)) == NULL) {
		/* Found no -G<grid> option; determine if -Qc is set */
		if ((opt = GMT_Find_Option (API, 'Q', *head)) && opt->arg[0] == 'c')
			deactivate_output = true;	/* Turn off implicit output since none is in effect */
	}
	/* 1m. Check if this is the grdgradient module, where primary dataset output should be turned off if -Qc and no -G is set */
	else if (!strncmp (module, "pstext", 6U) && (opt = GMT_Find_Option (API, 'F', *head))) {
		/* With -F+c<just>+t<label> there is no file to read */
		if (gmt_no_pstext_input (API, opt->arg))
			deactivate_input = true;	/* Turn off implicit input since none is in effect */
	}
	/* 1n. Check that in modern mode, grd2cpt requires -H to write out to stdout */
	else if (!strncmp (module, "grd2cpt", 7U)) {
		if (API->GMT->current.setting.run_mode == GMT_MODERN && (opt = GMT_Find_Option (API, 'H', *head)) == NULL) {	/* Modern mode, no -H */
			deactivate_output = true;	/* Turn off implicit output since none is in effect */
		}
	}
	/* 1o. Check if grdinterpolate is producing grids or datasets */
	else if (!strncmp (module, "grdinterpolate", 14U)) {
		type = gmtapi_grdinterpolate_type (API, *head);	/* Determine the right type for the output */
	}
	/* 1p. Check if grdgdal is reading a dataset */
	else if (!strncmp (module, "grdgdal", 7U)) {	/* Set input data type based on options */
		if ((opt = GMT_Find_Option (API, 'A', *head)) && (strstr (opt->arg, "grid") || strstr (opt->arg, "rasterize")))
			type = 'D';
		else
			type = 'G';
	}
	/* 1q. Check if gmtget is downloading dataset */
	else if (!strncmp (module, "gmtget", 6U) && (opt = GMT_Find_Option (API, 'D', *head))) {
		deactivate_output = true;	/* Download, turn off output */
	}
	/* 1r. Check if gmtbinstats is doing hexagonal tiling */
	else if (!strncmp (module, "gmtbinstats", 11U)) {
		type = ((opt = GMT_Find_Option (API, 'T', *head)) && opt->arg[0] != 'r') ? 'D' : 'G';	/* Giving -T[h] means we change default output from grid to dataset */
	}
	/* 1s. Check if psevents is doing data prep */
	else if (!strncmp (module, "psevents", 8U)) {
		type = ((opt = GMT_Find_Option (API, 'A', *head)) && opt->arg[0] == 'r' && opt->arg[1] && isdigit (opt->arg[1])) ? 'D' : 'X';	/* Giving -Ar<dpi> means we resample a line, else plotting */
	}
	/* 1t. Check if grdinfo is reading cubes or grids */
	else if (!strncmp (module, "grdinfo", 7U)) {
		type = ((opt = GMT_Find_Option (API, 'Q', *head))) ? 'U' : 'G'; /* Giving -Q means we are reading 3-D cubes */
	}
	/* 1u. Check if grdfill is writing grids or datasets */
	else if (!strncmp (module, "grdfill", 7U)) {
		type = ((opt = GMT_Find_Option (API, 'L', *head))) ? 'D' : 'G'; /* Giving -L means we are writing a table */
	}
	/* 1v. Check if spectrum1d uses stdout */
	else if (!strncmp (module, "spectrum1d", 10U)) {
		if ((opt = GMT_Find_Option (API, 'T', *head))) /* Giving -T deactivates stdout writing */
		deactivate_output = true;   /* Turn off implicit output since none is in effect */
	}
	/* 1w. Check if coupe is plotting or returning data */
	else if (!strncmp (module, "pscoupe", 7U)) {
		type = ((opt = GMT_Find_Option (API, 'A', *head)) && strstr (opt->arg, "+c")) ? 'D' : 'X';
	}
	/* 1x. Check if grdcut is just getting information and if input is a grid, image or cube */
	else if (!strncmp (module, "grdcut", 6U)) {
		if (GMT_Find_Option (API, 'D', *head))
			deactivate_output = true;   /* Turn off implicit output since none is in effect, only secondary -D output */
		else if (GMT_Find_Option (API, 'E', *head))
			type = 'U'; /* Giving -E means we are reading a 3-D cube */
		else
			type = 'G'; /* Default is a grid */
	}
	/* 1y. Check if this is the gravprisms module, where primary dataset input should be turned off if -C is used */
	else if (!strncmp (module, "gravprisms", 10U) && (opt = GMT_Find_Option (API, 'C', *head))) {
		deactivate_input = true;    /* Turn off implicit input since none is in effect */
	}
	/* 1z. Check if gmtgravmag3d is producing grids or datasets */
	else if (!strncmp (module, "gmtgravmag3d", 12U)) {
		//type = (opt = GMT_Find_Option (API, 'F', *head)) ? 'D' : 'G';	/* Giving -F<file> means compute over a line, else grid */
	}

	/* 2a. Get the option key array for this module */
	key = gmtapi_process_keys (API, keys, type, *head, n_per_family, &n_keys);	/* This is the array of keys for this module, e.g., "<D{,GG},..." */
	if (n_keys == 0) {		/* gmtset and begin for instances have no keys */
		*n = UINT_MAX;
		return NULL;
	}

	if (gmt_M_is_verbose (API->GMT, GMT_MSG_DEBUG)) {
		char txt[4] = {""};
		for (k = 0; k < GMT_N_FAMILIES; k++) if (n_per_family[k] != GMT_NOTSET) {
			(n_per_family[k] == GMTAPI_UNLIMITED) ? snprintf (txt, 4, ">1") : snprintf (txt, 4, "%d", n_per_family[k]);
			GMT_Report (API, GMT_MSG_DEBUG, "GMT_Encode_Options: For %s we expect %s input objects\n", GMT_family[k], txt);
		}
	}

	/* 2b. Make some specific modifications to the keys given the options passed */
	if (deactivate_output && (k = gmtapi_get_key (API, GMT_OPT_OUTFILE, key, n_keys)) >= 0)
		key[k][K_DIR] = api_not_required_io (key[k][K_DIR]);	/* Since an explicit output file already specified or not required */

	/* 2c. Make some specific modifications to the keys given the options passed */
	if (deactivate_input && (k = gmtapi_get_key (API, GMT_OPT_INFILE, key, n_keys)) >= 0)
		key[k][K_DIR] = api_not_required_io (key[k][K_DIR]);	/* Since an explicit input file already specified or not required */

	/* 3. Count command line output files */
	for (opt = *head; opt; opt = opt->next)
		if (opt->option == GMT_OPT_OUTFILE) n_out++;
	if (n_out > 1) {
		GMT_Report (API, GMT_MSG_ERROR, "GMT_Encode_Options: Can only specify one main output object via command line\n");
		return_null (NULL, GMT_ONLY_ONE_ALLOWED);	/* Too many output objects */
	}
	n_alloc = n_keys;	/* Initial number of allocations */
	if ((info = gmt_M_memory (API->GMT, NULL, n_alloc, struct GMT_RESOURCE)) == NULL) {
		GMT_Report (API, GMT_MSG_ERROR, "GMT_Encode_Options: Unable to allocate GMT_RESOURCE array\n");
		return_null (NULL, GMT_MEMORY_ERROR);
	}

	if (!strncmp (module, "psrose", 6U) && (opt = GMT_Find_Option (API, 'E', *head)) && strcmp (opt->arg, "m")) {
		/* Giving any -E option but -Em means we have either input or output so must update the key accordingly */
		if (strstr (opt->arg, "+w")) {	/* Writing output to file */
			k = gmtapi_get_key (API, 'E', key, n_keys);	/* We know this key exist so k is not -1 */
			gmt_M_str_free (key[k]);
			key[k] = strdup ("ED)=w");	/* Require key to select output to file given via -E+w<file> */
		}
	}

	/* 4. Determine position of file args given as ? or via missing arg (proxy for input matrix) */
	/* Note: All explicit objects must be given after all implicit matrices have been listed */
	for (opt = *head; opt; opt = opt->next) {	/* Process options */
		strip = (strip_colon_opt == opt->option) ? strip_colon : false;	/* Just turn strip possibly true for the relevant option */
		k = gmtapi_get_key (API, opt->option, key, n_keys);	/* If k >= 0 then this option is among those listed in the keys array */
		family = geometry = GMT_NOTSET;	/* Not set yet */
		if (k >= 0) {	/* Got a key, so split out family and geometry flags */
			sdir = gmtapi_key_to_family (API, key[k], &family, &geometry);	/* Get dir, datatype, and geometry */
			if (sdir < 0) {	/* Could not determine direction */
				GMT_Report (API, GMT_MSG_WARNING, "GMT_Encode_Options: Key not understood so direction is undefined? Notify developers\n");
				sdir = GMT_IN;
			}
			direction = (unsigned int) sdir;
		}
		mod_pos = gmtapi_extract_argument (opt->arg, argument, key, k, strip, strip_colon_opt, &n_pre_arg, &takes_mod);	/* Pull out the option argument, possibly modified by the key */
		if (gmtapi_B_custom_annotations (opt)) {	/* Special processing for -B[p|s][x|y|z]c<nofilegiven>] */
			/* Add this item to our list */
			direction = GMT_IN;
			info[n_items].option    = opt;
			info[n_items].family    = GMT_IS_DATASET;
			info[n_items].geometry  = GMT_IS_POINT;
			info[n_items].direction = GMT_IN;
			info[n_items].pos = pos = input_pos++;	/* Explicitly given arguments are the first given on the r.h.s. */
			kind = GMT_FILE_EXPLICIT;
			n_items++;
			n_in_added++;
		}
		else if (gmtapi_found_marker (argument)) {	/* Found an explicit questionmark within the option, e.g., -G?, -R? or -<? */
			if (opt->option == 'R' && !strcmp (opt->arg, "?")) {	/* -R? means append a grid so set those parameters here */
				GMT_Report (API, GMT_MSG_DEBUG, "GMT_Encode_Options: Option -R? found: explicit grid will be substituted\n");
				family = GMT_IS_GRID;
				geometry = GMT_IS_SURFACE;
				direction = GMT_IN;
			}
			else if (k == GMT_NOTSET) {	/* Found questionmark but no corresponding key found? */
				GMT_Report (API, GMT_MSG_WARNING, "GMT_Encode_Options: Got a -<option>? argument but not listed in keys\n");
				direction = GMT_IN;	/* Have to assume it is an input file if not specified */
			}
			if (is_grdmath && gmtapi_operator_takes_dataset (opt, &geometry))
				family = GMT_IS_DATASET;
			info[n_items].mode = (k >= 0 && api_is_required_IO (key[k][K_DIR])) ? K_PRIMARY : K_SECONDARY;
			if (k >= 0 && key[k][K_DIR] != '-')
				key[k][K_DIR] = api_not_required_io (key[k][K_DIR]);	/* Make sure required { becomes ( and } becomes ) so we don't add them later */
			/* Add this item to our list */
			info[n_items].option    = opt;
			info[n_items].family    = family;
			info[n_items].geometry  = geometry;
			info[n_items].direction = direction;
			info[n_items].pos = pos = (direction == GMT_IN) ? input_pos++ : output_pos++;	/* Explicitly given arguments are the first given on the r.h.s. */
			kind = GMT_FILE_EXPLICIT;
			n_items++;
			if (direction == GMT_IN) n_in_added++;
		}

        else if (k >= 0 && key[k][K_OPT] != GMT_OPT_INFILE && family != GMT_NOTSET && key[k][K_DIR] != '-') {	/* Got some option like -G or -Lu with further args */
			bool implicit = true;
			if (takes_mod == GMTAPI_MOD_SPECIAL)	/* Got some option like -E[+f] but no +f was given so no implicit file needed */
				implicit = false;
			else if ((len = strlen (argument)) == (size_t)n_pre_arg)	/* Got some option like -G or -Lu with no further args */
				GMT_Report (API, GMT_MSG_DEBUG, "GMT_Encode_Options: Option -%c needs implicit arg [offset = %d]\n", opt->option, n_pre_arg);
			else if (mod_pos && (argument[mod_pos] == '\0' || argument[mod_pos] == '+'))	/* Found an embedded +q<noarg> */
				GMT_Report (API, GMT_MSG_DEBUG, "GMT_Encode_Options: Option -%c needs implicit arg via argument-less +%c modifier\n", opt->option, key[k][K_MODIFIER]);
			else
				implicit = false;
			if (implicit) {
				/* This is an implicit reference and we must explicitly add the missing item by adding the questionmark */
				info[n_items].option    = opt;
				info[n_items].family    = family;
				info[n_items].geometry  = geometry;
				info[n_items].direction = direction;
				info[n_items].mode = (api_is_required_IO (key[k][K_DIR])) ? K_PRIMARY : K_SECONDARY;
				key[k][K_DIR] = api_not_required_io (key[k][K_DIR]);	/* Change to ( or ) since option was provided, albeit implicitly */
				info[n_items].pos = pos = (direction == GMT_IN) ? input_pos++ : output_pos++;
				/* Explicitly add the missing marker (e.g., ?) to the option argument */
				if (mod_pos) {	/* Must expand something like 300k+s+d+u into 300k+s?+d+u (assuming +s triggered this test) */
					strncpy (txt, opt->arg, mod_pos);
					strcat (txt, "?");
					if (opt->arg[mod_pos]) strcat (txt, &opt->arg[mod_pos]);
				}
				else if (strip)	/* Special case for quoted and decorated lines with colon separating label info */
					snprintf (txt, GMT_LEN256, "%s?%s", argument, &opt->arg[2]);
				else if (n_pre_arg)	/* Something like -Lu becomes -Lu? */
					snprintf (txt, GMT_LEN256, "%s?", opt->arg);
				else	/* Something like -C or -C+d200k becomes -C? or -C?+d200k */
					snprintf (txt, GMT_LEN256, "?%s", opt->arg);
				gmt_M_str_free (opt->arg);
				opt->arg = strdup (txt);
				kind = GMT_FILE_EXPLICIT;
				n_items++;
				if (direction == GMT_IN) n_in_added++;
			}
			else {	/* No implicit file argument involved, just check if this satisfies a required option */
				kind = GMT_FILE_NONE;
				if (k >= 0) {	/* If this was a required input|output it has now been satisfied */
					/* Add check to make sure argument for input is an existing file! */
					key[k][K_DIR] = api_not_required_io (key[k][K_DIR]);	/* Change to ( or ) since option was provided, albeit implicitly */
					satisfy = special_text[direction];
				}
				else	/* Nothing special about this option */
					satisfy = special_text[2];
			}
		}
		else {	/* No implicit file argument involved, just check if this satisfies a required option */
			kind = GMT_FILE_NONE;
			if (k >= 0) {	/* If this was a required input|output it has now been satisfied */
				/* Add check to make sure argument for input is an existing file! */
				key[k][K_DIR] = api_not_required_io (key[k][K_DIR]);	/* Change to ( or ) since option was provided, albeit implicitly */
				satisfy = special_text[direction];
			}
			else	/* Nothing special about this option */
				satisfy = special_text[2];
		}
		if (kind == GMT_FILE_EXPLICIT)
			GMT_Report (API, GMT_MSG_DEBUG, "%s: Option -%c%s includes a memory reference to %s argument # %d\n",
			            S[direction], opt->option, opt->arg, LR[direction], pos);
		else
			GMT_Report (API, GMT_MSG_DEBUG, "---: Option -%c%s includes no memory reference%s\n",
			            opt->option, opt->arg, satisfy);
		if (n_items == n_alloc) {
			n_alloc <<= 1;
			if ((info = gmt_M_memory (API->GMT, info, n_alloc, struct GMT_RESOURCE)) == NULL) {
				GMT_Report (API, GMT_MSG_ERROR, "GMT_Encode_Options: Unable to reallocate GMT_RESOURCE array\n");
				return_null (NULL, GMT_MEMORY_ERROR);
			}
		}
	}

	/* Done processing references that were explicitly given in the options.  Now determine if module
	 * has required input or output references that we must add (if not specified explicitly above) */

	for (ku = 0; ku < n_keys; ku++) {	/* Each set of keys specifies if the item is required via the 3rd key letter */
		if (api_is_required_IO (key[ku][K_DIR])) {	/* Required input|output that was not specified explicitly above */
			char str[2] = {'?',0};
			if ((sdir = gmtapi_key_to_family (API, key[ku], &family, &geometry)) == GMT_NOTSET) {	/* Extract family and geometry */
				GMT_Report (API, GMT_MSG_WARNING, "Failure to extract family, geometry, and direction!!!!\n");
				continue;
			}
			if (API->GMT->current.setting.run_mode == GMT_MODERN && family == GMT_IS_POSTSCRIPT) continue;	/* No PS output in modern mode please */
			direction = (unsigned int) sdir;
			/* We need to know how many implicit items for a given family we might have to add.  For instance,
			 * one can usually give any number of data or text tables but only one grid file.  However, this is
			 * not a fixed thing, hence we counted up n_per_family from the keys earlier so we have some limits */
			if (direction == GMT_OUT || n_in == GMT_NOTSET)	/* For output or lack of info we only add one item per key */
				n_to_add = 1;
			else	/* More information to act on for inputs */
				n_to_add = (n_per_family[family] == GMTAPI_UNLIMITED) ? n_in - n_in_added : n_per_family[family];
			for (e = 0; e < n_to_add; e++) {
				new_ptr = GMT_Make_Option (API, key[ku][K_OPT], str);	/* Create new option(s) with filename "?" */
				/* Append the new option to the list */
				*head = GMT_Append_Option (API, new_ptr, *head);
				info[n_items].option    = new_ptr;
				info[n_items].family    = family;
				info[n_items].geometry  = geometry;
				info[n_items].direction = direction;
				info[n_items].pos = (direction == GMT_IN) ? input_pos++ : output_pos++;
				info[n_items].mode = K_PRIMARY;
				GMT_Report (API, GMT_MSG_DEBUG, "%s: Must add -%c? as implicit memory reference to %s argument # %d\n",
					S[direction], key[ku][K_OPT], LR[direction], info[n_items].pos);
				n_items++;
				if (direction == GMT_IN) n_in_added++;
				if (n_items == n_alloc) {
					n_alloc <<= 1;
					if ((info = gmt_M_memory (API->GMT, info, n_alloc, struct GMT_RESOURCE)) == NULL) {
						GMT_Report (API, GMT_MSG_ERROR, "GMT_Encode_Options: Unable to reallocate GMT_RESOURCE array\n");
						return_null (NULL, GMT_MEMORY_ERROR);
					}
				}
			}
		}
		gmt_M_str_free (key[ku]);	/* Free up this key */
	}
	/* Free up the temporary key array */
	gmt_M_str_free (key);

	/* Reallocate the information structure array or remove entirely if nothing given. */
	if (n_items && n_items < n_alloc) {
		if ((info = gmt_M_memory (API->GMT, info, n_items, struct GMT_RESOURCE)) == NULL) {
			GMT_Report (API, GMT_MSG_ERROR, "GMT_Encode_Options: Unable to finalize size of GMT_RESOURCE array\n");
			return_null (NULL, GMT_MEMORY_ERROR);
		}
	}
	else if (n_items == 0)	/* No containers used */
		gmt_M_free (API->GMT, info);

	gmt_M_memset (nn, 4, unsigned int);
	for (ku = 0; ku < n_items; ku++)	/* Count how many primary and secondary objects each for input and output */
		nn[info[ku].direction][info[ku].mode]++;

	for (ku = 0; ku < n_items; ku++) {	/* Reorder positions so that primary objects are listed before secondary objects */
		if (info[ku].mode == K_SECONDARY) info[ku].pos += nn[info[ku].direction][K_PRIMARY];	/* Move secondary objects after all primary objects for this direction */
		else info[ku].pos -= nn[info[ku].direction][K_SECONDARY];	/* Move any primary objects to start of list for this direction */
	}
	GMT_Report (API, GMT_MSG_DEBUG, "GMT_Encode_Options: Found %d inputs and %d outputs that need memory hook-up\n", input_pos, output_pos);
#if 0
	{	/* Left here for simple debugging with messages on the GMT side */
		text = GMT_Create_Cmd (API, *head);
		GMT_Report (API, GMT_MSG_NOTICE, "GMT_Encode_Options: Revised command before memory-substitution: %s\n", text);
		GMT_Destroy_Cmd (API, &text);
	}
#endif
	/* Just checking that the options were properly processed */
	if (gmt_M_is_verbose (API->GMT, GMT_MSG_DEBUG)) {
		static char *omode[2] = {"Primary", "Secondary"};
		text = GMT_Create_Cmd (API, *head);
		GMT_Report (API, GMT_MSG_DEBUG, "GMT_Encode_Options: Revised command before memory-substitution: %s\n", text);
		GMT_Destroy_Cmd (API, &text);
		GMT_Report (API, GMT_MSG_DEBUG, "GMT_Encode_Options: List of items returned:\n");
		for (ku = 0; ku < n_items; ku++) {
			GMT_Report (API, GMT_MSG_DEBUG, "External API item %2d: Family: %14s Direction: %6s Pos: %d Mode: %s\n",
				ku, GMT_family[info[ku].family], GMT_direction[info[ku].direction], info[ku].pos, omode[info[ku].mode]);
		}
	}

	/* Pass back the info array and the number of items */
	*n = (n_items == 0) ? UINT_MAX : n_items;	/* E.g., n_keys = 0 for gmtset, gmtdefaults, gmtlogo */
	return (info);
}

#ifdef FORTRAN_API
struct GMT_RESOURCE *GMT_Encode_Options_ (const char *module, int *n_in, struct GMT_OPTION **head, unsigned int *n, int len) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Encode_Options (GMT_FORTRAN, module, *n_in, head, n));
}
#endif

/* Parsing API: to present, examine GMT Common Option current settings and GMT Default settings */

/*! . */
int GMT_Get_Common (void *V_API, unsigned int option, double par[]) {
	/* Inquires if specified GMT option has been set and obtains current values for some of them, if par is not NULL.
	 * Returns GMT_NOTSET if the option has not been specified.  Otherwise, returns the number of parameters
	 * it passed back via the par[] array.  Only some options passes back parameters; these are
	 * -R, -I, -X, -Y, -b, -f, -i, -o, -r, -t, -:, while the others return 0.
	 */
	int ret = GMT_NOTSET;
	struct GMTAPI_CTRL *API = NULL;
	struct GMT_CTRL *GMT = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;
	GMT = API->GMT;

	switch (option) {
		case 'B':	if (API->common_snapshot->B.active[0] || API->common_snapshot->B.active[1]) ret = 0; break;
		case 'I':
			if (API->common_snapshot->R.active[ISET]) {
				ret = 2;
				if (par) gmt_M_memcpy (par, API->common_snapshot->R.inc, 2, double);
			}
			break;
		case 'J':	if (API->common_snapshot->J.active) ret = 0; break;
		case 'K':	if (API->common_snapshot->K.active) ret = 0; break;
		case 'O':	if (API->common_snapshot->O.active) ret = 0; break;
		case 'P':	if (API->common_snapshot->P.active) ret = 0; break;
		case 'R':
			if (API->common_snapshot->R.active[RSET]) {
				ret = 4;
				if (par) gmt_M_memcpy (par, API->common_snapshot->R.wesn, 4, double);
			}
			break;
		case 'U':	if (API->common_snapshot->U.active) ret = 0; break;
		case 'V':	if (API->common_snapshot->V.active) ret = GMT->current.setting.verbose; break;
		case 'X':
			if (API->common_snapshot->X.active) {
				ret = 1;
				if (par) par[0] = API->common_snapshot->X.off;
			}
			break;
		case 'Y':
			if (API->common_snapshot->Y.active) {
				ret = 1;
				if (par) par[0] = API->common_snapshot->Y.off;
			}
			break;
		case 'a':	if (API->common_snapshot->a.active) ret = API->common_snapshot->a.geometry; break;
		case 'b':	if (API->common_snapshot->b.active[GMT_IN]) ret = GMT_IN; else if (API->common_snapshot->b.active[GMT_OUT]) ret = GMT_OUT; break;
		case 'f':	if (API->common_snapshot->f.active[GMT_IN]) ret = GMT_IN; else if (API->common_snapshot->f.active[GMT_OUT]) ret = GMT_OUT; break;
		case 'g':	if (API->common_snapshot->g.active) ret = 0; break;
		case 'h':	if (API->common_snapshot->h.active) ret = API->common_snapshot->h.mode; break;
		case 'i':	if (API->common_snapshot->i.col.select) ret = (int)API->common_snapshot->i.col.n_cols; break;
		case 'n':	if (API->common_snapshot->n.active) ret = 0; break;
		case 'o':	if (API->common_snapshot->o.col.select) ret = (int)API->common_snapshot->o.col.n_cols; break;
		case 'p':	if (API->common_snapshot->p.active) ret = 0; break;
		case 'r':	if (API->common_snapshot->R.active[GSET]) ret = API->common_snapshot->R.registration; break;
		case 's':	if (API->common_snapshot->s.active) ret = 0; break;
		case 't':
			if (API->common_snapshot->t.active) {
				ret = 2;
				if (par) gmt_M_memcpy (par, API->common_snapshot->t.value, 2, double);
			}
			break;
		case ':':	if (API->common_snapshot->colon.toggle[GMT_IN]) ret = GMT_IN; else if (API->common_snapshot->colon.toggle[GMT_OUT]) ret = GMT_OUT; break;
		default:
			gmtlib_report_error (API, GMT_OPTION_NOT_FOUND);
			break;
	}

	return (ret);
}

#ifdef FORTRAN_API
int GMT_Get_Common_ (unsigned int *option, double par[]) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Get_Common (GMT_FORTRAN, *option, par));
}
#endif

/*! . */
int GMT_Get_Default (void *V_API, const char *keyword, char *value) {
	/* Given the text representation of a GMT parameter keyword, return its setting as text.
	 * value must have enough space for the return information.
	 */
	int error = GMT_NOERROR;
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (keyword == NULL) return_error (V_API, GMT_NO_PARAMETERS);
	if (value == NULL) return_error (V_API, GMT_NO_PARAMETERS);
	API = gmtapi_get_api_ptr (V_API);
	/* First intercept any API Keywords */
	if (!strncmp (keyword, "API_VERSION", 11U))	/* The API version */
		sprintf (value, "%s", GMT_PACKAGE_VERSION);
	else if (!strncmp (keyword, "API_PAD", 7U))	/* Change the grid padding setting */
		sprintf (value, "%d", API->pad);
	else if (!strncmp (keyword, "API_BINDIR", 10U))	/* Report binary directory */
		sprintf (value, "%s", API->GMT->init.runtime_bindir);
	else if (!strncmp (keyword, "API_SHAREDIR", 12U))	/* Report share directory */
		sprintf (value, "%s", API->GMT->session.SHAREDIR);
	else if (!strncmp (keyword, "API_DATADIR", 12U))	/* Report data directory */
		sprintf (value, "%s", API->GMT->session.DATADIR);
	else if (!strncmp (keyword, "API_PLUGINDIR", 14U))	/* Report plugin directory */
		sprintf (value, "%s", API->GMT->init.runtime_plugindir);
	else if (!strncmp (keyword, "API_LIBRARY", 11U))	/* Report core library */
		sprintf (value, "%s", API->GMT->init.runtime_library);
	else if (!strncmp (keyword, "API_CORES", 9U))	/* Report number of cores */
		sprintf (value, "%d", API->n_cores);
	else if (!strncmp (keyword, "API_IMAGE_LAYOUT", 16U)) {	/* Report image/band layout */
		gmt_M_memcpy (value, API->GMT->current.gdal_read_in.O.mem_layout, 4, char);
	}
	else if (!strncmp (keyword, "API_BIN_VERSION", 15U))	/* The API version */
		sprintf (value, "%s", GMT_PACKAGE_VERSION_WITH_GIT_REVISION);
	else if (!strncmp (keyword, "API_GRID_LAYOUT", 15U)) {	/* Report grid layout */
		if (API->shape == GMT_IS_COL_FORMAT)
			strcpy (value, "columns");
		else if (API->shape == GMT_IS_ROW_FORMAT)
			strcpy (value, "rows");
	}
	else {	/* Must process as a GMT setting */
		strcpy (value, gmtlib_getparameter (API->GMT, keyword));
		error = (value[0] == '\0') ? GMT_OPTION_NOT_FOUND : GMT_NOERROR;
	}
	return_error (V_API, error);
}

#ifdef FORTRAN_API
int GMT_Get_Default_ (char *keyword, char *value, int len1, int len2) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Get_Default (GMT_FORTRAN, keyword, value));
}
#endif

/*! . */
int GMT_Set_Default (void *V_API, const char *keyword, const char *txt_val) {
	/* Given the text representation of a GMT or API parameter keyword, assign its value.
	 */
	unsigned int error = GMT_NOERROR;
	struct GMTAPI_CTRL *API = NULL;
	char *value = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (keyword == NULL) return_error (V_API, GMT_NOT_A_VALID_PARAMETER);
	if (txt_val == NULL) return_error (V_API, GMT_NO_PARAMETERS);
	API = gmtapi_get_api_ptr (V_API);
	value = strdup (txt_val);	/* Make local copy to be safe */
	/* First intercept any API Keywords */
	if (!strncmp (keyword, "API_PAD", 7U)) {	/* Change the grid padding setting */
		int pad = atoi (value);
		if (pad >= 0) {
			gmt_set_pad (API->GMT, pad);	/* Change the default pad; give GMT_NOTSET to leave as is */
			API->pad = pad;
		}
	}
	else if (!strncmp (keyword, "API_IMAGE_LAYOUT", 16U)) {	/* Change image/band layout */
		if (strlen (value) != 4U) {
			error = 1;
			GMT_Report (API, GMT_MSG_ERROR, "API_IMAGE_LAYOUT requires a 4-character specification. %s is ignored",  value);
		}
		else
			gmt_M_memcpy (API->GMT->current.gdal_read_in.O.mem_layout, value, 4, char);
	}
	else if (!strncmp (keyword, "API_GRID_LAYOUT", 15U)) {	/* Change grid layout */
		if (!strncmp (value, "columns", 7U) || (strlen(value) >= 2 && value[1] == 'C'))		/* Accept also TC, though ignore 1st and 3-end chars. Accept this to be consistent with the "API_IMAGE_LAYOUT" case */
			API->shape = GMT_IS_COL_FORMAT;	/* Switch to column-major format */
		else if (!strncmp (value, "rows", 4U) || (strlen(value) >= 2 && value[1] == 'R'))
			API->shape = GMT_IS_ROW_FORMAT;	/* Switch to row-major format */
		else {
			GMT_Report (API, GMT_MSG_ERROR, "API_GRID_LAYOUT must be either \"columns\" (or TC) or \"rows\" (TR)",  value);
			error = 1;
		}
	}
	else	/* Must process as a GMT setting */
		error = gmtlib_setparameter (API->GMT, keyword, value, false);
	gmt_M_str_free (value);
	return_error (V_API, (error) ? GMT_NOT_A_VALID_PARAMETER : GMT_NOERROR);
}

#ifdef FORTRAN_API
int GMT_Set_Default_ (char *keyword, char *value, int len1, int len2) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Set_Default (GMT_FORTRAN, keyword, value));
}
#endif

/*! . */
int GMT_Option (void *V_API, const char *options) {
	/* Take comma-separated GMT options and print the corresponding usage message(s).
	 * Taken: jbSGzCDkFyA */
	unsigned int pos = 0, k = 0, n = 0;
	char p[GMT_LEN64] = {""}, arg[GMT_LEN64] = {""};
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (options == NULL) return_error (V_API, GMT_NO_PARAMETERS);
	API = gmtapi_get_api_ptr (V_API);

	/* The following does the translation between the rules for the option string and the convoluted items gmtlib_explain_options expects. */
	while (gmt_strtok (options, ",", &pos, p) && k < (GMT_LEN64-1)) {
		switch (p[0]) {
			case 'B':	/* Let B be B and B- be b */
				arg[k++] = (p[1] == '-') ? 'b' : 'B';
				break;
			case 'J':	/* Let J be -J and J- be j, JX is -Jx|X only, and -J[-]3 be adding -Z for 3-D scaling */
				n = 1;
				if (p[1] == '-') { arg[k++] = 'j'; n++; }
				else if (p[1] == 'X') { arg[k++] = 'x'; n++; }
				else arg[k++] = 'J';
				if (p[n] == 'Z' || p[n] == 'z') arg[k++] = 'Z';
				break;
			case 'R':	/* Want -R region usage */
				if (p[1]) {	/* Gave modifiers */
					if (p[1] == 'x') arg[k++] = 'S';	/* CarteSian region */
					else if (p[1] == 'g') arg[k++] = 'G';	/* Geographic region */
					else arg[k++] = 'R';			/* Generic region [Default] */
					if (p[1] == '3' || p[2] == '3') arg[k++] = 'z';	/* 3-D region */
				}
				else arg[k++] = 'R';			/* Generic region [Default] */
				break;
			case 'b':	/* Binary i/o -bi -bo */
				arg[k++] = (p[1] == 'i') ? 'C' : 'D';
				arg[k++] = (p[2]) ? p[2] : '0';
				break;
			case 'd':	/* Nodata flag -d, -di, -do */
				if (p[1] == 'i') arg[k++] = 'k';
				else if (p[1] == 'o') arg[k++] = 'm';
				else arg[k++] = 'd';
				break;
			case 'j':	/* Spherical distance calculation mode */
				arg[k++] = 'A';
				break;
			case 'q':	/* Row selection, either just input, output, or both */
				if (p[1] == 'i')
					arg[k++] = 'u';
				else if (p[1] == 'o')
					arg[k++] = 'v';
				else
					arg[k++] = p[0];
				break;
			case 'r':	/* Pixel registration */
				arg[k++] = 'F';
				break;
			case 'x':	/* Number of threads (for multi-threaded progs) */
				arg[k++] = 'y';
				break;
			default:	/* All others are pass-through */
				arg[k++] = p[0];
				break;
		}
	}
	gmtlib_explain_options (API->GMT, arg);	/* Call the underlying explain_options machinery */
	return_error (V_API, GMT_NOERROR);
}

#ifdef FORTRAN_API
int GMT_Option_ (char *options, int len) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Option (GMT_FORTRAN, options));
}
#endif

/*! . */
int GMT_Message (void *V_API, unsigned int mode, const char *format, ...) {
	/* Message independent of verbosity, optionally with timestamp.
	 * mode = 0:	No time stamp
	 * mode = 1:	Abs time stamp formatted via GMT_TIME_STAMP
	 * mode = 2:	Report elapsed time since last reset.
	 * mode = 4:	Reset elapsed time to 0, no time stamp.
	 * mode = 6:	Reset elapsed time and report it as well.
	 */
	size_t source_info_len;
	char *stamp = NULL;
	struct GMTAPI_CTRL *API = NULL;
	FILE *err = stderr;
	va_list args;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (format == NULL) return GMT_PTR_IS_NULL;	/* Format cannot be NULL */
	API = gmtapi_get_api_ptr (V_API);	/* Get the typecast structure pointer to API */
	API->message[0] = '\0';	/* Start fresh */
	if (mode) stamp = gmtapi_tictoc_string (API, mode);	/* Pointer to a timestamp string */
	if (mode % 4) sprintf (API->message, "%s | ", stamp);	/* Lead with the time stamp */
	source_info_len = strlen (API->message);

	va_start (args, format);
	vsnprintf (API->message + source_info_len, GMT_MSGSIZ - source_info_len, format, args);
	va_end (args);
	assert (strlen (API->message) < GMT_MSGSIZ);
	if (API->GMT) err = API->GMT->session.std[GMT_ERR];
	API->print_func (err, API->message);	/* Do the printing */
	return_error (V_API, GMT_NOERROR);
}

#ifdef FORTRAN_API
int GMT_Message_ (unsigned int *mode, const char *message, int len) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Message (GMT_FORTRAN, *mode, message));
}
#endif

/*! . */
int GMT_Report (void *V_API, unsigned int level, const char *format, ...) {
	/* Message whose output depends on verbosity setting */
	size_t source_info_len = 0;
	unsigned int g_level;
	const char *module_name;
	char not_used[GMT_LEN32];
	FILE *err = stderr;
	struct GMTAPI_CTRL *API = NULL;
	struct GMT_CTRL *GMT = NULL;
	va_list args;
	/* GMT_Report may be called before GMT is set so take precautions */
	if (V_API == NULL) return GMT_NOERROR;		/* Not a fatal issue here but we cannot report anything */
	if (level == GMT_MSG_QUIET)		/* We don't want to hear it, period. */
		return GMT_NOERROR;
	API = gmtapi_get_api_ptr (V_API);	/* Get the typecast structure pointer to API */
	GMT = API->GMT;	/* Short-hand for the GMT sub-structure */
	g_level = (GMT) ? GMT->current.setting.verbose : GMT_MSG_QUIET;
	if (GMT) err = GMT->session.std[GMT_ERR];
	if (level > MAX(API->verbose, g_level))
		return GMT_NOERROR;
	if (format == NULL) return GMT_PTR_IS_NULL;	/* Format cannot be NULL */
	API->message[0] = '\0';	/* Start fresh */
	if (GMT && GMT->current.setting.timer_mode > GMT_NO_TIMER) {
		char *stamp = gmtapi_tictoc_string (API, GMT->current.setting.timer_mode);	/* NULL or pointer to a timestamp string */
		if (stamp) {
			sprintf (API->message, "%s | ", stamp);	/* Lead with the time stamp */
			source_info_len = strlen (API->message);	/* Update length of message from 0 */
		}
	}
	if (GMT && GMT->init.module_name)
		module_name = ((GMT->current.setting.run_mode == GMT_MODERN)) ? gmt_current_name (GMT->init.module_name, not_used) : GMT->init.module_name;
	else
		module_name = API->session_tag;

	snprintf (API->message + source_info_len, GMT_MSGSIZ-source_info_len, "%s [%s]: ", module_name, GMT_class[level]);
	source_info_len = strlen (API->message);
	va_start (args, format);
	/* append format to the message: */
	vsnprintf (API->message + source_info_len, GMT_MSGSIZ - source_info_len, format, args);
	va_end (args);
	assert (strlen (API->message) < GMT_MSGSIZ);
	gmt_M_memcpy (API->error_msg, API->message, GMT_BUFSIZ-1, char);
	API->print_func (err, API->message);
	return_error (V_API, GMT_NOERROR);
}

#ifdef FORTRAN_API
int GMT_Report_ (unsigned int *level, const char *format, int len) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Report (GMT_FORTRAN, *level, format));
}
#endif

GMT_LOCAL unsigned int gmtapi_hyphen (const char *line, unsigned int stop) {
    /* Examine a few cases:
     * 0) line[stop] is not a hyphen
     * 1) -option : Start of an option is not hyphenation.
     *    --PAR=value: Special case of option.
     * 2) hyphen-word: Detected if text/numbers on each side.  Just break, no continuation character needed.
     */
    if (line[stop] != '-') return 0;    /* Not a hyphenated case */
    if (stop == 0) return 2;    /* Nothing before the hyphen means option or negative number, cannot split */
    if (line[stop-1] == '-') return 0;   /* --PAR case caught */
    if (line[stop-1] == '[') return 0;   /*  [-option] case caught */
    if (line[stop-1] == ' ') return 0;   /*  -option case caught */
    //return (isalpha (line[stop+1]) && isalpha (line[stop-1]) ? 2 : 1);
    return (2); /* Regular hyphenated word or number range or similar */
}

GMT_LOCAL struct GMT_WORD * gmtapi_split_words (const char *line) {
	/* Split line into an array of words where words are separated by either
	 * a space (like between options), the occurrence of "][" sequences, or
	 * items separated by slashes "/", commas ",", bars "|" or hyphens "-".
	 * These are the places where we are allowed to break the line. */
	struct GMT_WORD *array = NULL;
	unsigned int n = 0, c, start = 0, next, end, j, stop, space = 0, n_alloc = GMT_LEN256, hyphen = 0;
	array = calloc (n_alloc, sizeof (struct GMT_WORD));
	while (line[start]) {	/* More line to chop up */
        hyphen = 0; /* Initialize */
		/* Find the next break location */
		stop = start;
		while (line[stop] && !(strchr (" ,/|", line[stop]) || (line[stop] == ']' && line[stop+1] == '[') || (hyphen = gmtapi_hyphen (line, stop)))) stop++;
		end = next = stop;	/* Mark likely end */
		array[n].space = space;	/* Do we need a leading space (set via previous word)? */
		if (line[stop] == ' ') {	/* Skip the space to start over at next word */
			while (line[stop] == ' ') stop++;	/* In case there are more than one space */
			next = stop; space = 1;
		}
		else if (line[stop] && strchr (",/|]", line[stop])) {   /* Include this char then break */
			next = ++end, space = 0;
		}
		else if (line[stop] == '-') {   /* Include this char then break with no break symbol */
			next = ++end, space = hyphen;
		}
		array[n].word = calloc (end - start + 1, sizeof (char));	/* Allocate space for word */
		for (j = start, c = 0; j < end; j++, c++) array[n].word[c] = line[j];
		n++;	/* Got another word */
		if (n == n_alloc) {	/* Need more memory, must be a long line */
			n_alloc += GMT_LEN256;
			array = realloc (array, n_alloc*sizeof (struct GMT_WORD));
		}
		start = next;	/* Advance to start of next word */
	}
	/* Finalize array length - keep one extra to indicate end of array */
	array = realloc (array, (n+1)*sizeof (struct GMT_WORD));
#if 0	/* Left for possible debug in case there are unresolved issues of breaking things up into words */
	fprintf (stderr, "Found %d words\n", n);
	for (j = 0; j < n; j++)
		fprintf (stderr, "%2.2d: [%d] %s\n", j, array[j].space, array[j].word);
	fprintf (stderr, "\n");
#endif

	return (array);
}

GMT_LOCAL unsigned int gmtapi_space (unsigned int space) {
    return (space == 2) ? 0 : space;
}

GMT_LOCAL void gmtapi_wrap_the_line (struct GMTAPI_CTRL *API, int level, FILE *fp, const char *in_line) {
	/* Break the in_ine across multiple lines determined by the terminal line width API->terminal_width */
	bool keep_same_indent = (level < 0), go = true, force = false;
	int k, j, next_level, current_width = 0, try = 0;
	unsigned int width;
	static int gmtapi_indent[8] = {0, 2, 5, 7, 10, 13, 15, 0}; /* Last one is for custom negative values exceeding 6 */
	struct GMT_WORD *W = gmtapi_split_words (in_line);	/* Create array of words */
	char message[GMT_MSGSIZ] = {""};

#ifdef DEBUG_USAGE_OPTIONS
	{	/* Breaks usage string into one word per line.  See gmt_usage_messages.sh in the sandbox/guru repo for what to do next */
		strcpy (message, in_line);
		gmt_strrepc (message, ' ', '\n');
		API->print_func (fp, message);  /* Do the printing */
		return;
	}
 #endif

	/* Start with any fixed indent */
	level = abs (level);	/* In case it was negative */
	if (level > 6) {    /* Place custom level in last entry.  This is to help g**math issue wrapped operator messages */
		gmtapi_indent[7] = level;
		level = 7;
		go = false;   /* Already indented and ready to go */
	}
	next_level = (keep_same_indent) ? level : level + 1;	/* Do we indent when we wrap or not */
	for (j = 0; go && j < gmtapi_indent[level]; j++) strcat (message, " ");	/* Starting spaces */
	current_width = gmtapi_indent[level];
	for (k = 0; W[k].word; k++) {	/* As long as there are more words... */
		width = (gmtapi_space (W[k+1].space)) ? API->terminal_width : API->terminal_width - 1;	/* May need one space for ellipsis at end */
		if (force || (current_width + strlen (W[k].word) + W[k].space) < width) {	/* Word will fit on current line */
			if (gmtapi_space (W[k].space))	/* This word requires a leading space */
				strcat (message, " ");
			strcat (message, W[k].word);
			current_width += (int)(strlen (W[k].word) + gmtapi_space (W[k].space));	/* Update line width so far */
			free (W[k].word);	/* Free the word we are done with */
			if (W[k+1].word == NULL)	/* Finalize the last line */
				strcat (message, "\n");
			force = false;  /* In case it was set */
		}
		else {	/* Must split at the current break point and continue on next line */
			if (W[k].space) { /* No break character needed since space separation is expected */
				strcat (message, "\n");	/* Move to new line */
				for (j = 0; j < gmtapi_indent[next_level]; j++) strcat (message, " ");	/* Initial indent plus possibly next indent*/
				current_width = gmtapi_indent[next_level];	/* Indent plus next indent */
			}
			else {	/* Split in the middle of an option so append breakline and start new line with ellipsis after indent */
				strcat (message, GMT_LINE_BREAK);
				strcat (message, "\n");
				for (j = 0; j < gmtapi_indent[next_level]; j++) strcat (message, " ");	/* Initial indent plus possibly next indent */
				strcat (message, GMT_LINE_CONT);		/* And the ellipsis */
				current_width = gmtapi_indent[next_level];	/* Indent plus the next indent */
			}
			W[k].space = 0;	/* Can be no leading space if starting a the line */
			k--;	/* Since k will be incremented by loop and we did not write this word yet */
			try++;
			if (try == 2) /* Word is longer than effective terminal width - must force output (even if too long) to get past this stalemate */
				force = true, try = 0;
		}
	}
	free (W);	/* Free the structure array */
	API->print_func (fp, message);	/* Do the printing */
}

/*! . */
int GMT_Usage (void *V_API, int level, const char *format, ...) {
	/* Wrapped usage message independent of verbosity.
	 * level is the starting indent level of the line
	 * format and optional args must be printed to a string first, then wrapped.
	 *
	 * Explanation for the use of level:
	 * 0  : Only use for the synopsis message where we start all the way to the left.
	 *      Once the first line wraps we indent all subsequent lines by the same amount.
	 *      Below, | here means the start of the terminal's left margin and the number
	 *      in braces {2} indicates which level was used for that line.
	 *      |usage: gmt gmtsimplify [<table>] -T<tolerance>[<unit>] [-Q...]<return> {0}
	 *      |  [-d[i|o]<nodata>] [-e[~]<pattern>] ...
	 * 1  : First level is used for listing of options:
	 *      |  -Rwest/east/south/north[+r]   {1}
	 *      |     Set the region of the plot, blah blah...
	 *      The text set with level = 1 that wraps will indent one more step
	 * -1 : A negative level means no indent once the line wraps. Thus, multi-line
	 *      wrapped text will all share the same left margin.
	 *      |  Furthermore, this options is deadly because ... {-1}
	 *      |  and that is why we skip this line.
	 * 2  : Used for a paragraph under an option that should be wrapped and indented
	 *      if it exceeds a line, e.g.
	 *      |     You may optionally do this or that or this or that, for instance, {2}
	 *      |       you could append a constant....
	 * -2 : Same as -1 but for level 2.  No further indent, just stays at that level.
	 * 3  : Used to indent separate modifiers under an option.  E.g. (for +a,b below):
	 *      |  -Q<value][+a][+b]...   {1}
	 *      |     Mess up the plot by random lines ... blah blah.  Available modifiers: {-1}
	 *      |       +a Draw random lines from a Poisson distribution so that we get {3}
	 *      |          a quirky plot.
	 *      |       +b Add 7 to all answers just for fun.  {3}
	 * -3:  Same as -1 but for level 3.  No further indent, just stay at that level.
	 * etc, etc.
	 */
	struct GMTAPI_CTRL *API = NULL;
	unsigned int k = 0;
	FILE *err = stderr;
	va_list args;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (format == NULL) return GMT_PTR_IS_NULL;	/* Format cannot be NULL */
	API = gmtapi_get_api_ptr (V_API);	/* Get the typecast structure pointer to API */
	API->message[0] = '\0';	/* Start fresh */

	va_start (args, format);
	vsnprintf (API->message, GMT_MSGSIZ, format, args);
	va_end (args);
	assert (strlen (API->message) < GMT_MSGSIZ);
	if (API->GMT) err = API->GMT->session.std[GMT_ERR];
	if (API->message[0] == '\n') {
		API->print_func (err, "\n");	/* Blank line to separate paragraphs */
		k = 1;
	}
	gmtapi_wrap_the_line (API, level, err, &(API->message)[k]);
	return_error (V_API, GMT_NOERROR);
}

#ifdef FORTRAN_API
int GMT_Usage_ (unsigned int *level, const char *format, int len) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Usage (GMT_FORTRAN, *level, format));
}
#endif


char * GMT_Error_Message (void *V_API) {
	struct GMTAPI_CTRL *API = NULL;
	if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION);
	API = gmtapi_get_api_ptr (V_API);	/* Get the typecast structure pointer to API */
	return (API->error_msg);
}

#ifdef FORTRAN_API
char * GMT_Error_Message_ () {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Error_Message (GMT_FORTRAN));
}
#endif

/*! . */
int GMT_Handle_Messages (void *V_API, unsigned int mode, unsigned int method, void *dest) {
	/* Change where verbosity messages go */
	struct GMTAPI_CTRL *API = NULL;
	FILE *fp = NULL;
	int *fd = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	API = gmtapi_get_api_ptr (V_API);
	switch (mode) {
		case GMT_LOG_OFF:	/* Close log file and reset to stderr */
			if (API->log_level == GMT_LOG_SET)
				fclose (API->GMT->session.std[GMT_ERR]);
			API->GMT->session.std[GMT_ERR] = stderr;
			break;
		case GMT_LOG_ONCE:	/* Redirect message just until end of next module */
		case GMT_LOG_SET:	/* Redirect message until end of session (or changed) */
			if (API->log_level)	/* Cannot turn on when already on */
				return_error (V_API, GMT_LOGGING_ALREADY_ACTIVE);
			switch (method) {
				case GMT_IS_FILE:
					if ((fp = fopen (dest, "w")) == NULL) {
						GMT_Report (API, GMT_MSG_ERROR, "Unable to open error log file %s\n", dest);
						return_error (API, GMT_ERROR_ON_FOPEN);
					}
					break;
				case GMT_IS_STREAM:
					fp = dest;
					break;
				case GMT_IS_FDESC:
					fd = (int *)dest;	/* Extract the file handle integer */
					if ((fp = fdopen (*fd, "w")) == NULL) {	/* Reopen handle as stream */
						GMT_Report (API, GMT_MSG_ERROR, "Unable to open file descriptor %d for error log\n", *fd);
						return_error (API, GMT_ERROR_ON_FDOPEN);
					}
					break;
				default:
					return_error (API, GMT_NOT_A_VALID_METHOD);
					break;
			}
			API->GMT->session.std[GMT_ERR] = fp;	/* Set the error fp pointer */
			API->log_level = mode;
			break;
		default:
			return_error (API, GMT_NOT_A_VALID_LOGMODE);
			break;
	}
	return (GMT_NOERROR);
}

#ifdef FORTRAN_API
int GMT_Handle_Messages_ (unsigned int *mode, unsigned int *method, void *dest) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Handle_Messages (GMT_FORTRAN, *mode, *method, dest));
}
#endif

/*! . */
int GMT_Get_Values (void *V_API, const char *arg, double par[], int maxpar) {
	/* Parse any number of comma, space, tab, semi-colon or slash-separated values.
	 * The array par must have enough space to hold a maximum of maxpar items.
	 * Function returns the number of items, or GMT_NOTSET if there was an error.
	 * When there are more than maxpar items, only the first maxpar are stored, and
	 * the value of maxpar is returned.
	 * We can handle dimension units (c|i|p), distance units (d|m|s|e|f|k|M|n|u),
	 * geographic coordinates, absolute dateTtime strings, and regular floats.
	 *
	 * Dimensions are returned in the current length unit [inch or cm].
	 * Distances are returned in meters.
	 * Arc distances are returned in degrees.
	 * Geographic dd:mm:ss[W|E|S|N] coordinates are returned in decimal degrees.
	 * DateTtime moments are returned in time in chosen units [sec] since chosen epoch [1970] */

	bool f_active[2], f_is_geo[2], f_is_cart[2];
	unsigned int pos = 0, mode, col_type_save[2][2];
	int npar = 0;
	size_t len;
	char p[GMT_BUFSIZ] = {""}, unit, col_set_save[2][2], f_string[GMT_LEN64];

	double value;
	struct GMTAPI_CTRL *API = NULL;
	struct GMT_CTRL *GMT = NULL;
	static const char separators[] = " \t,;/";

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (arg == NULL || arg[0] == '\0') return_value (V_API, GMT_NO_PARAMETERS, GMT_NOTSET);
	API = gmtapi_get_api_ptr (V_API);
	GMT = API->GMT;
	API->error = GMT_NOERROR;

	/* Because gmt_init_distaz and possibly gmt_scanf_arg may decide to change the GMT col_type
	 * for x and y we make a copy here and reset when done. */
	gmt_M_memcpy (col_type_save[GMT_IN],  GMT->current.io.col_type[GMT_IN],   2, unsigned int);
	gmt_M_memcpy (col_type_save[GMT_OUT], GMT->current.io.col_type[GMT_OUT],  2, unsigned int);
	gmt_M_memcpy (col_set_save[GMT_IN],   GMT->current.io.col_set[GMT_IN],    2, char);
	gmt_M_memcpy (col_set_save[GMT_OUT],  GMT->current.io.col_set[GMT_OUT],   2, char);
	/* Same goes for -f since gmt_set_geographic may be called */
	gmt_M_memcpy (f_active,  GMT->common.f.active,  2, bool);
	gmt_M_memcpy (f_is_geo,  GMT->common.f.is_geo,  2, bool);
	gmt_M_memcpy (f_is_cart, GMT->common.f.is_cart, 2, bool);
	gmt_M_memcpy (f_string,  GMT->common.f.string,  GMT_LEN64, char);

	while (gmt_strtok (arg, separators, &pos, p)) {	/* Loop over input arguments */
		if ((len = strlen (p)) == 0) continue;
		if (npar >= maxpar) {	/* Bail out when already maxpar values are stored */
			gmtlib_report_error (API, GMT_DIM_TOO_LARGE);
			break;
		}
		len--;	/* Position of last char, possibly a unit */
		if (strchr (GMT_DIM_UNITS, p[len]))	/* Dimension unit (c|i|p), return distance in GMT default length unit [inch or cm] */
			value = gmt_convert_units (GMT, p, GMT->current.setting.proj_length_unit, GMT->current.setting.proj_length_unit);
		else if (strchr (GMT_LEN_UNITS, p[len])) {	/* Distance units, return as meters [or degrees if arc] */
			mode = gmt_get_distance (GMT, p, &value, &unit);
			if (gmt_init_distaz (GMT, unit, mode, GMT_MAP_DIST) == GMT_NOT_A_VALID_TYPE) return_value (V_API, GMT_NOT_A_VALID_TYPE, GMT_NOTSET);
			value /= GMT->current.map.dist[GMT_MAP_DIST].scale;	/* Convert to default unit */
		}
		else	/* Perhaps coordinates or floats */
			(void) gmt_scanf_arg (GMT, p, GMT_IS_UNKNOWN, false, &value);
		par[npar++] = value;
	}
	/* Reset col_types to what they were before the parsing */
	gmt_M_memcpy (GMT->current.io.col_type[GMT_IN],  col_type_save[GMT_IN],  2, unsigned int);
	gmt_M_memcpy (GMT->current.io.col_type[GMT_OUT], col_type_save[GMT_OUT], 2, unsigned int);
	gmt_M_memcpy (GMT->current.io.col_set[GMT_IN],   col_set_save[GMT_IN],   2, char);
	gmt_M_memcpy (GMT->current.io.col_set[GMT_OUT],  col_set_save[GMT_OUT],  2, char);
	/* Reset -f settings to what they were before the parsing */
	gmt_M_memcpy (GMT->common.f.active,  f_active,  2, bool);
	gmt_M_memcpy (GMT->common.f.is_geo,  f_is_geo,  2, bool);
	gmt_M_memcpy (GMT->common.f.is_cart, f_is_cart, 2, bool);
	gmt_M_memcpy (GMT->common.f.string,  f_string,  GMT_LEN64, char);

	return (npar);
}

#ifdef FORTRAN_API
int GMT_Get_Values_ (char *arg, double par[], int len) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Get_Values (GMT_FORTRAN, arg, par, len));
}
#endif

/* Here lies the very basic F77 support for grid read and write only. It is assumed that no grid padding is required */

#define F_STRNCPY(dst,src,ldst,lsrc) { int l = MIN(ldst-1, lsrc); strncpy (dst, src, l); dst[l] = '\0'; }

int gmt_f77_readgrdinfo_ (unsigned int dim[], double limit[], double inc[], char *title, char *remark, const char *name, int ltitle, int lremark, int lname) {
	/* Note: When returning, dim[2] holds the registration (0 = gridline, 1 = pixel).
	 * limit[4-5] holds zmin/zmax. limit must thus at least have a length of 6.
	 */
	const char *argv = "GMT_F77_readgrdinfo";
	char *file = NULL;
	struct GMT_GRID_HEADER *header;
	struct GMTAPI_CTRL *API = NULL;	/* The API pointer assigned below */

	if (name == NULL) {
		GMT_Report (API, GMT_MSG_ERROR, "No filename given to GMT_F77_readgrdinfo\n");
		return GMT_ARG_IS_NULL;
	}
	if ((API = GMT_Create_Session (argv, 0U, 0U, NULL)) == NULL) return GMT_MEMORY_ERROR;
	file = strndup (name, lname);

	/* Read the grid header */
	header = gmt_get_header (API->GMT);
	if (gmtlib_read_grd_info (API->GMT, file, header)) {
		GMT_Report (API, GMT_MSG_ERROR, "Failure while opening file %s\n", file);
		gmt_M_str_free (file);
		GMT_Destroy_Session (API);
		return GMT_GRID_READ_ERROR;
	}
	gmt_M_str_free (file);

	/* Assign variables from header structure items */
	dim[GMT_X] = header->n_columns;	dim[GMT_Y] = header->n_rows;
	gmt_M_memcpy (limit, header->wesn, 4U, double);
	gmt_M_memcpy (inc, header->inc, 2U, double);
	limit[ZLO] = header->z_min;
	limit[ZHI] = header->z_max;
	dim[GMT_Z] = header->registration;
	if (title) F_STRNCPY (title, header->title, ltitle, GMT_GRID_TITLE_LEN80);
	if (remark) F_STRNCPY (remark, header->remark, lremark, GMT_GRID_REMARK_LEN160);
	gmt_free_header (API->GMT, &header);

	if (GMT_Destroy_Session (API) != GMT_NOERROR) return GMT_RUNTIME_ERROR;
	return GMT_NOERROR;
}

int gmt_f77_readgrd_ (gmt_grdfloat *array, unsigned int dim[], double limit[], double inc[], char *title, char *remark, const char *name, int ltitle, int lremark, int lname) {
	/* Note: When called, dim[2] is 1 we allocate the array, otherwise we assume it has enough space
	 * Also, if dim[3] == 1 then we transpose the array before writing.
	 * When returning, dim[2] holds the registration (0 = gridline, 1 = pixel).
	 * limit[4-5] holds zmin/zmax. limit must thus at least have a length of 6.
	 */
	double no_wesn[4] = {0.0, 0.0, 0.0, 0.0};
	const char *argv = "GMT_F77_readgrd";
	char *file = NULL;
	struct GMT_GRID_HEADER *header = NULL;
	struct GMTAPI_CTRL *API = NULL;	/* The API pointer assigned below */

	if (name == NULL) {
		GMT_Report (API, GMT_MSG_ERROR, "No filename given to GMT_F77_readgrd\n");
		return GMT_ARG_IS_NULL;
	}
	if ((API = GMT_Create_Session (argv, 0U, 0U, NULL)) == NULL) return GMT_MEMORY_ERROR;
	file = strndup (name, lname);

	header = gmt_get_header (API->GMT);
	/* Read the grid header */
	gmt_grd_init (API->GMT, header, NULL, false);
	if (gmtlib_read_grd_info (API->GMT, file, header)) {
		GMT_Report (API, GMT_MSG_ERROR, "Failure while opening file %s\n", file);
		gmt_M_str_free (file);
		gmt_free_header (API->GMT, &header);
		GMT_Destroy_Session (API);
		return GMT_GRID_READ_ERROR;
	}

	/* Read the grid, possibly after first allocating array space */
	if (dim[GMT_Z] == 1 && (array = gmt_M_memory (API->GMT, NULL, header->size, gmt_grdfloat)) == NULL) return GMT_MEMORY_ERROR;
	if (gmtlib_read_grd (API->GMT, file, header, array, no_wesn, GMT_no_pad, 0)) {
		GMT_Report (API, GMT_MSG_ERROR, "Failure while reading file %s\n", file);
		gmt_M_str_free (file);
		gmt_free_header (API->GMT, &header);
		GMT_Destroy_Session (API);
		return GMT_GRID_READ_ERROR;
	}
	gmt_M_str_free (file);

	if (dim[3] == 1) gmtlib_inplace_transpose (array, header->n_rows, header->n_columns);

	/* Assign variables from header structure items */
	dim[GMT_X] = header->n_columns;	dim[GMT_Y] = header->n_rows;
	gmt_M_memcpy (limit, header->wesn, 4U, double);
	gmt_M_memcpy (inc, header->inc, 2U, double);
	limit[ZLO] = header->z_min;
	limit[ZHI] = header->z_max;
	dim[GMT_Z] = header->registration;
	if (title) F_STRNCPY (title, header->title, ltitle, GMT_GRID_TITLE_LEN80);
	if (remark) F_STRNCPY (remark, header->remark, lremark, GMT_GRID_REMARK_LEN160);
	gmt_free_header (API->GMT, &header);

	if (GMT_Destroy_Session (API) != GMT_NOERROR) return GMT_RUNTIME_ERROR;
	return GMT_NOERROR;
}

int gmt_f77_writegrd_ (gmt_grdfloat *array, unsigned int dim[], double limit[], double inc[], const char *title, const char *remark, const char *name, int ltitle, int lremark, int lname) {
	/* Note: When called, dim[2] holds the registration (0 = gridline, 1 = pixel).
	 * Also, if dim[3] == 1 then we transpose the array before writing.  */
	const char *argv = "GMT_F77_writegrd";
	char *file = NULL;
	double no_wesn[4] = {0.0, 0.0, 0.0, 0.0};
	struct GMT_GRID_HEADER *header;
	struct GMTAPI_CTRL *API = NULL;	/* The API pointer assigned below */

	/* Initialize with default values */

	if (name == NULL) {
		GMT_Report (API, GMT_MSG_ERROR, "No filename given to GMT_F77_writegrd\n");
		return GMT_ARG_IS_NULL;
	}
	if ((API = GMT_Create_Session (argv, 0U, 0U, NULL)) == NULL) return GMT_MEMORY_ERROR;
	file = strndup (name, lname);

	header = gmt_get_header (API->GMT);
	gmt_grd_init (API->GMT, header, NULL, false);
	if (full_region (limit)) {	/* Here that means limit was not properly given */
		GMT_Report (API, GMT_MSG_ERROR, "Grid domain not specified for %s\n", file);
		gmt_M_str_free (file);
		GMT_Destroy_Session (API);
		return GMT_ARG_IS_NULL;
	}
	if (inc[GMT_X] == 0.0 || inc[GMT_Y] == 0.0) {	/* Here that means grid spacing was not properly given */
		GMT_Report (API, GMT_MSG_ERROR, "Grid spacing not specified for %s\n", file);
		gmt_M_str_free (file);
		GMT_Destroy_Session (API);
		return GMT_ARG_IS_NULL;
	}

	/* Set header parameters */

	gmt_M_memcpy (header->wesn, limit, 4U, double);
	gmt_M_memcpy (header->inc, inc, 2U, double);
	header->n_columns = dim[GMT_X];	header->n_rows = dim[GMT_Y];
	header->registration = dim[GMT_Z];
	gmt_set_grddim (API->GMT, header);
	if (title) F_STRNCPY (header->title, title, GMT_GRID_TITLE_LEN80, ltitle);
	if (remark) F_STRNCPY (header->remark, remark, GMT_GRID_REMARK_LEN160, lremark);

	if (dim[3] == 1) gmtlib_inplace_transpose (array, header->n_rows, header->n_columns);

	/* Write the file */

	if (gmtlib_write_grd (API->GMT, file, header, array, no_wesn, GMT_no_pad, 0)) {
		GMT_Report (API, GMT_MSG_ERROR, "Failure while writing file %s\n", file);
		gmt_M_str_free (file);
		GMT_Destroy_Session (API);
		return GMT_GRID_WRITE_ERROR;
	}
	gmt_M_str_free (file);
	gmt_free_header (API->GMT, &header);

	if (GMT_Destroy_Session (API) != GMT_NOERROR) return GMT_MEMORY_ERROR;
	return GMT_NOERROR;
}

/* wrappers for several FORTRAN compilers */
#define F77_ARG1 unsigned int dim[], double limit[], double inc[], char *title, char *remark, const char *name, int ltitle, int lremark, int lname
#define F77_ARG2 dim, limit, inc, title, remark, name, ltitle, lremark, lname
int gmt_f77_readgrdinfo__(F77_ARG1) { return gmt_f77_readgrdinfo_ (F77_ARG2); }
int gmt_f77_readgrdinfo  (F77_ARG1) { return gmt_f77_readgrdinfo_ (F77_ARG2); }
int GMT_F77_READGRDINFO_ (F77_ARG1) { return gmt_f77_readgrdinfo_ (F77_ARG2); }
int GMT_F77_READGRDINFO  (F77_ARG1) { return gmt_f77_readgrdinfo_ (F77_ARG2); }
#undef  F77_ARG1
#undef  F77_ARG2

#define F77_ARG1 gmt_grdfloat *array, unsigned int dim[], double limit[], double inc[], char *title, char *remark, const char *name, int ltitle, int lremark, int lname
#define F77_ARG2 array, dim, limit, inc, title, remark, name, ltitle, lremark, lname
int gmt_f77_readgrd__ (F77_ARG1) { return gmt_f77_readgrd_ (F77_ARG2); }
int gmt_f77_readgrd   (F77_ARG1) { return gmt_f77_readgrd_ (F77_ARG2); }
int GMT_F77_READGRD_  (F77_ARG1) { return gmt_f77_readgrd_ (F77_ARG2); }
int GMT_F77_READGRD   (F77_ARG1) { return gmt_f77_readgrd_ (F77_ARG2); }
#undef  F77_ARG1

#define F77_ARG1 gmt_grdfloat *array, unsigned int dim[], double limit[], double inc[], const char *title, const char *remark, const char *name, int ltitle, int lremark, int lname
int gmt_f77_writegrd__ (F77_ARG1) { return gmt_f77_writegrd_ (F77_ARG2); }
int gmt_f77_writegrd   (F77_ARG1) { return gmt_f77_writegrd_ (F77_ARG2); }
int GMT_F77_WRITEGRD_  (F77_ARG1) { return gmt_f77_writegrd_ (F77_ARG2); }
int GMT_F77_WRITEGRD   (F77_ARG1) { return gmt_f77_writegrd_ (F77_ARG2); }
#undef  F77_ARG1
#undef  F77_ARG2

char *GMT_Duplicate_String (void *API, const char* string) {
	/* Duplicate a string. The interest of this function is to make the memory allocation
	   inside the GMT lib so that GMT_Destroy_Data we can free it without any concerns of
	   Windows DLL hell */
	gmt_M_unused(API);
	return strdup (string);
}


/* Sub-functions to perform specific conversions */

#define do_tbl_header(flag) (flag == 0 || flag == 2)
#define do_seg_header(flag) (flag == 0 || flag == 1)

/* GMT_DATASET to GMT_* : */

GMT_LOCAL void * gmtapi_dataset2dataset (struct GMTAPI_CTRL *API, struct GMT_DATASET *In, struct GMT_DATASET *Out, unsigned int header, unsigned int mode) {
	/* Convert a dataset to another dataset using current formatting and column type information.
	 * If Out is not NULL then we assume it has exact same dimension as the dataset, but no headers/records allocated.
	 * header controls what we do with headers.
	 * If mode == GMT_WRITE_TABLE_SEGMENT then we combine all segments into a SINGLE segment in ONE table
	 * If mode == GMT_WRITE_TABLE then we collect all segments into ONE table.
	 * If mode == GMT_WRITE_SEGMENT then we combine segments into ONE segment per table.
	 */
	unsigned int hdr;
	uint64_t tbl, seg, row, col, n_rows, tbl_out = 0, row_out = 0, seg_out = 0;
	bool s_alloc, t_alloc, alloc, was;
	struct GMT_CTRL *GMT = API->GMT;
	struct GMT_DATATABLE *Tin = NULL;
	struct GMT_DATATABLE *Tout = NULL;
	struct GMT_DATASEGMENT *Sin = NULL;
	struct GMT_DATASEGMENT *Sout = NULL;
	struct GMT_DATATABLE_HIDDEN *TH = NULL;
	s_alloc = t_alloc = alloc = (Out == NULL);
	if (alloc) {	/* Must allocate output dataset */
		if ((Out = gmt_get_dataset (GMT)) == NULL) return NULL;
		Out->n_tables = (mode == GMT_WRITE_TABLE || mode == GMT_WRITE_TABLE_SEGMENT) ? 1 : In->n_tables;
		if ((Out->table = gmt_M_memory (GMT, NULL, Out->n_tables, struct GMT_DATATABLE *)) == NULL) return NULL;
	}
	was = GMT->current.setting.io_header[GMT_OUT];
	GMT->current.setting.io_header[GMT_OUT] = do_tbl_header (header);
	Out->n_segments = (mode == GMT_WRITE_TABLE_SEGMENT) ? 1 : ((mode == GMT_WRITE_SEGMENT) ? In->n_tables : In->n_segments);
	Out->n_records  = In->n_records;
	Out->n_columns  = In->n_columns;
	for (tbl = 0; tbl < In->n_tables; tbl++) {
		if (mode == GMT_WRITE_SEGMENT) row_out = 0;	/* Reset row output counter on a per table basis */
		else if (mode == 0) seg_out = 0;	/* Reset segment output counter on a per table basis */
		if (alloc && (mode == 0 || mode == GMT_WRITE_SEGMENT)) s_alloc = true;	/* Need to allocate at least one segment per table */
		Tin = In->table[tbl];	/* Shorthand to current input data table */
		if (t_alloc) {
			Out->table[tbl_out] = Tout = gmt_get_table (GMT);
			TH = gmt_get_DT_hidden (Tout);
			Tout->n_segments = TH->n_alloc = (mode == GMT_WRITE_TABLE_SEGMENT || mode == GMT_WRITE_SEGMENT) ? 1 : ((mode == GMT_WRITE_TABLE) ? In->n_segments : Tin->n_segments);	/* Number of segments in this table */
			Tout->n_records  = (mode == GMT_WRITE_TABLE || mode == GMT_WRITE_TABLE_SEGMENT) ? In->n_records : Tin->n_records;	/* Number of data records int this table */
			Tout->n_columns = In->n_columns;
		}
		else
			Tout = Out->table[tbl_out];
		if (t_alloc) {
			if (Tin->n_headers && do_tbl_header(header)) {	/* Allocate and duplicate headers */
				Tout->n_headers = Tin->n_headers;	/* Same number of header records as input table */
				if (alloc && (Tout->header = gmt_M_memory (GMT, NULL, Tout->n_headers, char *)) == NULL) return NULL;
				for (hdr = 0; hdr < Tout->n_headers; hdr++) Tout->header[hdr] = strdup (Tin->header[hdr]);
			}
			if ((Tout->segment = gmt_M_memory (GMT, NULL, Tout->n_segments, struct GMT_DATASEGMENT *)) == NULL) return NULL;
		}
		for (seg = 0; seg < Tin->n_segments; seg++) {	/* For each input table segment */
			if (mode == 0 || mode == GMT_WRITE_TABLE) row_out = 0;	/* Reset row output counter on a per segment basis */
			Sin = Tin->segment[seg];	/* Shorthand to current data segment */
			if (s_alloc) {	/* Allocate another segment */
				unsigned int smode = (Sin->text) ? GMT_WITH_STRINGS : GMT_NO_STRINGS;
				n_rows = (mode == GMT_WRITE_TABLE_SEGMENT) ? In->n_records : ((mode == GMT_WRITE_SEGMENT) ? Tin->n_records : Sin->n_rows);
				Tout->segment[seg_out] = GMT_Alloc_Segment (API, smode, n_rows, In->n_columns, NULL, NULL);
				Sout = Tout->segment[seg_out];	/* Shorthand to current text segment */
				if (Sin->header && do_seg_header(header)) Sout->header = strdup (Sin->header);
			}
			else
				Sout = Tout->segment[seg_out];
			for (row = 0; row < Sin->n_rows; row++, row_out++) {	/* Copy each row to (new) segment */
				for (col = 0; col < Sin->n_columns; col++)
					Sout->data[col][row_out] = Sin->data[col][row];
			}
			if (mode == GMT_WRITE_SEGMENT || mode == GMT_WRITE_TABLE_SEGMENT) s_alloc = false;	/* Only allocate this single segment, at least for this table */
			if (mode == 0 || mode == GMT_WRITE_TABLE) seg_out++;	/* More than one segment on output */
		}
		if (mode == GMT_WRITE_TABLE || mode == GMT_WRITE_TABLE_SEGMENT) t_alloc = false;	/* Only allocate this single table */
		if (mode == 0 || mode == GMT_WRITE_SEGMENT) tbl_out++;	/* More than one segment on output */
	}
	GMT->current.setting.io_header[GMT_OUT] = was;
	return Out;
}

GMT_LOCAL void *gmtapi_dataset2matrix (struct GMTAPI_CTRL *API, struct GMT_DATASET *In, struct GMT_MATRIX *Out, unsigned int header, unsigned int mode) {
	/* Convert a dataset to a matrix.
	 * If Out is not NULL then we assume it has enough rows and columns to hold the dataset records.
	 * Header controls if segment headers are written as NaN recs
	 * If mode > 0 then it is assumed to hold GMT_TYPE-1, else we assume the GMT default setting.
	 * If there are more than one segment we will insert NaN-records between segments.
	 */
	uint64_t tbl, seg, row, row_out, col, ij;
	bool alloc = (Out == NULL), add_NaN_record = (In->n_segments > 1 && do_seg_header(header));
	struct GMT_CTRL *GMT = API->GMT;
	struct GMT_DATATABLE *D = NULL;
	struct GMT_DATASEGMENT *SD = NULL;
	GMT_putfunction api_put_val = NULL;
	p_func_uint64_t GMT_2D_to_index = NULL;

	if (alloc) {	/* Must allocate the output matrix */
		struct GMT_MATRIX_HIDDEN *MH = NULL;
		if ((Out = gmtlib_create_matrix (GMT, 1U, 0)) == NULL) return (NULL);
		Out->n_rows = In->n_records + (add_NaN_record ? In->n_segments : 0);
		Out->n_columns = Out->dim = In->n_columns;
		Out->type = (mode) ? mode - 1 : API->GMT->current.setting.export_type;
		if (gmtlib_alloc_univector (GMT, &(Out->data), Out->type, Out->n_rows * Out->n_columns)) {
			gmt_M_free (GMT, Out);
			return (NULL);
		}
		MH = gmt_get_M_hidden (Out);
		MH->alloc_mode = GMT_ALLOC_INTERNALLY;
		MH->alloc_level = GMT->hidden.func_level;   /* Must be freed at this level. */
	}
	if ((api_put_val = gmtapi_select_put_function (API, Out->type)) == NULL) {
		gmt_M_free (GMT, Out);
		return (NULL);
	}
	if ((GMT_2D_to_index = gmtapi_get_2d_to_index (API, Out->shape, GMT_GRID_IS_REAL)) == NULL) {
		gmt_M_free (GMT, Out);
		return (NULL);
	}
	for (tbl = row_out = 0; tbl < In->n_tables; tbl++) {
		D = In->table[tbl];	/* Shorthand to current input data table */
		for (seg = 0; seg < D->n_segments; seg++) {
			SD = D->segment[seg];	/* Shorthand */
			if (add_NaN_record) {
				for (col = 0; col < SD->n_columns; col++) {
					ij = GMT_2D_to_index (row_out, col, Out->dim);
					api_put_val (&(Out->data), ij, GMT->session.d_NaN);
				}
				row_out++;	/* Due to the extra NaN-data header record we just wrote */
			}
			for (row = 0; row < SD->n_rows; row++, row_out++) {
				for (col = 0; col < SD->n_columns; col++) {
					ij = GMT_2D_to_index (row_out, col, Out->dim);
					api_put_val (&(Out->data), ij, SD->data[col][row]);
				}
			}
		}
	}
	return Out;
}

GMT_LOCAL void * gmtapi_dataset2vector (struct GMTAPI_CTRL *API, struct GMT_DATASET *In, struct GMT_VECTOR *Out, unsigned int header, unsigned int mode) {
	/* Convert a dataset to vectors.
	 * If Out is not NULL then we assume it has enough rows and columns to hold the dataset records.
	 * If mode > 0 then it is assumed to hold GMT_TYPE-1, else we assume the GMT default setting.
	 * If there are more than one segment we will insert NaN-records between segments.
	 */
	uint64_t tbl, seg, row, row_out, col;
	bool alloc = (Out == NULL), add_NaN_record = (In->n_segments > 1 && do_seg_header(header));
	struct GMT_CTRL *GMT = API->GMT;
	struct GMT_DATATABLE *D = NULL;
	struct GMT_DATASEGMENT *SD = NULL;
	GMT_putfunction api_put_val = NULL;
	if (alloc) {
		if ((Out = gmt_create_vector (GMT, In->n_columns, GMT_OUT)) == NULL) return NULL;
		Out->n_rows = In->n_records + (add_NaN_record ? In->n_segments : 0);
		for (col = 0; col < Out->n_columns; col++)	/* Set same export data type for all vectors */
			Out->type[col] = (mode) ? mode - 1 : API->GMT->current.setting.export_type;
		if ((API->error = gmtlib_alloc_vectors (GMT, Out, Out->n_rows)) != GMT_NOERROR) {
			gmt_M_free (GMT, Out);
			return (NULL);
		}
	}
	if ((api_put_val = gmtapi_select_put_function (API, Out->type[0])) == NULL) { /* Since all columns are of same type we get the pointer here */
		gmt_M_free (GMT, Out);
		return NULL;
	}
	for (tbl = row_out = 0; tbl < In->n_tables; tbl++) {
		D = In->table[tbl];	/* Shorthand to current input data table */
		for (seg = 0; seg < D->n_segments; seg++) {
			SD = D->segment[seg];	/* Shorthand */
			if (add_NaN_record) {
				for (col = 0; col < SD->n_columns; col++)
					api_put_val (&(Out->data[col]), row_out, GMT->session.d_NaN);
				row_out++;	/* Due to the extra NaN-data header record we just wrote */
			}
			for (row = 0; row < SD->n_rows; row++, row_out++) {
				for (col = 0; col < SD->n_columns; col++)
					api_put_val (&(Out->data[col]), row_out, SD->data[col][row]);
			}
		}
	}
	return Out;
}

/* GMT_MATRIX to GMT_* : */

GMT_LOCAL void * gmtapi_matrix2dataset (struct GMTAPI_CTRL *API, struct GMT_MATRIX *In, struct GMT_DATASET *Out, unsigned int header) {
	/* Convert a matrix to a dataset (one table with one segment).
	 * If Out is not NULL then we assume it has enough rows and columns to hold the dataset records.
	 * header controls what we do with headers.
	 */
	uint64_t row, col, ij;
	unsigned int mode = (In->text) ? GMT_WITH_STRINGS : 0;
	bool alloc = (Out == NULL);
	struct GMT_CTRL *GMT = API->GMT;
	struct GMT_DATASEGMENT *SD = NULL;
	GMT_getfunction api_get_val = NULL;
	p_func_uint64_t GMT_2D_to_index = NULL;
	if (header) GMT_Report (API, GMT_MSG_WARNING, "gmtapi_matrix2dataset: Header stripping not implemented yet - ignored!\n");
	if (alloc && (Out = gmtlib_create_dataset (GMT, 1U, 1U, In->n_rows, In->n_columns, GMT_IS_POINT, mode, true)) == NULL)
		return_null (API, GMT_MEMORY_ERROR);
	SD = Out->table[0]->segment[0];	/* Shorthand to only segment in the dataset */
	if ((api_get_val = gmtapi_select_get_function (API, In->type)) == NULL)
		return_null (API, GMT_NOT_A_VALID_TYPE);
	if ((GMT_2D_to_index = gmtapi_get_2d_to_index (API, In->shape, GMT_GRID_IS_REAL)) == NULL)
		return_null (API, GMT_WRONG_MATRIX_SHAPE);
	for (row = 0; row < In->n_rows; row++) {
		for (col = 0; col < In->n_columns; col++) {
			ij = GMT_2D_to_index (row, col, In->dim);	/* Index into the user data matrix depends on layout (M->shape) */
			api_get_val (&(In->data), ij, &(SD->data[col][row]));
		}
		if (mode) SD->text[row] = strdup (In->text[row]);
	}
	return Out;
}

GMT_LOCAL void *gmtapi_matrix2vector (struct GMTAPI_CTRL *API, struct GMT_MATRIX *In, struct GMT_VECTOR *Out, unsigned int header, unsigned int mode) {
	/* Convert a matrix to vectors.
	 * If Out is not NULL then we assume it has enough rows to hold the vector rows.
	 * header controls what we do with headers.
	 * If mode > 0 then it is assumed to hold GMT_TYPE-1, else we assume the GMT default setting.
	 */
	uint64_t row, col, ij;
	bool alloc = (Out == NULL);
	double value;
	struct GMT_CTRL *GMT = API->GMT;
	GMT_getfunction api_get_val_m = NULL;
	GMT_putfunction api_put_val_v = NULL;
	p_func_uint64_t GMT_2D_to_index = NULL;
	if (header) GMT_Report (API, GMT_MSG_WARNING, "gmtapi_matrix2vector: Header stripping not implemented yet - ignored!\n");
	if (alloc) {
		if ((Out = gmt_create_vector (GMT, In->n_columns, GMT_OUT)) == NULL)
			return_null (API, GMT_MEMORY_ERROR);
		Out->n_rows = In->n_rows;
		for (col = 0; col < Out->n_columns; col++)	/* Set same export data type for all vectors */
			Out->type[col] = (mode) ? mode - 1 : API->GMT->current.setting.export_type;
		if ((API->error = gmtlib_alloc_vectors (GMT, Out, Out->n_rows)) != GMT_NOERROR) {
			gmt_M_free (GMT, Out);
			return_null (API, GMT_MEMORY_ERROR);
		}
	}

	if ((api_get_val_m = gmtapi_select_get_function (API, In->type)) == NULL) {
		if (alloc) gmt_M_free (GMT, Out);
		return_null (API, GMT_NOT_A_VALID_TYPE);
	}
	if ((api_put_val_v = gmtapi_select_put_function (API, GMT->current.setting.export_type)) == NULL) {	/* Since all columns are of same type we get the pointer here */
		if (alloc) gmt_M_free (GMT, Out);
		return_null (API, GMT_NOT_A_VALID_TYPE);
	}
	if ((GMT_2D_to_index = gmtapi_get_2d_to_index (API, In->shape, GMT_GRID_IS_REAL)) == NULL) {
		if (alloc) gmt_M_free (GMT, Out);
		return_null (API, GMT_WRONG_MATRIX_SHAPE);
	}
	for (row = 0; row < In->n_rows; row++) {
		for (col = 0; col < In->n_columns; col++) {
			ij = GMT_2D_to_index (row, col, In->dim);	/* Index into the user data matrix depends on layout (M->shape) */
			api_get_val_m (&(In->data), ij, &value);
			api_put_val_v (&(Out->data[col]), row, value);
		}
	}
	return Out;
}

/* GMT_VECTOR to GMT_* : */

GMT_LOCAL void * gmtapi_vector2dataset (struct GMTAPI_CTRL *API, struct GMT_VECTOR *In, struct GMT_DATASET *Out, unsigned int header) {
	/* Convert a vector to a dataset (one table with one segment).
	 * header controls what we do with headers.
	 * If Out is not NULL then we assume it has enough rows and columns to hold the dataset records.
	 */
	uint64_t row, col;
	unsigned int mode = (In->text) ? GMT_WITH_STRINGS : 0;
	bool alloc = (Out == NULL);
	struct GMT_CTRL *GMT = API->GMT;
	struct GMT_DATASEGMENT *SD = NULL;
	GMT_getfunction api_get_val;
	if (header) GMT_Report (API, GMT_MSG_WARNING, "gmtapi_vector2dataset: Header stripping not implemented yet - ignored!\n");
	if (alloc && (Out = gmtlib_create_dataset (GMT, 1U, 1U, In->n_rows, In->n_columns, GMT_IS_POINT, mode, true)) == NULL)
		return_null (API, GMT_MEMORY_ERROR);
	SD = Out->table[0]->segment[0];	/* Shorthand to only segment in the dataset */
	for (col = 0; col < In->n_columns; col++) {
		if ((api_get_val = gmtapi_select_get_function (API, In->type[col])) == NULL)
			return_null (API, GMT_NOT_A_VALID_TYPE);
		for (row = 0; row < In->n_rows; row++)
			api_get_val (&(In->data[col]), row, &(SD->data[col][row]));
	}
	if (mode) {	/* Duplicate the strings */
		for (row = 0; row < In->n_rows; row++)
			SD->text[row] = strdup (In->text[row]);	/* Duplicate the strings */
	}
	return Out;
}

GMT_LOCAL void * gmtapi_vector2matrix (struct GMTAPI_CTRL *API, struct GMT_VECTOR *In, struct GMT_MATRIX *Out, unsigned int header, unsigned int mode) {
	/* Convert a vector to a matrix.
	 * If Out is not NULL then we assume it has enough rows to hold the rows.
	 * header controls what we do with headers.
	 * If mode > 0 then it is assumed to hold GMT_TYPE-1, else we assume the GMT default setting.
	 */
	uint64_t row, col, ij;
	bool alloc = (Out == NULL);
	double value;
	struct GMT_CTRL *GMT = API->GMT;
	GMT_getfunction api_get_val = NULL;
	GMT_putfunction api_put_val = NULL;
	p_func_uint64_t GMT_2D_to_index = NULL;
	if (header) GMT_Report (API, GMT_MSG_WARNING, "gmtapi_vector2matrix: Header stripping not implemented yet - ignored!\n");
	if (alloc) {
		struct GMT_MATRIX_HIDDEN *MH = NULL;
		Out = gmtlib_create_matrix (GMT, 1U, 0);
		Out->n_columns = In->n_columns;
		Out->n_rows = In->n_rows;
		Out->type = (mode) ? mode - 1 : API->GMT->current.setting.export_type;
		if (gmtlib_alloc_univector (GMT, &(Out->data), Out->type, Out->n_rows * Out->n_columns)) {
			gmt_M_free (GMT, Out);
			return (NULL);
		}
		MH = gmt_get_M_hidden (Out);
		MH->alloc_mode = GMT_ALLOC_INTERNALLY;
		MH->alloc_level = GMT->hidden.func_level;   /* Must be freed at this level. */
	}

	if ((GMT_2D_to_index = gmtapi_get_2d_to_index (API, Out->shape, GMT_GRID_IS_REAL)) == NULL) {
		gmt_M_free (GMT, Out);
		return (NULL);
	}
	if ((api_put_val = gmtapi_select_put_function (API, Out->type)) == NULL) {	/* Since all columns are of same type we get the pointer here */
		gmt_M_free (GMT, Out);
		return (NULL);
	}
	for (col = 0; col < In->n_columns; col++) {
		if ((api_get_val = gmtapi_select_get_function (API, In->type[col])) == NULL) {
			gmt_M_free (GMT, Out);
			return (NULL);
		}
		for (row = 0; row < In->n_rows; row++) {
			api_get_val (&(In->data[col]), row, &value);
			ij = GMT_2D_to_index (row, col, Out->dim);
			api_put_val (&(Out->data), ij, value);
		}
	}
	return Out;
}

/* New function to convert between objects */

#define GMT_HEADER_MODE	0
#define GMT_TYPE_MODE	1
#define GMT_FORMAT_MODE	1	/* Same as GMT_TYPE_MODE [not a typo] */

void *GMT_Convert_Data (void *V_API, void *In, unsigned int family_in, void *Out, unsigned int family_out, unsigned int flag[]) {
	/* Convert between valid pairs of objects,  If Out == NULL then we allocate an output object,
	 * otherwise we assume we are given adequate space already.  This is most likely restricted to a GMT_MATRIX.
	 * flag is an array with two unsigned integers controlling various aspects of the conversion:
	 * flag[0]: Controls how headers are handled on output:
	 * 	 0 : All headers are passed on as is.  For Matrix/Vector all table headers are always ignored but
	 * 	     segment headers will be encoded as NaN records
	 * 	 1 : Headers are not copied, but segment headers are preserved
	 * 	 2 : Headers are preserved, but segment headers are initialized to blank
	 * 	 3 : All headers headers are eliminated
	 *	     The GMT Default settings in effect will control any output to files later.
	 * [Note if that happens it is not considered an error, so API->error is GMT_NOERROR].
	 * flag[1]: Controls the data type to use for MATRIX and VECTOR.
	 * 	0: Use the GMT default data type [GMT_EXPORT_TYPE]
	 * 	>0: Assumed to contain datatype + 1 (e.g., GMT_FLOAT+1, GMT_DOUBLE+1)
	 * If DATASET, this integer controls the restructuring of the set:
	 * 	GMT_WRITE_TABLE_SEGMENT: Combine all segments into a SINGLE segment in ONE table
	 * 	GMT_WRITE_TABLE: Collect all segments into ONE table.
	 * 	GMT_WRITE_SEGMENT: Combine segments into ONE segment per table.
	 * 	0: Retain initial layout.
	 * If GRID then flags are not yet used
	 *
	 * The following conversions are valid; the brackets indicate any side-effects or limitations]
	 *
	 * DATASET -> MATRIX,  VECTOR
	 * MATRIX  -> DATASET, VECTOR
	 * VECTOR  -> DATASET, MATRIX
	 * GRID    -> MATRIX
	 */
	int object_ID, item;
	void *X = NULL;
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;

	switch (family_in) {
		case GMT_IS_DATASET:
			switch (family_out) {
				case GMT_IS_DATASET:
					X = gmtapi_dataset2dataset (API, In, Out, flag[GMT_HEADER_MODE], flag[GMT_TYPE_MODE]);
					break;
				case GMT_IS_MATRIX:
					X = gmtapi_dataset2matrix(API, In, Out, flag[GMT_HEADER_MODE], flag[GMT_TYPE_MODE]);
					break;
				case GMT_IS_VECTOR:
					X = gmtapi_dataset2vector (API, In, Out, flag[GMT_HEADER_MODE], flag[GMT_TYPE_MODE]);
					break;
				default:
					API->error = GMT_NOT_A_VALID_FAMILY;
					break;
			}
			break;
		case GMT_IS_MATRIX:
			switch (family_out) {
				case GMT_IS_DATASET:
					X = gmtapi_matrix2dataset (API, In, Out, flag[GMT_HEADER_MODE]);
					break;
				case GMT_IS_VECTOR:
					X = gmtapi_matrix2vector (API, In, Out, flag[GMT_HEADER_MODE], flag[GMT_TYPE_MODE]);
					break;
				default:
					API->error = GMT_NOT_A_VALID_FAMILY;
					break;
			}
			break;
		case GMT_IS_VECTOR:
			switch (family_out) {
				case GMT_IS_DATASET:
					X = gmtapi_vector2dataset (API, In, Out, flag[GMT_HEADER_MODE]);
					break;
				case GMT_IS_MATRIX:
					X = gmtapi_vector2matrix (API, In, Out, flag[GMT_HEADER_MODE], flag[GMT_TYPE_MODE]);
					break;
				default:
					API->error = GMT_NOT_A_VALID_FAMILY;
					break;
			}
			break;
		case GMT_IS_GRID:
			switch (family_out) {
				case GMT_IS_MATRIX:
					X = gmtapi_grid2matrix (API, In, Out);
					break;
				default:
					API->error = GMT_NOT_A_VALID_FAMILY;
					break;
			}
			break;
		default:
			API->error = GMT_NOT_A_VALID_FAMILY;
			break;
	}
	if (X == NULL)
		return_null (API, GMT_PTR_IS_NULL);
	if (API->error)
		return_null (API, API->error);
	if ((object_ID = GMT_Register_IO (API, family_out, GMT_IS_REFERENCE, GMT_IS_POINT, GMT_IN, NULL, X)) == GMT_NOTSET)
		return_null (API, API->error);	/* Failure to register */
	if ((item = gmtlib_validate_id (API, family_out, object_ID, GMT_IN, GMT_NOTSET)) == GMT_NOTSET)
		return_null (API, API->error);
	API->object[item]->resource = X;	/* Retain pointer to the allocated data so we use garbage collection later */
#ifdef DEBUG
	gmtapi_list_objects (API, "GMT_Convert_Data");
#endif
	return (X);
}

#ifdef FORTRAN_API
void * GMT_Convert_Data_ (void *In, unsigned int *family_in, void *Out, unsigned int *family_out, unsigned int flag[]) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Convert_Data (GMT_FORTRAN, In, *family_in, Out, *family_out, flag));
}
#endif

void * GMT_Alloc_Segment (void *V_API, unsigned int mode, uint64_t n_rows, uint64_t n_columns, char *header, void *S) {
	/* Allocate or reallocate a GMT_DATASEGMENT.
	 * The n_columns may be 0 if no numerical data in the segment.
	 * header, if not NULL or blank, sets the segment header.
	 * if mode == GMT_WITH_STRINGS then we also allocate the empty array of string pointers */
	struct GMT_DATASEGMENT *Snew = NULL;
	struct GMTAPI_CTRL *API = NULL;
	bool first = true, alloc;
	char *H = header;

	if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;
	if ((Snew = S) != NULL)	/* Existing segment given */
		first = false;
	else if ((Snew = gmt_get_segment (API->GMT, n_columns)) == NULL) /* Something went wrong */
		return_null (V_API, GMT_MEMORY_ERROR);
		/* Only reallocate if desired n_rows differ from current n_rows */
	alloc = (first || (n_rows && n_rows != Snew->n_rows));	/* Alloc first time or reallocate later if necessary */
	if (alloc && gmt_alloc_segment (API->GMT, Snew, n_rows, n_columns, mode, first))  {	/* Something went wrong */
		if (first) gmt_M_free (API->GMT, Snew);
		return_null (V_API, GMT_MEMORY_ERROR);
	}
	if (H && H[0] == API->GMT->current.setting.io_seg_marker[GMT_IN]) {	/* User gave a record with segment marker in it */
		H++;	/* Skip the segment marker */
		while (*H && (*H == ' ' || *H == '\t')) H++;	/* Then skip any leading whitespace */
	}
	if (H && strlen (H)) {	/* Gave a header string to (re)place in the segment */
		if (Snew->header) gmt_M_str_free (Snew->header);
		Snew->header = strdup (H);
	}
	return Snew;
}

#ifdef FORTRAN_API
void * GMT_Alloc_Segment_ (unsigned int *family, uint64_t *n_rows, uint64_t *n_columns, char *header, void *S, int len) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Alloc_Segment (GMT_FORTRAN, *family, *n_rows, *n_columns, header, S));
}
#endif

int GMT_Set_Columns (void *V_API, unsigned int direction, unsigned int n_cols, unsigned int mode) {
	/* Specify how many input or output columns to use for record-by-record output, if fixed */
	int error = 0;
	uint64_t n_in = 0;
	struct GMTAPI_CTRL *API = NULL;
	if (!(direction == GMT_IN || direction == GMT_OUT)) return_error (V_API, GMT_NOT_A_VALID_DIRECTION);
	if (direction == GMT_IN && !(mode == GMT_COL_FIX || mode == GMT_COL_VAR || mode == GMT_COL_FIX_NO_TEXT)) return_error (V_API, GMT_NOT_A_VALID_MODE);
	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;

	if (direction == GMT_OUT) {	/* Output */
		if ((mode == GMT_COL_ADD || mode == GMT_COL_SUB) && (n_in = gmt_get_cols (API->GMT, GMT_IN)) == 0) {	/* Get number of input columns */
			GMT_Report (API, GMT_MSG_ERROR, "GMT_Set_Columns: Premature call - number of input columns not known yet\n");
			return_error (API, GMT_N_COLS_NOT_SET);
		}
		/* If no columns specified we set output to the same as input columns */
		if (n_cols == 0 && mode != GMT_COL_FIX && (error = gmt_set_cols (API->GMT, GMT_OUT, n_in)) != 0)
			return_error (API, GMT_N_COLS_NOT_SET);
		/* Set output record type */
		if (n_cols == 0)
			API->GMT->current.io.record_type[GMT_OUT] = GMT_WRITE_TEXT;
		else if (mode == GMT_COL_FIX_NO_TEXT)
			API->GMT->current.io.record_type[GMT_OUT] = GMT_WRITE_DATA;
		else
			API->GMT->current.io.record_type[GMT_OUT] = GMT_WRITE_MIXED;
	}

	/* Get here when n_cols is not zero (of we have a special case), so must consult mode */

	switch (mode) {
		case GMT_COL_FIX_NO_TEXT:	/* Specific a fixed number of columns, and ignore trailing text */
			API->GMT->current.io.trailing_text[direction] = false;
			/* Intentionally fall through - to set columns */
		case GMT_COL_FIX:	/* Specific a fixed number of columns */
			error = gmt_set_cols (API->GMT, direction, n_cols);
			break;
		case GMT_COL_VAR:	/* Flag we have a variable number of input columns */
			API->GMT->current.io.variable_in_columns = true;
			break;
		case GMT_COL_ADD:	/* Add to the number of input columns */
			error = gmt_set_cols (API->GMT, GMT_OUT, n_in + n_cols);
			break;
		case GMT_COL_SUB:	/* Subtract from the number of input columns */
			if (n_cols >= n_in) {
				GMT_Report (API, GMT_MSG_ERROR, "GMT_Set_Columns: Cannot specify less than one output column!\n");
				return_error (API, GMT_DIM_TOO_SMALL);
			}
			error = gmt_set_cols (API->GMT, GMT_OUT, n_in - n_cols);
			break;
	}
	if (error) return_error (API, GMT_N_COLS_NOT_SET);
	return (GMT_NOERROR);
}

#ifdef FORTRAN_API
int GMT_Set_Columns_ (unsigned int *direction, unsigned int *n_cols, unsigned int *mode) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Set_Columns (GMT_FORTRAN, *direction, *n_cols, *mode));
}
#endif

GMT_LOCAL int gmtapi_change_gridlayout (struct GMTAPI_CTRL *API, char *code, unsigned int mode, struct GMT_GRID *G, gmt_grdfloat *out) {
	enum GMT_enum_family family;
	openmp_int row, col;
	unsigned int pad[4], old_layout, new_layout;
	uint64_t from_node, to_node;
	gmt_grdfloat *tmp = NULL;
	gmt_M_unused(mode);

	old_layout = gmtapi_decode_layout (API, G->header->mem_layout, &family);
	if (family != GMT_IS_GRID) return GMT_NOT_A_VALID_FAMILY;
	new_layout = gmtapi_decode_layout (API, code, &family);
	if (old_layout == new_layout) return GMT_NOERROR;	/* Nothing to do */

	/* Remove the high bits for complex data */
	old_layout &= 3;	new_layout &= 3;
	/* Grids may be column vs row oriented and from top or from bottom */
	gmt_M_memcpy (pad, G->header->pad, 4, unsigned int);	/* Remember the pad */
	if (((tmp = out) == NULL) && (tmp = gmt_M_memory_aligned (API->GMT, NULL, G->header->size, gmt_grdfloat)) == NULL)
		return (GMT_MEMORY_ERROR);		/* Something went wrong */

	gmt_grd_pad_off (API->GMT, G);	/* Simplify working with no pad */
	if (old_layout == 0 && new_layout == 2) { /* Change from TR to TC */
		for (row = 0, from_node = 0; row < (openmp_int)G->header->n_rows; row++)
			for (col = 0; col < (openmp_int)G->header->n_columns; col++, from_node++)
				tmp[(uint64_t)col * (uint64_t)G->header->n_rows + row] = G->data[from_node];
	}
	else if (old_layout == 0 && new_layout == 3) {	/* Change from TR to BC */
		for (row = 0, from_node = 0; row < (openmp_int)G->header->n_rows; row++)
			for (col = 0; col < (openmp_int)G->header->n_columns; col++, from_node++)
				tmp[(uint64_t)col * (uint64_t)G->header->n_rows + (G->header->n_rows - row - 1)] = G->data[from_node];
	}
	else if (old_layout == 2 && new_layout == 0) {	/* Change from TC to TR */
		for (row = 0, to_node = 0; row < (openmp_int)G->header->n_rows; row++)
			for (col = 0; col < (openmp_int)G->header->n_columns; col++, to_node++)
				tmp[to_node] = G->data[(uint64_t)col * (uint64_t)G->header->n_rows + row];
	}
	else if (old_layout == 3 && new_layout == 0) {	/* Change from BC to TR */
		for (row = 0, to_node = 0; row < (openmp_int)G->header->n_rows; row++)
			for (col = 0; col < (openmp_int)G->header->n_columns; col++, to_node++)
				tmp[to_node] = G->data[(uint64_t)col * (uint64_t)G->header->n_rows + (G->header->n_rows - row - 1)];
	}
	else {		/* Other cases to be added later ...*/
		GMT_Report (API, GMT_MSG_WARNING, "gmtapi_change_gridlayout: reordering function for case %s -> %s not yet written. Doing nothing\n",
		            G->header->mem_layout, code);
		for (to_node = 0; to_node < G->header->size; to_node++)
			tmp[to_node] = G->data[to_node];
	}

	if (out == 0) {	/* Means we must update the grid data */
		gmt_M_free_aligned (API->GMT, G->data);			/* Free previous aligned grid memory */
		G->data = tmp;
	}
	gmt_grd_pad_on (API->GMT, G, pad);	/* Restore pad on grid */
	return (GMT_NOERROR);
}

GMT_LOCAL int gmtapi_change_imagelayout (struct GMTAPI_CTRL *API, char *code, unsigned int mode, struct GMT_IMAGE *I, unsigned char *out1, unsigned char *out2) {
	/* code: The new memory layout code, e.g "TRB"
	   mode: Currently unused (for future expansion)
	   out1: Array with the image data converted to the requested layout.
	   out2: Array with the transparencies converted to the requested layout.
	         If NULL on input the necessary memory is allocated inside this function, otherwise
	         it is ASSUMED that it points a memory chunk big enough to hold the reshuffled data.
	*/
	bool changed = true;
	unsigned char *tmp = NULL, *alpha = NULL;
	enum GMT_enum_family family;
	unsigned int old_layout, new_layout;
	uint64_t band, row, col, to_node, from_node;
	struct GMT_IMAGE_HIDDEN *IH = NULL;
	gmt_M_unused(mode);

	old_layout = gmtapi_decode_layout (API, I->header->mem_layout, &family);
	new_layout = gmtapi_decode_layout(API, code, &family);
	if (old_layout == new_layout) return GMT_NOERROR;	/* Nothing to do */

	if ((tmp = out1) == NULL && (tmp = gmt_M_memory_aligned (API->GMT, NULL, I->header->n_bands * I->header->size, unsigned char)) == NULL)
		return (GMT_MEMORY_ERROR);		/* Something went wrong */
	if (I->alpha && (alpha = out2) == NULL && (alpha = gmt_M_memory_aligned (API->GMT, NULL, I->header->size, unsigned char)) == NULL) {
		if (out2 == NULL) gmt_M_free (API->GMT, alpha);
		gmt_M_free_aligned (API->GMT, tmp);
		return (GMT_MEMORY_ERROR);		/* Something went wrong */
	}

	/* Images may be column vs row oriented, from top or from bottom and may be Band|Line|Pixel interleaved
	   That sums up to a lot of combinations. We will add them on a by-need basis. */

	if (old_layout == 8 && new_layout == 2) {		/* Change from TRP to TCB */
		for (row = from_node = 0; row < I->header->my; row++)
			for (col = 0; col < I->header->mx; col++)
				for (band = 0; band < I->header->n_bands; band++, from_node++) {
					to_node = row + col*I->header->my + band * I->header->size;
					tmp[to_node] = (uint8_t)I->data[from_node];
				}
		if (I->alpha) {
			for (row = from_node = 0; row < I->header->my; row++)
				for (col = 0; col < I->header->mx; col++, from_node++) {
					to_node = row + col*I->header->my;
					alpha[to_node] = (uint8_t)I->alpha[from_node];
				}
		}
	}
	else if (old_layout == 0 && new_layout == 4) {		/* Change from TRB to TRL [UNTESTED] */
		for (row = 0; row < I->header->my; row++)
			for (col = 0; col < I->header->mx; col++)
				for (band = 0; band < I->header->n_bands; band++) {
					from_node = col + row*I->header->mx + band * I->header->size;
					to_node = col + (band + row * I->header->n_bands) * I->header->mx;
					tmp[to_node] = (uint8_t)I->data[from_node];
				}
		if (I->alpha)	/* Same since only one band of alpha */
			gmt_M_memcpy (alpha, I->alpha, I->header->size, uint8_t);
	}
	else if (old_layout == 4 && new_layout == 0) {		/* Change from TRL to TRB [UNTESTED] */
		for (row = 0; row < I->header->my; row++)
			for (col = 0; col < I->header->mx; col++)
				for (band = 0; band < I->header->n_bands; band++) {
					to_node = col + row*I->header->mx + band * I->header->size;
					from_node = col + (band + row * I->header->n_bands) * I->header->mx;
					tmp[to_node] = (uint8_t)I->data[from_node];
				}
		if (I->alpha)	/* Same since only one band of alpha */
			gmt_M_memcpy (alpha, I->alpha, I->header->size, uint8_t);
	}
	else if (old_layout == 0 && new_layout == 8) {		/* Change from TRB to TRP */
		for (row = to_node = 0; row < I->header->my; row++)
			for (col = 0; col < I->header->mx; col++)
				for (band = 0; band < I->header->n_bands; band++, to_node++) {
					from_node = col + row*I->header->mx + band * I->header->size;
					tmp[to_node] = (uint8_t)I->data[from_node];
				}
		if (I->alpha)	/* Same since only one band of alpha */
			gmt_M_memcpy (alpha, I->alpha, I->header->size, uint8_t);
	}
	else if (old_layout == 8 && new_layout == 0) {		/* Change from TRP to TRB [UNTESTED] */
		for (row = from_node = 0; row < I->header->my; row++)
			for (col = 0; col < I->header->mx; col++)
				for (band = 0; band < I->header->n_bands; band++, from_node++) {
					to_node = col + row*I->header->mx + band * I->header->size;
					tmp[to_node] = (uint8_t)I->data[from_node];
				}
		if (I->alpha)	/* Same since only one band of alpha */
			gmt_M_memcpy (alpha, I->alpha, I->header->size, uint8_t);
	}
	else if (old_layout == 0 && new_layout == 9) {		/* Change from TRB to BRP */
		for (row = to_node = 0; row < I->header->my; row++)		/* Not UD flipping so what we call B is probably T */
			for (col = 0; col < I->header->mx; col++)
				for (band = 0; band < I->header->n_bands; band++, to_node++) {
					from_node = col + row*I->header->mx + band * I->header->size;
					tmp[to_node] = (uint8_t)I->data[from_node];
				}
		if (I->alpha)	/* Same since only one band of alpha and no transposition */
			gmt_M_memcpy (alpha, I->alpha, I->header->size, uint8_t);
	}
	else if (old_layout == 2 && new_layout == 9) {		/* Change from TCB to BRP */
		for (row = to_node = 0; row < I->header->my; row++)		/* Not UD flipping so what we call B is probably T */
			for (col = 0; col < I->header->mx; col++)
				for (band = 0; band < I->header->n_bands; band++, to_node++) {
					from_node = row + col*I->header->my + band * I->header->size;
					tmp[to_node] = (uint8_t)I->data[from_node];
				}
		if (I->alpha) {
			for (row = to_node = 0; row < I->header->my; row++)
				for (col = 0; col < I->header->mx; col++, to_node++) {
					from_node = row + col*I->header->my;
					alpha[to_node] = (uint8_t)I->alpha[from_node];
				}
		}
	}
	else if (old_layout == 11 && new_layout == 9) {		/* Change from BCP to BRP */
		for (row = to_node = 0; row < I->header->my; row++)
			for (col = 0; col < I->header->mx; col++)
				for (band = 0; band < I->header->n_bands; band++, to_node++) {
					from_node = row + col*I->header->my + band * I->header->size;
					tmp[to_node] = (uint8_t)I->data[from_node];
				}
		if (I->alpha)	/* Same since only one band of alpha and no transposition */
			gmt_M_memcpy (alpha, I->alpha, I->header->size, uint8_t);
	}
	else if (old_layout == 3 && new_layout == 9) {		/* Change from BCB to BRP. Here we believe in first B but not 2nd */
		for (row = to_node = 0; row < I->header->my; row++)
			for (col = 0; col < I->header->mx; col++)
				for (band = 0; band < I->header->n_bands; band++, to_node++) {
					from_node = (I->header->my - 1 - row) + col*I->header->my + band * I->header->size;
					tmp[to_node] = (uint8_t)I->data[from_node];
				}
		if (I->alpha) {
			for (row = to_node = 0; row < I->header->my; row++)
				for (col = 0; col < I->header->mx; col++, to_node++) {
					from_node = (I->header->my - 1 - row) + col*I->header->my;
					alpha[to_node] = (uint8_t)I->alpha[from_node];
				}
		}
	}
	else if (old_layout == 9 && new_layout == 0) {		/* Change from BRP to TRB */
		for (row = from_node = 0; row < I->header->my; row++)
			for (col = 0; col < I->header->mx; col++)
				for (band = 0; band < I->header->n_bands; band++, from_node++) {
					//to_node = col + (I->header->my - 1 - row)*I->header->my + band*I->header->size;  /* PW: This was always like this - commented out, so where is the B->T happening? */
					to_node = col + row*I->header->my + band * I->header->size;
					tmp[to_node] = (uint8_t)I->data[from_node];
				}
		if (I->alpha) {
			for (row = from_node = 0; row < I->header->my; row++)
				for (col = 0; col < I->header->mx; col++, from_node++) {
					to_node = col + row*I->header->my;
					alpha[to_node] = (uint8_t)I->alpha[from_node];
				}
		}
	}
	//else if (old_layout == 2 && new_layout == 0) {}	/* Change from TCB to TRB */
	else {		/* Other cases to be added later ...*/
		GMT_Report (API, GMT_MSG_WARNING, "gmtapi_change_imagelayout: reordering function for case %s -> %s not yet written. Doing nothing.\n",
			I->header->mem_layout, code);
		for (from_node = 0; from_node < I->header->size; from_node++)
			tmp[from_node] = I->data[from_node];
		changed = false;
	}

	if (out1 == NULL) {	/* Means we must update the Image data */
		IH = gmt_get_I_hidden (I);
		if (IH->alloc_mode != GMT_ALLOC_EXTERNALLY)
			gmt_M_free_aligned (API->GMT, I->data);			/* Free previous aligned image memory */
		I->data = tmp;
	}
	if (out2 == NULL && alpha) {	/* Means we must update the alpha data */
		if (!IH) IH = gmt_get_I_hidden (I);
		if (IH->alloc_mode != GMT_ALLOC_EXTERNALLY)
			gmt_M_free_aligned (API->GMT, I->alpha);		/* Free previous aligned image transparency */
		I->alpha = alpha;
	}

	if (changed) {	/* Update the mem_layout for this image */
		strncpy (I->header->mem_layout, code, MIN(strlen(code),4));
		if (I->alpha) I->header->mem_layout[3] = 'a';	/* Flag that we have transparency */
	}

	return (GMT_NOERROR);
}

int GMT_Change_Layout (void *V_API, unsigned int family, char *code, unsigned int mode, void *obj, void *out, void *alpha) {
	/* Reorder the memory layout of a grid or image given the new desired layout in code.
	 * If out == NULL then we allocate space to hold the new grid|image and replace obj->data with this new array.
	 *   For grids we preserve any padding in effect for the object but for out we have no padding.
	 * Otherwise we assume out points to allocated memory and we simply fill it out, assuming no pad.
	 * mode is presently unused.
	 * alpha is only considered for images and may be used to return a modified transparency array.
	 */
	struct GMTAPI_CTRL *API = NULL;
	int error;
	if (V_API == NULL) return_error (V_API, GMT_NOERROR);	/* Not fuss if nothing is given */
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;
	if (code == NULL || code[0] == '\0') return_error (V_API, GMT_NOT_A_SESSION);
	switch (family) {
		case GMT_IS_GRID:
			error = gmtapi_change_gridlayout (V_API, code, mode, obj, out);
			break;
		case GMT_IS_IMAGE:
			error = gmtapi_change_imagelayout (V_API, code, mode, obj, out, alpha);
			break;
		default:
			error = GMT_NOT_A_VALID_FAMILY;
			break;
	}
	return_error (API, error);
}

#ifdef FORTRAN_API
int GMT_Change_Layout_ (unsigned int *family, char *code, unsigned int *mode, void *obj, void *out, void *alpha, int len) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Change_Layout (GMT_FORTRAN, *family, code, *mode, obj, out, alpha));
}
#endif

/* Deal with assignments of custom vectors and matrices to GMT containers */

GMT_LOCAL int gmtapi_insert_vector (struct GMTAPI_CTRL *API, union GMT_UNIVECTOR *V, unsigned int type, void *vector) {
	/* Hook a vector to the correct union member given data type */
	gmt_M_unused (API);
	switch (type) {
		case GMT_DOUBLE:	V->f8  = vector;	break;
		case GMT_FLOAT:		V->f4  = vector;	break;
		case GMT_ULONG:		V->ui8 = vector;	break;
		case GMT_LONG:		V->si8 = vector;	break;
		case GMT_UINT:		V->ui4 = vector;	break;
		case GMT_INT:		V->si4 = vector;	break;
		case GMT_USHORT:	V->ui2 = vector;	break;
		case GMT_SHORT:		V->si2 = vector;	break;
		case GMT_UCHAR:		V->uc1 = vector;	break;
		case GMT_CHAR:		V->sc1 = vector;	break;
		default:
			return (GMT_NOT_A_VALID_TYPE);
			break;
	}
	return (GMT_NOERROR);
}

GMT_LOCAL void * gmtapi_retrieve_vector (void *API, union GMT_UNIVECTOR *V, unsigned int type) {
	void *vector = NULL;
	gmt_M_unused (API);
	switch (type) {
		case GMT_DOUBLE:	vector = V->f8;	break;
		case GMT_FLOAT:		vector = V->f4;	break;
		case GMT_ULONG:		vector = V->ui8;	break;
		case GMT_LONG:		vector = V->si8;	break;
		case GMT_UINT:		vector = V->ui4;	break;
		case GMT_INT:		vector = V->si4;	break;
		case GMT_USHORT:	vector = V->ui2;	break;
		case GMT_SHORT:		vector = V->si2;	break;
		case GMT_UCHAR:		vector = V->uc1;	break;
		case GMT_CHAR:		vector = V->sc1;	break;
		default:
			return NULL;
			break;
	}
	return vector;
}

int GMT_Put_Vector (void *V_API, struct GMT_VECTOR *V, unsigned int col, unsigned int type, void *vector) {
	/* Hooks a users custom vector onto V's column array and sets the type.
	 * It is the user's responsibility to pass correct type for the given vector.
	 * We also check that the number of rows have been set earlier.
	 * We also allow special text-based arrays for longitude, latitude, datetime or Cartesian data to be passed
	 * which may be logically OR'ed with desired array type (e.g., GMT_LONG|GMT_TEXT).
	 * Note: We do not check for data loss in the conversion (e.g., GMT_UCHAR|GMT_TEXT) */
	unsigned int special_type;
	enum GMT_enum_alloc alloc_mode = GMT_ALLOC_EXTERNALLY;	/* Default is to pass vectors in read-only */
	struct GMTAPI_CTRL *API = NULL;
	struct GMT_VECTOR_HIDDEN *VH = NULL;

	API = gmtapi_get_api_ptr (V_API);
	if (API == NULL) return_error (API, GMT_NOT_A_SESSION);
	if (V == NULL) return_error (API, GMT_PTR_IS_NULL);
	if (V->n_rows == 0) return_error (API, GMT_DIM_TOO_SMALL);
	if (col >= V->n_columns) return_error (API, GMT_DIM_TOO_LARGE);

	special_type = type & (GMT_TEXT | GMT_DATETIME);	/* Backwards compatible with just GMT_DATETIME */
	if (special_type == 0) {	/* Just passing in a numerical array directly via read-only pointer; hook it up */
		if (gmtapi_insert_vector (API, &(V->data[col]), type, vector))
			return_error (API, GMT_NOT_A_VALID_TYPE);
		V->type[col] = type;	/* Set column type */
	}
	else {	/* Convert text to something else */
		unsigned L_type = GMT_IS_UNKNOWN, got;
		double value;
		uint64_t row, n_bad = 0, L;
		char **dt = NULL;
		char text[GMT_LEN64] = {""};
		GMT_putfunction api_put_val = NULL;

		if (gmtapi_retrieve_vector (API, &(V->data[col]), type)) {	/* Refuse to overwrite existing pointer unless NULL */
			GMT_Report (API, GMT_MSG_ERROR, "Array already exist for column %d\n", col);
			return_error (API, GMT_PTR_NOT_NULL);
		}
		type -= special_type;	/* Remove the higher bit flag(s) */
		if (type == 0) type = GMT_DOUBLE;	/* Default is double precision if a type was not specified */
		if ((dt = gmtapi_get_char_char_ptr (vector)) == NULL) {
			GMT_Report (API, GMT_MSG_ERROR, "Given string array is NULL\n");
			return_error (API, GMT_MEMORY_ERROR);
		}
		strncpy (text, dt[0], GMT_LEN64);	/* Since gmt_scanf may try to temporarily change the string... */
		if ((L = strlen (text)) == 0) {
			GMT_Report (API, GMT_MSG_ERROR, "Given blank string in array\n");
			return_error (API, GMT_MEMORY_ERROR);
		}
		if (special_type == GMT_DATETIME || gmtlib_maybe_abstime (API->GMT, text))	/* Honor backwards compatibility for GMT_DATETIME */
			L_type = GMT_IS_ABSTIME;
		else if (strchr ("WE", text[L-1]))
			L_type = GMT_IS_LON;
		else if (strchr ("SN", text[L-1]))
			L_type = GMT_IS_LAT;
		else if (strchr (text, ':'))
			L_type = GMT_IS_GEO;
		if ((api_put_val = gmtapi_select_put_function (API, type)) == NULL)
			return_error (API, GMT_NOT_A_VALID_TYPE);
		/* Here we know the type is valid */
		if (gmtlib_alloc_univector (API->GMT, &V->data[col], type, V->n_rows) != GMT_NOERROR) {
			GMT_Report (API, GMT_MSG_ERROR, "Unable to allocate array of %" PRIu64 " %s-values for converted strings\n", V->n_rows, GMT_type[type]);
			return_error (API, GMT_MEMORY_ERROR);
		}
		/* Do the conversion to double precision */
		for (row = 0; row < V->n_rows; row++) {
			strncpy (text, dt[row], GMT_LEN64);
			if ((got = gmt_scanf (API->GMT, text, L_type, &value)) == GMT_IS_NAN) {
				n_bad++;	/* Check for bad conversions */
				value = API->GMT->session.d_NaN;
			}
			else if (got != GMT_IS_FLOAT && L_type == GMT_IS_UNKNOWN)	/* Got something other than plain float, use that from now on */
				L_type = got;
			api_put_val (&(V->data[col]), row, value);	/* Place value in vector of selected type */
		}
		V->type[col] = type;	/* Flag as the new type after conversion */
		if (L_type == GMT_IS_UNKNOWN) L_type = GMT_IS_FLOAT;	/* We held out this long but now must default to it */
		gmt_set_column_type (API->GMT, GMT_IO, col, L_type);
		if (n_bad) {	/* Report on values that could not be converted */
			if (L_type == GMT_IS_LON)
				GMT_Report (API, GMT_MSG_WARNING, "Unable to parse %" PRIu64 " longitude strings\n", n_bad);
			else if (L_type == GMT_IS_LAT)
				GMT_Report (API, GMT_MSG_WARNING, "Unable to parse %" PRIu64 " latitude strings\n", n_bad);
			else if (L_type == GMT_IS_ABSTIME)
				GMT_Report (API, GMT_MSG_WARNING, "Unable to parse %" PRIu64 " datetime strings (ISO datetime format required)\n", n_bad);
		}
		alloc_mode = GMT_ALLOC_INTERNALLY;
	}

	VH = gmt_get_V_hidden (V);
	VH->alloc_mode[col] = alloc_mode;

	return GMT_NOERROR;
}

#ifdef FORTRAN_API
int GMT_Put_Vector_ (struct GMT_VECTOR *V, unsigned int *col, unsigned int *type, void *vector) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Put_Vector (GMT_FORTRAN, V, *col, *type, vector));
}
#endif

void * GMT_Get_Vector (void *API, struct GMT_VECTOR *V, unsigned int col) {
	/* Returns a pointer to the specified column array. Users can consult
	 * V->type[col] to know what data type is pointed to.  */
	void *vector = NULL;
	if (API == NULL) return_null (API, GMT_NOT_A_SESSION);
	if (V == NULL) return_null (API, GMT_PTR_IS_NULL);
	if (col >= V->n_columns) return_null (API, GMT_DIM_TOO_LARGE);
	if ((vector = gmtapi_retrieve_vector (API, &(V->data[col]), V->type[col])) == NULL)
		return_null (API, GMT_NOT_A_VALID_TYPE);
	return vector;
}

#ifdef FORTRAN_API
void * GMT_Get_Vector_ (struct GMT_VECTOR *V, unsigned int *col) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Get_Vector (GMT_FORTRAN, V, *col));
}
#endif

int GMT_Put_Matrix (void *V_API, struct GMT_MATRIX *M, unsigned int type, int pad, void *matrix) {
	/* Hooks a user's custom matrix onto M's data array and sets the type.
	 * It is the user's responsibility to pass correct type for the given matrix.
	 * We check that dimensions have been set earlier */
	int item;
	struct GMT_MATRIX_HIDDEN *MH = NULL;
	struct GMTAPI_CTRL *API = NULL;
	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (M == NULL) return_error (V_API, GMT_PTR_IS_NULL);
	if (M->n_columns == 0 || M->n_rows == 0) return_error (V_API, GMT_DIM_TOO_SMALL);
	API = gmtapi_get_api_ptr (V_API);
	if (gmtapi_insert_vector (API, &(M->data), type, matrix))
		return_error (API, GMT_NOT_A_VALID_TYPE);
	M->type = type;
	MH = gmt_get_M_hidden (M);
	MH->alloc_mode = GMT_ALLOC_EXTERNALLY;	/* Since it clearly is a user array */
	MH->pad = pad;	/* Placing the pad argument here */
	if ((item = gmtapi_get_item (API, GMT_IS_GRID, M)) != GMT_NOTSET)	/* Found in list, update type here as well */
		API->object[item]->type = type;

	return GMT_NOERROR;
}

#ifdef FORTRAN_API
int GMT_Put_Matrix_ (struct GMT_MATRIX *M, unsigned int *type, int *pad, void *matrix) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Put_Matrix (GMT_FORTRAN, M, *type, *pad, matrix));
}
#endif

void * GMT_Get_Matrix (void *API, struct GMT_MATRIX *M) {
	/* Returns a pointer to the matrix.  Users can consult
	 * M->type to know what data type is pointed to.  */
	void *matrix = NULL;
	if (API == NULL) return_null (API, GMT_NOT_A_SESSION);
	if (M == NULL) return_null (API, GMT_PTR_IS_NULL);
	if ((matrix = gmtapi_retrieve_vector (API, &(M->data), M->type)) == NULL)
		return_null (API, GMT_NOT_A_VALID_TYPE);
	return matrix;
}

#ifdef FORTRAN_API
void * GMT_Get_Matrix_ (struct GMT_MATRIX *M) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Get_Matrix (GMT_FORTRAN, M));
}
#endif

int GMT_Put_Strings (void *V_API, unsigned int family, void *object, char **array) {
	/* Hook pointer to the text array in a matrix or vector */
	bool dup = false;
	enum GMT_enum_CPT code;
	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (object == NULL) return_error (V_API, GMT_PTR_IS_NULL);
	if (array == NULL) return_error (V_API, GMT_PTR_IS_NULL);
	if (family & GMT_IS_DUPLICATE) {	/* Need to duplicate the strings */
		dup = true;
		family -= GMT_IS_DUPLICATE;
	}
	else if (family & GMT_IS_REFERENCE)	/* This is the default action, just remove the mode */
		family -= GMT_IS_REFERENCE;
	if (family & GMT_IS_PALETTE_KEY) {
		family -= GMT_IS_PALETTE_KEY;
		code = GMT_IS_PALETTE_KEY;
	}
	else if (family & GMT_IS_PALETTE_LABEL) {
		family -= GMT_IS_PALETTE_LABEL;
		code = GMT_IS_PALETTE_LABEL;
	}
	if (family == GMT_IS_PALETTE && code == 0) {	/* This is specific to CPTs */
		return_error (V_API, GMT_VALUE_NOT_SET);
	}
	if (!(family == GMT_IS_VECTOR || family == GMT_IS_MATRIX || family == GMT_IS_PALETTE)) return_error (V_API, GMT_NOT_A_VALID_FAMILY);

	if (family == GMT_IS_VECTOR) {
		struct GMT_VECTOR *V = gmtapi_get_vector_data (object);
		struct GMT_VECTOR_HIDDEN *VH = gmt_get_V_hidden (V);
		if (dup) {	/* Must duplicate the input array of strings */
			uint64_t k;
			struct GMTAPI_CTRL *API = gmtapi_get_api_ptr (V_API);
			if ((V->text = gmt_M_memory (API->GMT, NULL, V->n_rows, char **)) == NULL) {
				GMT_Report (API, GMT_MSG_ERROR, "GMT_Put_Strings: Unable to allocate text string array for vector\n");
				return (GMT_MEMORY_ERROR);
			}
			for (k = 0; k < V->n_rows; k++)
				if (array[k]) V->text[k] = strdup (array[k]);
			VH->alloc_mode_text = GMT_ALLOC_INTERNALLY;
		}
		else {	/* By reference */
			V->text = array;
			VH->alloc_mode_text = GMT_ALLOC_EXTERNALLY;
		}
	}
	else if (family == GMT_IS_MATRIX) {
		struct GMT_MATRIX *M = gmtapi_get_matrix_data (object);
		struct GMT_MATRIX_HIDDEN *MH = gmt_get_M_hidden (M);
		if (dup) {	/* Must duplicate the input array of strings */
			uint64_t k;
			struct GMTAPI_CTRL *API = gmtapi_get_api_ptr (V_API);
			if ((M->text = gmt_M_memory (API->GMT, NULL, M->n_rows, char **)) == NULL) {
				GMT_Report (API, GMT_MSG_ERROR, "GMT_Put_Strings: Unable to allocate text string array for matrix\n");
				return (GMT_MEMORY_ERROR);
			}
			for (k = 0; k < M->n_rows; k++)
				if (array[k]) M->text[k] = strdup (array[k]);
			MH->alloc_mode_text = GMT_ALLOC_INTERNALLY;
		}
		else {	/* By reference */
			M->text = array;
			MH->alloc_mode_text = GMT_ALLOC_EXTERNALLY;
		}
	}
	else if (family == GMT_IS_PALETTE) {
		unsigned int k, item = (code == GMT_IS_PALETTE_LABEL) ? GMT_CPT_INDEX_LBL : GMT_CPT_INDEX_KEY;
		struct GMT_PALETTE *P = gmtapi_get_palette_data (object);
		struct GMT_PALETTE_HIDDEN *CH = gmt_get_C_hidden (P);
		for (k = 0; k < P->n_colors; k++) {
			if (array[k] == NULL) continue;	/* No string given for this entry */
			if (code == GMT_IS_PALETTE_LABEL) {
				if (dup && P->data[k].label) gmt_M_str_free (P->data[k].label);	/* Free any old entry */
				P->data[k].label = (dup) ? strdup (array[k]) : array[k];
			}
			else if (code == GMT_IS_PALETTE_KEY) {
				if (dup && P->data[k].key) gmt_M_str_free (P->data[k].key);	/* Free any old entry */
				P->data[k].key = (dup) ? strdup (array[k]) : array[k];
			}
		}
		CH->alloc_mode_text[item] = (dup) ? GMT_ALLOC_INTERNALLY : GMT_ALLOC_EXTERNALLY;
	}
	return (GMT_NOERROR);
}

#ifdef FORTRAN_API
int GMT_Put_Strings_ (unsigned int *family, void *object, char **array, int len) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Put_Strings (GMT_FORTRAN, *family, object, array));
}
#endif

char ** GMT_Get_Strings (void *V_API, unsigned int family, void *object) {
	/* Return pointer to the text array in a matrix or vector */
	char **array = NULL;
	if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION);
	if (object == NULL) return_null (V_API, GMT_PTR_IS_NULL);
	if (!(family == GMT_IS_VECTOR || family == GMT_IS_MATRIX)) return_null (V_API, GMT_NOT_A_VALID_FAMILY);
	if (family == GMT_IS_VECTOR) {
		struct GMT_VECTOR *V = gmtapi_get_vector_data (object);
		array = V->text;
	}
	else if (family == GMT_IS_MATRIX) {
		struct GMT_MATRIX *M = gmtapi_get_matrix_data (object);
		array = M->text;
	}
	if (array == NULL)
		return_null (V_API, GMT_PTR_IS_NULL);
	return (array);
}

#ifdef FORTRAN_API
char ** GMT_Get_Strings_ (unsigned int *family, void *object) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Get_Strings (GMT_FORTRAN, *family, object));
}
#endif

#define GMT_NO_SUCH_ENUM -99999
#include "gmt_enum_dict.h"

int GMT_Get_Enum (void *V_API, char *key) {
	/* Access to GMT enums from environments unable to parse in gmt_resources.h.
	 * Return value of enum or GMT_NO_SUCH_ENUM if not found */
	int lo = 0, hi = GMT_N_API_ENUMS, mid, value;
	gmt_M_unused (V_API);
	if (key == NULL || key[0] == '\0') return GMT_NO_SUCH_ENUM;
	while (lo != hi) {	/* Do a binary search since gmt_api_enums is lexically sorted */
		mid = (lo + hi) / 2;
		value = strcmp (key, gmt_api_enums[mid].name);
		if (value == 0) return gmt_api_enums[mid].value;
		if ((hi-lo) == 1)
			lo = hi = mid;
		else if (value > 0)
			lo = mid;
		else if (value < 0)
			hi = mid;
	}
	return GMT_NO_SUCH_ENUM;
}

#ifdef FORTRAN_API
int GMT_Get_Enum_ (char *arg, int len) {
	return (GMT_Get_Enum (GMT_FORTRAN, arg));
}
#endif

/* A few more FORTRAN bindings moved from gmt_fft.c: */

#ifdef FORTRAN_API
double GMT_FFT_Wavenumber_ (uint64_t *k, unsigned int *mode, void *v_K) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_FFT_Wavenumber (GMT_FORTRAN, *k, *mode, v_K));
}
#endif

#ifdef FORTRAN_API
int GMT_FFT_1D_ (gmt_grdfloat *data, uint64_t *n, int *direction, unsigned int *mode) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_FFT_1D (GMT_FORTRAN, data, *n, *direction, *mode));
}
#endif

#ifdef FORTRAN_API
int GMT_FFT_2D_ (gmt_grdfloat *data, unsigned int *n_columns, unsigned int *n_rows, int *direction, unsigned int *mode) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_FFT_2D (GMT_FORTRAN, data, *n_columns, *n_rows, *direction, *mode));
}
#endif

int GMT_Get_Family (void *V_API, unsigned int direction, struct GMT_OPTION *head) {
	/* Scan the registered module input|output resources to learn what their family is.
	 * direction:	Either GMT_IN or GMT_OUT
	 * head:	Head of the list of module options
	 *
	 * Returns:	The family value (GMT_IS_DATASET|CPT|GRID|IMAGE|PS) or GMT_NOTSET if not known
	 */
	struct GMTAPI_CTRL *API = NULL;
	struct GMT_OPTION *current = NULL;
	int item, object_ID, family = GMT_NOTSET;
	//int flag = (direction == GMT_IN) ? GMTAPI_MODULE_INPUT : GMT_NOTSET;
	unsigned int n_kinds = 0, k, counter[GMT_N_FAMILIES];
	char desired_option = (direction == GMT_IN) ? GMT_OPT_INFILE : GMT_OPT_OUTFILE;
	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	API = gmtapi_get_api_ptr (V_API);
	gmt_M_memset (counter, GMT_N_FAMILIES, unsigned int);	/* Initialize counter */
	API->error = GMT_NOERROR;	/* Reset in case it has some previous error */

	for (current = head; current; current = current->next) {		/* Loop over the list and look for input files */
		if (current->option != desired_option) continue;				/* Not a module resource argument */
		if ((object_ID = gmtapi_decode_id (current->arg)) == GMT_NOTSET) continue;	/* Not a registered resource */
		//if ((item = gmtlib_validate_id (API, GMT_NOTSET, object_ID, direction, flag)) == GMT_NOTSET) continue;	/* Not the right attributes */
		if ((item = gmtlib_validate_id (API, GMT_NOTSET, object_ID, direction, GMT_NOTSET)) == GMT_NOTSET) continue;	/* Not the right attributes */
		counter[(API->object[item]->family)]++;	/* Update counts of this family */
	}
	for (k = 0; k < GMT_N_FAMILIES; k++) {	/* Determine which family we found, if any */
		if (counter[k]) n_kinds++, family = k;
	}
	if (n_kinds != 1) {	/* Could not determine family */
		family = GMT_NOTSET;
		GMT_Report (API, GMT_MSG_DEBUG, "GMT_Get_Family: Could not determine family\n");
	}
	else	/* Found a unique family */
		GMT_Report (API, GMT_MSG_DEBUG, "GMT_Get_Family: Determined family to be %s\n", GMT_family[family]);
	return (family);
}

#ifdef FORTRAN_API
int GMT_Get_Family_ (unsigned int *direction, struct GMT_OPTION *head) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Get_Family (GMT_FORTRAN, *direction, head));
}
#endif

int GMT_Set_AllocMode (void *V_API, unsigned int family, void *object) {
	/* Set the allocation mode of this object to EXTERNAL so that GMT does not try to free it.
	 * It is used in externals who provide its own allocated memory objects. */
	int error = GMT_NOERROR;
	uint64_t col;
	struct GMT_VECTOR		*V = NULL;
	struct GMT_DATASET_HIDDEN	*DH = NULL;
	struct GMT_PALETTE_HIDDEN	*CH = NULL;
	struct GMT_POSTSCRIPT_HIDDEN	*PH = NULL;
	struct GMT_CUBE_HIDDEN		*UH = NULL;
	struct GMT_VECTOR_HIDDEN	*VH = NULL;
	struct GMT_MATRIX_HIDDEN	*MH = NULL;
	struct GMT_GRID_HIDDEN		*GH = NULL;
	struct GMT_IMAGE_HIDDEN		*IH = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (object == NULL) return_error (V_API, GMT_PTR_IS_NULL);

	switch (family) {	/* grid, image, or matrix */
		case GMT_IS_GRID:	/* GMT Grid */
			GH = gmt_get_G_hidden (gmtapi_get_grid_data (object));
			GH->alloc_mode = GMT_ALLOC_EXTERNALLY;
			break;
		case GMT_IS_IMAGE:	/* GMT Image */
			IH = gmt_get_I_hidden (gmtapi_get_image_data (object));
			IH->alloc_mode = GMT_ALLOC_EXTERNALLY;
			break;
		case GMT_IS_DATASET:	/* GMT Dataset */
			DH = gmt_get_DD_hidden (gmtapi_get_dataset_data (object));
			DH->alloc_mode = GMT_ALLOC_EXTERNALLY;
			break;
		case GMT_IS_PALETTE:	/* GMT CPT */
			CH = gmt_get_C_hidden (gmtapi_get_palette_data (object));
			CH->alloc_mode = GMT_ALLOC_EXTERNALLY;
			break;
		case GMT_IS_CUBE:	/* GMT Cube */
			UH = gmt_get_U_hidden (gmtapi_get_cube_data (object));
			UH->alloc_mode = GMT_ALLOC_EXTERNALLY;
			break;
		case GMT_IS_POSTSCRIPT:		/* GMT PS */
			PH = gmt_get_P_hidden (gmtapi_get_postscript_data (object));
			PH->alloc_mode = GMT_ALLOC_EXTERNALLY;
			break;
		case GMT_IS_VECTOR:	/* GMT Vector */
			V = gmtapi_get_vector_data (object);
			VH = gmt_get_V_hidden (V);
			for (col = 0; col < V->n_columns; col++)
				VH->alloc_mode[col] = GMT_ALLOC_EXTERNALLY;
			break;
		case GMT_IS_MATRIX:	/* GMT Matrix */
			MH = gmt_get_M_hidden (gmtapi_get_matrix_data (object));
			MH->alloc_mode = GMT_ALLOC_EXTERNALLY;
			break;
		default:
			error = GMT_NOT_A_VALID_FAMILY;
			break;
	}
	return_error (V_API, error);
}

#ifdef FORTRAN_API
int GMT_Set_AllocMode_ (unsigned int *family, void *object) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Set_AllocMode (GMT_FORTRAN, *family, object));
}
#endif

/*! . */
int GMT_Get_FilePath (void *V_API, unsigned int family, unsigned int direction, unsigned int mode, char **file_ptr) {
	/* Replace file with its full path if that file exists, else return an error.
	 * If (mode & GMT_FILE_REMOTE) then we try to download any remote files
	 * given but not yet cached locally), and if the downloaded file is readable then
	 * we update file_ptr with the local path, otherwise return an error.
	 * If (mode & GMT_FILE_CHECK) then we only return error code and don't update file_ptr.
	 * The explicit mode for only examining local files is GMT_FILE_LOCAL [0].
	 *
	 * Filename complications:  Both grid, image and CPT filenames may have modifiers or
	 * format identifiers appended to their names.  Thus, as given, file may name be a valid
	 * filename until we have chopped off these strings.  Here is a summary of what GMT allows:
	 *
	 * imagefile[=gd[+b<band>]]
	 * grdfile[=<id>][+o<offset>][+n<invalid>][+s<scale>][+u|U<unit>]
	 * cptfile[+h<hinge>][+u|U<unit>]
	 *   Note: Some modules also allows cptfile[+h<hinge>][+u|U<unit>][i<dz>] but the +d
	 *   modifier is processed and removed in the module (grd-image/view/vector/2kml).
	 *
	 * gridfiles may also have strings to select specific layers of nigher-dimension netCDFfiles, using
	 * grdfile?<variables>[layer]|(value).
	 *
	 * URL queries also use ? as in http://<address>?<par1>=<val1>...
	 * Remote grids may also have format specification and modifiers like local grids:
	 * https://<address>/grdfile[=<id>][+o<offset>][+n<invalid>][+s<scale>][+u|U<unit>]
	 */

	char remote_path[PATH_MAX] = {""}, local_path[PATH_MAX] = {""}, was = '\0', *file = NULL, *c = NULL, *f = NULL;
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (!(direction == GMT_IN || direction == GMT_OUT)) return_error (API, GMT_NOT_A_VALID_DIRECTION);
	if (!gmtapi_valid_input_family (family)) return_error (API, GMT_NOT_A_VALID_FAMILY);
	if (mode > (GMT_FILE_CHECK+GMT_FILE_REMOTE)) return_error (API, GMT_NOT_A_VALID_MODE);
	API = gmtapi_get_api_ptr (V_API);
	API->error = GMT_NOERROR;

	if (file_ptr == NULL || (file = *file_ptr) == NULL || file[0] == '\0') {
		GMT_Report (API, GMT_MSG_ERROR, "No filename provided\n");
		return_error (V_API, GMT_ARG_IS_NULL);
	}

	if (direction == GMT_OUT) return GMT_NOERROR;

	if (gmt_M_file_is_memory (file)) return GMT_NOERROR;	/* Memory files are always fine */

	if (gmtlib_found_url_for_gdal (file)) {	/* Special URLs for grids to be read via GDAL */
		return GMT_NOERROR;
	}

	if ((mode & GMT_FILE_CHECK) == 0 && gmt_set_unspecified_remote_registration (API, file_ptr))	/* Complete remote filenames without registration information */
		GMT_Report (API, GMT_MSG_DEBUG, "Revised remote file name to %s\n", file_ptr);

	gmt_filename_get (file);	/* Replace any ASCII 29 with spaces (if filename had spaces they may now be ASCII 29) */

	switch (family) {
		case GMT_IS_GRID:
			if (!gmt_file_is_tiled_list (API, file, NULL, NULL, NULL) && (c = strchr (file, '='))) {	/* Got filename=id[+modifiers] */
				/* Nothing*/
			}
			else if (gmt_M_file_is_netcdf (file))	/* Meaning it specifies a layer etc via ?<args> */
				c = strchr (file, '?');
			else {	/* Check for possible file modifiers */
				if ((f = gmt_strrstr (file, ".grd")) || (f = gmt_strrstr (file, ".nc")))
					c = gmtlib_last_valid_file_modifier (API, f, GMT_GRIDFILE_MODIFIERS);
				else
					c = gmtlib_last_valid_file_modifier (API, file, GMT_GRIDFILE_MODIFIERS);
			}
			break;
		case GMT_IS_IMAGE:
			c = strstr (file, "=gd");	/* Got image=gd[+modifiers] */
			break;
		case GMT_IS_PALETTE:
			if ((f = gmt_strrstr (file, GMT_CPT_EXTENSION)))
				c = gmtlib_last_valid_file_modifier (API, f, GMT_CPTFILE_MODIFIERS);
			else
				c = gmtlib_last_valid_file_modifier (API, file, GMT_CPTFILE_MODIFIERS);
			break;
		default:	/* No checks for the other families */
			break;
	}

	if (c && !gmt_M_file_is_url (file)) {	/* Other that queries, we don't want to pass modifiers when copying files */
		was = c[0];
		c[0] = '\0';
	}

	if (gmt_set_remote_and_local_filenames (API->GMT, file, local_path, remote_path, GMT_AUTO_DIR)) {
		GMT_Report (API, GMT_MSG_ERROR, "Cannot find file %s\n", file);
		return_error (V_API, GMT_FILE_NOT_FOUND);
	}

	/* Here we have found a local file or we must download from server first */

	if (remote_path[0]) {	/* Remote file given but not yet stored locally */
		if (mode & GMT_FILE_REMOTE) {
			GMT_Report (API, GMT_MSG_DEBUG, "Download %s to %s\n", remote_path, local_path);
			if (gmt_download_file (API->GMT, file, remote_path, local_path, true)) {
				GMT_Report (API, GMT_MSG_ERROR, "Unable to obtain remote file %s\n", file);
				return_error (V_API, GMT_FILE_NOT_FOUND);
			}
		}
		else {
			GMT_Report (API, GMT_MSG_DEBUG, "Given a remote file %s but mode is not GMT_ADD_REMOTE\n", file);
			return_error (V_API, GMT_FILE_NOT_FOUND);
		}
	}

	if (c) c[0] = was; /* Restore what we did*/
	if ((mode & GMT_FILE_CHECK) == 0) {	/* Pass the local path back */
		GMT_Report (API, GMT_MSG_DEBUG, "Replace file %s with %s\n", file, local_path);
		if (c) /* Also append any file directives via modifiers */
			strncat (local_path, c, PATH_MAX-1);
		gmt_M_str_free (*file_ptr);
		*file_ptr = strdup (local_path);
	}

	return GMT_NOERROR;
}

#ifdef FORTRAN_API
int GMT_Get_FilePath_ (unsigned int *family, unsigned int *direction, unsigned int *mode, char **file, int len) {
	/* FORTRAN version: We pass the global GMT_FORTRAN structure */
	return (GMT_Get_FilePath (GMT_FORTRAN, *family, *direction, *mode, file));
}
#endif

int GMT_Extract_Region (void *V_API, char *file, double wesn[]) {
	FILE *fp = NULL;
	bool found = false;
	struct GMTAPI_CTRL *API = gmtapi_get_api_ptr (V_API);
	char xx1[GMT_LEN64] = {""}, xx2[GMT_LEN64] = {""}, yy1[GMT_LEN64] = {""}, yy2[GMT_LEN64] = {""}, line[GMT_LEN256] = {""};

	if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION);
	if (wesn == NULL) return_error (V_API, GMT_PTR_IS_NULL);

	if (API->GMT->current.setting.run_mode == GMT_MODERN) {	/* Modern mode, file must be NULL */
		GMT_Report (API, GMT_MSG_DEBUG, "GMT_Extract_Region: Modern mode\n");
		if (file) {	/* Cannot specify file in modern mode */
			GMT_Report (API, GMT_MSG_ERROR, "GMT_Extract_Region: Cannot give a PostScript filename in modern mode\n");
			return_error (V_API, GMT_FILE_NOT_FOUND);
		}
		if (gmt_set_psfilename (API->GMT) == 0) {	/* Get hidden file name for current PS */
			GMT_Report (API, GMT_MSG_ERROR, "No hidden PS file found\n");
			return_error (V_API, GMT_FILE_NOT_FOUND);
		}
		GMT_Report (API, GMT_MSG_DEBUG, "Hidden PS file %s found\n", API->GMT->current.ps.filename);
		if ((fp = fopen (API->GMT->current.ps.filename, "r")) == NULL) {
			GMT_Report (API, GMT_MSG_ERROR, "GMT_Extract_Region: Failed to find/open current PS file %s\n", API->GMT->current.ps.filename);
			return_error (V_API, GMT_FILE_NOT_FOUND);
		}
	}
	else {	/* Classic mode, file must be given */
		GMT_Report (API, GMT_MSG_DEBUG, "GMT_Extract_Region: Classic mode\n");
		if (file == NULL) {	/* Must specify file in classic mode */
			GMT_Report (API, GMT_MSG_ERROR, "GMT_Extract_Region: Filename required in classic mode\n");
			return_error (V_API, GMT_FILE_NOT_FOUND);
		}
		if ((fp = fopen (file, "r")) == NULL) {
			GMT_Report (API, GMT_MSG_ERROR, "GMT_Extract_Region: Failed to find/open %s\n", file);
			return_error (V_API, GMT_FILE_NOT_FOUND);
		}
	}

	/* We expect GMT_Extract_Region to be applied to GMT-produced PS files so we know they are clean records readable with fgets */

	while (!found && gmt_fgets (API->GMT, line, GMT_LEN256, fp)) {
		if (!strncmp (&line[2], "PROJ", 4)) {	/* Search for the PROJ tag in the ps file */
			sscanf (&line[8], "%*s %s %s %s %s", xx1, xx2, yy1, yy2);
			wesn[XLO] = atof (xx1);		wesn[XHI] = atof (xx2);
			wesn[YLO] = atof (yy1);		wesn[YHI] = atof (yy2);
			if (wesn[XLO] > 180.0 && wesn[XHI] > 180.0) {
				wesn[XLO] -= 360.0;
				wesn[XHI] -= 360.0;
			}
			found = true;
		}
	}
	fclose (fp);
	if (!found) {
		GMT_Report (API, GMT_MSG_ERROR, "GMT_Extract_Region: Failed to find the PROJ tag with the region\n");
		return_error (V_API, GMT_VALUE_NOT_SET);
	}
	return_error (V_API, GMT_NOERROR);
}

float GMT_Get_Version (void *API, unsigned int *major, unsigned int *minor, unsigned int *patch) {
	/* Return the current lib version as a float, e.g. 6.0, and optionally its constituents.
	 * Either one or all of in *major, *minor, *patch args can be NULL. If they are not, one
	 * gets the corresponding version component. */
	int major_loc, minor_loc, patch_loc;
	gmt_M_unused(API);

	major_loc = GMT_PACKAGE_VERSION_MAJOR;
	minor_loc = GMT_PACKAGE_VERSION_MINOR;
	patch_loc = GMT_PACKAGE_VERSION_PATCH;
	if (major) *major = (unsigned int)major_loc;
	if (minor) *minor = (unsigned int)minor_loc;
	if (patch) *patch = (unsigned int)patch_loc;
	return major_loc + (float)minor_loc / 10;
}

/* Help functions specific to the Julia/GMT API.  They are not documented */

EXTERN_MSC int gmtlib_blind_change_struct(void *V_API, void *ptr, void *what, char *type, size_t off) {
	/* This is a magic backdoor to change static members of API structures that had to be declared as
	   immutables types in Julia and therefore impossible to change from within Julia.
	   *ptr  -> structure pointer whose member identified by the offset 'off' is to be changed.
	   *what -> pointer to the new value of the struct member that will be changed.
	   *type -> string with the type description, using the Julia types names. e.g. 'UInt32' or 'Float64'
	   The offset value 'off' is that obtained with the Julia's fieldoffsets() function, which is
	   equivalent to the 'offsetof()' C macro.
	*/
	if (!strcmp(type, "Int32"))
		*(int *)((char *)ptr + off) = *(int *)what;
	else if (!strcmp(type, "UInt32"))
		*(unsigned int *)((char *)ptr + off) = *(unsigned int *)what;
	else if (!strcmp(type, "Int64"))
		*(int64_t *)((char *)ptr + off) = *(int64_t *)what;
	else if (!strcmp(type, "UInt64"))
		*(uint64_t *)((char *)ptr + off) = *(uint64_t *)what;
	else if (!strcmp(type, "Float32"))
		*(float *)((char *)ptr + off) = *(float *)what;
	else if (!strcmp(type, "Float64"))
		*(double *)((char *)ptr + off) = *(double *)what;
	else if (!strcmp(type, "Int16"))
		*(signed short *)((char *)ptr + off) = *(signed short *)what;
	else if (!strcmp(type, "UInt16"))
		*(unsigned short *)((char *)ptr + off) = *(unsigned short *)what;
	else if (!strcmp(type, "UInt8"))
		*(unsigned char *)((char *)ptr + off) = *(unsigned char *)what;
	else if (!strcmp(type, "Int8"))
		*(char *)((char *)ptr + off) = *(char *)what;
	else {
		GMT_Report(V_API, GMT_MSG_ERROR, "GMT/Julia Backdoor: Type (%s) not accepted. Possibly a pointer to something.\n", type);
		return_error (V_API, GMT_NOT_A_VALID_PARAMETER);
	}
	return GMT_NOERROR;
}

EXTERN_MSC void * gmtlib_get_ctrl (void *V_API) {
	/* For external environments that need to get the GMT pointer for calling
	 * lower-level GMT library functions that expects the GMT pointer */
	struct GMTAPI_CTRL *API = NULL;

	if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION);
	API = gmtapi_get_api_ptr (V_API);
	return API->GMT;	/* Pass back the GMT ctrl pointer as void pointer */
}

int64_t gmt_eliminate_duplicates (struct GMTAPI_CTRL *API, struct GMT_DATASET *D, uint64_t cols[], uint64_t ncols, bool text) {
	/* Scan dataset per segment and eliminate any duplicate records as identified by having no change in all the specified cols.
	 * If no change then we skip the duplicate records.  No segment will be eliminated since first record always survives.
	 * Including the trailing text in the comparison is optional and requires setting of the text flag to true. */
	bool may_be_duplicate;	/* Initially true, gets set to false if we fail any of the one or more tests */
	uint64_t tbl, seg, row, last_row, k, n_dup_seg, n_dup = 0;
	int64_t n_skip;	/* Number of consecutive duplicate rows */
	unsigned int mode;
	struct GMT_DATASEGMENT *S = NULL;

	if (ncols == 0 || cols == NULL) {
		gmtlib_report_error (API, GMT_N_COLS_NOT_SET);
		return -GMT_N_COLS_NOT_SET;
	}
	for (k = 0; k < ncols; k++) if (cols[k] >= D->n_columns) {
		gmtlib_report_error (API, GMT_DIM_TOO_LARGE);
		return -GMT_DIM_TOO_LARGE;
	}

	for (tbl = 0; tbl < D->n_tables; tbl++) {	/* Examine each table */
		for (seg = 0; seg < D->table[tbl]->n_segments; seg++) {	/* Examine each segment */
			S = D->table[tbl]->segment[seg];	/* Current segment shorthand */
			mode = (S->text) ? GMT_WITH_STRINGS : GMT_NO_STRINGS;
			last_row = 0;	/* Always keep the first row of any segment */
			n_dup_seg = 0;	/* None yet found in this segment */
			row = 0;
			while (row < (S->n_rows-1)) {	/* Since we increase row inside we must stop this loop at one less */
				n_skip = -1;	/* Since incremented before the test */
				do {	/* Check if this row is same as last, for given cols */
					row++;	/* Advance to next record */
					n_skip++;	/* So now it is 0 the very first time */
					may_be_duplicate = true;	/* See if we can fail a test */
					for (k = 0; may_be_duplicate && k < ncols; k++) {	/* Check the columns indicated as long as the records may be duplicates */
						if (!doubleAlmostEqualZero (S->data[cols[k]][row], S->data[cols[k]][last_row]))
							may_be_duplicate = false;	/* Failed to match across these two rows for this column */
					}
					if (may_be_duplicate && text && mode && S->text[row] && S->text[last_row] && strcmp (S->text[row], S->text[last_row]))
						may_be_duplicate = false;	/* Failed to match across these two rows for trailing text */
				} while (may_be_duplicate && row < S->n_rows);

				if (n_skip) {	/* Must move up all memory and bury this repeat record */
					for (k = 0; k < S->n_columns; k++)
						memmove (&S->data[k][row-n_skip], &S->data[k][row], (S->n_rows-row)*sizeof(double));
					if (mode & GMT_WITH_STRINGS)
						memmove (&S->text[row-n_skip], &S->text[row], (S->n_rows-row)*sizeof(char *));
					S->n_rows -= n_skip;	/* Since we lost records */
					n_dup_seg += n_skip;
					row -= n_skip;
				}
				last_row++;
			}
			if (n_dup_seg) {	/* Found duplicates, need  to reallocate arrays */
				GMT_Report (API, GMT_MSG_DEBUG, "Removed %" PRIu64 " duplicate records from table %" PRIu64", segment %" PRIu64"\n", n_dup_seg, tbl, seg);
				if (gmt_alloc_segment (API->GMT, S, S->n_rows, S->n_columns, mode, false))
					return -GMT_RUNTIME_ERROR;	/* Failure of some sort */
				n_dup += n_dup_seg;
			}
		}
	}
	if (n_dup) {
		gmt_set_dataset_minmax (API->GMT, D);	/* Update min/max for each column */
		GMT_Report (API, GMT_MSG_DEBUG, "Removed %" PRIu64 " duplicate records from the entire dataset\n", n_dup);
	}

	return (n_dup);
}
