PISM, A Parallel Ice Sheet Model 2.3.0-79cae578d committed by Constantine Khrulev on 2026-03-22
Loading...
Searching...
No Matches
projection.cc
Go to the documentation of this file.
1/* Copyright (C) 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025 PISM Authors
2 *
3 * This file is part of PISM.
4 *
5 * PISM is free software; you can redistribute it and/or modify it under the
6 * terms of the GNU General Public License as published by the Free Software
7 * Foundation; either version 3 of the License, or (at your option) any later
8 * version.
9 *
10 * PISM is distributed in the hope that it will be useful, but WITHOUT ANY
11 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13 * details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with PISM; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include <cstdlib> // strtol
21#include <cmath> // std::pow, std::sqrt, std::fabs
22#include <string>
23#include <vector>
24#include <tuple>
25
26#include "io/IO_Flags.hh"
27#include "pism/util/projection.hh"
28#include "pism/util/VariableMetadata.hh"
29#include "pism/util/error_handling.hh"
30#include "pism/util/io/File.hh"
31#include "pism/util/io/io_helpers.hh"
32#include "pism/util/Grid.hh"
33#include "pism/util/array/Scalar.hh"
34#include "pism/util/array/Array3D.hh"
35
36#include "pism/pism_config.hh"
37#include "pism/util/pism_utilities.hh"
38
39#if (Pism_USE_PROJ==1)
40#include "pism/util/Proj.hh"
41#endif
42
43namespace pism {
44
45int parse_epsg(const std::string &proj_string) {
46 try {
47 int auth_len = 5; // length of "epsg:"
48 std::string::size_type position = std::string::npos;
49 for (const auto &auth : { "epsg:", "EPSG:" }) {
50 position = proj_string.find(auth);
51 if (position != std::string::npos) {
52 break;
53 }
54 }
55
56 if (position == std::string::npos) {
57 throw RuntimeError(PISM_ERROR_LOCATION, "could not find 'epsg:' or 'EPSG:'");
58 }
59
60
61 {
62 auto epsg_string = proj_string.substr(position + auth_len);
63 char *endptr = NULL;
64 long int result = strtol(epsg_string.c_str(), &endptr, 10);
65 if (endptr == epsg_string.c_str()) {
66 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "Can't parse %s (expected an integer)",
67 epsg_string.c_str());
68 }
69 return (int)result;
70 }
71
72 } catch (RuntimeError &e) {
73 e.add_context("parsing the EPSG code in the PROJ string '%s'",
74 proj_string.c_str());
75 throw;
76 }
77}
78
79//! @brief Return CF-Convention "mapping" variable corresponding to an EPSG code specified in a
80//! PROJ string.
81VariableMetadata epsg_to_cf(units::System::Ptr system, const std::string &proj_string) {
82 VariableMetadata mapping("mapping", system);
83
84 int epsg = parse_epsg(proj_string);
85
86 switch (epsg) {
87 case 3413:
88 mapping["latitude_of_projection_origin"] = {90.0};
89 mapping["scale_factor_at_projection_origin"] = {1.0};
90 mapping["straight_vertical_longitude_from_pole"] = {-45.0};
91 mapping["standard_parallel"] = {70.0};
92 mapping["false_northing"] = {0.0};
93 mapping["grid_mapping_name"] = "polar_stereographic";
94 mapping["false_easting"] = {0.0};
95 break;
96 case 3031:
97 mapping["latitude_of_projection_origin"] = {-90.0};
98 mapping["scale_factor_at_projection_origin"] = {1.0};
99 mapping["straight_vertical_longitude_from_pole"] = {0.0};
100 mapping["standard_parallel"] = {-71.0};
101 mapping["false_northing"] = {0.0};
102 mapping["grid_mapping_name"] = "polar_stereographic";
103 mapping["false_easting"] = {0.0};
104 break;
105 case 3057:
106 mapping["grid_mapping_name"] = "lambert_conformal_conic" ;
107 mapping["longitude_of_central_meridian"] = {-19.} ;
108 mapping["false_easting"] = {500000.} ;
109 mapping["false_northing"] = {500000.} ;
110 mapping["latitude_of_projection_origin"] = {65.} ;
111 mapping["standard_parallel"] = {64.25, 65.75} ;
112 mapping["long_name"] = "CRS definition" ;
113 mapping["longitude_of_prime_meridian"] = {0.} ;
114 mapping["semi_major_axis"] = {6378137.} ;
115 mapping["inverse_flattening"] = {298.257222101} ;
116 break;
117 case 5936:
118 mapping["latitude_of_projection_origin"] = {90.0};
119 mapping["scale_factor_at_projection_origin"] = {1.0};
120 mapping["straight_vertical_longitude_from_pole"] = {-150.0};
121 mapping["standard_parallel"] = {90.0};
122 mapping["false_northing"] = {2000000.0};
123 mapping["grid_mapping_name"] = "polar_stereographic";
124 mapping["false_easting"] = {2000000.0};
125 break;
126 case 26710:
127 mapping["longitude_of_central_meridian"] = {-123.0};
128 mapping["false_easting"] = {500000.0};
129 mapping["false_northing"] = {0.0};
130 mapping["grid_mapping_name"] = "transverse_mercator";
131 mapping["inverse_flattening"] = {294.978698213898};
132 mapping["latitude_of_projection_origin"] = {0.0};
133 mapping["scale_factor_at_central_meridian"] = {0.9996};
134 mapping["semi_major_axis"] = {6378206.4};
135 mapping["unit"] = "metre";
136 break;
137 default:
138 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "unknown EPSG code '%d' in PROJ string '%s'",
139 (int)epsg, proj_string.c_str());
140 }
141
142 return mapping;
143}
144
145void check_consistency_epsg(const VariableMetadata &cf_mapping, const std::string &proj_string) {
146
147 VariableMetadata epsg_mapping = epsg_to_cf(cf_mapping.unit_system(), proj_string);
148
149 // All CF grid mapping variables have to have the "grid_mapping_name" attribute.
150 bool mapping_is_empty = not cf_mapping.has_attribute("grid_mapping_name");
151 bool epsg_mapping_is_empty = not epsg_mapping.has_attribute("grid_mapping_name");
152
153 if (mapping_is_empty and epsg_mapping_is_empty) {
154 // empty mapping variables are equivalent
155 return;
156 }
157
158 // Check if the `cf_mapping` variable matches the EPSG code in `proj_string`.
159 //
160 // Check strings.
161 for (const auto &s : epsg_mapping.all_strings()) {
162 if (not cf_mapping.has_attribute(s.first)) {
164 "inconsistent metadata:\n"
165 "PROJ string \"%s\" requires %s = \"%s\",\n"
166 "but the mapping variable has no attribute \"%s\".",
167 proj_string.c_str(), s.first.c_str(), s.second.c_str(),
168 s.first.c_str());
169 }
170
171 std::string string = cf_mapping[s.first];
172
173 if (not(string == s.second)) {
175 "inconsistent metadata:\n"
176 "%s requires %s = \"%s\",\n"
177 "but the mapping variable has %s = \"%s\".",
178 proj_string.c_str(), s.first.c_str(), s.second.c_str(),
179 s.first.c_str(), string.c_str());
180 }
181 }
182
183 // Check doubles
184 const double eps = 1e-12;
185 for (auto d : epsg_mapping.all_doubles()) {
186 if (not cf_mapping.has_attribute(d.first)) {
188 "inconsistent metadata:\n"
189 "%s requires %s = %f,\n"
190 "but the mapping variable has no attribute \"%s\".",
191 proj_string.c_str(), d.first.c_str(), d.second[0],
192 d.first.c_str());
193 }
194
195 double value = cf_mapping.get_number(d.first);
196
197 if (std::fabs(value - d.second[0]) > eps) {
199 "inconsistent metadata:\n"
200 "%s requires %s = %f,\n"
201 "but the mapping variable has %s = %f.",
202 proj_string.c_str(), d.first.c_str(), d.second[0],
203 d.first.c_str(), value);
204 }
205 }
206}
207
208std::string grid_name(const pism::File &file, const std::string &variable_name,
209 pism::units::System::Ptr sys, bool piecewise_constant) {
210
211 std::vector<std::string> dimensions = file.dimensions(variable_name);
212
213 if (dimensions.empty()) {
214 // variable `variable_name` has no dimensions, so we check if it is a domain variable
215 auto dimension_list = file.read_text_attribute(variable_name, "dimensions");
216 dimensions = split(dimension_list, ' ');
217 }
218
219 if (dimensions.empty()) {
221 "variable '%s' in '%s' does not define a grid",
222 variable_name.c_str(), file.name().c_str());
223 }
224
225 std::string result = split(file.name(), '/').back();
226
227 for (const auto &d : dimensions) {
228 auto type = file.dimension_type(d, sys);
229
230 if (type == pism::X_AXIS or type == pism::Y_AXIS) {
231 result += ":";
232 result += d;
233 }
234 }
235
236 if (piecewise_constant) {
237 result += ":piecewise_constant";
238 }
239
240 return result;
241}
242
243/*!
244 * Return true if `string` contains "epsg:" or "EPSG:", otherwise false.
245 */
246static bool contains_epsg(const std::string &string) {
247 for (const auto &auth : { "epsg:", "EPSG:" }) {
248 if (string.find(auth) != std::string::npos) {
249 return true;
250 }
251 }
252 return false;
253}
254
255/*!
256 * Get PROJ parameters from the grid mapping variable `mapping_variable_name` in
257 * `input_file` *or* from global attributes.
258 *
259 * Return the string containing PROJ parameters.
260 *
261 * Tries
262 *
263 * - reading the "crs_wkt" attribute of the grid mapping variable (CF conventions)
264 * - reading the "proj_params" attribute of the grid mapping variable (convention used by CDO)
265 *
266 * If failed to discover PROJ parameters, tries global attributes "proj" and "proj4", in
267 * this order (PISM's old convention).
268 */
269static std::string get_proj_parameters(const File &input_file, const std::string &mapping_variable_name) {
270
271 std::string proj_string;
272
273 if (not mapping_variable_name.empty()) {
274 // try the crs_wkt attribute
275 proj_string = input_file.read_text_attribute(mapping_variable_name, "crs_wkt");
276
277 // if failed, try the "proj_params" attribute used by CDO:
278 if (proj_string.empty()) {
279 proj_string = input_file.read_text_attribute(mapping_variable_name, "proj_params");
280 }
281 }
282
283 // if failed, try the global attribute "proj"
284 if (proj_string.empty()) {
285 proj_string = input_file.read_text_attribute("PISM_GLOBAL", "proj");
286 }
287
288 // if failed, try the global attribute "proj4"
289 if (proj_string.empty()) {
290 proj_string = input_file.read_text_attribute("PISM_GLOBAL", "proj4");
291 }
292
293 if (contains_epsg(proj_string)) {
294 // re-create the PROJ string to make sure it does not contain "+init="
295 proj_string = pism::printf("EPSG:%d", parse_epsg(proj_string));
296 }
297
298 return proj_string;
299}
300
301
302/*!
303 * Get projection info for a variable `variable_name` from `input_file`.
304 *
305 * Obtains the string containing PROJ parameters as described in `get_proj_parameters()`.
306 * If the grid mapping variable has
307 */
308VariableMetadata mapping_info_from_file(const File &input_file, const std::string &variable_name,
309 units::System::Ptr unit_system) {
310
311 auto mapping_variable_name = input_file.read_text_attribute(variable_name, "grid_mapping");
312
313 auto proj_string = get_proj_parameters(input_file, mapping_variable_name);
314
315 auto proj_is_epsg = contains_epsg(proj_string);
316
317 // Initialize (and possibly validate) the CF-style grid mapping variable by reading
318 // metadata from the input file:
319 VariableMetadata cf_mapping(mapping_variable_name.empty() ? "mapping" : mapping_variable_name,
320 unit_system);
321 if (input_file.variable_exists(mapping_variable_name)) {
322 // input file has a mapping variable
323
324 cf_mapping = io::read_attributes(input_file, mapping_variable_name, unit_system);
325
326 // From the CF Conventions document, section 5.6:
327 //
328 // The one attribute that all grid mapping variables must have is grid_mapping_name, which
329 // takes a string value that contains the mapping’s name.
330
331 if (cf_mapping.has_attribute("grid_mapping_name") and proj_is_epsg) {
332 // The grid mapping variable contains the CF Conventions-style grid mapping
333 // definition: check consistency
334 try {
335 check_consistency_epsg(cf_mapping, proj_string);
336 } catch (RuntimeError &e) {
337 e.add_context("getting projection info from variable '%s' in '%s'", variable_name.c_str(),
338 input_file.name().c_str());
339 throw;
340 }
341 }
342 }
343
344 if (cf_mapping.has_attribute("grid_mapping_name")) {
345 if (proj_string.empty()) {
346 // CF-style mapping was initialized, by the PROJ string was not: set proj_string
347 // from cf_mapping
348 proj_string = cf_to_proj(cf_mapping);
349 }
350 } else {
351 if (proj_is_epsg) {
352 // cf_mapping was not initialized by the code above and proj_string contains an EPSG
353 // code: we may be able to convert to a CF-style mapping variable and set cf_mapping
354 // from proj_string
355 cf_mapping = epsg_to_cf(unit_system, proj_string);
356 }
357 }
358
359 // Store the PROJ string in the "proj_params" attribute - for compatibility with CDO and
360 // to retrieve later.
361 if (not proj_string.empty()) {
362 cf_mapping["proj_params"] = proj_string;
363 }
364
365 return cf_mapping;
366}
367
369
370#if (Pism_USE_PROJ==1)
371
372//! Computes the area of a triangle using vector cross product.
373static double triangle_area(const double *A, const double *B, const double *C) {
374 double V1[3], V2[3];
375 for (int j = 0; j < 3; ++j) {
376 V1[j] = B[j] - A[j];
377 V2[j] = C[j] - A[j];
378 }
379 using std::pow;
380 using std::sqrt;
381 return 0.5*sqrt(pow(V1[1]*V2[2] - V2[1]*V1[2], 2) +
382 pow(V1[0]*V2[2] - V2[0]*V1[2], 2) +
383 pow(V1[0]*V2[1] - V2[0]*V1[1], 2));
384}
385
386void compute_cell_areas(const std::string &projection, array::Scalar &result) {
387 auto grid = result.grid();
388
389 Proj pism_to_geocent(projection, "+proj=geocent +datum=WGS84 +ellps=WGS84");
390
391// Cell layout:
392// (nw) (ne)
393// +-----------+
394// | |
395// | |
396// | o |
397// | |
398// | |
399// +-----------+
400// (sw) (se)
401
402 double dx2 = 0.5 * grid->dx(), dy2 = 0.5 * grid->dy();
403
404 array::AccessScope list(result);
405
406 for (auto p : grid->points()) {
407 const int i = p.i(), j = p.j();
408
409 const double
410 x = grid->x(i),
411 y = grid->y(j);
412 double
413 x_nw = x - dx2, y_nw = y + dy2,
414 x_ne = x + dx2, y_ne = y + dy2,
415 x_se = x + dx2, y_se = y - dy2,
416 x_sw = x - dx2, y_sw = y - dy2;
417
418 PJ_COORD out;
419
420 out = proj_trans(pism_to_geocent, PJ_FWD, proj_coord(x_nw, y_nw, 0, 0));
421 double nw[3] = {out.xyz.x, out.xyz.y, out.xyz.z};
422
423 out = proj_trans(pism_to_geocent, PJ_FWD, proj_coord(x_ne, y_ne, 0, 0));
424 double ne[3] = {out.xyz.x, out.xyz.y, out.xyz.z};
425
426 out = proj_trans(pism_to_geocent, PJ_FWD, proj_coord(x_se, y_se, 0, 0));
427 double se[3] = {out.xyz.x, out.xyz.y, out.xyz.z};
428
429 out = proj_trans(pism_to_geocent, PJ_FWD, proj_coord(x_sw, y_sw, 0, 0));
430 double sw[3] = {out.xyz.x, out.xyz.y, out.xyz.z};
431
432 result(i, j) = triangle_area(sw, se, ne) + triangle_area(ne, nw, sw);
433 }
434}
435
436static void compute_lon_lat(const std::string &projection,
437 LonLat which, array::Scalar &result) {
438
439 Proj crs(projection, "EPSG:4326");
440
441// Cell layout:
442// (nw) (ne)
443// +-----------+
444// | |
445// | |
446// | o |
447// | |
448// | |
449// +-----------+
450// (sw) (se)
451
452 auto grid = result.grid();
453
454 array::AccessScope list{&result};
455
456 for (auto p : grid->points()) {
457 const int i = p.i(), j = p.j();
458
459 PJ_COORD out = proj_trans(crs, PJ_FWD, proj_coord(grid->x(i), grid->y(j), 0, 0));
460
461 if (which == LONGITUDE) {
462 // longitude: lambda
463 result(i, j) = out.lp.lam;
464 } else {
465 // latitude: phi
466 result(i, j) = out.lp.phi;
467 }
468 }
469}
470
471static void compute_lon_lat_bounds(const std::string &projection,
472 LonLat which,
473 array::Array3D &result) {
474
475 Proj crs(projection, "EPSG:4326");
476
477 auto grid = result.grid();
478
479 double dx2 = 0.5 * grid->dx(), dy2 = 0.5 * grid->dy();
480 double x_offsets[] = {-dx2, dx2, dx2, -dx2};
481 double y_offsets[] = {-dy2, -dy2, dy2, dy2};
482
483 array::AccessScope list{&result};
484
485 for (auto p : grid->points()) {
486 const int i = p.i(), j = p.j();
487
488 double x0 = grid->x(i), y0 = grid->y(j);
489
490 double *values = result.get_column(i, j);
491
492 for (int k = 0; k < 4; ++k) {
493
494 PJ_COORD out;
495
496 // compute lon,lat coordinates:
497 out = proj_trans(crs, PJ_FWD, proj_coord(x0 + x_offsets[k], y0 + y_offsets[k], 0, 0));
498
499 if (which == LONGITUDE) {
500 // longitude: lambda
501 values[k] = out.lp.lam;
502 } else {
503 // latitude: phi
504 values[k] = out.lp.phi;
505 }
506 }
507 }
508}
509
510#else
511
512void compute_cell_areas(const std::string &projection, array::Scalar &result) {
513 (void) projection;
514
515 auto grid = result.grid();
516 result.set(grid->dx() * grid->dy());
517}
518
519static void compute_lon_lat(const std::string &projection, LonLat which,
520 array::Scalar &result) {
521 (void) projection;
522 (void) which;
523 (void) result;
524
525 throw RuntimeError(PISM_ERROR_LOCATION, "Cannot compute longitude and latitude."
526 " Please rebuild PISM with PROJ.");
527}
528
529static void compute_lon_lat_bounds(const std::string &projection,
530 LonLat which,
531 array::Array3D &result) {
532 (void) projection;
533 (void) which;
534 (void) result;
535
536 throw RuntimeError(PISM_ERROR_LOCATION, "Cannot compute longitude and latitude bounds."
537 " Please rebuild PISM with PROJ.");
538}
539
540#endif
541
542void compute_longitude(const std::string &projection, array::Scalar &result) {
543 compute_lon_lat(projection, LONGITUDE, result);
544}
545void compute_latitude(const std::string &projection, array::Scalar &result) {
546 compute_lon_lat(projection, LATITUDE, result);
547}
548
549void compute_lon_bounds(const std::string &projection, array::Array3D &result) {
550 compute_lon_lat_bounds(projection, LONGITUDE, result);
551}
552
553void compute_lat_bounds(const std::string &projection, array::Array3D &result) {
554 compute_lon_lat_bounds(projection, LATITUDE, result);
555}
556
557static std::string albers_conical_equal_area_to_proj(const VariableMetadata &mapping) {
558 // standard_parallel - There may be 1 or 2 values.
559 // longitude_of_central_meridian
560 // latitude_of_projection_origin
561 // false_easting
562 // false_northing
563 double longitude_of_projection_origin = mapping["longitude_of_projection_origin"];
564 double latitude_of_projection_origin = mapping["latitude_of_projection_origin"];
565 double false_easting = mapping["false_easting"];
566 double false_northing = mapping["false_northing"];
567
568 std::vector<double> standard_parallel = mapping["standard_parallel"];
569
570 auto result = pism::printf("+proj=aea +type=crs +lon_0=%f +lat_0=%f +x_0=%f +y_0=%f +lat_1=%f",
571 longitude_of_projection_origin,
572 latitude_of_projection_origin,
573 false_easting,
574 false_northing,
575 standard_parallel[0]);
576 if (standard_parallel.size() == 2) {
577 result += pism::printf(" +lat_2=%f", standard_parallel[1]);
578 }
579
580 return result;
581}
582
583static std::string azimuthal_equidistant_to_proj(const VariableMetadata &mapping) {
584 // Parameters:
585 // longitude_of_projection_origin
586 // latitude_of_projection_origin
587 // false_easting
588 // false_northing
589 double longitude_of_projection_origin = mapping["longitude_of_projection_origin"];
590 double latitude_of_projection_origin = mapping["latitude_of_projection_origin"];
591 double false_easting = mapping["false_easting"];
592 double false_northing = mapping["false_northing"];
593 return pism::printf("+proj=aeqd +type=crs +lon_0=%f +lat_0=%f +x_0=%f +y_0=%f",
594 longitude_of_projection_origin,
595 latitude_of_projection_origin, false_easting, false_northing);
596}
597
598static std::string lambert_azimuthal_equal_area_to_proj(const VariableMetadata &mapping) {
599 // longitude_of_projection_origin
600 // latitude_of_projection_origin
601 // false_easting
602 // false_northing
603 double longitude_of_projection_origin = mapping["longitude_of_projection_origin"];
604 double latitude_of_projection_origin = mapping["latitude_of_projection_origin"];
605 double false_easting = mapping["false_easting"];
606 double false_northing = mapping["false_northing"];
607 return pism::printf("+proj=laea +type=crs +lon_0=%f +lat_0=%f +x_0=%f +y_0=%f",
608 longitude_of_projection_origin,
609 latitude_of_projection_origin, false_easting, false_northing);
610}
611
612static std::string lambert_conformal_conic_to_proj(const VariableMetadata &mapping) {
613 // standard_parallel - There may be 1 or 2 values.
614 // longitude_of_central_meridian
615 // latitude_of_projection_origin
616 // false_easting
617 // false_northing
618 double longitude_of_projection_origin = mapping["longitude_of_projection_origin"];
619 double latitude_of_projection_origin = mapping["latitude_of_projection_origin"];
620 double false_easting = mapping["false_easting"];
621 double false_northing = mapping["false_northing"];
622 std::vector<double> standard_parallel = mapping["standard_parallel"];
623
624 auto result = pism::printf("+proj=lcc +type=crs +lon_0=%f +lat_0=%f +x_0=%f +y_0=%f +lat_1=%f",
625 longitude_of_projection_origin,
626 latitude_of_projection_origin,
627 false_easting,
628 false_northing,
629 standard_parallel[0]);
630 if (standard_parallel.size() == 2) {
631 result += pism::printf(" +lat_2=%f", standard_parallel[1]);
632 }
633 return result;
634}
635
637 // Parameters:
638 // longitude_of_central_meridian
639 // Either standard_parallel or scale_factor_at_projection_origin
640 // false_easting
641 // false_northing
642
643 double longitude_of_central_meridian = mapping["longitude_of_central_meridian"];
644 double false_easting = mapping["false_easting"];
645 double false_northing = mapping["false_northing"];
646
647 auto result = pism::printf("+proj=cea +type=crs +lon_0=%f +x_0=%f +y_0=%f",
648 longitude_of_central_meridian, false_easting, false_northing);
649
650 if (mapping.has_attribute("standard_parallel")) {
651 result += pism::printf(" +lat_ts=%f", (double)mapping["standard_parallel"]);
652 } else {
653 result += pism::printf(" +k_0=%f", (double)mapping["scale_factor_at_projection_origin"]);
654 }
655
656 return result;
657}
658
659static std::string latitude_longitude_to_proj(const VariableMetadata &/* unused */) {
660 // No parameters
661 return "+proj=lonlat +type=crs";
662}
663
664static std::string mercator_to_proj(const VariableMetadata &mapping) {
665 // Parameters:
666 // longitude_of_projection_origin
667 // Either standard_parallel (EPSG 9805) or scale_factor_at_projection_origin (EPSG 9804)
668 // false_easting
669 // false_northing
670
671 double longitude_of_projection_origin = mapping["longitude_of_projection_origin"];
672 double false_easting = mapping["false_easting"];
673 double false_northing = mapping["false_northing"];
674
675 auto result = pism::printf("+proj=merc +type=crs +lon_0=%f +x_0=%f +y_0=%f",
676 longitude_of_projection_origin,
677 false_easting, false_northing);
678
679 if (mapping.has_attribute("standard_parallel")) {
680 result += pism::printf(" +lat_ts=%f", (double)mapping["standard_parallel"]);
681 } else {
682 result += pism::printf(" +k_0=%f", (double)mapping["scale_factor_at_projection_origin"]);
683 }
684
685 return result;
686}
687
688static std::string orthographic_to_proj(const VariableMetadata &mapping) {
689 // Parameters:
690 // longitude_of_projection_origin
691 // latitude_of_projection_origin
692 // false_easting
693 // false_northing
694 double longitude_of_projection_origin = mapping["longitude_of_projection_origin"];
695 double latitude_of_projection_origin = mapping["latitude_of_projection_origin"];
696 double false_easting = mapping["false_easting"];
697 double false_northing = mapping["false_northing"];
698
699 // clang-format off
700 return pism::printf("+proj=ortho +type=crs +lon_0=%f +lat_0=%f +x_0=%f +y_0=%f",
701 longitude_of_projection_origin,
702 latitude_of_projection_origin,
703 false_easting,
704 false_northing);
705 // clang-format on
706}
707
708static std::string polar_stereographic_to_proj(const VariableMetadata &mapping) {
709 /* Parameters:
710 straight_vertical_longitude_from_pole
711 latitude_of_projection_origin - Either +90. or -90.
712 Either standard_parallel or scale_factor_at_projection_origin
713 false_easting
714 false_northing
715 */
716
717 double straight_vertical_longitude_from_pole = mapping["straight_vertical_longitude_from_pole"];
718 double latitude_of_projection_origin = mapping["latitude_of_projection_origin"];
719 double false_easting = mapping["false_easting"];
720 double false_northing = mapping["false_northing"];
721
722 auto result = pism::printf("+proj=stere +type=crs +lat_0=%f +lon_0=%f +x_0=%f +y_0=%f",
723 latitude_of_projection_origin,
724 straight_vertical_longitude_from_pole,
725 false_easting,
726 false_northing);
727
728 if (mapping.has_attribute("standard_parallel")) {
729 result += pism::printf(" +lat_ts=%f", (double)mapping["standard_parallel"]);
730 } else {
731 result += pism::printf(" +k_0=%f", (double)mapping["scale_factor_at_projection_origin"]);
732 }
733
734 return result;
735}
736
737static std::string rotated_latitude_longitude_to_proj(const VariableMetadata &mapping) {
738 // grid_north_pole_latitude
739 // grid_north_pole_longitude
740 // north_pole_grid_longitude - This parameter is optional (default is 0).
741
742 /*
743 * From https://github.com/OSGeo/PROJ/blob/356e255022cea47bf242bc9e6345a1e87ab1031d/src/iso19111/operation/conversion.cpp#L2419
744 *
745 * Several conventions for the pole rotation method exists.
746 * The parameters provided in this method are remapped to the PROJ ob_tran
747 * operation with:
748 * <pre>
749 * +proj=ob_tran +o_proj=longlat +o_lon_p=northPoleGridLongitude
750 * +o_lat_p=gridNorthPoleLatitude
751 * +lon_0=180+gridNorthPoleLongitude
752 * </pre>
753 */
754
755 double grid_north_pole_latitude = mapping["grid_north_pole_latitude"];
756 double grid_north_pole_longitude = mapping["grid_north_pole_longitude"];
757 double north_pole_grid_longitude = 0.0;
758
759 if (mapping.has_attribute("north_pole_grid_longitude")) {
760 north_pole_grid_longitude = mapping["north_pole_grid_longitude"];
761 }
762
763 return pism::printf("+proj=ob_tran +type=crs +o_proj=longlat +o_lon_p=%f +o_lat_p=%f +lon_0=%f",
764 north_pole_grid_longitude,
765 grid_north_pole_latitude,
766 180.0 + grid_north_pole_longitude);
767}
768
769static std::string stereographic_to_proj(const VariableMetadata &mapping) {
770 /* Parameters:
771 longitude_of_projection_origin
772 latitude_of_projection_origin
773 scale_factor_at_projection_origin
774 false_easting
775 false_northing
776*/
777
778 double straight_vertical_longitude_from_pole = mapping["straight_vertical_longitude_from_pole"];
779 double latitude_of_projection_origin = mapping["latitude_of_projection_origin"];
780 double false_easting = mapping["false_easting"];
781 double false_northing = mapping["false_northing"];
782 double scale_factor_at_projection_origin = mapping["scale_factor_at_projection_origin"];
783
784 // clang-format off
785 return pism::printf("+proj=stere +type=crs +lat_0=%f +lon_0=%f +x_0=%f +y_0=%f +k_0=%f",
786 latitude_of_projection_origin,
787 straight_vertical_longitude_from_pole,
788 false_easting,
789 false_northing,
790 scale_factor_at_projection_origin);
791 // clang-format on
792}
793
794static std::string transverse_mercator_to_proj(const VariableMetadata &mapping) {
795 double scale_factor_at_central_meridian = mapping["scale_factor_at_central_meridian"];
796 double longitude_of_central_meridian = mapping["longitude_of_central_meridian"];
797 double latitude_of_projection_origin = mapping["latitude_of_projection_origin"];
798 double false_easting = mapping["false_easting"];
799 double false_northing = mapping["false_northing"];
800
801 return pism::printf("+proj=tmerc +type=crs +lon_0=%f +lat_0=%f +k=%f +x_0=%f +y_0=%f",
802 longitude_of_central_meridian,
803 latitude_of_projection_origin,
804 scale_factor_at_central_meridian,
805 false_easting,
806 false_northing);
807}
808
809static std::string vertical_perspective_to_proj(const VariableMetadata &mapping) {
810 // Parameters:
811 // latitude_of_projection_origin
812 // longitude_of_projection_origin
813 // perspective_point_height
814 // false_easting
815 // false_northing
816 std::string name = mapping["grid_mapping_name"];
817 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "grid mapping '%s' is not supported",
818 name.c_str());
819 return "";
820}
821
822static std::string common_flags(const VariableMetadata &mapping) {
823
824 auto flag = [&mapping](const char* name, const char* proj_name) {
825 if (mapping.has_attribute(name)) {
826 return pism::printf(" +%s=%f", proj_name, (double)mapping[name]);
827 }
828 return std::string{};
829 };
830
831 std::string result;
832
833 // ellipsoid
834 result += flag("earth_radius", "R");
835 result += flag("inverse_flattening", "rf");
836 result += flag("semi_major_axis", "a");
837 result += flag("semi_minor_axis", "b");
838
839 // prime meridian
840 result += flag("longitude_of_prime_meridian", "pm");
841
842 return result;
843}
844
845/*!
846 * Convert a CF-style grid mapping definition to a PROJ string.
847 *
848 * See http://cfconventions.org/Data/cf-conventions/cf-conventions-1.7/build/apf.html
849 *
850 * See also https://proj.org/en/9.4/operations/projections/index.html for a list of
851 * projections and their parameters.
852 *
853 * PROJ strings returned by this function are inspired by
854 * https://scitools.org.uk/cartopy/docs/latest/index.html
855 */
856std::string cf_to_proj(const VariableMetadata &mapping) {
857
858 // all projections: use the crs_wkt attribute if it is present
859 std::string wkt = mapping["crs_wkt"];
860 if (not wkt.empty()) {
861 return wkt;
862 }
863
864 typedef std::string(*cf_to_proj_fn)(const VariableMetadata&);
865
866 std::map<std::string, cf_to_proj_fn> mappings = {
867 { "albers_conical_equal_area", albers_conical_equal_area_to_proj },
868 { "azimuthal_equidistant", azimuthal_equidistant_to_proj },
869 { "lambert_azimuthal_equal_area", lambert_azimuthal_equal_area_to_proj },
870 { "lambert_conformal_conic", lambert_conformal_conic_to_proj },
871 { "lambert_cylindrical_equal_area", lambert_cylindrical_equal_area_to_proj },
872 { "latitude_longitude", latitude_longitude_to_proj },
873 { "mercator", mercator_to_proj },
874 { "orthographic", orthographic_to_proj },
875 { "polar_stereographic", polar_stereographic_to_proj },
876 { "rotated_latitude_longitude", rotated_latitude_longitude_to_proj },
877 { "stereographic", stereographic_to_proj },
878 { "transverse_mercator", transverse_mercator_to_proj },
879 { "vertical_perspective", vertical_perspective_to_proj },
880 };
881
882 std::string mapping_name = mapping["grid_mapping_name"];
883
884 if (mappings.find(mapping_name) == mappings.end()) {
886 "conversion CF -> PROJ for a '%s' grid is not implemented",
887 mapping_name.c_str());
888 }
889
890 return mappings[mapping_name](mapping) + common_flags(mapping);
891}
892
893} // end of namespace pism
AxisType dimension_type(const std::string &name, units::System::Ptr unit_system) const
Get the "type" of a dimension.
Definition File.cc:460
bool variable_exists(const std::string &short_name) const
Checks if a variable exists.
Definition File.cc:378
std::string name() const
Definition File.cc:274
std::vector< std::string > dimensions(const std::string &variable_name) const
Definition File.cc:390
std::string read_text_attribute(const std::string &var_name, const std::string &att_name) const
Get a text attribute.
Definition File.cc:650
High-level PISM I/O class.
Definition File.hh:57
void add_context(const std::string &message)
Add a message providing some context. This way we can (sort of) get a stack trace even though C++ exc...
static RuntimeError formatted(const ErrorLocation &location, const char format[],...) __attribute__((format(printf
build a RuntimeError with a formatted message
double get_number(const std::string &name) const
Get a single-valued scalar attribute.
const std::map< std::string, std::string > & all_strings() const
std::shared_ptr< units::System > unit_system() const
bool has_attribute(const std::string &name) const
const std::map< std::string, std::vector< double > > & all_doubles() const
A virtual class collecting methods common to ice and bedrock 3D fields.
Definition Array3D.hh:33
std::shared_ptr< const Grid > grid() const
Definition Array.cc:134
void set(double c)
Result: v[j] <- c for all j.
Definition Array.cc:659
std::shared_ptr< System > Ptr
Definition Units.hh:47
#define PISM_ERROR_LOCATION
VariableMetadata read_attributes(const File &file, const std::string &variable_name, std::shared_ptr< units::System > unit_system)
static std::string lambert_azimuthal_equal_area_to_proj(const VariableMetadata &mapping)
void check_consistency_epsg(const VariableMetadata &cf_mapping, const std::string &proj_string)
Check consistency of the "mapping" variable with the EPSG code in the proj string.
static bool contains_epsg(const std::string &string)
static std::string get_proj_parameters(const File &input_file, const std::string &mapping_variable_name)
@ X_AXIS
Definition IO_Flags.hh:34
@ Y_AXIS
Definition IO_Flags.hh:34
static std::string mercator_to_proj(const VariableMetadata &mapping)
static std::string stereographic_to_proj(const VariableMetadata &mapping)
static std::string polar_stereographic_to_proj(const VariableMetadata &mapping)
static void compute_lon_lat_bounds(const std::string &projection, LonLat which, array::Array3D &result)
void compute_latitude(const std::string &projection, array::Scalar &result)
int parse_epsg(const std::string &proj_string)
Definition projection.cc:45
static std::string lambert_conformal_conic_to_proj(const VariableMetadata &mapping)
std::string printf(const char *format,...)
void compute_longitude(const std::string &projection, array::Scalar &result)
static std::string azimuthal_equidistant_to_proj(const VariableMetadata &mapping)
VariableMetadata epsg_to_cf(units::System::Ptr system, const std::string &proj_string)
Return CF-Convention "mapping" variable corresponding to an EPSG code specified in a PROJ string.
Definition projection.cc:81
static const double k
Definition exactTestP.cc:42
VariableMetadata mapping_info_from_file(const File &input_file, const std::string &variable_name, units::System::Ptr unit_system)
std::string grid_name(const pism::File &file, const std::string &variable_name, pism::units::System::Ptr sys, bool piecewise_constant)
static std::string vertical_perspective_to_proj(const VariableMetadata &mapping)
@ LONGITUDE
@ LATITUDE
static std::string transverse_mercator_to_proj(const VariableMetadata &mapping)
static double triangle_area(const Point &a, const Point &b, const Point &c)
static void compute_lon_lat(const std::string &projection, LonLat which, array::Scalar &result)
static std::string albers_conical_equal_area_to_proj(const VariableMetadata &mapping)
void compute_lat_bounds(const std::string &projection, array::Array3D &result)
static std::string rotated_latitude_longitude_to_proj(const VariableMetadata &mapping)
static std::string lambert_cylindrical_equal_area_to_proj(const VariableMetadata &mapping)
void compute_cell_areas(const std::string &projection, array::Scalar &result)
void compute_lon_bounds(const std::string &projection, array::Array3D &result)
std::string cf_to_proj(const VariableMetadata &mapping)
static std::string orthographic_to_proj(const VariableMetadata &mapping)
static std::string latitude_longitude_to_proj(const VariableMetadata &)
static std::string common_flags(const VariableMetadata &mapping)
std::vector< std::string > split(const std::string &input, char separator)
Transform a separator-separated list (a string) into a vector of strings.
std::shared_ptr< Grid > grid(std::shared_ptr< Context > ctx)
Definition pism.cc:173