Fix message references for inches and feet
[survex.git] / src / gpx.cc
bloba60eb297b00ab2bc1906c12f2bd602c314986291
1 /* gpx.cc
2 * Export from Aven as GPX.
3 */
4 /* Copyright (C) 2012 Olaf Kähler
5 * Copyright (C) 2012-2024 Olly Betts
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.
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.
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
22 #include <config.h>
24 #include "gpx.h"
26 #include "export.h" // For LABELS, etc
28 #include <stdio.h>
29 #include <string>
30 #include <time.h>
31 #include <math.h>
33 #include "useful.h"
34 #include <proj.h>
36 #include "aven.h"
37 #include "message.h"
39 using namespace std;
41 #define WGS84_DATUM_STRING "EPSG:4326"
43 static void
44 html_escape(FILE *fh, const char *s)
46 while (*s) {
47 switch (*s) {
48 case '<':
49 fputs("&lt;", fh);
50 break;
51 case '>':
52 fputs("&gt;", fh);
53 break;
54 case '&':
55 fputs("&amp;", fh);
56 break;
57 default:
58 PUTC(*s, fh);
60 ++s;
64 static void discarding_proj_logger(void *, int, const char *) { }
66 GPX::GPX(const char * input_datum)
68 /* Prevent stderr spew from PROJ. */
69 proj_log_func(PJ_DEFAULT_CTX, nullptr, discarding_proj_logger);
71 pj = proj_create_crs_to_crs(PJ_DEFAULT_CTX,
72 input_datum, WGS84_DATUM_STRING,
73 NULL);
75 if (pj) {
76 // Normalise the output order so x is longitude and y latitude - by
77 // default new PROJ has them switched for EPSG:4326 which just seems
78 // confusing.
79 PJ* pj_norm = proj_normalize_for_visualization(PJ_DEFAULT_CTX, pj);
80 proj_destroy(pj);
81 pj = pj_norm;
84 if (!pj) {
85 wxString m = wmsg(/*Failed to initialise input coordinate system “%s”*/287);
86 m = wxString::Format(m.c_str(), input_datum);
87 throw m;
91 GPX::~GPX()
93 if (pj)
94 proj_destroy(pj);
95 free((void*)trk_name);
98 const int *
99 GPX::passes() const
101 static const int default_passes[] = { LABELS|ENTS|FIXES|EXPORTS, LEGS|SURF, 0 };
102 return default_passes;
105 /* Initialise GPX routines. */
106 void GPX::header(const char * title, const char *, time_t datestamp_numeric,
107 double, double, double, double, double, double)
109 fputs(
110 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
111 "<gpx version=\"1.0\" creator=\"" PACKAGE_STRING " (aven) - https://survex.com/\""
112 " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
113 " xmlns=\"http://www.topografix.com/GPX/1/0\""
114 " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0"
115 " http://www.topografix.com/GPX/1/0/gpx.xsd\">\n", fh);
116 if (title) {
117 fputs("<name>", fh);
118 html_escape(fh, title);
119 fputs("</name>\n", fh);
120 trk_name = strdup(title);
122 if (datestamp_numeric != time_t(-1)) {
123 struct tm * tm = gmtime(&datestamp_numeric);
124 if (tm) {
125 char buf[32];
126 if (strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", tm)) {
127 fputs("<time>", fh);
128 fputs(buf, fh);
129 fputs("</time>\n", fh);
133 // FIXME: optional in GPX, but perhaps useful:
134 // <bounds minlat="..." minlon="..." maxlat="..." maxlon="..." />
135 // NB Not necessarily the same as the bounds in survex coords translated
136 // to WGS84 lat+long...
139 void
140 GPX::line(const img_point *p1, const img_point *p, unsigned /*flags*/, bool fPendingMove)
142 if (fPendingMove) {
143 if (in_trkseg) {
144 fputs("</trkseg><trkseg>\n", fh);
145 } else {
146 fputs("<trk>", fh);
147 if (trk_name) {
148 fputs("<name>", fh);
149 html_escape(fh, trk_name);
150 fputs("</name>", fh);
152 fputs("<trkseg>\n", fh);
153 in_trkseg = true;
155 PJ_COORD coord{{p1->x, p1->y, p1->z, HUGE_VAL}};
156 coord = proj_trans(pj, PJ_FWD, coord);
157 if (coord.xyzt.x == HUGE_VAL ||
158 coord.xyzt.y == HUGE_VAL ||
159 coord.xyzt.z == HUGE_VAL) {
160 // FIXME report errors
162 // %.8f is at worst just over 1mm.
163 fprintf(fh, "<trkpt lon=\"%.8f\" lat=\"%.8f\"><ele>%.2f</ele></trkpt>\n",
164 coord.xyzt.x,
165 coord.xyzt.y,
166 coord.xyzt.z);
169 PJ_COORD coord{{p->x, p->y, p->z, HUGE_VAL}};
170 coord = proj_trans(pj, PJ_FWD, coord);
171 if (coord.xyzt.x == HUGE_VAL ||
172 coord.xyzt.y == HUGE_VAL ||
173 coord.xyzt.z == HUGE_VAL) {
174 // FIXME report errors
176 // %.8f is at worst just over 1mm.
177 fprintf(fh, "<trkpt lon=\"%.8f\" lat=\"%.8f\"><ele>%.2f</ele></trkpt>\n",
178 coord.xyzt.x,
179 coord.xyzt.y,
180 coord.xyzt.z);
183 void
184 GPX::label(const img_point *p, const wxString& str, int /*sflags*/, int type)
186 const char* s = str.utf8_str();
187 PJ_COORD coord{{p->x, p->y, p->z, HUGE_VAL}};
188 coord = proj_trans(pj, PJ_FWD, coord);
189 if (coord.xyzt.x == HUGE_VAL ||
190 coord.xyzt.y == HUGE_VAL ||
191 coord.xyzt.z == HUGE_VAL) {
192 // FIXME report errors
194 // %.8f is at worst just over 1mm.
195 fprintf(fh, "<wpt lon=\"%.8f\" lat=\"%.8f\"><ele>%.2f</ele><name>",
196 coord.xyzt.x,
197 coord.xyzt.y,
198 coord.xyzt.z);
199 html_escape(fh, s);
200 fputs("</name>", fh);
201 // Add a "pin" symbol with colour matching what aven shows.
202 switch (type) {
203 case FIXES:
204 fputs("<sym>Pin, Red</sym>", fh);
205 break;
206 case EXPORTS:
207 fputs("<sym>Pin, Blue</sym>", fh);
208 break;
209 case ENTS:
210 fputs("<sym>Pin, Green</sym>", fh);
211 break;
213 fputs("</wpt>\n", fh);
216 void
217 GPX::footer()
219 if (in_trkseg)
220 fputs("</trkseg></trk>\n", fh);
221 fputs("</gpx>\n", fh);