source: git/src/survexport.cc @ 8a7804fb

Last change on this file since 8a7804fb was 0b99107, checked in by Olly Betts <olly@…>, 3 weeks ago

Eliminate old FSF addresses

Update GPL/LGPL licence files and boilerplate to direct people who
didn't receive the licence text to the FSF website, as the current
versions of the FSF licence texts now do, rather than giving a postal
address.

  • Property mode set to 100644
File size: 14.2 KB
RevLine 
[79b32a95]1/* survexport.cc
2 * Convert a processed survey data file to another format.
[bd30612]3 */
4
[a901cea]5/* Copyright (C) 1994-2024 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
[0b99107]19 * along with this program; if not, see
20 * <https://www.gnu.org/licenses/>.
[bd30612]21 */
22
23#include <config.h>
24
[9990aab]25#define MSG_SETUP_PROJ_SEARCH_PATH 1
26
[a901cea]27#include <ctype.h>
[bd30612]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"
[1940bcd]38#include "img_for_survex.h"
[bd30612]39#include "message.h"
40#include "useful.h"
41
[a8d2349]42#include <iostream>
[f7fb2ec]43#include <string>
44
45using namespace std;
46
[bd30612]47int
48main(int argc, char **argv)
49{
50   double pan = 0;
51   double tilt = -90.0;
[4d3c8915]52   export_format format = FMT_MAX_PLUS_ONE_;
[bd30612]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;
[1a46879]59   SurveyFilter* filter = NULL;
[bd30612]60
[b349e7e2]61   {
62       /* Default to .pos output if installed as 3dtopos. */
63       char* progname = baseleaf_from_fnm(argv[0]);
[a901cea]64       for (char * p = progname; *p; ++p) {
65           *p = tolower((unsigned char)*p);
66       }
67       if (strcmp(progname, "3dtopos") == 0) {
[b349e7e2]68           format = FMT_POS;
69       }
[ae917b96]70       free(progname);
[b349e7e2]71   }
72
[4d3c8915]73   const int OPT_FMT_BASE = 20000;
[02ba4c9]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,
[32a040e]78       OPT_CENTRED, OPT_FULL_COORDS, OPT_CLAMP_TO_GROUND, OPT_DEFAULTS
[02ba4c9]79   };
[bd30612]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'},
[02ba4c9]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},
[f7fb2ec]88        {"legs", no_argument, 0, OPT_LEGS},
[6e89e5d]89        {"surface-legs", no_argument, 0, OPT_SURF},
[02ba4c9]90        {"splays", no_argument, 0, OPT_SPLAYS},
[f7fb2ec]91        {"crosses", no_argument, 0, OPT_CROSSES},
92        {"station-names", no_argument, 0, OPT_LABELS},
[02ba4c9]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},
[32a040e]101        {"clamp-to-ground", no_argument, 0, OPT_CLAMP_TO_GROUND},
[f7fb2ec]102        {"defaults", no_argument, 0, OPT_DEFAULTS},
[bd30612]103        {"grid", optional_argument, 0, 'g'},
104        {"text-height", required_argument, 0, 't'},
105        {"marker-size", required_argument, 0, 'm'},
[355df41]106        {"3d", no_argument, 0, OPT_FMT_BASE + FMT_3D},
[df121c2d]107        {"csv", no_argument, 0, OPT_FMT_BASE + FMT_CSV},
[4d3c8915]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},
[85f7905]116        {"shp-lines", no_argument, 0, OPT_FMT_BASE + FMT_SHP_LINES},
117        {"shp-points", no_argument, 0, OPT_FMT_BASE + FMT_SHP_POINTS},
[4d3c8915]118        {"svg", no_argument, 0, OPT_FMT_BASE + FMT_SVG},
[bd30612]119        {"help", no_argument, 0, HLP_HELP},
120        {"version", no_argument, 0, HLP_VERSION},
[02ba4c9]121        // US spelling:
122        {"origin-in-center", no_argument, 0, OPT_CENTRED},
123        // Abbreviation:
124        {"full-coords", no_argument, 0, OPT_FULL_COORDS},
[bd30612]125        {0,0,0,0}
126   };
127
[02ba4c9]128#define short_opts "s:g::t:m:"
[bd30612]129
130   static struct help_msg help[] = {
131        /*                      <-- */
[5a2d346]132        {HLP_ENCODELONG(0),   /*only load the sub-survey with this prefix*/199, 0, 0},
[c0e4f18]133        /* TRANSLATORS: These example input values should not be translated. */
[5a2d346]134        {HLP_ENCODELONG(1),   /*scale (50, 0.02, 1:50 and 2:100 all mean 1:50)*/217, 0, 0},
[c0e4f18]135        /* TRANSLATORS: These example input values should not be translated. */
[5a2d346]136        {HLP_ENCODELONG(2),   /*bearing (90, 90d, 100g all mean 90°)*/460, 0, 0},
[c0e4f18]137        /* TRANSLATORS: These example input values should not be translated. */
[5a2d346]138        {HLP_ENCODELONG(3),   /*tilt (45, 45d, 50g, 100% all mean 45°)*/461, 0, 0},
[c0e4f18]139        /* TRANSLATORS: Don't translate example command line option --tilt=-90 */
[5a2d346]140        {HLP_ENCODELONG(4),   /*plan view (equivalent to --tilt=-90)*/462, 0, 0},
[c0e4f18]141        /* TRANSLATORS: Don't translate example command line option --tilt=0 */
[5a2d346]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},
[bd30612]169        /* TRANSLATORS: "Compass" and "Carto" are the names of software packages,
170         * so should not be translated. */
[5a2d346]171        {HLP_ENCODELONG(32),  /*produce Compass PLT output for Carto*/159, 0, 0},
172        {HLP_ENCODELONG(33),  /*produce Survex POS output*/459, 0, 0},
[85f7905]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},
[5a2d346]176        {0, 0, 0, 0}
[bd30612]177   };
178
179   msg_init(argv);
180
[f7fb2ec]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);
[bd30612]186   while (1) {
[f7fb2ec]187      long_index = -1;
[bd30612]188      int opt = cmdline_getopt();
189      if (opt == EOF) break;
[f7fb2ec]190      int bit = 0;
[bd30612]191      switch (opt) {
[02ba4c9]192       case OPT_LEGS:
[f7fb2ec]193         bit = LEGS;
[bd30612]194         break;
[02ba4c9]195       case OPT_SURF:
[f7fb2ec]196         bit = SURF;
[02ba4c9]197         break;
198       case OPT_SPLAYS:
[f7fb2ec]199         bit = SPLAYS;
[02ba4c9]200         break;
201       case OPT_CROSSES:
[f7fb2ec]202         bit = STNS;
[bd30612]203         break;
[02ba4c9]204       case OPT_LABELS:
[f7fb2ec]205         bit = LABELS;
[bd30612]206         break;
[02ba4c9]207       case OPT_ENTS:
[f7fb2ec]208         bit = ENTS;
[02ba4c9]209         break;
210       case OPT_FIXES:
[f7fb2ec]211         bit = FIXES;
[02ba4c9]212         break;
213       case OPT_EXPORTS:
[f7fb2ec]214         bit = EXPORTS;
[02ba4c9]215         break;
216       case OPT_XSECT:
[f7fb2ec]217         bit = XSECT;
[02ba4c9]218         break;
219       case OPT_WALLS:
[f7fb2ec]220         bit = WALLS;
[02ba4c9]221         break;
222       case OPT_PASG:
[f7fb2ec]223         bit = PASG;
[02ba4c9]224         break;
225       case OPT_CENTRED:
[f7fb2ec]226         bit = CENTRED;
[02ba4c9]227         break;
228       case OPT_FULL_COORDS:
[f7fb2ec]229         bit = FULL_COORDS;
230         break;
[32a040e]231       case OPT_CLAMP_TO_GROUND:
232         bit = CLAMP_TO_GROUND;
233         break;
[f7fb2ec]234       case OPT_DEFAULTS:
235         always_include_defaults = true;
[bd30612]236         break;
237       case 'g': /* Grid */
238         if (optarg) {
239            grid = cmdline_double_arg();
240         } else {
241            grid = (double)DEFAULT_GRID_SPACING;
242         }
[f7fb2ec]243         bit = GRID;
[bd30612]244         break;
[02ba4c9]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();
[f7fb2ec]258             optarg -= 2;
[02ba4c9]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;
[f7fb2ec]265             *colon = ':';
[02ba4c9]266         }
[f7fb2ec]267         bit = SCALE;
[02ba4c9]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             }
[f7fb2ec]282             pan = cmdline_double_arg();
283             optarg[len - 1] = ch;
284         } else {
285             pan = cmdline_double_arg();
[02ba4c9]286         }
287         if (units == 'g') {
288             pan *= 0.9;
289         }
[40623c5]290         bit = ORIENTABLE;
[02ba4c9]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             }
[f7fb2ec]306             tilt = cmdline_double_arg();
307             optarg[len - 1] = ch;
308         } else {
309             tilt = cmdline_double_arg();
[02ba4c9]310         }
311         if (units == 'g') {
312             tilt *= 0.9;
313         } else if (units == '%') {
314             tilt = deg(atan(tilt * 0.01));
315         }
[40623c5]316         bit = ORIENTABLE;
[02ba4c9]317         break;
318       }
319       case OPT_PLAN:
320         tilt = -90.0;
[40623c5]321         bit = ORIENTABLE;
[02ba4c9]322         break;
323       case OPT_ELEV:
324         tilt = 0.0;
[40623c5]325         bit = ORIENTABLE;
[bd30612]326         break;
327       case 't': /* Text height */
328         text_height = cmdline_double_arg();
[f7fb2ec]329         bit = TEXT_HEIGHT;
[bd30612]330         break;
331       case 'm': /* Marker size */
332         marker_size = cmdline_double_arg();
[f7fb2ec]333         bit = MARKER_SIZE;
[bd30612]334         break;
335       case 's':
[1a46879]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         }
[bd30612]345         break;
[4d3c8915]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         }
[bd30612]350      }
[f7fb2ec]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      }
[bd30612]381   }
382
[1a46879]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
[bd30612]387   const char* fnm_in = argv[optind++];
388   const char* fnm_out = argv[optind];
389   if (fnm_out) {
[4d3c8915]390      if (format == FMT_MAX_PLUS_ONE_) {
391         // Select format based on extension.
[bd30612]392         size_t len = strlen(fnm_out);
[a901cea]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         }
[4d3c8915]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);
[bd30612]402            if (len > l + 1 &&
[a901cea]403                strcmp(ext + MAX_EXT_LEN + 1 - l, info.extension) == 0) {
[85f7905]404               // Shapefile (lines) will be selected for .shp, which is
405               // probably what's wanted.
[bd30612]406               format = export_format(i);
407               break;
408            }
409         }
[4d3c8915]410         if (format == FMT_MAX_PLUS_ONE_) {
411            fatalerror(/*Export format not specified and not known from output file extension*/252);
412         }
[bd30612]413      }
414   } else {
[4d3c8915]415      if (format == FMT_MAX_PLUS_ONE_) {
416         fatalerror(/*Export format not specified*/253);
417      }
[bd30612]418      char *baseleaf = baseleaf_from_fnm(fnm_in);
419      /* note : memory allocated by fnm_out gets leaked in this case... */
[4d3c8915]420      fnm_out = add_ext(baseleaf, export_format_info[format].extension);
[ae917b96]421      free(baseleaf);
[bd30612]422   }
423
[f7fb2ec]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;
[7e7433c]429       unsigned bit = 1;
[f7fb2ec]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
[40623c5]448   if (!(format_info_mask & ORIENTABLE)) {
[f7fb2ec]449       pan = 0.0;
450       tilt = -90.0;
451   }
452
[bd30612]453   Model model;
454   int err = model.Load(fnm_in, survey);
455   if (err) fatalerror(err, fnm_in);
[1a46879]456   if (filter) filter->SetSeparator(model.GetSeparator());
[bd30612]457
[2907665]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) {
[a8d2349]467       wxString r = msg_appname();
468       r += ": ";
469       r += wmsg(/*error*/93);
470       r += ": ";
471       r += m;
472       wcerr << r.c_str() << '\n';
[bd30612]473   }
[2907665]474
[bd30612]475   return 0;
476}
Note: See TracBrowser for help on using the repository browser.