aboutsummaryrefslogtreecommitdiff
path: root/src/gie.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gie.c')
-rw-r--r--src/gie.c722
1 files changed, 722 insertions, 0 deletions
diff --git a/src/gie.c b/src/gie.c
new file mode 100644
index 00000000..adfc4e0f
--- /dev/null
+++ b/src/gie.c
@@ -0,0 +1,722 @@
+/***********************************************************************
+
+ gie - The Geospatial Integrity Investigation Environment
+
+************************************************************************
+
+The Geospatial Integrity Investigation Environment "gie" is a modest
+regression testing environment for the PROJ.4 transformation library.
+
+Its primary design goal was to be able to replace those thousands of
+lines of regression testing code that are (at time of writing) part
+of PROJ.4, while not requiring any other kind of tooling than the same
+C compiler already employed for compiling the library.
+
+The basic functionality of the gie command language is implemented
+through just 3 command verbs:
+
+OPERATION, which defines the PROJ.4 operation to test,
+ACCEPT, which defines the input coordinate to read, and
+EXPECT, which defines the result to expect.
+
+E.g:
+
+operation +proj=utm +zone=32 +ellps=GRS80
+accept 12 55
+expect 691_875.632_14 6_098_907.825_05
+
+Note that gie accepts the underscore ("_") as a thousands separator.
+It is not required (in fact, it is entirely ignored by the input
+routine), but it significantly improves the readability of the very
+long strings of numbers typically required in projected coordinates.
+
+By default, gie considers the EXPECTation met, if the result comes to
+within 0.5 mm of the expected. This default can be changed using the
+TOLERANCE command verb (and yes, I know, linguistically speaking, both
+"operation" and "tolerance" are nouns, not verbs). See the first
+few hundred lines of the "builtins.gie" test file for more details of
+the command verbs available (verbs of both the VERBal and NOUNistic
+persuation).
+
+--
+
+But more importantly than being an acronym for "Geospatial Integrity
+Investigation Environment", gie were also the initials, user id, and
+USGS email address of Gerald Ian Evenden (1935--2016), the geospatial
+visionary, who, already in the 1980s, started what was to become the
+PROJ.4 of today.
+
+Gerald's clear vision was that map projections are *just special
+functions*. Some of them rather complex, most of them of two variables,
+but all of them *just special functions*, and not particularly more
+special than the sin(), cos(), tan(), and hypot() already available in
+the C standard library.
+
+And hence, *they should not be particularly much harder to use*, for a
+programmer, than the sin()s, tan()s and hypot()s so readily available.
+
+Gerald's ingenuity also showed in the implementation of the vision,
+where he devised a highly comprehensible, yet simple, system of key-value
+pairs for parameterising a map projection, and the highly flexible
+PJ struct, storing run-time compiled versions of those key-value pairs,
+hence making a map projection function call, pj_fwd(PJ, point), as easy
+as a traditional function call like hypot(x,y).
+
+While today, we may have more formally well defined metadata systems
+(most prominent the OGC WKT representation), nothing comes close being
+as easily readable ("human compatible") as Gerald's key-value system.
+This system in particular, and the PROJ.4 system in general, was
+Gerald's great gift to anyone using and/or communicating about geodata.
+
+It is only reasonable to name a program keeping an eye on the integrity
+of the PROJ.4 system in honour of Gerald. So in honour, and hopefully
+also in the spirit, of Gerald Ian Evenden (1935--2016), this is the
+Geospatial Integrity Investigation Environment.
+
+************************************************************************
+
+Thomas Knudsen, thokn@sdfe.dk, 2017-10-01/2017-10-08
+
+************************************************************************
+
+* Copyright (c) 2017 Thomas Knudsen
+* Copyright (c) 2017, SDFE
+*
+* 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.
+
+***********************************************************************/
+
+#include "optargpm.h"
+
+#include <proj.h>
+#include "proj_internal.h"
+#include "projects.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include <string.h>
+#include <ctype.h>
+
+#include <math.h>
+#include <errno.h>
+
+
+
+/* from proj_strtod.c */
+double proj_strtod(const char *str, char **endptr);
+double proj_atof(const char *str);
+
+static char *column (char *buf, int n);
+int main(int argc, char **argv);
+
+static int process_file (char *fname);
+static int errmsg (int errlev, char *msg, ...);
+static int get_inp (FILE *f, char *inp, int size);
+static int get_cmnd (char *inp, char *cmnd, int len);
+static char *get_args (char *inp);
+static int dispatch (char *cmnd, char *args);
+
+
+
+#define SKIP -1
+
+typedef struct {
+ char operation[10000];
+ PJ *P;
+ PJ_COORD a, b, c, e;
+ PJ_DIRECTION dir;
+ int verbosity;
+ int nargs;
+ int op_id;
+ int op_ok, op_ko;
+ int total_ok, total_ko;
+ int grand_ok, grand_ko;
+ double tolerance;
+ char *curr_file;
+ FILE *fout;
+} gie_ctx;
+
+gie_ctx T = {{""}, 0, {{0,0,0,0}}, {{0,0,0,0}}, {{0,0,0,0}}, {{0,0,0,0}}, PJ_FWD, 1, 0, 0,0,0,0,0,0,0, 0.0005, 0, 0};
+
+OPTARGS *o;
+
+
+
+size_t tol_lineno = 0;
+size_t lineno = 0;
+size_t level = 0;
+char delim[] = {"-------------------------------------------------------------------------------\n"};
+char DELIM[] = {"===============================================================================\n"};
+
+
+#define CMDLEN 25000
+
+int nfiles = 0;
+
+
+static const char usage[] = {
+ "--------------------------------------------------------------------------------\n"
+ "Usage: %s [-options]... infile...\n"
+ "--------------------------------------------------------------------------------\n"
+ "Options:\n"
+ "--------------------------------------------------------------------------------\n"
+ " -h Help: print this usage information\n"
+ " -o /path/to/file Specify output file name\n"
+ " -v Verbose: Provide non-essential informational output.\n"
+ " Repeat -v for more verbosity (e.g. -vv)\n"
+ " -q Quiet: Opposite of verbose. In quiet mode not even errors\n"
+ " are reported. Only interaction is through the return code\n"
+ " (0 on success, non-zero indicates number of FAILED tests)\n"
+ "--------------------------------------------------------------------------------\n"
+ "Long Options:\n"
+ "--------------------------------------------------------------------------------\n"
+ " --output Alias for -o\n"
+ " --verbose Alias for -v\n"
+ " --help Alias for -h\n"
+ "--------------------------------------------------------------------------------\n"
+ "Examples:\n"
+ "--------------------------------------------------------------------------------\n"
+ "1. Run all tests in file \"corner-cases.gie\", providing much extra information\n"
+ " gie -vvvv corner-cases.gie\n"
+ "2. Run all tests in files \"foo\" and \"bar\", providing info on failures only\n"
+ " gie foo bar\n"
+ "--------------------------------------------------------------------------------\n"
+};
+
+int main (int argc, char **argv) {
+ int i;
+ const char *longflags[] = {"v=verbose", "q=quiet", "h=help", 0};
+ const char *longkeys[] = {"o=output", 0};
+
+ o = opt_parse (argc, argv, "hvq", "o", longflags, longkeys);
+ if (0==o)
+ return 0;
+
+ if (opt_given (o, "h")) {
+ printf (usage, o->progname);
+ return 0;
+ }
+
+
+
+ T.verbosity = opt_given (o, "q");
+ if (T.verbosity)
+ T.verbosity = -1;
+ if (T.verbosity != -1)
+ T.verbosity = opt_given (o, "v") + 1;
+
+ T.fout = stdout;
+ if (opt_given (o, "o"))
+ T.fout = fopen (opt_arg (o, "output"), "rt");
+ if (0==T.fout) {
+ fprintf (stderr, "%s: Cannot open '%s' for output\n", o->progname, opt_arg (o, "output"));
+ free (o);
+ return 1;
+ }
+
+ if (0==o->fargc) {
+ if (T.verbosity==-1)
+ return -1;
+ fprintf (T.fout, "Nothing to do\n");
+ return 0;
+ }
+
+ for (i = 0; i < o->fargc; i++)
+ process_file (o->fargv[i]);
+
+ if (T.verbosity > 0) {
+ if (o->fargc > 1)
+ fprintf (T.fout, "%sGrand total: %d. Success: %d, Failure: %d\n", delim, T.grand_ok+T.grand_ko, T.grand_ok, T.grand_ko);
+ printf (delim);
+ }
+ else
+ if (T.grand_ko)
+ fprintf (T.fout, "Failures: %d", T.grand_ko);
+
+ if (stdout != T.fout)
+ fclose (T.fout);
+
+ free (o);
+ return T.grand_ko;
+}
+
+
+
+
+
+static int process_file (char *fname) {
+ FILE *f;
+ char inp[CMDLEN];
+ char cmnd[1000];
+ char *args;
+
+ lineno = level = 0;
+ T.op_ok = T.total_ok = 0;
+ T.op_ko = T.total_ko = 0;
+
+ f = fopen (fname, "rt");
+ if (0==f) {
+ if (T.verbosity > 0) {
+ fprintf (T.fout, "%sCannot open spec'd input file '%s' - bye!\n", delim, fname);
+ return 2;
+ }
+ errmsg (2, "Cannot open spec'd input file '%s' - bye!\n", fname);
+ }
+ if (T.verbosity > 0)
+ fprintf (T.fout, "%sReading file '%s'\n", delim, fname);
+ T.curr_file = fname;
+ while (get_inp(f, inp, CMDLEN)) {
+ int len;
+
+ if (feof(f))
+ break;
+ len = get_cmnd (inp, cmnd, 1000);
+ if (len>=999) {
+ errmsg (2, "Command verb too long: '%s' - bye!\n", cmnd);
+ proj_destroy (T.P);
+ T.P = 0;
+ return 0;
+
+ }
+ args = get_args (inp);
+ if (SKIP==dispatch (cmnd, args))
+ return proj_destroy (T.P), T.P = 0, 0;
+ }
+ fclose (f);
+
+ T.grand_ok += T.total_ok;
+ T.grand_ko += T.total_ko;
+ if (T.verbosity > 0)
+ fprintf (T.fout, "%stotal: %2d tests succeeded, %2d tests %s\n", delim, T.total_ok, T.total_ko, T.total_ko? "FAILED!": "failed.");
+
+ if (level==0)
+ return errmsg (-3, "File '%s':Missing 'BEGIN' cmnd - bye!\n", fname);
+ if (level && level%2)
+ return errmsg (-4, "File '%s':Missing 'END' cmnd - bye!\n", fname);
+ return 0;
+}
+
+
+
+
+
+
+/* return a pointer to the n'th column of buf or a pointer to the terminating 0 if less than n */
+static char *column (char *buf, int n) {
+ int i;
+ if (n <= 0)
+ return buf;
+ for (i = 0; i < n; i++) {
+ while (isspace(*buf))
+ buf++;
+ if (i == n - 1)
+ break;
+ while ((0 != *buf) && !isspace(*buf))
+ buf++;
+ }
+ return buf;
+}
+
+
+
+
+static int banner (char *args) {
+ char dots[] = {"..."}, nodots[] = {""}, *thedots = nodots;
+ if (T.total_ko > 0 && T.op_ko==0)
+ printf ("\n\n");
+ if (strlen(args) > 70)
+ thedots = dots;
+ fprintf (T.fout, "%s%-70.70s%s\n", delim, args, thedots);
+ return 0;
+}
+
+
+
+
+
+
+static int tolerance (char *args) {
+ char *endp = args;
+ T.tolerance = proj_strtod (endp, &endp);
+ if (HUGE_VAL==T.tolerance) {
+ T.tolerance = 0.0005;
+ return 1;
+ }
+ while (isspace (*endp))
+ endp++;
+
+ if (0==strcmp(endp, "km"))
+ T.tolerance *= 1000;
+ else if (0==strcmp(endp, "m"))
+ T.tolerance *= 1;
+ else if (0==strcmp(endp, "dm"))
+ T.tolerance /= 10;
+ else if (0==strcmp(endp, "cm"))
+ T.tolerance /= 100;
+ else if (0==strcmp(endp, "mm"))
+ T.tolerance /= 1000;
+ else if (0==strcmp(endp, "um"))
+ T.tolerance /= 1e6;
+ else if (0==strcmp(endp, "nm"))
+ T.tolerance /= 1e9;
+ else
+ T.tolerance /= 1000; /* If no unit, assume mm */
+ return 0;
+}
+
+
+static int direction (char *args) {
+ char *endp = args;
+ while (isspace (*endp))
+ endp++;
+ switch (*endp) {
+ case 'F':
+ case 'f':
+ T.dir = PJ_FWD;
+ break;
+ case 'I':
+ case 'i':
+ case 'R':
+ case 'r':
+ T.dir = PJ_INV;
+ break;
+ default:
+ return 1;
+ }
+ return 0;
+}
+
+
+
+
+static void finish_previous_operation () {
+ if (T.verbosity > 1 && T.op_id > 1 && T.op_ok+T.op_ko)
+ fprintf (T.fout, "%s %d tests succeeded, %d tests %s\n", delim, T.op_ok, T.op_ko, T.op_ko? "FAILED!": "failed.");
+}
+
+static int operation (char *args) {
+ T.op_id++;
+ strcpy (&(T.operation[0]), args);
+ if (T.verbosity > 1) {
+ finish_previous_operation (args);
+ banner (args);
+ }
+ /* if (0==T.op_ko)
+ printf ("%d\n", (int) tol_lineno); */
+ T.op_ok = 0;
+ T.op_ko = 0;
+
+ direction ("forward");
+ tolerance ("0.5");
+
+ if (T.P)
+ proj_destroy (T.P);
+ T.P = proj_create (0, args);
+ if (0==T.P)
+ return errmsg(3, "Invalid operation definition!\n");
+ return 0;
+}
+
+
+static PJ_COORD torad_if_needed (PJ *P, PJ_DIRECTION dir, PJ_COORD a) {
+ enum pj_io_units u = P->left;
+ PJ_COORD c;
+ if (dir==PJ_INV)
+ u = P->right;
+ if (u==PJ_IO_UNITS_CLASSIC || u==PJ_IO_UNITS_METERS)
+ return a;
+
+ if (u==PJ_IO_UNITS_RADIANS) {
+ c.lpz.lam = proj_torad (T.a.lpz.lam);
+ c.lpz.phi = proj_torad (T.a.lpz.phi);
+ }
+
+ return c;
+}
+
+
+static int accept (char *args) {
+ int n, i;
+ char *endp = args;
+ T.a = proj_coord (0,0,0,0);
+ n = 4;
+ for (i = 0; i < 4; i++) {
+ T.a.v[i] = proj_strtod (endp, &endp);
+ if (HUGE_VAL==T.a.v[i]) {
+ n--;
+ T.a.v[i] = 0;
+ }
+ }
+ T.a = torad_if_needed (T.P, T.dir, T.a);
+ if (T.verbosity > 3)
+ printf ("# %s", args);
+ return 0;
+}
+
+
+
+static int roundtrip (char *args) {
+ int ntrips;
+ double d, r, ans;
+ char *endp;
+ ans = proj_strtod (args, &endp);
+ ntrips = ans==HUGE_VAL? 100: fabs(ans);
+ d = proj_strtod (endp, &endp);
+ d = d==HUGE_VAL? T.tolerance: d / 1000;
+ r = proj_roundtrip (T.P, PJ_FWD, ntrips, T.a);
+ if (r > d) {
+ if (T.verbosity > -1) {
+ if (0==T.op_ko && T.verbosity < 2)
+ banner (T.operation);
+ fprintf (T.fout, T.op_ko? " -----\n": delim);
+ fprintf (T.fout, " FAILURE in %s(%d):\n", opt_strip_path (T.curr_file), (int) lineno);
+ fprintf (T.fout, " roundtrip deivation: %.3f mm, expected: %.3f mm\n", 1000*r, 1000*d);
+ }
+ T.op_ko++;
+ T.total_ko++;
+ }
+ return 0;
+}
+
+static int expect (char *args) {
+ double d;
+ enum pj_io_units unit;
+ char *endp = args;
+ int i;
+
+ T.e = proj_coord (0,0,0,0);
+ T.b = proj_coord (0,0,0,0);
+ T.nargs = 4;
+ for (i = 0; i < 4; i++) {
+ T.e.v[i] = proj_strtod (endp, &endp);
+ if (HUGE_VAL==T.e.v[i]) {
+ T.nargs--;
+ T.e.v[i] = 0;
+ }
+ }
+ T.e = torad_if_needed (T.P, T.dir==PJ_FWD? PJ_INV:PJ_FWD, T.e);
+
+ T.b = proj_trans_coord (T.P, T.dir, T.a);
+ T.b = torad_if_needed (T.P, T.dir==PJ_FWD? PJ_INV:PJ_FWD, T.b);
+
+ if (T.nargs < 2) {
+ T.op_ko++;
+ T.total_ko++;
+ if (T.verbosity > -1) {
+ if (0==T.op_ko && T.verbosity < 2)
+ banner (T.operation);
+ fprintf (T.fout, T.op_ko? " -----\n": delim);
+ fprintf (T.fout, " FAILURE in %s(%d):\n Too few args: %s\n", opt_strip_path (T.curr_file), (int) lineno, args);
+ }
+ return 1;
+ }
+
+ unit = T.dir==PJ_FWD? T.P->right: T.P->left;
+ if (PJ_IO_UNITS_CLASSIC==unit)
+ unit = PJ_IO_UNITS_METERS;
+
+ if (unit==PJ_IO_UNITS_RADIANS)
+ d = proj_lp_dist (T.P, T.b.lp, T.e.lp);
+ else
+ d = proj_xyz_dist (T.b.xyz, T.e.xyz);
+
+ if (d > T.tolerance) {
+ if (d > 10)
+ d = 9.999999;
+ if (T.verbosity > -1) {
+ if (0==T.op_ko && T.verbosity < 2)
+ banner (T.operation);
+ fprintf (T.fout, T.op_ko? " -----\n": delim);
+
+ fprintf (T.fout, " FAILURE in %s(%d):\n", opt_strip_path (T.curr_file), (int) lineno);
+ fprintf (T.fout, " expected: %s\n", args);
+ fprintf (T.fout, " got: %.9f %.9f", T.b.xy.x, T.b.xy.y);
+ if (T.nargs > 2)
+ fprintf (T.fout, " %.9f", T.b.xyz.z);
+ if (T.nargs > 3)
+ fprintf (T.fout, " %.9f", T.b.xyzt.t);
+ fprintf (T.fout, "\n");
+ fprintf (T.fout, " deviation: %.3f mm, expected: %.3f mm\n", 1000*d, 1000*T.tolerance);
+ }
+ T.op_ko++;
+ T.total_ko++;
+ }
+ else {
+ T.op_ok++;
+ T.total_ok++;
+ }
+ return 0;
+}
+
+
+
+
+
+
+static int verbose (char *args) {
+ int i = proj_atof (args);
+
+ /* if -q/--quiet flag has been given, we do nothing */
+ if (T.verbosity < 0)
+ return 0;
+
+ if (strlen (args))
+ T.verbosity = i;
+ else
+ T.verbosity++;
+ return 0;
+}
+
+static int comment (char *args) {
+ (void) args;
+ return 0;
+}
+
+
+static int echo (char *args) {
+ fprintf (T.fout, "%s\n", args);
+ return 0;
+}
+
+
+
+static int dispatch (char *cmnd, char *args) {
+ if (0==level%2) {
+ if (0==strcmp (cmnd, "BEGIN"))
+ level++;
+ return 0;
+ }
+ if (0==stricmp (cmnd, "OPERATION")) return operation (args);
+ if (0==stricmp (cmnd, "ACCEPT")) return accept (args);
+ if (0==stricmp (cmnd, "EXPECT")) return expect (args);
+ if (0==stricmp (cmnd, "ROUNDTRIP")) return roundtrip (args);
+ if (0==stricmp (cmnd, "BANNER")) return banner (args);
+ if (0==stricmp (cmnd, "VERBOSE")) return verbose (args);
+ if (0==stricmp (cmnd, "DIRECTION")) return direction (args);
+ if (0==stricmp (cmnd, "TOLERANCE")) return tolerance (args);
+ if (0==stricmp (cmnd, "ECHO")) return echo (args);
+ if (0==strcmp (cmnd, "END")) return finish_previous_operation (args), level++, 0;
+ if ('#'==cmnd[0]) return comment (args);
+ return 0;
+}
+
+
+
+
+
+
+
+
+static int errmsg (int errlev, char *msg, ...) {
+ va_list args;
+ va_start(args, msg);
+ vfprintf(stdout, msg, args);
+ va_end(args);
+ if (errlev)
+ errno = errlev;
+ return errlev;
+}
+
+#define skipspace(f, c) \
+ do { \
+ while (isspace (c=fgetc(f)) && !feof(f)){ \
+ if ('\n'==c) lineno++; \
+ } \
+ if (feof(f)) \
+ break; \
+ } while (ungetc(c, f), 0)
+
+#define skipline(f, c) \
+ do { \
+ while ((c=fgetc(f)) && !feof(f)) { \
+ if ((c=='\r') || (c=='\n')) \
+ break; \
+ } \
+ skipspace (f, c); \
+ } while (0)
+
+
+/* skip whitespace at continuation line */
+#define continuation(f, buf, c) \
+ if ((c=='\r')||(c=='\n')) { \
+ if (c=='\n') lineno++; \
+ next--; \
+ while (isspace (c=fgetc(f)) && !feof(f)); \
+ }
+
+static int get_inp (FILE *f, char *inp, int size) {
+ char *next;
+ int c, esc;
+ char *endp = inp + size - 2;
+
+ skipspace (f, c);
+
+ for (c = esc = 0, next = inp; !feof(f); ) {
+ c = fgetc(f);
+ if (esc) {
+ continuation (f, next, c);
+ esc = 0;
+ /* handle escape sequences here */
+ switch (c) {
+ case '\\': c = '\\'; break;
+ default: (void) c;
+ }
+ }
+ if (c=='\r')
+ break;
+ if (c=='\n') {
+ lineno++;
+ break;
+ }
+
+ *next++ = c;
+ if ('\\'==c)
+ esc = 1;
+ if (feof(f) || (next==endp))
+ break;
+ }
+ *(next) = 0;
+ return strlen(inp);
+}
+
+static int get_cmnd (char *inp, char *cmnd, int len) {
+ cmnd[0] = 0;
+ while (isspace(*inp++));
+ inp--;
+ while (len-- && !isspace(*inp) && *inp)
+ *cmnd++ = *inp++;
+ *cmnd = 0;
+ return len;
+}
+
+static char *get_args (char *inp) {
+ char *args = inp;
+ while (isspace(*args++))
+ if (0==*args)
+ return args;
+ while (!isspace(*++args))
+ if (0==*args)
+ return args;
+ while (isspace(*args++))
+ if (0==*args)
+ return args;
+ return --args;
+}