diff options
Diffstat (limited to 'src/internal.cpp')
| -rw-r--r-- | src/internal.cpp | 639 |
1 files changed, 355 insertions, 284 deletions
diff --git a/src/internal.cpp b/src/internal.cpp index c43605d1..3f3d191e 100644 --- a/src/internal.cpp +++ b/src/internal.cpp @@ -1,11 +1,14 @@ /****************************************************************************** + * Project: PROJ.4 + * Purpose: This is primarily material originating from pj_obs_api.c + * (now proj_4D_api.c), that does not fit into the API + * category. Hence this pile of tubings and fittings for + * PROJ.4 internal plumbing. * - * Project: PROJ - * Purpose: ISO19111:2018 implementation - * Author: Even Rouault <even dot rouault at spatialys dot com> + * Author: Thomas Knudsen, thokn@sdfe.dk, 2017-07-05 * ****************************************************************************** - * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * Copyright (c) 2016, 2017, 2018, Thomas Knudsen/SDFE * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -24,351 +27,419 @@ * 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. - ****************************************************************************/ + *****************************************************************************/ + +#include <ctype.h> +#include <errno.h> +#include <math.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> -#ifndef FROM_PROJ_CPP -#define FROM_PROJ_CPP -#endif +#include "geodesic.h" +#include "proj_internal.h" +#include "projects.h" -#include "proj/internal/internal.hpp" -#include <cstdint> -#include <cstring> -#ifdef _MSC_VER -#include <string.h> -#else -#include <strings.h> -#endif -#include <exception> -#include <iomanip> // std::setprecision -#include <locale> -#include <sstream> // std::istringstream and std::ostringstream -#include <string> - -#include "sqlite3.h" - -NS_PROJ_START - -namespace internal { - -// --------------------------------------------------------------------------- - -/** - * Replace all occurrences of before with after. - */ -std::string replaceAll(const std::string &str, const std::string &before, - const std::string &after) { - std::string ret(str); - const size_t nBeforeSize = before.size(); - const size_t nAfterSize = after.size(); - if (nBeforeSize) { - size_t nStartPos = 0; - while ((nStartPos = ret.find(before, nStartPos)) != std::string::npos) { - ret.replace(nStartPos, nBeforeSize, after); - nStartPos += nAfterSize; - } - } - return ret; +enum pj_io_units pj_left (PJ *P) { + enum pj_io_units u = P->inverted? P->right: P->left; + if (u==PJ_IO_UNITS_CLASSIC) + return PJ_IO_UNITS_PROJECTED; + return u; } -// --------------------------------------------------------------------------- - -inline static bool EQUALN(const char *a, const char *b, size_t size) { -#ifdef _MSC_VER - return _strnicmp(a, b, size) == 0; -#else - return strncasecmp(a, b, size) == 0; -#endif +enum pj_io_units pj_right (PJ *P) { + enum pj_io_units u = P->inverted? P->left: P->right; + if (u==PJ_IO_UNITS_CLASSIC) + return PJ_IO_UNITS_PROJECTED; + return u; } -/** - * Case-insensitive equality test - */ -bool ci_equal(const std::string &a, const std::string &b) noexcept { - const auto size = a.size(); - if (size != b.size()) { - return false; - } - return EQUALN(a.c_str(), b.c_str(), size); + +/* Work around non-constness of MSVC HUGE_VAL by providing functions rather than constants */ +PJ_COORD proj_coord_error (void) { + PJ_COORD c; + c.v[0] = c.v[1] = c.v[2] = c.v[3] = HUGE_VAL; + return c; } -bool ci_equal(const std::string &a, const char *b) noexcept { - const auto size = a.size(); - if (size != strlen(b)) { - return false; + + +/**************************************************************************************/ +PJ_COORD pj_approx_2D_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coo) { +/*************************************************************************************** +Behave mostly as proj_trans, but attempt to use 2D interfaces only. +Used in gie.c, to enforce testing 2D code, and by PJ_pipeline.c to implement +chained calls starting out with a call to its 2D interface. +***************************************************************************************/ + if (nullptr==P) + return coo; + if (P->inverted) + direction = static_cast<PJ_DIRECTION>(-direction); + switch (direction) { + case PJ_FWD: + coo.xy = pj_fwd (coo.lp, P); + return coo; + case PJ_INV: + coo.lp = pj_inv (coo.xy, P); + return coo; + case PJ_IDENT: + return coo; + default: + break; } - return EQUALN(a.c_str(), b, size); + proj_errno_set (P, EINVAL); + return proj_coord_error (); } -bool ci_equal(const char *a, const char *b) noexcept { - const auto size = strlen(a); - if (size != strlen(b)) { - return false; + +/**************************************************************************************/ +PJ_COORD pj_approx_3D_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coo) { +/*************************************************************************************** +Companion to pj_approx_2D_trans. + +Behave mostly as proj_trans, but attempt to use 3D interfaces only. +Used in gie.c, to enforce testing 3D code, and by PJ_pipeline.c to implement +chained calls starting out with a call to its 3D interface. +***************************************************************************************/ + if (nullptr==P) + return coo; + if (P->inverted) + direction = static_cast<PJ_DIRECTION>(-direction); + switch (direction) { + case PJ_FWD: + coo.xyz = pj_fwd3d (coo.lpz, P); + return coo; + case PJ_INV: + coo.lpz = pj_inv3d (coo.xyz, P); + return coo; + case PJ_IDENT: + return coo; + default: + break; } - return EQUALN(a, b, size); + proj_errno_set (P, EINVAL); + return proj_coord_error (); } -// --------------------------------------------------------------------------- - -bool ci_less(const std::string &a, const std::string &b) noexcept { -#ifdef _MSC_VER - return _stricmp(a.c_str(), b.c_str()) < 0; -#else - return strcasecmp(a.c_str(), b.c_str()) < 0; -#endif +/**************************************************************************************/ +int pj_has_inverse(PJ *P) { +/*************************************************************************************** +Check if a a PJ has an inverse. +***************************************************************************************/ + return ( (P->inverted && (P->fwd || P->fwd3d || P->fwd4d) ) || + ( P->inv || P->inv3d || P->inv4d) ); } -// --------------------------------------------------------------------------- -/** - * Convert to lower case. - */ +/* Move P to a new context - or to the default context if 0 is specified */ +void proj_context_set (PJ *P, PJ_CONTEXT *ctx) { + if (nullptr==ctx) + ctx = pj_get_default_ctx (); + pj_set_ctx (P, ctx); +} -std::string tolower(const std::string &str) -{ - std::string ret(str); - for (size_t i = 0; i < ret.size(); i++) - ret[i] = static_cast<char>(::tolower(ret[i])); - return ret; +void proj_context_inherit (PJ *parent, PJ *child) { + if (nullptr==parent) + pj_set_ctx (child, pj_get_default_ctx()); + else + pj_set_ctx (child, pj_get_ctx(parent)); } -// --------------------------------------------------------------------------- -/** - * Convert to upper case. - */ -std::string toupper(const std::string &str) +/*****************************************************************************/ +char *pj_chomp (char *c) { +/****************************************************************************** +Strip pre- and postfix whitespace. Inline comments (indicated by '#') are +considered whitespace. +******************************************************************************/ + size_t i, n; + char *comment; + char *start = c; + + if (nullptr==c) + return nullptr; + + comment = strchr (c, '#'); + if (comment) + *comment = 0; + + n = strlen (c); + if (0==n) + return c; + + /* Eliminate postfix whitespace */ + for (i = n - 1; (i > 0) && (isspace (c[i]) || ';'==c[i]); i--) + c[i] = 0; + + /* Find start of non-whitespace */ + while (0 != *start && (';'==*start || isspace (*start))) + start++; + + n = strlen (start); + if (0==n) { + c[0] = 0; + return c; + } -{ - std::string ret(str); - for (size_t i = 0; i < ret.size(); i++) - ret[i] = static_cast<char>(::toupper(ret[i])); - return ret; + memmove (c, start, n + 1); + return c; } -// --------------------------------------------------------------------------- -/** Strip leading and trailing double quote characters */ -std::string stripQuotes(const std::string &str) { - if (str.size() >= 2 && str[0] == '"' && str.back() == '"') { - return str.substr(1, str.size() - 2); - } - return str; -} -// --------------------------------------------------------------------------- - -size_t ci_find(const std::string &str, const char *needle) noexcept { - const size_t needleSize = strlen(needle); - for (size_t i = 0; i + needleSize <= str.size(); i++) { - if (EQUALN(str.c_str() + i, needle, needleSize)) { - return i; +/*****************************************************************************/ +char *pj_shrink (char *c) { +/****************************************************************************** +Collapse repeated whitespace. Remove '+' and ';'. Make ',' and '=' greedy, +consuming their surrounding whitespace. +******************************************************************************/ + size_t i, j, n; + + /* Flag showing that a whitespace (ws) has been written after last non-ws */ + size_t ws; + + if (nullptr==c) + return nullptr; + + pj_chomp (c); + n = strlen (c); + + /* First collapse repeated whitespace (including +/;) */ + i = 0; + ws = 0; + for (j = 0; j < n; j++) { + + /* Eliminate prefix '+', only if preceded by whitespace */ + /* (i.e. keep it in 1.23e+08) */ + if ((i > 0) && ('+'==c[j]) && ws) + c[j] = ' '; + if ((i==0) && ('+'==c[j])) + c[j] = ' '; + + if (isspace (c[j]) || ';'==c[j]) { + if (0==ws && (i > 0)) + c[i++] = ' '; + ws = 1; + continue; } - } - return std::string::npos; -} - -// --------------------------------------------------------------------------- - -size_t ci_find(const std::string &str, const std::string &needle, - size_t startPos) noexcept { - const size_t needleSize = needle.size(); - for (size_t i = startPos; i + needleSize <= str.size(); i++) { - if (EQUALN(str.c_str() + i, needle.c_str(), needleSize)) { - return i; + else { + ws = 0; + c[i++] = c[j]; } } - return std::string::npos; -} - -// --------------------------------------------------------------------------- + c[i] = 0; + n = strlen(c); + + /* Then make ',' and '=' greedy */ + i = 0; + for (j = 0; j < n; j++) { + if (i==0) { + c[i++] = c[j]; + continue; + } -/* -bool starts_with(const std::string &str, const std::string &prefix) noexcept { - if (str.size() < prefix.size()) { - return false; - } - return std::memcmp(str.c_str(), prefix.c_str(), prefix.size()) == 0; -} -*/ + /* Skip space before '='/',' */ + if ('='==c[j] || ','==c[j]) { + if (c[i - 1]==' ') + c[i - 1] = c[j]; + else + c[i++] = c[j]; + continue; + } -// --------------------------------------------------------------------------- + if (' '==c[j] && ('='==c[i - 1] || ','==c[i - 1]) ) + continue; -/* -bool starts_with(const std::string &str, const char *prefix) noexcept { - const size_t prefixSize = std::strlen(prefix); - if (str.size() < prefixSize) { - return false; + c[i++] = c[j]; } - return std::memcmp(str.c_str(), prefix, prefixSize) == 0; + c[i] = 0; + return c; } -*/ -// --------------------------------------------------------------------------- -bool ci_starts_with(const char *str, const char *prefix) noexcept { - const auto str_size = strlen(str); - const auto prefix_size = strlen(prefix); - if (str_size < prefix_size) { - return false; - } - return EQUALN(str, prefix, prefix_size); -} -// --------------------------------------------------------------------------- - -bool ci_starts_with(const std::string &str, - const std::string &prefix) noexcept { - if (str.size() < prefix.size()) { - return false; +/*****************************************************************************/ +size_t pj_trim_argc (char *args) { +/****************************************************************************** +Trim all unnecessary whitespace (and non-essential syntactic tokens) from the +argument string, args, and count its number of elements. +******************************************************************************/ + size_t i, m, n; + pj_shrink (args); + n = strlen (args); + if (n==0) + return 0; + for (i = m = 0; i < n; i++) { + if (' '==args[i]) { + args[i] = 0; + m++; + } } - return EQUALN(str.c_str(), prefix.c_str(), prefix.size()); + return m + 1; } -// --------------------------------------------------------------------------- -bool ends_with(const std::string &str, const std::string &suffix) noexcept { - if (str.size() < suffix.size()) { - return false; - } - return std::memcmp(str.c_str() + str.size() - suffix.size(), suffix.c_str(), - suffix.size()) == 0; -} -// --------------------------------------------------------------------------- - -double c_locale_stod(const std::string &s) { - - const auto s_size = s.size(); - // Fast path - if (s_size > 0 && s_size < 15) { - std::int64_t acc = 0; - std::int64_t div = 1; - bool afterDot = false; - size_t i = 0; - if (s[0] == '-') { - ++i; - div = -1; - } else if (s[0] == '+') { - ++i; - } - for (; i < s_size; ++i) { - const auto ch = s[i]; - if (ch >= '0' && ch <= '9') { - acc = acc * 10 + ch - '0'; - if (afterDot) { - div *= 10; - } - } else if (ch == '.') { - afterDot = true; - } else { - div = 0; - } - } - if (div) { - return static_cast<double>(acc) / div; +/*****************************************************************************/ +char **pj_trim_argv (size_t argc, char *args) { +/****************************************************************************** +Create an argv-style array from elements placed in the argument string, args. + +args is a trimmed string as returned by pj_trim_argc(), and argc is the number +of trimmed strings found (i.e. the return value of pj_trim_args()). Hence, + int argc = pj_trim_argc (args); + char **argv = pj_trim_argv (argc, args); +will produce a classic style (argc, argv) pair from a string of whitespace +separated args. No new memory is allocated for storing the individual args +(they stay in the args string), but for the pointers to the args a new array +is allocated and returned. + +It is the duty of the caller to free this array. +******************************************************************************/ + size_t i, j; + char **argv; + + if (nullptr==args) + return nullptr; + if (0==argc) + return nullptr; + + + /* turn the input string into an array of strings */ + argv = (char **) calloc (argc, sizeof (char *)); + if (nullptr==argv) + return nullptr; + argv[0] = args; + j = 1; + for (i = 0; ; i++) { + if (0==args[i]) { + argv[j++] = args + (i + 1); } + if (j==argc) + break; } - - std::istringstream iss(s); - iss.imbue(std::locale::classic()); - double d; - iss >> d; - if (!iss.eof() || iss.fail()) { - throw std::invalid_argument("non double value"); - } - return d; + return argv; } -// --------------------------------------------------------------------------- -std::vector<std::string> split(const std::string &str, char separator) { - std::vector<std::string> res; - size_t lastPos = 0; - size_t newPos = 0; - while ((newPos = str.find(separator, lastPos)) != std::string::npos) { - res.push_back(str.substr(lastPos, newPos - lastPos)); - lastPos = newPos + 1; + +/*****************************************************************************/ +char *pj_make_args (size_t argc, char **argv) { +/****************************************************************************** +pj_make_args is the inverse of the pj_trim_argc/pj_trim_argv combo: It +converts free format command line input to something proj_create can consume. + +Allocates, and returns, an array of char, large enough to hold a whitespace +separated copy of the args in argv. It is the duty of the caller to free this +array. +******************************************************************************/ + size_t i, n; + char *p; + + for (i = n = 0; i < argc; i++) + n += strlen (argv[i]); + + p = static_cast<char*>(pj_calloc (n + argc + 1, sizeof (char))); + if (nullptr==p) + return nullptr; + if (0==argc) + return p; + + for (i = 0; i < argc; i++) { + strcat (p, argv[i]); + strcat (p, " "); } - res.push_back(str.substr(lastPos)); - return res; + return pj_shrink (p); } -// --------------------------------------------------------------------------- -#ifdef _WIN32 -// For some reason, sqlite3_snprintf() in the sqlite3 builds used on AppVeyor -// doesn't round identically to the Unix builds, and thus breaks a number of -// unit test. So to avoid this, use the stdlib formatting - -std::string toString(int val) { - std::ostringstream buffer; - buffer.imbue(std::locale::classic()); - buffer << val; - return buffer.str(); +/*****************************************************************************/ +void proj_context_errno_set (PJ_CONTEXT *ctx, int err) { +/****************************************************************************** +Raise an error directly on a context, without going through a PJ belonging +to that context. +******************************************************************************/ + if (nullptr==ctx) + ctx = pj_get_default_ctx(); + pj_ctx_set_errno (ctx, err); } -std::string toString(double val, int precision) { - std::ostringstream buffer; - buffer.imbue(std::locale::classic()); - buffer << std::setprecision(precision); - buffer << val; - auto str = buffer.str(); - if (precision == 15 && str.find("9999999999") != std::string::npos) { - buffer.str(""); - buffer.clear(); - buffer << std::setprecision(14); - buffer << val; - return buffer.str(); - } - return str; +/* logging */ + +/* pj_vlog resides in pj_log.c and relates to pj_log as vsprintf relates to sprintf */ +void pj_vlog( projCtx ctx, int level, const char *fmt, va_list args ); + + +/***************************************************************************************/ +PJ_LOG_LEVEL proj_log_level (PJ_CONTEXT *ctx, PJ_LOG_LEVEL log_level) { +/**************************************************************************************** + Set logging level 0-3. Higher number means more debug info. 0 turns it off +****************************************************************************************/ + PJ_LOG_LEVEL previous; + if (nullptr==ctx) + ctx = pj_get_default_ctx(); + if (nullptr==ctx) + return PJ_LOG_TELL; + previous = static_cast<PJ_LOG_LEVEL>(abs (ctx->debug_level)); + if (PJ_LOG_TELL==log_level) + return previous; + ctx->debug_level = log_level; + return previous; } -#else -std::string toString(int val) { - // use sqlite3 API that is slighly faster than std::ostringstream - // with forcing the C locale. sqlite3_snprintf() emulates a C locale. - constexpr int BUF_SIZE = 16; - char szBuffer[BUF_SIZE]; - sqlite3_snprintf(BUF_SIZE, szBuffer, "%d", val); - return szBuffer; -} - -std::string toString(double val, int precision) { - // use sqlite3 API that is slighly faster than std::ostringstream - // with forcing the C locale. sqlite3_snprintf() emulates a C locale. - constexpr int BUF_SIZE = 32; - char szBuffer[BUF_SIZE]; - sqlite3_snprintf(BUF_SIZE, szBuffer, "%.*g", precision, val); - if (precision == 15 && strstr(szBuffer, "9999999999")) { - sqlite3_snprintf(BUF_SIZE, szBuffer, "%.14g", val); - } - return szBuffer; +/*****************************************************************************/ +void proj_log_error (PJ *P, const char *fmt, ...) { +/****************************************************************************** + For reporting the most severe events. +******************************************************************************/ + va_list args; + va_start( args, fmt ); + pj_vlog (pj_get_ctx (P), PJ_LOG_ERROR , fmt, args); + va_end( args ); } -#endif -// --------------------------------------------------------------------------- - -std::string concat(const char *a, const std::string &b) { - std::string res(a); - res += b; - return res; +/*****************************************************************************/ +void proj_log_debug (PJ *P, const char *fmt, ...) { +/****************************************************************************** + For reporting debugging information. +******************************************************************************/ + va_list args; + va_start( args, fmt ); + pj_vlog (pj_get_ctx (P), PJ_LOG_DEBUG_MAJOR , fmt, args); + va_end( args ); } -std::string concat(const char *a, const std::string &b, const char *c) { - std::string res(a); - res += b; - res += c; - return res; -} -// --------------------------------------------------------------------------- +/*****************************************************************************/ +void proj_log_trace (PJ *P, const char *fmt, ...) { +/****************************************************************************** + For reporting embarrasingly detailed debugging information. +******************************************************************************/ + va_list args; + va_start( args, fmt ); + pj_vlog (pj_get_ctx (P), PJ_LOG_DEBUG_MINOR , fmt, args); + va_end( args ); +} -} // namespace internal -NS_PROJ_END +/*****************************************************************************/ +void proj_log_func (PJ_CONTEXT *ctx, void *app_data, PJ_LOG_FUNCTION logf) { +/****************************************************************************** + Put a new logging function into P's context. The opaque object app_data is + passed as first arg at each call to the logger +******************************************************************************/ + if (nullptr==ctx) + pj_get_default_ctx (); + if (nullptr==ctx) + return; + ctx->app_data = app_data; + if (nullptr!=logf) + ctx->logger = logf; +} |
