1 /*
2
3 Support for Motorrad Routenplaner (Map&Guide) .bcr files.
4
5 Copyright (C) 2005-2007 Olaf Klein, o.b.klein@gpsbabel.org
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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22 /*
23 2006/01/22: reader simplified with inifile library
24 2007/01/30: new option prefer_shortnames
25 don't check global_opts.objective
26 2007/04&14: new handling of DESCRIPTION lines
27 */
28
29 #include <cmath> // for M_PI, atan, exp, log, tan
30 #include <cstdio> // for printf, snprintf, sscanf
31 #include <cstdlib> // for atof, atoi
32
33 #include <QtCore/QString> // for QString, operator+
34 #include <QtCore/Qt> // for CaseInsensitive
35 #include <QtCore/QtGlobal> // for foreach
36
37 #include "defs.h"
38 #include "csv_util.h" // for csv_stringclean
39 #include "garmin_tables.h" // for gt_find_desc_from_icon_number, gt_find_icon_number_from_desc, MAPSOURCE
40 #include "gbfile.h" // for gbfprintf, gbfclose, gbfopen, gbfile
41 #include "inifile.h" // for inifile_readstr, inifile_done, inifile_init, inifile_t
42
43
44 #define MYNAME "bcr"
45
46 #undef BCR_DEBUG
47
48 #define R_EARTH 6371000 /* radius of our big blue ball */
49 #define BCR_DEF_ICON "Standort"
50 #define BCR_DEF_MPS_ICON "Waypoint"
51 #define BCR_UNKNOWN /*(double) */ 999999999
52
53 /*
54 6371014 would be a better value when converting to f.e. to mapsoure,
55 but this seems to be used by Map&Guide when exporting to XML.
56 */
57
58 static gbfile* fout;
59 static int curr_rte_num, target_rte_num;
60 static double radius;
61 static inifile_t* ini;
62
63 /* placeholders for options */
64
65 static char* rtenum_opt;
66 static char* rtename_opt;
67 static char* radius_opt;
68 static char* prefer_shortnames_opt;
69
70 static
71 QVector<arglist_t> bcr_args = {
72 {
73 "index", &rtenum_opt, "Index of route to write (if more than one in source)",
74 nullptr, ARGTYPE_INT, "1", nullptr, nullptr
75 },
76 {
77 "name", &rtename_opt, "New name for the route",
78 nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr
79 },
80 {
81 "radius", &radius_opt, "Radius of our big earth (default 6371000 meters)", "6371000",
82 ARGTYPE_FLOAT, ARG_NOMINMAX, nullptr
83 },
84 {
85 "prefer_shortnames", &prefer_shortnames_opt, "Use shortname instead of description",
86 nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
87 },
88 };
89
90 struct bcr_icon_mapping_t {
91 const char* bcr_name;
92 const char* mps_name;
93 const char* symbol_DE;
94 bool warned;
95 };
96
97 static
98 bcr_icon_mapping_t bcr_icon_mapping[] = {
99 { BCR_DEF_ICON, BCR_DEF_MPS_ICON, BCR_DEF_ICON, false },
100 { "", BCR_DEF_MPS_ICON, "Eigene Adressen", false },
101 { "AdrMon alpen", "Summit", "Pass-Strassen", false },
102 { "AdrMon bauern", nullptr, "Bauern- und Biohoefe", false },
103 { "AdrMon cmpngs", "Campground", "Campingplaetzte", false },
104 { "AdrMon p_aeu", "Scenic Area", "Sehenswertes", false },
105 { "AdrMon p_beu", "Gas Station", "Tanken", false },
106 { "AdrMon p_deu", "Parking Area", "Parken", false },
107 { "AdrMon p_feu", "Restaurant", "Gastro", false },
108 { "AdrMon p_geu", "Museum", "Freizeit", false },
109 { "AdrMon p_heu", "Gas Station", "Tankstellen", false },
110 { "AdrMon p_keu", nullptr, "Faehrverbindungen", false },
111 { "AdrMon p_leu", nullptr, "Grenzuebergaenge", false },
112 { "AdrMon p_teu", nullptr, "Wein- und Sektgueter", false },
113 { "AdrMon RUINEN", "Ghost Town", "Burgen und Schloesser", false },
114 { "AdrMon NFHAUS", "Residence", "Naturfreundehaeuser", false },
115 { "AdrMon racing", "Bike Trail", "Rennstrecken", false },
116 { "AdrMon TNKRST", "Bar", "Tankraststaetten", false },
117 { "AdrMon tpclub", "Contact, Biker", "Motorrad-Clubs", false },
118 { "AdrMon tpequ", nullptr, "Motorrad-Equipment", false },
119 { "AdrMon tphot", "Hotel", "Motorrad-Hotels", false },
120 { "AdrMon tpmh", nullptr, "Motorradhaendler", false },
121 { "AdrMon tpss", "Restricted Area", "Sperrungen", false },
122 { "AdrMon tpsw", "Scenic Area", "Sehenswertes", false },
123 { "AdrMon tptref", nullptr, "Treffpunkte", false },
124 { "AdrMon VORTE", "Information", "Ortsinformationen", false },
125 { "AdrMon WEBCAM", nullptr, "WebCam-Standorte", false },
126 { "AdrMon youthh", nullptr, "Jugendherbergen", false },
127 { "Town", "City (Small)", "Orte", false },
128 { nullptr, nullptr, nullptr, false }
129 };
130
131 static void
bcr_handle_icon_str(const char * str,Waypoint * wpt)132 bcr_handle_icon_str(const char* str, Waypoint* wpt)
133 {
134 wpt->icon_descr = BCR_DEF_MPS_ICON;
135
136 for (bcr_icon_mapping_t* m = bcr_icon_mapping; (m->bcr_name); m++) {
137 if (case_ignore_strcmp(str, m->bcr_name) == 0) {
138 if (m->symbol_DE == nullptr) {
139 if (! m->warned) {
140 m->warned = true;
141 warning(MYNAME ": Unknown icon \"%s\" found. Please report.\n", str);
142 }
143 return;
144 }
145 wpt->description = m->symbol_DE;
146 if (m->mps_name != nullptr) {
147 int nr = gt_find_icon_number_from_desc(m->mps_name, MAPSOURCE);
148 wpt->icon_descr = gt_find_desc_from_icon_number(nr, MAPSOURCE);
149 }
150 return;
151 }
152 }
153 }
154
155 static const char*
get_bcr_icon_from_icon_descr(const QString & icon_descr)156 get_bcr_icon_from_icon_descr(const QString& icon_descr)
157 {
158 const char* result = BCR_DEF_ICON;
159
160 if (!icon_descr.isNull()) {
161 for (bcr_icon_mapping_t* m = bcr_icon_mapping; (m->bcr_name); m++) {
162 if (! m->mps_name) {
163 continue;
164 }
165 if (icon_descr.compare(m->mps_name, Qt::CaseInsensitive) == 0) {
166 result = m->bcr_name;
167 break;
168 }
169 }
170 }
171 return result;
172 }
173
174 static void
bcr_init_radius()175 bcr_init_radius()
176 {
177 if (radius_opt != nullptr) { /* preinitialize the earth radius */
178 radius = atof(radius_opt);
179 if (radius <= 0) {
180 fatal(MYNAME ": Sorry, the radius should be greater than zero!\n");
181 }
182 } else {
183 radius = (double)R_EARTH;
184 }
185
186 if (global_opts.verbose_status > 0) {
187 printf(MYNAME ": We calculate with radius %f meters.\n", radius);
188 }
189 }
190
191 static void
bcr_rd_init(const QString & fname)192 bcr_rd_init(const QString& fname)
193 {
194 ini = inifile_init(fname, MYNAME);
195 bcr_init_radius();
196 }
197
198 static void
bcr_rd_deinit()199 bcr_rd_deinit()
200 {
201 inifile_done(ini);
202 }
203
204 /* ------------------------------------------------------------*/
205
206 static void
bcr_create_waypts_from_route(route_head * route)207 bcr_create_waypts_from_route(route_head* route)
208 {
209 foreach (const Waypoint* wpt, route->waypoint_list) {
210 waypt_add(new Waypoint(*wpt));
211 }
212 }
213
214 static void
bcr_wgs84_to_mercator(const double lat,const double lon,int * north,int * east)215 bcr_wgs84_to_mercator(const double lat, const double lon, int* north, int* east)
216 {
217 double N = log(tan(lat * M_PI / 360 + M_PI / 4)) * radius;
218 double E = lon * radius * M_PI / 180.0;
219
220 if (lat > 0) {
221 N += 0.500000000001; /* we go from double to integer */
222 } else {
223 N -= 0.500000000001; /* it's time to round a little bit */
224 }
225 if (lon > 0) {
226 E += 0.500000000001;
227 } else {
228 E -= 0.500000000001;
229 }
230
231 *north = N;
232 *east = E;
233 }
234
235 static void
bcr_mercator_to_wgs84(const int north,const int east,double * lat,double * lon)236 bcr_mercator_to_wgs84(const int north, const int east, double* lat, double* lon)
237 {
238 *lat = 2 * (atan(exp(north / radius)) - M_PI / 4) / M_PI * 180.0;
239 *lon = (double)east * 180.0 / (radius * M_PI);
240 }
241
242 /* ------------------------------------------------------------- */
243
244 static void
bcr_data_read()245 bcr_data_read()
246 {
247 auto* route = new route_head;
248 route_add_head(route);
249
250 QString routename = inifile_readstr(ini, "client", "routename");
251 if (!routename.isNull()) {
252 route->rte_name = routename;
253 }
254
255 for (int index = 1; index > 0; index ++) {
256 char station[32];
257 QString str;
258 int mlat, mlon; /* mercator data */
259
260 snprintf(station, sizeof(station), "STATION%d", index);
261 str = inifile_readstr(ini, "coordinates", station);
262 if (str.isNull()) {
263 break;
264 }
265
266 if (2 != sscanf(CSTR(str), "%d,%d", &mlon, &mlat)) {
267 fatal(MYNAME ": structure error at %s (Coordinates)!\n", station);
268 }
269
270 auto* wpt = new Waypoint;
271
272 wpt->shortname = station;
273 bcr_mercator_to_wgs84(mlat, mlon, &wpt->latitude, &wpt->longitude);
274
275 str = inifile_readstr(ini, "client", station);
276 if (!str.isNull()) {
277 int cx = str.indexOf(',');
278 if (cx < 0) {
279 fatal(MYNAME ": structure error at %s (Client)!\n", station);
280 }
281 bcr_handle_icon_str(CSTR(str.left(cx)), wpt);
282 }
283
284 str = inifile_readstr(ini, "description", station);
285 if (!str.isNull()) {
286 QString note = str.section(',', 0, 0);
287 if (!note.isEmpty()) {
288 wpt->notes = note;
289 }
290 QString shortname = str.section(',', 1, 1);
291 if (!shortname.isEmpty()) {
292 wpt->shortname = shortname;
293 }
294 }
295
296 route_add_wpt(route, wpt);
297 }
298
299 /* remove empty route */
300 if (route->rte_waypt_ct == 0) {
301 route_del_head(route);
302 } else {
303 bcr_create_waypts_from_route(route);
304 }
305 }
306
307 /* %%% bcr write support %%% ----------------------------------- */
308
309 static void
bcr_wr_init(const QString & fname)310 bcr_wr_init(const QString& fname)
311 {
312 fout = gbfopen(fname, "wb", MYNAME);
313 bcr_init_radius();
314 }
315
316 static void
bcr_wr_deinit()317 bcr_wr_deinit()
318 {
319 gbfclose(fout);
320 }
321
bcr_write_line(gbfile * fileout,const QString & key,const int * index,const QString & value)322 static void bcr_write_line(gbfile* fileout, const QString& key,
323 const int* index, const QString& value)
324 {
325 if (value.isEmpty()) { // Windows. Add CR/LF on output.
326 gbfprintf(fileout, "%s\r\n", CSTR(key));
327 } else {
328 char* tmp = (value != nullptr) ? xstrdup(value) : xstrdup("");
329 if (index != nullptr) {
330 gbfprintf(fileout, "%s%d=%s\r\n", CSTR(key), *index, tmp);
331 } else {
332 gbfprintf(fileout, "%s=%s\r\n", CSTR(key), tmp);
333 }
334 xfree(tmp);
335 }
336 }
337
338 static void
bcr_route_header(const route_head * route)339 bcr_route_header(const route_head* route)
340 {
341 int north, east, nmax, emin;
342
343 curr_rte_num++;
344 if (curr_rte_num != target_rte_num) {
345 return;
346 }
347
348 bcr_write_line(fout, "[CLIENT]", nullptr, nullptr); /* client section */
349 bcr_write_line(fout, "REQUEST", nullptr, "TRUE");
350
351 QString sout = route->rte_name;
352 if (rtename_opt != nullptr) {
353 sout = rtename_opt;
354 }
355 if (sout != nullptr) {
356 bcr_write_line(fout, "ROUTENAME", nullptr, sout);
357 } else {
358 bcr_write_line(fout, "ROUTENAME", nullptr, "Route");
359 }
360
361 bcr_write_line(fout, "DESCRIPTIONLINES", nullptr, "0");
362
363 int i = 0;
364 foreach (const Waypoint* wpt, route->waypoint_list) {
365
366 i++;
367
368 const char* icon = get_bcr_icon_from_icon_descr(wpt->icon_descr);
369
370 sout = QString("%1,%2").arg(icon).arg(BCR_UNKNOWN,10);
371 bcr_write_line(fout, "STATION", &i, sout);
372 }
373
374 bcr_write_line(fout, "[COORDINATES]", nullptr, nullptr); /* coords section */
375
376 int nmin = emin = (1<<30);
377 int emax = nmax = -nmin;
378
379 i = 0;
380 foreach (const Waypoint* wpt, route->waypoint_list) {
381 i++;
382
383 bcr_wgs84_to_mercator(wpt->latitude, wpt->longitude, &north, &east);
384
385 if (north > nmax) {
386 nmax = north;
387 }
388 if (east > emax) {
389 emax = east;
390 }
391 if (north < nmin) {
392 nmin = north;
393 }
394 if (east < emin) {
395 emin = east;
396 }
397
398 sout = QString::number(east) + "," + QString::number(north);
399 bcr_write_line(fout, "STATION", &i, sout);
400 }
401
402 bcr_write_line(fout, "[DESCRIPTION]", nullptr, nullptr); /* descr. section */
403
404 i = 0;
405 foreach (const Waypoint* wpt, route->waypoint_list) {
406 QString s2;
407
408 i++;
409 QString s1 = wpt->notes;
410 if (s1.isEmpty()) {
411 s1 = wpt->description;
412 }
413
414 if (prefer_shortnames_opt || (s1.isEmpty())) {
415 s2 = s1;
416 s1 = wpt->shortname;
417 } else {
418 s2 = wpt->shortname;
419 }
420
421 if (s1.isEmpty()) {
422 s1 = QString();
423 } else {
424 s1 = csv_stringclean(s1, ",\t\r\n");
425 }
426 if (s2.isEmpty()) {
427 s2 = QString();
428 } else {
429 s2 = csv_stringclean(s2, ",\t\r\n");
430 }
431
432 if (sout.isEmpty()) {
433 sout = QString("%1,%2,@,0").arg(s1, s1);
434 } else {
435 sout = QString("%1,%2,@,0").arg(s1, s2);
436 }
437
438 bcr_write_line(fout, "STATION", &i, sout);
439 }
440
441 bcr_write_line(fout, "[ROUTE]", nullptr, nullptr); /* route section */
442
443 sout = QString::number(emin) + "," +
444 QString::number(nmax) + "," +
445 QString::number(emax) + "," +
446 QString::number(nmin);
447 bcr_write_line(fout, "ROUTERECT", nullptr, sout);
448 }
449
450 static void
bcr_data_write()451 bcr_data_write()
452 {
453 target_rte_num = 1;
454
455 if (rtenum_opt != nullptr) {
456 target_rte_num = atoi(rtenum_opt);
457 if (((unsigned)target_rte_num > route_count()) || (target_rte_num < 1))
458 fatal(MYNAME ": invalid route number %d (1..%d))!\n",
459 target_rte_num, route_count());
460 }
461 curr_rte_num = 0;
462 route_disp_all(bcr_route_header, nullptr, nullptr);
463 }
464
465 ff_vecs_t bcr_vecs = {
466 ff_type_file,
467 { ff_cap_none, ff_cap_none, (ff_cap)(ff_cap_read | ff_cap_write)},
468 bcr_rd_init,
469 bcr_wr_init,
470 bcr_rd_deinit,
471 bcr_wr_deinit,
472 bcr_data_read,
473 bcr_data_write,
474 nullptr,
475 &bcr_args,
476 CET_CHARSET_MS_ANSI, 0, /* CET-REVIEW */
477 NULL_POS_OPS,
478 nullptr
479 };
480