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