From acd6d434ea53d57efbd2609251bac24d1311a878 Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 28 Jan 2026 20:42:41 +0800 Subject: [PATCH 01/12] add GMT_IS_IMAGE case --- src/gmt_init.c | 15 +++++++++++++++ test/grdimage/subplot_images.sh | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100755 test/grdimage/subplot_images.sh diff --git a/src/gmt_init.c b/src/gmt_init.c index 6992c4f16a7..5376a0bb145 100644 --- a/src/gmt_init.c +++ b/src/gmt_init.c @@ -14883,6 +14883,7 @@ GMT_LOCAL int gmtinit_get_region_from_data (struct GMTAPI_CTRL *API, int family, char virt_file[GMT_VF_LEN] = {""}, tmpfile[PATH_MAX] = {""}, *list = "bfi:", *file = NULL; struct GMT_GRID_HEADER_HIDDEN *HH = NULL; + struct GMT_IMAGE *I = NULL; switch (family) { case GMT_IS_GRID: if ((opt = GMT_Find_Option (API, GMT_OPT_INFILE, *options)) == NULL) return GMT_NO_INPUT; /* Got no input argument*/ @@ -14903,6 +14904,20 @@ GMT_LOCAL int gmtinit_get_region_from_data (struct GMTAPI_CTRL *API, int family, } break; + case GMT_IS_IMAGE: + if ((opt = GMT_Find_Option (API, GMT_OPT_INFILE, *options)) == NULL) return GMT_NO_INPUT; /* Got no input argument*/ + file = opt->arg; + if (gmt_access (API->GMT, file, R_OK)) return GMT_FILE_NOT_FOUND; /* No such file found */ + if ((I = GMT_Read_Data (API, GMT_IS_IMAGE, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_ONLY|GMT_GRID_IS_IMAGE|GMT_IO_RESET, NULL, file, NULL)) == NULL) + return API->error; /* Failure to read image header */ + gmt_M_memcpy (wesn, I->header->wesn, 4, double); /* Copy over the image region */ + HH = gmt_get_H_hidden (I->header); + if (!exact) gmt_round_wesn (wesn, HH->grdtype > 0); /* Use image w/e/s/n to round to nearest reasonable multiples */ + if (I->header->x_units[0] && I->header->y_units[0] && !strcmp (I->header->x_units, I->header->y_units)) /* Want constant scale */ + *aspect = ((I->header->wesn[XHI]-I->header->wesn[XLO]) >= (I->header->wesn[YHI]-I->header->wesn[YLO])) ? 1 : -1; + if (GMT_Destroy_Data (API, &I) != GMT_NOERROR) return API->error; /* Failure to destroy the temporary image structure */ + break; + case GMT_IS_DATASET: for (opt = *options; opt; opt = opt->next) { /* Loop over all options */ if (opt->option != GMT_OPT_INFILE) continue; /* Look for input files we can append to new list */ diff --git a/test/grdimage/subplot_images.sh b/test/grdimage/subplot_images.sh new file mode 100755 index 00000000000..263909882fa --- /dev/null +++ b/test/grdimage/subplot_images.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# Test subplot with two pre-loaded images + +gmt begin subplot_images ps + gmt grdmath -R0/10/0/10 -I0.1 X Y MUL = grid1.grd + gmt grdmath -R0/10/0/10 -I0.1 X Y ADD = grid2.grd + + gmt grdimage grid1.grd -Aimg1.tif -Cjet -JX10c/10c -Q + gmt grdimage grid2.grd -Aimg2.tif -Cjet -JX10c/10c -Q + + gmt subplot begin 2x1 -Fs10c/10c -A+gwhite+p0.5p + gmt subplot set 0 -A"Image 1" + gmt grdimage img1.tif -Cjet + gmt subplot set 1 -A"Image 2" + gmt grdimage img2.tif -Cjet + gmt subplot end + rm -f grid1.grd grid2.grd img1.tif img2.tif +gmt end show From 4c3c59802753c577e705e745f9383848e2336f96 Mon Sep 17 00:00:00 2001 From: chuan Date: Fri, 30 Jan 2026 21:24:15 +0800 Subject: [PATCH 02/12] Fix issue #8878: Support pre-loaded images in subplot - Modified gmtinit_set_missing_R_from_grid to detect image files by extension and use GMT_IS_IMAGE instead of hardcoded GMT_IS_GRID - Modified gmtapi_import_image to skip padding checks when GMT_CONTAINER_ONLY is set, allowing pre-loaded images to be imported for header reading - This fixes the failure when using multiple pre-loaded images in Julia subplots --- .gitignore | 1 + grid1.grd | Bin 0 -> 43092 bytes grid2.grd | Bin 0 -> 43092 bytes img1.tif | Bin 0 -> 13564 bytes img2.tif | Bin 0 -> 3563 bytes src/gmt_api.c | 2 +- src/gmt_init.c | 16 ++++++++++++++-- subplot_images.ps | Bin 0 -> 43388 bytes 8 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 grid1.grd create mode 100644 grid2.grd create mode 100644 img1.tif create mode 100644 img2.tif create mode 100644 subplot_images.ps diff --git a/.gitignore b/.gitignore index 9579a3846a8..bead8b7c198 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ doc/examples/**/*.gif .vscode cmake/ConfigUser.cmake.orig cmake/ConfigUserAdvanced.cmake.orig +/install/ \ No newline at end of file diff --git a/grid1.grd b/grid1.grd new file mode 100644 index 0000000000000000000000000000000000000000..3314e4829d26d3efa8c6e3cc7b61fd1fc6683e53 GIT binary patch literal 43092 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 ta%u literal 0 HcmV?d00001 diff --git a/grid2.grd b/grid2.grd new file mode 100644 index 0000000000000000000000000000000000000000..ece9d245a184fe3b168905be71b4c7725f3572b0 GIT binary patch literal 43092 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 literal 0 HcmV?d00001 diff --git a/img1.tif b/img1.tif new file mode 100644 index 0000000000000000000000000000000000000000..ff2ec142b2222abbac4f440dccd676f977db0dc9 GIT binary patch literal 13564 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≦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 literal 0 HcmV?d00001 diff --git a/img2.tif b/img2.tif new file mode 100644 index 0000000000000000000000000000000000000000..581118a6d1506fa73c37a68213b75dd6af81873b GIT binary patch literal 3563 zcmbW14Lp?D9>+&*C^RXnB_$fBjXUOznW-@)q`5OFL@$;mks>c+ijcOoqn#~e+)NB} zuT-*OQCh6gWh7H}QcA6)W}7H1c8Vm`p7S()uH4=}Ju~Nde$W5>{{Qnl-*cYBWG+GJ zqEIM96iRC3JjV&zOQT^;Z=hj~*}yb2SsyhO=&orf)OX+OBV&yK{q6B7s77Ei1)lvt z!#q4_@X*!#wt>C?&^Cj%1vGen6ZBhy2Ts*Nq58nfux6l8C3+~7kpXyj&5!Qp;>30j z=5e-icpS!zg$wCSUhvL^5uBaDj+Wj&PCiU;AEvW+pu5w0ru#Z~%Y`wVol)VS4{ymJ z+S}9Zp#4L6u1+3rB@rkNI0^|*Arc(OG&1$$1(+WHH}J=Y_4aXbarJ#`U@8GeqTul) z0+C82kv?9+*NM$^30&jq4CgFAb&J-wJ7KCWJCCZ;UpMnYymk>Qct z?Ge0SZj>csUBuSlknIt{Ti;#jL+lBk!yY6NXjB~8fl8q|5U5oAdtPDUz?0d78XpHY znLS9NkZ|@SJb{GAnft`N$#VzN@Si(dAeVrDPxdbIS7c-Sz$pju7oA70 zCYTj(tZ})2UNv-Hv>By+&rgUE2r*3fUyPhF+2pEvgNUe1`Bq|f|2=I=HrpELYw(u- zA{AZNV@G%Y_eRqW2Cud+zU}c(gBSjLCLg;fy=eivQ(Qxe2^(l?s61g~BWlNm=RW6# z^j%j7q(M4vx(CY8zm^%il35qame|THTV%Fu8D@NX0bW136)kNw$ZMTh(yE_2WY{I! z6lY+kq|x~ohh`1PzNs>xD3|*4FGm?FWE-dkSS8hr-)K?p*+2Vsqu-Xy^;lm)@Rp3D z*bu=!!FfUSmVbR^tu078CK}BfJu;d-nlZ{IpC$LP*4mkO44)_y#paE9-MTD}*!y+= zgLOaG8I7oq1qRMY*yC`@jxVK~w2K-77NqvFICkhJXivz5_Pp)T-d+ss+@JtpuRaIu z{u`hjR|4&G+o3&14DFQCG#e|3Ib+KbfVtHZFkJkaB+dgc649pJj2Jb(tQ^c-??3NW>;8GQ-G|#vcLu_Qs8DO zltyXFSxGWn_=NcwN4Mdp-^b68;dNiM>fITdzchdT#(a~yhHay|gUWx@rig~jBn?4v zx=)q3&J=O0?oH(q|CED=saOv|&>x3yVN2(edjV&^G)RI~2ubi`0644bfD+diV3RZ9 ztsCq&gF*FaKvW24gmn%={2fx=S^|SdU@!{?Hz2`M7|ez>&Cyz20%Q>bv#_{1C%ieUzgaRaoJteUq0JeO z-mO$!yLu!!YDVb9}uLJpUlY0Kes=vu2kZ= zZ5@%AzCt6p9k*yyd0y{b@BkuChP< zl`w|N52Aj57x^m0WiwBg=^w^$p*FSVhtWy+O~ao^F1vkfQcr ztWG8~51wTnNEG58WiU(*TbN`JvJZd#%$h{R>Q!KDDy-*KSS?etMpP@(R4!57s?MwY zWLAP-lK<`M@?Bx~6&=zeI;?bKNsFU+&tDI2To(&sgUnZ%rsQaE9P5Z*TElqJcH?SW z`%Pji`8rA%w`F7gWuXaOHg#Zc&@`b*NvW%GVH7t!zkJUV#rT0OrY2D|MgrZ7q$ zY)I!hbI)<+v~#}9;uv;wjO!L%IbLTpEW|_C2CYnJ5_-cR4ek;`XDENa3FOPE_k9OeBZPx2k!^S~VE~#boaY}^KTQNz z?|lC#WDR5Tz#aW5g)u5QKZMyN_oxt=Vce$EEDBYJax?X~%+hB2YKvb~X`Pum>eZ5F z(jJSSRjIX^Xf@OHIJ0gwoCzK?{fYcrefbaL)^mtf|C+Za x?U_RljV}Bxb*5j9|HJ5$R|HyaDP~`lZ)-`LzUgH6(ee<*k5+nJb!Uck{syAbg^U0I literal 0 HcmV?d00001 diff --git a/src/gmt_api.c b/src/gmt_api.c index dd548cf9f8b..a6c870a5801 100644 --- a/src/gmt_api.c +++ b/src/gmt_api.c @@ -4924,7 +4924,7 @@ GMT_LOCAL struct GMT_IMAGE *gmtapi_import_image (struct GMTAPI_CTRL *API, int ob 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); + if (IH->alloc_mode == GMT_ALLOC_EXTERNALLY && !(mode & GMT_CONTAINER_ONLY)) 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); diff --git a/src/gmt_init.c b/src/gmt_init.c index 5376a0bb145..49438a36908 100644 --- a/src/gmt_init.c +++ b/src/gmt_init.c @@ -15056,11 +15056,23 @@ GMT_LOCAL int gmtinit_set_missing_R_from_grid (struct GMTAPI_CTRL *API, const ch struct GMT_OPTION *opt = NULL; double wesn[4] = {0.0, 0.0, 0.0, 0.0}; char region[GMT_LEN256] = {""}; - int err = GMT_NOERROR; + int err = GMT_NOERROR, family = GMT_IS_GRID; gmt_M_unused(args); /* Here we know the module is using a grid to get -R implicitly */ - if ((err = gmtinit_get_region_from_data (API, GMT_IS_GRID, exact, options, wesn, &API->GMT->common.R.aspect))) + /* First check if the input file is an image */ + if ((opt = GMT_Find_Option (API, GMT_OPT_INFILE, *options)) != NULL) { + char *ext = strrchr (opt->arg, '.'); + if (ext) { + /* Check for common image extensions */ + if (strcmp (ext, ".tif") == 0 || strcmp (ext, ".tiff") == 0 || strcmp (ext, ".png") == 0 || + strcmp (ext, ".jpg") == 0 || strcmp (ext, ".jpeg") == 0 || strcmp (ext, ".bmp") == 0 || + strcmp (ext, ".gif") == 0) { + family = GMT_IS_IMAGE; + } + } + } + if ((err = gmtinit_get_region_from_data (API, family, exact, options, wesn, &API->GMT->common.R.aspect))) return err; snprintf (region, GMT_LEN256, "%.16g/%.16g/%.16g/%.16g", wesn[XLO], wesn[XHI], wesn[YLO], wesn[YHI]); diff --git a/subplot_images.ps b/subplot_images.ps new file mode 100644 index 0000000000000000000000000000000000000000..35f4420bead615bdbdc5a355c23fbe6e154a1244 GIT binary patch literal 43388 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 literal 0 HcmV?d00001 From f62ad45ad6841fa42d975f1adee58d997385d768 Mon Sep 17 00:00:00 2001 From: chuan Date: Sun, 15 Feb 2026 17:29:34 +0800 Subject: [PATCH 03/12] The loaded image object is accessed directly when retrieving the region. --- src/gmt_init.c | 56 +++++++++++++++++++++++++++++++++-------------- src/gmt_private.h | 1 + 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/gmt_init.c b/src/gmt_init.c index 9ba9ef62ed7..182e31c94ae 100644 --- a/src/gmt_init.c +++ b/src/gmt_init.c @@ -14884,7 +14884,6 @@ GMT_LOCAL int gmtinit_get_region_from_data(struct GMTAPI_CTRL *API, int family, char virt_file[GMT_VF_LEN] = {""}, tmpfile[PATH_MAX] = {""}, *list = "bfi:", *file = NULL; struct GMT_GRID_HEADER_HIDDEN *HH = NULL; - struct GMT_IMAGE *I = NULL; switch (family) { case GMT_IS_GRID: if ((opt = GMT_Find_Option(API, GMT_OPT_INFILE, *options)) == NULL) return GMT_NO_INPUT; /* Got no input argument*/ @@ -14949,15 +14948,34 @@ GMT_LOCAL int gmtinit_get_region_from_data(struct GMTAPI_CTRL *API, int family, case GMT_IS_IMAGE: if ((opt = GMT_Find_Option (API, GMT_OPT_INFILE, *options)) == NULL) return GMT_NO_INPUT; /* Got no input argument*/ file = opt->arg; - if (gmt_access (API->GMT, file, R_OK)) return GMT_FILE_NOT_FOUND; /* No such file found */ - if ((I = GMT_Read_Data (API, GMT_IS_IMAGE, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_ONLY|GMT_GRID_IS_IMAGE|GMT_IO_RESET, NULL, file, NULL)) == NULL) - return API->error; /* Failure to read image header */ - gmt_M_memcpy (wesn, I->header->wesn, 4, double); /* Copy over the image region */ - HH = gmt_get_H_hidden (I->header); - if (!exact) gmt_round_wesn (wesn, HH->grdtype > 0); /* Use image w/e/s/n to round to nearest reasonable multiples */ - if (I->header->x_units[0] && I->header->y_units[0] && !strcmp (I->header->x_units, I->header->y_units)) /* Want constant scale */ - *aspect = ((I->header->wesn[XHI]-I->header->wesn[XLO]) >= (I->header->wesn[YHI]-I->header->wesn[YLO])) ? 1 : -1; - if (GMT_Destroy_Data (API, &I) != GMT_NOERROR) return API->error; /* Failure to destroy the temporary image structure */ + if (gmt_M_file_is_memory (file)) { + /* For memory files, directly access the already-loaded image object */ + int object_ID, obj_item; + struct GMTAPI_DATA_OBJECT *S_obj = NULL; + if (sscanf (&file[GMTAPI_OBJECT_ID_START], "%d", &object_ID) != 1) return GMT_OBJECT_NOT_FOUND; + if ((obj_item = gmtlib_validate_id (API, GMT_NOTSET, object_ID, GMT_NOTSET, GMT_NOTSET)) == GMT_NOTSET) return GMT_OBJECT_NOT_FOUND; + S_obj = API->object[obj_item]; + if (S_obj == NULL || S_obj->resource == NULL) return GMT_PTR_IS_NULL; + I = (struct GMT_IMAGE *)S_obj->resource; + gmt_M_memcpy (wesn, I->header->wesn, 4, double); /* Copy over the image region */ + HH = gmt_get_H_hidden (I->header); + if (!exact) gmt_round_wesn (wesn, HH->grdtype > 0); /* Use image w/e/s/n to round to nearest reasonable multiples */ + if (I->header->x_units[0] && I->header->y_units[0] && !strcmp (I->header->x_units, I->header->y_units)) /* Want constant scale */ + *aspect = ((I->header->wesn[XHI]-I->header->wesn[XLO]) >= (I->header->wesn[YHI]-I->header->wesn[YLO])) ? 1 : -1; + /* Don't destroy the image - it's a reference to the original */ + } + else { + /* For regular files, read the image header */ + if (gmt_access (API->GMT, file, R_OK)) return GMT_FILE_NOT_FOUND; /* No such file found */ + if ((I = GMT_Read_Data (API, GMT_IS_IMAGE, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_ONLY|GMT_GRID_IS_IMAGE|GMT_IO_RESET, NULL, file, NULL)) == NULL) + return API->error; /* Failure to read image header */ + gmt_M_memcpy (wesn, I->header->wesn, 4, double); /* Copy over the image region */ + HH = gmt_get_H_hidden (I->header); + if (!exact) gmt_round_wesn (wesn, HH->grdtype > 0); /* Use image w/e/s/n to round to nearest reasonable multiples */ + if (I->header->x_units[0] && I->header->y_units[0] && !strcmp (I->header->x_units, I->header->y_units)) /* Want constant scale */ + *aspect = ((I->header->wesn[XHI]-I->header->wesn[XLO]) >= (I->header->wesn[YHI]-I->header->wesn[YLO])) ? 1 : -1; + if (GMT_Destroy_Data (API, &I) != GMT_NOERROR) return API->error; /* Failure to destroy the temporary image structure */ + } break; case GMT_IS_DATASET: @@ -15104,13 +15122,19 @@ GMT_LOCAL int gmtinit_set_missing_R_from_grid (struct GMTAPI_CTRL *API, const ch /* Here we know the module is using a grid to get -R implicitly */ /* First check if the input file is an image */ if ((opt = GMT_Find_Option (API, GMT_OPT_INFILE, *options)) != NULL) { - char *ext = strrchr (opt->arg, '.'); - if (ext) { + /* Check for pre-loaded image in memory (virtual filename has family code 'I') */ + if (gmt_M_memfile_is_image (opt->arg)) { + family = GMT_IS_IMAGE; + } + else { /* Check for common image extensions */ - if (strcmp (ext, ".tif") == 0 || strcmp (ext, ".tiff") == 0 || strcmp (ext, ".png") == 0 || - strcmp (ext, ".jpg") == 0 || strcmp (ext, ".jpeg") == 0 || strcmp (ext, ".bmp") == 0 || - strcmp (ext, ".gif") == 0) { - family = GMT_IS_IMAGE; + char *ext = strrchr (opt->arg, '.'); + if (ext) { + if (strcmp (ext, ".tif") == 0 || strcmp (ext, ".tiff") == 0 || strcmp (ext, ".png") == 0 || + strcmp (ext, ".jpg") == 0 || strcmp (ext, ".jpeg") == 0 || strcmp (ext, ".bmp") == 0 || + strcmp (ext, ".gif") == 0) { + family = GMT_IS_IMAGE; + } } } } diff --git a/src/gmt_private.h b/src/gmt_private.h index 628675ee9f5..bde87e617dd 100644 --- a/src/gmt_private.h +++ b/src/gmt_private.h @@ -247,6 +247,7 @@ struct GMTAPI_CTRL { #define GMTAPI_OBJECT_ID_START 21U /* Start position of the encoded object ID in the virtual filename */ #define gmt_M_file_is_memory(file) (file && !strncmp (file, "@GMTAPI@-", GMTAPI_PREFIX_LEN) && strlen (file) == GMTAPI_MEMFILE_LEN) #define gmt_M_memfile_is_grid(file) (gmt_M_file_is_memory(file) && file[GMTAPI_OBJECT_FAMILY_START] == 'G') +#define gmt_M_memfile_is_image(file) (gmt_M_file_is_memory(file) && file[GMTAPI_OBJECT_FAMILY_START] == 'I') #define gmt_M_memfile_is_cube(file) (gmt_M_file_is_memory(file) && file[GMTAPI_OBJECT_FAMILY_START] == 'U') #ifdef __cplusplus From c1047cb19980dfc2a7cdee68badc0faea92d4889 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 15 Feb 2026 17:33:34 +0800 Subject: [PATCH 04/12] Delete .gitignore --- .gitignore | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index d11dcda256b..00000000000 --- a/.gitignore +++ /dev/null @@ -1,21 +0,0 @@ -build -?build -?build-mp -Testing -.*~ -cmake/ConfigUser.cmake -cmake/ConfigUserAdvanced.cmake -share/coast -share/dcw -src/newsuppl* -**/gmt.history -.DS_Store -doc/examples/**/*.mp4 -doc/examples/**/*.png -doc/examples/**/*.gif -test/**/*.ps -!test/**/*.ps.dvc -.vscode -cmake/ConfigUser.cmake.orig -cmake/ConfigUserAdvanced.cmake.orig -/install/ \ No newline at end of file From 0b2c84314b943408d6e108caa6ff8f2f674b9e7d Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 15 Feb 2026 17:33:48 +0800 Subject: [PATCH 05/12] Delete grid1.grd --- grid1.grd | Bin 43092 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 grid1.grd diff --git a/grid1.grd b/grid1.grd deleted file mode 100644 index 3314e4829d26d3efa8c6e3cc7b61fd1fc6683e53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43092 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 ta%u From 1a1ccd6c8ace46e0795235813a78ff30dd922c9b Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 15 Feb 2026 17:34:03 +0800 Subject: [PATCH 06/12] Delete grid2.grd --- grid2.grd | Bin 43092 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 grid2.grd diff --git a/grid2.grd b/grid2.grd deleted file mode 100644 index ece9d245a184fe3b168905be71b4c7725f3572b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43092 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 From c8765fab9bce2a5043fec272befa68edfc4ac56f Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 15 Feb 2026 17:34:25 +0800 Subject: [PATCH 07/12] Delete subplot_images.ps --- subplot_images.ps | Bin 43388 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 subplot_images.ps diff --git a/subplot_images.ps b/subplot_images.ps deleted file mode 100644 index 35f4420bead615bdbdc5a355c23fbe6e154a1244..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43388 zcmdqKce~=c&N%w_;Zx8%B%$}-d+!he2@pyG3G6fLo#%Y_D?15g&CK5KuipFIHN(QO zWLcJFS(Y6yi=or^WLa^;dHh~dXR+kOhrsf}J}3T&=}Gt(j0W|QfDb?wzQ@VGgL_H% zK}a|Om;?TP=3@GCbD;k#GRqJf=<<)!-tz9`OW-4Mx~Mm_66v{^HItJ!-dB z3zTJI`uv$=gm8{W?6oBxIA-oZIHvE2(p_S3eg_Bu7Zjir1vW2UP{U++%5i&M=t$yO z-0OTnQqS_+yMBW*b%a6xbHfX)p5=D`(Q7FkVbcFRi;R+O&=FZZA#?Y=#C+*ZIwFUk zSVlger#ya=_M0tKAfcRso{-MGz}-dkRMh`uA6LD6m!O0H&9nD|{wiMo15`_TV#lA) z2&oK3E+PmH7pY*92l+*^igT{a{mw}ODp1E6+soJKS#3j4ylQRgi5I<=o)EZyLut%# zM^9|eqReT}J*Zm*d2)zuAL!mC@q`}+dIE$i$Y&Ke)DwG&lR-Bycm$6!^A}E!F+Zq& zrHMdls{gqI(PFLOerykVdCQzR{0PEC-$)2ED0mS6jp{CS^gr*?gVS#hjD)${LCtqg zKxu^A9O{2w7!c$SJy1|mIKQv|N%Wh{U9Up74@rW~SD#SdqS8J6PmJ_Y7Ib*0|B3li z@k)r=ZmY)-Vkhf=j`Z+Y(hvWG)dcli7oh;;nbRgAM}(Z#D~nRII{N-Iv(yHu$a+ym z9Q7If&!)cF*CPcX^_YpWp7e%&p z+_R{iAR%b#Qxc@Uj2nrXk3OmZ3a>VEh$?P%Ptj2zOzNpO@-GhdYLEGR-r!U0YR7rh zOz6N+&>sp#LP6Ytn&3nFpPs<0RN=|uXJ;MMDwJYnXswTYR)rP71Fe2)#2={eZ&Z{% zR=LDwDR0>CBZFjwRCG*ZQK<^)!>h?hm6B4U#|QTFFTGC>L!0}dS0Bs&pj|I&!W&;t zmG}5V&>+f7g@eH`u=Oc7KgjJ}=l{^+>z|YZ##;*y1F$bm{m+-&)zQ&8aA44W5?@=o z-%%C)$6H!rSpFevFi?1+-=UFd(*Ij2iR?L)*K&8vS>}`(h2L?IR*MKCjqUv}r8L0r z3StWj1Qdo87JqByu%+IK(nYT{gnr1J^xm3R29SHTiTs1ne9=S7feeviB-ICoW%cq+ zy2Iq5iRYsNB#rZrHwj^>oqy1x#P9VA5Sd<#_w~yCQUr&ksbP~|n6$s)s(Js>KSlhF z4_t*p7csB!^TFb83aE@c3w(VPQ04xdKR}L9i`IKT;StmTe(Pbrz+V8xdZJ{iov4t+ zJ*gxI`WCi0-}rf1H>5R~o+Z75d8yahOoyVHH|@jJ&{MgQXdu969w)se#`aBIjx$!C`zoX zpDR6x)fgq!zCgB_eCaP1;ZjeO9>Ny6gixDCN6XUqDTo(_7xZ(m?NA_3>gN<#d7P&7 zb1W(>IwVR!XaO*lg8wQ#NUI8PVNS?l%0V?35{K1g#Ia4CByom-S!6$+l=}#`p z9Z~x3L@86`pQbr=N<~y(Gt)@_Q>~(j>q8y_`iHPoiT{OAKvK{Acbq5Izx6+T`mK2V z0>mH;1njH4c_}}B*Yx10*P?cg(t6EHQyiHBxRc0v{6b_9!gCr`#fGfcTfTNa#T+Qu&)APgI9sxc{k!7shYN zKfxE!NmXahFrHBL$aZNf)lR=b|G*QsIuWCcuUCeS7=UA?5*X-TP~;^LuSdM3dJ%g< z71{`j#ETFljuW;b3vStS4T+?4=7c@0V&7Kqdi_sI^+kzN6^#^-BhdM&Dv4%-{{!I( ziNW(I7A{dl^wIQ$nNsIdOqf&R0kD?Ei&Y?`)7av!GL0h#c%H|cqAVFNpYd84>W29O zLl7}~3!$AmSOfTto&j@vLe7kaBwpW9U_2n*RN!dl`)Da%DPJMXp)Ndu28mX}I52@z zXaT-uu80hGf3C(gJd|-1VG5)$@`Z;P?99WLe!f&F;h=^ZD?l8c)xgXd1!fdy4zjUq zKS2~0yyCz@)k+M3VUG_PVoTT`hFl16NLL7h+)<~X&xc%**pZ{8NDL{#Q#ME@s8Y@I z*RWm^Md)wx#sz@jTLFae>MJ#j@nisdyRd>ljYgQcCe28yZs3gmx98IH85QwD>yQJm z6$R*Fh)$ia2j(R&Y}?+*BJ>UDGJ8QPjsOaIl&^^HlMupCD*Sy#{{bF;>WM46yD-w} z-RYjgO1_C}P+LUgvu_m=TLwQV#ECw4AE1EGm4Wcd_Dio4#HIk%p*l8bG*tWT2E^b5 z%0)j%(p*P)1@R8r`>+4C#Q%rsY#!$`cmLPF2=095uQD(H3&qG-4hbGIFK7fIsGufx zJUso^mXSn=He!bvL4Rz|%mos+hj{1M&G}!1@VIO^N#^&$zX%qB8;k_tD+iHP;r=gz zxj<581Xe8qYpBCrN8o;fm>OWXwa`4H+rvRTKd`j>s>xc?`RpC>E zPaQrD_%z|uf=?T!3i#^6rw5-td{9gnFB@UNp9e?)x$3&MJy-y-s=d|_o&B@@HxQ;H5+v_ z5{w8P246S$Adw=;pw>U&^A~*nts{_dzu@y<@Im}?8n*}^XS}m{=g%w$bX4VPBeAi} zeh(7X5?HkwN=u;=fg}<;aQfiSGE9D;%Sd_pm;{u1=Bo-OqNYt*9s(p-)n^*wS)&Q6 zbU%oXH{FCfH;fF7Y09nCp*^XTSrNKImU*NV5JQG)nC;_^z7^^3&fE`>4{joeklDfj z0^mjh-Ggr^&TP@x&=D{0a22>&3IizOjtFurm}W+02*ZK9!$a6J(&KVw9v}g656X`^ z?6)J8g`E#X2ZkcZ8_$|RnNXaD?H6T1?*D5?#UVKDDe1oJz&Zb>{g(9IWPcSL@aP5} z@DP4IYVy4xg#&ZJfZ(qONMd7v8i(lXUg83Yyh;Ph7e?MbkPq?3v(_L1jd2MNZYXwO z&M*bw`qgYOliHw1!h|gqt=qF2U)lGL&uTrNeRXeE?ayox8jx3e6*gEjN=1!Q@tsof zMWCu-RyEA(J7!hG1e$j=kXsrPZLMHiE7*Q7*nZ+^6rh(L=R1Erf9Z&}HdKZhm7zvu z_)cZ`Dm>K?1_x0>ngwI}0@ixmNDDtc~{>Ziz?DxtliXPze~o>>*(`v9~zXCB6p3WLD-w?dzA*a;y9{DqS}JqUdX zYAJ{`sJU;|z}|$KSq0~n->SIbXEwt907U?l2j>8EL`9Cia^712i;X9y=aWGSHCrWu zi+ls4#`nK28}Y1GL7^sR{{j!Y%t$N;WyF&PLaqG<`bG}5{0*Z3CRi(s+W!sotuPV= zstyF{c`u3N@Sgu6BnrtygEo-Aq*e%9I^O^v<$Ry|&;l)jJyJzgVE_KT+ZEWmzHGu2 zCmkfs4g)(cydZus0cQ%{ARmSNNR-b)Zy!B z5)vaqDuAo8VeYcpM}8!>-q!=BWe@IlPjQ&{R5U0O*)PzM21U{;i}wS+&E;}xt)JzQ zxPFWAq=6*WAM{0Guh#Dw3Vb=|84gO3Cm#VMwf>+4i+uSXFp&`Zg9^vrc~6hqKVW_0 z`7KF2WCwLPlJO6SNW6dKP-_S5ZT|y@PfR57zvJ+Uh=kw&8xB=OwB|;k$~*T1M{e|> z=r|gKjrcnhExzBMKVmiz9Qb0nD+H}@*hz`(Kqex4z$>V4030&;#1(ft2n>9~gE+yr z9K?9V+I0xnA88#zj28|#me3IH7#SlxH%S$=EyVn_u7guH*N@5+&ebzIFBV~5eXkxKzgLcHdXdGoYmZ938U;-2 zy-x^@7$*A(L7D;uQCJmk13+`t$Q6^eM>|ezX)WL^~?^ITkm7e;J~DCg!PS^Z*G39c{x$wiJ6p>GyE{4=pDDVqhOiRS1l!AiQKv;m-a*QBIF( zWMNW$o|t=*8tKu%LK?w8^+Z3w-0QK%fkHtHR^L;zNNmwyGfsU*wgWE#O6q1klD|?T z97IE5PV|Gj10`PQaCA!rKKI|R5KX_sMphH7A13kb{F)5bONA&=aD;@EPu~X!E>Q%- zQda5Wm^iBRt=jeno65nqU#k5L@jo;4chzdb!aGCSDFdatr?6FK|7lRdX+dpJXy2%2 zr9)riQZb9jxZ}kcpwee%!<}hmc9eC`n7p6>WEuxMXbN|Ymie)Bf+?xxKe3eMn4)s% zj2G%%jf6%Q+6StAmCy9)Y0Yt)aUNz4ms8k8+OOb(-anNT(N*Be9ug*o5Vn7zNr6+& zhoEu_JsHk?qB5FGpbd&wo~+jL%F$mRci;=}Z0Xe51wpZiV6pR}N=&D=2hRM;?@hv0 zUe6F{L=!!(0HgTy5Nv?ip1Y|H!iZZ4cw^l~Y-79t#tVGI1BpN!3>g-6>qlt}rhzs| z(BAKp1O>i7stkR9R>w-rzdulY!H~iie1D>(fzr={IJFdeiIgij681Fkc82lDO5U6SCz`oyHw_c~gDik620@ zF)gL9Pq>)y`?&$t{`IOI_MPx>QJDJeY=S|scSheY*LQ}{b{ZY+(*0AxgvANR@y$M^hT zIczoMS2i>Wwh!<`g>tc~3WCZIYSCdek3*$g7(bnF2FMjTxnY!i)k1b`&VE^9g;`M}gG?o`5SPLp2Rgh!5yWWXDjC zX+G4;T~ZOy=8dGhd7!!0Dg@0x??pfSWa8B~nrrmuw)|6TRMV+xzS59in}AOzVAF?n z30kMI2$Lyd8pg3+v5#+4I>uG!aD=4!An%40h6#4Yl<8RKuZ$=oX6e5ltv8O4ze5kcHbzFY0f~dG=#}r+8lY!C!FX}uNmu4{) z^tS=8ocsE{C!;d|v;r#Ucm<4j&<54pSj3BE1k(nU3d5^G_0X_pAaEYS{T$OMM?r^o zF-Ud9K1b#6C4wc27qpn38co1sMFS}_9Oh5qNi$Oj8I}6+eHg2xl!Dr*e3pgv_p>gP zz_n|uPBc!?;{SsLUSoe^D64Zd67b_02>8M&WI*s4@9&lbONUq1_J43fq3b?PHT2z^ zwMBd>D*=@HR6Z;)WitK)5LqPLwBHm&6Y(F4p&|Pl5S)Qff@BEx(h0nfRh^%I7!R;# z1tR<`u6ZYl!hiTtXnXY4`1g^5(3Bt>BDQC)pcL=ICsYP;eJ?T}ecDsn@D1gi%2U1f zcfE@JlmQ(70~c2@^7!}La0VNL*!s?}Hj5~jxNlTYQ@g_I+B$ z)j?SqV*uj&>J$Tot?CPGKU|pqIdxRc%Ri~2iuQMEh@_1921y~!|I|cH;=hsip9>=W z{9{3FvVJR0q1Rtu=2?-JvV>87Utl1ikmO78F%CFlh{|u>IMrN!&IY&?cCvm~?&X=h zTQ@9}*JPu0*Pk3ZH2B^I?aRV{SQTYxXh1aGV}`b9Lc)F`EZ085!Y~8CCX$SL3Zp7L z_U?+Hf|%DQ-X6Ky=m!)T-oe1Y7kiqWfulrvfT9+{GWaKaLuB-m5Jp7={z796MN9FZ zg40oOTo*f!P!3A*iei5vp@u<6sottKEkbk}1N1-wDKV;Ihx-_%kn;Qs!NGr39ocUM zVFvx%g1D2B%*XZ+NpQvTj2zG{D?g)RkRwxydn*N*BmGS!drgOftr4y z^GUyG7(M_5s2Hf}2jT>2pSm~)XB~qPc};(nMrjb01BDnu`)oU)DIi4g1$tHhYnMM% zph2)=XjFc!0MLG|K(jhnrk}EWdF!v`FWxsp^(b*a^vLhNKxtmh#R0?LC!jz!LdKhPYALl|MMx+N_~I0S6t@PYo+49a@@gA`qVN$~biIck-kXC1 z!xhZFTK;p+QudP6sr(1*fIbKG$2|M>M~LF0XwfxgNGNMsg8#lKS6%q49w4vYfP88nvR<`t>8VZM!1qPf}2EHh`qY;1RtX563$GM{7Zyq&DC;wIJxT7JcX2fYic1iYY_}3r;t|7liaw zp(DzWq3#L*Z<=X(nFzQd0b3kwB2n-Smx1pOhZ#lkkXHWA8iS$HY4e&Qd9^{x!&?`? z2zU)&ToLrC+U!puG!(hMRfig@ZmmFtAG=dZlpWJkw*!FNm-dgK-PpLp8zS)D^t)L> zm1;W(NIJjYeZp9nyc(oO=JI`d0s0t6^$b61SLRz>5LxY4(T~WDa?%13!I(E}lR@oNnZXtxJ*!mlGqQOQQ`CGnc&QvW z=S^2o357PQ5Y{J(2?R84+z;P}1PnC>oi$YT6z!&}ii!yRuWbTK=&B+P)@m9hI7WvW z0AHUwV%i4R6Y>8yJAKODgSK0Y6-?Vg{upw2F#dP9{E&BnJ6GMZ)yP91|K1!R~W3@r~PZtms#$?rQ!x(6+U_%MTk$(k_AC2K!-2OLUc%4CmzRlnYZLO!_ zgFs#NKT~R#o&?myFZ0Xi*hTSvWmqqikKRz|$lIj- zeViZ_!sZp`eN^WD;|_sVB;xn!6k~u#r(tOO(r6}BR~4v;vIIj&%EAwN@E1}q=yS7I zsp)O@#l88P#o=$+f-(EC@Ymcrz+&vfklj_tRN5-MM%o?r-(jv>Zz`vk$9A1h9E)#41wMZ za6%!|!)cY-FA|?ud$kNnRC_|FKlk{sn$j}d(*;}{i$%E#166?`2)O_*xpHCyDnJ>S z0VVm)1OlmyTwi!fa`0`uJllrm`qq1Wk7PzY1KmLFPazn_#F#3Xu$KPxGdsW7T|J%CtRu!;~-1GMwrSU39 zU&<4fF21x4VM3cg(X?U;>nfsFEQd+HiYe*}V#6x=t*C;mHXb?#)$uB-)`$we)v8EJ zMb?Tcp69FR|9;JXoBFZdqm%7ec)u4uG*l6;YO56Ui9-j_Mi}bK+6Yll>>fI04cD6} zjm5KhS&yzj#DqTlcrI=oqY)wB4ieq%Zc_TQI7q_t}^ah2M`A1J7H zP^lSTX4+9MFIBHH?Q{3X6`z*Hw{raMl8)b8(E&D@`mn{oG%gPzMX=rYmag#ut^}#d z?JI*`GQ?+@;npi~=+UJkEWW2pS*8A_EV_QR&;J*vpR1SG!WCN83j?>T?sG6)^#WX+ zfbP@!4K)iILjR2)fi4?UE&x?dF2iAYUK^@^ivkDn z_cWZ^g4ev^ta)61SJx(Wa7!aNg(DR?jO96f4+c?mQ%`3$nWlydA+2n ze@mhP^1LDnJ$#bHTex_I4LYMQk-(ZA*&Fv3u=4X}OTkG#wXY-p&DK}sJ8h5fKrP!=glWym)eU{NX0 zsD&s6$FbBnJy3Z0q8eD;K|+{N{}c6n<@7{Vt5Y_Pv|PpG2HL1>f1?}fH2#|UmjK}+ zvKfHjy>&Er(Cmgn1@M4;oj@4Q#v><=)Z=79=XqvzaFg&%+tQ8TwHXiKqR#+u!4~0P z*j~Tg3W>)0+ijERo=M!5_*Tb&o@g@i3126PE_Q@}FzpmLe03N+qrDLPf!r~^dz&ID zq)7NxY@E))I}sgDg8Ra+aB~Hm5Q`x9L8+suTsA#Nh={mHNx*MZ3%q62hZo@C4of(g zjqaO3WCru<;9 z&1x-jIrlqz&Jp(JuY{;agYib z7vC9NLzU4a9_pPk_WGh{sMzcFKIM$YL&4JgF?2?$L2O#Cr`>_Va<`1?s3P5eObqrp zzaOkUVf%QrXJ?K<>ezL2`(yV}%4`?4X)_+KSmUS%Q@2< zp0=%r&2wo6n%+gQ;yJ|j-MgXaixRQ4^#9DB#C*H8G9qU}~(c%m()xIQ0UA{%L2lGfSO;-N}pWjQ^VvXWe( z3E8pKXo2z zWW2f&#_{f^SMarGMcN-L&tq$$PTS21ou@Y^|2NN-|pN z3hn*Kz6q6@y3;V&OXR97lOJ3B&b^S@dSOWt*}09>+kU;DoZ(4#P)k zzl_ga!rmG4Fll#HbY+WoQ)tj@&O&XIPz_$W3mIDFZG3Pg#8!2|YRlCV%=~dP@cnrp z5x>}JBl9qp&JBNTa4C3=I+nOiZH(P{E2igHo^?7S{$#^&H!ci^?0O{~oxZa~^sBe{ z-E+82R@JVf#S943+?}YHT;^kE=via~)qSAU>?9Ihhut|dwB`%{)M4SBk*-%46btg4 zHZ47^V=R-ch6C*(HFdYfv2ZjPTUiT{LN4SpU0as+fv-A9??lS1r*d*%3>(k=o2xP_ zU)aW)tqTz^+c*lHBOlAsTkD1pP2DuZ=vLkB+)0s{B|l?BLv|CBd|Qjdncv%-7yBuZ ziO?kA8ca_D#8Vk6(&0axFP$kHgl|IPaR$ zVq{_mAWkMJF-uR)p3T|Vf*hze!*PQdZU@NPmh{}F>!m^7N%QqI+Ak$%P&CGGd7o?S zVa+3dx|y^zoD5^QIB)H^|J-bMU_51bv01lO67~jjtF%b7WQ-J#q`7cPH4^zysl1>2 zoMLPx*r)SjLO1pL0{cQPFl;ixvgGo+qdh8RZ&gkFFq=C`vvPdfy|gBG`=&dI)%pF> zYbkK8tv$D6-Og#o*;@oJy&iAzt=PI*4DXF%cG&O=bL$})Ufj330c&`KM{R2C50@?p zzukB;)1kJoD4B8vX;*3`$L*^-6?Zn{r|x#RI#;?8;^YiO$duS`j|bwQI`!7A4igFQ zCYb%kn|G87xdq#(8um>h>{Q1s9Y@M`n+787@U>Mlf?29dE6d2??xr%dDYEJ^{a(%x zotjzqwdIrs<7w%3JM^di7P|_sZXxMo zEaki!iB9WLj$AIYtV9qFA|^cQd{Q_>Y|X@O>StpC(K=;!P9sh8!!2EPCHBrn^s%7L z4Qs{aH{15NS)kh^vf;YT<#I>M)69MI80mT$Yb>z7nXiG2ljS|T^wAXIQvG3gKAJ-KpJ1C?DOZkwJKJy+nCX!>+UdwXjN&&5rMFSbG(TfVoG z^TlQ}5%Ld+E8#Qb>Qv+CD6eCNf@?Z8re?)Ne^nmt2eI+OSeXr_tk|Ij?5Oa#bbO8} zbL-v)w)&hiX8M(lM`v8~p2mVs)W_*n$039R&U3_0HjkZ7w9M}4$5f^hyAjnpEbjH{ znal@jw;ACuwnNA3(3P4}2DrvS9FCRyWBYOi5-mt1LI=(mYe` zZN0|;$!`kFdos1!^$T69S~w=B`|i|W47A+cdbU8A+d*#3vibVFXQd|gyt5#5jl=6M zb?65!qDf~QX8X0;-qJl!!30y4r86p)1sd6iF1_q^7qyHSZ^rGnMntrE zGf7kT%)nJmm$T0?r}I?d5t}@^!pVG-wsq^n@rW~ZLVB=kF)-PSbhIEINpHmw>{y8i zwFuqPm!yNPScVg!M(S!@cxP|Nn|0$^tmIwUt@$DN$Wi)4^KqJDQijRg>7kZ8hi(7p z*F?ezw}rCM>HB_lX>FK0?n_{j97fY2hry6(jylx=IT8(~*?!XU)|j=f!5Gh>#NpOR zgvz;5!9iaSVX=G2lx$V8QT8SK37$FmSgPZCJeazz>oSchGnp7Vy;<2|xz97}(cK=5 zd#vJqS8w^#xxsYD_b%N9Nt}8PgU-2=o6^2TwQsQp-}7ygm&V9@u(_HZpKIzmErP9T zEkE?Q0%;~E^Vib8c8R-ft3)%9qedZWIJkRl;|nv5Il#bw!{ z8}~0G2WftU_Km4?&NXxKS$uL`I^lHsXmRF~w|ID8%i6YE4$GXe5h@Atq21|KNCNr=Tf!>Cp0J@D_U;$ET_!;k`mVWererm zEsk!t>vq0-g_dmbl%>$ZNX+)B+K4)YEv9h&u?qx4Gk0%&YeZ){i`8r>NOyADu{BJ+ zd^u(DkMhAuxtHP`MA>^zQEatjl8&2Hi4G0|p5(c9nbfIdfTxG0lHbO*7vW;pLox-* z-3+lckG7{oTSXs(iEu(o7^&d=-cM)zBFdc zw6Ph$@IbHGOLoU`yhAPm^R=VEGUWEP3ZZ7KD5@#zH zJDGkrb7ilqWY{G4q+QHk37dvT@DPW)*_sormiClzS;TBIeib-ReCf9iW2daREzTCL z(REd+-d$qOFssw;OQTGl<7jf=lCHHz)T1F!&YGSMI zZ(g}wOnRuO>(sdM-ATq|@(JV4BHZ;_c9!(89$y-(J)#q}kUp8&80;idi)ZPAyzks; zDqmYx#)B?3f@HWxVP59ThWo zvv1{0{sxWNoZU6U%(1u7Zjz115yZt_)X*)l2nWp(m;$hD&@0ar=JnmdN zXCs)u&SKUKOBH*OItsC*-{Tbe5%bKR*z0J!tKkb}A|qEp;-q%L*;&^NXGf$_9g!EB zbQSFMN*Y`nuERBLaB%gcPd0m^MQT}(4-=t7Za4RDqP|#em$~GY`Ebl}cl-R5MWWC3 z;K9(=73X3mG?RE=#+=;RsZ=zUvsau(e|vat3YNZ|p7eZ_)mLK%RJI<4)90ZH8JV8j zgL~U+aoASZ#BLC{_PlI*lbE@qo?LD=G$4w9iMq`B;&-SP2 zF(o%+reJL<1_B;u)Ko1@{r1u7k!zLdq$AP`u$Rdrx0zgOj%Z?Nu91T7OSjw zzgQYF8B_njn+~Oj`9!8Cu&f*SXo94>k3v-TouX1I8SbpDUaFfW_ib3mXNcWx5p>w$ zj~Gy?6KjqYHYL8D&om)DsXGv|^e zBc$u4(0oeTg~5_-pUlL=CRA5$v3%Q=TLq`BRMAEJ3wFKh&Mos?!|NSxed%h}MYq^9 zF%0K}*3c~%E0t0+sY3Fx$KDm>rIu^clp3iethBd=OwTxQxn{%lq)2X=W?7z5j6l0j zjb@@Vi^2TD(aT(A6Kt2)-oZ6+PF5nTOv|z!`>#+=5%f03!mWa?rI&w80+M|rinitYGHf=ArAM-`Oadf)! zWNm$_t%>H=9y>NGbJHrcsmFTr&?4(>`9rH!C$=k43rRREghMc6*>!C6S{H>V$8C0* zo&3Y7ebKN5<+y3$&1Bmh**rh(@*}}G2_0?pTxj}Yc}pl&Y;}XMtz(}Eww>4-lnkzk zY;iPqeKT33qqYit8{c9Qm7%RYLwl=>_qQ2Sbh6)flIg&3BuazEY*%%~%5i7?5J<;D z{e*cuI1@xD`iQoi<3eeaF3N>ztVltKY7}4XN0Pk|^!n@O;~W~nn+1aQezTXCvtta z<2I>??sj18_Ueo#(tkb0MgCz5HoK=@EWRHYXVz?+*>%gtgNgTM8q8rtRRaz0{(KP^ z{gy?DKP+5p>>LxGI%%8BerB;_Ewq>$nB=r$RVx|_GiD4+*11tf<=Mo8Y1dPw^QE84 zImn1OCfQOjuunD8Jsi`z{&%#;1lq~yKDZ`E5QS|dxkCUuJ( zf{_B1BDYT(P!#H`?=gj~6@$;l$+_FZ=(__3)&lqvUY_H>UF zA5od)<+#0O;^dT@cdg|$VMwIed@`ETZ9^ABPYUNAK{_9d6=U~VuG{U+_BYc-Af#QH z?UVb;zS>TNZ0k^;^iba6Xcjtb7Os2Iu(r5tN4n?TSv@`*o$8TyF=X*k&Ndf|?2@^1 zE~|5}nG|aXOYwQrR4mU(@wyh1Hmf7g(2%w+STMxkc#sjKdl<0f%=|kSeTS)YioFQ-t{8yygDu3b0D1#4Xa$N+%&oz z7t2blcq{q+Ai`S7UILsz%4mp!XQ+CLCn5dneU$X^YZgL5v z&9m$D(c8QH_i*uY>AF(khDr2RWimu>Z1a33mEsdrWnMV0t>KNetXl@|{(Pn#3C&o4 zD%xH~L*>((EanDNmu<+G&^jq5%WZPJC@|~YiuTjzzK0pjBi7Z!QOIO3r(veN2#y=o zSSFa7`-IeaT`@KDx@kC2m#8+p0v!^C2;B(X7)CQ#Pb-e$H!< zEVjnBksv7l&~9|kJ&$STvF;AqdB1Obc1X=tI#4c~j$UHQbj|iaLYIq9R^~-@yW6bf zPA=W)ZN!5VwbeF*W6)rn`YqO~%W&|sVOzy*zZBiJ#WXEDhx2I5U4j|gDV&O}(q!oo zf<@<}eC`vj?)}khIOS|MaLK21u33f-x6Ga6+_u=BCb#?W78Rt^;+QcHI}{#O9-lbd zuYzI9F_0{maw8C<`gs`JTsUpJOw;#p!x(AR+O$kI3|wWHJWEm8<1!|Qkf*9U7OtB> z#^4b`)c0+A!kkWhEwAd zy!fNrZdt%BgGSqejan?mVzjVtwX1xpBcR!!E3V`$jr_2c;wpqGyFl3XJp8rq_OasC=(dkB$>6w%r?M*u?@o334bA7iZEM7a=GX%4v zkJ&BO(@~bCm0ftZooRPT1dO@C#=7)g$UeJc2r4Swc;~&x8l%UY>>j$h%a}7;@SBpR zR?)N-qOPbXdpm^u9$$7xo`dZcwQO&!#ts(J&J7N=E_Z8Qb9Tcn8J^^q5NT%e5B_2Z zHXKnyn@d8(%rz4BjZ9+V&oKAM&|+8%Rg1Mr1uun7j-K6;ferZA(T;I+%(5$5@LMxw zqi7t~cP#6b3%Xg@ooLSMa-o)dKrpH@@37A5Ey(*eMT3KM-HP{`*Sor96UT0rW9_r$ zY0ec1LYlVvV|rE(3|z)^Dv-T2bCbbtM-ATi7}$*^ou?q!%}2vBv>ha8yszgqbE%cv z6}WP?W#F>OMJa3FmNQ$hXtIq{_T*|m%!St6d@XJJMAe;tESuzDyX=^+ow_MjjS&T@ z+4tIZqoi+;)G_n?Ep_#es$8^sY0I!t)(yVdpTk0?;?|(UO6;;Bh-V*oX1%?2A;?!* zwyN7~ParSuwWnJcJ1Q5aXWBPL7`_5AgI(2QKJTNtUd^%~rXnZ1o1kv61U#E6JG#tN zllHE8nrdH9!^y)lUpkiU@yZ#o%>4Co<6!9vW}cmluF4S>PoCRs+%FqKVswy9 zRCC7Y)VQ`58m^O-#JB-Cbq_2>4o`XoEAeo(c@i$IqJ8$yrE32!uZ*Hn{E-{ z?+&il>dwpX!(3~>(*+aT_}LWfRU%Yw*LQc5t#K!3ZS3-utz?AhuzYsUWWjCgG*?qg zUAlSZ^R;1~UD5&T*x}tK$|bs0a8L$&q#j*zy=}`SdcE<*)tR342Fs+bZ)2Bt$0TMp zrOb1PNz5FZ!sO-(5Tm@odCnS`dw~ed2GL`>XFr;K6W^e2GRKV03EwxxqRZJWR7j`*kdoo+w(}m-P<&qgX+zm7?bNPlc*XxUH3LD_)YPCa(^%D zCbpo|^}0{6K=X$(cY$rusokKpszyVP{5U^!iryvPCo*$OK4z&6#j?M^XPZOhiu zA{*?!NG2If4Ey^icxqQCUG;XeM_8P@146ui6j9*7zKWgYI6?Axk~mVj0N_Ze||3 zg$vBpS-+Tv?z}10oh^@^dSi7W8YI6=pm>{SU*#>ae09F>>EMqN`FhuTh2SQz<35J? zLbkQpoSF2@x}MpRZg=`>&+acNFI-YiCZng}>@rzw&AV}jupRQ-c3Vic>*49{T!KRf zJndl}on^>f$_J=YDqJt$g z@KwA_Gq>2N)twWyqVp(Nx|Z_U?2Rl8wM$06Ea-5!r)$VJdFOKu`x$4vbU&_{%GMH2 zx?Enjob$VOqqWZ(PH|&bq_(eImIiiv+--p3 z|1ee89hnS@WA@Yxq{e(lM+&oK5KaIkLi>GrRCOk*yJE^3dQ==Oe_PrtS^r?-8J;Ih z*AHG*ckJya-AN0mWI0=a#X>1&ky4DS3c{5QU4OS5zDuL{t_rJp`8_wB%6?4SSSyK08pPQHy*&ZK(y-DjP z#?G%sOJdOUja@>ZA!aW3$Zm5s$42=gS0jmwcU2? zT37B(Dwbt;CR;SPyEn7PTQp{+Zr))o7VXFOwRL>l=H>}@=`Gp{4(YybPtQ`DkM)y+ zQ8(DGcHHQ6Jf~W%G91aG$T0l*AfYpHcuPVtGje-oWe0QaR>*CC6d89TtDBp+2!piE z$Mw6z#JFV{ES6`Fe^N5gyZw3-^Nf74(zd>dRiR6C#!&ij?Nu|SP||#-y)7o24$M8b zv)$)#C8Ndkgm11c!973O2p3nk$))3KmwC0xTprwNQrZkgN1kGbyG1NqJmkm`9L=cq zdc9z?x?cw9lTA09LI5|tXfTQ9Wn;Rr;*7CobKzYmw6{Lez0t89o=1Dk+_HsmX0za; z879{E*o?Mi&LCSu=}tM~-qy2jE*RXnTU+=XVG3vHt?#aaLR13SRxuVtSB4E*SH}`LF znnVI~+M&&-d*5`6BFTV-!w$kDEjKI%$Q|Ltku^eFY8E9h21rl zDkih(RHiqOT`kxabV)~}ZC^8*7k3-wXzo+p#MAkXjTjJ14$eoxf-7rw*o%GEGs&!+o7LPi3YL5QJ2`7|!JL1%r*fT~G%vb`7b(!r zbT_%?O!C>i=B?WqsqZSR_Z+&JQiWd8?Ht65*?px9J3|KtX=@K-bvQ(Nr54Sc)n$!T zC5N@P^thXc?ZCMS@ZRCsRWVfHt;6otkjoXX#}V0S#AlrcAHM~y+0b$mrnmD#s0p_O zdIkd~kZns_n>%vsl`2Mpd(i&04!o4fX0}Vq7jX*lD#vo~7Q6W-jPJ|*n2OUZvv#KB z>LxJT!`sK}kuUG?UY+FG6ZD*o{v#Z?iF!Nx)qUpcE>EYF!6uCZg&odT0(l)g8ZcA+;L zQUhZ$yAfPnAvY<_n~p5IjECB1BFltqsdN@}DSnQ5i`D{TVJhX@+!@O@&y`hnJvbSQ z(~zNa4d&3x|OC=2+>{@}ht>z2`2)W6tqe3+Tht$eUCqQ*JHBR2^gMxNfl5Sx1Q z%tWpYt6L8x(BqnyxK=9FYvmZOO`;I5BErUQg~+A0>*~zKNfy`K6wi8@mEC*1ZqvHa zu}+m{rghv;O~_nzAyhiQ>!a{4r&md{mk#|=44i;fr9DLkw7|zT! zZ!@W$52A5Bx7Ge#P_%bFb*x(tW!-D9GN*R^`Zx;~E5}+0t`V%Na_74Dh-Xdi+L}Jw z_wS2F{It_~*4-zaRo~BFiudzV ztfj5^(cToPg-i{XCN~aL_YdyW)4AxKn_aM+E!yd#qaxOC(s6MyxCU=qzIZ#I;mlyA z-<4YO=-fs=f47=j6{q9gP(D61^{wP=Dy(^~+hRGp=_dUHGpDh6ATD|qZcJw2anx*H zp~d7)w3`aWdigd_)kbAETgwD4v3=9vDDQeEHd>Kh{8-bCA(be$ACJKeD}!iri3m)Q zK38<|8MrEXX46w8lqxOd@+IA63ekY&sixC;i`AMfOO!w&PmO)*DOrH)blDDkAvigT zQ%?ETUGo;TP#;d)pJq;&dRTfdZj8wtpSq7*pA$LTE%DHc9=CqlHY@VeOtDtw4SSTA zo>*AfJZPI?DOw5mY6r7^tW4Q4DhS(UX4LI4rnVTz>N`uDCkFEeUm`q5p|;ysgROHc z0~dYe#nlQ;@{($Mol-twHKT*zA<1s8Cu0=d20iP7vhfG}ty*IrHsgUY7vKf`uu>mK z>3z~4kXXXtF&IwY$0F4ToQhY?v@g>=vjJ{kM2A`UmKz;B z9cjd5{R7c+yk8%@5w%h=(t>+ySE(8D_eRT(lD?*?TY8tBERCt~C=_$qQgIdPG1>!M zMwslU=J8mo)=&FEoYS&!#cu3lcFw`tED)DXRUYir%sFT3o?ynLkChd}!i^j0hm&6D z-6!4c^r;=oP;HLRu;`NQ)cjFHDD38Q#;mYqK4ujDbsU!4Kd)x_8A@@6zuxa@OyN!tyQ~PA%=| z0qCk3l@zUa?;@K_+}$ayTU3qIo@g~|M>bFU>O+nSaAGx2jomKg^-P71>twv)qhNL$ zCfnf>rE)Uh&D+~ftt@Q8=Vgv((^XGRN?FtJ)pN({mFlL{>NS3^cdFNDD#Lel-4WZv zrfb)dqRDH<#x1w>#!R`Hdw6$q-q+X!TiHy;vw{Wd-Gy3Wkt}3rHl3Ecr$vwn-StW; zXFpEoPD$F%*WPIP2KC)&S}BUH{xTgm+Gtp~mK*BK4lq|_mYq{s38XlU_e?{Ds=2y9 zjUx#u@0(2$*Xr6y7HDVZEk>{Nl|fDDLyLWslgd6FOdofPiM_C{b9S^Y&#PkTp!FK% zs2hLGU1r05z7XBZ^jI#gZqoHE63ZTK8SP!*v%B=zW9BqpucTp(dg5CC}}>8HU^6XHw~pMsc3or zl>s{r_VB!OntFTI1+5MvYEUlZ;{fa_$xd7GXgTvi)< z<%!P@7vF|c36BjRaq?K$2=naifl5W>ND6|(i2Qehlft|nml(kCbHN<+Ee895Hwd1F z5t$c$z!(WCynWu0mEeAQpFu7m{kZ2#g5UeWtbFu_{a5W~v}HkUd~k0OU$^znJgk^N zfrk-fY)1>C(6{qHzwLkXz$xbI7XP*-zHLcwTg=;2@W^~;9ltXwKO`@#;yv8v z3>YV`3fJu*bLc0N;nnKaB>Kkke0ds{{P!3CCuRyvG1%*Qrr>eI$9j*EA^QxH(fSOM zf%*)-r1k!oZ_@oywM4F~H@QA;r~&(lj%>SOB%kE zE@}Bvx}@bx>5`VOrAwM@Je04)0LpwG8luV~_=TbR&l}X= zyQ$ef>88e>>c54Nn*B)a8_$1eT>qL!Vx<0?`lsO|* z01-(NB1u9dNeCnffg~Z2BzV##PY@mucm*7RIFbc?Ho$>ck_Ah$U?B@k7Kx-wC`1AX zA_yV~B1u3F2$Ka79z(+kSkS9V3l12iCR1y*}sFIR^>?I%zc;-bAg)EC8 z5IHY^SQJhGDpGVF6s5B8g$w>jJV2_>0i>}U2u@3IAV&?>NAQrdAX8mrL9)6E zM*~?f&}Ci#WnsNMiwO{*1}lruM)U<3%mX4kU<^Z8kPsm=hy=l+#e|UvM+cH1S448AAv&29e~22}1}I29e~22}1}I29YFz2}1}I29b1$34>uH2uwx1 z4M~u<3i99!iTx)OMmqSJ#QsQOnZchf4bl5p6#|Na{LSk@D4{qNsM*R*d{pvohI_WT z5^IHrhVQz*y`Te)82J{C_l`n#G?p($vndFwot4Cb2SiyEVnRInz5x10xB^h>(a zwD%*<>h>Cb-MOXpQdGPzSM22^0sn&^xYe__yKSzjJsb_4^7m4AtBE)%;0F5Ah;IV- zT{g4kWxilIwTyhuMrLi=%s=MNG0!O4?}YsR`7NS_3g48A?nXMygwrfk8~s|#d0ieN z=P>VAeeidyUu!wfD=EL$a^9^E{nc~km$Tz{=gco!dp%di68yE6^J^`~{`P;dmP3|d z{^~l;@}){8xv=n)+JIS6Ay8rf From ce128c91fdb7bb9333a45c004a73e3d742652d80 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 15 Feb 2026 17:35:04 +0800 Subject: [PATCH 08/12] Delete img1.tif --- img1.tif | Bin 13564 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 img1.tif diff --git a/img1.tif b/img1.tif deleted file mode 100644 index ff2ec142b2222abbac4f440dccd676f977db0dc9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13564 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≦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 From e85e90826dd42de496f0ac4c7e51feb82151ed45 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 15 Feb 2026 17:35:20 +0800 Subject: [PATCH 09/12] Delete img2.tif --- img2.tif | Bin 3563 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 img2.tif diff --git a/img2.tif b/img2.tif deleted file mode 100644 index 581118a6d1506fa73c37a68213b75dd6af81873b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3563 zcmbW14Lp?D9>+&*C^RXnB_$fBjXUOznW-@)q`5OFL@$;mks>c+ijcOoqn#~e+)NB} zuT-*OQCh6gWh7H}QcA6)W}7H1c8Vm`p7S()uH4=}Ju~Nde$W5>{{Qnl-*cYBWG+GJ zqEIM96iRC3JjV&zOQT^;Z=hj~*}yb2SsyhO=&orf)OX+OBV&yK{q6B7s77Ei1)lvt z!#q4_@X*!#wt>C?&^Cj%1vGen6ZBhy2Ts*Nq58nfux6l8C3+~7kpXyj&5!Qp;>30j z=5e-icpS!zg$wCSUhvL^5uBaDj+Wj&PCiU;AEvW+pu5w0ru#Z~%Y`wVol)VS4{ymJ z+S}9Zp#4L6u1+3rB@rkNI0^|*Arc(OG&1$$1(+WHH}J=Y_4aXbarJ#`U@8GeqTul) z0+C82kv?9+*NM$^30&jq4CgFAb&J-wJ7KCWJCCZ;UpMnYymk>Qct z?Ge0SZj>csUBuSlknIt{Ti;#jL+lBk!yY6NXjB~8fl8q|5U5oAdtPDUz?0d78XpHY znLS9NkZ|@SJb{GAnft`N$#VzN@Si(dAeVrDPxdbIS7c-Sz$pju7oA70 zCYTj(tZ})2UNv-Hv>By+&rgUE2r*3fUyPhF+2pEvgNUe1`Bq|f|2=I=HrpELYw(u- zA{AZNV@G%Y_eRqW2Cud+zU}c(gBSjLCLg;fy=eivQ(Qxe2^(l?s61g~BWlNm=RW6# z^j%j7q(M4vx(CY8zm^%il35qame|THTV%Fu8D@NX0bW136)kNw$ZMTh(yE_2WY{I! z6lY+kq|x~ohh`1PzNs>xD3|*4FGm?FWE-dkSS8hr-)K?p*+2Vsqu-Xy^;lm)@Rp3D z*bu=!!FfUSmVbR^tu078CK}BfJu;d-nlZ{IpC$LP*4mkO44)_y#paE9-MTD}*!y+= zgLOaG8I7oq1qRMY*yC`@jxVK~w2K-77NqvFICkhJXivz5_Pp)T-d+ss+@JtpuRaIu z{u`hjR|4&G+o3&14DFQCG#e|3Ib+KbfVtHZFkJkaB+dgc649pJj2Jb(tQ^c-??3NW>;8GQ-G|#vcLu_Qs8DO zltyXFSxGWn_=NcwN4Mdp-^b68;dNiM>fITdzchdT#(a~yhHay|gUWx@rig~jBn?4v zx=)q3&J=O0?oH(q|CED=saOv|&>x3yVN2(edjV&^G)RI~2ubi`0644bfD+diV3RZ9 ztsCq&gF*FaKvW24gmn%={2fx=S^|SdU@!{?Hz2`M7|ez>&Cyz20%Q>bv#_{1C%ieUzgaRaoJteUq0JeO z-mO$!yLu!!YDVb9}uLJpUlY0Kes=vu2kZ= zZ5@%AzCt6p9k*yyd0y{b@BkuChP< zl`w|N52Aj57x^m0WiwBg=^w^$p*FSVhtWy+O~ao^F1vkfQcr ztWG8~51wTnNEG58WiU(*TbN`JvJZd#%$h{R>Q!KDDy-*KSS?etMpP@(R4!57s?MwY zWLAP-lK<`M@?Bx~6&=zeI;?bKNsFU+&tDI2To(&sgUnZ%rsQaE9P5Z*TElqJcH?SW z`%Pji`8rA%w`F7gWuXaOHg#Zc&@`b*NvW%GVH7t!zkJUV#rT0OrY2D|MgrZ7q$ zY)I!hbI)<+v~#}9;uv;wjO!L%IbLTpEW|_C2CYnJ5_-cR4ek;`XDENa3FOPE_k9OeBZPx2k!^S~VE~#boaY}^KTQNz z?|lC#WDR5Tz#aW5g)u5QKZMyN_oxt=Vce$EEDBYJax?X~%+hB2YKvb~X`Pum>eZ5F z(jJSSRjIX^Xf@OHIJ0gwoCzK?{fYcrefbaL)^mtf|C+Za x?U_RljV}Bxb*5j9|HJ5$R|HyaDP~`lZ)-`LzUgH6(ee<*k5+nJb!Uck{syAbg^U0I From 79f4250ecc5638cb63165dc69a587fc9d9d97c5f Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 15 Feb 2026 17:35:47 +0800 Subject: [PATCH 10/12] Delete test/grdimage/subplot_images.sh --- test/grdimage/subplot_images.sh | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100755 test/grdimage/subplot_images.sh diff --git a/test/grdimage/subplot_images.sh b/test/grdimage/subplot_images.sh deleted file mode 100755 index 263909882fa..00000000000 --- a/test/grdimage/subplot_images.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash -# Test subplot with two pre-loaded images - -gmt begin subplot_images ps - gmt grdmath -R0/10/0/10 -I0.1 X Y MUL = grid1.grd - gmt grdmath -R0/10/0/10 -I0.1 X Y ADD = grid2.grd - - gmt grdimage grid1.grd -Aimg1.tif -Cjet -JX10c/10c -Q - gmt grdimage grid2.grd -Aimg2.tif -Cjet -JX10c/10c -Q - - gmt subplot begin 2x1 -Fs10c/10c -A+gwhite+p0.5p - gmt subplot set 0 -A"Image 1" - gmt grdimage img1.tif -Cjet - gmt subplot set 1 -A"Image 2" - gmt grdimage img2.tif -Cjet - gmt subplot end - rm -f grid1.grd grid2.grd img1.tif img2.tif -gmt end show From e24f683a1fe03e9f68ae7b51fe7e14bb6d7a80f5 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 15 Feb 2026 17:36:10 +0800 Subject: [PATCH 11/12] Delete src/gmt_api.c --- src/gmt_api.c | 16312 ------------------------------------------------ 1 file changed, 16312 deletions(-) delete mode 100644 src/gmt_api.c diff --git a/src/gmt_api.c b/src/gmt_api.c deleted file mode 100644 index e93df225a44..00000000000 --- a/src/gmt_api.c +++ /dev/null @@ -1,16312 +0,0 @@ -/*-------------------------------------------------------------------- - * - * 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 . 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 -#include "gmt_gsformats.h" - -#ifdef HAVE_DIRENT_H_ -# include -#endif - -#ifdef HAVE_SYS_DIR_H_ -# include -#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 -# include -# define getpid _getpid -#else -# include -#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) */ - 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][]] */ - 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 -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 [.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 /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_LEN64] = {""}; - 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 */ - if ((opt = GMT_Find_Option (API, s[k][K_FAMILY], head))) { /* A -Y 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 + 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 ( 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_LEN64-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], ""); - 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], ""); - 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 && !(mode & GMT_CONTAINER_ONLY)) 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 ? */ - 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, ""); - else - strcpy (M_file, ""); - } - 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, ""); - else - strcpy (M_file, ""); - 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, ""); - else - strcpy (M_file, ""); - } - 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, ""); - else - strcpy (M_file, ""); - 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, ""); - else - strcpy (V_file, ""); - } - 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, ""); - else - strcpy (V_file, ""); - 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, ""); - else - strcpy (V_file, ""); - } - 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, ""); - else - strcpy (V_file, ""); - 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 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 (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 = 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 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 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 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-, 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] = {"", "/"}, *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 %% 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 _ [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 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__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__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 +. - * 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 : 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 + */ - 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 + in the option */ - char code[3] = {"+?"}; - code[1] = key[k][K_MODIFIER]; - if ((c = strstr (inarg, code))) { /* Found + */ - 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 will have pos = 2 as start of */ - } - 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}" - * - * 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, L = Dataset/Line, P = Dataset/Polygon, - * 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, 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 with 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 option is _required_ and that is how we can update the primary - * data type. Example: gmtread can read any GMT object but requires -T. It thus has the keys - * "?},-T-". Hence, we examine -T and replace ? with the dataset implied by 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 used by several modules, such - * as triangulate, to use the non-NaN nodes in a grid as the input data instead of reading the primary input - * source. So F-( would turn off primary input. However, if +d is present then we want to combine the grid with - * the primary input and hence we read that as well. - * - * 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 on 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|/+d) 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[][+c][+f]) 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 -= */ - 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 options into a single -= 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 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 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+t