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