Ticket #150: 0001-cavern-Allow-cs-custom-to-read-the-CRS-from-a-file.patch
| File 0001-cavern-Allow-cs-custom-to-read-the-CRS-from-a-file.patch, 19.1 KB (added by , 3 days ago) |
|---|
-
doc/3dformat.htm
From 1e000e26e383837035b56e65219c7221ab3c29c9 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: Allow *cs custom to read the CRS from a file `*cs custom @FILENAME` reads the coordinate system description from a separate file. This makes it possible to specify a coordinate system as WKT or PROJJSON, which the .svx file itself can't carry because a quoted string has no way to represent the double quotes those formats are made of, and because they're usually written over several lines. --- doc/3dformat.htm | 9 +++- doc/datafile.rst | 35 +++++++++++++- src/commands.c | 115 ++++++++++++++++++++++++++++++++++++++++++++- 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 | 33 +++++++++++++ tests/csprj2.svx | 11 +++++ tests/csprjbad.out | 30 ++++++++++++ tests/csprjbad.svx | 8 ++++ 16 files changed, 343 insertions(+), 5 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 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 57952747..45b6b06e 100644
a b Example 501 501 ; Output in the coordinate system used in the Totes Gebirge in Austria 502 502 *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" 503 503 504 :: 505 506 ; Output in a low distortion projection described by mammoth.prj 507 *cs out custom @mammoth.prj 508 504 509 Description 505 510 ``*cs`` allows the coordinate systems used for fixed points and for 506 511 processed survey data to be specified. … … Description 529 534 resource for finding the EPSG code you want. For example, ``EPSG:4167`` 530 535 is NZGD2000. Supported since Survex 1.2.15. 531 536 532 * ``CUSTOM`` followed by a PROJ string (like in the example above). 537 * ``CUSTOM`` followed by a PROJ string, or by ``@`` and the name of a file 538 containing a description of the coordinate system - the last two examples 539 above show each of these in turn. 540 541 Such a file may contain a PROJ string, but the point of it is that it can 542 also hold WKT or PROJJSON, which are made up of double quoted strings 543 written over several lines and so are awkward to write in a ``.svx`` 544 file. This means a ``.prj`` file such as those which accompany ESRI 545 shapefiles can be used directly, so you can georeference a survey to match 546 GIS data you already have. 547 548 It's worth using WKT when the datum you need is one PROJ has no 549 ``+datum=`` keyword for, which is the case for most datums in current use 550 (such as NAD83(2011), ETRS89 and GDA2020). A PROJ string can only express 551 a datum as an ellipsoid plus a static shift, so PROJ quietly drops such a 552 datum when it converts a coordinate system to a PROJ string. 553 554 The filename is relative to the directory containing the file the ``*cs`` 555 command is in, and an extension of ``.prj`` is assumed if the name as 556 given doesn't exist. Use double quotes around the name if it contains 557 spaces - the ``@`` may go either side of the opening quote, so both 558 ``@"My Cave.prj"`` and ``"@My Cave.prj"`` work. 559 560 The lines of the file are joined together with a single space, since the 561 coordinate system is stored in the ``.3d`` file as a single line. This 562 only affects whitespace which the formats treat as insignificant, because 563 neither WKT nor PROJJSON allows a newline inside a quoted name. 564 565 Reading the coordinate system from a file was added in Survex 1.4.23. 533 566 534 567 * ``ESRI:`` followed by a positive integer code. ESRI codes are used by 535 568 ArcGIS to specify coordinate systems (in a similar way to EPSG codes) -
src/commands.c
diff --git a/src/commands.c b/src/commands.c index 5834a3d2..f826f0bf 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" … … static const sztok cs_tab[] = { 2486 2487 {NULL, CS_NONE} 2487 2488 }; 2488 2489 2490 /* Read a coordinate system from the file FNM, as specified by 2491 * `*cs custom @FILENAME`. FP is the position of FILENAME in the current file, 2492 * which any diagnostic is reported against. 2493 * 2494 * WKT and PROJJSON are made up of double quoted strings and are usually 2495 * written over several lines, which makes them awkward to write in a .svx 2496 * file. Keeping such a description in its own file also means a .prj file 2497 * such as those which accompany ESRI shapefiles can be used directly. 2498 * 2499 * Returns the coordinate system description, or NULL if the file couldn't be 2500 * opened (in which case a diagnostic has been reported). 2501 */ 2502 static char * 2503 read_cs_from_file(const char *fnm, const filepos *fp) 2504 { 2505 char *pth = path_from_fnm(file.filename); 2506 char *fnm_used = NULL; 2507 FILE *fh = fopen_portable(pth, fnm, EXT_PRJ, "rb", &fnm_used); 2508 free(pth); 2509 if (fh == NULL) { 2510 set_pos(fp); 2511 compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Couldn’t open file “%s”*/1, 2512 fnm); 2513 return NULL; 2514 } 2515 2516 int c = GETC(fh); 2517 if (c == 0xef) { 2518 /* Skip a UTF-8 "BOM" if there is one - PROJ rejects a description which 2519 * starts with one. */ 2520 if (GETC(fh) == 0xbb && GETC(fh) == 0xbf) { 2521 c = GETC(fh); 2522 } else { 2523 rewind(fh); 2524 c = GETC(fh); 2525 } 2526 } 2527 2528 /* We store the coordinate system in the .3d file as part of a 2529 * newline-terminated line, so it can't contain a newline. Join the lines 2530 * with a single space, dropping blanks at the start and end of each line. 2531 * Neither WKT nor PROJJSON allows a newline inside a quoted name, so only 2532 * insignificant whitespace is affected. 2533 */ 2534 /* The s_clear() calls give each string a buffer, which for an empty file it 2535 * would otherwise still lack by the time we use it - s_steal() writes the 2536 * terminating zero byte to one, and s_appends() reads from one. 2537 */ 2538 string cs = S_INIT; 2539 s_clear(&cs); 2540 string blanks = S_INIT; 2541 s_clear(&blanks); 2542 bool line_break = false; 2543 for ( ; c != EOF; c = GETC(fh)) { 2544 if (c == '\n' || c == '\r') { 2545 line_break = true; 2546 s_clear(&blanks); 2547 continue; 2548 } 2549 if (c == ' ' || c == '\t') { 2550 /* Only keep blanks which turn out to be between two non-blanks on 2551 * the same line. */ 2552 if (cs.len) s_appendch(&blanks, c); 2553 continue; 2554 } 2555 if (cs.len) { 2556 if (line_break) { 2557 s_appendch(&cs, ' '); 2558 } else { 2559 s_appends(&cs, &blanks); 2560 } 2561 } 2562 s_clear(&blanks); 2563 line_break = false; 2564 s_appendch(&cs, c); 2565 } 2566 s_free(&blanks); 2567 2568 if (FERROR(fh)) 2569 fatalerror_in_file(fnm_used, 0, /*Error reading file*/18); 2570 fclose(fh); 2571 free(fnm_used); 2572 2573 return s_steal(&cs); 2574 } 2575 2489 2576 static void 2490 2577 cmd_cs(void) 2491 2578 { … … cmd_cs(void) 2545 2632 switch (cs) { 2546 2633 case CS_NONE: 2547 2634 break; 2548 case CS_CUSTOM: 2635 case CS_CUSTOM: { 2549 2636 ok_for_output = MAYBE; 2637 skipblanks(); 2638 /* `@FILENAME` reads the coordinate system from a file. If FILENAME 2639 * is quoted then the `@` may be written either side of the opening 2640 * quote. 2641 */ 2642 bool from_file = (ch == '@'); 2643 if (from_file) nextch(); 2550 2644 get_pos(&fp); 2551 2645 string str = S_INIT; 2552 2646 read_string(&str); 2553 proj_str = s_steal(&str); 2647 const char *p = s_str(&str); 2648 if (!from_file && *p == '@') { 2649 from_file = true; 2650 ++p; 2651 } 2652 if (from_file) { 2653 proj_str = read_cs_from_file(p, &fp); 2654 s_free(&str); 2655 if (!proj_str) { 2656 skipline(); 2657 return; 2658 } 2659 } else { 2660 proj_str = s_steal(&str); 2661 } 2554 2662 cs_sub = 0; 2555 2663 break; 2664 } 2556 2665 case CS_EPSG: case CS_ESRI: 2557 2666 ok_for_output = MAYBE; 2558 2667 if (ch == ':' && isdigit(nextch())) { … … cmd_cs(void) 2766 2875 /* Same as the current output projection, so valid for input. */ 2767 2876 } else if (pcs->proj_str && strcmp(proj_str, pcs->proj_str) == 0) { 2768 2877 /* Same as the current input projection, so nothing to do! */ 2878 free(proj_str); 2769 2879 return; 2770 2880 } else if (ok_for_output == MAYBE) { 2771 2881 /* (ok_for_output == MAYBE) also happens to indicate whether we need … … cmd_cs(void) 2779 2889 proj_context_errno_string(PJ_DEFAULT_CTX, 2780 2890 proj_context_errno(PJ_DEFAULT_CTX))); 2781 2891 skipline(); 2892 free(proj_str); 2782 2893 return; 2783 2894 } 2784 2895 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 0fef3790..2f9aeb92 100644
a b csbad.altout csbad.out csbad.svx\ 177 177 csbadsdfix.altout csbadsdfix.out csbadsdfix.svx\ 178 178 csfeet.out csfeet.pos csfeet.svx\ 179 179 cslonglat.out cslonglat.svx\ 180 csprj.out csprj.pos csprj.prj csprj.svx\ 181 csprj2.out csprj2.pos csprj2.prj csprj2.svx\ 182 csprjbad.out csprjbad.svx\ 180 183 omitfixaroundsolve.out omitfixaroundsolve.svx\ 181 184 repeatreading.svx repeatreading.out repeatreading.pos\ 182 185 mixedeols.out mixedeols.svx\ -
tests/cavern.tst
diff --git a/tests/cavern.tst b/tests/cavern.tst index 4b1d228b..5d2882bb 100755
a b TESTS_= 120 120 passage hanging_lrud equatenosuchstn surveytypo\ 121 121 skipafterbadomit passagebad badreadingdotplus badcalibrate calibrate_clino\ 122 122 badunits badbegin anonstn anonstnbad anonstnrev doubleinc reenterlots\ 123 cs csbad csbadsdfix csfeet cslonglat omitfixaroundsolve repeatreading\ 123 cs csbad csbadsdfix csfeet cslonglat csprj csprj2 csprjbad\ 124 omitfixaroundsolve repeatreading\ 124 125 mixedeols utf8bom nonewlineateof suspectreadings cmd_data_default\ 125 126 cmd_data_ignore\ 126 127 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..a7cccb4b
- + 1 ; pos=yes warn=0 2 ; Test *cs custom @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 CUSTOM @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..df9d4f2a
- + 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["Mammoth Cave LDP TM", 10 METHOD["Transverse Mercator", 11 ID["EPSG",9807]], 12 PARAMETER["Latitude of natural origin",37, 13 ANGLEUNIT["degree",0.0174532925199433], 14 ID["EPSG",8801]], 15 PARAMETER["Longitude of natural origin",-86.1, 16 ANGLEUNIT["degree",0.0174532925199433], 17 ID["EPSG",8802]], 18 PARAMETER["Scale factor at natural origin",1.0000383, 19 SCALEUNIT["unity",1], 20 ID["EPSG",8805]], 21 PARAMETER["False easting",10000, 22 LENGTHUNIT["metre",1], 23 ID["EPSG",8806]], 24 PARAMETER["False northing",20000, 25 LENGTHUNIT["metre",1], 26 ID["EPSG",8807]]], 27 CS[Cartesian,2], 28 AXIS["easting (E)",east, 29 ORDER[1], 30 LENGTHUNIT["metre",1]], 31 AXIS["northing (N)",north, 32 ORDER[2], 33 LENGTHUNIT["metre",1]]] -
new file tests/csprj2.svx
diff --git a/tests/csprj2.svx b/tests/csprj2.svx new file mode 100644 index 00000000..9ae0d094
- + 1 ; pos=yes warn=0 2 ; Test that *cs custom @FILENAME assumes a .prj extension if the name as given 3 ; doesn't exist, and that a UTF-8 "BOM" at the start of the file is skipped. 4 ; The coordinate system is quoted here too, with the `@` inside the quotes. 5 *cs EPSG:6318 6 *cs out CUSTOM "@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..e3aa2292
- + 1 ./csprjbad.svx:4:17: error: Couldn't open file "nosuchfile.prj" 2 *cs out CUSTOM @nosuchfile.prj 3 ^~~~~~~~~~~~~~ 4 ./csprjbad.svx:6: error: The input projection is set but the output projection isn't 5 *fix entrance -86.0905 37.1966 200 6 7 Removing trailing traverses... 8 9 Concatenating traverses... 10 11 Simplifying network... 12 13 Calculating network... 14 15 Calculating traverses... 16 17 Calculating trailing traverses... 18 19 Calculating statistics... 20 21 Survey contains 2 survey stations, joined by 1 leg. 22 There are 0 loops. 23 Total length of survey legs = 30.00m ( 30.00m adjusted) 24 Total plan length of survey legs = 29.89m 25 Total vertical length of survey legs = 2.61m 26 Vertical range = 2.61m (from entrance at 200.00m to a at 197.39m) 27 North-South range = 0.00m (from a at 37.20m to a at 37.20m) 28 East-West range = 29.89m (from a at -56.20m to entrance at -86.09m) 29 30 There were 0 warning(s) and 2 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..8491a7fa
- + 1 ; pos=fail error=2 2 ; Test the error from *cs custom @FILENAME naming a file which isn't there. 3 *cs EPSG:6318 4 *cs out CUSTOM @nosuchfile.prj 5 6 *fix entrance -86.0905 37.1966 200 7 8 entrance a 30.0 090 -05
