GRASS 8 Programmer's Manual 8.6.0dev(2026)-a3f62a394d
Loading...
Searching...
No Matches
do_proj.c
Go to the documentation of this file.
1/**
2 \file do_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 <string.h>
18#include <ctype.h>
19#include <math.h>
20
21#include <grass/gis.h>
22#include <grass/gprojects.h>
23#include <grass/glocale.h>
24
25/* a couple defines to simplify reading the function */
26#define MULTIPLY_LOOP(x, y, c, m) \
27 do { \
28 for (i = 0; i < c; ++i) { \
29 x[i] *= m; \
30 y[i] *= m; \
31 } \
32 } while (0)
33
34#define DIVIDE_LOOP(x, y, c, m) \
35 do { \
36 for (i = 0; i < c; ++i) { \
37 x[i] /= m; \
38 y[i] /= m; \
39 } \
40 } while (0)
41
42int get_pj_area(const struct pj_info *iproj, double *xmin, double *xmax,
43 double *ymin, double *ymax)
44{
45 struct Cell_head window;
46
47 /* modules must set the current window, do not unset this window here */
48 /* G_unset_window(); */
49 G_get_set_window(&window);
50 *xmin = window.west;
51 *xmax = window.east;
52 *ymin = window.south;
53 *ymax = window.north;
54
55 if (window.proj != PROJECTION_LL) {
56 /* transform to ll equivalent */
57 double estep, nstep;
58 double x[85], y[85];
59 int i;
60 const char *projstr = NULL;
61 char *indef = NULL;
62 struct pj_info oproj, tproj; /* proj parameters */
63
64 oproj.pj = NULL;
65 oproj.proj[0] = '\0';
66 tproj.def = NULL;
67
70
72 if (source_crs) {
73 projstr =
75 if (projstr) {
77 }
79 }
80 }
81 else {
83 if (projstr) {
85 }
86 }
87
88 if (indef == NULL)
89 indef = G_store(iproj->def);
90
91 /* needs +over to properly cross the anti-meridian
92 * the +over switch can be used to disable the default wrapping
93 * of output longitudes to the range -180 to 180 */
94 G_asprintf(&tproj.def, "+proj=pipeline +step +inv %s +over", indef);
95 G_debug(1, "get_pj_area() tproj.def: %s", tproj.def);
97
98 if (tproj.pj == NULL) {
99 G_warning(_("proj_create() failed for '%s'"), tproj.def);
100 G_free(indef);
101 G_free(tproj.def);
103
104 return 0;
105 }
107 if (projstr == NULL) {
108 G_warning(_("proj_create() failed for '%s'"), tproj.def);
109 G_free(indef);
110 G_free(tproj.def);
112
113 return 0;
114 }
115 else {
116 G_debug(1, "proj_create() projstr '%s'", projstr);
117 }
118 G_free(indef);
119
120 /* inpired by gdal/ogr/ogrct.cpp OGRProjCT::ListCoordinateOperations()
121 */
122 estep = (window.east - window.west) / 21.;
123 nstep = (window.north - window.south) / 21.;
124 for (i = 0; i < 20; i++) {
125 x[i] = window.west + estep * (i + 1);
126 y[i] = window.north;
127
128 x[i + 20] = window.west + estep * (i + 1);
129 y[i + 20] = window.south;
130
131 x[i + 40] = window.west;
132 y[i + 40] = window.south + nstep * (i + 1);
133
134 x[i + 60] = window.east;
135 y[i + 60] = window.south + nstep * (i + 1);
136 }
137 x[80] = window.west;
138 y[80] = window.north;
139 x[81] = window.west;
140 y[81] = window.south;
141 x[82] = window.east;
142 y[82] = window.north;
143 x[83] = window.east;
144 y[83] = window.south;
145 x[84] = (window.west + window.east) / 2.;
146 y[84] = (window.north + window.south) / 2.;
147
149
151 G_free(tproj.def);
152
153 *xmin = *xmax = x[84];
154 *ymin = *ymax = y[84];
155 for (i = 0; i < 84; i++) {
156 if (*xmin > x[i])
157 *xmin = x[i];
158 if (*xmax < x[i])
159 *xmax = x[i];
160 if (*ymin > y[i])
161 *ymin = y[i];
162 if (*ymax < y[i])
163 *ymax = y[i];
164 }
165
166 /* The west longitude is generally lower than the east longitude,
167 * except for areas of interest that go across the anti-meridian.
168 * do not reduce global coverage to a small north-south strip
169 */
170 if (*xmin < -180 && *xmax < 180 && *xmin + 360 > *xmax) {
171 /* must be crossing the anti-meridian at 180W */
172 *xmin += 360;
173 }
174 else if (*xmax > 180 && *xmin > -180 && *xmax - 360 < *xmin) {
175 /* must be crossing the anti-meridian at 180E */
176 *xmax -= 360;
177 }
178
179 G_debug(1, "input window north: %.8f", window.north);
180 G_debug(1, "input window south: %.8f", window.south);
181 G_debug(1, "input window east: %.8f", window.east);
182 G_debug(1, "input window west: %.8f", window.west);
183
184 G_debug(1, "transformed xmin: %.8f", *xmin);
185 G_debug(1, "transformed xmax: %.8f", *xmax);
186 G_debug(1, "transformed ymin: %.8f", *ymin);
187 G_debug(1, "transformed ymax: %.8f", *ymax);
188
189 /* test valid values, as in
190 * gdal/ogr/ogrct.cpp
191 * OGRCoordinateTransformationOptions::SetAreaOfInterest()
192 */
193 if (fabs(*xmin) > 180) {
194 G_warning(_("Invalid west longitude %g"), *xmin);
195 return 0;
196 }
197 if (fabs(*xmax) > 180) {
198 G_warning(_("Invalid east longitude %g"), *xmax);
199 return 0;
200 }
201 if (fabs(*ymin) > 90) {
202 G_warning(_("Invalid south latitude %g"), *ymin);
203 return 0;
204 }
205 if (fabs(*ymax) > 90) {
206 G_warning(_("Invalid north latitude %g"), *ymax);
207 return 0;
208 }
209 if (*ymin > *ymax) {
210 G_warning(_("South %g is larger than north %g"), *ymin, *ymax);
211 return 0;
212 }
213 }
214 G_debug(1, "get_pj_area(): xmin %g, xmax %g, ymin %g, ymax %g", *xmin,
215 *xmax, *ymin, *ymax);
216
217 return 1;
218}
219
221{
222 char *pj_type = NULL;
223
224 switch (proj_get_type(pj)) {
225 case PJ_TYPE_UNKNOWN:
226 G_asprintf(&pj_type, "unknown");
227 break;
229 G_asprintf(&pj_type, "ellipsoid");
230 break;
232 G_asprintf(&pj_type, "prime meridian");
233 break;
235 G_asprintf(&pj_type, "geodetic reference frame");
236 break;
238 G_asprintf(&pj_type, "dynamic geodetic reference frame");
239 break;
241 G_asprintf(&pj_type, "vertical reference frame");
242 break;
244 G_asprintf(&pj_type, "dynamic vertical reference frame");
245 break;
247 G_asprintf(&pj_type, "datum ensemble");
248 break;
249
250 /** Abstract type, not returned by proj_get_type() */
251 case PJ_TYPE_CRS:
252 G_asprintf(&pj_type, "crs");
253 break;
255 G_asprintf(&pj_type, "geodetic crs");
256 break;
258 G_asprintf(&pj_type, "geocentric crs");
259 break;
260
261 /** proj_get_type() will never return that type, but
262 * PJ_TYPE_GEOGRAPHIC_2D_CRS or PJ_TYPE_GEOGRAPHIC_3D_CRS. */
264 G_asprintf(&pj_type, "geographic crs");
265 break;
267 G_asprintf(&pj_type, "geographic 2D crs");
268 break;
270 G_asprintf(&pj_type, "geographic 3D crs");
271 break;
273 G_asprintf(&pj_type, "vertical crs");
274 break;
276 G_asprintf(&pj_type, "projected crs");
277 break;
279 G_asprintf(&pj_type, "compound crs");
280 break;
282 G_asprintf(&pj_type, "temporal crs");
283 break;
285 G_asprintf(&pj_type, "engineering crs");
286 break;
288 G_asprintf(&pj_type, "bound crs");
289 break;
291 G_asprintf(&pj_type, "other crs");
292 break;
294 G_asprintf(&pj_type, "conversion");
295 break;
297 G_asprintf(&pj_type, "transformation");
298 break;
300 G_asprintf(&pj_type, "concatenated operation");
301 break;
303 G_asprintf(&pj_type, "other coordinate operation");
304 break;
305 default:
306 G_asprintf(&pj_type, "unknown");
307 break;
308 }
309
310 return pj_type;
311}
312
313PJ *get_pj_object(const struct pj_info *in_gpj, char **in_defstr)
314{
315 PJ *in_pj = NULL;
316
317 *in_defstr = NULL;
318
319 /* 1. SRID, 2. WKT, 3. standard pj from pj_get_kv */
320 if (in_gpj->srid) {
321 G_debug(1, "Trying SRID '%s' ...", in_gpj->srid);
323 if (!in_pj) {
324 G_warning(_("Unrecognized SRID '%s'"), in_gpj->srid);
325 }
326 else {
327 *in_defstr = G_store(in_gpj->srid);
328 /* PROJ will do the unit conversion if set up from srid
329 * -> disable unit conversion for GPJ_transform */
330 /* ugly hack */
331 ((struct pj_info *)in_gpj)->meters = 1;
332 }
333 }
334 if (!in_pj && in_gpj->wkt) {
335 G_debug(1, "Trying WKT '%s' ...", in_gpj->wkt);
337 if (!in_pj) {
338 G_warning(_("Unrecognized WKT '%s'"), in_gpj->wkt);
339 }
340 else {
341 *in_defstr = G_store(in_gpj->wkt);
342 /* PROJ will do the unit conversion if set up from wkt
343 * -> disable unit conversion for GPJ_transform */
344 /* ugly hack */
345 ((struct pj_info *)in_gpj)->meters = 1;
346 }
347 }
348 if (!in_pj && in_gpj->pj) {
351 if (*in_defstr && !**in_defstr)
352 *in_defstr = NULL;
353 }
354
355 if (!in_pj) {
356 G_warning(_("Unable to create PROJ object"));
357
358 return NULL;
359 }
360
361 /* Even Rouault:
362 * if info_in->def contains a +towgs84/+nadgrids clause,
363 * this pipeline would apply it, whereas you probably only want
364 * the reverse projection, and no datum shift.
365 * The easiest would probably to mess up with the PROJ string.
366 * Otherwise with the PROJ API, you could
367 * instantiate a PJ object from the string,
368 * check if it is a BoundCRS with proj_get_source_crs(),
369 * and in that case, take the source CRS with proj_get_source_crs(),
370 * and do the inverse transform on it */
371
373 PJ *source_crs;
374
375 G_debug(1, "found bound crs");
377 if (source_crs) {
378 *in_defstr =
380 if (*in_defstr && !**in_defstr)
381 *in_defstr = NULL;
383 }
384 }
385
386 return in_pj;
387}
388
389/**
390 * \brief Create a PROJ transformation object to transform coordinates
391 * from an input SRS to an output SRS
392 *
393 * After the transformation has been initialized with this function,
394 * coordinates can be transformed from input SRS to output SRS with
395 * GPJ_transform() and direction = PJ_FWD, and back from output SRS to
396 * input SRS with direction = OJ_INV.
397 * If coordinates should be transformed between the input SRS and its
398 * latlong equivalent, an uninitialized info_out with
399 * info_out->pj = NULL can be passed to the function. In this case,
400 * coordinates will be transformed between the input SRS and its
401 * latlong equivalent, and for PROJ 5+, the transformation object is
402 * created accordingly
403 *
404 * PROJ 5+:
405 * info_in->pj must not be null
406 * if info_out->pj is null, assume info_out to be the ll equivalent
407 * of info_in
408 * create info_trans as conversion from info_in to its ll equivalent
409 * NOTE: this is the inverse of the logic of PROJ 5 which by default
410 * converts from ll to a given SRS, not from a given SRS to ll
411 * thus PROJ 5+ itself uses an inverse transformation in the
412 * first step of the pipeline for proj_create_crs_to_crs()
413 * if info_trans->def is not NULL, this pipeline definition will be
414 * used to create a transformation object
415 *
416 * \param info_in pointer to pj_info struct for input co-ordinate system
417 * \param info_out pointer to pj_info struct for output co-ordinate system
418 * \param info_trans pointer to pj_info struct for a transformation object (PROJ
419 *5+)
420 *
421 * \return 1 on success, -1 on failure
422 **/
424 const struct pj_info *info_out,
425 struct pj_info *info_trans)
426{
427 if (info_in->pj == NULL)
428 G_fatal_error(_("Input coordinate system is NULL"));
429
430 if (info_in->def == NULL)
431 G_fatal_error(_("Input coordinate system definition is NULL"));
432
433 /* PROJ6+: enforce axis order easting, northing
434 * +axis=enu (works with proj-4.8+) */
435
436 info_trans->pj = NULL;
437 info_trans->meters = 1.;
438 info_trans->zone = 0;
439 snprintf(info_trans->proj, sizeof(info_trans->proj), "pipeline");
440
441 /* user-provided pipeline */
442 if (info_trans->def) {
443 const char *projstr;
444
445 /* info_in->pj, info_in->proj, info_out->pj, info_out->proj
446 * must be set */
447 if (!info_in->pj || !info_in->proj[0] || !info_out->pj ||
448 !info_out->proj[0]) {
449 G_warning(_(
450 "A custom pipeline requires input and output projection info"));
451
452 return -1;
453 }
454
455 /* create a pj from user-defined transformation pipeline */
457 if (info_trans->pj == NULL) {
458 G_warning(_("proj_create() failed for '%s'"), info_trans->def);
459
460 return -1;
461 }
463 if (projstr == NULL) {
464 G_warning(_("proj_create() failed for '%s'"), info_trans->def);
465
466 return -1;
467 }
468 else {
469 /* make sure axis order is easting, northing
470 * proj_normalize_for_visualization() does not work here
471 * because source and target CRS are unknown to PROJ
472 * remove any "+step +proj=axisswap +order=2,1" ?
473 * */
474 info_trans->def = G_store(projstr);
475
476 if (strstr(info_trans->def, "axisswap")) {
477 G_warning(
478 _("The transformation pipeline contains an '%s' step. "
479 "Remove this step if easting and northing are swapped in "
480 "the output."),
481 "axisswap");
482 }
483
484 G_debug(1, "proj_create() pipeline: %s", info_trans->def);
485
486 /* the user-provided PROJ pipeline is supposed to do
487 * all the needed unit conversions */
488 /* ugly hack */
489 ((struct pj_info *)info_in)->meters = 1;
490 ((struct pj_info *)info_out)->meters = 1;
491 }
492 }
493 /* if no output CRS is defined,
494 * assume info_out to be ll equivalent of info_in */
495 else if (info_out->pj == NULL) {
496 const char *projstr = NULL;
497 char *indef = NULL;
498
499 /* get PROJ-style definition */
500 indef = G_store(info_in->def);
501 G_debug(1, "ll equivalent definition: %s", indef);
502
503 /* what about axis order?
504 * is it always enu?
505 * probably yes, as long as there is no +proj=axisswap step */
506 G_asprintf(&(info_trans->def), "+proj=pipeline +step +inv %s", indef);
508 if (info_trans->pj == NULL) {
509 G_warning(_("proj_create() failed for '%s'"), info_trans->def);
510 G_free(indef);
511
512 return -1;
513 }
515 if (projstr == NULL) {
516 G_warning(_("proj_create() failed for '%s'"), info_trans->def);
517 G_free(indef);
518
519 return -1;
520 }
521 G_free(indef);
522 }
523 /* input and output CRS are available */
524 else if (info_in->def && info_out->pj && info_out->def) {
525 char *indef = NULL, *outdef = NULL;
526 char *insrid = NULL, *outsrid = NULL;
527 PJ *in_pj, *out_pj;
531 double xmin, xmax, ymin, ymax;
532 int op_count = 0, op_count_area = 0;
533
534 /* get pj_area */
535 /* do it here because get_pj_area() will use
536 * the PROJ definition for simple transformation to the
537 * ll equivalent and we need to do unit conversion */
538 if (get_pj_area(info_in, &xmin, &xmax, &ymin, &ymax)) {
540 proj_area_set_bbox(pj_area, xmin, ymin, xmax, ymax);
541 }
542 else {
543 G_warning(_("Unable to determine area of interest for '%s'"),
544 info_in->def);
545
546 return -1;
547 }
548
549 G_debug(1, "source proj string: %s", info_in->def);
550 G_debug(1, "source type: %s", get_pj_type_string(info_in->pj));
551
552 /* PROJ6+: EPSG must be uppercase EPSG */
553 if (info_in->srid) {
554 if (strncmp(info_in->srid, "epsg", 4) == 0) {
556 G_free(info_in->srid);
557 ((struct pj_info *)info_in)->srid = insrid;
558 insrid = NULL;
559 }
560 }
561
563 if (in_pj == NULL || indef == NULL) {
564 G_warning(_("Input CRS not available for '%s'"), info_in->def);
565
566 return -1;
567 }
568 G_debug(1, "Input CRS definition: %s", indef);
569
570 G_debug(1, "target proj string: %s", info_out->def);
571 G_debug(1, "target type: %s", get_pj_type_string(info_out->pj));
572
573 /* PROJ6+: EPSG must be uppercase EPSG */
574 if (info_out->srid) {
575 if (strncmp(info_out->srid, "epsg", 4) == 0) {
577 G_free(info_out->srid);
578 ((struct pj_info *)info_out)->srid = outsrid;
579 outsrid = NULL;
580 }
581 }
582
584 if (out_pj == NULL || outdef == NULL) {
585 G_warning(_("Output CRS not available for '%s'"), info_out->def);
586
587 return -1;
588 }
589 G_debug(1, "Output CRS definition: %s", outdef);
590
591 /* check number of operations */
592
595 /* proj_create_operations() works only if both source_crs
596 * and target_crs are found in the proj db
597 * if any is not found, proj can not get a list of operations
598 * and we have to take care of datumshift manually */
599 /* list all operations irrespecitve of area and
600 * grid availability */
604
605 op_count = 0;
606 if (op_list)
608 if (op_count > 1) {
609 int i;
610
611 G_important_message(_("Found %d possible transformations"),
612 op_count);
613 for (i = 0; i < op_count; i++) {
614 const char *area_of_use, *projstr;
615 double e, w, s, n;
617 PJ *op, *op_norm;
618
621
622 if (!op_norm) {
623 G_warning(_("proj_normalize_for_visualization() failed for "
624 "operation %d"),
625 i + 1);
626 }
627 else {
629 op = op_norm;
630 }
631
633 proj_get_area_of_use(NULL, op, &w, &s, &e, &n, &area_of_use);
634 G_important_message("************************");
635 G_important_message(_("Operation %d:"), i + 1);
636 if (pj_info.description) {
637 G_important_message(_("Description: %s"),
638 pj_info.description);
639 }
640 if (area_of_use) {
642 G_important_message(_("Area of use: %s"), area_of_use);
643 }
644 if (pj_info.accuracy > 0) {
646 G_important_message(_("Accuracy within area of use: %g m"),
647 pj_info.accuracy);
648 }
649 const char *str = proj_get_remarks(op);
650
651 if (str && *str) {
653 G_important_message(_("Remarks: %s"), str);
654 }
655 str = proj_get_scope(op);
656 if (str && *str) {
658 G_important_message(_("Scope: %s"), str);
659 }
660
662 if (projstr) {
664 G_important_message(_("PROJ string:"));
666 }
668 }
669 G_important_message("************************");
670
671 G_important_message(_("See also output of:"));
672 G_important_message("projinfo -o PROJ -s \"%s\" -t \"%s\"", indef,
673 outdef);
674 G_important_message(_("Please provide the appropriate PROJ string "
675 "with the %s option"),
676 "pipeline");
677 G_important_message("************************");
678 }
679
680 if (op_list)
682
683 /* following code copied from proj_create_crs_to_crs_from_pj()
684 * in proj src/4D_api.cpp
685 * using PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION
686 * this can cause problems and artefacts
687 * switch to PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT
688 * in case of problems
689 * but results can be different from gdalwarp:
690 * shifted geolocation in some areas
691 * in these cases there is no right or wrong,
692 * different pipelines are all regarded as valid by PROJ
693 * depending on the area of interest
694 *
695 * see also:
696 * OGRProjCT::ListCoordinateOperations() in GDAL ogr/ogrct.cpp
697 * create_operation_to_geog_crs() in PROJ src/4D_api.cpp
698 * proj_create_crs_to_crs_from_pj() in PROJ src/4D_api.cpp
699 * proj_operation_factory_context_set_spatial_criterion() in PROJ
700 * src/iso19111/c_api.cpp
701 * */
702
703 /* now use the current region as area of interest */
707 PJ_DEFAULT_CTX, operation_ctx, xmin, ymin, xmax, ymax);
711 /* from GDAL OGRProjCT::ListCoordinateOperations() */
717
718 /* The operations are sorted with the most relevant ones first:
719 * by descending area (intersection of the transformation area
720 * with the area of interest, or intersection of the
721 * transformation with the area of use of the CRS),
722 * and by increasing accuracy.
723 * Operations with unknown accuracy are sorted last,
724 * whatever their area.
725 */
729 op_count_area = 0;
730 if (op_list)
732 if (op_count_area == 0) {
733 /* no operations */
734 info_trans->pj = NULL;
735 }
736 else if (op_count_area == 1) {
738 }
739 else { /* op_count_area > 1 */
740 /* can't use pj_create_prepared_operations()
741 * this is a PROJ-internal function
742 * trust the sorting of PROJ and use the first one */
744 }
745 if (op_list)
747
748 /* try proj_create_crs_to_crs() */
749 /*
750 G_debug(1, "trying %s to %s", indef, outdef);
751 */
752
753 /* proj_create_crs_to_crs() does not work because it calls
754 * proj_create_crs_to_crs_from_pj() which calls
755 * proj_operation_factory_context_set_spatial_criterion()
756 * with PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION
757 * instead of
758 * PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT
759 *
760 * fixed in PROJ master, probably available with PROJ 7.3.x */
761
762 /*
763 info_trans->pj = proj_create_crs_to_crs(PJ_DEFAULT_CTX,
764 indef,
765 outdef,
766 pj_area);
767 */
768
769 if (in_pj)
771 if (out_pj)
773
774 if (info_trans->pj) {
775 const char *projstr;
776 PJ *pj_norm = NULL;
777
778 G_debug(1, "proj_create_crs_to_crs() succeeded with PROJ%d",
780
781 projstr =
783
784 info_trans->def = G_store(projstr);
785
786 if (projstr) {
787 /* make sure axis order is easting, northing
788 * proj_normalize_for_visualization() requires
789 * source and target CRS
790 * -> does not work with ll equivalent of input:
791 * no target CRS in +proj=pipeline +step +inv %s */
793 info_trans->pj);
794
795 if (!pj_norm) {
796 G_warning(
797 _("proj_normalize_for_visualization() failed for '%s'"),
798 info_trans->def);
799 }
800 else {
801 projstr =
803 if (projstr && *projstr) {
805 info_trans->pj = pj_norm;
806 info_trans->def = G_store(projstr);
807 }
808 else {
810 G_warning(_("No PROJ definition for normalized version "
811 "of '%s'"),
812 info_trans->def);
813 }
814 }
815 G_important_message(_("Selected PROJ pipeline:"));
816 G_important_message(_("%s"), info_trans->def);
817 G_important_message("************************");
818 }
819 else {
821 info_trans->pj = NULL;
822 }
823 }
824
825 if (pj_area)
827
828 if (insrid)
829 G_free(insrid);
830 if (outsrid)
832 G_free(indef);
833 G_free(outdef);
834 }
835 if (info_trans->pj == NULL) {
836 G_warning(_("proj_create() failed for '%s'"), info_trans->def);
837
838 return -1;
839 }
840
841 return 1;
842}
843
844/* TODO: rename pj_ to GPJ_ to avoid symbol clash with PROJ lib */
845
846/**
847 * \brief Re-project a point between two co-ordinate systems using a
848 * transformation object prepared with GPJ_prepare_pj()
849 *
850 * This function takes pointers to three pj_info structures as arguments,
851 * and projects a point between the input and output co-ordinate system.
852 * The pj_info structure info_trans must have been initialized with
853 * GPJ_init_transform().
854 * The direction determines if a point is projected from input CRS to
855 * output CRS (PJ_FWD) or from output CRS to input CRS (PJ_INV).
856 * The easting, northing, and height of the point are contained in the
857 * pointers passed to the function; these will be overwritten by the
858 * coordinates of the transformed point.
859 *
860 * \param info_in pointer to pj_info struct for input co-ordinate system
861 * \param info_out pointer to pj_info struct for output co-ordinate system
862 * \param info_trans pointer to pj_info struct for a transformation object (PROJ
863 *5+) \param dir direction of the transformation (PJ_FWD or PJ_INV) \param x
864 *Pointer to a double containing easting or longitude \param y Pointer to a
865 *double containing northing or latitude \param z Pointer to a double containing
866 *height, or NULL
867 *
868 * \return Return value from PROJ proj_trans() function
869 **/
870
871int GPJ_transform(const struct pj_info *info_in, const struct pj_info *info_out,
872 const struct pj_info *info_trans, int dir, double *x,
873 double *y, double *z)
874{
875 int ok = 0;
876
878 PJ_COORD c;
879 double METERS_in = 1.0, METERS_out = 1.0;
880
881 if (info_in->pj == NULL)
882 G_fatal_error(_("No input projection"));
883
884 if (info_trans->pj == NULL)
885 G_fatal_error(_("No transformation object"));
886
888 if (dir == PJ_FWD) {
889 /* info_in -> info_out */
890 METERS_in = info_in->meters;
891 in_is_ll = !strncmp(info_in->proj, "ll", 2);
892 /* PROJ 6+: conversion to radians is not always needed:
893 * if proj_angular_input(info_trans->pj, dir) == 1
894 * -> convert from degrees to radians */
895 if (in_is_ll && proj_angular_input(info_trans->pj, dir) == 0) {
896 in_deg2rad = 0;
897 }
898 if (info_out->pj) {
899 METERS_out = info_out->meters;
900 out_is_ll = !strncmp(info_out->proj, "ll", 2);
901 /* PROJ 6+: conversion to radians is not always needed:
902 * if proj_angular_input(info_trans->pj, dir) == 1
903 * -> convert from degrees to radians */
904 if (out_is_ll && proj_angular_output(info_trans->pj, dir) == 0) {
905 out_rad2deg = 0;
906 }
907 }
908 else {
909 METERS_out = 1.0;
910 out_is_ll = 1;
911 }
912 }
913 else {
914 /* info_out -> info_in */
915 METERS_out = info_in->meters;
916 out_is_ll = !strncmp(info_in->proj, "ll", 2);
917 /* PROJ 6+: conversion to radians is not always needed:
918 * if proj_angular_input(info_trans->pj, dir) == 1
919 * -> convert from degrees to radians */
920 if (out_is_ll && proj_angular_output(info_trans->pj, dir) == 0) {
921 out_rad2deg = 0;
922 }
923 if (info_out->pj) {
924 METERS_in = info_out->meters;
925 in_is_ll = !strncmp(info_out->proj, "ll", 2);
926 /* PROJ 6+: conversion to radians is not always needed:
927 * if proj_angular_input(info_trans->pj, dir) == 1
928 * -> convert from degrees to radians */
929 if (in_is_ll && proj_angular_input(info_trans->pj, dir) == 0) {
930 in_deg2rad = 0;
931 }
932 }
933 else {
934 METERS_in = 1.0;
935 in_is_ll = 1;
936 }
937 }
938
939 /* prepare */
940 if (in_is_ll) {
941 if (in_deg2rad) {
942 /* convert degrees to radians */
943 c.lpzt.lam = (*x) / RAD_TO_DEG;
944 c.lpzt.phi = (*y) / RAD_TO_DEG;
945 }
946 else {
947 c.lpzt.lam = (*x);
948 c.lpzt.phi = (*y);
949 }
950 c.lpzt.z = 0;
951 if (z)
952 c.lpzt.z = *z;
953 c.lpzt.t = 0;
954 }
955 else {
956 /* convert to meters */
957 c.xyzt.x = *x * METERS_in;
958 c.xyzt.y = *y * METERS_in;
959 c.xyzt.z = 0;
960 if (z)
961 c.xyzt.z = *z;
962 c.xyzt.t = 0;
963 }
964
965 G_debug(1, "c.xyzt.x: %g", c.xyzt.x);
966 G_debug(1, "c.xyzt.y: %g", c.xyzt.y);
967 G_debug(1, "c.xyzt.z: %g", c.xyzt.z);
968
969 /* transform */
970 c = proj_trans(info_trans->pj, dir, c);
971 ok = proj_errno(info_trans->pj);
972
973 if (ok < 0) {
974 G_warning(_("proj_trans() failed: %s"), proj_errno_string(ok));
975 return ok;
976 }
977
978 /* output */
979 if (out_is_ll) {
980 /* convert to degrees */
981 if (out_rad2deg) {
982 /* convert radians to degrees */
983 *x = c.lp.lam * RAD_TO_DEG;
984 *y = c.lp.phi * RAD_TO_DEG;
985 }
986 else {
987 *x = c.lp.lam;
988 *y = c.lp.phi;
989 }
990 if (z)
991 *z = c.lpzt.z;
992 }
993 else {
994 /* convert to map units */
995 *x = c.xyzt.x / METERS_out;
996 *y = c.xyzt.y / METERS_out;
997 if (z)
998 *z = c.xyzt.z;
999 }
1000
1001 return ok;
1002}
1003
1004/**
1005 * \brief Re-project an array of points between two co-ordinate systems
1006 * using a transformation object prepared with GPJ_prepare_pj()
1007 *
1008 * This function takes pointers to three pj_info structures as arguments,
1009 * and projects an array of pointd between the input and output
1010 * co-ordinate system. The pj_info structure info_trans must have been
1011 * initialized with GPJ_init_transform().
1012 * The direction determines if a point is projected from input CRS to
1013 * output CRS (PJ_FWD) or from output CRS to input CRS (PJ_INV).
1014 * The easting, northing, and height of the point are contained in the
1015 * pointers passed to the function; these will be overwritten by the
1016 * coordinates of the transformed point.
1017 *
1018 * \param info_in pointer to pj_info struct for input co-ordinate system
1019 * \param info_out pointer to pj_info struct for output co-ordinate system
1020 * \param info_trans pointer to pj_info struct for a transformation object (PROJ
1021 *5+) \param dir direction of the transformation (PJ_FWD or PJ_INV) \param x
1022 *pointer to an array of type double containing easting or longitude \param y
1023 *pointer to an array of type double containing northing or latitude \param z
1024 *pointer to an array of type double containing height, or NULL \param n number
1025 *of points in the arrays to be transformed
1026 *
1027 * \return Return value from PROJ proj_trans() function
1028 **/
1029
1031 const struct pj_info *info_out,
1032 const struct pj_info *info_trans, int dir, double *x,
1033 double *y, double *z, int n)
1034{
1035 int ok;
1036 int i;
1037 int has_z = 1;
1038
1039 /* PROJ 5+ variant */
1041 PJ_COORD c;
1042 double METERS_in = 1.0, METERS_out = 1.0;
1043
1044 if (info_trans->pj == NULL)
1045 G_fatal_error(_("No transformation object"));
1046
1047 in_deg2rad = out_rad2deg = 1;
1048 if (dir == PJ_FWD) {
1049 /* info_in -> info_out */
1050 METERS_in = info_in->meters;
1051 in_is_ll = !strncmp(info_in->proj, "ll", 2);
1052 /* PROJ 6+: conversion to radians is not always needed:
1053 * if proj_angular_input(info_trans->pj, dir) == 1
1054 * -> convert from degrees to radians */
1055 if (in_is_ll && proj_angular_input(info_trans->pj, dir) == 0) {
1056 in_deg2rad = 0;
1057 }
1058 if (info_out->pj) {
1059 METERS_out = info_out->meters;
1060 out_is_ll = !strncmp(info_out->proj, "ll", 2);
1061 /* PROJ 6+: conversion to radians is not always needed:
1062 * if proj_angular_input(info_trans->pj, dir) == 1
1063 * -> convert from degrees to radians */
1064 if (out_is_ll && proj_angular_output(info_trans->pj, dir) == 0) {
1065 out_rad2deg = 0;
1066 }
1067 }
1068 else {
1069 METERS_out = 1.0;
1070 out_is_ll = 1;
1071 }
1072 }
1073 else {
1074 /* info_out -> info_in */
1075 METERS_out = info_in->meters;
1076 out_is_ll = !strncmp(info_in->proj, "ll", 2);
1077 /* PROJ 6+: conversion to radians is not always needed:
1078 * if proj_angular_input(info_trans->pj, dir) == 1
1079 * -> convert from degrees to radians */
1080 if (out_is_ll && proj_angular_output(info_trans->pj, dir) == 0) {
1081 out_rad2deg = 0;
1082 }
1083 if (info_out->pj) {
1084 METERS_in = info_out->meters;
1085 in_is_ll = !strncmp(info_out->proj, "ll", 2);
1086 /* PROJ 6+: conversion to degrees is not always needed:
1087 * if proj_angular_output(info_trans->pj, dir) == 1
1088 * -> convert from degrees to radians */
1089 if (in_is_ll && proj_angular_input(info_trans->pj, dir) == 0) {
1090 in_deg2rad = 0;
1091 }
1092 }
1093 else {
1094 METERS_in = 1.0;
1095 in_is_ll = 1;
1096 }
1097 }
1098
1099 if (z == NULL) {
1100 z = G_malloc(sizeof(double) * n);
1101 /* they say memset is only guaranteed for chars ;-( */
1102 for (i = 0; i < n; i++)
1103 z[i] = 0.0;
1104 has_z = 0;
1105 }
1106 ok = 0;
1107 if (in_is_ll) {
1108 c.lpzt.t = 0;
1109 if (out_is_ll) {
1110 /* what is more costly ?
1111 * calling proj_trans for each point
1112 * or having three loops over all points ?
1113 * proj_trans_array() itself calls proj_trans() in a loop
1114 * -> one loop over all points is better than
1115 * three loops over all points
1116 */
1117 for (i = 0; i < n; i++) {
1118 if (in_deg2rad) {
1119 /* convert degrees to radians */
1120 c.lpzt.lam = x[i] / RAD_TO_DEG;
1121 c.lpzt.phi = y[i] / RAD_TO_DEG;
1122 }
1123 else {
1124 c.lpzt.lam = x[i];
1125 c.lpzt.phi = y[i];
1126 }
1127 c.lpzt.z = z[i];
1128 c = proj_trans(info_trans->pj, dir, c);
1129 if ((ok = proj_errno(info_trans->pj)) < 0)
1130 break;
1131 if (out_rad2deg) {
1132 /* convert radians to degrees */
1133 x[i] = c.lp.lam * RAD_TO_DEG;
1134 y[i] = c.lp.phi * RAD_TO_DEG;
1135 }
1136 else {
1137 x[i] = c.lp.lam;
1138 y[i] = c.lp.phi;
1139 }
1140 }
1141 }
1142 else {
1143 for (i = 0; i < n; i++) {
1144 if (in_deg2rad) {
1145 /* convert degrees to radians */
1146 c.lpzt.lam = x[i] / RAD_TO_DEG;
1147 c.lpzt.phi = y[i] / RAD_TO_DEG;
1148 }
1149 else {
1150 c.lpzt.lam = x[i];
1151 c.lpzt.phi = y[i];
1152 }
1153 c.lpzt.z = z[i];
1154 c = proj_trans(info_trans->pj, dir, c);
1155 if ((ok = proj_errno(info_trans->pj)) < 0)
1156 break;
1157
1158 /* convert to map units */
1159 x[i] = c.xy.x / METERS_out;
1160 y[i] = c.xy.y / METERS_out;
1161 }
1162 }
1163 }
1164 else {
1165 c.xyzt.t = 0;
1166 if (out_is_ll) {
1167 for (i = 0; i < n; i++) {
1168 /* convert to meters */
1169 c.xyzt.x = x[i] * METERS_in;
1170 c.xyzt.y = y[i] * METERS_in;
1171 c.xyzt.z = z[i];
1172 c = proj_trans(info_trans->pj, dir, c);
1173 if ((ok = proj_errno(info_trans->pj)) < 0)
1174 break;
1175 if (out_rad2deg) {
1176 /* convert radians to degrees */
1177 x[i] = c.lp.lam * RAD_TO_DEG;
1178 y[i] = c.lp.phi * RAD_TO_DEG;
1179 }
1180 else {
1181 x[i] = c.lp.lam;
1182 y[i] = c.lp.phi;
1183 }
1184 }
1185 }
1186 else {
1187 for (i = 0; i < n; i++) {
1188 /* convert to meters */
1189 c.xyzt.x = x[i] * METERS_in;
1190 c.xyzt.y = y[i] * METERS_in;
1191 c.xyzt.z = z[i];
1192 c = proj_trans(info_trans->pj, dir, c);
1193 if ((ok = proj_errno(info_trans->pj)) < 0)
1194 break;
1195 /* convert to map units */
1196 x[i] = c.xy.x / METERS_out;
1197 y[i] = c.xy.y / METERS_out;
1198 }
1199 }
1200 }
1201 if (!has_z)
1202 G_free(z);
1203
1204 if (ok < 0) {
1205 G_warning(_("proj_trans() failed: %s"), proj_errno_string(ok));
1206 }
1207
1208 return ok;
1209}
1210
1211/*
1212 * old API, to be deleted
1213 */
1214
1215/**
1216 * \brief Re-project a point between two co-ordinate systems
1217 *
1218 * This function takes pointers to two pj_info structures as arguments,
1219 * and projects a point between the co-ordinate systems represented by them.
1220 * The easting and northing of the point are contained in two pointers passed
1221 * to the function; these will be overwritten by the co-ordinates of the
1222 * re-projected point.
1223 *
1224 * \param x Pointer to a double containing easting or longitude
1225 * \param y Pointer to a double containing northing or latitude
1226 * \param info_in pointer to pj_info struct for input co-ordinate system
1227 * \param info_out pointer to pj_info struct for output co-ordinate system
1228 *
1229 * \return Return value from PROJ proj_trans() function
1230 **/
1231
1232int pj_do_proj(double *x, double *y, const struct pj_info *info_in,
1233 const struct pj_info *info_out)
1234{
1235 int ok;
1236
1237 struct pj_info info_trans;
1238 PJ_COORD c;
1239 double METERS_in = 1.0, METERS_out = 1.0;
1240
1242 return -1;
1243 }
1244
1245 METERS_in = info_in->meters;
1246 METERS_out = info_out->meters;
1247
1248 if (strncmp(info_in->proj, "ll", 2) == 0) {
1249 /* convert to radians */
1250 c.lpzt.lam = (*x) / RAD_TO_DEG;
1251 c.lpzt.phi = (*y) / RAD_TO_DEG;
1252 c.lpzt.z = 0;
1253 c.lpzt.t = 0;
1254 c = proj_trans(info_trans.pj, PJ_FWD, c);
1255 ok = proj_errno(info_trans.pj);
1256
1257 if (strncmp(info_out->proj, "ll", 2) == 0) {
1258 /* convert to degrees */
1259 *x = c.lp.lam * RAD_TO_DEG;
1260 *y = c.lp.phi * RAD_TO_DEG;
1261 }
1262 else {
1263 /* convert to map units */
1264 *x = c.xy.x / METERS_out;
1265 *y = c.xy.y / METERS_out;
1266 }
1267 }
1268 else {
1269 /* convert to meters */
1270 c.xyzt.x = *x * METERS_in;
1271 c.xyzt.y = *y * METERS_in;
1272 c.xyzt.z = 0;
1273 c.xyzt.t = 0;
1274 c = proj_trans(info_trans.pj, PJ_FWD, c);
1275 ok = proj_errno(info_trans.pj);
1276
1277 if (strncmp(info_out->proj, "ll", 2) == 0) {
1278 /* convert to degrees */
1279 *x = c.lp.lam * RAD_TO_DEG;
1280 *y = c.lp.phi * RAD_TO_DEG;
1281 }
1282 else {
1283 /* convert to map units */
1284 *x = c.xy.x / METERS_out;
1285 *y = c.xy.y / METERS_out;
1286 }
1287 }
1289
1290 if (ok < 0) {
1291 G_warning(_("proj_trans() failed: %d"), ok);
1292 }
1293 return ok;
1294}
1295
1296/**
1297 * \brief Re-project an array of points between two co-ordinate systems with
1298 * optional ellipsoidal height conversion
1299 *
1300 * This function takes pointers to two pj_info structures as arguments,
1301 * and projects an array of points between the co-ordinate systems
1302 * represented by them. Pointers to the three arrays of easting, northing,
1303 * and ellipsoidal height of the point (this one may be NULL) are passed
1304 * to the function; these will be overwritten by the co-ordinates of the
1305 * re-projected points.
1306 *
1307 * \param count Number of points in the arrays to be transformed
1308 * \param x Pointer to an array of type double containing easting or longitude
1309 * \param y Pointer to an array of type double containing northing or latitude
1310 * \param h Pointer to an array of type double containing ellipsoidal height.
1311 * May be null in which case a two-dimensional re-projection will be
1312 * done
1313 * \param info_in pointer to pj_info struct for input co-ordinate system
1314 * \param info_out pointer to pj_info struct for output co-ordinate system
1315 *
1316 * \return Return value from PROJ proj_trans() function
1317 **/
1318
1319int pj_do_transform(int count, double *x, double *y, double *h,
1320 const struct pj_info *info_in,
1321 const struct pj_info *info_out)
1322{
1323 int ok;
1324 int i;
1325 int has_h = 1;
1326
1327 struct pj_info info_trans;
1328 PJ_COORD c;
1329 double METERS_in = 1.0, METERS_out = 1.0;
1330
1332 return -1;
1333 }
1334
1335 METERS_in = info_in->meters;
1336 METERS_out = info_out->meters;
1337
1338 if (h == NULL) {
1339 h = G_malloc(sizeof *h * count);
1340 /* they say memset is only guaranteed for chars ;-( */
1341 for (i = 0; i < count; ++i)
1342 h[i] = 0.0;
1343 has_h = 0;
1344 }
1345 ok = 0;
1346 if (strncmp(info_in->proj, "ll", 2) == 0) {
1347 c.lpzt.t = 0;
1348 if (strncmp(info_out->proj, "ll", 2) == 0) {
1349 for (i = 0; i < count; i++) {
1350 /* convert to radians */
1351 c.lpzt.lam = x[i] / RAD_TO_DEG;
1352 c.lpzt.phi = y[i] / RAD_TO_DEG;
1353 c.lpzt.z = h[i];
1354 c = proj_trans(info_trans.pj, PJ_FWD, c);
1355 if ((ok = proj_errno(info_trans.pj)) < 0)
1356 break;
1357 /* convert to degrees */
1358 x[i] = c.lp.lam * RAD_TO_DEG;
1359 y[i] = c.lp.phi * RAD_TO_DEG;
1360 }
1361 }
1362 else {
1363 for (i = 0; i < count; i++) {
1364 /* convert to radians */
1365 c.lpzt.lam = x[i] / RAD_TO_DEG;
1366 c.lpzt.phi = y[i] / RAD_TO_DEG;
1367 c.lpzt.z = h[i];
1368 c = proj_trans(info_trans.pj, PJ_FWD, c);
1369 if ((ok = proj_errno(info_trans.pj)) < 0)
1370 break;
1371 /* convert to map units */
1372 x[i] = c.xy.x / METERS_out;
1373 y[i] = c.xy.y / METERS_out;
1374 }
1375 }
1376 }
1377 else {
1378 c.xyzt.t = 0;
1379 if (strncmp(info_out->proj, "ll", 2) == 0) {
1380 for (i = 0; i < count; i++) {
1381 /* convert to meters */
1382 c.xyzt.x = x[i] * METERS_in;
1383 c.xyzt.y = y[i] * METERS_in;
1384 c.xyzt.z = h[i];
1385 c = proj_trans(info_trans.pj, PJ_FWD, c);
1386 if ((ok = proj_errno(info_trans.pj)) < 0)
1387 break;
1388 /* convert to degrees */
1389 x[i] = c.lp.lam * RAD_TO_DEG;
1390 y[i] = c.lp.phi * RAD_TO_DEG;
1391 }
1392 }
1393 else {
1394 for (i = 0; i < count; i++) {
1395 /* convert to meters */
1396 c.xyzt.x = x[i] * METERS_in;
1397 c.xyzt.y = y[i] * METERS_in;
1398 c.xyzt.z = h[i];
1399 c = proj_trans(info_trans.pj, PJ_FWD, c);
1400 if ((ok = proj_errno(info_trans.pj)) < 0)
1401 break;
1402 /* convert to map units */
1403 x[i] = c.xy.x / METERS_out;
1404 y[i] = c.xy.y / METERS_out;
1405 }
1406 }
1407 }
1408 if (!has_h)
1409 G_free(h);
1411
1412 if (ok < 0) {
1413 G_warning(_("proj_trans() failed: %d"), ok);
1414 }
1415 return ok;
1416}
#define NULL
Definition ccmath.h:32
void G_free(void *)
Free allocated memory.
Definition gis/alloc.c:147
void void void void G_fatal_error(const char *,...) __attribute__((format(printf
void G_warning(const char *,...) __attribute__((format(printf
void G_get_set_window(struct Cell_head *)
Get the current working window (region)
#define G_malloc(n)
Definition defs/gis.h:139
char * G_store_upper(const char *)
Copy string to allocated memory and convert copied string to upper case.
Definition strings.c:117
void void void G_important_message(const char *,...) __attribute__((format(printf
int G_asprintf(char **, const char *,...) __attribute__((format(printf
char * G_store(const char *)
Copy string to allocated memory.
Definition strings.c:87
int G_debug(int, const char *,...) __attribute__((format(printf
int GPJ_transform(const struct pj_info *info_in, const struct pj_info *info_out, const struct pj_info *info_trans, int dir, double *x, double *y, double *z)
Re-project a point between two co-ordinate systems using a transformation object prepared with GPJ_pr...
Definition do_proj.c:871
char * get_pj_type_string(PJ *pj)
Definition do_proj.c:220
int pj_do_proj(double *x, double *y, const struct pj_info *info_in, const struct pj_info *info_out)
Re-project a point between two co-ordinate systems.
Definition do_proj.c:1232
int pj_do_transform(int count, double *x, double *y, double *h, const struct pj_info *info_in, const struct pj_info *info_out)
Re-project an array of points between two co-ordinate systems with optional ellipsoidal height conver...
Definition do_proj.c:1319
int get_pj_area(const struct pj_info *iproj, double *xmin, double *xmax, double *ymin, double *ymax)
Definition do_proj.c:42
int GPJ_transform_array(const struct pj_info *info_in, const struct pj_info *info_out, const struct pj_info *info_trans, int dir, double *x, double *y, double *z, int n)
Re-project an array of points between two co-ordinate systems using a transformation object prepared ...
Definition do_proj.c:1030
PJ * get_pj_object(const struct pj_info *in_gpj, char **in_defstr)
Definition do_proj.c:313
int GPJ_init_transform(const struct pj_info *info_in, const struct pj_info *info_out, struct pj_info *info_trans)
Create a PROJ transformation object to transform coordinates from an input SRS to an output SRS.
Definition do_proj.c:423
#define PROJECTION_LL
Projection code - Latitude-Longitude.
Definition gis.h:129
#define _(str)
Definition glocale.h:10
#define RAD_TO_DEG
Definition gprojects.h:25
#define PJ_WKT2_LATEST
Definition gprojects.h:30
int count
2D/3D raster map header (used also for region)
Definition gis.h:446
double north
Extent coordinates (north)
Definition gis.h:492
double east
Extent coordinates (east)
Definition gis.h:496
int proj
Projection code.
Definition gis.h:478
double south
Extent coordinates (south)
Definition gis.h:494
double west
Extent coordinates (west)
Definition gis.h:498
PJ * pj
Definition gprojects.h:42
#define x