source: git/src/survexport.cc

Last change on this file was a49a80c0, checked in by Olly Betts <olly@…>, 4 months ago

Clean up inclusion of osalloc.h

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