source: git/src/export.cc @ 8553bdb

RELEASE/1.2debug-cidebug-ci-sanitiserswalls-datawalls-data-hanging-as-warning
Last change on this file since 8553bdb was 32a040e, checked in by Olly Betts <olly@…>, 6 years ago

Support "clamp to ground" for KML export

The default altitude mode for KML is "clampToGround", which renders
data on the surface of the terrain. This is useful with KML viewers
which render the terrain as opaque so underground data isn't visible.
Rendering cave passages on the surface isn't great, but is better
than not being able to see them at all.

This option may also be helpful if you want to see where to look on the
surface for new entrances.

  • Property mode set to 100644
File size: 46.5 KB
Line 
1/* export.cc
2 * Export to CAD-like formats (DXF, Skencil, SVG, EPS) and also Compass PLT.
3 */
4
5/* Copyright (C) 1994-2004,2005,2006,2008,2010,2011,2012,2013,2014,2015,2016,2018 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#ifdef HAVE_CONFIG_H
24#include <config.h>
25#endif
26
27#include "export.h"
28
29#include "wx.h"
30#include <wx/utils.h>
31#include "exportfilter.h"
32#include "gpx.h"
33#include "hpgl.h"
34#include "json.h"
35#include "kml.h"
36#include "mainfrm.h"
37#include "pos.h"
38
39#include <float.h>
40#include <locale.h>
41#include <math.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45#include <time.h>
46
47#if defined(HAVE_GETPWUID) && !defined(__DJGPP__)
48# include <pwd.h>
49# include <sys/types.h>
50# include <unistd.h>
51#endif
52
53#include <utility>
54#include <vector>
55
56#include "cmdline.h"
57#include "debug.h"
58#include "filename.h"
59#include "hash.h"
60#include "img_hosted.h"
61#include "message.h"
62#include "useful.h"
63
64#define POINTS_PER_INCH 72.0
65#define POINTS_PER_MM (POINTS_PER_INCH / MM_PER_INCH)
66
67#define SQRT_2          1.41421356237309504880168872420969
68
69// Order here needs to match order of export_format enum in export.h.
70
71const format_info export_format_info[] = {
72    { ".dxf", /*DXF files*/411,
73      LABELS|LEGS|SURF|SPLAYS|STNS|PASG|XSECT|WALLS|MARKER_SIZE|TEXT_HEIGHT|GRID|FULL_COORDS,
74      LABELS|LEGS|STNS },
75    { ".eps", /*EPS files*/412,
76      LABELS|LEGS|SURF|SPLAYS|STNS|PASG|XSECT|WALLS,
77      LABELS|LEGS|STNS },
78    { ".gpx", /*GPX files*/413,
79      LABELS|LEGS|SURF|SPLAYS|ENTS|FIXES|EXPORTS|PROJ|EXPORT_3D,
80      LABELS },
81    /* TRANSLATORS: Here "plotter" refers to a machine which draws a printout
82     * on a (usually large) sheet of paper using a pen mounted in a motorised
83     * mechanism. */
84    { ".hpgl", /*HPGL for plotters*/414,
85      LABELS|LEGS|SURF|SPLAYS|STNS|CENTRED,
86      LABELS|LEGS|STNS },
87    { ".json", /*JSON files*/445,
88      LEGS|SPLAYS|CENTRED|EXPORT_3D,
89      LEGS },
90    { ".kml", /*KML files*/444,
91      LABELS|LEGS|SPLAYS|PASG|XSECT|WALLS|ENTS|FIXES|EXPORTS|PROJ|EXPORT_3D|CLAMP_TO_GROUND,
92      LABELS|LEGS },
93    /* TRANSLATORS: "Compass" and "Carto" are the names of software packages,
94     * so should not be translated:
95     * http://www.fountainware.com/compass/
96     * http://www.psc-cavers.org/carto/ */
97    { ".plt", /*Compass PLT for use with Carto*/415,
98      LABELS|LEGS|SURF|SPLAYS,
99      LABELS|LEGS },
100    /* TRANSLATORS: "Skencil" is the name of a software package, so should not be
101     * translated: http://www.skencil.org/ */
102    { ".sk", /*Skencil files*/416,
103      LABELS|LEGS|SURF|SPLAYS|STNS|MARKER_SIZE|GRID|SCALE,
104      LABELS|LEGS|STNS },
105    /* TRANSLATORS: Survex is the name of the software, and "pos" refers to a
106     * file extension, so neither should be translated. */
107    { ".pos", /*Survex pos files*/166,
108      LABELS|ENTS|FIXES|EXPORTS|EXPORT_3D,
109      LABELS },
110    { ".svg", /*SVG files*/417,
111      LABELS|LEGS|SURF|SPLAYS|STNS|PASG|XSECT|WALLS|MARKER_SIZE|TEXT_HEIGHT|SCALE,
112      LABELS|LEGS|STNS },
113};
114
115static_assert(sizeof(export_format_info) == FMT_MAX_PLUS_ONE_ * sizeof(export_format_info[0]),
116              "export_format_info[] matches enum export_format");
117
118static void
119html_escape(FILE *fh, const char *s)
120{
121    while (*s) {
122        switch (*s) {
123            case '<':
124                fputs("&lt;", fh);
125                break;
126            case '>':
127                fputs("&gt;", fh);
128                break;
129            case '&':
130                fputs("&amp;", fh);
131                break;
132            default:
133                PUTC(*s, fh);
134        }
135        ++s;
136    }
137}
138
139// Used by Skencil and SVG.
140static const char *layer_name(int mask) {
141    switch (mask) {
142        case LEGS: case LEGS|SURF:
143            return "Legs";
144        case SURF:
145            return "Surface";
146        case STNS:
147            return "Stations";
148        case LABELS:
149            return "Labels";
150        case XSECT:
151            return "Cross-sections";
152        case WALL1: case WALL2: case WALLS:
153            return "Walls";
154        case PASG:
155            return "Passages";
156    }
157    return "";
158}
159
160static double marker_size; /* for station markers */
161static double grid; /* grid spacing (or 0 for no grid) */
162
163const int *
164ExportFilter::passes() const
165{
166    static const int default_passes[] = { LEGS|SURF|STNS|LABELS, 0 };
167    return default_passes;
168}
169
170class DXF : public ExportFilter {
171    const char * to_close;
172    /* for station labels */
173    double text_height;
174    char pending[1024];
175
176  public:
177    explicit DXF(double text_height_)
178        : to_close(0), text_height(text_height_) { pending[0] = '\0'; }
179    const int * passes() const;
180    bool fopen(const wxString& fnm_out);
181    void header(const char *, const char *, time_t,
182                double min_x, double min_y, double min_z,
183                double max_x, double max_y, double max_z);
184    void line(const img_point *, const img_point *, unsigned, bool);
185    void label(const img_point *, const char *, bool, int);
186    void cross(const img_point *, bool);
187    void xsect(const img_point *, double, double, double);
188    void wall(const img_point *, double, double);
189    void passage(const img_point *, double, double, double);
190    void tube_end();
191    void footer();
192};
193
194const int *
195DXF::passes() const
196{
197    static const int dxf_passes[] = {
198        PASG, XSECT, WALL1, WALL2, LEGS|SURF|STNS|LABELS, 0
199    };
200    return dxf_passes;
201}
202
203bool
204DXF::fopen(const wxString& fnm_out)
205{
206    // DXF gets written as text rather than binary.
207    fh = wxFopen(fnm_out.fn_str(), wxT("w"));
208    return (fh != NULL);
209}
210
211void
212DXF::header(const char *, const char *, time_t,
213            double min_x, double min_y, double min_z,
214            double max_x, double max_y, double max_z)
215{
216   fprintf(fh, "0\nSECTION\n"
217               "2\nHEADER\n");
218   fprintf(fh, "9\n$EXTMIN\n"); /* lower left corner of drawing */
219   fprintf(fh, "10\n%#-.2f\n", min_x); /* x */
220   fprintf(fh, "20\n%#-.2f\n", min_y); /* y */
221   fprintf(fh, "30\n%#-.2f\n", min_z); /* min z */
222   fprintf(fh, "9\n$EXTMAX\n"); /* upper right corner of drawing */
223   fprintf(fh, "10\n%#-.2f\n", max_x); /* x */
224   fprintf(fh, "20\n%#-.2f\n", max_y); /* y */
225   fprintf(fh, "30\n%#-.2f\n", max_z); /* max z */
226   fprintf(fh, "9\n$PDMODE\n70\n3\n"); /* marker style as CROSS */
227   fprintf(fh, "9\n$PDSIZE\n40\n%6.2f\n", marker_size); /* marker size */
228   fprintf(fh, "0\nENDSEC\n");
229
230   fprintf(fh, "0\nSECTION\n"
231               "2\nTABLES\n");
232   fprintf(fh, "0\nTABLE\n" /* Define CONTINUOUS and DASHED line types. */
233               "2\nLTYPE\n"
234               "70\n10\n"
235               "0\nLTYPE\n"
236               "2\nCONTINUOUS\n"
237               "70\n64\n"
238               "3\nContinuous\n"
239               "72\n65\n"
240               "73\n0\n"
241               "40\n0.0\n"
242               "0\nLTYPE\n"
243               "2\nDASHED\n"
244               "70\n64\n"
245               "3\nDashed\n"
246               "72\n65\n"
247               "73\n2\n"
248               "40\n2.5\n"
249               "49\n1.25\n"
250               "49\n-1.25\n"
251               "0\nENDTAB\n");
252   fprintf(fh, "0\nTABLE\n"
253               "2\nLAYER\n");
254   fprintf(fh, "70\n10\n"); /* max # off layers in this DXF file : 10 */
255   /* First Layer: CentreLine */
256   fprintf(fh, "0\nLAYER\n2\nCentreLine\n");
257   fprintf(fh, "70\n64\n"); /* shows layer is referenced by entities */
258   fprintf(fh, "62\n5\n"); /* color: kept the same used by SpeleoGen */
259   fprintf(fh, "6\nCONTINUOUS\n"); /* linetype */
260   /* Next Layer: Stations */
261   fprintf(fh, "0\nLAYER\n2\nStations\n");
262   fprintf(fh, "70\n64\n"); /* shows layer is referenced by entities */
263   fprintf(fh, "62\n7\n"); /* color: kept the same used by SpeleoGen */
264   fprintf(fh, "6\nCONTINUOUS\n"); /* linetype */
265   /* Next Layer: Labels */
266   fprintf(fh, "0\nLAYER\n2\nLabels\n");
267   fprintf(fh, "70\n64\n"); /* shows layer is referenced by entities */
268   fprintf(fh, "62\n7\n"); /* color: kept the same used by SpeleoGen */
269   fprintf(fh, "6\nCONTINUOUS\n"); /* linetype */
270   /* Next Layer: Surface */
271   fprintf(fh, "0\nLAYER\n2\nSurface\n");
272   fprintf(fh, "70\n64\n"); /* shows layer is referenced by entities */
273   fprintf(fh, "62\n5\n"); /* color */
274   fprintf(fh, "6\nDASHED\n"); /* linetype */
275   /* Next Layer: SurfaceStations */
276   fprintf(fh, "0\nLAYER\n2\nSurfaceStations\n");
277   fprintf(fh, "70\n64\n"); /* shows layer is referenced by entities */
278   fprintf(fh, "62\n7\n"); /* color */
279   fprintf(fh, "6\nCONTINUOUS\n"); /* linetype */
280   /* Next Layer: SurfaceLabels */
281   fprintf(fh, "0\nLAYER\n2\nSurfaceLabels\n");
282   fprintf(fh, "70\n64\n"); /* shows layer is referenced by entities */
283   fprintf(fh, "62\n7\n"); /* color */
284   fprintf(fh, "6\nCONTINUOUS\n"); /* linetype */
285   if (grid > 0) {
286      /* Next Layer: Grid */
287      fprintf(fh, "0\nLAYER\n2\nGrid\n");
288      fprintf(fh, "70\n64\n"); /* shows layer is referenced by entities */
289      fprintf(fh, "62\n7\n"); /* color: kept the same used by SpeleoGen */
290      fprintf(fh, "6\nCONTINUOUS\n"); /* linetype */
291   }
292   fprintf(fh, "0\nENDTAB\n"
293               "0\nENDSEC\n");
294
295   fprintf(fh, "0\nSECTION\n"
296               "2\nENTITIES\n");
297
298   if (grid > 0) {
299      double x, y;
300      x = floor(min_x / grid) * grid + grid;
301      y = floor(min_y / grid) * grid + grid;
302      while (x < max_x) {
303         /* horizontal line */
304         fprintf(fh, "0\nLINE\n");
305         fprintf(fh, "8\nGrid\n"); /* Layer */
306         fprintf(fh, "10\n%6.2f\n", x);
307         fprintf(fh, "20\n%6.2f\n", min_y);
308         fprintf(fh, "30\n0\n");
309         fprintf(fh, "11\n%6.2f\n", x);
310         fprintf(fh, "21\n%6.2f\n", max_y);
311         fprintf(fh, "31\n0\n");
312         x += grid;
313      }
314      while (y < max_y) {
315         /* vertical line */
316         fprintf(fh, "0\nLINE\n");
317         fprintf(fh, "8\nGrid\n"); /* Layer */
318         fprintf(fh, "10\n%6.2f\n", min_x);
319         fprintf(fh, "20\n%6.2f\n", y);
320         fprintf(fh, "30\n0\n");
321         fprintf(fh, "11\n%6.2f\n", max_x);
322         fprintf(fh, "21\n%6.2f\n", y);
323         fprintf(fh, "31\n0\n");
324         y += grid;
325      }
326   }
327}
328
329void
330DXF::line(const img_point *p1, const img_point *p, unsigned flags, bool fPendingMove)
331{
332   bool fSurface = (flags & SURF);
333   (void)fPendingMove; /* unused */
334   fprintf(fh, "0\nLINE\n");
335   fprintf(fh, fSurface ? "8\nSurface\n" : "8\nCentreLine\n"); /* Layer */
336   fprintf(fh, "10\n%6.2f\n", p1->x);
337   fprintf(fh, "20\n%6.2f\n", p1->y);
338   fprintf(fh, "30\n%6.2f\n", p1->z);
339   fprintf(fh, "11\n%6.2f\n", p->x);
340   fprintf(fh, "21\n%6.2f\n", p->y);
341   fprintf(fh, "31\n%6.2f\n", p->z);
342}
343
344void
345DXF::label(const img_point *p, const char *s, bool fSurface, int)
346{
347   /* write station label to dxf file */
348   fprintf(fh, "0\nTEXT\n");
349   fprintf(fh, fSurface ? "8\nSurfaceLabels\n" : "8\nLabels\n"); /* Layer */
350   fprintf(fh, "10\n%6.2f\n", p->x);
351   fprintf(fh, "20\n%6.2f\n", p->y);
352   fprintf(fh, "30\n%6.2f\n", p->z);
353   fprintf(fh, "40\n%6.2f\n", text_height);
354   fprintf(fh, "1\n%s\n", s);
355}
356
357void
358DXF::cross(const img_point *p, bool fSurface)
359{
360   /* write station marker to dxf file */
361   fprintf(fh, "0\nPOINT\n");
362   fprintf(fh, fSurface ? "8\nSurfaceStations\n" : "8\nStations\n"); /* Layer */
363   fprintf(fh, "10\n%6.2f\n", p->x);
364   fprintf(fh, "20\n%6.2f\n", p->y);
365   fprintf(fh, "30\n%6.2f\n", p->z);
366}
367
368void
369DXF::xsect(const img_point *p, double angle, double d1, double d2)
370{
371   double s = sin(rad(angle));
372   double c = cos(rad(angle));
373   fprintf(fh, "0\nLINE\n");
374   fprintf(fh, "8\nCross-sections\n"); /* Layer */
375   fprintf(fh, "10\n%6.2f\n", p->x + c * d1);
376   fprintf(fh, "20\n%6.2f\n", p->y + s * d1);
377   fprintf(fh, "30\n%6.2f\n", p->z);
378   fprintf(fh, "11\n%6.2f\n", p->x - c * d2);
379   fprintf(fh, "21\n%6.2f\n", p->y - s * d2);
380   fprintf(fh, "31\n%6.2f\n", p->z);
381}
382
383void
384DXF::wall(const img_point *p, double angle, double d)
385{
386   if (!to_close) {
387       fprintf(fh, "0\nPOLYLINE\n");
388       fprintf(fh, "8\nWalls\n"); /* Layer */
389       fprintf(fh, "70\n0\n"); /* bit 0 == 0 => Open polyline */
390       to_close = "0\nSEQEND\n";
391   }
392   double s = sin(rad(angle));
393   double c = cos(rad(angle));
394   fprintf(fh, "0\nVERTEX\n");
395   fprintf(fh, "8\nWalls\n"); /* Layer */
396   fprintf(fh, "10\n%6.2f\n", p->x + c * d);
397   fprintf(fh, "20\n%6.2f\n", p->y + s * d);
398   fprintf(fh, "30\n%6.2f\n", p->z);
399}
400
401void
402DXF::passage(const img_point *p, double angle, double d1, double d2)
403{
404   fprintf(fh, "0\nSOLID\n");
405   fprintf(fh, "8\nPassages\n"); /* Layer */
406   double s = sin(rad(angle));
407   double c = cos(rad(angle));
408   double x1 = p->x + c * d1;
409   double y1 = p->y + s * d1;
410   double x2 = p->x - c * d2;
411   double y2 = p->y - s * d2;
412   if (*pending) {
413       fputs(pending, fh);
414       fprintf(fh, "12\n%6.2f\n22\n%6.2f\n32\n%6.2f\n"
415                   "13\n%6.2f\n23\n%6.2f\n33\n%6.2f\n",
416                   x1, y1, p->z,
417                   x2, y2, p->z);
418   }
419   sprintf(pending, "10\n%6.2f\n20\n%6.2f\n30\n%6.2f\n"
420                    "11\n%6.2f\n21\n%6.2f\n31\n%6.2f\n",
421                    x1, y1, p->z,
422                    x2, y2, p->z);
423}
424
425void
426DXF::tube_end()
427{
428   *pending = '\0';
429   if (to_close) {
430      fputs(to_close, fh);
431      to_close = NULL;
432   }
433}
434
435void
436DXF::footer()
437{
438   fprintf(fh, "000\nENDSEC\n");
439   fprintf(fh, "000\nEOF\n");
440}
441
442class Skencil : public ExportFilter {
443    double factor;
444  public:
445    explicit Skencil(double scale)
446        : factor(POINTS_PER_MM * 1000.0 / scale) { }
447    const int * passes() const;
448    void header(const char *, const char *, time_t,
449                double min_x, double min_y, double min_z,
450                double max_x, double max_y, double max_z);
451    void start_pass(int layer);
452    void line(const img_point *, const img_point *, unsigned, bool);
453    void label(const img_point *, const char *, bool, int);
454    void cross(const img_point *, bool);
455    void footer();
456};
457
458const int *
459Skencil::passes() const
460{
461    static const int skencil_passes[] = { LEGS|SURF, STNS, LABELS, 0 };
462    return skencil_passes;
463}
464
465void
466Skencil::header(const char *, const char *, time_t,
467                double min_x, double min_y, double /*min_z*/,
468                double max_x, double max_y, double /*max_z*/)
469{
470   fprintf(fh, "##Sketch 1 2\n"); /* File format version */
471   fprintf(fh, "document()\n");
472   fprintf(fh, "layout((%.3f,%.3f),0)\n",
473           (max_x - min_x) * factor, (max_y - min_y) * factor);
474}
475
476void
477Skencil::start_pass(int layer)
478{
479   fprintf(fh, "layer('%s',1,1,0,0,(0,0,0))\n", layer_name(layer));
480}
481
482void
483Skencil::line(const img_point *p1, const img_point *p, unsigned flags, bool fPendingMove)
484{
485   (void)flags; /* unused */
486   if (fPendingMove) {
487       fprintf(fh, "b()\n");
488       fprintf(fh, "bs(%.3f,%.3f,%.3f)\n", p1->x * factor, p1->y * factor, 0.0);
489   }
490   fprintf(fh, "bs(%.3f,%.3f,%.3f)\n", p->x * factor, p->y * factor, 0.0);
491}
492
493void
494Skencil::label(const img_point *p, const char *s, bool fSurface, int)
495{
496   (void)fSurface; /* unused */
497   fprintf(fh, "fp((0,0,0))\n");
498   fprintf(fh, "le()\n");
499   fprintf(fh, "Fn('Times-Roman')\n");
500   fprintf(fh, "Fs(5)\n");
501   fprintf(fh, "txt('");
502   while (*s) {
503      int ch = *s++;
504      if (ch == '\'' || ch == '\\') PUTC('\\', fh);
505      PUTC(ch, fh);
506   }
507   fprintf(fh, "',(%.3f,%.3f))\n", p->x * factor, p->y * factor);
508}
509
510void
511Skencil::cross(const img_point *p, bool fSurface)
512{
513   (void)fSurface; /* unused */
514   fprintf(fh, "b()\n");
515   fprintf(fh, "bs(%.3f,%.3f,%.3f)\n",
516           p->x * factor - marker_size, p->y * factor - marker_size, 0.0);
517   fprintf(fh, "bs(%.3f,%.3f,%.3f)\n",
518           p->x * factor + marker_size, p->y * factor + marker_size, 0.0);
519   fprintf(fh, "bn()\n");
520   fprintf(fh, "bs(%.3f,%.3f,%.3f)\n",
521           p->x * factor + marker_size, p->y * factor - marker_size, 0.0);
522   fprintf(fh, "bs(%.3f,%.3f,%.3f)\n",
523           p->x * factor - marker_size, p->y * factor + marker_size, 0.0);
524}
525
526void
527Skencil::footer(void)
528{
529   fprintf(fh, "guidelayer('Guide Lines',1,0,0,1,(0,0,1))\n");
530   if (grid) {
531      fprintf(fh, "grid((0,0,%.3f,%.3f),1,(0,0,1),'Grid')\n",
532              grid * factor, grid * factor);
533   }
534}
535
536typedef struct point {
537   img_point p;
538   const char *label;
539   struct point *next;
540} point;
541
542#define HTAB_SIZE 0x2000
543
544static point **htab;
545
546static void
547set_name(const img_point *p, const char *s)
548{
549   int hash;
550   point *pt;
551   union {
552      char data[sizeof(int) * 3];
553      int x[3];
554   } u;
555
556   u.x[0] = (int)(p->x * 100);
557   u.x[1] = (int)(p->y * 100);
558   u.x[2] = (int)(p->z * 100);
559   hash = (hash_data(u.data, sizeof(int) * 3) & (HTAB_SIZE - 1));
560   for (pt = htab[hash]; pt; pt = pt->next) {
561      if (pt->p.x == p->x && pt->p.y == p->y && pt->p.z == p->z) {
562         /* already got name for these coordinates */
563         /* FIXME: what about multiple names for the same station? */
564         return;
565      }
566   }
567
568   pt = osnew(point);
569   pt->label = osstrdup(s);
570   pt->p = *p;
571   pt->next = htab[hash];
572   htab[hash] = pt;
573
574   return;
575}
576
577static const char *
578find_name(const img_point *p)
579{
580   int hash;
581   point *pt;
582   union {
583      char data[sizeof(int) * 3];
584      int x[3];
585   } u;
586   wxASSERT(p);
587
588   u.x[0] = (int)(p->x * 100);
589   u.x[1] = (int)(p->y * 100);
590   u.x[2] = (int)(p->z * 100);
591   hash = (hash_data(u.data, sizeof(int) * 3) & (HTAB_SIZE - 1));
592   for (pt = htab[hash]; pt; pt = pt->next) {
593      if (pt->p.x == p->x && pt->p.y == p->y && pt->p.z == p->z)
594         return pt->label;
595   }
596   return "?";
597}
598
599class SVG : public ExportFilter {
600    const char * to_close;
601    bool close_g;
602    double factor;
603    /* for station labels */
604    double text_height;
605    char pending[1024];
606
607  public:
608    SVG(double scale, double text_height_)
609        : to_close(NULL),
610          close_g(false),
611          factor(1000.0 / scale),
612          text_height(text_height_) {
613        pending[0] = '\0';
614    }
615    const int * passes() const;
616    void header(const char *, const char *, time_t,
617                double min_x, double min_y, double min_z,
618                double max_x, double max_y, double max_z);
619    void start_pass(int layer);
620    void line(const img_point *, const img_point *, unsigned, bool);
621    void label(const img_point *, const char *, bool, int);
622    void cross(const img_point *, bool);
623    void xsect(const img_point *, double, double, double);
624    void wall(const img_point *, double, double);
625    void passage(const img_point *, double, double, double);
626    void tube_end();
627    void footer();
628};
629
630const int *
631SVG::passes() const
632{
633    static const int svg_passes[] = {
634        PASG, LEGS|SURF, XSECT, WALL1, WALL2, LABELS, STNS, 0
635    };
636    return svg_passes;
637}
638
639void
640SVG::header(const char * title, const char *, time_t,
641            double min_x, double min_y, double /*min_z*/,
642            double max_x, double max_y, double /*max_z*/)
643{
644   const char *unit = "mm";
645   const double SVG_MARGIN = 5.0; // In units of "unit".
646   htab = (point **)osmalloc(HTAB_SIZE * ossizeof(point *));
647   for (size_t i = 0; i < HTAB_SIZE; ++i) htab[i] = NULL;
648   fprintf(fh, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
649   double width = (max_x - min_x) * factor + SVG_MARGIN * 2;
650   double height = (max_y - min_y) * factor + SVG_MARGIN * 2;
651   fprintf(fh, "<svg version=\"1.1\" baseProfile=\"full\"\n"
652               "xmlns=\"http://www.w3.org/2000/svg\"\n"
653               "xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
654               "xmlns:ev=\"http://www.w3.org/2001/xml-events\"\n"
655               "width=\"%.3f%s\" height=\"%.3f%s\"\n"
656               "viewBox=\"0 0 %0.3f %0.3f\">\n",
657           width, unit, height, unit, width, height);
658   if (title && title[0]) {
659       fputs("<title>", fh);
660       html_escape(fh, title);
661       fputs("</title>\n", fh);
662   }
663   fprintf(fh, "<g transform=\"translate(%.3f %.3f)\">\n",
664           SVG_MARGIN - min_x * factor, SVG_MARGIN + max_y * factor);
665   to_close = NULL;
666   close_g = false;
667}
668
669void
670SVG::start_pass(int layer)
671{
672   if (to_close) {
673      fputs(to_close, fh);
674      to_close = NULL;
675   }
676   if (close_g) {
677      fprintf(fh, "</g>\n");
678   }
679   fprintf(fh, "<g id=\"%s\"", layer_name(layer));
680   if (layer & LEGS)
681      fprintf(fh, " stroke=\"black\" fill=\"none\" stroke-width=\"0.4px\"");
682   else if (layer & STNS)
683      fprintf(fh, " stroke=\"black\" fill=\"none\" stroke-width=\"0.05px\"");
684   else if (layer & LABELS)
685      fprintf(fh, " font-size=\"%.3fem\"", text_height);
686   else if (layer & XSECT)
687      fprintf(fh, " stroke=\"grey\" fill=\"none\" stroke-width=\"0.1px\"");
688   else if (layer & WALLS)
689      fprintf(fh, " stroke=\"black\" fill=\"none\" stroke-width=\"0.1px\"");
690   else if (layer & PASG)
691      fprintf(fh, " stroke=\"none\" fill=\"peru\"");
692   fprintf(fh, ">\n");
693
694   close_g = true;
695}
696
697void
698SVG::line(const img_point *p1, const img_point *p, unsigned flags, bool fPendingMove)
699{
700   bool splay = (flags & SPLAYS);
701   if (fPendingMove) {
702       if (to_close) {
703           fputs(to_close, fh);
704       }
705       fprintf(fh, "<path ");
706       if (splay) fprintf(fh, "stroke=\"grey\" stroke-width=\"0.1px\" ");
707       fprintf(fh, "d=\"M%.3f %.3f", p1->x * factor, p1->y * -factor);
708   }
709   fprintf(fh, "L%.3f %.3f", p->x * factor, p->y * -factor);
710   to_close = "\"/>\n";
711}
712
713void
714SVG::label(const img_point *p, const char *s, bool fSurface, int)
715{
716   (void)fSurface; /* unused */
717   fprintf(fh, "<text transform=\"translate(%.3f %.3f)\">",
718           p->x * factor, p->y * -factor);
719   html_escape(fh, s);
720   fputs("</text>\n", fh);
721   set_name(p, s);
722}
723
724void
725SVG::cross(const img_point *p, bool fSurface)
726{
727   (void)fSurface; /* unused */
728   fprintf(fh, "<circle id=\"%s\" cx=\"%.3f\" cy=\"%.3f\" r=\"%.3f\"/>\n",
729           find_name(p), p->x * factor, p->y * -factor, marker_size * SQRT_2);
730   fprintf(fh, "<path d=\"M%.3f %.3fL%.3f %.3fM%.3f %.3fL%.3f %.3f\"/>\n",
731           p->x * factor - marker_size, p->y * -factor - marker_size,
732           p->x * factor + marker_size, p->y * -factor + marker_size,
733           p->x * factor + marker_size, p->y * -factor - marker_size,
734           p->x * factor - marker_size, p->y * -factor + marker_size);
735}
736
737void
738SVG::xsect(const img_point *p, double angle, double d1, double d2)
739{
740   double s = sin(rad(angle));
741   double c = cos(rad(angle));
742   fprintf(fh, "<path d=\"M%.3f %.3fL%.3f %.3f\"/>\n",
743           (p->x + c * d1) * factor, (p->y + s * d1) * -factor,
744           (p->x - c * d2) * factor, (p->y - s * d2) * -factor);
745}
746
747void
748SVG::wall(const img_point *p, double angle, double d)
749{
750   if (!to_close) {
751       fprintf(fh, "<path d=\"M");
752       to_close = "\"/>\n";
753   } else {
754       fprintf(fh, "L");
755   }
756   double s = sin(rad(angle));
757   double c = cos(rad(angle));
758   fprintf(fh, "%.3f %.3f", (p->x + c * d) * factor, (p->y + s * d) * -factor);
759}
760
761void
762SVG::passage(const img_point *p, double angle, double d1, double d2)
763{
764   double s = sin(rad(angle));
765   double c = cos(rad(angle));
766   double x1 = (p->x + c * d1) * factor;
767   double y1 = (p->y + s * d1) * -factor;
768   double x2 = (p->x - c * d2) * factor;
769   double y2 = (p->y - s * d2) * -factor;
770   if (*pending) {
771       fputs(pending, fh);
772       fprintf(fh, "L%.3f %.3fL%.3f %.3fZ\"/>\n", x2, y2, x1, y1);
773   }
774   sprintf(pending, "<path d=\"M%.3f %.3fL%.3f %.3f", x1, y1, x2, y2);
775}
776
777void
778SVG::tube_end()
779{
780   *pending = '\0';
781   if (to_close) {
782      fputs(to_close, fh);
783      to_close = NULL;
784   }
785}
786
787void
788SVG::footer()
789{
790   if (to_close) {
791      fputs(to_close, fh);
792      to_close = NULL;
793   }
794   if (close_g) {
795      fprintf(fh, "</g>\n");
796      close_g = false;
797   }
798   fprintf(fh, "</g>\n</svg>\n");
799}
800
801class PLT : public ExportFilter {
802    string escaped;
803
804    const char * find_name_plt(const img_point *p);
805
806    double min_N, max_N, min_E, max_E, min_A, max_A;
807
808  public:
809    PLT() { }
810    const int * passes() const;
811    void header(const char *, const char *, time_t,
812                double min_x, double min_y, double min_z,
813                double max_x, double max_y, double max_z);
814    void line(const img_point *, const img_point *, unsigned, bool);
815    void label(const img_point *, const char *, bool, int);
816    void footer();
817};
818
819const int *
820PLT::passes() const
821{
822    static const int plt_passes[] = { LABELS, LEGS|SURF, 0 };
823    return plt_passes;
824}
825
826void
827PLT::header(const char *title, const char *, time_t,
828            double min_x, double min_y, double min_z,
829            double max_x, double max_y, double max_z)
830{
831   // FIXME: allow survey to be set from aven somehow!
832   const char *survey = NULL;
833   htab = (point **)osmalloc(HTAB_SIZE * ossizeof(point *));
834   for (size_t i = 0; i < HTAB_SIZE; ++i) htab[i] = NULL;
835   /* Survex is E, N, Alt - PLT file is N, E, Alt */
836   min_N = min_y / METRES_PER_FOOT;
837   max_N = max_y / METRES_PER_FOOT;
838   min_E = min_x / METRES_PER_FOOT;
839   max_E = max_x / METRES_PER_FOOT;
840   min_A = min_z / METRES_PER_FOOT;
841   max_A = max_z / METRES_PER_FOOT;
842   fprintf(fh, "Z %.3f %.3f %.3f %.3f %.3f %.3f\r\n",
843           min_N, max_N, min_E, max_E, min_A, max_A);
844   fprintf(fh, "N%s D 1 1 1 C%s\r\n", survey ? survey : "X",
845           (title && title[0]) ? title : "X");
846}
847
848void
849PLT::line(const img_point *p1, const img_point *p, unsigned flags, bool fPendingMove)
850{
851   (void)flags; /* unused */
852   if (fPendingMove) {
853       /* Survex is E, N, Alt - PLT file is N, E, Alt */
854       fprintf(fh, "M %.3f %.3f %.3f ",
855               p1->y / METRES_PER_FOOT, p1->x / METRES_PER_FOOT, p1->z / METRES_PER_FOOT);
856       /* dummy passage dimensions are required to avoid compass bug */
857       fprintf(fh, "S%s P -9 -9 -9 -9\r\n", find_name_plt(p1));
858   }
859   /* Survex is E, N, Alt - PLT file is N, E, Alt */
860   fprintf(fh, "D %.3f %.3f %.3f ",
861           p->y / METRES_PER_FOOT, p->x / METRES_PER_FOOT, p->z / METRES_PER_FOOT);
862   /* dummy passage dimensions are required to avoid compass bug */
863   fprintf(fh, "S%s P -9 -9 -9 -9\r\n", find_name_plt(p));
864}
865
866const char *
867PLT::find_name_plt(const img_point *p)
868{
869    const char * s = find_name(p);
870    escaped.resize(0);
871
872    // PLT format can't handle spaces or control characters, so escape them
873    // like in URLs (an arbitrary choice of escaping, but at least a familiar
874    // one and % isn't likely to occur in station names).
875    const char * q;
876    for (q = s; *q; ++q) {
877        unsigned char ch = *q;
878        if (ch <= ' ' || ch == '%') {
879            escaped.append(s, q - s);
880            escaped += '%';
881            escaped += "0123456789abcdef"[ch >> 4];
882            escaped += "0123456789abcdef"[ch & 0x0f];
883            s = q + 1;
884        }
885    }
886    if (!escaped.empty()) {
887        escaped.append(s, q - s);
888        return escaped.c_str();
889    }
890    return s;
891}
892
893void
894PLT::label(const img_point *p, const char *s, bool fSurface, int)
895{
896   (void)fSurface; /* unused */
897   set_name(p, s);
898}
899
900void
901PLT::footer(void)
902{
903   /* Survex is E, N, Alt - PLT file is N, E, Alt */
904   fprintf(fh, "X %.3f %.3f %.3f %.3f %.3f %.3f\r\n",
905           min_N, max_N, min_E, max_E, min_A, max_A);
906   /* Yucky DOS "end of textfile" marker */
907   PUTC('\x1a', fh);
908}
909
910class EPS : public ExportFilter {
911    double factor;
912    bool first;
913    vector<pair<double, double>> psg;
914  public:
915    explicit EPS(double scale)
916        : factor(POINTS_PER_MM * 1000.0 / scale) { }
917    const int * passes() const;
918    void header(const char *, const char *, time_t,
919                double min_x, double min_y, double min_z,
920                double max_x, double max_y, double max_z);
921    void start_pass(int layer);
922    void line(const img_point *, const img_point *, unsigned, bool);
923    void label(const img_point *, const char *, bool, int);
924    void cross(const img_point *, bool);
925    void xsect(const img_point *, double, double, double);
926    void wall(const img_point *, double, double);
927    void passage(const img_point *, double, double, double);
928    void tube_end();
929    void footer();
930};
931
932const int *
933EPS::passes() const
934{
935    static const int eps_passes[] = {
936        PASG, XSECT, WALL1, WALL2, LEGS|SURF|STNS|LABELS, 0
937    };
938    return eps_passes;
939}
940
941void
942EPS::header(const char *title, const char *, time_t,
943            double min_x, double min_y, double /*min_z*/,
944            double max_x, double max_y, double /*max_z*/)
945{
946   const char * fontname_labels = "helvetica"; // FIXME
947   int fontsize_labels = 10; // FIXME
948   fputs("%!PS-Adobe-2.0 EPSF-1.2\n", fh);
949   fputs("%%Creator: Survex " VERSION " EPS Export Filter\n", fh);
950
951   if (title && title[0])
952       fprintf(fh, "%%%%Title: %s\n", title);
953
954   char buf[64];
955   time_t now = time(NULL);
956   if (strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S %Z\n", localtime(&now))) {
957      fputs("%%CreationDate: ", fh);
958      fputs(buf, fh);
959   }
960
961   string name;
962#if defined(HAVE_GETPWUID) && !defined(__DJGPP__)
963   struct passwd * ent = getpwuid(getuid());
964   if (ent && ent->pw_gecos[0]) name = ent->pw_gecos;
965#endif
966   if (name.empty()) {
967       name = ::wxGetUserName().mb_str();
968       if (name.empty()) {
969           name = ::wxGetUserId().mb_str();
970       }
971   }
972   if (!name.empty()) {
973       fprintf(fh, "%%%%For: %s\n", name.c_str());
974   }
975
976   fprintf(fh, "%%%%BoundingBox: %d %d %d %d\n",
977           int(floor(min_x * factor)), int(floor(min_y * factor)),
978           int(ceil(max_x * factor)), int(ceil(max_y * factor)));
979   fprintf(fh, "%%%%HiResBoundingBox: %.4f %.4f %.4f %.4f\n",
980           min_x * factor, min_y * factor, max_x * factor, max_y * factor);
981   fputs("%%LanguageLevel: 1\n"
982         "%%PageOrder: Ascend\n"
983         "%%Pages: 1\n"
984         "%%Orientation: Portrait\n", fh);
985
986   fprintf(fh, "%%%%DocumentFonts: %s\n", fontname_labels);
987
988   fputs("%%EndComments\n"
989         "%%Page 1 1\n"
990         "save countdictstack mark\n", fh);
991
992   /* this code adapted from a2ps */
993   fputs("%%BeginResource: encoding ISO88591Encoding\n"
994         "/ISO88591Encoding [\n", fh);
995   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
996   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
997   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
998   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
999   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1000   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1001   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1002   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1003   fputs(
1004"/space /exclam /quotedbl /numbersign\n"
1005"/dollar /percent /ampersand /quoteright\n"
1006"/parenleft /parenright /asterisk /plus\n"
1007"/comma /minus /period /slash\n"
1008"/zero /one /two /three\n"
1009"/four /five /six /seven\n"
1010"/eight /nine /colon /semicolon\n"
1011"/less /equal /greater /question\n"
1012"/at /A /B /C /D /E /F /G\n"
1013"/H /I /J /K /L /M /N /O\n"
1014"/P /Q /R /S /T /U /V /W\n"
1015"/X /Y /Z /bracketleft\n"
1016"/backslash /bracketright /asciicircum /underscore\n"
1017"/quoteleft /a /b /c /d /e /f /g\n"
1018"/h /i /j /k /l /m /n /o\n"
1019"/p /q /r /s /t /u /v /w\n"
1020"/x /y /z /braceleft\n"
1021"/bar /braceright /asciitilde /.notdef\n", fh);
1022   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1023   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1024   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1025   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1026   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1027   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1028   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1029   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1030   fputs(
1031"/space /exclamdown /cent /sterling\n"
1032"/currency /yen /brokenbar /section\n"
1033"/dieresis /copyright /ordfeminine /guillemotleft\n"
1034"/logicalnot /hyphen /registered /macron\n"
1035"/degree /plusminus /twosuperior /threesuperior\n"
1036"/acute /mu /paragraph /bullet\n"
1037"/cedilla /onesuperior /ordmasculine /guillemotright\n"
1038"/onequarter /onehalf /threequarters /questiondown\n"
1039"/Agrave /Aacute /Acircumflex /Atilde\n"
1040"/Adieresis /Aring /AE /Ccedilla\n"
1041"/Egrave /Eacute /Ecircumflex /Edieresis\n"
1042"/Igrave /Iacute /Icircumflex /Idieresis\n"
1043"/Eth /Ntilde /Ograve /Oacute\n"
1044"/Ocircumflex /Otilde /Odieresis /multiply\n"
1045"/Oslash /Ugrave /Uacute /Ucircumflex\n"
1046"/Udieresis /Yacute /Thorn /germandbls\n"
1047"/agrave /aacute /acircumflex /atilde\n"
1048"/adieresis /aring /ae /ccedilla\n"
1049"/egrave /eacute /ecircumflex /edieresis\n"
1050"/igrave /iacute /icircumflex /idieresis\n"
1051"/eth /ntilde /ograve /oacute\n"
1052"/ocircumflex /otilde /odieresis /divide\n"
1053"/oslash /ugrave /uacute /ucircumflex\n"
1054"/udieresis /yacute /thorn /ydieresis\n"
1055"] def\n"
1056"%%EndResource\n", fh);
1057
1058   /* this code adapted from a2ps */
1059   fputs(
1060"/reencode {\n" /* def */
1061"dup length 5 add dict begin\n"
1062"{\n" /* forall */
1063"1 index /FID ne\n"
1064"{ def }{ pop pop } ifelse\n"
1065"} forall\n"
1066"/Encoding exch def\n"
1067
1068/* Use the font's bounding box to determine the ascent, descent,
1069 * and overall height; don't forget that these values have to be
1070 * transformed using the font's matrix.
1071 * We use `load' because sometimes BBox is executable, sometimes not.
1072 * Since we need 4 numbers and not an array avoid BBox from being executed
1073 */
1074"/FontBBox load aload pop\n"
1075"FontMatrix transform /Ascent exch def pop\n"
1076"FontMatrix transform /Descent exch def pop\n"
1077"/FontHeight Ascent Descent sub def\n"
1078
1079/* Define these in case they're not in the FontInfo (also, here
1080 * they're easier to get to.
1081 */
1082"/UnderlinePosition 1 def\n"
1083"/UnderlineThickness 1 def\n"
1084
1085/* Get the underline position and thickness if they're defined. */
1086"currentdict /FontInfo known {\n"
1087"FontInfo\n"
1088
1089"dup /UnderlinePosition known {\n"
1090"dup /UnderlinePosition get\n"
1091"0 exch FontMatrix transform exch pop\n"
1092"/UnderlinePosition exch def\n"
1093"} if\n"
1094
1095"dup /UnderlineThickness known {\n"
1096"/UnderlineThickness get\n"
1097"0 exch FontMatrix transform exch pop\n"
1098"/UnderlineThickness exch def\n"
1099"} if\n"
1100
1101"} if\n"
1102"currentdict\n"
1103"end\n"
1104"} bind def\n", fh);
1105
1106   fprintf(fh, "/lab ISO88591Encoding /%s findfont reencode definefont pop\n",
1107           fontname_labels);
1108
1109   fprintf(fh, "/lab findfont %d scalefont setfont\n", int(fontsize_labels));
1110
1111#if 0
1112   /* C<digit> changes colour */
1113   /* FIXME: read from ini */
1114   {
1115      size_t i;
1116      for (i = 0; i < sizeof(colour) / sizeof(colour[0]); ++i) {
1117         fprintf(fh, "/C%u {stroke %.3f %.3f %.3f setrgbcolor} def\n", i,
1118                 (double)(colour[i] & 0xff0000) / 0xff0000,
1119                 (double)(colour[i] & 0xff00) / 0xff00,
1120                 (double)(colour[i] & 0xff) / 0xff);
1121      }
1122   }
1123   fputs("C0\n", fh);
1124#endif
1125
1126   /* Postscript definition for drawing a cross */
1127   fprintf(fh, "/X {stroke moveto %.2f %.2f rmoveto %.2f %.2f rlineto "
1128           "%.2f 0 rmoveto %.2f %.2f rlineto %.2f %.2f rmoveto} def\n",
1129           -marker_size, -marker_size,  marker_size * 2, marker_size * 2,
1130           -marker_size * 2,  marker_size * 2, -marker_size * 2,
1131           -marker_size, marker_size );
1132
1133   /* define some functions to keep file short */
1134   fputs("/M {stroke moveto} def\n"
1135         "/P {stroke newpath moveto} def\n"
1136         "/F {closepath gsave 0.8 setgray fill grestore} def\n"
1137         "/L {lineto} def\n"
1138         "/R {rlineto} def\n"
1139         "/S {show} def\n", fh);
1140
1141   fprintf(fh, "gsave %.8f dup scale\n", factor);
1142#if 0
1143   if (grid > 0) {
1144      double x, y;
1145      x = floor(min_x / grid) * grid + grid;
1146      y = floor(min_y / grid) * grid + grid;
1147      while (x < max_x) {
1148         /* horizontal line */
1149         fprintf(fh, "0\nLINE\n");
1150         fprintf(fh, "8\nGrid\n"); /* Layer */
1151         fprintf(fh, "10\n%6.2f\n", x);
1152         fprintf(fh, "20\n%6.2f\n", min_y);
1153         fprintf(fh, "30\n0\n");
1154         fprintf(fh, "11\n%6.2f\n", x);
1155         fprintf(fh, "21\n%6.2f\n", max_y);
1156         fprintf(fh, "31\n0\n");
1157         x += grid;
1158      }
1159      while (y < max_y) {
1160         /* vertical line */
1161         fprintf(fh, "0\nLINE\n");
1162         fprintf(fh, "8\nGrid\n"); /* Layer */
1163         fprintf(fh, "10\n%6.2f\n", min_x);
1164         fprintf(fh, "20\n%6.2f\n", y);
1165         fprintf(fh, "30\n0\n");
1166         fprintf(fh, "11\n%6.2f\n", max_x);
1167         fprintf(fh, "21\n%6.2f\n", y);
1168         fprintf(fh, "31\n0\n");
1169         y += grid;
1170      }
1171   }
1172#endif
1173}
1174
1175void
1176EPS::start_pass(int layer)
1177{
1178    first = true;
1179    switch (layer) {
1180        case LEGS|SURF|STNS|LABELS:
1181            fprintf(fh, "0.1 setlinewidth\n");
1182            break;
1183        case PASG: case XSECT: case WALL1: case WALL2:
1184            fprintf(fh, "0.01 setlinewidth\n");
1185            break;
1186    }
1187}
1188
1189void
1190EPS::line(const img_point *p1, const img_point *p, unsigned flags, bool fPendingMove)
1191{
1192   (void)flags; /* unused */
1193   if (fPendingMove) {
1194       fprintf(fh, "%.2f %.2f M\n", p1->x, p1->y);
1195   }
1196   fprintf(fh, "%.2f %.2f L\n", p->x, p->y);
1197}
1198
1199void
1200EPS::label(const img_point *p, const char *s, bool /*fSurface*/, int)
1201{
1202   fprintf(fh, "%.2f %.2f M\n", p->x, p->y);
1203   PUTC('(', fh);
1204   while (*s) {
1205       unsigned char ch = *s++;
1206       switch (ch) {
1207           case '(': case ')': case '\\': /* need to escape these characters */
1208               PUTC('\\', fh);
1209               PUTC(ch, fh);
1210               break;
1211           default:
1212               PUTC(ch, fh);
1213               break;
1214       }
1215   }
1216   fputs(") S\n", fh);
1217}
1218
1219void
1220EPS::cross(const img_point *p, bool fSurface)
1221{
1222   (void)fSurface; /* unused */
1223   fprintf(fh, "%.2f %.2f X\n", p->x, p->y);
1224}
1225
1226void
1227EPS::xsect(const img_point *p, double angle, double d1, double d2)
1228{
1229    double s = sin(rad(angle));
1230    double c = cos(rad(angle));
1231    fprintf(fh, "%.2f %.2f M %.2f %.2f R\n",
1232            p->x - c * d2, p->y - s * d2,
1233            c * (d1 + d2), s * (d1 + d2));
1234}
1235
1236void
1237EPS::wall(const img_point *p, double angle, double d)
1238{
1239    double s = sin(rad(angle));
1240    double c = cos(rad(angle));
1241    fprintf(fh, "%.2f %.2f %c\n", p->x + c * d, p->y + s * d, first ? 'M' : 'L');
1242    first = false;
1243}
1244
1245void
1246EPS::passage(const img_point *p, double angle, double d1, double d2)
1247{
1248    double s = sin(rad(angle));
1249    double c = cos(rad(angle));
1250    double x1 = p->x + c * d1;
1251    double y1 = p->y + s * d1;
1252    double x2 = p->x - c * d2;
1253    double y2 = p->y - s * d2;
1254    fprintf(fh, "%.2f %.2f %c\n", x1, y1, first ? 'P' : 'L');
1255    first = false;
1256    psg.push_back(make_pair(x2, y2));
1257}
1258
1259void
1260EPS::tube_end()
1261{
1262    if (!psg.empty()) {
1263        vector<pair<double, double>>::const_reverse_iterator i;
1264        for (i = psg.rbegin(); i != psg.rend(); ++i) {
1265            fprintf(fh, "%.2f %.2f L\n", i->first, i->second);
1266        }
1267        fputs("F\n", fh);
1268        psg.clear();
1269    }
1270}
1271
1272void
1273EPS::footer(void)
1274{
1275   fputs("stroke showpage grestore\n"
1276         "%%Trailer\n"
1277         "cleartomark countdictstack exch sub { end } repeat restore\n"
1278         "%%EOF\n", fh);
1279}
1280
1281class UseNumericCLocale {
1282    char * current_locale;
1283
1284  public:
1285    UseNumericCLocale() {
1286        current_locale = osstrdup(setlocale(LC_NUMERIC, NULL));
1287        setlocale(LC_NUMERIC, "C");
1288    }
1289
1290    ~UseNumericCLocale() {
1291        setlocale(LC_NUMERIC, current_locale);
1292        osfree(current_locale);
1293    }
1294};
1295
1296static void
1297transform_point(const Point& pos, const Vector3* pre_offset,
1298                double COS, double SIN, double COST, double SINT,
1299                img_point* p)
1300{
1301    double x = pos.GetX();
1302    double y = pos.GetY();
1303    double z = pos.GetZ();
1304    if (pre_offset) {
1305        x += pre_offset->GetX();
1306        y += pre_offset->GetY();
1307        z += pre_offset->GetZ();
1308    }
1309    p->x = x * COS - y * SIN;
1310    double tmp = x * SIN + y * COS;
1311    p->y = z * COST - tmp * SINT;
1312    p->z = -(z * SINT + tmp * COST);
1313}
1314
1315bool
1316Export(const wxString &fnm_out, const wxString &title,
1317       const wxString &datestamp,
1318       const Model& model,
1319       const SurveyFilter* filter,
1320       double pan, double tilt, int show_mask, export_format format,
1321       double grid_, double text_height, double marker_size_,
1322       double scale)
1323{
1324   UseNumericCLocale dummy;
1325   int fPendingMove = 0;
1326   img_point p, p1;
1327   const int *pass;
1328   double SIN = sin(rad(pan));
1329   double COS = cos(rad(pan));
1330   double SINT = sin(rad(tilt));
1331   double COST = cos(rad(tilt));
1332
1333   grid = grid_;
1334   marker_size = marker_size_;
1335
1336   // Do we need to calculate min and max for each dimension?
1337   bool need_bounds = true;
1338   ExportFilter * filt;
1339   switch (format) {
1340       case FMT_DXF:
1341           filt = new DXF(text_height);
1342           break;
1343       case FMT_EPS:
1344           filt = new EPS(scale);
1345           break;
1346       case FMT_GPX:
1347           filt = new GPX(model.GetCSProj().c_str());
1348           show_mask |= FULL_COORDS;
1349           need_bounds = false;
1350           break;
1351       case FMT_HPGL:
1352           filt = new HPGL;
1353           // factor = POINTS_PER_MM * 1000.0 / scale;
1354           // HPGL doesn't use the bounds itself, but they are needed to set
1355           // the origin to the centre of lower left.
1356           break;
1357       case FMT_JSON:
1358           filt = new JSON;
1359           break;
1360       case FMT_KML: {
1361           bool clamp_to_ground = (show_mask & CLAMP_TO_GROUND);
1362           filt = new KML(model.GetCSProj().c_str(), clamp_to_ground);
1363           show_mask |= FULL_COORDS;
1364           need_bounds = false;
1365           break;
1366       }
1367       case FMT_PLT:
1368           filt = new PLT;
1369           show_mask |= FULL_COORDS;
1370           break;
1371       case FMT_POS:
1372           filt = new POS(model.GetSeparator());
1373           show_mask |= FULL_COORDS;
1374           need_bounds = false;
1375           break;
1376       case FMT_SK:
1377           filt = new Skencil(scale);
1378           break;
1379       case FMT_SVG:
1380           filt = new SVG(scale, text_height);
1381           break;
1382       default:
1383           return false;
1384   }
1385
1386   if (!filt->fopen(fnm_out)) {
1387       delete filt;
1388       return false;
1389   }
1390
1391   const Vector3* pre_offset = NULL;
1392   if (show_mask & FULL_COORDS) {
1393        pre_offset = &(model.GetOffset());
1394   }
1395
1396   /* Get bounding box */
1397   double min_x, min_y, min_z, max_x, max_y, max_z;
1398   min_x = min_y = min_z = HUGE_VAL;
1399   max_x = max_y = max_z = -HUGE_VAL;
1400   if (need_bounds) {
1401        for (int f = 0; f != 8; ++f) {
1402            if ((show_mask & (f & img_FLAG_SURFACE) ? SURF : LEGS) == 0) {
1403                // Not showing traverse because of surface/underground status.
1404                continue;
1405            }
1406            if ((f & img_FLAG_SPLAY) && (show_mask & SPLAYS) == 0) {
1407                // Not showing because it's a splay.
1408                continue;
1409            }
1410            list<traverse>::const_iterator trav = model.traverses_begin(f, filter);
1411            list<traverse>::const_iterator tend = model.traverses_end(f);
1412            for ( ; trav != tend; trav = model.traverses_next(f, filter, trav)) {
1413                vector<PointInfo>::const_iterator pos = trav->begin();
1414                vector<PointInfo>::const_iterator end = trav->end();
1415                for ( ; pos != end; ++pos) {
1416                    transform_point(*pos, pre_offset, COS, SIN, COST, SINT, &p);
1417
1418                    if (p.x < min_x) min_x = p.x;
1419                    if (p.x > max_x) max_x = p.x;
1420                    if (p.y < min_y) min_y = p.y;
1421                    if (p.y > max_y) max_y = p.y;
1422                    if (p.z < min_z) min_z = p.z;
1423                    if (p.z > max_z) max_z = p.z;
1424                }
1425            }
1426        }
1427        list<LabelInfo*>::const_iterator pos = model.GetLabels();
1428        list<LabelInfo*>::const_iterator end = model.GetLabelsEnd();
1429        for ( ; pos != end; ++pos) {
1430            if (filter && !filter->CheckVisible((*pos)->GetText()))
1431                continue;
1432
1433            transform_point(**pos, pre_offset, COS, SIN, COST, SINT, &p);
1434
1435            if (p.x < min_x) min_x = p.x;
1436            if (p.x > max_x) max_x = p.x;
1437            if (p.y < min_y) min_y = p.y;
1438            if (p.y > max_y) max_y = p.y;
1439            if (p.z < min_z) min_z = p.z;
1440            if (p.z > max_z) max_z = p.z;
1441        }
1442
1443        if (grid > 0) {
1444            min_x -= grid / 2;
1445            max_x += grid / 2;
1446            min_y -= grid / 2;
1447            max_y += grid / 2;
1448        }
1449   }
1450
1451   /* Handle empty file and gracefully, and also zero for the !need_bounds
1452    * case. */
1453   if (min_x > max_x) {
1454      min_x = min_y = min_z = 0;
1455      max_x = max_y = max_z = 0;
1456   }
1457
1458   double x_offset, y_offset, z_offset;
1459   if (show_mask & FULL_COORDS) {
1460       // Full coordinates - offset is applied before rotations.
1461       x_offset = y_offset = z_offset = 0.0;
1462   } else if (show_mask & CENTRED) {
1463       // Centred.
1464       x_offset = (min_x + max_x) * -0.5;
1465       y_offset = (min_y + max_y) * -0.5;
1466       z_offset = (min_z + max_z) * -0.5;
1467   } else {
1468       // Origin at lowest SW corner.
1469       x_offset = -min_x;
1470       y_offset = -min_y;
1471       z_offset = -min_z;
1472   }
1473   if (need_bounds) {
1474        min_x += x_offset;
1475        max_x += x_offset;
1476        min_y += y_offset;
1477        max_y += y_offset;
1478        min_z += z_offset;
1479        max_z += z_offset;
1480   }
1481
1482   /* Header */
1483   filt->header(title.utf8_str(), datestamp.utf8_str(), model.GetDateStamp(),
1484                min_x, min_y, min_z, max_x, max_y, max_z);
1485
1486   p1.x = p1.y = p1.z = 0; /* avoid compiler warning */
1487
1488   for (pass = filt->passes(); *pass; ++pass) {
1489      int pass_mask = show_mask & *pass;
1490      if (!pass_mask)
1491          continue;
1492      filt->start_pass(*pass);
1493      if (pass_mask & (LEGS|SURF)) {
1494          for (int f = 0; f != 8; ++f) {
1495              unsigned flags = (f & img_FLAG_SURFACE) ? SURF : LEGS;
1496              if ((pass_mask & flags) == 0) {
1497                  // Not showing traverse because of surface/underground status.
1498                  continue;
1499              }
1500              if (f & img_FLAG_SPLAY) flags |= SPLAYS;
1501              list<traverse>::const_iterator trav = model.traverses_begin(f, filter);
1502              list<traverse>::const_iterator tend = model.traverses_end(f);
1503              for ( ; trav != tend; trav = model.traverses_next(f, filter, trav)) {
1504                  assert(trav->size() > 1);
1505                  vector<PointInfo>::const_iterator pos = trav->begin();
1506                  vector<PointInfo>::const_iterator end = trav->end();
1507                  for ( ; pos != end; ++pos) {
1508                      transform_point(*pos, pre_offset, COS, SIN, COST, SINT, &p);
1509                      p.x += x_offset;
1510                      p.y += y_offset;
1511                      p.z += z_offset;
1512
1513                      if (pos == trav->begin()) {
1514                          // First point is move...
1515                          fPendingMove = 1;
1516                      } else {
1517                          filt->line(&p1, &p, flags, fPendingMove);
1518                          fPendingMove = 0;
1519                      }
1520                      p1 = p;
1521                  }
1522              }
1523          }
1524      }
1525      if (pass_mask & (STNS|LABELS|ENTS|FIXES|EXPORTS)) {
1526          list<LabelInfo*>::const_iterator pos = model.GetLabels();
1527          list<LabelInfo*>::const_iterator end = model.GetLabelsEnd();
1528          for ( ; pos != end; ++pos) {
1529              if (filter && !filter->CheckVisible((*pos)->GetText()))
1530                  continue;
1531
1532              transform_point(**pos, pre_offset, COS, SIN, COST, SINT, &p);
1533              p.x += x_offset;
1534              p.y += y_offset;
1535              p.z += z_offset;
1536
1537              int type = 0;
1538              if ((pass_mask & ENTS) && (*pos)->IsEntrance()) {
1539                  type = ENTS;
1540              } else if ((pass_mask & FIXES) && (*pos)->IsFixedPt()) {
1541                  type = FIXES;
1542              } else if ((pass_mask & EXPORTS) && (*pos)->IsExportedPt())  {
1543                  type = EXPORTS;
1544              } else if (pass_mask & LABELS) {
1545                  type = LABELS;
1546              }
1547              /* Use !UNDERGROUND as the criterion - we want stations where a
1548               * surface and underground survey meet to be in the underground
1549               * layer */
1550              bool f_surface = !(*pos)->IsUnderground();
1551              if (type) {
1552                  const wxString & text = (*pos)->GetText();
1553                  filt->label(&p, text.utf8_str(), f_surface, type);
1554              }
1555              if (pass_mask & STNS)
1556                  filt->cross(&p, f_surface);
1557          }
1558      }
1559      if (pass_mask & (XSECT|WALLS|PASG)) {
1560          bool elevation = (tilt == 0.0);
1561          list<vector<XSect>>::const_iterator tube = model.tubes_begin();
1562          list<vector<XSect>>::const_iterator tube_end = model.tubes_end();
1563          for ( ; tube != tube_end; ++tube) {
1564              vector<XSect>::const_iterator pos = tube->begin();
1565              vector<XSect>::const_iterator end = tube->end();
1566              size_t active_tube_len = 0;
1567              for ( ; pos != end; ++pos) {
1568                  const XSect & xs = *pos;
1569                  // FIXME: This filtering can create tubes containing a single
1570                  // cross-section, which otherwise don't exist in aven (the
1571                  // Model class currently filters them out).  Perhaps we
1572                  // should just always include these - a single set of LRUD
1573                  // measurements is useful even if a single cross-section
1574                  // 3D tube perhaps isn't.
1575                  if (filter && !filter->CheckVisible(xs.GetLabel())) {
1576                      // Close any active tube.
1577                      if (active_tube_len > 0) {
1578                          active_tube_len = 0;
1579                          filt->tube_end();
1580                      }
1581                      continue;
1582                  }
1583
1584                  ++active_tube_len;
1585                  transform_point(xs.GetPoint(), pre_offset, COS, SIN, COST, SINT, &p);
1586                  p.x += x_offset;
1587                  p.y += y_offset;
1588                  p.z += z_offset;
1589
1590                  if (elevation) {
1591                      if (pass_mask & XSECT)
1592                          filt->xsect(&p, 90, xs.GetU(), xs.GetD());
1593                      if (pass_mask & WALL1)
1594                          filt->wall(&p, 90, xs.GetU());
1595                      if (pass_mask & WALL2)
1596                          filt->wall(&p, 270, xs.GetD());
1597                      if (pass_mask & PASG)
1598                          filt->passage(&p, 90, xs.GetU(), xs.GetD());
1599                  } else {
1600                      // Should only be enabled in plan or elevation mode.
1601                      double angle = pan + xs.get_right_bearing();
1602                      if (pass_mask & XSECT)
1603                          filt->xsect(&p, angle + 180, xs.GetL(), xs.GetR());
1604                      if (pass_mask & WALL1)
1605                          filt->wall(&p, angle + 180, xs.GetL());
1606                      if (pass_mask & WALL2)
1607                          filt->wall(&p, angle, xs.GetR());
1608                      if (pass_mask & PASG)
1609                          filt->passage(&p, angle + 180, xs.GetL(), xs.GetR());
1610                  }
1611              }
1612              if (active_tube_len > 0) {
1613                  filt->tube_end();
1614              }
1615          }
1616      }
1617   }
1618   filt->footer();
1619   delete filt;
1620   osfree(htab);
1621   htab = NULL;
1622   return true;
1623}
Note: See TracBrowser for help on using the repository browser.