GRASS GIS 8 Programmer's Manual  8.5.0dev(2025)-eb16a2cfc9
get_proj.c
Go to the documentation of this file.
1 /**
2  \file get_proj.c
3 
4  \brief GProj library - Functions for re-projecting point data
5 
6  \author Original Author unknown, probably Soil Conservation Service,
7  Eric Miller, Paul Kelly, Markus Metz
8 
9  (C) 2003-2008, 2018 by the GRASS Development Team
10 
11  This program is free software under the GNU General Public
12  License (>=v2). Read the file COPYING that comes with GRASS
13  for details.
14 **/
15 
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <ctype.h>
19 #include <math.h>
20 #include <string.h>
21 #include <grass/gis.h>
22 #include <grass/gprojects.h>
23 #include <grass/glocale.h>
24 
25 /* Finder function for datum transformation grids */
26 #define FINDERFUNC set_proj_share
27 #define PERMANENT "PERMANENT"
28 #define MAX_PARGS 100
29 
30 static void alloc_options(char *);
31 
32 static char *opt_in[MAX_PARGS];
33 static int nopt;
34 
35 /* TODO: rename pj_ to GPJ_ to avoid symbol clash with PROJ lib */
36 
37 /**
38  * \brief Create a pj_info struct Co-ordinate System definition from a set of
39  * PROJ_INFO / PROJ_UNITS-style key-value pairs
40  *
41  * This function takes a GRASS-style co-ordinate system definition as stored
42  * in the PROJ_INFO and PROJ_UNITS files and processes it to create a pj_info
43  * representation for use in re-projecting with pj_do_proj(). In addition to
44  * the parameters passed to it it may also make reference to the system
45  * ellipse.table and datum.table files if necessary.
46  *
47  * \param info Pointer to a pj_info struct (which must already exist) into
48  * which the co-ordinate system definition will be placed
49  * \param in_proj_keys PROJ_INFO-style key-value pairs
50  * \param in_units_keys PROJ_UNITS-style key-value pairs
51  *
52  * \return -1 on error (unable to initialise PROJ.4)
53  * 2 if "default" 3-parameter datum shift values from datum.table
54  * were used
55  * 3 if an unrecognised datum name was passed on to PROJ.4 (and
56  * initialization was successful)
57  * 1 otherwise
58  **/
59 
60 int pj_get_kv(struct pj_info *info, const struct Key_Value *in_proj_keys,
61  const struct Key_Value *in_units_keys)
62 {
63  const char *str;
64  int i;
65  double a, es, rf;
66  int returnval = 1;
67  char buffa[300], factbuff[50];
68  int deflen;
69  char proj_in[250], *datum, *params;
70 
71 #ifdef HAVE_PROJ_H
72  PJ *pj;
73  PJ_CONTEXT *pjc;
74 #else
75  projPJ *pj;
76 #endif
77 
78  proj_in[0] = '\0';
79  info->zone = 0;
80  info->meters = 1.0;
81  info->proj[0] = '\0';
82  info->def = NULL;
83  info->pj = NULL;
84  info->srid = NULL;
85  info->wkt = NULL;
86 
87  str = G_find_key_value("meters", in_units_keys);
88  if (str != NULL) {
89  strcpy(factbuff, str);
90  if (strlen(factbuff) > 0)
91  sscanf(factbuff, "%lf", &(info->meters));
92  }
93  str = G_find_key_value("name", in_proj_keys);
94  if (str != NULL) {
95  snprintf(proj_in, sizeof(proj_in), "%s", str);
96  }
97  str = G_find_key_value("proj", in_proj_keys);
98  if (str != NULL) {
99  snprintf(info->proj, sizeof(info->proj), "%s", str);
100  }
101  if (strlen(info->proj) <= 0)
102  snprintf(info->proj, sizeof(info->proj), "ll");
103  str = G_find_key_value("init", in_proj_keys);
104  if (str != NULL) {
105  info->srid = G_store(str);
106  }
107 
108  nopt = 0;
109  for (i = 0; i < in_proj_keys->nitems; i++) {
110  /* the name parameter is just for grasses use */
111  if (strcmp(in_proj_keys->key[i], "name") == 0) {
112  continue;
113 
114  /* init is here ignored */
115  }
116  else if (strcmp(in_proj_keys->key[i], "init") == 0) {
117  continue;
118 
119  /* zone handled separately at end of loop */
120  }
121  else if (strcmp(in_proj_keys->key[i], "zone") == 0) {
122  continue;
123 
124  /* Datum and ellipsoid-related parameters will be handled
125  * separately after end of this loop PK */
126  }
127  else if (strcmp(in_proj_keys->key[i], "datum") == 0 ||
128  strcmp(in_proj_keys->key[i], "dx") == 0 ||
129  strcmp(in_proj_keys->key[i], "dy") == 0 ||
130  strcmp(in_proj_keys->key[i], "dz") == 0 ||
131  strcmp(in_proj_keys->key[i], "datumparams") == 0 ||
132  strcmp(in_proj_keys->key[i], "nadgrids") == 0 ||
133  strcmp(in_proj_keys->key[i], "towgs84") == 0 ||
134  strcmp(in_proj_keys->key[i], "ellps") == 0 ||
135  strcmp(in_proj_keys->key[i], "a") == 0 ||
136  strcmp(in_proj_keys->key[i], "b") == 0 ||
137  strcmp(in_proj_keys->key[i], "es") == 0 ||
138  strcmp(in_proj_keys->key[i], "f") == 0 ||
139  strcmp(in_proj_keys->key[i], "rf") == 0) {
140  continue;
141 
142  /* PROJ.4 uses longlat instead of ll as 'projection name' */
143  }
144  else if (strcmp(in_proj_keys->key[i], "proj") == 0) {
145  if (strcmp(in_proj_keys->value[i], "ll") == 0)
146  snprintf(buffa, sizeof(buffa), "proj=longlat");
147  else
148  snprintf(buffa, sizeof(buffa), "proj=%s",
149  in_proj_keys->value[i]);
150 
151  /* 'One-sided' PROJ.4 flags will have the value in
152  * the key-value pair set to 'defined' and only the
153  * key needs to be passed on. */
154  }
155  else if (strcmp(in_proj_keys->value[i], "defined") == 0)
156  snprintf(buffa, sizeof(buffa), "%s", in_proj_keys->key[i]);
157 
158  else
159  snprintf(buffa, sizeof(buffa), "%s=%s", in_proj_keys->key[i],
160  in_proj_keys->value[i]);
161 
162  alloc_options(buffa);
163  }
164 
165  str = G_find_key_value("zone", in_proj_keys);
166  if (str != NULL) {
167  if (sscanf(str, "%d", &(info->zone)) != 1) {
168  G_fatal_error(_("Invalid zone %s specified"), str);
169  }
170  if (info->zone < 0) {
171 
172  /* if zone is negative, write abs(zone) and define south */
173  info->zone = -info->zone;
174 
175  if (G_find_key_value("south", in_proj_keys) == NULL) {
176  snprintf(buffa, sizeof(buffa), "south");
177  alloc_options(buffa);
178  }
179  }
180  snprintf(buffa, sizeof(buffa), "zone=%d", info->zone);
181  alloc_options(buffa);
182  }
183 
184  if ((GPJ__get_ellipsoid_params(in_proj_keys, &a, &es, &rf) == 0) &&
185  (str = G_find_key_value("ellps", in_proj_keys)) != NULL) {
186  /* Default values were returned but an ellipsoid name not recognised
187  * by GRASS is present---perhaps it will be recognised by
188  * PROJ.4 even though it wasn't by GRASS */
189  snprintf(buffa, sizeof(buffa), "ellps=%s", str);
190  alloc_options(buffa);
191  }
192  else {
193  snprintf(buffa, sizeof(buffa), "a=%.16g", a);
194  alloc_options(buffa);
195  /* Cannot use es directly because the OSRImportFromProj4()
196  * function in OGR only accepts b or rf as the 2nd parameter */
197  if (es == 0)
198  snprintf(buffa, sizeof(buffa), "b=%.16g", a);
199  else
200  snprintf(buffa, sizeof(buffa), "rf=%.16g", rf);
201  alloc_options(buffa);
202  }
203  /* Workaround to stop PROJ reading values from defaults file when
204  * rf (and sometimes ellps) is not specified */
205  if (G_find_key_value("no_defs", in_proj_keys) == NULL) {
206  snprintf(buffa, sizeof(buffa), "no_defs");
207  alloc_options(buffa);
208  }
209 
210  /* If datum parameters are present in the PROJ_INFO keys, pass them on */
211  if (GPJ__get_datum_params(in_proj_keys, &datum, &params) == 2) {
212  snprintf(buffa, sizeof(buffa), "%s", params);
213  alloc_options(buffa);
214  G_free(params);
215 
216  /* else if a datum name is present take it and look up the parameters
217  * from the datum.table file */
218  }
219  else if (datum != NULL) {
220 
221  if (GPJ_get_default_datum_params_by_name(datum, &params) > 0) {
222  snprintf(buffa, sizeof(buffa), "%s", params);
223  alloc_options(buffa);
224  returnval = 2;
225  G_free(params);
226 
227  /* else just pass the datum name on and hope it is recognised by
228  * PROJ.4 even though it isn't recognised by GRASS */
229  }
230  else {
231  snprintf(buffa, sizeof(buffa), "datum=%s", datum);
232  alloc_options(buffa);
233  returnval = 3;
234  }
235  /* else there'll be no datum transformation taking place here... */
236  }
237  else {
238  returnval = 4;
239  }
240  G_free(datum);
241 
242 #ifdef HAVE_PROJ_H
243 #if PROJ_VERSION_MAJOR >= 6
244  /* without type=crs, PROJ6 does not recognize what this is,
245  * a crs or some kind of coordinate operation, falling through to
246  * PJ_TYPE_OTHER_COORDINATE_OPERATION */
247  alloc_options("type=crs");
248 #endif
249  pjc = proj_context_create();
250  if (!(pj = proj_create_argv(pjc, nopt, opt_in))) {
251 #else
252  /* Set finder function for locating datum conversion tables PK */
253  pj_set_finder(FINDERFUNC);
254 
255  if (!(pj = pj_init(nopt, opt_in))) {
256 #endif
257  strcpy(
258  buffa,
259  _("Unable to initialise PROJ with the following parameter list:"));
260  for (i = 0; i < nopt; i++) {
261  char err[50];
262 
263  snprintf(err, sizeof(err), " +%s", opt_in[i]);
264  strcat(buffa, err);
265  }
266  G_warning("%s", buffa);
267 #ifndef HAVE_PROJ_H
268  G_warning(_("The PROJ error message: %s"), pj_strerrno(pj_errno));
269 #endif
270  return -1;
271  }
272 
273 #ifdef HAVE_PROJ_H
274  int perr = proj_errno(pj);
275 
276  if (perr)
277  G_fatal_error("PROJ 5 error %d", perr);
278 
279 #if PROJ_VERSION_MAJOR >= 6
280  if (proj_get_type(pj) == PJ_TYPE_BOUND_CRS) {
281  PJ *source_crs = proj_get_source_crs(pjc, pj);
282  if (source_crs) {
283  proj_destroy(pj);
284  pj = source_crs;
285  }
286  }
287 #endif
288 #endif
289 
290  info->pj = pj;
291 
292  deflen = 0;
293  for (i = 0; i < nopt; i++)
294  deflen += strlen(opt_in[i]) + 2;
295 
296  info->def = G_malloc(deflen + 1);
297 
298  snprintf(buffa, sizeof(buffa), "+%s ", opt_in[0]);
299  strcpy(info->def, buffa);
300  G_free(opt_in[0]);
301 
302  for (i = 1; i < nopt; i++) {
303  snprintf(buffa, sizeof(buffa), "+%s ", opt_in[i]);
304  strcat(info->def, buffa);
305  G_free(opt_in[i]);
306  }
307 
308  return returnval;
309 }
310 
311 static void alloc_options(char *buffa)
312 {
313  size_t nsize;
314 
315  nsize = strlen(buffa) + 1;
316  opt_in[nopt++] = (char *)G_malloc(nsize);
317  snprintf(opt_in[nopt - 1], nsize, "%s", buffa);
318  return;
319 }
320 
321 /**
322  * \brief Create a pj_info struct Co-ordinate System definition from a
323  * string with a sequence of key=value pairs
324  *
325  * This function takes a GRASS- or PROJ style co-ordinate system definition
326  * and processes it to create a pj_info representation for use in
327  * re-projecting with pj_do_proj(). In addition to the parameters passed
328  * to it it may also make reference to the system ellipse.table and
329  * datum.table files if necessary.
330  *
331  * \param info Pointer to a pj_info struct (which must already exist) into
332  * which the co-ordinate system definition will be placed
333  * \param str input string with projection definition
334  * \param in_units_keys PROJ_UNITS-style key-value pairs
335  *
336  * \return -1 on error (unable to initialise PROJ.4)
337  * 1 on success
338  **/
339 
340 int pj_get_string(struct pj_info *info, char *str)
341 {
342  char *s;
343  int i, nsize;
344  char zonebuff[50], buffa[300];
345  int deflen;
346 
347 #ifdef HAVE_PROJ_H
348  PJ *pj;
349  PJ_CONTEXT *pjc;
350 #else
351  projPJ *pj;
352 #endif
353 
354  info->zone = 0;
355  info->proj[0] = '\0';
356  info->meters = 1.0;
357  info->def = NULL;
358  info->srid = NULL;
359  info->pj = NULL;
360 
361  nopt = 0;
362 
363  if ((str == NULL) || (str[0] == '\0')) {
364  /* Null Pointer or empty string is supplied for parameters,
365  * implying latlong projection; just need to set proj
366  * parameter and call pj_init PK */
367  snprintf(info->proj, sizeof(info->proj), "ll");
368  snprintf(buffa, sizeof(buffa), "proj=latlong ellps=WGS84");
369  alloc_options(buffa);
370  }
371  else {
372  /* Parameters have been provided; parse through them but don't
373  * bother with most of the checks in pj_get_kv; assume the
374  * programmer knows what he / she is doing when using this
375  * function rather than reading a PROJ_INFO file PK */
376  s = str;
377  while (s = strtok(s, " \t\n"), s) {
378  if (strncmp(s, "+unfact=", 8) == 0) {
379  s = s + 8;
380  info->meters = atof(s);
381  }
382  else {
383  if (strncmp(s, "+", 1) == 0)
384  ++s;
385  if (nsize = strlen(s), nsize) {
386  if (nopt >= MAX_PARGS) {
387  fprintf(stderr, "nopt = %d, s=%s\n", nopt, str);
389  _("Option input overflowed option table"));
390  }
391 
392  if (strncmp("zone=", s, 5) == 0) {
393  snprintf(zonebuff, sizeof(zonebuff), "%s", s + 5);
394  sscanf(zonebuff, "%d", &(info->zone));
395  }
396 
397  if (strncmp(s, "init=", 5) == 0) {
398  info->srid = G_store(s + 6);
399  }
400 
401  if (strncmp("proj=", s, 5) == 0) {
402  snprintf(info->proj, sizeof(info->proj), "%s", s + 5);
403  if (strcmp(info->proj, "ll") == 0)
404  snprintf(buffa, sizeof(buffa), "proj=latlong");
405  else
406  snprintf(buffa, sizeof(buffa), "%s", s);
407  }
408  else {
409  snprintf(buffa, sizeof(buffa), "%s", s);
410  }
411  alloc_options(buffa);
412  }
413  }
414  s = 0;
415  }
416  }
417 
418 #ifdef HAVE_PROJ_H
419 #if PROJ_VERSION_MAJOR >= 6
420  /* without type=crs, PROJ6 does not recognize what this is,
421  * a crs or some kind of coordinate operation, falling through to
422  * PJ_TYPE_OTHER_COORDINATE_OPERATION */
423  alloc_options("type=crs");
424 #endif
425  pjc = proj_context_create();
426  if (!(pj = proj_create_argv(pjc, nopt, opt_in))) {
427  G_warning(_("Unable to initialize pj cause: %s"),
428  proj_errno_string(proj_context_errno(pjc)));
429  return -1;
430  }
431 
432 #if PROJ_VERSION_MAJOR >= 6
433  if (proj_get_type(pj) == PJ_TYPE_BOUND_CRS) {
434  PJ *source_crs = proj_get_source_crs(pjc, pj);
435  if (source_crs) {
436  proj_destroy(pj);
437  pj = source_crs;
438  }
439  }
440 #endif
441 #else
442  /* Set finder function for locating datum conversion tables PK */
443  pj_set_finder(FINDERFUNC);
444 
445  if (!(pj = pj_init(nopt, opt_in))) {
446  G_warning(_("Unable to initialize pj cause: %s"),
447  pj_strerrno(pj_errno));
448  return -1;
449  }
450 #endif
451  info->pj = pj;
452 
453  deflen = 0;
454  for (i = 0; i < nopt; i++)
455  deflen += strlen(opt_in[i]) + 2;
456 
457  info->def = G_malloc(deflen + 1);
458 
459  snprintf(buffa, sizeof(buffa), "+%s ", opt_in[0]);
460  strcpy(info->def, buffa);
461  G_free(opt_in[0]);
462 
463  for (i = 1; i < nopt; i++) {
464  snprintf(buffa, sizeof(buffa), "+%s ", opt_in[i]);
465  strcat(info->def, buffa);
466  G_free(opt_in[i]);
467  }
468 
469  return 1;
470 }
471 
472 #ifndef HAVE_PROJ_H
473 /* GPJ_get_equivalent_latlong(): only available with PROJ 4 API
474  * with the new PROJ 5+ API, use pjold directly with PJ_FWD/PJ_INV
475  * transformation
476  */
477 
478 /**
479  * \brief Define a latitude / longitude co-ordinate system with the same
480  * ellipsoid and datum parameters as an existing projected system
481  *
482  * This function is useful when projected co-ordinates need to be simply
483  * converted to and from latitude / longitude.
484  *
485  * \param pjnew Pointer to pj_info struct for geographic co-ordinate system
486  * that will be created
487  * \param pjold Pointer to pj_info struct for existing projected co-ordinate
488  * system
489  *
490  * \return 1 on success; -1 if there was an error (i.e. if the PROJ.4
491  * pj_latlong_from_proj() function returned NULL)
492  **/
493 
494 int GPJ_get_equivalent_latlong(struct pj_info *pjnew, struct pj_info *pjold)
495 {
496  char *deftmp;
497 
498  pjnew->meters = 1.;
499  pjnew->zone = 0;
500  pjnew->def = NULL;
501  snprintf(pjnew->proj, sizeof(pjnew->proj), "ll");
502  if ((pjnew->pj = pj_latlong_from_proj(pjold->pj)) == NULL)
503  return -1;
504 
505  deftmp = pj_get_def(pjnew->pj, 1);
506  pjnew->def = G_store(deftmp);
507  pj_dalloc(deftmp);
508 
509  return 1;
510 }
511 #endif
512 
513 /* set_proj_share()
514  * 'finder function' for use with PROJ.4 pj_set_finder() function
515  * this is used to find grids, usually in /usr/share/proj
516  * GRASS no longer provides copies of proj grids in GRIDDIR
517  * -> do not use gisbase/GRIDDIR */
518 
519 const char *set_proj_share(const char *name)
520 {
521  static char *buf = NULL;
522  const char *projshare;
523  static size_t buf_len = 0;
524  size_t len;
525 
526  projshare = getenv("GRASS_PROJSHARE");
527  if (!projshare)
528  return NULL;
529 
530  len = strlen(projshare) + strlen(name) + 2;
531 
532  if (buf_len < len) {
533  if (buf != NULL)
534  G_free(buf);
535  buf_len = len + 20;
536  buf = G_malloc(buf_len);
537  }
538 
539  snprintf(buf, buf_len, "%s/%s", projshare, name);
540 
541  return buf;
542 }
543 
544 /**
545  * \brief Print projection parameters as used by PROJ.4 for input and
546  * output co-ordinate systems
547  *
548  * \param iproj 'Input' co-ordinate system
549  * \param oproj 'Output' co-ordinate system
550  *
551  * \return 1 on success, -1 on error (i.e. if the PROJ-style definition
552  * is NULL for either co-ordinate system)
553  **/
554 
555 int pj_print_proj_params(const struct pj_info *iproj,
556  const struct pj_info *oproj)
557 {
558  char *str;
559 
560  if (iproj) {
561  str = iproj->def;
562  if (str != NULL) {
563  fprintf(stderr, "%s: %s\n", _("Input Projection Parameters"), str);
564  fprintf(stderr, "%s: %.16g\n", _("Input Unit Factor"),
565  iproj->meters);
566  }
567  else
568  return -1;
569  }
570 
571  if (oproj) {
572  str = oproj->def;
573  if (str != NULL) {
574  fprintf(stderr, "%s: %s\n", _("Output Projection Parameters"), str);
575  fprintf(stderr, "%s: %.16g\n", _("Output Unit Factor"),
576  oproj->meters);
577  }
578  else
579  return -1;
580  }
581 
582  return 1;
583 }
#define NULL
Definition: ccmath.h:32
void G_free(void *)
Free allocated memory.
Definition: gis/alloc.c:150
void void void void G_fatal_error(const char *,...) __attribute__((format(printf
void G_warning(const char *,...) __attribute__((format(printf
const char * G_find_key_value(const char *, const struct Key_Value *)
Find given key (case sensitive)
Definition: key_value1.c:85
#define G_malloc(n)
Definition: defs/gis.h:94
char * G_store(const char *)
Copy string to allocated memory.
Definition: strings.c:87
int GPJ_get_default_datum_params_by_name(const char *, char **)
"Last resort" function to retrieve a "default" set of datum parameters for a datum (N....
Definition: proj/datum.c:86
int GPJ__get_datum_params(const struct Key_Value *, char **, char **)
Extract the datum transformation-related parameters from a set of general PROJ_INFO parameters.
Definition: proj/datum.c:173
int GPJ__get_ellipsoid_params(const struct Key_Value *, double *, double *, double *)
Get the ellipsoid parameters from proj keys structure.
Definition: ellipse.c:74
int GPJ_get_equivalent_latlong(struct pj_info *, struct pj_info *)
int pj_get_kv(struct pj_info *info, const struct Key_Value *in_proj_keys, const struct Key_Value *in_units_keys)
Create a pj_info struct Co-ordinate System definition from a set of PROJ_INFO / PROJ_UNITS-style key-...
Definition: get_proj.c:60
#define MAX_PARGS
Definition: get_proj.c:28
int pj_print_proj_params(const struct pj_info *iproj, const struct pj_info *oproj)
Print projection parameters as used by PROJ.4 for input and output co-ordinate systems.
Definition: get_proj.c:555
const char * set_proj_share(const char *name)
Definition: get_proj.c:519
#define FINDERFUNC
Definition: get_proj.c:26
int pj_get_string(struct pj_info *info, char *str)
Create a pj_info struct Co-ordinate System definition from a string with a sequence of key=value pair...
Definition: get_proj.c:340
#define _(str)
Definition: glocale.h:10
const char * name
Definition: named_colr.c:6
#define strcpy
Definition: parson.c:62
Definition: gis.h:534
char ** value
Definition: gis.h:538
int nitems
Definition: gis.h:535
char ** key
Definition: gis.h:537
char proj[100]
Definition: gprojects.h:79
int zone
Definition: gprojects.h:78
char * def
Definition: gprojects.h:80
PJ * pj
Definition: gprojects.h:73
char * srid
Definition: gprojects.h:81
char * wkt
Definition: gprojects.h:82
double meters
Definition: gprojects.h:77
SYMBOL * err(FILE *fp, SYMBOL *s, char *msg)
Definition: symbol/read.c:216