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