source: git/src/kml.cc @ 92debc5

stereo-2025
Last change on this file since 92debc5 was 371385f, checked in by Olly Betts <olly@…>, 12 months ago

Include all station flags in 3d export

  • Property mode set to 100644
File size: 9.6 KB
RevLine 
[1534ed9]1/* kml.cc
2 * Export from Aven as KML.
3 */
4/* Copyright (C) 2012 Olaf Kähler
[371385f]5 * Copyright (C) 2012-2024 Olly Betts
[1534ed9]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
[4c83f84]22#include <config.h>
[1534ed9]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"
[c2ea0c5]33#include <proj.h>
[1534ed9]34
35#include "aven.h"
36#include "message.h"
37
38using namespace std;
39
[297d142]40#define WGS84_DATUM_STRING "EPSG:4326"
[1534ed9]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
[c2ea0c5]63static void discarding_proj_logger(void *, int, const char *) { }
64
[32a040e]65KML::KML(const char * input_datum, bool clamp_to_ground_)
66    : clamp_to_ground(clamp_to_ground_)
[1534ed9]67{
[c2ea0c5]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);
[297d142]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
[c2ea0c5]84    if (!pj) {
[1534ed9]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{
[c2ea0c5]93    if (pj)
94        proj_destroy(pj);
[1534ed9]95}
96
97const int *
98KML::passes() const
99{
[f2cb101]100    static const int default_passes[] = {
[e63994c]101        PASG, XSECT, WALL1, WALL2, LEGS|SURF, LABELS|ENTS|FIXES|EXPORTS, 0
[f2cb101]102    };
[1534ed9]103    return default_passes;
104}
105
106/* Initialise KML routines. */
[55a861a]107void KML::header(const char * title, const char *, time_t,
108                 double, double, double, double, double, double)
[1534ed9]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);
[7aed359]116    // Set up styles for the icons to reduce the file size.
117    fputs("<Style id=\"fix\"><IconStyle>"
[72e6593]118          "<Icon><href>http://maps.google.com/mapfiles/kml/paddle/red-blank.png</href></Icon>"
[7aed359]119          "</IconStyle></Style>\n", fh);
120    fputs("<Style id=\"exp\"><IconStyle>"
[72e6593]121          "<Icon><href>http://maps.google.com/mapfiles/kml/paddle/blu-blank.png</href></Icon>"
[7aed359]122          "</IconStyle></Style>\n", fh);
123    fputs("<Style id=\"ent\"><IconStyle>"
[72e6593]124          "<Icon><href>http://maps.google.com/mapfiles/kml/paddle/grn-blank.png</href></Icon>"
[7aed359]125          "</IconStyle></Style>\n", fh);
[1534ed9]126    // FIXME: does KML allow bounds?
127    // NB Lat+long bounds are not necessarily the same as the bounds in survex
128    // coords translated to WGS84 lat+long...
129}
130
[f03d89e]131void
[0fe63d3]132KML::start_pass(int)
[f03d89e]133{
134    if (in_linestring) {
135        fputs("</coordinates></LineString></MultiGeometry></Placemark>\n", fh);
136        in_linestring = false;
137    }
138}
139
[1534ed9]140void
[a2c29c1]141KML::line(const img_point *p1, const img_point *p, unsigned /*flags*/, bool fPendingMove)
[1534ed9]142{
143    if (fPendingMove) {
[3b23819]144        if (!in_linestring) {
145            in_linestring = true;
146            fputs("<Placemark><MultiGeometry>\n", fh);
147        } else {
148            fputs("</coordinates></LineString>\n", fh);
149        }
[32a040e]150        if (clamp_to_ground) {
151            fputs("<LineString><coordinates>\n", fh);
152        } else {
153            fputs("<LineString><altitudeMode>absolute</altitudeMode><coordinates>\n", fh);
154        }
[c2ea0c5]155
[47b2b81]156        PJ_COORD coord{{p1->x, p1->y, p1->z, HUGE_VAL}};
[c2ea0c5]157        coord = proj_trans(pj, PJ_FWD, coord);
158        if (coord.xyzt.x == HUGE_VAL ||
159            coord.xyzt.y == HUGE_VAL ||
160            coord.xyzt.z == HUGE_VAL) {
161            // FIXME report errors
162        }
[1534ed9]163        // %.8f is at worst just over 1mm.
[c2ea0c5]164        fprintf(fh, "%.8f,%.8f,%.2f\n",
165                coord.xyzt.x,
166                coord.xyzt.y,
167                coord.xyzt.z);
168    }
169
[47b2b81]170    PJ_COORD coord{{p->x, p->y, p->z, HUGE_VAL}};
[c2ea0c5]171    coord = proj_trans(pj, PJ_FWD, coord);
172    if (coord.xyzt.x == HUGE_VAL ||
173        coord.xyzt.y == HUGE_VAL ||
174        coord.xyzt.z == HUGE_VAL) {
175        // FIXME report errors
[1534ed9]176    }
177    // %.8f is at worst just over 1mm.
[c2ea0c5]178    fprintf(fh, "%.8f,%.8f,%.2f\n",
179            coord.xyzt.x,
180            coord.xyzt.y,
181            coord.xyzt.z);
[1534ed9]182}
183
[e63994c]184void
185KML::xsect(const img_point *p, double angle, double d1, double d2)
186{
[32a040e]187    if (clamp_to_ground) {
188        fputs("<Placemark><name></name><LineString><coordinates>", fh);
189    } else {
190        fputs("<Placemark><name></name><LineString><altitudeMode>absolute</altitudeMode><coordinates>", fh);
191    }
[c2ea0c5]192
193    double s = sin(rad(angle));
194    double c = cos(rad(angle));
195
196    {
[47b2b81]197        PJ_COORD coord{{p->x + s * d1, p->y + c * d1, p->z, HUGE_VAL}};
[c2ea0c5]198        coord = proj_trans(pj, PJ_FWD, coord);
199        if (coord.xyzt.x == HUGE_VAL ||
200            coord.xyzt.y == HUGE_VAL ||
201            coord.xyzt.z == HUGE_VAL) {
202            // FIXME report errors
203        }
204        // %.8f is at worst just over 1mm.
205        fprintf(fh, "%.8f,%.8f,%.2f ",
206                coord.xyzt.x,
207                coord.xyzt.y,
208                coord.xyzt.z);
209    }
210
211    {
[47b2b81]212        PJ_COORD coord{{p->x - s * d2, p->y - c * d2, p->z, HUGE_VAL}};
[c2ea0c5]213        coord = proj_trans(pj, PJ_FWD, coord);
214        if (coord.xyzt.x == HUGE_VAL ||
215            coord.xyzt.y == HUGE_VAL ||
216            coord.xyzt.z == HUGE_VAL) {
217            // FIXME report errors
218        }
219        // %.8f is at worst just over 1mm.
220        fprintf(fh, "%.8f,%.8f,%.2f\n",
221                coord.xyzt.x,
222                coord.xyzt.y,
223                coord.xyzt.z);
224    }
225
[e63994c]226    fputs("</coordinates></LineString></Placemark>\n", fh);
227}
228
229void
230KML::wall(const img_point *p, double angle, double d)
231{
232    if (!in_wall) {
[6028fff]233        if (clamp_to_ground) {
234            fputs("<Placemark><name></name><LineString><coordinates>", fh);
235        } else {
236            fputs("<Placemark><name></name><LineString><altitudeMode>absolute</altitudeMode><coordinates>", fh);
237        }
238        in_wall = true;
[e63994c]239    }
[c2ea0c5]240
241    double s = sin(rad(angle));
242    double c = cos(rad(angle));
243
[47b2b81]244    PJ_COORD coord{{p->x + s * d, p->y + c * d, p->z, HUGE_VAL}};
[c2ea0c5]245    coord = proj_trans(pj, PJ_FWD, coord);
246    if (coord.xyzt.x == HUGE_VAL ||
247        coord.xyzt.y == HUGE_VAL ||
248        coord.xyzt.z == HUGE_VAL) {
249        // FIXME report errors
250    }
251    // %.8f is at worst just over 1mm.
252    fprintf(fh, "%.8f,%.8f,%.2f\n",
253            coord.xyzt.x,
254            coord.xyzt.y,
255            coord.xyzt.z);
[e63994c]256}
257
[f2cb101]258void
259KML::passage(const img_point *p, double angle, double d1, double d2)
260{
261    double s = sin(rad(angle));
262    double c = cos(rad(angle));
263
[47b2b81]264    PJ_COORD coord1{{p->x + s * d1, p->y + c * d1, p->z, HUGE_VAL}};
[c2ea0c5]265    coord1 = proj_trans(pj, PJ_FWD, coord1);
266    if (coord1.xyzt.x == HUGE_VAL ||
267        coord1.xyzt.y == HUGE_VAL ||
268        coord1.xyzt.z == HUGE_VAL) {
269        // FIXME report errors
270    }
271    double x1 = coord1.xyzt.x;
272    double y1 = coord1.xyzt.y;
273    double z1 = coord1.xyzt.z;
274
[47b2b81]275    PJ_COORD coord2{{p->x - s * d2, p->y - c * d2, p->z, HUGE_VAL}};
[c2ea0c5]276    coord2 = proj_trans(pj, PJ_FWD, coord2);
277    if (coord2.xyzt.x == HUGE_VAL ||
278        coord2.xyzt.y == HUGE_VAL ||
279        coord2.xyzt.z == HUGE_VAL) {
280        // FIXME report errors
281    }
282    double x2 = coord2.xyzt.x;
283    double y2 = coord2.xyzt.y;
284    double z2 = coord2.xyzt.z;
[f2cb101]285
[6028fff]286    // Define each passage as a multigeometry comprising of one quadrilateral
287    // per section.  This prevents invalid geometry (such as self-intersecting
288    // polygons) being created.
[0939dcd]289
290    if (!in_passage){
[6028fff]291        in_passage = true;
292        fputs("<Placemark><name></name><MultiGeometry>\n", fh);
293    } else {
294        if (clamp_to_ground) {
295            fputs("<Polygon>"
296                  "<outerBoundaryIs><LinearRing><coordinates>\n", fh);
297        } else {
298            fputs("<Polygon><altitudeMode>absolute</altitudeMode>"
299                  "<outerBoundaryIs><LinearRing><coordinates>\n", fh);
300        }
[0939dcd]301
[6028fff]302        // Draw anti-clockwise around the ring.
303        fprintf(fh, "%.8f,%.8f,%.2f\n", v2.GetX(), v2.GetY(), v2.GetZ());
304        fprintf(fh, "%.8f,%.8f,%.2f\n", v1.GetX(), v1.GetY(), v1.GetZ());
[0939dcd]305
[6028fff]306        fprintf(fh, "%.8f,%.8f,%.2f\n", x1, y1, z1);
307        fprintf(fh, "%.8f,%.8f,%.2f\n", x2, y2, z2);
[0939dcd]308
[6028fff]309        // Close the ring.
310        fprintf(fh, "%.8f,%.8f,%.2f\n", v2.GetX(), v2.GetY(), v2.GetZ());
[0939dcd]311
[6028fff]312        fputs("</coordinates></LinearRing></outerBoundaryIs>"
313              "</Polygon>\n", fh);
[0939dcd]314    }
315
[6028fff]316    v2 = Vector3(x2, y2, z2);
317    v1 = Vector3(x1, y1, z1);
[f2cb101]318}
319
320void
321KML::tube_end()
322{
[0939dcd]323    if (in_passage){
[6028fff]324        fputs("</MultiGeometry></Placemark>\n", fh);
325        in_passage = false;
[e63994c]326    }
327    if (in_wall) {
[6028fff]328        fputs("</coordinates></LineString></Placemark>\n", fh);
329        in_wall = false;
[f2cb101]330    }
331}
332
[1534ed9]333void
[371385f]334KML::label(const img_point *p, const wxString& str, int /*sflags*/, int type)
[1534ed9]335{
[8c4cefb]336    const char* s = str.utf8_str();
[47b2b81]337    PJ_COORD coord{{p->x, p->y, p->z, HUGE_VAL}};
[c2ea0c5]338    coord = proj_trans(pj, PJ_FWD, coord);
339    if (coord.xyzt.x == HUGE_VAL ||
340        coord.xyzt.y == HUGE_VAL ||
341        coord.xyzt.z == HUGE_VAL) {
342        // FIXME report errors
343    }
[1534ed9]344    // %.8f is at worst just over 1mm.
[c2ea0c5]345    fprintf(fh, "<Placemark><Point><coordinates>%.8f,%.8f,%.2f</coordinates></Point><name>",
346            coord.xyzt.x,
347            coord.xyzt.y,
348            coord.xyzt.z);
[1534ed9]349    html_escape(fh, s);
350    fputs("</name>", fh);
351    // Add a "pin" symbol with colour matching what aven shows.
352    switch (type) {
353        case FIXES:
[7aed359]354            fputs("<styleUrl>#fix</styleUrl>", fh);
[1534ed9]355            break;
356        case EXPORTS:
[7aed359]357            fputs("<styleUrl>#exp</styleUrl>", fh);
[1534ed9]358            break;
359        case ENTS:
[7aed359]360            fputs("<styleUrl>#ent</styleUrl>", fh);
[1534ed9]361            break;
362    }
363    fputs("</Placemark>\n", fh);
364}
365
366void
367KML::footer()
368{
[ae6a68c]369    if (in_linestring)
[3b23819]370        fputs("</coordinates></LineString></MultiGeometry></Placemark>\n", fh);
[1534ed9]371    fputs("</Document></kml>\n", fh);
372}
Note: See TracBrowser for help on using the repository browser.