PISM, A Parallel Ice Sheet Model  stable v2.1-1-g6902d5502 committed by Ed Bueler on 2023-12-20 08:38:27 -0800
projection.cc
Go to the documentation of this file.
1 /* Copyright (C) 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 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 
23 #include "pism/util/projection.hh"
24 #include "pism/util/VariableMetadata.hh"
25 #include "pism/util/error_handling.hh"
26 #include "pism/util/io/File.hh"
27 #include "pism/util/io/io_helpers.hh"
28 #include "pism/util/Grid.hh"
29 #include "pism/util/array/Scalar.hh"
30 #include "pism/util/array/Array3D.hh"
31 
32 #include "pism/pism_config.hh"
33 
34 #if (Pism_USE_PROJ==1)
35 #include "pism/util/Proj.hh"
36 #endif
37 
38 namespace pism {
39 
40 MappingInfo::MappingInfo(const std::string &mapping_name, units::System::Ptr unit_system)
41  : mapping(mapping_name, unit_system) {
42  // empty
43 }
44 
45 //! @brief Return CF-Convention "mapping" variable corresponding to an EPSG code specified in a
46 //! PROJ string.
47 VariableMetadata epsg_to_cf(units::System::Ptr system, const std::string &proj_string) {
48  VariableMetadata mapping("mapping", system);
49 
50  int auth_len = 5; // length of "epsg:"
51  std::string::size_type position = std::string::npos;
52  for (const auto &auth : {"epsg:", "EPSG:"}) {
53  position = proj_string.find(auth);
54  if (position != std::string::npos) {
55  break;
56  }
57  }
58 
59  if (position == std::string::npos) {
61  "could not parse EPSG code '%s'", proj_string.c_str());
62  }
63 
64  long int epsg = 0;
65  {
66  std::string epsg_code = proj_string.substr(position + auth_len);
67  const char *str = epsg_code.c_str();
68  char *endptr = NULL;
69  epsg = strtol(str, &endptr, 10);
70  if (endptr == str) {
72  "failed to parse EPSG code at '%s' in PROJ string '%s'",
73  epsg_code.c_str(), proj_string.c_str());
74  }
75  }
76 
77  switch (epsg) {
78  case 3413:
79  mapping["latitude_of_projection_origin"] = {90.0};
80  mapping["scale_factor_at_projection_origin"] = {1.0};
81  mapping["straight_vertical_longitude_from_pole"] = {-45.0};
82  mapping["standard_parallel"] = {70.0};
83  mapping["false_northing"] = {0.0};
84  mapping["grid_mapping_name"] = "polar_stereographic";
85  mapping["false_easting"] = {0.0};
86  break;
87  case 3031:
88  mapping["latitude_of_projection_origin"] = {-90.0};
89  mapping["scale_factor_at_projection_origin"] = {1.0};
90  mapping["straight_vertical_longitude_from_pole"] = {0.0};
91  mapping["standard_parallel"] = {-71.0};
92  mapping["false_northing"] = {0.0};
93  mapping["grid_mapping_name"] = "polar_stereographic";
94  mapping["false_easting"] = {0.0};
95  break;
96  case 3057:
97  mapping["grid_mapping_name"] = "lambert_conformal_conic" ;
98  mapping["longitude_of_central_meridian"] = {-19.} ;
99  mapping["false_easting"] = {500000.} ;
100  mapping["false_northing"] = {500000.} ;
101  mapping["latitude_of_projection_origin"] = {65.} ;
102  mapping["standard_parallel"] = {64.25, 65.75} ;
103  mapping["long_name"] = "CRS definition" ;
104  mapping["longitude_of_prime_meridian"] = {0.} ;
105  mapping["semi_major_axis"] = {6378137.} ;
106  mapping["inverse_flattening"] = {298.257222101} ;
107  break;
108  case 5936:
109  mapping["latitude_of_projection_origin"] = {90.0};
110  mapping["scale_factor_at_projection_origin"] = {1.0};
111  mapping["straight_vertical_longitude_from_pole"] = {-150.0};
112  mapping["standard_parallel"] = {90.0};
113  mapping["false_northing"] = {2000000.0};
114  mapping["grid_mapping_name"] = "polar_stereographic";
115  mapping["false_easting"] = {2000000.0};
116  break;
117  case 26710:
118  mapping["longitude_of_central_meridian"] = {-123.0};
119  mapping["false_easting"] = {500000.0};
120  mapping["false_northing"] = {0.0};
121  mapping["grid_mapping_name"] = "transverse_mercator";
122  mapping["inverse_flattening"] = {294.978698213898};
123  mapping["latitude_of_projection_origin"] = {0.0};
124  mapping["scale_factor_at_central_meridian"] = {0.9996};
125  mapping["semi_major_axis"] = {6378206.4};
126  mapping["unit"] = "metre";
127  break;
128  default:
129  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "unknown EPSG code '%d' in PROJ string '%s'",
130  (int)epsg, proj_string.c_str());
131  }
132 
133  return mapping;
134 }
135 
137 
138  VariableMetadata epsg_mapping = epsg_to_cf(info.mapping.unit_system(), info.proj);
139 
140  bool mapping_is_empty = not info.mapping.has_attributes();
141  bool epsg_mapping_is_empty = not epsg_mapping.has_attributes();
142 
143  if (mapping_is_empty and epsg_mapping_is_empty) {
144  // empty mapping variables are equivalent
145  return;
146  } else {
147  // Check if the "info.mapping" variable in the input file matches the EPSG code.
148  // Check strings.
149  for (const auto &s : epsg_mapping.all_strings()) {
150  if (not info.mapping.has_attribute(s.first)) {
151  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "inconsistent metadata:\n"
152  "PROJ string \"%s\" requires %s = \"%s\",\n"
153  "but the mapping variable has no %s.",
154  info.proj.c_str(),
155  s.first.c_str(), s.second.c_str(),
156  s.first.c_str());
157  }
158 
159  std::string string = info.mapping[s.first];
160 
161  if (not (string == s.second)) {
162  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "inconsistent metadata:\n"
163  "%s requires %s = \"%s\",\n"
164  "but the mapping variable has %s = \"%s\".",
165  info.proj.c_str(),
166  s.first.c_str(), s.second.c_str(),
167  s.first.c_str(),
168  string.c_str());
169  }
170  }
171 
172  // Check doubles
173  for (auto d : epsg_mapping.all_doubles()) {
174  if (not info.mapping.has_attribute(d.first)) {
175  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "inconsistent metadata:\n"
176  "%s requires %s = %f,\n"
177  "but the mapping variable has no %s.",
178  info.proj.c_str(),
179  d.first.c_str(), d.second[0],
180  d.first.c_str());
181  }
182 
183  double value = info.mapping.get_number(d.first);
184 
185  if (std::fabs(value - d.second[0]) > 1e-12) {
186  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "inconsistent metadata:\n"
187  "%s requires %s = %f,\n"
188  "but the mapping variable has %s = %f.",
189  info.proj.c_str(),
190  d.first.c_str(), d.second[0],
191  d.first.c_str(),
192  value);
193  }
194  }
195  }
196 }
197 
198 MappingInfo get_projection_info(const File &input_file, const std::string &mapping_name,
199  units::System::Ptr unit_system) {
200  MappingInfo result(mapping_name, unit_system);
201 
202  result.proj = input_file.read_text_attribute("PISM_GLOBAL", "proj");
203 
204  bool proj_is_epsg = false;
205  for (const auto &auth : {"epsg:", "EPSG:"}) {
206  if (result.proj.find(auth) != std::string::npos) {
207  proj_is_epsg = true;
208  break;
209  }
210  }
211 
212  if (input_file.find_variable(mapping_name)) {
213  // input file has a mapping variable
214 
215  io::read_attributes(input_file, mapping_name, result.mapping);
216 
217  if (proj_is_epsg) {
218  // check consistency
219  try {
220  check_consistency_epsg(result);
221  } catch (RuntimeError &e) {
222  e.add_context("getting projection info from %s", input_file.filename().c_str());
223  throw;
224  }
225  } else {
226  // use mapping read from input_file (can't check consistency here)
227  }
228  } else {
229  // no mapping variable in the input file
230 
231  if (proj_is_epsg) {
232  result.mapping = epsg_to_cf(unit_system, result.proj);
233  } else {
234  // leave mapping empty
235  }
236  }
237  return result;
238 }
239 
241 
242 #if (Pism_USE_PROJ==1)
243 
244 //! Computes the area of a triangle using vector cross product.
245 static double triangle_area(const double *A, const double *B, const double *C) {
246  double V1[3], V2[3];
247  for (int j = 0; j < 3; ++j) {
248  V1[j] = B[j] - A[j];
249  V2[j] = C[j] - A[j];
250  }
251  using std::pow;
252  using std::sqrt;
253  return 0.5*sqrt(pow(V1[1]*V2[2] - V2[1]*V1[2], 2) +
254  pow(V1[0]*V2[2] - V2[0]*V1[2], 2) +
255  pow(V1[0]*V2[1] - V2[0]*V1[1], 2));
256 }
257 
258 void compute_cell_areas(const std::string &projection, array::Scalar &result) {
259  auto grid = result.grid();
260 
261  Proj pism_to_geocent(projection, "+proj=geocent +datum=WGS84 +ellps=WGS84");
262 
263 // Cell layout:
264 // (nw) (ne)
265 // +-----------+
266 // | |
267 // | |
268 // | o |
269 // | |
270 // | |
271 // +-----------+
272 // (sw) (se)
273 
274  double dx2 = 0.5 * grid->dx(), dy2 = 0.5 * grid->dy();
275 
276  array::AccessScope list(result);
277 
278  for (auto p = grid->points(); p; p.next()) {
279  const int i = p.i(), j = p.j();
280 
281  const double
282  x = grid->x(i),
283  y = grid->y(j);
284  double
285  x_nw = x - dx2, y_nw = y + dy2,
286  x_ne = x + dx2, y_ne = y + dy2,
287  x_se = x + dx2, y_se = y - dy2,
288  x_sw = x - dx2, y_sw = y - dy2;
289 
290  PJ_COORD in, out;
291 
292  in.xy = {x_nw, y_nw};
293  out = proj_trans(*pism_to_geocent, PJ_FWD, in);
294  double nw[3] = {out.xyz.x, out.xyz.y, out.xyz.z};
295 
296  in.xy = {x_ne, y_ne};
297  out = proj_trans(*pism_to_geocent, PJ_FWD, in);
298  double ne[3] = {out.xyz.x, out.xyz.y, out.xyz.z};
299 
300  in.xy = {x_se, y_se};
301  out = proj_trans(*pism_to_geocent, PJ_FWD, in);
302  double se[3] = {out.xyz.x, out.xyz.y, out.xyz.z};
303 
304  in.xy = {x_sw, y_sw};
305  out = proj_trans(*pism_to_geocent, PJ_FWD, in);
306  double sw[3] = {out.xyz.x, out.xyz.y, out.xyz.z};
307 
308  result(i, j) = triangle_area(sw, se, ne) + triangle_area(ne, nw, sw);
309  }
310 }
311 
312 static void compute_lon_lat(const std::string &projection,
313  LonLat which, array::Scalar &result) {
314 
315  Proj crs(projection, "EPSG:4326");
316 
317 // Cell layout:
318 // (nw) (ne)
319 // +-----------+
320 // | |
321 // | |
322 // | o |
323 // | |
324 // | |
325 // +-----------+
326 // (sw) (se)
327 
328  auto grid = result.grid();
329 
330  array::AccessScope list{&result};
331 
332  for (auto p = grid->points(); p; p.next()) {
333  const int i = p.i(), j = p.j();
334 
335  PJ_COORD in, out;
336 
337  in.xy = {grid->x(i), grid->y(j)};
338  out = proj_trans(*crs, PJ_FWD, in);
339 
340  if (which == LONGITUDE) {
341  result(i, j) = out.lp.phi;
342  } else {
343  result(i, j) = out.lp.lam;
344  }
345  }
346 }
347 
348 static void compute_lon_lat_bounds(const std::string &projection,
349  LonLat which,
350  array::Array3D &result) {
351 
352  Proj crs(projection, "EPSG:4326");
353 
354  auto grid = result.grid();
355 
356  double dx2 = 0.5 * grid->dx(), dy2 = 0.5 * grid->dy();
357  double x_offsets[] = {-dx2, dx2, dx2, -dx2};
358  double y_offsets[] = {-dy2, -dy2, dy2, dy2};
359 
360  array::AccessScope list{&result};
361 
362  for (auto p = grid->points(); p; p.next()) {
363  const int i = p.i(), j = p.j();
364 
365  double x0 = grid->x(i), y0 = grid->y(j);
366 
367  double *values = result.get_column(i, j);
368 
369  for (int k = 0; k < 4; ++k) {
370 
371  PJ_COORD in, out;
372 
373  in.xy = {x0 + x_offsets[k], y0 + y_offsets[k]};
374 
375  // compute lon,lat coordinates:
376  out = proj_trans(*crs, PJ_FWD, in);
377 
378  if (which == LATITUDE) {
379  values[k] = out.lp.lam;
380  } else {
381  values[k] = out.lp.phi;
382  }
383  }
384  }
385 }
386 
387 #else
388 
389 void compute_cell_areas(const std::string &projection, array::Scalar &result) {
390  (void) projection;
391 
392  auto grid = result.grid();
393  result.set(grid->dx() * grid->dy());
394 }
395 
396 static void compute_lon_lat(const std::string &projection, LonLat which,
397  array::Scalar &result) {
398  (void) projection;
399  (void) which;
400  (void) result;
401 
402  throw RuntimeError(PISM_ERROR_LOCATION, "Cannot compute longitude and latitude."
403  " Please rebuild PISM with PROJ.");
404 }
405 
406 static void compute_lon_lat_bounds(const std::string &projection,
407  LonLat which,
408  array::Array3D &result) {
409  (void) projection;
410  (void) which;
411  (void) result;
412 
413  throw RuntimeError(PISM_ERROR_LOCATION, "Cannot compute longitude and latitude bounds."
414  " Please rebuild PISM with PROJ.");
415 }
416 
417 #endif
418 
419 void compute_longitude(const std::string &projection, array::Scalar &result) {
420  compute_lon_lat(projection, LONGITUDE, result);
421 }
422 void compute_latitude(const std::string &projection, array::Scalar &result) {
423  compute_lon_lat(projection, LATITUDE, result);
424 }
425 
426 void compute_lon_bounds(const std::string &projection, array::Array3D &result) {
427  compute_lon_lat_bounds(projection, LONGITUDE, result);
428 }
429 
430 void compute_lat_bounds(const std::string &projection, array::Array3D &result) {
431  compute_lon_lat_bounds(projection, LATITUDE, result);
432 }
433 
435 #if (Pism_USE_PROJ==1)
436  Impl(const std::string &proj_string) : coordinate_mapping(proj_string, "EPSG:4326") {
437  }
438  Proj coordinate_mapping;
439 #else
440  Impl(const std::string & /*unused*/) {
442  "Build PISM with PROJ to use pism::LonLatCalculator");
443  }
444 #endif
445 };
446 
447 LonLatCalculator::LonLatCalculator(const std::string &proj_string) : m_impl(new Impl(proj_string)) {
448 }
449 
451  delete m_impl;
452 }
453 
454 double LonLatCalculator::lon(double x, double y) const {
455  return lonlat(x, y)[0];
456 }
457 
458 double LonLatCalculator::lat(double x, double y) const {
459  return lonlat(x, y)[1];
460 }
461 
462 std::array<double, 2> LonLatCalculator::lonlat(double x, double y) const {
463 #if (Pism_USE_PROJ == 1)
464  PJ_COORD in, out;
465 
466  in.xy = { x, y };
467  out = proj_trans(*(m_impl->coordinate_mapping), PJ_FWD, in);
468 
469  return { out.lp.phi, out.lp.lam };
470 #else
471  (void) x;
472  (void) y;
473 #endif
474  return { 0.0, 0.0 };
475 }
476 
477 } // end of namespace pism
VariableLookupData find_variable(const std::string &short_name, const std::string &std_name) const
Find a variable using its standard name and/or short name.
Definition: File.cc:361
std::string filename() const
Definition: File.cc:307
std::string read_text_attribute(const std::string &var_name, const std::string &att_name) const
Get a text attribute.
Definition: File.cc:693
High-level PISM I/O class.
Definition: File.hh:56
std::array< double, 2 > lonlat(double x, double y) const
Definition: projection.cc:462
LonLatCalculator(const std::string &proj_string)
Definition: projection.cc:447
double lat(double x, double y) const
Definition: projection.cc:458
double lon(double x, double y) const
Definition: projection.cc:454
std::string proj
Definition: projection.hh:47
VariableMetadata mapping
Definition: projection.hh:46
MappingInfo(const std::string &mapping_name, units::System::Ptr unit_system)
Definition: projection.cc:40
A wrapper for PJ that makes sure pj_destroy is called.
Definition: Proj.hh:32
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
units::System::Ptr 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:132
void set(double c)
Result: v[j] <- c for all j.
Definition: Array.cc:707
std::shared_ptr< System > Ptr
Definition: Units.hh:47
#define PISM_ERROR_LOCATION
void read_attributes(const File &file, const std::string &variable_name, VariableMetadata &variable)
Definition: io_helpers.cc:1162
static void compute_lon_lat_bounds(const std::string &projection, LonLat which, array::Array3D &result)
Definition: projection.cc:406
void compute_latitude(const std::string &projection, array::Scalar &result)
Definition: projection.cc:422
void compute_longitude(const std::string &projection, array::Scalar &result)
Definition: projection.cc:419
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:47
static const double k
Definition: exactTestP.cc:42
void check_consistency_epsg(const MappingInfo &info)
Check consistency of the "mapping" variable with the EPSG code in the proj string.
Definition: projection.cc:136
@ LONGITUDE
Definition: projection.cc:240
@ LATITUDE
Definition: projection.cc:240
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)
Definition: projection.cc:396
MappingInfo get_projection_info(const File &input_file, const std::string &mapping_name, units::System::Ptr unit_system)
Get projection info from a file.
Definition: projection.cc:198
void compute_lat_bounds(const std::string &projection, array::Array3D &result)
Definition: projection.cc:430
void compute_cell_areas(const std::string &projection, array::Scalar &result)
Definition: projection.cc:389
void compute_lon_bounds(const std::string &projection, array::Array3D &result)
Definition: projection.cc:426
const int C[]
Definition: ssafd_code.cc:44
const double dy2
Definition: ssafd_code.cc:1
const double dx2
Definition: ssafd_code.cc:1
Impl(const std::string &)
Definition: projection.cc:440