pism

[fork] customized build of PISM, the parallel ice sheet model (tillflux branch)
git clone git://src.adamsgaard.dk/pism # fast
git clone https://src.adamsgaard.dk/pism.git # slow
Log | Files | Refs | README | LICENSE Back to index

projection.cc (14160B)


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