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