source: git/src/survexport.cc @ 14000ea

debug-ci-sanitisersfaster-cavernloglog-selectstereo-2025walls-datawalls-data-hanging-as-warningwarn-only-for-hanging-survey
Last change on this file since 14000ea was 355df41, checked in by Olly Betts <olly@…>, 3 years ago

Add ability to export as Survex 3d

This is useful as you can filter to a subset of surveys, filter
out splays, convert from other formats the img library can read,
etc.

This is a bit rough and ready currently. Please report issues.

  • Property mode set to 100644
File size: 13.8 KB
RevLine 
[79b32a95]1/* survexport.cc
2 * Convert a processed survey data file to another format.
[bd30612]3 */
4
[355df41]5/* Copyright (C) 1994-2004,2008,2010,2011,2013,2014,2018,2020,2022 Olly Betts
[bd30612]6 * Copyright (C) 2004 John Pybus (SVG Output code)
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21 */
22
23#ifdef HAVE_CONFIG_H
24#include <config.h>
25#endif
26
[9990aab]27#define MSG_SETUP_PROJ_SEARCH_PATH 1
28
[bd30612]29#include <math.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33
34#include "export.h"
35#include "mainfrm.h"
36
37#include "cmdline.h"
38#include "filename.h"
39#include "img_hosted.h"
40#include "message.h"
41#include "str.h"
42#include "useful.h"
43
[a8d2349]44#include <iostream>
[f7fb2ec]45#include <string>
46
47using namespace std;
48
[bd30612]49int
50main(int argc, char **argv)
51{
52   double pan = 0;
53   double tilt = -90.0;
[4d3c8915]54   export_format format = FMT_MAX_PLUS_ONE_;
[bd30612]55   int show_mask = 0;
56   const char *survey = NULL;
57   double grid = 0.0; /* grid spacing (or 0 for no grid) */
58   double text_height = DEFAULT_TEXT_HEIGHT; /* for station labels */
59   double marker_size = DEFAULT_MARKER_SIZE; /* for station markers */
60   double scale = 500.0;
[1a46879]61   SurveyFilter* filter = NULL;
[bd30612]62
[b349e7e2]63   {
64       /* Default to .pos output if installed as 3dtopos. */
65       char* progname = baseleaf_from_fnm(argv[0]);
66       if (strcasecmp(progname, "3dtopos") == 0) {
67           format = FMT_POS;
68       }
69       osfree(progname);
70   }
71
[4d3c8915]72   const int OPT_FMT_BASE = 20000;
[02ba4c9]73   enum {
74       OPT_SCALE = 0x100, OPT_BEARING, OPT_TILT, OPT_PLAN, OPT_ELEV,
75       OPT_LEGS, OPT_SURF, OPT_SPLAYS, OPT_CROSSES, OPT_LABELS, OPT_ENTS,
76       OPT_FIXES, OPT_EXPORTS, OPT_XSECT, OPT_WALLS, OPT_PASG,
[32a040e]77       OPT_CENTRED, OPT_FULL_COORDS, OPT_CLAMP_TO_GROUND, OPT_DEFAULTS
[02ba4c9]78   };
[bd30612]79   static const struct option long_opts[] = {
80        /* const char *name; int has_arg (0 no_argument, 1 required, 2 options_*); int *flag; int val */
81        {"survey", required_argument, 0, 's'},
[02ba4c9]82        {"scale", required_argument, 0, OPT_SCALE},
83        {"bearing", required_argument, 0, OPT_BEARING},
84        {"tilt", required_argument, 0, OPT_TILT},
85        {"plan", no_argument, 0, OPT_PLAN},
86        {"elevation", no_argument, 0, OPT_ELEV},
[f7fb2ec]87        {"legs", no_argument, 0, OPT_LEGS},
[6e89e5d]88        {"surface-legs", no_argument, 0, OPT_SURF},
[02ba4c9]89        {"splays", no_argument, 0, OPT_SPLAYS},
[f7fb2ec]90        {"crosses", no_argument, 0, OPT_CROSSES},
91        {"station-names", no_argument, 0, OPT_LABELS},
[02ba4c9]92        {"entrances", no_argument, 0, OPT_ENTS},
93        {"fixes", no_argument, 0, OPT_FIXES},
94        {"exports", no_argument, 0, OPT_EXPORTS},
95        {"cross-sections", no_argument, 0, OPT_XSECT},
96        {"walls", no_argument, 0, OPT_WALLS},
97        {"passages", no_argument, 0, OPT_PASG},
98        {"origin-in-centre", no_argument, 0, OPT_CENTRED},
99        {"full-coordinates", no_argument, 0, OPT_FULL_COORDS},
[32a040e]100        {"clamp-to-ground", no_argument, 0, OPT_CLAMP_TO_GROUND},
[f7fb2ec]101        {"defaults", no_argument, 0, OPT_DEFAULTS},
[bd30612]102        {"grid", optional_argument, 0, 'g'},
103        {"text-height", required_argument, 0, 't'},
104        {"marker-size", required_argument, 0, 'm'},
[355df41]105        {"3d", no_argument, 0, OPT_FMT_BASE + FMT_3D},
[df121c2d]106        {"csv", no_argument, 0, OPT_FMT_BASE + FMT_CSV},
[4d3c8915]107        {"dxf", no_argument, 0, OPT_FMT_BASE + FMT_DXF},
108        {"eps", no_argument, 0, OPT_FMT_BASE + FMT_EPS},
109        {"gpx", no_argument, 0, OPT_FMT_BASE + FMT_GPX},
110        {"hpgl", no_argument, 0, OPT_FMT_BASE + FMT_HPGL},
111        {"json", no_argument, 0, OPT_FMT_BASE + FMT_JSON},
112        {"kml", no_argument, 0, OPT_FMT_BASE + FMT_KML},
113        {"plt", no_argument, 0, OPT_FMT_BASE + FMT_PLT},
114        {"skencil", no_argument, 0, OPT_FMT_BASE + FMT_SK},
115        {"pos", no_argument, 0, OPT_FMT_BASE + FMT_POS},
116        {"svg", no_argument, 0, OPT_FMT_BASE + FMT_SVG},
[bd30612]117        {"help", no_argument, 0, HLP_HELP},
118        {"version", no_argument, 0, HLP_VERSION},
[02ba4c9]119        // US spelling:
120        {"origin-in-center", no_argument, 0, OPT_CENTRED},
121        // Abbreviation:
122        {"full-coords", no_argument, 0, OPT_FULL_COORDS},
[bd30612]123        {0,0,0,0}
124   };
125
[02ba4c9]126#define short_opts "s:g::t:m:"
[bd30612]127
128   static struct help_msg help[] = {
129        /*                      <-- */
130        {HLP_ENCODELONG(0),   /*only load the sub-survey with this prefix*/199, 0},
[c0e4f18]131        /* TRANSLATORS: These example input values should not be translated. */
[02ba4c9]132        {HLP_ENCODELONG(1),   /*scale (50, 0.02, 1:50 and 2:100 all mean 1:50)*/217, 0},
[c0e4f18]133        /* TRANSLATORS: These example input values should not be translated. */
[02ba4c9]134        {HLP_ENCODELONG(2),   /*bearing (90, 90d, 100g all mean 90°)*/460, 0},
[c0e4f18]135        /* TRANSLATORS: These example input values should not be translated. */
[02ba4c9]136        {HLP_ENCODELONG(3),   /*tilt (45, 45d, 50g, 100% all mean 45°)*/461, 0},
[c0e4f18]137        /* TRANSLATORS: Don't translate example command line option --tilt=-90 */
[02ba4c9]138        {HLP_ENCODELONG(4),   /*plan view (equivalent to --tilt=-90)*/462, 0},
[c0e4f18]139        /* TRANSLATORS: Don't translate example command line option --tilt=0 */
[02ba4c9]140        {HLP_ENCODELONG(5),   /*elevation view (equivalent to --tilt=0)*/463, 0},
[f7fb2ec]141        {HLP_ENCODELONG(6),   /*underground survey legs*/476, 0},
[02ba4c9]142        {HLP_ENCODELONG(7),   /*surface survey legs*/464, 0},
143        {HLP_ENCODELONG(8),   /*splay legs*/465, 0},
[f7fb2ec]144        {HLP_ENCODELONG(9),   /*station markers*/474, 0},
145        {HLP_ENCODELONG(10),  /*station labels*/475, 0},
[02ba4c9]146        {HLP_ENCODELONG(11),  /*entrances*/466, 0},
147        {HLP_ENCODELONG(12),  /*fixed points*/467, 0},
148        {HLP_ENCODELONG(13),  /*exported stations*/468, 0},
149        {HLP_ENCODELONG(14),  /*cross-sections*/469, 0},
150        {HLP_ENCODELONG(15),  /*walls*/470, 0},
151        {HLP_ENCODELONG(16),  /*passages*/471, 0},
152        {HLP_ENCODELONG(17),  /*origin in centre*/472, 0},
153        {HLP_ENCODELONG(18),  /*full coordinates*/473, 0},
[32a040e]154        {HLP_ENCODELONG(19),  /*clamp to ground*/478, 0},
155        {HLP_ENCODELONG(20),  /*include items exported by default*/155, 0},
156        {HLP_ENCODELONG(21),  /*generate grid (default %sm)*/148, STRING(DEFAULT_GRID_SPACING)},
157        {HLP_ENCODELONG(22),  /*station labels text height (default %s)*/149, STRING(DEFAULT_TEXT_HEIGHT)},
158        {HLP_ENCODELONG(23),  /*station marker size (default %s)*/152, STRING(DEFAULT_MARKER_SIZE)},
[355df41]159        {HLP_ENCODELONG(24),  /*produce Survex 3d output*/487, 0},
160        {HLP_ENCODELONG(25),  /*produce CSV output*/102, 0},
161        {HLP_ENCODELONG(26),  /*produce DXF output*/156, 0},
162        {HLP_ENCODELONG(27),  /*produce EPS output*/454, 0},
163        {HLP_ENCODELONG(28),  /*produce GPX output*/455, 0},
164        {HLP_ENCODELONG(29),  /*produce HPGL output*/456, 0},
165        {HLP_ENCODELONG(30),  /*produce JSON output*/457, 0},
166        {HLP_ENCODELONG(31),  /*produce KML output*/458, 0},
[bd30612]167        /* TRANSLATORS: "Compass" and "Carto" are the names of software packages,
168         * so should not be translated. */
[355df41]169        {HLP_ENCODELONG(32),  /*produce Compass PLT output for Carto*/159, 0},
[4d3c8915]170        /* TRANSLATORS: "Skencil" is the name of a software package, so should not be
171         * translated. */
[355df41]172        {HLP_ENCODELONG(33),  /*produce Skencil output*/158, 0},
173        {HLP_ENCODELONG(34),  /*produce Survex POS output*/459, 0},
174        {HLP_ENCODELONG(35),  /*produce SVG output*/160, 0},
[bd30612]175        {0, 0, 0}
176   };
177
178   msg_init(argv);
179
[f7fb2ec]180   string optmap[sizeof(show_mask) * CHAR_BIT];
181
182   int long_index;
183   bool always_include_defaults = false;
184   cmdline_init(argc, argv, short_opts, long_opts, &long_index, help, 1, 2);
[bd30612]185   while (1) {
[f7fb2ec]186      long_index = -1;
[bd30612]187      int opt = cmdline_getopt();
188      if (opt == EOF) break;
[f7fb2ec]189      int bit = 0;
[bd30612]190      switch (opt) {
[02ba4c9]191       case OPT_LEGS:
[f7fb2ec]192         bit = LEGS;
[bd30612]193         break;
[02ba4c9]194       case OPT_SURF:
[f7fb2ec]195         bit = SURF;
[02ba4c9]196         break;
197       case OPT_SPLAYS:
[f7fb2ec]198         bit = SPLAYS;
[02ba4c9]199         break;
200       case OPT_CROSSES:
[f7fb2ec]201         bit = STNS;
[bd30612]202         break;
[02ba4c9]203       case OPT_LABELS:
[f7fb2ec]204         bit = LABELS;
[bd30612]205         break;
[02ba4c9]206       case OPT_ENTS:
[f7fb2ec]207         bit = ENTS;
[02ba4c9]208         break;
209       case OPT_FIXES:
[f7fb2ec]210         bit = FIXES;
[02ba4c9]211         break;
212       case OPT_EXPORTS:
[f7fb2ec]213         bit = EXPORTS;
[02ba4c9]214         break;
215       case OPT_XSECT:
[f7fb2ec]216         bit = XSECT;
[02ba4c9]217         break;
218       case OPT_WALLS:
[f7fb2ec]219         bit = WALLS;
[02ba4c9]220         break;
221       case OPT_PASG:
[f7fb2ec]222         bit = PASG;
[02ba4c9]223         break;
224       case OPT_CENTRED:
[f7fb2ec]225         bit = CENTRED;
[02ba4c9]226         break;
227       case OPT_FULL_COORDS:
[f7fb2ec]228         bit = FULL_COORDS;
229         break;
[32a040e]230       case OPT_CLAMP_TO_GROUND:
231         bit = CLAMP_TO_GROUND;
232         break;
[f7fb2ec]233       case OPT_DEFAULTS:
234         always_include_defaults = true;
[bd30612]235         break;
236       case 'g': /* Grid */
237         if (optarg) {
238            grid = cmdline_double_arg();
239         } else {
240            grid = (double)DEFAULT_GRID_SPACING;
241         }
[f7fb2ec]242         bit = GRID;
[bd30612]243         break;
[02ba4c9]244       case OPT_SCALE: {
245         char* colon = strchr(optarg, ':');
246         if (!colon) {
247             /* --scale=1000 => 1:1000 => scale = 1000 */
248             scale = cmdline_double_arg();
249             if (scale < 1.0) {
250                 /* --scale=0.001 => 1:1000 => scale = 1000 */
251                 scale = 1.0 / scale;
252             }
253         } else if (colon - optarg == 1 && optarg[0] == '1') {
254             /* --scale=1:1000 => 1:1000 => scale = 1000 */
255             optarg += 2;
256             scale = cmdline_double_arg();
[f7fb2ec]257             optarg -= 2;
[02ba4c9]258         } else {
259             /* --scale=2:1000 => 1:500 => scale = 500 */
260             *colon = '\0';
261             scale = cmdline_double_arg();
262             optarg = colon + 1;
263             scale = cmdline_double_arg() / scale;
[f7fb2ec]264             *colon = ':';
[02ba4c9]265         }
[f7fb2ec]266         bit = SCALE;
[02ba4c9]267         break;
268       }
269       case OPT_BEARING: {
270         int units = 0;
271         size_t len = strlen(optarg);
272         if (len > 0) {
273             char ch = optarg[len - 1];
274             switch (ch) {
275                 case 'd':
276                 case 'g':
277                     units = ch;
278                     optarg[len - 1] = '\0';
279                     break;
280             }
[f7fb2ec]281             pan = cmdline_double_arg();
282             optarg[len - 1] = ch;
283         } else {
284             pan = cmdline_double_arg();
[02ba4c9]285         }
286         if (units == 'g') {
287             pan *= 0.9;
288         }
[40623c5]289         bit = ORIENTABLE;
[02ba4c9]290         break;
291       }
292       case OPT_TILT: {
293         int units = 0;
294         size_t len = strlen(optarg);
295         if (len > 0) {
296             char ch = optarg[len - 1];
297             switch (ch) {
298                 case '%':
299                 case 'd':
300                 case 'g':
301                     units = ch;
302                     optarg[len - 1] = '\0';
303                     break;
304             }
[f7fb2ec]305             tilt = cmdline_double_arg();
306             optarg[len - 1] = ch;
307         } else {
308             tilt = cmdline_double_arg();
[02ba4c9]309         }
310         if (units == 'g') {
311             tilt *= 0.9;
312         } else if (units == '%') {
313             tilt = deg(atan(tilt * 0.01));
314         }
[40623c5]315         bit = ORIENTABLE;
[02ba4c9]316         break;
317       }
318       case OPT_PLAN:
319         tilt = -90.0;
[40623c5]320         bit = ORIENTABLE;
[02ba4c9]321         break;
322       case OPT_ELEV:
323         tilt = 0.0;
[40623c5]324         bit = ORIENTABLE;
[bd30612]325         break;
326       case 't': /* Text height */
327         text_height = cmdline_double_arg();
[f7fb2ec]328         bit = TEXT_HEIGHT;
[bd30612]329         break;
330       case 'm': /* Marker size */
331         marker_size = cmdline_double_arg();
[f7fb2ec]332         bit = MARKER_SIZE;
[bd30612]333         break;
334       case 's':
[1a46879]335         if (survey) {
336             if (!filter) {
337                 filter = new SurveyFilter();
338                 filter->add(survey);
339             }
340             filter->add(optarg);
341         } else {
342             survey = optarg;
343         }
[bd30612]344         break;
[4d3c8915]345       default:
346         if (opt >= OPT_FMT_BASE && opt < OPT_FMT_BASE + FMT_MAX_PLUS_ONE_) {
347             format = export_format(opt - OPT_FMT_BASE);
348         }
[bd30612]349      }
[f7fb2ec]350      if (bit) {
351          show_mask |= bit;
352          int i = 0;
353          while (((bit >> i) & 1) == 0) ++i;
354
355          if (!optmap[i].empty()) optmap[i] += ' ';
356
357          // Reconstruct what the command line option was.
358          if (long_index < 0) {
359              optmap[i] += '-';
360              optmap[i] += char(opt);
361              if (optarg) {
362                  if (optarg == argv[optind - 1]) {
363                      optmap[i] += ' ';
364                  }
365                  optmap[i] += optarg;
366              }
367          } else {
368              optmap[i] += "--";
369              optmap[i] += long_opts[long_index].name;
370              if (optarg) {
371                  if (optarg == argv[optind - 1]) {
372                      optmap[i] += ' ';
373                  } else {
374                      optmap[i] += '=';
375                  }
376                  optmap[i] += optarg;
377              }
378          }
379      }
[bd30612]380   }
381
[1a46879]382   // A single --survey is handled by img at load-time.  Multiple --survey are
383   // handled via a SurveyFilter at export time.
384   if (filter) survey = NULL;
385
[bd30612]386   const char* fnm_in = argv[optind++];
387   const char* fnm_out = argv[optind];
388   if (fnm_out) {
[4d3c8915]389      if (format == FMT_MAX_PLUS_ONE_) {
390         // Select format based on extension.
[bd30612]391         size_t len = strlen(fnm_out);
[4d3c8915]392         for (size_t i = 0; i < FMT_MAX_PLUS_ONE_; ++i) {
393            const auto& info = export_format_info[i];
394            size_t l = strlen(info.extension);
[bd30612]395            if (len > l + 1 &&
[4d3c8915]396                strcasecmp(fnm_out + len - l, info.extension) == 0) {
[bd30612]397               format = export_format(i);
398               break;
399            }
400         }
[4d3c8915]401         if (format == FMT_MAX_PLUS_ONE_) {
402            fatalerror(/*Export format not specified and not known from output file extension*/252);
403         }
[bd30612]404      }
405   } else {
[4d3c8915]406      if (format == FMT_MAX_PLUS_ONE_) {
407         fatalerror(/*Export format not specified*/253);
408      }
[bd30612]409      char *baseleaf = baseleaf_from_fnm(fnm_in);
410      /* note : memory allocated by fnm_out gets leaked in this case... */
[4d3c8915]411      fnm_out = add_ext(baseleaf, export_format_info[format].extension);
[bd30612]412      osfree(baseleaf);
413   }
414
[f7fb2ec]415   const auto& format_info_mask = export_format_info[format].mask;
416   unsigned not_allowed = show_mask &~ format_info_mask;
417   if (not_allowed) {
418       printf("warning: The following options are not supported for this export format and will be ignored:\n");
419       int i = 0;
[7e7433c]420       unsigned bit = 1;
[f7fb2ec]421       while (not_allowed) {
422           if (not_allowed & bit) {
423               // E.g. --walls maps to two bits in show_mask, but the options
424               // are only put on the least significant in such cases.
425               if (!optmap[i].empty())
426                   printf("%s\n", optmap[i].c_str());
427               not_allowed &= ~bit;
428           }
429           ++i;
430           bit <<= 1;
431       }
432       show_mask &= format_info_mask;
433   }
434
435   if (always_include_defaults || show_mask == 0) {
436       show_mask |= export_format_info[format].defaults;
437   }
438
[40623c5]439   if (!(format_info_mask & ORIENTABLE)) {
[f7fb2ec]440       pan = 0.0;
441       tilt = -90.0;
442   }
443
[bd30612]444   Model model;
445   int err = model.Load(fnm_in, survey);
446   if (err) fatalerror(err, fnm_in);
[1a46879]447   if (filter) filter->SetSeparator(model.GetSeparator());
[bd30612]448
[2907665]449   try {
450       if (!Export(fnm_out, model.GetSurveyTitle(),
451                   model.GetDateString(),
452                   model, filter,
453                   pan, tilt, show_mask, format,
454                   grid, text_height, marker_size,
455                   scale)) {
456          fatalerror(/*Couldn’t write file “%s”*/402, fnm_out);
457       }
458   } catch (const wxString & m) {
[a8d2349]459       wxString r = msg_appname();
460       r += ": ";
461       r += wmsg(/*error*/93);
462       r += ": ";
463       r += m;
464       wcerr << r.c_str() << '\n';
[bd30612]465   }
[2907665]466
[bd30612]467   return 0;
468}
Note: See TracBrowser for help on using the repository browser.