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