source: git/src/gpx.cc @ 04078a7

faster-cavernlogwalls-datawalls-data-hanging-as-warning
Last change on this file since 04078a7 was 8c4cefb, checked in by Olly Betts <olly@…>, 2 months ago

Pass station name to export code as wxString

This means we don't force a conversion to UTF8 for formats where
the name isn't actually used, and also means we can pass the name
in to the cross method too without worrying about extra costs.

This fixes poor handling of equated stations in SVG export where
previously we'd write out the same station name for each equated
station. SVG export is also more efficient than before.

  • Property mode set to 100644
File size: 5.6 KB
Line 
1/* gpx.cc
2 * Export from Aven as GPX.
3 */
4/* Copyright (C) 2012 Olaf Kähler
5 * Copyright (C) 2012,2013,2014,2015,2016 Olly Betts
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
20 */
21
22#ifdef HAVE_CONFIG_H
23# include <config.h>
24#endif
25
26#include "gpx.h"
27
28#include "export.h" // For LABELS, etc
29
30#include <stdio.h>
31#include <string>
32#include <time.h>
33#include <math.h>
34
35#include "useful.h"
36#include <proj.h>
37
38#include "aven.h"
39#include "message.h"
40
41using namespace std;
42
43#define WGS84_DATUM_STRING "EPSG:4326"
44
45static void
46html_escape(FILE *fh, const char *s)
47{
48    while (*s) {
49        switch (*s) {
50            case '<':
51                fputs("&lt;", fh);
52                break;
53            case '>':
54                fputs("&gt;", fh);
55                break;
56            case '&':
57                fputs("&amp;", fh);
58                break;
59            default:
60                PUTC(*s, fh);
61        }
62        ++s;
63    }
64}
65
66static void discarding_proj_logger(void *, int, const char *) { }
67
68GPX::GPX(const char * input_datum)
69{
70    /* Prevent stderr spew from PROJ. */
71    proj_log_func(PJ_DEFAULT_CTX, nullptr, discarding_proj_logger);
72
73    pj = proj_create_crs_to_crs(PJ_DEFAULT_CTX,
74                                input_datum, WGS84_DATUM_STRING,
75                                NULL);
76
77    if (pj) {
78        // Normalise the output order so x is longitude and y latitude - by
79        // default new PROJ has them switched for EPSG:4326 which just seems
80        // confusing.
81        PJ* pj_norm = proj_normalize_for_visualization(PJ_DEFAULT_CTX, pj);
82        proj_destroy(pj);
83        pj = pj_norm;
84    }
85
86    if (!pj) {
87        wxString m = wmsg(/*Failed to initialise input coordinate system “%s”*/287);
88        m = wxString::Format(m.c_str(), input_datum);
89        throw m;
90    }
91}
92
93GPX::~GPX()
94{
95    if (pj)
96        proj_destroy(pj);
97    free((void*)trk_name);
98}
99
100const int *
101GPX::passes() const
102{
103    static const int default_passes[] = { LABELS|ENTS|FIXES|EXPORTS, LEGS|SURF, 0 };
104    return default_passes;
105}
106
107/* Initialise GPX routines. */
108void GPX::header(const char * title, const char *, time_t datestamp_numeric,
109                 double, double, double, double, double, double)
110{
111    fputs(
112"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
113"<gpx version=\"1.0\" creator=\"" PACKAGE_STRING " (aven) - https://survex.com/\""
114" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
115" xmlns=\"http://www.topografix.com/GPX/1/0\""
116" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0"
117" http://www.topografix.com/GPX/1/0/gpx.xsd\">\n", fh);
118    if (title) {
119        fputs("<name>", fh);
120        html_escape(fh, title);
121        fputs("</name>\n", fh);
122        trk_name = strdup(title);
123    }
124    if (datestamp_numeric != time_t(-1)) {
125        struct tm * tm = gmtime(&datestamp_numeric);
126        if (tm) {
127            char buf[32];
128            if (strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", tm)) {
129                fputs("<time>", fh);
130                fputs(buf, fh);
131                fputs("</time>\n", fh);
132            }
133        }
134    }
135    // FIXME: optional in GPX, but perhaps useful:
136    // <bounds minlat="..." minlon="..." maxlat="..." maxlon="..." />
137    // NB Not necessarily the same as the bounds in survex coords translated
138    // to WGS84 lat+long...
139}
140
141void
142GPX::line(const img_point *p1, const img_point *p, unsigned /*flags*/, bool fPendingMove)
143{
144    if (fPendingMove) {
145        if (in_trkseg) {
146            fputs("</trkseg><trkseg>\n", fh);
147        } else {
148            fputs("<trk>", fh);
149            if (trk_name) {
150                fputs("<name>", fh);
151                html_escape(fh, trk_name);
152                fputs("</name>", fh);
153            }
154            fputs("<trkseg>\n", fh);
155            in_trkseg = true;
156        }
157        PJ_COORD coord{{p1->x, p1->y, p1->z, HUGE_VAL}};
158        coord = proj_trans(pj, PJ_FWD, coord);
159        if (coord.xyzt.x == HUGE_VAL ||
160            coord.xyzt.y == HUGE_VAL ||
161            coord.xyzt.z == HUGE_VAL) {
162            // FIXME report errors
163        }
164        // %.8f is at worst just over 1mm.
165        fprintf(fh, "<trkpt lon=\"%.8f\" lat=\"%.8f\"><ele>%.2f</ele></trkpt>\n",
166                coord.xyzt.x,
167                coord.xyzt.y,
168                coord.xyzt.z);
169    }
170
171    PJ_COORD coord{{p->x, p->y, p->z, HUGE_VAL}};
172    coord = proj_trans(pj, PJ_FWD, coord);
173    if (coord.xyzt.x == HUGE_VAL ||
174        coord.xyzt.y == HUGE_VAL ||
175        coord.xyzt.z == HUGE_VAL) {
176        // FIXME report errors
177    }
178    // %.8f is at worst just over 1mm.
179    fprintf(fh, "<trkpt lon=\"%.8f\" lat=\"%.8f\"><ele>%.2f</ele></trkpt>\n",
180            coord.xyzt.x,
181            coord.xyzt.y,
182            coord.xyzt.z);
183}
184
185void
186GPX::label(const img_point *p, const wxString& str, bool /*fSurface*/, int type)
187{
188    const char* s = str.utf8_str();
189    PJ_COORD coord{{p->x, p->y, p->z, HUGE_VAL}};
190    coord = proj_trans(pj, PJ_FWD, coord);
191    if (coord.xyzt.x == HUGE_VAL ||
192        coord.xyzt.y == HUGE_VAL ||
193        coord.xyzt.z == HUGE_VAL) {
194        // FIXME report errors
195    }
196    // %.8f is at worst just over 1mm.
197    fprintf(fh, "<wpt lon=\"%.8f\" lat=\"%.8f\"><ele>%.2f</ele><name>",
198            coord.xyzt.x,
199            coord.xyzt.y,
200            coord.xyzt.z);
201    html_escape(fh, s);
202    fputs("</name>", fh);
203    // Add a "pin" symbol with colour matching what aven shows.
204    switch (type) {
205        case FIXES:
206            fputs("<sym>Pin, Red</sym>", fh);
207            break;
208        case EXPORTS:
209            fputs("<sym>Pin, Blue</sym>", fh);
210            break;
211        case ENTS:
212            fputs("<sym>Pin, Green</sym>", fh);
213            break;
214    }
215    fputs("</wpt>\n", fh);
216}
217
218void
219GPX::footer()
220{
221    if (in_trkseg)
222        fputs("</trkseg></trk>\n", fh);
223    fputs("</gpx>\n", fh);
224}
Note: See TracBrowser for help on using the repository browser.