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

walls-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: 9.7 KB
Line 
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
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 "kml.h"
27
28#include "export.h" // For LABELS, etc
29
30#include <stdio.h>
31#include <string>
32#include <math.h>
33
34#include "useful.h"
35#include <proj.h>
36
37#include "aven.h"
38#include "message.h"
39
40using namespace std;
41
42#define WGS84_DATUM_STRING "EPSG:4326"
43
44static void
45html_escape(FILE *fh, const char *s)
46{
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);
60        }
61        ++s;
62    }
63}
64
65static void discarding_proj_logger(void *, int, const char *) { }
66
67KML::KML(const char * input_datum, bool clamp_to_ground_)
68    : clamp_to_ground(clamp_to_ground_)
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
93KML::~KML()
94{
95    if (pj)
96        proj_destroy(pj);
97}
98
99const int *
100KML::passes() const
101{
102    static const int default_passes[] = {
103        PASG, XSECT, WALL1, WALL2, LEGS|SURF, LABELS|ENTS|FIXES|EXPORTS, 0
104    };
105    return default_passes;
106}
107
108/* Initialise KML routines. */
109void KML::header(const char * title, const char *, time_t,
110                 double, double, double, double, double, double)
111{
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...
131}
132
133void
134KML::start_pass(int)
135{
136    if (in_linestring) {
137        fputs("</coordinates></LineString></MultiGeometry></Placemark>\n", fh);
138        in_linestring = false;
139    }
140}
141
142void
143KML::line(const img_point *p1, const img_point *p, unsigned /*flags*/, bool fPendingMove)
144{
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);
151        }
152        if (clamp_to_ground) {
153            fputs("<LineString><coordinates>\n", fh);
154        } else {
155            fputs("<LineString><altitudeMode>absolute</altitudeMode><coordinates>\n", fh);
156        }
157
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
164        }
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);
170    }
171
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
178    }
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);
184}
185
186void
187KML::xsect(const img_point *p, double angle, double d1, double d2)
188{
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);
193    }
194
195    double s = sin(rad(angle));
196    double c = cos(rad(angle));
197
198    {
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
205        }
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);
211    }
212
213    {
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
220        }
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);
226    }
227
228    fputs("</coordinates></LineString></Placemark>\n", fh);
229}
230
231void
232KML::wall(const img_point *p, double angle, double d)
233{
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);
239        }
240        in_wall = true;
241    }
242
243    double s = sin(rad(angle));
244    double c = cos(rad(angle));
245
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
252    }
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);
258}
259
260void
261KML::passage(const img_point *p, double angle, double d1, double d2)
262{
263    double s = sin(rad(angle));
264    double c = cos(rad(angle));
265
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
272    }
273    double x1 = coord1.xyzt.x;
274    double y1 = coord1.xyzt.y;
275    double z1 = coord1.xyzt.z;
276
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
283    }
284    double x2 = coord2.xyzt.x;
285    double y2 = coord2.xyzt.y;
286    double z2 = coord2.xyzt.z;
287
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.
291
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);
302        }
303
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());
307
308        fprintf(fh, "%.8f,%.8f,%.2f\n", x1, y1, z1);
309        fprintf(fh, "%.8f,%.8f,%.2f\n", x2, y2, z2);
310
311        // Close the ring.
312        fprintf(fh, "%.8f,%.8f,%.2f\n", v2.GetX(), v2.GetY(), v2.GetZ());
313
314        fputs("</coordinates></LinearRing></outerBoundaryIs>"
315              "</Polygon>\n", fh);
316    }
317
318    v2 = Vector3(x2, y2, z2);
319    v1 = Vector3(x1, y1, z1);
320}
321
322void
323KML::tube_end()
324{
325    if (in_passage){
326        fputs("</MultiGeometry></Placemark>\n", fh);
327        in_passage = false;
328    }
329    if (in_wall) {
330        fputs("</coordinates></LineString></Placemark>\n", fh);
331        in_wall = false;
332    }
333}
334
335void
336KML::label(const img_point *p, const wxString& str, bool /*fSurface*/, int type)
337{
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
345    }
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;
364    }
365    fputs("</Placemark>\n", fh);
366}
367
368void
369KML::footer()
370{
371    if (in_linestring)
372        fputs("</coordinates></LineString></MultiGeometry></Placemark>\n", fh);
373    fputs("</Document></kml>\n", fh);
374}
Note: See TracBrowser for help on using the repository browser.