GRASS 8 Programmer's Manual 8.6.0dev(2026)-ddeab64dbf
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
42static double METERS_in = 1.0, METERS_out = 1.0;
43
44int get_pj_area(const struct pj_info *iproj, double *xmin, double *xmax,
45 double *ymin, double *ymax)
46{
47 struct Cell_head window;
48
49 /* modules must set the current window, do not unset this window here */
50 /* G_unset_window(); */
51 G_get_set_window(&window);
52 *xmin = window.west;
53 *xmax = window.east;
54 *ymin = window.south;
55 *ymax = window.north;
56
57 if (window.proj != PROJECTION_LL) {
58 /* transform to ll equivalent */
59 double estep, nstep;
60 double x[85], y[85];
61 int i;
62 const char *projstr = NULL;
63 char *indef = NULL;
64 struct pj_info oproj, tproj; /* proj parameters */
65
66 oproj.pj = NULL;
67 oproj.proj[0] = '\0';
68 tproj.def = NULL;
69
72
74 if (source_crs) {
75 projstr =
77 if (projstr) {
79 }
81 }
82 }
83 else {
85 if (projstr) {
87 }
88 }
89
90 if (indef == NULL)
91 indef = G_store(iproj->def);
92
93 /* needs +over to properly cross the anti-meridian
94 * the +over switch can be used to disable the default wrapping
95 * of output longitudes to the range -180 to 180 */
96 G_asprintf(&tproj.def, "+proj=pipeline +step +inv %s +over", indef);
97 G_debug(1, "get_pj_area() tproj.def: %s", tproj.def);
99
100 if (tproj.pj == NULL) {
101 G_warning(_("proj_create() failed for '%s'"), tproj.def);
102 G_free(indef);
103 G_free(tproj.def);
105
106 return 0;
107 }
109 if (projstr == NULL) {
110 G_warning(_("proj_create() failed for '%s'"), tproj.def);
111 G_free(indef);
112 G_free(tproj.def);
114
115 return 0;
116 }
117 else {
118 G_debug(1, "proj_create() projstr '%s'", projstr);
119 }
120 G_free(indef);
121
122 /* inpired by gdal/ogr/ogrct.cpp OGRProjCT::ListCoordinateOperations()
123 */
124 estep = (window.east - window.west) / 21.;
125 nstep = (window.north - window.south) / 21.;
126 for (i = 0; i < 20; i++) {
127 x[i] = window.west + estep * (i + 1);
128 y[i] = window.north;
129
130 x[i + 20] = window.west + estep * (i + 1);
131 y[i + 20] = window.south;
132
133 x[i + 40] = window.west;
134 y[i + 40] = window.south + nstep * (i + 1);
135
136 x[i + 60] = window.east;
137 y[i + 60] = window.south + nstep * (i + 1);
138 }
139 x[80] = window.west;
140 y[80] = window.north;
141 x[81] = window.west;
142 y[81] = window.south;
143 x[82] = window.east;
144 y[82] = window.north;
145 x[83] = window.east;
146 y[83] = window.south;
147 x[84] = (window.west + window.east) / 2.;
148 y[84] = (window.north + window.south) / 2.;
149
151
153 G_free(tproj.def);
154
155 *xmin = *xmax = x[84];
156 *ymin = *ymax = y[84];
157 for (i = 0; i < 84; i++) {
158 if (*xmin > x[i])
159 *xmin = x[i];
160 if (*xmax < x[i])
161 *xmax = x[i];
162 if (*ymin > y[i])
163 *ymin = y[i];
164 if (*ymax < y[i])
165 *ymax = y[i];
166 }
167
168 /* The west longitude is generally lower than the east longitude,
169 * except for areas of interest that go across the anti-meridian.
170 * do not reduce global coverage to a small north-south strip
171 */
172 if (*xmin < -180 && *xmax < 180 && *xmin + 360 > *xmax) {
173 /* must be crossing the anti-meridian at 180W */
174 *xmin += 360;
175 }
176 else if (*xmax > 180 && *xmin > -180 && *xmax - 360 < *xmin) {
177 /* must be crossing the anti-meridian at 180E */
178 *xmax -= 360;
179 }
180
181 G_debug(1, "input window north: %.8f", window.north);
182 G_debug(1, "input window south: %.8f", window.south);
183 G_debug(1, "input window east: %.8f", window.east);
184 G_debug(1, "input window west: %.8f", window.west);
185
186 G_debug(1, "transformed xmin: %.8f", *xmin);
187 G_debug(1, "transformed xmax: %.8f", *xmax);
188 G_debug(1, "transformed ymin: %.8f", *ymin);
189 G_debug(1, "transformed ymax: %.8f", *ymax);
190
191 /* test valid values, as in
192 * gdal/ogr/ogrct.cpp
193 * OGRCoordinateTransformationOptions::SetAreaOfInterest()
194 */
195 if (fabs(*xmin) > 180) {
196 G_warning(_("Invalid west longitude %g"), *xmin);
197 return 0;
198 }
199 if (fabs(*xmax) > 180) {
200 G_warning(_("Invalid east longitude %g"), *xmax);
201 return 0;
202 }
203 if (fabs(*ymin) > 90) {
204 G_warning(_("Invalid south latitude %g"), *ymin);
205 return 0;
206 }
207 if (fabs(*ymax) > 90) {
208 G_warning(_("Invalid north latitude %g"), *ymax);
209 return 0;
210 }
211 if (*ymin > *ymax) {
212 G_warning(_("South %g is larger than north %g"), *ymin, *ymax);
213 return 0;
214 }
215 }
216 G_debug(1, "get_pj_area(): xmin %g, xmax %g, ymin %g, ymax %g", *xmin,
217 *xmax, *ymin, *ymax);
218
219 return 1;
220}
221
223{
224 char *pj_type = NULL;
225
226 switch (proj_get_type(pj)) {
227 case PJ_TYPE_UNKNOWN:
228 G_asprintf(&pj_type, "unknown");
229 break;
231 G_asprintf(&pj_type, "ellipsoid");
232 break;
234 G_asprintf(&pj_type, "prime meridian");
235 break;
237 G_asprintf(&pj_type, "geodetic reference frame");
238 break;
240 G_asprintf(&pj_type, "dynamic geodetic reference frame");
241 break;
243 G_asprintf(&pj_type, "vertical reference frame");
244 break;
246 G_asprintf(&pj_type, "dynamic vertical reference frame");
247 break;
249 G_asprintf(&pj_type, "datum ensemble");
250 break;
251
252 /** Abstract type, not returned by proj_get_type() */
253 case PJ_TYPE_CRS:
254 G_asprintf(&pj_type, "crs");
255 break;
257 G_asprintf(&pj_type, "geodetic crs");
258 break;
260 G_asprintf(&pj_type, "geocentric crs");
261 break;
262
263 /** proj_get_type() will never return that type, but
264 * PJ_TYPE_GEOGRAPHIC_2D_CRS or PJ_TYPE_GEOGRAPHIC_3D_CRS. */
266 G_asprintf(&pj_type, "geographic crs");
267 break;
269 G_asprintf(&pj_type, "geographic 2D crs");
270 break;
272 G_asprintf(&pj_type, "geographic 3D crs");
273 break;
275 G_asprintf(&pj_type, "vertical crs");
276 break;
278 G_asprintf(&pj_type, "projected crs");
279 break;
281 G_asprintf(&pj_type, "compound crs");
282 break;
284 G_asprintf(&pj_type, "temporal crs");
285 break;
287 G_asprintf(&pj_type, "engineering crs");
288 break;
290 G_asprintf(&pj_type, "bound crs");
291 break;
293 G_asprintf(&pj_type, "other crs");
294 break;
296 G_asprintf(&pj_type, "conversion");
297 break;
299 G_asprintf(&pj_type, "transformation");
300 break;
302 G_asprintf(&pj_type, "concatenated operation");
303 break;
305 G_asprintf(&pj_type, "other coordinate operation");
306 break;
307 default:
308 G_asprintf(&pj_type, "unknown");
309 break;
310 }
311
312 return pj_type;
313}
314
315PJ *get_pj_object(const struct pj_info *in_gpj, char **in_defstr)
316{
317 PJ *in_pj = NULL;
318
319 *in_defstr = NULL;
320
321 /* 1. SRID, 2. WKT, 3. standard pj from pj_get_kv */
322 if (in_gpj->srid) {
323 G_debug(1, "Trying SRID '%s' ...", in_gpj->srid);
325 if (!in_pj) {
326 G_warning(_("Unrecognized SRID '%s'"), in_gpj->srid);
327 }
328 else {
329 *in_defstr = G_store(in_gpj->srid);
330 /* PROJ will do the unit conversion if set up from srid
331 * -> disable unit conversion for GPJ_transform */
332 /* ugly hack */
333 ((struct pj_info *)in_gpj)->meters = 1;
334 }
335 }
336 if (!in_pj && in_gpj->wkt) {
337 G_debug(1, "Trying WKT '%s' ...", in_gpj->wkt);
339 if (!in_pj) {
340 G_warning(_("Unrecognized WKT '%s'"), in_gpj->wkt);
341 }
342 else {
343 *in_defstr = G_store(in_gpj->wkt);
344 /* PROJ will do the unit conversion if set up from wkt
345 * -> disable unit conversion for GPJ_transform */
346 /* ugly hack */
347 ((struct pj_info *)in_gpj)->meters = 1;
348 }
349 }
350 if (!in_pj && in_gpj->pj) {
353 if (*in_defstr && !**in_defstr)
354 *in_defstr = NULL;
355 }
356
357 if (!in_pj) {
358 G_warning(_("Unable to create PROJ object"));
359
360 return NULL;
361 }
362
363 /* Even Rouault:
364 * if info_in->def contains a +towgs84/+nadgrids clause,
365 * this pipeline would apply it, whereas you probably only want
366 * the reverse projection, and no datum shift.
367 * The easiest would probably to mess up with the PROJ string.
368 * Otherwise with the PROJ API, you could
369 * instantiate a PJ object from the string,
370 * check if it is a BoundCRS with proj_get_source_crs(),
371 * and in that case, take the source CRS with proj_get_source_crs(),
372 * and do the inverse transform on it */
373
375 PJ *source_crs;
376
377 G_debug(1, "found bound crs");
379 if (source_crs) {
380 *in_defstr =
382 if (*in_defstr && !**in_defstr)
383 *in_defstr = NULL;
385 }
386 }
387
388 return in_pj;
389}
390
391/**
392 * \brief Create a PROJ transformation object to transform coordinates
393 * from an input SRS to an output SRS
394 *
395 * After the transformation has been initialized with this function,
396 * coordinates can be transformed from input SRS to output SRS with
397 * GPJ_transform() and direction = PJ_FWD, and back from output SRS to
398 * input SRS with direction = OJ_INV.
399 * If coordinates should be transformed between the input SRS and its
400 * latlong equivalent, an uninitialized info_out with
401 * info_out->pj = NULL can be passed to the function. In this case,
402 * coordinates will be transformed between the input SRS and its
403 * latlong equivalent, and for PROJ 5+, the transformation object is
404 * created accordingly
405 *
406 * PROJ 5+:
407 * info_in->pj must not be null
408 * if info_out->pj is null, assume info_out to be the ll equivalent
409 * of info_in
410 * create info_trans as conversion from info_in to its ll equivalent
411 * NOTE: this is the inverse of the logic of PROJ 5 which by default
412 * converts from ll to a given SRS, not from a given SRS to ll
413 * thus PROJ 5+ itself uses an inverse transformation in the
414 * first step of the pipeline for proj_create_crs_to_crs()
415 * if info_trans->def is not NULL, this pipeline definition will be
416 * used to create a transformation object
417 *
418 * \param info_in pointer to pj_info struct for input co-ordinate system
419 * \param info_out pointer to pj_info struct for output co-ordinate system
420 * \param info_trans pointer to pj_info struct for a transformation object (PROJ
421 *5+)
422 *
423 * \return 1 on success, -1 on failure
424 **/
426 const struct pj_info *info_out,
427 struct pj_info *info_trans)
428{
429 if (info_in->pj == NULL)
430 G_fatal_error(_("Input coordinate system is NULL"));
431
432 if (info_in->def == NULL)
433 G_fatal_error(_("Input coordinate system definition is NULL"));
434
435 /* PROJ6+: enforce axis order easting, northing
436 * +axis=enu (works with proj-4.8+) */
437
438 info_trans->pj = NULL;
439 info_trans->meters = 1.;
440 info_trans->zone = 0;
441 snprintf(info_trans->proj, sizeof(info_trans->proj), "pipeline");
442
443 /* user-provided pipeline */
444 if (info_trans->def) {
445 const char *projstr;
446
447 /* info_in->pj, info_in->proj, info_out->pj, info_out->proj
448 * must be set */
449 if (!info_in->pj || !info_in->proj[0] || !info_out->pj ||
450 !info_out->proj[0]) {
451 G_warning(_(
452 "A custom pipeline requires input and output projection info"));
453
454 return -1;
455 }
456
457 /* create a pj from user-defined transformation pipeline */
459 if (info_trans->pj == NULL) {
460 G_warning(_("proj_create() failed for '%s'"), info_trans->def);
461
462 return -1;
463 }
465 if (projstr == NULL) {
466 G_warning(_("proj_create() failed for '%s'"), info_trans->def);
467
468 return -1;
469 }
470 else {
471 /* make sure axis order is easting, northing
472 * proj_normalize_for_visualization() does not work here
473 * because source and target CRS are unknown to PROJ
474 * remove any "+step +proj=axisswap +order=2,1" ?
475 * */
476 info_trans->def = G_store(projstr);
477
478 if (strstr(info_trans->def, "axisswap")) {
479 G_warning(
480 _("The transformation pipeline contains an '%s' step. "
481 "Remove this step if easting and northing are swapped in "
482 "the output."),
483 "axisswap");
484 }
485
486 G_debug(1, "proj_create() pipeline: %s", info_trans->def);
487
488 /* the user-provided PROJ pipeline is supposed to do
489 * all the needed unit conversions */
490 /* ugly hack */
491 ((struct pj_info *)info_in)->meters = 1;
492 ((struct pj_info *)info_out)->meters = 1;
493 }
494 }
495 /* if no output CRS is defined,
496 * assume info_out to be ll equivalent of info_in */
497 else if (info_out->pj == NULL) {
498 const char *projstr = NULL;
499 char *indef = NULL;
500
501 /* get PROJ-style definition */
502 indef = G_store(info_in->def);
503 G_debug(1, "ll equivalent definition: %s", indef);
504
505 /* what about axis order?
506 * is it always enu?
507 * probably yes, as long as there is no +proj=axisswap step */
508 G_asprintf(&(info_trans->def), "+proj=pipeline +step +inv %s", indef);
510 if (info_trans->pj == NULL) {
511 G_warning(_("proj_create() failed for '%s'"), info_trans->def);
512 G_free(indef);
513
514 return -1;
515 }
517 if (projstr == NULL) {
518 G_warning(_("proj_create() failed for '%s'"), info_trans->def);
519 G_free(indef);
520
521 return -1;
522 }
523 G_free(indef);
524 }
525 /* input and output CRS are available */
526 else if (info_in->def && info_out->pj && info_out->def) {
527 char *indef = NULL, *outdef = NULL;
528 char *insrid = NULL, *outsrid = NULL;
529 PJ *in_pj, *out_pj;
533 double xmin, xmax, ymin, ymax;
534 int op_count = 0, op_count_area = 0;
535
536 /* get pj_area */
537 /* do it here because get_pj_area() will use
538 * the PROJ definition for simple transformation to the
539 * ll equivalent and we need to do unit conversion */
540 if (get_pj_area(info_in, &xmin, &xmax, &ymin, &ymax)) {
542 proj_area_set_bbox(pj_area, xmin, ymin, xmax, ymax);
543 }
544 else {
545 G_warning(_("Unable to determine area of interest for '%s'"),
546 info_in->def);
547
548 return -1;
549 }
550
551 G_debug(1, "source proj string: %s", info_in->def);
552 G_debug(1, "source type: %s", get_pj_type_string(info_in->pj));
553
554 /* PROJ6+: EPSG must be uppercase EPSG */
555 if (info_in->srid) {
556 if (strncmp(info_in->srid, "epsg", 4) == 0) {
558 G_free(info_in->srid);
559 ((struct pj_info *)info_in)->srid = insrid;
560 insrid = NULL;
561 }
562 }
563
565 if (in_pj == NULL || indef == NULL) {
566 G_warning(_("Input CRS not available for '%s'"), info_in->def);
567
568 return -1;
569 }
570 G_debug(1, "Input CRS definition: %s", indef);
571
572 G_debug(1, "target proj string: %s", info_out->def);
573 G_debug(1, "target type: %s", get_pj_type_string(info_out->pj));
574
575 /* PROJ6+: EPSG must be uppercase EPSG */
576 if (info_out->srid) {
577 if (strncmp(info_out->srid, "epsg", 4) == 0) {
579 G_free(info_out->srid);
580 ((struct pj_info *)info_out)->srid = outsrid;
581 outsrid = NULL;
582 }
583 }
584
586 if (out_pj == NULL || outdef == NULL) {
587 G_warning(_("Output CRS not available for '%s'"), info_out->def);
588
589 return -1;
590 }
591 G_debug(1, "Output CRS definition: %s", outdef);
592
593 /* check number of operations */
594
597 /* proj_create_operations() works only if both source_crs
598 * and target_crs are found in the proj db
599 * if any is not found, proj can not get a list of operations
600 * and we have to take care of datumshift manually */
601 /* list all operations irrespecitve of area and
602 * grid availability */
606
607 op_count = 0;
608 if (op_list)
610 if (op_count > 1) {
611 int i;
612
613 G_important_message(_("Found %d possible transformations"),
614 op_count);
615 for (i = 0; i < op_count; i++) {
616 const char *area_of_use, *projstr;
617 double e, w, s, n;
619 PJ *op, *op_norm;
620
623
624 if (!op_norm) {
625 G_warning(_("proj_normalize_for_visualization() failed for "
626 "operation %d"),
627 i + 1);
628 }
629 else {
631 op = op_norm;
632 }
633
635 proj_get_area_of_use(NULL, op, &w, &s, &e, &n, &area_of_use);
636 G_important_message("************************");
637 G_important_message(_("Operation %d:"), i + 1);
638 if (pj_info.description) {
639 G_important_message(_("Description: %s"),
640 pj_info.description);
641 }
642 if (area_of_use) {
644 G_important_message(_("Area of use: %s"), area_of_use);
645 }
646 if (pj_info.accuracy > 0) {
648 G_important_message(_("Accuracy within area of use: %g m"),
649 pj_info.accuracy);
650 }
651 const char *str = proj_get_remarks(op);
652
653 if (str && *str) {
655 G_important_message(_("Remarks: %s"), str);
656 }
657 str = proj_get_scope(op);
658 if (str && *str) {
660 G_important_message(_("Scope: %s"), str);
661 }
662
664 if (projstr) {
666 G_important_message(_("PROJ string:"));
668 }
670 }
671 G_important_message("************************");
672
673 G_important_message(_("See also output of:"));
674 G_important_message("projinfo -o PROJ -s \"%s\" -t \"%s\"", indef,
675 outdef);
676 G_important_message(_("Please provide the appropriate PROJ string "
677 "with the %s option"),
678 "pipeline");
679 G_important_message("************************");
680 }
681
682 if (op_list)
684
685 /* following code copied from proj_create_crs_to_crs_from_pj()
686 * in proj src/4D_api.cpp
687 * using PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION
688 * this can cause problems and artefacts
689 * switch to PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT
690 * in case of problems
691 * but results can be different from gdalwarp:
692 * shifted geolocation in some areas
693 * in these cases there is no right or wrong,
694 * different pipelines are all regarded as valid by PROJ
695 * depending on the area of interest
696 *
697 * see also:
698 * OGRProjCT::ListCoordinateOperations() in GDAL ogr/ogrct.cpp
699 * create_operation_to_geog_crs() in PROJ src/4D_api.cpp
700 * proj_create_crs_to_crs_from_pj() in PROJ src/4D_api.cpp
701 * proj_operation_factory_context_set_spatial_criterion() in PROJ
702 * src/iso19111/c_api.cpp
703 * */
704
705 /* now use the current region as area of interest */
709 PJ_DEFAULT_CTX, operation_ctx, xmin, ymin, xmax, ymax);
713 /* from GDAL OGRProjCT::ListCoordinateOperations() */
719
720 /* The operations are sorted with the most relevant ones first:
721 * by descending area (intersection of the transformation area
722 * with the area of interest, or intersection of the
723 * transformation with the area of use of the CRS),
724 * and by increasing accuracy.
725 * Operations with unknown accuracy are sorted last,
726 * whatever their area.
727 */
731 op_count_area = 0;
732 if (op_list)
734 if (op_count_area == 0) {
735 /* no operations */
736 info_trans->pj = NULL;
737 }
738 else if (op_count_area == 1) {
740 }
741 else { /* op_count_area > 1 */
742 /* can't use pj_create_prepared_operations()
743 * this is a PROJ-internal function
744 * trust the sorting of PROJ and use the first one */
746 }
747 if (op_list)
749
750 /* try proj_create_crs_to_crs() */
751 /*
752 G_debug(1, "trying %s to %s", indef, outdef);
753 */
754
755 /* proj_create_crs_to_crs() does not work because it calls
756 * proj_create_crs_to_crs_from_pj() which calls
757 * proj_operation_factory_context_set_spatial_criterion()
758 * with PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION
759 * instead of
760 * PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT
761 *
762 * fixed in PROJ master, probably available with PROJ 7.3.x */
763
764 /*
765 info_trans->pj = proj_create_crs_to_crs(PJ_DEFAULT_CTX,
766 indef,
767 outdef,
768 pj_area);
769 */
770
771 if (in_pj)
773 if (out_pj)
775
776 if (info_trans->pj) {
777 const char *projstr;
778 PJ *pj_norm = NULL;
779
780 G_debug(1, "proj_create_crs_to_crs() succeeded with PROJ%d",
782
783 projstr =
785
786 info_trans->def = G_store(projstr);
787
788 if (projstr) {
789 /* make sure axis order is easting, northing
790 * proj_normalize_for_visualization() requires
791 * source and target CRS
792 * -> does not work with ll equivalent of input:
793 * no target CRS in +proj=pipeline +step +inv %s */
795 info_trans->pj);
796
797 if (!pj_norm) {
798 G_warning(
799 _("proj_normalize_for_visualization() failed for '%s'"),
800 info_trans->def);
801 }
802 else {
803 projstr =
805 if (projstr && *projstr) {
807 info_trans->pj = pj_norm;
808 info_trans->def = G_store(projstr);
809 }
810 else {
812 G_warning(_("No PROJ definition for normalized version "
813 "of '%s'"),
814 info_trans->def);
815 }
816 }
817 G_important_message(_("Selected PROJ pipeline:"));
818 G_important_message(_("%s"), info_trans->def);
819 G_important_message("************************");
820 }
821 else {
823 info_trans->pj = NULL;
824 }
825 }
826
827 if (pj_area)
829
830 if (insrid)
831 G_free(insrid);
832 if (outsrid)
834 G_free(indef);
835 G_free(outdef);
836 }
837 if (info_trans->pj == NULL) {
838 G_warning(_("proj_create() failed for '%s'"), info_trans->def);
839
840 return -1;
841 }
842
843 return 1;
844}
845
846/* TODO: rename pj_ to GPJ_ to avoid symbol clash with PROJ lib */
847
848/**
849 * \brief Re-project a point between two co-ordinate systems using a
850 * transformation object prepared with GPJ_prepare_pj()
851 *
852 * This function takes pointers to three pj_info structures as arguments,
853 * and projects a point between the input and output co-ordinate system.
854 * The pj_info structure info_trans must have been initialized with
855 * GPJ_init_transform().
856 * The direction determines if a point is projected from input CRS to
857 * output CRS (PJ_FWD) or from output CRS to input CRS (PJ_INV).
858 * The easting, northing, and height of the point are contained in the
859 * pointers passed to the function; these will be overwritten by the
860 * coordinates of the transformed point.
861 *
862 * \param info_in pointer to pj_info struct for input co-ordinate system
863 * \param info_out pointer to pj_info struct for output co-ordinate system
864 * \param info_trans pointer to pj_info struct for a transformation object (PROJ
865 *5+) \param dir direction of the transformation (PJ_FWD or PJ_INV) \param x
866 *Pointer to a double containing easting or longitude \param y Pointer to a
867 *double containing northing or latitude \param z Pointer to a double containing
868 *height, or NULL
869 *
870 * \return Return value from PROJ proj_trans() function
871 **/
872
873int GPJ_transform(const struct pj_info *info_in, const struct pj_info *info_out,
874 const struct pj_info *info_trans, int dir, double *x,
875 double *y, double *z)
876{
877 int ok = 0;
878
880 PJ_COORD c;
881
882 if (info_in->pj == NULL)
883 G_fatal_error(_("No input projection"));
884
885 if (info_trans->pj == NULL)
886 G_fatal_error(_("No transformation object"));
887
889 if (dir == PJ_FWD) {
890 /* info_in -> info_out */
891 METERS_in = info_in->meters;
892 in_is_ll = !strncmp(info_in->proj, "ll", 2);
893 /* PROJ 6+: conversion to radians is not always needed:
894 * if proj_angular_input(info_trans->pj, dir) == 1
895 * -> convert from degrees to radians */
896 if (in_is_ll && proj_angular_input(info_trans->pj, dir) == 0) {
897 in_deg2rad = 0;
898 }
899 if (info_out->pj) {
900 METERS_out = info_out->meters;
901 out_is_ll = !strncmp(info_out->proj, "ll", 2);
902 /* PROJ 6+: conversion to radians is not always needed:
903 * if proj_angular_input(info_trans->pj, dir) == 1
904 * -> convert from degrees to radians */
905 if (out_is_ll && proj_angular_output(info_trans->pj, dir) == 0) {
906 out_rad2deg = 0;
907 }
908 }
909 else {
910 METERS_out = 1.0;
911 out_is_ll = 1;
912 }
913 }
914 else {
915 /* info_out -> info_in */
916 METERS_out = info_in->meters;
917 out_is_ll = !strncmp(info_in->proj, "ll", 2);
918 /* PROJ 6+: conversion to radians is not always needed:
919 * if proj_angular_input(info_trans->pj, dir) == 1
920 * -> convert from degrees to radians */
921 if (out_is_ll && proj_angular_output(info_trans->pj, dir) == 0) {
922 out_rad2deg = 0;
923 }
924 if (info_out->pj) {
925 METERS_in = info_out->meters;
926 in_is_ll = !strncmp(info_out->proj, "ll", 2);
927 /* PROJ 6+: conversion to radians is not always needed:
928 * if proj_angular_input(info_trans->pj, dir) == 1
929 * -> convert from degrees to radians */
930 if (in_is_ll && proj_angular_input(info_trans->pj, dir) == 0) {
931 in_deg2rad = 0;
932 }
933 }
934 else {
935 METERS_in = 1.0;
936 in_is_ll = 1;
937 }
938 }
939
940 /* prepare */
941 if (in_is_ll) {
942 if (in_deg2rad) {
943 /* convert degrees to radians */
944 c.lpzt.lam = (*x) / RAD_TO_DEG;
945 c.lpzt.phi = (*y) / RAD_TO_DEG;
946 }
947 else {
948 c.lpzt.lam = (*x);
949 c.lpzt.phi = (*y);
950 }
951 c.lpzt.z = 0;
952 if (z)
953 c.lpzt.z = *z;
954 c.lpzt.t = 0;
955 }
956 else {
957 /* convert to meters */
958 c.xyzt.x = *x * METERS_in;
959 c.xyzt.y = *y * METERS_in;
960 c.xyzt.z = 0;
961 if (z)
962 c.xyzt.z = *z;
963 c.xyzt.t = 0;
964 }
965
966 G_debug(1, "c.xyzt.x: %g", c.xyzt.x);
967 G_debug(1, "c.xyzt.y: %g", c.xyzt.y);
968 G_debug(1, "c.xyzt.z: %g", c.xyzt.z);
969
970 /* transform */
971 c = proj_trans(info_trans->pj, dir, c);
972 ok = proj_errno(info_trans->pj);
973
974 if (ok < 0) {
975 G_warning(_("proj_trans() failed: %s"), proj_errno_string(ok));
976 return ok;
977 }
978
979 /* output */
980 if (out_is_ll) {
981 /* convert to degrees */
982 if (out_rad2deg) {
983 /* convert radians to degrees */
984 *x = c.lp.lam * RAD_TO_DEG;
985 *y = c.lp.phi * RAD_TO_DEG;
986 }
987 else {
988 *x = c.lp.lam;
989 *y = c.lp.phi;
990 }
991 if (z)
992 *z = c.lpzt.z;
993 }
994 else {
995 /* convert to map units */
996 *x = c.xyzt.x / METERS_out;
997 *y = c.xyzt.y / METERS_out;
998 if (z)
999 *z = c.xyzt.z;
1000 }
1001
1002 return ok;
1003}
1004
1005/**
1006 * \brief Re-project an array of points between two co-ordinate systems
1007 * using a transformation object prepared with GPJ_prepare_pj()
1008 *
1009 * This function takes pointers to three pj_info structures as arguments,
1010 * and projects an array of pointd between the input and output
1011 * co-ordinate system. The pj_info structure info_trans must have been
1012 * initialized with GPJ_init_transform().
1013 * The direction determines if a point is projected from input CRS to
1014 * output CRS (PJ_FWD) or from output CRS to input CRS (PJ_INV).
1015 * The easting, northing, and height of the point are contained in the
1016 * pointers passed to the function; these will be overwritten by the
1017 * coordinates of the transformed point.
1018 *
1019 * \param info_in pointer to pj_info struct for input co-ordinate system
1020 * \param info_out pointer to pj_info struct for output co-ordinate system
1021 * \param info_trans pointer to pj_info struct for a transformation object (PROJ
1022 *5+) \param dir direction of the transformation (PJ_FWD or PJ_INV) \param x
1023 *pointer to an array of type double containing easting or longitude \param y
1024 *pointer to an array of type double containing northing or latitude \param z
1025 *pointer to an array of type double containing height, or NULL \param n number
1026 *of points in the arrays to be transformed
1027 *
1028 * \return Return value from PROJ proj_trans() function
1029 **/
1030
1032 const struct pj_info *info_out,
1033 const struct pj_info *info_trans, int dir, double *x,
1034 double *y, double *z, int n)
1035{
1036 int ok;
1037 int i;
1038 int has_z = 1;
1039
1040 /* PROJ 5+ variant */
1042 PJ_COORD c;
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) / RAD_TO_DEG;
1121 c.lpzt.phi = (*y) / RAD_TO_DEG;
1122 }
1123 else {
1124 c.lpzt.lam = (*x);
1125 c.lpzt.phi = (*y);
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) / RAD_TO_DEG;
1147 c.lpzt.phi = (*y) / RAD_TO_DEG;
1148 }
1149 else {
1150 c.lpzt.lam = (*x);
1151 c.lpzt.phi = (*y);
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
1241 return -1;
1242 }
1243
1244 METERS_in = info_in->meters;
1245 METERS_out = info_out->meters;
1246
1247 if (strncmp(info_in->proj, "ll", 2) == 0) {
1248 /* convert to radians */
1249 c.lpzt.lam = (*x) / RAD_TO_DEG;
1250 c.lpzt.phi = (*y) / RAD_TO_DEG;
1251 c.lpzt.z = 0;
1252 c.lpzt.t = 0;
1253 c = proj_trans(info_trans.pj, PJ_FWD, c);
1254 ok = proj_errno(info_trans.pj);
1255
1256 if (strncmp(info_out->proj, "ll", 2) == 0) {
1257 /* convert to degrees */
1258 *x = c.lp.lam * RAD_TO_DEG;
1259 *y = c.lp.phi * RAD_TO_DEG;
1260 }
1261 else {
1262 /* convert to map units */
1263 *x = c.xy.x / METERS_out;
1264 *y = c.xy.y / METERS_out;
1265 }
1266 }
1267 else {
1268 /* convert to meters */
1269 c.xyzt.x = *x * METERS_in;
1270 c.xyzt.y = *y * METERS_in;
1271 c.xyzt.z = 0;
1272 c.xyzt.t = 0;
1273 c = proj_trans(info_trans.pj, PJ_FWD, c);
1274 ok = proj_errno(info_trans.pj);
1275
1276 if (strncmp(info_out->proj, "ll", 2) == 0) {
1277 /* convert to degrees */
1278 *x = c.lp.lam * RAD_TO_DEG;
1279 *y = c.lp.phi * RAD_TO_DEG;
1280 }
1281 else {
1282 /* convert to map units */
1283 *x = c.xy.x / METERS_out;
1284 *y = c.xy.y / METERS_out;
1285 }
1286 }
1288
1289 if (ok < 0) {
1290 G_warning(_("proj_trans() failed: %d"), ok);
1291 }
1292 return ok;
1293}
1294
1295/**
1296 * \brief Re-project an array of points between two co-ordinate systems with
1297 * optional ellipsoidal height conversion
1298 *
1299 * This function takes pointers to two pj_info structures as arguments,
1300 * and projects an array of points between the co-ordinate systems
1301 * represented by them. Pointers to the three arrays of easting, northing,
1302 * and ellipsoidal height of the point (this one may be NULL) are passed
1303 * to the function; these will be overwritten by the co-ordinates of the
1304 * re-projected points.
1305 *
1306 * \param count Number of points in the arrays to be transformed
1307 * \param x Pointer to an array of type double containing easting or longitude
1308 * \param y Pointer to an array of type double containing northing or latitude
1309 * \param h Pointer to an array of type double containing ellipsoidal height.
1310 * May be null in which case a two-dimensional re-projection will be
1311 * done
1312 * \param info_in pointer to pj_info struct for input co-ordinate system
1313 * \param info_out pointer to pj_info struct for output co-ordinate system
1314 *
1315 * \return Return value from PROJ proj_trans() function
1316 **/
1317
1318int pj_do_transform(int count, double *x, double *y, double *h,
1319 const struct pj_info *info_in,
1320 const struct pj_info *info_out)
1321{
1322 int ok;
1323 int i;
1324 int has_h = 1;
1325
1326 struct pj_info info_trans;
1327 PJ_COORD c;
1328
1330 return -1;
1331 }
1332
1333 METERS_in = info_in->meters;
1334 METERS_out = info_out->meters;
1335
1336 if (h == NULL) {
1337 h = G_malloc(sizeof *h * count);
1338 /* they say memset is only guaranteed for chars ;-( */
1339 for (i = 0; i < count; ++i)
1340 h[i] = 0.0;
1341 has_h = 0;
1342 }
1343 ok = 0;
1344 if (strncmp(info_in->proj, "ll", 2) == 0) {
1345 c.lpzt.t = 0;
1346 if (strncmp(info_out->proj, "ll", 2) == 0) {
1347 for (i = 0; i < count; i++) {
1348 /* convert to radians */
1349 c.lpzt.lam = x[i] / RAD_TO_DEG;
1350 c.lpzt.phi = y[i] / RAD_TO_DEG;
1351 c.lpzt.z = h[i];
1352 c = proj_trans(info_trans.pj, PJ_FWD, c);
1353 if ((ok = proj_errno(info_trans.pj)) < 0)
1354 break;
1355 /* convert to degrees */
1356 x[i] = c.lp.lam * RAD_TO_DEG;
1357 y[i] = c.lp.phi * RAD_TO_DEG;
1358 }
1359 }
1360 else {
1361 for (i = 0; i < count; i++) {
1362 /* convert to radians */
1363 c.lpzt.lam = x[i] / RAD_TO_DEG;
1364 c.lpzt.phi = y[i] / RAD_TO_DEG;
1365 c.lpzt.z = h[i];
1366 c = proj_trans(info_trans.pj, PJ_FWD, c);
1367 if ((ok = proj_errno(info_trans.pj)) < 0)
1368 break;
1369 /* convert to map units */
1370 x[i] = c.xy.x / METERS_out;
1371 y[i] = c.xy.y / METERS_out;
1372 }
1373 }
1374 }
1375 else {
1376 c.xyzt.t = 0;
1377 if (strncmp(info_out->proj, "ll", 2) == 0) {
1378 for (i = 0; i < count; i++) {
1379 /* convert to meters */
1380 c.xyzt.x = x[i] * METERS_in;
1381 c.xyzt.y = y[i] * METERS_in;
1382 c.xyzt.z = h[i];
1383 c = proj_trans(info_trans.pj, PJ_FWD, c);
1384 if ((ok = proj_errno(info_trans.pj)) < 0)
1385 break;
1386 /* convert to degrees */
1387 x[i] = c.lp.lam * RAD_TO_DEG;
1388 y[i] = c.lp.phi * RAD_TO_DEG;
1389 }
1390 }
1391 else {
1392 for (i = 0; i < count; i++) {
1393 /* convert to meters */
1394 c.xyzt.x = x[i] * METERS_in;
1395 c.xyzt.y = y[i] * METERS_in;
1396 c.xyzt.z = h[i];
1397 c = proj_trans(info_trans.pj, PJ_FWD, c);
1398 if ((ok = proj_errno(info_trans.pj)) < 0)
1399 break;
1400 /* convert to map units */
1401 x[i] = c.xy.x / METERS_out;
1402 y[i] = c.xy.y / METERS_out;
1403 }
1404 }
1405 }
1406 if (!has_h)
1407 G_free(h);
1409
1410 if (ok < 0) {
1411 G_warning(_("proj_trans() failed: %d"), ok);
1412 }
1413 return ok;
1414}
#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:873
char * get_pj_type_string(PJ *pj)
Definition do_proj.c:222
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:1318
int get_pj_area(const struct pj_info *iproj, double *xmin, double *xmax, double *ymin, double *ymax)
Definition do_proj.c:44
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:1031
PJ * get_pj_object(const struct pj_info *in_gpj, char **in_defstr)
Definition do_proj.c:315
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:425
#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