/****************************************************************************** * Project: PROJ.4 * Purpose: Mainline program sort of like ``proj'' for converting between * two coordinate systems. * Author: Frank Warmerdam, warmerda@home.com * ****************************************************************************** * Copyright (c) 2000, Frank Warmerdam * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #define FROM_PROJ_CPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include // PROJ include order is sensitive // clang-format off #include "proj.h" #include "proj_experimental.h" #include "proj_internal.h" #include "emess.h" #include "utils.h" // clang-format on #define MAX_LINE 1000 static PJ *transformation = nullptr; static bool srcIsLongLat = false; static double srcToRadians = 0.0; static bool destIsLongLat = false; static double destToRadians = 0.0; static bool destIsLatLong = false; static int reversein = 0, /* != 0 reverse input arguments */ reverseout = 0, /* != 0 reverse output arguments */ echoin = 0, /* echo input data to output line */ tag = '#'; /* beginning of line tag character */ static const char *oform = nullptr; /* output format for x-y or decimal degrees */ static char oform_buffer[16]; /* buffer for oform when using -d */ static const char *oterr = "*\t*"; /* output line for unprojectable input */ static const char *usage = "%s\nusage: %s [-dDeEfIlrstvwW [args]]\n" " [[--area name_or_code] | [--bbox west_long,south_lat,east_long,north_lat]]\n" " [--authority {name}] [--accuracy {accuracy}] [--no-ballpark] [--3d]\n" " [+opt[=arg] ...] [+to +opt[=arg] ...] [file ...]\n"; static double (*informat)(const char *, char **); /* input data deformatter function */ using namespace NS_PROJ::io; using namespace NS_PROJ::metadata; using namespace NS_PROJ::util; using namespace NS_PROJ::internal; /************************************************************************/ /* process() */ /* */ /* File processing function. */ /************************************************************************/ static void process(FILE *fid) { char line[MAX_LINE + 3], *s, pline[40]; PJ_UV data; for (;;) { double z; ++emess_dat.File_line; if (!(s = fgets(line, MAX_LINE, fid))) break; if (!strchr(s, '\n')) { /* overlong line */ int c; (void)strcat(s, "\n"); /* gobble up to newline */ while ((c = fgetc(fid)) != EOF && c != '\n') ; } if (*s == tag) { fputs(line, stdout); continue; } if (reversein) { data.v = (*informat)(s, &s); data.u = (*informat)(s, &s); } else { data.u = (*informat)(s, &s); data.v = (*informat)(s, &s); } z = strtod(s, &s); /* To avoid breaking existing tests, we read what is a possible t */ /* component of the input and rewind the s-pointer so that the final */ /* output has consistent behavior, with or without t values. */ /* This is a bit of a hack, in most cases 4D coordinates will be */ /* written to STDOUT (except when using -E) but the output format */ /* specified with -f is not respected for the t component, rather it */ /* is forward verbatim from the input. */ char *before_time = s; double t = strtod(s, &s); if( s == before_time ) t = HUGE_VAL; s = before_time; if (data.v == HUGE_VAL) data.u = HUGE_VAL; if (!*s && (s > line)) --s; /* assumed we gobbled \n */ if (echoin) { char temp; temp = *s; *s = '\0'; (void)fputs(line, stdout); *s = temp; putchar('\t'); } if (data.u != HUGE_VAL) { if (srcIsLongLat) { /* dmstor gives values to radians. Convert now to the SRS unit */ data.u /= srcToRadians; data.v /= srcToRadians; } PJ_COORD coord; coord.xyzt.x = data.u; coord.xyzt.y = data.v; coord.xyzt.z = z; coord.xyzt.t = t; coord = proj_trans(transformation, PJ_FWD, coord); data.u = coord.xyz.x; data.v = coord.xyz.y; z = coord.xyz.z; } if (data.u == HUGE_VAL) /* error output */ fputs(oterr, stdout); else if (destIsLongLat && !oform) { /*ascii DMS output */ // rtodms() expect radians: convert from the output SRS unit data.u *= destToRadians; data.v *= destToRadians; if (destIsLatLong) { if (reverseout) { fputs(rtodms(pline, data.v, 'E', 'W'), stdout); putchar('\t'); fputs(rtodms(pline, data.u, 'N', 'S'), stdout); } else { fputs(rtodms(pline, data.u, 'N', 'S'), stdout); putchar('\t'); fputs(rtodms(pline, data.v, 'E', 'W'), stdout); } } else if (reverseout) { fputs(rtodms(pline, data.v, 'N', 'S'), stdout); putchar('\t'); fputs(rtodms(pline, data.u, 'E', 'W'), stdout); } else { fputs(rtodms(pline, data.u, 'E', 'W'), stdout); putchar('\t'); fputs(rtodms(pline, data.v, 'N', 'S'), stdout); } } else { /* x-y or decimal degree ascii output */ if (destIsLongLat) { data.v *= destToRadians * RAD_TO_DEG; data.u *= destToRadians * RAD_TO_DEG; } if (reverseout) { printf(oform, data.v); putchar('\t'); printf(oform, data.u); } else { printf(oform, data.u); putchar('\t'); printf(oform, data.v); } } putchar(' '); if (oform != nullptr) printf(oform, z); else printf("%.3f", z); if (s) printf("%s", s); else printf("\n"); fflush(stdout); } } /************************************************************************/ /* instantiate_crs() */ /************************************************************************/ static PJ *instantiate_crs(const std::string &definition, bool &isLongLatCS, double &toRadians, bool &isLatFirst) { PJ *crs = proj_create(nullptr, pj_add_type_crs_if_needed(definition).c_str()); if (!crs) { return nullptr; } isLongLatCS = false; toRadians = 0.0; isLatFirst = false; auto type = proj_get_type(crs); if (type == PJ_TYPE_BOUND_CRS) { auto base = proj_get_source_crs(nullptr, crs); proj_destroy(crs); crs = base; type = proj_get_type(crs); } if (type == PJ_TYPE_GEOGRAPHIC_2D_CRS || type == PJ_TYPE_GEOGRAPHIC_3D_CRS || type == PJ_TYPE_GEODETIC_CRS) { auto cs = proj_crs_get_coordinate_system(nullptr, crs); assert(cs); const char *axisName = ""; proj_cs_get_axis_info(nullptr, cs, 0, &axisName, // name, nullptr, // abbrev nullptr, // direction &toRadians, nullptr, // unit name nullptr, // unit authority nullptr // unit code ); isLatFirst = NS_PROJ::internal::ci_find(std::string(axisName), "latitude") != std::string::npos; isLongLatCS = isLatFirst || NS_PROJ::internal::ci_find(std::string(axisName), "longitude") != std::string::npos; proj_destroy(cs); } return crs; } /************************************************************************/ /* get_geog_crs_proj_string_from_proj_crs() */ /************************************************************************/ static std::string get_geog_crs_proj_string_from_proj_crs(PJ *src, double &toRadians, bool &isLatFirst) { auto srcType = proj_get_type(src); if (srcType != PJ_TYPE_PROJECTED_CRS) { return std::string(); } auto base = proj_get_source_crs(nullptr, src); assert(base); auto baseType = proj_get_type(base); if (baseType != PJ_TYPE_GEOGRAPHIC_2D_CRS && baseType != PJ_TYPE_GEOGRAPHIC_3D_CRS) { proj_destroy(base); return std::string(); } auto cs = proj_crs_get_coordinate_system(nullptr, base); assert(cs); const char *axisName = ""; proj_cs_get_axis_info(nullptr, cs, 0, &axisName, // name, nullptr, // abbrev nullptr, // direction &toRadians, nullptr, // unit name nullptr, // unit authority nullptr // unit code ); isLatFirst = NS_PROJ::internal::ci_find(std::string(axisName), "latitude") != std::string::npos; proj_destroy(cs); auto retCStr = proj_as_proj_string(nullptr, base, PJ_PROJ_5, nullptr); std::string ret(retCStr ? retCStr : ""); proj_destroy(base); return ret; } /************************************************************************/ /* main() */ /************************************************************************/ int main(int argc, char **argv) { char *arg; char **eargv = argv; std::string fromStr; std::string toStr; FILE *fid; int eargc = 0, mon = 0; int have_to_flag = 0, inverse = 0; int use_env_locale = 0; if( argc == 0 ) { exit(1); } /* This is just to check that pj_init() is locale-safe */ /* Used by nad/testvarious */ if (getenv("PROJ_USE_ENV_LOCALE") != nullptr) use_env_locale = 1; /* Enable compatibility mode for init=epsg:XXXX by default */ if (getenv("PROJ_USE_PROJ4_INIT_RULES") == nullptr) { proj_context_use_proj4_init_rules(nullptr, true); } if ((emess_dat.Prog_name = strrchr(*argv, DIR_CHAR)) != nullptr) ++emess_dat.Prog_name; else emess_dat.Prog_name = *argv; inverse = !strncmp(emess_dat.Prog_name, "inv", 3); if (argc <= 1) { (void)fprintf(stderr, usage, pj_get_release(), emess_dat.Prog_name); exit(0); } // First pass to check if we have "cs2cs [-bla]* []" syntax bool isProj4StyleSyntax = false; for (int i = 1; i < argc; i++) { if (argv[i][0] == '+') { isProj4StyleSyntax = true; break; } } ExtentPtr bboxFilter; std::string area; const char* authority = nullptr; double accuracy = -1; bool allowBallpark = true; bool promoteTo3D = false; /* process run line arguments */ while (--argc > 0) { /* collect run line arguments */ ++argv; if (strcmp(*argv, "--area") == 0 ) { ++argv; --argc; if( argc == 0 ) { emess(1, "missing argument for --area"); std::exit(1); } area = *argv; } else if (strcmp(*argv, "--bbox") == 0) { ++argv; --argc; if( argc == 0 ) { emess(1, "missing argument for --bbox"); std::exit(1); } auto bboxStr(*argv); auto bbox(split(bboxStr, ',')); if (bbox.size() != 4) { std::cerr << "Incorrect number of values for option --bbox: " << bboxStr << std::endl; std::exit(1); } try { bboxFilter = Extent::createFromBBOX( c_locale_stod(bbox[0]), c_locale_stod(bbox[1]), c_locale_stod(bbox[2]), c_locale_stod(bbox[3])) .as_nullable(); } catch (const std::exception &e) { std::cerr << "Invalid value for option --bbox: " << bboxStr << ", " << e.what() << std::endl; std::exit(1); } } else if (strcmp(*argv, "--accuracy") == 0 ) { ++argv; --argc; if( argc == 0 ) { emess(1, "missing argument for --accuracy"); std::exit(1); } try { accuracy = c_locale_stod(*argv); } catch (const std::exception &e) { std::cerr << "Invalid value for option --accuracy: " << e.what() << std::endl; std::exit(1); } } else if (strcmp(*argv, "--authority") == 0 ) { ++argv; --argc; if( argc == 0 ) { emess(1, "missing argument for --authority"); std::exit(1); } authority = *argv; } else if (strcmp(*argv, "--no-ballpark") == 0 ) { allowBallpark = false; } else if (strcmp(*argv, "--3d") == 0 ) { promoteTo3D = true; } else if (**argv == '-') { for (arg = *argv;;) { switch (*++arg) { case '\0': /* position of "stdin" */ if (arg[-1] == '-') eargv[eargc++] = const_cast("-"); break; case 'v': /* monitor dump of initialization */ mon = 1; continue; case 'I': /* alt. method to spec inverse */ inverse = 1; continue; case 'E': /* echo ascii input to ascii output */ echoin = 1; continue; case 't': /* set col. one char */ if (arg[1]) tag = *++arg; else emess(1, "missing -t col. 1 tag"); continue; case 'l': /* list projections, ellipses or units */ if (!arg[1] || arg[1] == 'p' || arg[1] == 'P') { /* list projections */ const struct PJ_LIST *lp; int do_long = arg[1] == 'P', c; const char *str; for (lp = proj_list_operations(); lp->id; ++lp) { (void)printf("%s : ", lp->id); if (do_long) /* possibly multiline description */ (void)puts(*lp->descr); else { /* first line, only */ str = *lp->descr; while ((c = *str++) && c != '\n') putchar(c); putchar('\n'); } } } else if (arg[1] == '=') { /* list projection 'descr' */ const struct PJ_LIST *lp; arg += 2; for (lp = proj_list_operations(); lp->id; ++lp) if (!strcmp(lp->id, arg)) { (void)printf("%9s : %s\n", lp->id, *lp->descr); break; } } else if (arg[1] == 'e') { /* list ellipses */ const struct PJ_ELLPS *le; for (le = proj_list_ellps(); le->id; ++le) (void)printf("%9s %-16s %-16s %s\n", le->id, le->major, le->ell, le->name); } else if (arg[1] == 'u') { /* list units */ auto units = proj_get_units_from_database(nullptr, nullptr, "linear", false, nullptr); for( int i = 0; units && units[i]; i++ ) { if( units[i]->proj_short_name ) { (void)printf("%12s %-20.15g %s\n", units[i]->proj_short_name, units[i]->conv_factor, units[i]->name); } } proj_unit_list_destroy(units); } else if (arg[1] == 'm') { /* list prime meridians */ const struct PJ_PRIME_MERIDIANS *lpm; for (lpm = proj_list_prime_meridians(); lpm->id; ++lpm) (void)printf("%12s %-30s\n", lpm->id, lpm->defn); } else emess(1, "invalid list option: l%c", arg[1]); exit(0); /* cppcheck-suppress duplicateBreak */ continue; /* artificial */ case 'e': /* error line alternative */ if (--argc <= 0) noargument: emess(1, "missing argument for -%c", *arg); oterr = *++argv; continue; case 'W': /* specify seconds precision */ case 'w': /* -W for constant field width */ { char c = arg[1]; if (c != 0 && isdigit(c)) { set_rtodms(c - '0', *arg == 'W'); ++arg; } else emess(1, "-W argument missing or non-digit"); continue; } case 'f': /* alternate output format degrees or xy */ if (--argc <= 0) goto noargument; oform = *++argv; continue; case 'r': /* reverse input */ reversein = 1; continue; case 's': /* reverse output */ reverseout = 1; continue; case 'D': /* set debug level */ { if (--argc <= 0) goto noargument; int log_level = atoi(*++argv); if (log_level <= 0) { proj_log_level(pj_get_default_ctx(), PJ_LOG_NONE); } else if (log_level == 1) { proj_log_level(pj_get_default_ctx(), PJ_LOG_ERROR); } else if (log_level == 2) { proj_log_level(pj_get_default_ctx(), PJ_LOG_DEBUG); } else if (log_level == 3) { proj_log_level(pj_get_default_ctx(), PJ_LOG_TRACE); } else { proj_log_level(pj_get_default_ctx(), PJ_LOG_TELL); } continue; } case 'd': if (--argc <= 0) goto noargument; sprintf(oform_buffer, "%%.%df", atoi(*++argv)); oform = oform_buffer; break; default: emess(1, "invalid option: -%c", *arg); break; } break; } } else if (!isProj4StyleSyntax) { if (fromStr.empty()) fromStr = *argv; else if( toStr.empty() ) toStr = *argv; else { /* assumed to be input file name(s) */ eargv[eargc++] = *argv; } } else if (strcmp(*argv, "+to") == 0) { have_to_flag = 1; } else if (**argv == '+') { /* + argument */ if (have_to_flag) { if (!toStr.empty()) toStr += ' '; toStr += *argv; } else { if (!fromStr.empty()) fromStr += ' '; fromStr += *argv; } } else if (!have_to_flag) { fromStr = *argv; } else if (toStr.empty()) { toStr = *argv; } else /* assumed to be input file name(s) */ eargv[eargc++] = *argv; } if (eargc == 0) /* if no specific files force sysin */ eargv[eargc++] = const_cast("-"); if( oform ) { if( !validate_form_string_for_numbers(oform) ) { emess(3, "invalid format string"); exit(0); } } if (bboxFilter && !area.empty()) { std::cerr << "ERROR: --bbox and --area are exclusive" << std::endl; std::exit(1); } PJ_AREA* pj_area = nullptr; if (!area.empty()) { DatabaseContextPtr dbContext; try { dbContext = DatabaseContext::create().as_nullable(); } catch (const std::exception &e) { std::cerr << "ERROR: Cannot create database connection: " << e.what() << std::endl; std::exit(1); } // Process area of use try { if (area.find(' ') == std::string::npos && area.find(':') != std::string::npos) { auto tokens = split(area, ':'); if (tokens.size() == 2) { const std::string &areaAuth = tokens[0]; const std::string &areaCode = tokens[1]; bboxFilter = AuthorityFactory::create( NN_NO_CHECK(dbContext), areaAuth) ->createExtent(areaCode) .as_nullable(); } } if (!bboxFilter) { auto authFactory = AuthorityFactory::create( NN_NO_CHECK(dbContext), std::string()); auto res = authFactory->listAreaOfUseFromName(area, false); if (res.size() == 1) { bboxFilter = AuthorityFactory::create(NN_NO_CHECK(dbContext), res.front().first) ->createExtent(res.front().second) .as_nullable(); } else { res = authFactory->listAreaOfUseFromName(area, true); if (res.size() == 1) { bboxFilter = AuthorityFactory::create(NN_NO_CHECK(dbContext), res.front().first) ->createExtent(res.front().second) .as_nullable(); } else if (res.empty()) { std::cerr << "No area of use matching provided name" << std::endl; std::exit(1); } else { std::cerr << "Several candidates area of use " "matching provided name :" << std::endl; for (const auto &candidate : res) { auto obj = AuthorityFactory::create( NN_NO_CHECK(dbContext), candidate.first) ->createExtent(candidate.second); std::cerr << " " << candidate.first << ":" << candidate.second << " : " << *obj->description() << std::endl; } std::exit(1); } } } } catch (const std::exception &e) { std::cerr << "Area of use retrieval failed: " << e.what() << std::endl; std::exit(1); } } if (bboxFilter) { auto geogElts = bboxFilter->geographicElements(); if (geogElts.size() == 1) { auto bbox = std::dynamic_pointer_cast( geogElts[0].as_nullable()); if (bbox) { pj_area = proj_area_create(); proj_area_set_bbox(pj_area, bbox->westBoundLongitude(), bbox->southBoundLatitude(), bbox->eastBoundLongitude(), bbox->northBoundLatitude()); } } } /* * If the user has requested inverse, then just reverse the * coordinate systems. */ if (inverse) { std::swap(fromStr, toStr); } if (use_env_locale) { /* Set locale from environment */ setlocale(LC_ALL, ""); } if (fromStr.empty() && toStr.empty()) { emess(3, "missing source and target coordinate systems"); } proj_context_use_proj4_init_rules(nullptr, proj_context_get_use_proj4_init_rules(nullptr, TRUE)); PJ *src = nullptr; if (!fromStr.empty()) { bool ignored; src = instantiate_crs(fromStr, srcIsLongLat, srcToRadians, ignored); if (!src) { emess(3, "cannot instantiate source coordinate system"); } } PJ *dst = nullptr; if (!toStr.empty()) { dst = instantiate_crs(toStr, destIsLongLat, destToRadians, destIsLatLong); if (!dst) { emess(3, "cannot instantiate target coordinate system"); } } if (toStr.empty()) { assert(src); toStr = get_geog_crs_proj_string_from_proj_crs(src, destToRadians, destIsLatLong); if (toStr.empty()) { emess(3, "missing target CRS and source CRS is not a projected CRS"); } destIsLongLat = true; } else if (fromStr.empty()) { assert(dst); bool ignored; fromStr = get_geog_crs_proj_string_from_proj_crs(dst, srcToRadians, ignored); if (fromStr.empty()) { emess(3, "missing source CRS and target CRS is not a projected CRS"); } srcIsLongLat = true; } proj_destroy(src); proj_destroy(dst); src = proj_create(nullptr, pj_add_type_crs_if_needed(fromStr).c_str()); dst = proj_create(nullptr, pj_add_type_crs_if_needed(toStr).c_str()); if( promoteTo3D ) { auto src3D = proj_crs_promote_to_3D(nullptr, nullptr, src); if( src3D ) { proj_destroy(src); src = src3D; } auto dst3D = proj_crs_promote_to_3D(nullptr, nullptr, dst); if( dst3D ) { proj_destroy(dst); dst = dst3D; } } std::string authorityOption; /* keep this variable in this outer scope ! */ std::string accuracyOption; /* keep this variable in this outer scope ! */ std::vector options; if( authority ) { authorityOption = "AUTHORITY="; authorityOption += authority; options.push_back(authorityOption.data()); } if( accuracy >= 0 ) { accuracyOption = "ACCURACY="; accuracyOption += toString(accuracy); options.push_back(accuracyOption.data()); } if( !allowBallpark ) { options.push_back("ALLOW_BALLPARK=NO"); } options.push_back(nullptr); transformation = proj_create_crs_to_crs_from_pj(nullptr, src, dst, pj_area, options.data()); proj_destroy(src); proj_destroy(dst); proj_area_destroy(pj_area); if (!transformation) { emess(3, "cannot initialize transformation\ncause: %s", proj_errno_string(proj_context_errno(nullptr))); } if (use_env_locale) { /* Restore C locale to avoid issues in parsing/outputting numbers*/ setlocale(LC_ALL, "C"); } if (mon) { printf("%c ---- From Coordinate System ----\n", tag); printf("%s\n", fromStr.c_str()); printf("%c ---- To Coordinate System ----\n", tag); printf("%s\n", toStr.c_str()); } /* set input formatting control */ if (!srcIsLongLat) informat = strtod; else { informat = dmstor; } if (!destIsLongLat && !oform) oform = "%.2f"; /* process input file list */ for (; eargc--; ++eargv) { if (**eargv == '-') { fid = stdin; emess_dat.File_name = const_cast(""); } else { if ((fid = fopen(*eargv, "rt")) == nullptr) { emess(-2, *eargv, "input file"); continue; } emess_dat.File_name = *eargv; } emess_dat.File_line = 0; process(fid); fclose(fid); emess_dat.File_name = nullptr; } proj_destroy(transformation); proj_cleanup(); exit(0); /* normal completion */ }