source: git/src/kml.cc

walls-data
Last change on this file was 4c83f84, checked in by Olly Betts <olly@…>, 4 days ago

Don't check HAVE_CONFIG_H in most cases

This check is only useful for img.c, which is intended to be usable
outside of Survex (and had fallbacks for functions which may not be
available which will get used if built in a non-autotools project).
For all the other source files it's just useless boilerplate.

  • Property mode set to 100644
File size: 9.6 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#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, const char *, 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    fputs("<Style id=\"fix\"><IconStyle>"
118          "<Icon><href>http://maps.google.com/mapfiles/kml/paddle/red-blank.png</href></Icon>"
119          "</IconStyle></Style>\n", fh);
120    fputs("<Style id=\"exp\"><IconStyle>"
121          "<Icon><href>http://maps.google.com/mapfiles/kml/paddle/blu-blank.png</href></Icon>"
122          "</IconStyle></Style>\n", fh);
123    fputs("<Style id=\"ent\"><IconStyle>"
124          "<Icon><href>http://maps.google.com/mapfiles/kml/paddle/grn-blank.png</href></Icon>"
125          "</IconStyle></Style>\n", fh);
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
131void
132KML::start_pass(int)
133{
134    if (in_linestring) {
135        fputs("</coordinates></LineString></MultiGeometry></Placemark>\n", fh);
136        in_linestring = false;
137    }
138}
139
140void
141KML::line(const img_point *p1, const img_point *p, unsigned /*flags*/, bool fPendingMove)
142{
143    if (fPendingMove) {
144        if (!in_linestring) {
145            in_linestring = true;
146            fputs("<Placemark><MultiGeometry>\n", fh);
147        } else {
148            fputs("</coordinates></LineString>\n", fh);
149        }
150        if (clamp_to_ground) {
151            fputs("<LineString><coordinates>\n", fh);
152        } else {
153            fputs("<LineString><altitudeMode>absolute</altitudeMode><coordinates>\n", fh);
154        }
155
156        PJ_COORD coord{{p1->x, p1->y, p1->z, HUGE_VAL}};
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        }
163        // %.8f is at worst just over 1mm.
164        fprintf(fh, "%.8f,%.8f,%.2f\n",
165                coord.xyzt.x,
166                coord.xyzt.y,
167                coord.xyzt.z);
168    }
169
170    PJ_COORD coord{{p->x, p->y, p->z, HUGE_VAL}};
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
176    }
177    // %.8f is at worst just over 1mm.
178    fprintf(fh, "%.8f,%.8f,%.2f\n",
179            coord.xyzt.x,
180            coord.xyzt.y,
181            coord.xyzt.z);
182}
183
184void
185KML::xsect(const img_point *p, double angle, double d1, double d2)
186{
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    }
192
193    double s = sin(rad(angle));
194    double c = cos(rad(angle));
195
196    {
197        PJ_COORD coord{{p->x + s * d1, p->y + c * d1, p->z, HUGE_VAL}};
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    {
212        PJ_COORD coord{{p->x - s * d2, p->y - c * d2, p->z, HUGE_VAL}};
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
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) {
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;
239    }
240
241    double s = sin(rad(angle));
242    double c = cos(rad(angle));
243
244    PJ_COORD coord{{p->x + s * d, p->y + c * d, p->z, HUGE_VAL}};
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);
256}
257
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
264    PJ_COORD coord1{{p->x + s * d1, p->y + c * d1, p->z, HUGE_VAL}};
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
275    PJ_COORD coord2{{p->x - s * d2, p->y - c * d2, p->z, HUGE_VAL}};
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;
285
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.
289
290    if (!in_passage){
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        }
301
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());
305
306        fprintf(fh, "%.8f,%.8f,%.2f\n", x1, y1, z1);
307        fprintf(fh, "%.8f,%.8f,%.2f\n", x2, y2, z2);
308
309        // Close the ring.
310        fprintf(fh, "%.8f,%.8f,%.2f\n", v2.GetX(), v2.GetY(), v2.GetZ());
311
312        fputs("</coordinates></LinearRing></outerBoundaryIs>"
313              "</Polygon>\n", fh);
314    }
315
316    v2 = Vector3(x2, y2, z2);
317    v1 = Vector3(x1, y1, z1);
318}
319
320void
321KML::tube_end()
322{
323    if (in_passage){
324        fputs("</MultiGeometry></Placemark>\n", fh);
325        in_passage = false;
326    }
327    if (in_wall) {
328        fputs("</coordinates></LineString></Placemark>\n", fh);
329        in_wall = false;
330    }
331}
332
333void
334KML::label(const img_point *p, const wxString& str, bool /*fSurface*/, int type)
335{
336    const char* s = str.utf8_str();
337    PJ_COORD coord{{p->x, p->y, p->z, HUGE_VAL}};
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    }
344    // %.8f is at worst just over 1mm.
345    fprintf(fh, "<Placemark><Point><coordinates>%.8f,%.8f,%.2f</coordinates></Point><name>",
346            coord.xyzt.x,
347            coord.xyzt.y,
348            coord.xyzt.z);
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:
354            fputs("<styleUrl>#fix</styleUrl>", fh);
355            break;
356        case EXPORTS:
357            fputs("<styleUrl>#exp</styleUrl>", fh);
358            break;
359        case ENTS:
360            fputs("<styleUrl>#ent</styleUrl>", fh);
361            break;
362    }
363    fputs("</Placemark>\n", fh);
364}
365
366void
367KML::footer()
368{
369    if (in_linestring)
370        fputs("</coordinates></LineString></MultiGeometry></Placemark>\n", fh);
371    fputs("</Document></kml>\n", fh);
372}
Note: See TracBrowser for help on using the repository browser.