source: git/src/kml.cc

Last change on this file was 696db01, checked in by Olly Betts <olly@…>, 5 months ago

Stop passing string datestamp to export machinery

We now only actually use the time_t datestamp value.

  • Property mode set to 100644
File size: 10.7 KB
Line 
1/* kml.cc
2 * Export from Aven as KML.
3 */
4/* Copyright (C) 2012 Olaf Kähler
5 * Copyright (C) 2012-2024 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#include <config.h>
23
24#include "kml.h"
25
26#include "export.h" // For LABELS, etc
27
28#include <stdio.h>
29#include <string>
30#include <math.h>
31
32#include "useful.h"
33#include <proj.h>
34
35#include "aven.h"
36#include "message.h"
37
38using namespace std;
39
40#define WGS84_DATUM_STRING "EPSG:4326"
41
42static void
43html_escape(FILE *fh, const char *s)
44{
45    while (*s) {
46        switch (*s) {
47            case '<':
48                fputs("&lt;", fh);
49                break;
50            case '>':
51                fputs("&gt;", fh);
52                break;
53            case '&':
54                fputs("&amp;", fh);
55                break;
56            default:
57                PUTC(*s, fh);
58        }
59        ++s;
60    }
61}
62
63static void discarding_proj_logger(void *, int, const char *) { }
64
65KML::KML(const char * input_datum, bool clamp_to_ground_)
66    : clamp_to_ground(clamp_to_ground_)
67{
68    /* Prevent stderr spew from PROJ. */
69    proj_log_func(PJ_DEFAULT_CTX, nullptr, discarding_proj_logger);
70
71    pj = proj_create_crs_to_crs(PJ_DEFAULT_CTX,
72                                input_datum, WGS84_DATUM_STRING,
73                                NULL);
74
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;
82    }
83
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;
88    }
89}
90
91KML::~KML()
92{
93    if (pj)
94        proj_destroy(pj);
95}
96
97const int *
98KML::passes() const
99{
100    static const int default_passes[] = {
101        PASG, XSECT, WALL1, WALL2, LEGS|SURF, LABELS|ENTS|FIXES|EXPORTS, 0
102    };
103    return default_passes;
104}
105
106/* Initialise KML routines. */
107void KML::header(const char * title, time_t,
108                 double, double, double, double, double, double)
109{
110    fputs(
111"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
112"<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n", fh);
113    fputs("<Document><name>", fh);
114    html_escape(fh, title);
115    fputs("</name>\n", fh);
116    // Set up styles for the icons to reduce the file size.
117    // Note "color" code order is aabbggrr
118    fputs("<Style id=\"fix\"><IconStyle>"
119          "<Icon><href>https://maps.google.com/mapfiles/kml/paddle/red-blank.png</href></Icon>"
120          "<color>ff0000ff</color>"
121          "<hotSpot x=\"32\" y=\"1\" xunits=\"pixels\" yunits=\"pixels\"/>"
122          "</IconStyle></Style>\n", fh);
123    fputs("<Style id=\"exp\"><IconStyle>"
124          "<Icon><href>https://maps.google.com/mapfiles/kml/paddle/blu-blank.png</href></Icon>"
125          "<color>ffff0000</color>"
126          "<hotSpot x=\"32\" y=\"1\" xunits=\"pixels\" yunits=\"pixels\"/>"
127          "</IconStyle></Style>\n", fh);
128    fputs("<Style id=\"ent\"><IconStyle>"
129          "<Icon><href>https://maps.google.com/mapfiles/kml/paddle/grn-blank.png</href></Icon>"
130          "<color>ff00ff00</color>"
131          "<hotSpot x=\"32\" y=\"1\" xunits=\"pixels\" yunits=\"pixels\"/>"
132          "</IconStyle></Style>\n", fh);
133    // Set up styles for surface legs and splays.
134    fputs("<Style id=\"surf\"><LineStyle>"
135          "<color>ff00ff00</color>" // Green.
136          "</LineStyle></Style>\n", fh);
137    fputs("<Style id=\"splay\"><LineStyle>"
138          "<color>40ff00ff</color>" // Partly transparent magenta.
139          "</LineStyle></Style>\n", fh);
140    // FIXME: does KML allow bounds?
141    // NB Lat+long bounds are not necessarily the same as the bounds in survex
142    // coords translated to WGS84 lat+long...
143}
144
145void
146KML::start_pass(int)
147{
148    if (linestring_flags) {
149        fputs("</coordinates></LineString></MultiGeometry></Placemark>\n", fh);
150        linestring_flags = 0;
151    }
152}
153
154void
155KML::line(const img_point *p1, const img_point *p, unsigned flags, bool fPendingMove)
156{
157    if (linestring_flags && linestring_flags != (flags & (LEGS|SURF|SPLAYS))) {
158        fputs("</coordinates></LineString></MultiGeometry></Placemark>\n", fh);
159        linestring_flags = 0;
160        fPendingMove = true;
161    }
162    if (fPendingMove) {
163        if (linestring_flags == 0) {
164            linestring_flags = (flags & (LEGS|SURF|SPLAYS));
165            if (flags & SURF) {
166                fputs("<Placemark>"
167                      "<styleUrl>#surf</styleUrl>"
168                      "<MultiGeometry>", fh);
169            } else if (flags & SPLAYS) {
170                fputs("<Placemark>"
171                      "<styleUrl>#splay</styleUrl>"
172                      "<MultiGeometry>", fh);
173            } else {
174                fputs("<Placemark><MultiGeometry>", fh);
175            }
176        } else {
177            fputs("</coordinates></LineString>\n", fh);
178        }
179        if (clamp_to_ground) {
180            fputs("<LineString><coordinates>\n", fh);
181        } else {
182            fputs("<LineString><altitudeMode>absolute</altitudeMode><coordinates>\n", fh);
183        }
184
185        PJ_COORD coord{{p1->x, p1->y, p1->z, HUGE_VAL}};
186        coord = proj_trans(pj, PJ_FWD, coord);
187        if (coord.xyzt.x == HUGE_VAL ||
188            coord.xyzt.y == HUGE_VAL ||
189            coord.xyzt.z == HUGE_VAL) {
190            // FIXME report errors
191        }
192        // %.8f is at worst just over 1mm.
193        fprintf(fh, "%.8f,%.8f,%.2f\n",
194                coord.xyzt.x,
195                coord.xyzt.y,
196                coord.xyzt.z);
197    }
198
199    PJ_COORD coord{{p->x, p->y, 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\n",
208            coord.xyzt.x,
209            coord.xyzt.y,
210            coord.xyzt.z);
211}
212
213void
214KML::xsect(const img_point *p, double angle, double d1, double d2)
215{
216    if (clamp_to_ground) {
217        fputs("<Placemark><name></name><LineString><coordinates>", fh);
218    } else {
219        fputs("<Placemark><name></name><LineString><altitudeMode>absolute</altitudeMode><coordinates>", fh);
220    }
221
222    double s = sin(rad(angle));
223    double c = cos(rad(angle));
224
225    {
226        PJ_COORD coord{{p->x + s * d1, p->y + c * d1, p->z, HUGE_VAL}};
227        coord = proj_trans(pj, PJ_FWD, coord);
228        if (coord.xyzt.x == HUGE_VAL ||
229            coord.xyzt.y == HUGE_VAL ||
230            coord.xyzt.z == HUGE_VAL) {
231            // FIXME report errors
232        }
233        // %.8f is at worst just over 1mm.
234        fprintf(fh, "%.8f,%.8f,%.2f ",
235                coord.xyzt.x,
236                coord.xyzt.y,
237                coord.xyzt.z);
238    }
239
240    {
241        PJ_COORD coord{{p->x - s * d2, p->y - c * d2, p->z, HUGE_VAL}};
242        coord = proj_trans(pj, PJ_FWD, coord);
243        if (coord.xyzt.x == HUGE_VAL ||
244            coord.xyzt.y == HUGE_VAL ||
245            coord.xyzt.z == HUGE_VAL) {
246            // FIXME report errors
247        }
248        // %.8f is at worst just over 1mm.
249        fprintf(fh, "%.8f,%.8f,%.2f\n",
250                coord.xyzt.x,
251                coord.xyzt.y,
252                coord.xyzt.z);
253    }
254
255    fputs("</coordinates></LineString></Placemark>\n", fh);
256}
257
258void
259KML::wall(const img_point *p, double angle, double d)
260{
261    if (!in_wall) {
262        if (clamp_to_ground) {
263            fputs("<Placemark><name></name><LineString><coordinates>", fh);
264        } else {
265            fputs("<Placemark><name></name><LineString><altitudeMode>absolute</altitudeMode><coordinates>", fh);
266        }
267        in_wall = true;
268    }
269
270    double s = sin(rad(angle));
271    double c = cos(rad(angle));
272
273    PJ_COORD coord{{p->x + s * d, p->y + c * d, p->z, HUGE_VAL}};
274    coord = proj_trans(pj, PJ_FWD, coord);
275    if (coord.xyzt.x == HUGE_VAL ||
276        coord.xyzt.y == HUGE_VAL ||
277        coord.xyzt.z == HUGE_VAL) {
278        // FIXME report errors
279    }
280    // %.8f is at worst just over 1mm.
281    fprintf(fh, "%.8f,%.8f,%.2f\n",
282            coord.xyzt.x,
283            coord.xyzt.y,
284            coord.xyzt.z);
285}
286
287void
288KML::passage(const img_point *p, double angle, double d1, double d2)
289{
290    double s = sin(rad(angle));
291    double c = cos(rad(angle));
292
293    PJ_COORD coord1{{p->x + s * d1, p->y + c * d1, p->z, HUGE_VAL}};
294    coord1 = proj_trans(pj, PJ_FWD, coord1);
295    if (coord1.xyzt.x == HUGE_VAL ||
296        coord1.xyzt.y == HUGE_VAL ||
297        coord1.xyzt.z == HUGE_VAL) {
298        // FIXME report errors
299    }
300    double x1 = coord1.xyzt.x;
301    double y1 = coord1.xyzt.y;
302    double z1 = coord1.xyzt.z;
303
304    PJ_COORD coord2{{p->x - s * d2, p->y - c * d2, p->z, HUGE_VAL}};
305    coord2 = proj_trans(pj, PJ_FWD, coord2);
306    if (coord2.xyzt.x == HUGE_VAL ||
307        coord2.xyzt.y == HUGE_VAL ||
308        coord2.xyzt.z == HUGE_VAL) {
309        // FIXME report errors
310    }
311    double x2 = coord2.xyzt.x;
312    double y2 = coord2.xyzt.y;
313    double z2 = coord2.xyzt.z;
314
315    // Define each passage as a multigeometry comprising of one quadrilateral
316    // per section.  This prevents invalid geometry (such as self-intersecting
317    // polygons) being created.
318
319    if (!in_passage){
320        in_passage = true;
321        fputs("<Placemark><name></name><MultiGeometry>\n", fh);
322    } else {
323        if (clamp_to_ground) {
324            fputs("<Polygon>"
325                  "<outerBoundaryIs><LinearRing><coordinates>\n", fh);
326        } else {
327            fputs("<Polygon><altitudeMode>absolute</altitudeMode>"
328                  "<outerBoundaryIs><LinearRing><coordinates>\n", fh);
329        }
330
331        // Draw anti-clockwise around the ring.
332        fprintf(fh, "%.8f,%.8f,%.2f\n", v2.GetX(), v2.GetY(), v2.GetZ());
333        fprintf(fh, "%.8f,%.8f,%.2f\n", v1.GetX(), v1.GetY(), v1.GetZ());
334
335        fprintf(fh, "%.8f,%.8f,%.2f\n", x1, y1, z1);
336        fprintf(fh, "%.8f,%.8f,%.2f\n", x2, y2, z2);
337
338        // Close the ring.
339        fprintf(fh, "%.8f,%.8f,%.2f\n", v2.GetX(), v2.GetY(), v2.GetZ());
340
341        fputs("</coordinates></LinearRing></outerBoundaryIs>"
342              "</Polygon>\n", fh);
343    }
344
345    v2 = Vector3(x2, y2, z2);
346    v1 = Vector3(x1, y1, z1);
347}
348
349void
350KML::tube_end()
351{
352    if (in_passage){
353        fputs("</MultiGeometry></Placemark>\n", fh);
354        in_passage = false;
355    }
356    if (in_wall) {
357        fputs("</coordinates></LineString></Placemark>\n", fh);
358        in_wall = false;
359    }
360}
361
362void
363KML::label(const img_point *p, const wxString& str, int /*sflags*/, int type)
364{
365    const char* s = str.utf8_str();
366    PJ_COORD coord{{p->x, p->y, p->z, HUGE_VAL}};
367    coord = proj_trans(pj, PJ_FWD, coord);
368    if (coord.xyzt.x == HUGE_VAL ||
369        coord.xyzt.y == HUGE_VAL ||
370        coord.xyzt.z == HUGE_VAL) {
371        // FIXME report errors
372    }
373    // %.8f is at worst just over 1mm.
374    fprintf(fh, "<Placemark><Point><coordinates>%.8f,%.8f,%.2f</coordinates></Point><name>",
375            coord.xyzt.x,
376            coord.xyzt.y,
377            coord.xyzt.z);
378    html_escape(fh, s);
379    fputs("</name>", fh);
380    // Add a "pin" symbol with colour matching what aven shows.
381    switch (type) {
382        case FIXES:
383            fputs("<styleUrl>#fix</styleUrl>", fh);
384            break;
385        case EXPORTS:
386            fputs("<styleUrl>#exp</styleUrl>", fh);
387            break;
388        case ENTS:
389            fputs("<styleUrl>#ent</styleUrl>", fh);
390            break;
391    }
392    fputs("</Placemark>\n", fh);
393}
394
395void
396KML::footer()
397{
398    if (linestring_flags)
399        fputs("</coordinates></LineString></MultiGeometry></Placemark>\n", fh);
400    fputs("</Document></kml>\n", fh);
401}
Note: See TracBrowser for help on using the repository browser.