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