Ticket #150: 0001-cavern-Add-cs-file-to-read-the-coordinate-system-fro.patch
| File 0001-cavern-Add-cs-file-to-read-the-coordinate-system-fro.patch, 20.1 KB (added by , 18 hours ago) |
|---|
-
doc/3dformat.htm
From 70cef2cd4fe5af3700f64413798c5ac826d888c2 Mon Sep 17 00:00:00 2001 From: Philip Schuchardt <vpicaver@gmail.com> Date: Tue, 11 Aug 2026 19:55:12 -0500 Subject: [PATCH] cavern: Add *cs file to read the coordinate system from a file `*cs file FILENAME` reads the coordinate system description from a separate file, so it can be a PROJ string, WKT or PROJJSON. WKT and PROJJSON are made up of double quoted strings written over several lines, which is awkward to write in a .svx file, and they can express datums a PROJ string can't, such as NAD83(2011), ETRS89 and GDA2020. It also means a .prj file such as those which accompany ESRI shapefiles can be used directly, so a survey can be georeferenced to match GIS data you already have. The filename is resolved relative to the directory of the file the *cs is in, a .prj extension is tried if the name as given isn't found, and the lines of the file are joined with a space since the coordinate system is stored in the .3d file as a single line. This addresses the review of the patch on https://trac.survex.com/ticket/150: `file` is a new coordinate system keyword rather than an `@` prefix on `custom`, so `custom` still always means a literal PROJ/WKT string, and there's no UTF-8 BOM skipping - PROJ rejects a BOM and so does GDAL's .prj reader, so cavern now does too. --- doc/3dformat.htm | 9 ++++- doc/datafile.rst | 29 +++++++++++++++ src/commands.c | 88 +++++++++++++++++++++++++++++++++++++++++++++- src/filelist.h | 1 + tests/Makefile.am | 3 ++ tests/cavern.tst | 3 +- tests/csprj.out | 24 +++++++++++++ tests/csprj.pos | 4 +++ tests/csprj.prj | 33 +++++++++++++++++ tests/csprj.svx | 11 ++++++ tests/csprj2.out | 24 +++++++++++++ tests/csprj2.pos | 4 +++ tests/csprj2.prj | 1 + tests/csprj2.svx | 11 ++++++ tests/csprjbad.out | 36 +++++++++++++++++++ tests/csprjbad.svx | 13 +++++++ tests/csprjbom.prj | 1 + 17 files changed, 292 insertions(+), 3 deletions(-) create mode 100644 tests/csprj.out create mode 100644 tests/csprj.pos create mode 100644 tests/csprj.prj create mode 100644 tests/csprj.svx create mode 100644 tests/csprj2.out create mode 100644 tests/csprj2.pos create mode 100644 tests/csprj2.prj create mode 100644 tests/csprj2.svx create mode 100644 tests/csprjbad.out create mode 100644 tests/csprjbad.svx create mode 100644 tests/csprjbom.prj diff --git a/doc/3dformat.htm b/doc/3dformat.htm index b6e8ab42..17546c91 100644
a b There's no length limit on this string. 60 60 which can be passed to PROJ. For a coordinate system with an EPSG code 61 61 <code>EPSG:</code> followed by the code number can be used (we recommend 62 62 using this if an EPSG code exists). Similarly, an ESRI code can be specified 63 with <code>ESRI:</code> followed by the code number. 63 with <code>ESRI:</code> followed by the code number. Any other description 64 PROJ accepts may be used, such as a PROJ string, WKT or PROJJSON - this is how 65 a coordinate system based on a datum PROJ has no <code>+datum=</code> keyword 66 for gets recorded. Since the metadata ends at a linefeed, the string must be 67 written on a single line - WKT and PROJJSON are usually pretty printed over 68 several lines, so a writer needs to join those lines (a single space in place 69 of each line break works, as neither format allows a newline inside a quoted 70 name). 64 71 <li>Survey hierarchy separator character. Survey station names form a 65 72 hierarchy, and this character separates levels in the hierarchy. E.g. 66 73 <code>161.entrance.6</code>. Defaults to <code>.</code> if this item is not -
doc/datafile.rst
diff --git a/doc/datafile.rst b/doc/datafile.rst index 86848568..76421e9e 100644
a b Example 523 523 ; Output in the coordinate system used in the Totes Gebirge in Austria 524 524 *cs out custom "+proj=tmerc +lat_0=0 +lon_0=13d20 +k=1 +x_0=0 +y_0=-5200000 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232" 525 525 526 :: 527 528 ; Output in a low distortion projection described by mammoth.prj 529 *cs out file "mammoth.prj" 530 526 531 Description 527 532 ``*cs`` allows the coordinate systems used for fixed points and for 528 533 processed survey data to be specified. … … Description 560 565 * ``EUR79Z30`` for UTM zone 30, EUR79 datum. Supported since Survex 561 566 1.2.15. 562 567 568 * ``FILE`` followed by the name of a file which describes the coordinate 569 system (like in the example above). The description may be a PROJ string, 570 but the point of reading it from a file is that it can also be WKT or 571 PROJJSON, which are made up of double quoted strings written over several 572 lines and so are awkward to write in a ``.svx`` file. It means a ``.prj`` 573 file such as those which accompany ESRI shapefiles can be used directly, 574 so you can georeference a survey to match GIS data you already have. 575 576 WKT can also express datums which a PROJ string can't, such as 577 NAD83(2011), ETRS89 and GDA2020. 578 579 The file is resolved relative to the directory which the file containing 580 the ``*cs`` is in, and if the name as specified is not found cavern will 581 try adding a ``.prj`` extension. If the filename contains spaces, it must 582 be enclosed in double quotes, and you can double the quote character to 583 include it in the filename. 584 585 The lines of the file are joined with a single space, since the coordinate 586 system is stored in the ``.3d`` file as a single line. Neither WKT nor 587 PROJJSON allows a newline inside a quoted name, so only insignificant 588 whitespace is affected. 589 590 Supported since Survex 1.4.23. 591 563 592 * ``IJTSK`` for the modified version of the Czechoslovak S-JTSK system 564 593 where the axes point East and North. Supported since Survex 1.2.15. 565 594 -
src/commands.c
diff --git a/src/commands.c b/src/commands.c index 1f247a1d..2e5e4fc8 100644
a b 33 33 #include "datain.h" 34 34 #include "date.h" 35 35 #include "debug.h" 36 #include "filelist.h" 36 37 #include "filename.h" 37 38 #include "message.h" 38 39 #include "netbits.h" … … typedef enum { 2466 2467 CS_EPSG, 2467 2468 CS_ESRI, 2468 2469 CS_EUR79Z30, 2470 CS_FILE, 2469 2471 CS_IJTSK, 2470 2472 CS_IJTSK03, 2471 2473 CS_JTSK, … … static const sztok cs_tab[] = { 2483 2485 {"EPSG", CS_EPSG}, /* EPSG:<number> */ 2484 2486 {"ESRI", CS_ESRI}, /* ESRI:<number> */ 2485 2487 {"EUR79Z30", CS_EUR79Z30}, 2488 {"FILE", CS_FILE}, /* FILE <filename> */ 2486 2489 {"IJTSK", CS_IJTSK}, 2487 2490 {"IJTSK03", CS_IJTSK03}, 2488 2491 {"JTSK", CS_JTSK}, … … static const sztok cs_tab[] = { 2497 2500 {NULL, CS_NONE} 2498 2501 }; 2499 2502 2503 /* Read a coordinate system description from the file FNM, as specified by 2504 * `*cs file FILENAME`. FP is the position of FILENAME, which any diagnostic 2505 * is reported against. 2506 * 2507 * Returns the description, or NULL if the file couldn't be opened (in which 2508 * case a diagnostic has been reported). 2509 */ 2510 static char * 2511 read_cs_from_file(const char *fnm, const filepos *fp) 2512 { 2513 char *pth = path_from_fnm(file.filename); 2514 char *fnm_used = NULL; 2515 FILE *fh = fopen_portable(pth, fnm, EXT_PRJ, "rb", &fnm_used); 2516 free(pth); 2517 if (fh == NULL) { 2518 set_pos(fp); 2519 compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Couldn’t open file “%s”*/1, 2520 fnm); 2521 return NULL; 2522 } 2523 2524 /* We store the coordinate system in the .3d file as a single line, so join 2525 * the lines with a space, dropping blanks at the start and end of each. 2526 * Neither WKT nor PROJJSON allows a newline inside a quoted name, so only 2527 * insignificant whitespace is affected. 2528 */ 2529 string cs = S_INIT; 2530 /* s_clear() gives cs a buffer, which s_steal() needs even for an empty 2531 * file. */ 2532 s_clear(&cs); 2533 string blanks = S_INIT; 2534 bool line_break = false; 2535 for (int c = GETC(fh); c != EOF; c = GETC(fh)) { 2536 if (c == '\n' || c == '\r') { 2537 line_break = true; 2538 s_clear(&blanks); 2539 continue; 2540 } 2541 if (c == ' ' || c == '\t') { 2542 /* Only keep blanks which turn out to be between two non-blanks on 2543 * the same line. */ 2544 if (cs.len) s_appendch(&blanks, c); 2545 continue; 2546 } 2547 if (cs.len) { 2548 if (line_break) { 2549 s_appendch(&cs, ' '); 2550 } else { 2551 s_appends(&cs, &blanks); 2552 } 2553 } 2554 s_clear(&blanks); 2555 line_break = false; 2556 s_appendch(&cs, c); 2557 } 2558 s_free(&blanks); 2559 2560 if (FERROR(fh)) 2561 fatalerror_in_file(fnm_used, 0, /*Error reading file*/18); 2562 fclose(fh); 2563 free(fnm_used); 2564 2565 return s_steal(&cs); 2566 } 2567 2500 2568 static void 2501 2569 cmd_cs(void) 2502 2570 { … … cmd_cs(void) 2564 2632 proj_str = s_steal(&str); 2565 2633 cs_sub = 0; 2566 2634 break; 2635 case CS_FILE: { 2636 ok_for_output = MAYBE; 2637 get_pos(&fp); 2638 string fnm_str = S_INIT; 2639 read_string(&fnm_str); 2640 proj_str = read_cs_from_file(s_str(&fnm_str), &fp); 2641 s_free(&fnm_str); 2642 if (!proj_str) { 2643 skipline(); 2644 return; 2645 } 2646 cs_sub = 0; 2647 break; 2648 } 2567 2649 case CS_EPSG: case CS_ESRI: 2568 2650 ok_for_output = MAYBE; 2569 2651 if (ch == ':' && isdigit(nextch())) { … … cmd_cs(void) 2635 2717 break; 2636 2718 } 2637 2719 } 2638 if (cs_sub == INT_MIN || (cs != CS_CUSTOM && isalnum(ch))) { 2720 if (cs_sub == INT_MIN || 2721 (cs != CS_CUSTOM && cs != CS_FILE && isalnum(ch))) { 2639 2722 set_pos(&fp); 2640 2723 compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Unknown coordinate system*/434); 2641 2724 skipline(); … … cmd_cs(void) 2646 2729 case CS_NONE: 2647 2730 break; 2648 2731 case CS_CUSTOM: 2732 case CS_FILE: 2649 2733 /* proj_str already set */ 2650 2734 break; 2651 2735 case CS_EPSG: … … cmd_cs(void) 2777 2861 /* Same as the current output projection, so valid for input. */ 2778 2862 } else if (pcs->proj_str && strcmp(proj_str, pcs->proj_str) == 0) { 2779 2863 /* Same as the current input projection, so nothing to do! */ 2864 free(proj_str); 2780 2865 return; 2781 2866 } else if (ok_for_output == MAYBE) { 2782 2867 /* (ok_for_output == MAYBE) also happens to indicate whether we need … … cmd_cs(void) 2790 2875 proj_context_errno_string(PJ_DEFAULT_CTX, 2791 2876 proj_context_errno(PJ_DEFAULT_CTX))); 2792 2877 skipline(); 2878 free(proj_str); 2793 2879 return; 2794 2880 } 2795 2881 proj_destroy(pj); -
src/filelist.h
diff --git a/src/filelist.h b/src/filelist.h index 215cbbff..694d5d78 100644
a b 24 24 #define EXT_SVX_MSG "msg" 25 25 #define EXT_INI "ini" 26 26 #define EXT_LOG "log" 27 #define EXT_PRJ "prj" -
tests/Makefile.am
diff --git a/tests/Makefile.am b/tests/Makefile.am index 51bd3a6a..e41e8f04 100644
a b csbad.altout csbad.out csbad.svx\ 178 178 csbadsdfix.altout csbadsdfix.out csbadsdfix.svx\ 179 179 csfeet.out csfeet.pos csfeet.svx\ 180 180 cslonglat.out cslonglat.svx\ 181 csprj.out csprj.pos csprj.prj csprj.svx\ 182 csprj2.out csprj2.pos csprj2.prj csprj2.svx\ 183 csprjbad.out csprjbad.svx csprjbom.prj\ 181 184 omitfixaroundsolve.out omitfixaroundsolve.svx\ 182 185 repeatreading.svx repeatreading.out repeatreading.pos\ 183 186 mixedeols.out mixedeols.svx\ -
tests/cavern.tst
diff --git a/tests/cavern.tst b/tests/cavern.tst index 51e4c564..662b4027 100755
a b TESTS_= 121 121 passage hanging_lrud equatenosuchstn surveytypo\ 122 122 skipafterbadomit passagebad badreadingdotplus badcalibrate calibrate_clino\ 123 123 badunits badbegin anonstn anonstnbad anonstnrev doubleinc reenterlots\ 124 cs csbad csbadsdfix csfeet cslonglat omitfixaroundsolve repeatreading\ 124 cs csbad csbadsdfix csfeet cslonglat csprj csprj2 csprjbad\ 125 omitfixaroundsolve repeatreading\ 125 126 mixedeols utf8bom nonewlineateof suspectreadings cmd_data_default\ 126 127 cmd_data_ignore\ 127 128 quadrant_bearing bad_quadrant_bearing\ -
new file tests/csprj.out
diff --git a/tests/csprj.out b/tests/csprj.out new file mode 100644 index 00000000..c4cb8d94
- + 1 2 Removing trailing traverses... 3 4 Concatenating traverses... 5 6 Simplifying network... 7 8 Calculating network... 9 10 Calculating traverses... 11 12 Calculating trailing traverses... 13 14 Calculating statistics... 15 16 Approximate full range of grid convergence: 0.0dg at entrance to 0.0dg at b 17 Survey contains 3 survey stations, joined by 2 legs. 18 There are 0 loops. 19 Total length of survey legs = 55.00m ( 55.00m adjusted) 20 Total plan length of survey legs = 54.87m 21 Total vertical length of survey legs = 3.49m 22 Vertical range = 2.61m (from entrance at 200.00m to a at 197.39m) 23 North-South range = 17.67m (from a at 1109.86m to b at 1092.19m) 24 East-West range = 47.55m (from b at 935.37m to entrance at 887.82m) -
new file tests/csprj.pos
diff --git a/tests/csprj.pos b/tests/csprj.pos new file mode 100644 index 00000000..b2af0f79
- + 1 ( Easting, Northing, Altitude ) 2 ( 917.71, 1109.86, 197.39 ) a 3 ( 935.37, 1092.19, 198.26 ) b 4 ( 887.82, 1109.86, 200.00 ) entrance -
new file tests/csprj.prj
diff --git a/tests/csprj.prj b/tests/csprj.prj new file mode 100644 index 00000000..bb40c2e4
- + 1 PROJCRS["Mammoth Cave LDP", 2 BASEGEOGCRS["NAD83(2011)", 3 DATUM["NAD83 (National Spatial Reference System 2011)", 4 ELLIPSOID["GRS 1980",6378137,298.257222101, 5 LENGTHUNIT["metre",1]]], 6 PRIMEM["Greenwich",0, 7 ANGLEUNIT["degree",0.0174532925199433]], 8 ID["EPSG",6318]], 9 CONVERSION["Transverse Mercator", 10 METHOD["Transverse Mercator", 11 ID["EPSG",9807]], 12 PARAMETER["Latitude of natural origin",37.1866, 13 ANGLEUNIT["degree",0.0174532925199433], 14 ID["EPSG",8801]], 15 PARAMETER["Longitude of natural origin",-86.1005, 16 ANGLEUNIT["degree",0.0174532925199433], 17 ID["EPSG",8802]], 18 PARAMETER["Scale factor at natural origin",1, 19 SCALEUNIT["unity",1], 20 ID["EPSG",8805]], 21 PARAMETER["False easting",0, 22 LENGTHUNIT["metre",1], 23 ID["EPSG",8806]], 24 PARAMETER["False northing",0, 25 LENGTHUNIT["metre",1], 26 ID["EPSG",8807]]], 27 CS[Cartesian,2], 28 AXIS["(E)",east, 29 ORDER[1], 30 LENGTHUNIT["metre",1]], 31 AXIS["(N)",north, 32 ORDER[2], 33 LENGTHUNIT["metre",1]]] -
new file tests/csprj.svx
diff --git a/tests/csprj.svx b/tests/csprj.svx new file mode 100644 index 00000000..f099422d
- + 1 ; pos=yes warn=0 2 ; Test *cs file FILENAME, which reads the coordinate system from a separate 3 ; file. Here that's a low distortion projection for a cave, written as WKT 4 ; over several lines - a PROJ string can't express the datum it's based on. 5 *cs EPSG:6318 6 *cs out FILE csprj.prj 7 8 *fix entrance -86.0905 37.1966 200 9 10 entrance a 30.0 090 -05 11 a b 25.0 135 +02 -
new file tests/csprj2.out
diff --git a/tests/csprj2.out b/tests/csprj2.out new file mode 100644 index 00000000..cac0d37f
- + 1 2 Removing trailing traverses... 3 4 Concatenating traverses... 5 6 Simplifying network... 7 8 Calculating network... 9 10 Calculating traverses... 11 12 Calculating trailing traverses... 13 14 Calculating statistics... 15 16 Approximate full range of grid convergence: 0.0dg at entrance to 0.0dg at b 17 Survey contains 3 survey stations, joined by 2 legs. 18 There are 0 loops. 19 Total length of survey legs = 55.00m ( 55.00m adjusted) 20 Total plan length of survey legs = 54.87m 21 Total vertical length of survey legs = 3.49m 22 Vertical range = 2.61m (from entrance at 200.00m to a at 197.39m) 23 North-South range = 17.67m (from a at 41819.44m to b at 41801.78m) 24 East-West range = 47.55m (from b at 10891.01m to entrance at 10843.46m) -
new file tests/csprj2.pos
diff --git a/tests/csprj2.pos b/tests/csprj2.pos new file mode 100644 index 00000000..7fb569af
- + 1 ( Easting, Northing, Altitude ) 2 ( 10873.35, 41819.44, 197.39 ) a 3 ( 10891.01, 41801.78, 198.26 ) b 4 ( 10843.46, 41819.44, 200.00 ) entrance -
new file tests/csprj2.prj
diff --git a/tests/csprj2.prj b/tests/csprj2.prj new file mode 100644 index 00000000..d89bdac5
- + 1 PROJCRS["Mammoth Cave LDP", BASEGEOGCRS["NAD83(2011)", DATUM["NAD83 (National Spatial Reference System 2011)", ELLIPSOID["GRS 1980",6378137,298.257222101, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",6318]], CONVERSION["Mammoth Cave LDP TM", METHOD["Transverse Mercator", ID["EPSG",9807]], PARAMETER["Latitude of natural origin",37, ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8801]], PARAMETER["Longitude of natural origin",-86.1, ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8802]], PARAMETER["Scale factor at natural origin",1.0000383, SCALEUNIT["unity",1], ID["EPSG",8805]], PARAMETER["False easting",10000, LENGTHUNIT["metre",1], ID["EPSG",8806]], PARAMETER["False northing",20000, LENGTHUNIT["metre",1], ID["EPSG",8807]]], CS[Cartesian,2], AXIS["easting (E)",east, ORDER[1], LENGTHUNIT["metre",1]], AXIS["northing (N)",north, ORDER[2], LENGTHUNIT["metre",1]]] -
new file tests/csprj2.svx
diff --git a/tests/csprj2.svx b/tests/csprj2.svx new file mode 100644 index 00000000..6785fb35
- + 1 ; pos=yes warn=0 2 ; Test that *cs file FILENAME assumes a .prj extension if the name as given 3 ; doesn't exist, that the filename can be quoted, and that a description 4 ; written on a single line (as ESRI .prj files are) works. 5 *cs EPSG:6318 6 *cs out FILE "csprj2" 7 8 *fix entrance -86.0905 37.1966 200 9 10 entrance a 30.0 090 -05 11 a b 25.0 135 +02 -
new file tests/csprjbad.out
diff --git a/tests/csprjbad.out b/tests/csprjbad.out new file mode 100644 index 00000000..12f4acae
- + 1 ./csprjbad.svx:3:14: error: Couldn't open file "nosuchfile.prj" 2 *cs out FILE nosuchfile.prj 3 ^~~~~~~~~~~~~~ 4 ./csprjbad.svx:5:14: error: Invalid coordinate system: Invalid PROJ string syntax 5 *cs out FILE csprjbom.prj 6 ^~~~~~~~~~~~ 7 ./csprjbad.svx:8:21: error: End of line not blank 8 *cs FILE "csprj.prj"x 9 ^ 10 ./csprjbad.svx:11: error: The input projection is set but the output projection isn't 11 *fix entrance -86.0905 37.1966 200 12 13 Removing trailing traverses... 14 15 Concatenating traverses... 16 17 Simplifying network... 18 19 Calculating network... 20 21 Calculating traverses... 22 23 Calculating trailing traverses... 24 25 Calculating statistics... 26 27 Survey contains 2 survey stations, joined by 1 leg. 28 There are 0 loops. 29 Total length of survey legs = 30.00m ( 30.00m adjusted) 30 Total plan length of survey legs = 29.89m 31 Total vertical length of survey legs = 2.61m 32 Vertical range = 2.61m (from entrance at 200.00m to a at 197.39m) 33 North-South range = 0.00m (from a at 37.20m to a at 37.20m) 34 East-West range = 29.89m (from a at -56.20m to entrance at -86.09m) 35 36 There were 0 warning(s) and 4 error(s) - no output files produced. -
new file tests/csprjbad.svx
diff --git a/tests/csprjbad.svx b/tests/csprjbad.svx new file mode 100644 index 00000000..990df42e
- + 1 ; pos=fail warn=0 error=4 2 ; Test errors from *cs file FILENAME. 3 *cs out FILE nosuchfile.prj 4 ; PROJ rejects a description which starts with a UTF-8 "BOM". 5 *cs out FILE csprjbom.prj 6 ; Junk after the filename is reported as such, not as an unknown coordinate 7 ; system. 8 *cs FILE "csprj.prj"x 9 *cs EPSG:6318 10 11 *fix entrance -86.0905 37.1966 200 12 13 entrance a 30.0 090 -05 -
new file tests/csprjbom.prj
diff --git a/tests/csprjbom.prj b/tests/csprjbom.prj new file mode 100644 index 00000000..1a1c8d4a
- + 1 PROJCRS["Mammoth Cave LDP", BASEGEOGCRS["NAD83(2011)", DATUM["NAD83 (National Spatial Reference System 2011)", ELLIPSOID["GRS 1980",6378137,298.257222101, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",6318]], CONVERSION["Mammoth Cave LDP TM", METHOD["Transverse Mercator", ID["EPSG",9807]], PARAMETER["Latitude of natural origin",37, ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8801]], PARAMETER["Longitude of natural origin",-86.1, ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8802]], PARAMETER["Scale factor at natural origin",1.0000383, SCALEUNIT["unity",1], ID["EPSG",8805]], PARAMETER["False easting",10000, LENGTHUNIT["metre",1], ID["EPSG",8806]], PARAMETER["False northing",20000, LENGTHUNIT["metre",1], ID["EPSG",8807]]], CS[Cartesian,2], AXIS["easting (E)",east, ORDER[1], LENGTHUNIT["metre",1]], AXIS["northing (N)",north, ORDER[2], LENGTHUNIT["metre",1]]]
