Pass station name to export code as wxString
[survex.git] / src / kml.cc
blob5b9f4fd6eded7569ae67d88a1eee1a9f6e6084c5
1 /* kml.cc
2 * Export from Aven as KML.
3 */
4 /* Copyright (C) 2012 Olaf Kähler
5 * Copyright (C) 2012,2013,2014,2015,2016,2017,2018,2019 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 #ifdef HAVE_CONFIG_H
23 # include <config.h>
24 #endif
26 #include "kml.h"
28 #include "export.h" // For LABELS, etc
30 #include <stdio.h>
31 #include <string>
32 #include <math.h>
34 #include "useful.h"
35 #include <proj.h>
37 #include "aven.h"
38 #include "message.h"
40 using namespace std;
42 #define WGS84_DATUM_STRING "EPSG:4326"
44 static void
45 html_escape(FILE *fh, const char *s)
47 while (*s) {
48 switch (*s) {
49 case '<':
50 fputs("&lt;", fh);
51 break;
52 case '>':
53 fputs("&gt;", fh);
54 break;
55 case '&':
56 fputs("&amp;", fh);
57 break;
58 default:
59 PUTC(*s, fh);
61 ++s;
65 static void discarding_proj_logger(void *, int, const char *) { }
67 KML::KML(const char * input_datum, bool clamp_to_ground_)
68 : clamp_to_ground(clamp_to_ground_)
70 /* Prevent stderr spew from PROJ. */
71 proj_log_func(PJ_DEFAULT_CTX, nullptr, discarding_proj_logger);
73 pj = proj_create_crs_to_crs(PJ_DEFAULT_CTX,
74 input_datum, WGS84_DATUM_STRING,
75 NULL);
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;
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;
93 KML::~KML()
95 if (pj)
96 proj_destroy(pj);
99 const int *
100 KML::passes() const
102 static const int default_passes[] = {
103 PASG, XSECT, WALL1, WALL2, LEGS|SURF, LABELS|ENTS|FIXES|EXPORTS, 0
105 return default_passes;
108 /* Initialise KML routines. */
109 void KML::header(const char * title, const char *, time_t,
110 double, double, double, double, double, double)
112 fputs(
113 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
114 "<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n", fh);
115 fputs("<Document><name>", fh);
116 html_escape(fh, title);
117 fputs("</name>\n", fh);
118 // Set up styles for the icons to reduce the file size.
119 fputs("<Style id=\"fix\"><IconStyle>"
120 "<Icon><href>http://maps.google.com/mapfiles/kml/paddle/red-blank.png</href></Icon>"
121 "</IconStyle></Style>\n", fh);
122 fputs("<Style id=\"exp\"><IconStyle>"
123 "<Icon><href>http://maps.google.com/mapfiles/kml/paddle/blu-blank.png</href></Icon>"
124 "</IconStyle></Style>\n", fh);
125 fputs("<Style id=\"ent\"><IconStyle>"
126 "<Icon><href>http://maps.google.com/mapfiles/kml/paddle/grn-blank.png</href></Icon>"
127 "</IconStyle></Style>\n", fh);
128 // FIXME: does KML allow bounds?
129 // NB Lat+long bounds are not necessarily the same as the bounds in survex
130 // coords translated to WGS84 lat+long...
133 void
134 KML::start_pass(int)
136 if (in_linestring) {
137 fputs("</coordinates></LineString></MultiGeometry></Placemark>\n", fh);
138 in_linestring = false;
142 void
143 KML::line(const img_point *p1, const img_point *p, unsigned /*flags*/, bool fPendingMove)
145 if (fPendingMove) {
146 if (!in_linestring) {
147 in_linestring = true;
148 fputs("<Placemark><MultiGeometry>\n", fh);
149 } else {
150 fputs("</coordinates></LineString>\n", fh);
152 if (clamp_to_ground) {
153 fputs("<LineString><coordinates>\n", fh);
154 } else {
155 fputs("<LineString><altitudeMode>absolute</altitudeMode><coordinates>\n", fh);
158 PJ_COORD coord{{p1->x, p1->y, p1->z, HUGE_VAL}};
159 coord = proj_trans(pj, PJ_FWD, coord);
160 if (coord.xyzt.x == HUGE_VAL ||
161 coord.xyzt.y == HUGE_VAL ||
162 coord.xyzt.z == HUGE_VAL) {
163 // FIXME report errors
165 // %.8f is at worst just over 1mm.
166 fprintf(fh, "%.8f,%.8f,%.2f\n",
167 coord.xyzt.x,
168 coord.xyzt.y,
169 coord.xyzt.z);
172 PJ_COORD coord{{p->x, p->y, p->z, HUGE_VAL}};
173 coord = proj_trans(pj, PJ_FWD, coord);
174 if (coord.xyzt.x == HUGE_VAL ||
175 coord.xyzt.y == HUGE_VAL ||
176 coord.xyzt.z == HUGE_VAL) {
177 // FIXME report errors
179 // %.8f is at worst just over 1mm.
180 fprintf(fh, "%.8f,%.8f,%.2f\n",
181 coord.xyzt.x,
182 coord.xyzt.y,
183 coord.xyzt.z);
186 void
187 KML::xsect(const img_point *p, double angle, double d1, double d2)
189 if (clamp_to_ground) {
190 fputs("<Placemark><name></name><LineString><coordinates>", fh);
191 } else {
192 fputs("<Placemark><name></name><LineString><altitudeMode>absolute</altitudeMode><coordinates>", fh);
195 double s = sin(rad(angle));
196 double c = cos(rad(angle));
199 PJ_COORD coord{{p->x + s * d1, p->y + c * d1, p->z, HUGE_VAL}};
200 coord = proj_trans(pj, PJ_FWD, coord);
201 if (coord.xyzt.x == HUGE_VAL ||
202 coord.xyzt.y == HUGE_VAL ||
203 coord.xyzt.z == HUGE_VAL) {
204 // FIXME report errors
206 // %.8f is at worst just over 1mm.
207 fprintf(fh, "%.8f,%.8f,%.2f ",
208 coord.xyzt.x,
209 coord.xyzt.y,
210 coord.xyzt.z);
214 PJ_COORD coord{{p->x - s * d2, p->y - c * d2, p->z, HUGE_VAL}};
215 coord = proj_trans(pj, PJ_FWD, coord);
216 if (coord.xyzt.x == HUGE_VAL ||
217 coord.xyzt.y == HUGE_VAL ||
218 coord.xyzt.z == HUGE_VAL) {
219 // FIXME report errors
221 // %.8f is at worst just over 1mm.
222 fprintf(fh, "%.8f,%.8f,%.2f\n",
223 coord.xyzt.x,
224 coord.xyzt.y,
225 coord.xyzt.z);
228 fputs("</coordinates></LineString></Placemark>\n", fh);
231 void
232 KML::wall(const img_point *p, double angle, double d)
234 if (!in_wall) {
235 if (clamp_to_ground) {
236 fputs("<Placemark><name></name><LineString><coordinates>", fh);
237 } else {
238 fputs("<Placemark><name></name><LineString><altitudeMode>absolute</altitudeMode><coordinates>", fh);
240 in_wall = true;
243 double s = sin(rad(angle));
244 double c = cos(rad(angle));
246 PJ_COORD coord{{p->x + s * d, p->y + c * d, p->z, HUGE_VAL}};
247 coord = proj_trans(pj, PJ_FWD, coord);
248 if (coord.xyzt.x == HUGE_VAL ||
249 coord.xyzt.y == HUGE_VAL ||
250 coord.xyzt.z == HUGE_VAL) {
251 // FIXME report errors
253 // %.8f is at worst just over 1mm.
254 fprintf(fh, "%.8f,%.8f,%.2f\n",
255 coord.xyzt.x,
256 coord.xyzt.y,
257 coord.xyzt.z);
260 void
261 KML::passage(const img_point *p, double angle, double d1, double d2)
263 double s = sin(rad(angle));
264 double c = cos(rad(angle));
266 PJ_COORD coord1{{p->x + s * d1, p->y + c * d1, p->z, HUGE_VAL}};
267 coord1 = proj_trans(pj, PJ_FWD, coord1);
268 if (coord1.xyzt.x == HUGE_VAL ||
269 coord1.xyzt.y == HUGE_VAL ||
270 coord1.xyzt.z == HUGE_VAL) {
271 // FIXME report errors
273 double x1 = coord1.xyzt.x;
274 double y1 = coord1.xyzt.y;
275 double z1 = coord1.xyzt.z;
277 PJ_COORD coord2{{p->x - s * d2, p->y - c * d2, p->z, HUGE_VAL}};
278 coord2 = proj_trans(pj, PJ_FWD, coord2);
279 if (coord2.xyzt.x == HUGE_VAL ||
280 coord2.xyzt.y == HUGE_VAL ||
281 coord2.xyzt.z == HUGE_VAL) {
282 // FIXME report errors
284 double x2 = coord2.xyzt.x;
285 double y2 = coord2.xyzt.y;
286 double z2 = coord2.xyzt.z;
288 // Define each passage as a multigeometry comprising of one quadrilateral
289 // per section. This prevents invalid geometry (such as self-intersecting
290 // polygons) being created.
292 if (!in_passage){
293 in_passage = true;
294 fputs("<Placemark><name></name><MultiGeometry>\n", fh);
295 } else {
296 if (clamp_to_ground) {
297 fputs("<Polygon>"
298 "<outerBoundaryIs><LinearRing><coordinates>\n", fh);
299 } else {
300 fputs("<Polygon><altitudeMode>absolute</altitudeMode>"
301 "<outerBoundaryIs><LinearRing><coordinates>\n", fh);
304 // Draw anti-clockwise around the ring.
305 fprintf(fh, "%.8f,%.8f,%.2f\n", v2.GetX(), v2.GetY(), v2.GetZ());
306 fprintf(fh, "%.8f,%.8f,%.2f\n", v1.GetX(), v1.GetY(), v1.GetZ());
308 fprintf(fh, "%.8f,%.8f,%.2f\n", x1, y1, z1);
309 fprintf(fh, "%.8f,%.8f,%.2f\n", x2, y2, z2);
311 // Close the ring.
312 fprintf(fh, "%.8f,%.8f,%.2f\n", v2.GetX(), v2.GetY(), v2.GetZ());
314 fputs("</coordinates></LinearRing></outerBoundaryIs>"
315 "</Polygon>\n", fh);
318 v2 = Vector3(x2, y2, z2);
319 v1 = Vector3(x1, y1, z1);
322 void
323 KML::tube_end()
325 if (in_passage){
326 fputs("</MultiGeometry></Placemark>\n", fh);
327 in_passage = false;
329 if (in_wall) {
330 fputs("</coordinates></LineString></Placemark>\n", fh);
331 in_wall = false;
335 void
336 KML::label(const img_point *p, const wxString& str, bool /*fSurface*/, int type)
338 const char* s = str.utf8_str();
339 PJ_COORD coord{{p->x, p->y, p->z, HUGE_VAL}};
340 coord = proj_trans(pj, PJ_FWD, coord);
341 if (coord.xyzt.x == HUGE_VAL ||
342 coord.xyzt.y == HUGE_VAL ||
343 coord.xyzt.z == HUGE_VAL) {
344 // FIXME report errors
346 // %.8f is at worst just over 1mm.
347 fprintf(fh, "<Placemark><Point><coordinates>%.8f,%.8f,%.2f</coordinates></Point><name>",
348 coord.xyzt.x,
349 coord.xyzt.y,
350 coord.xyzt.z);
351 html_escape(fh, s);
352 fputs("</name>", fh);
353 // Add a "pin" symbol with colour matching what aven shows.
354 switch (type) {
355 case FIXES:
356 fputs("<styleUrl>#fix</styleUrl>", fh);
357 break;
358 case EXPORTS:
359 fputs("<styleUrl>#exp</styleUrl>", fh);
360 break;
361 case ENTS:
362 fputs("<styleUrl>#ent</styleUrl>", fh);
363 break;
365 fputs("</Placemark>\n", fh);
368 void
369 KML::footer()
371 if (in_linestring)
372 fputs("</coordinates></LineString></MultiGeometry></Placemark>\n", fh);
373 fputs("</Document></kml>\n", fh);