1 /*
2 
3    Support for Raymarine Waypoint File (.rwf).
4 
5    Copyright (C) 2006,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     Known format limits:
24 
25     	Waypoint name: max. 16 characters
26     	Route name:    max. 16 characters
27 	Routes:        max. 50 waypoints per route
28 	???:           the character set may be only a subset of std. ASCII
29 
30     History:
31 
32     	2006/10/30: Initial release (not yet in GPSBabel source tree)
33     	2006/11/08:
34 	2007/03/17: Remove GUIDs from writer (not really valid)
35 	            Fix "PredictedTwa" output
36 		    Initialize location with "My Waypoints"
37 		    Change default value for RcCount and RelSet (now 0)
38 	2007/04/18: Limit route names also to 16 characters
39 		    Bug-fix - add missing comma (write_route_wpt_cb/items)
40 		    Change line feeds to fixed CRLF
41 		    Sort waypoints by name (not really needed, but nice)
42 		    Add some MapSource icon names to icon mappings
43 		    Remove unused id from icon table
44 */
45 
46 #include "defs.h"
47 #include "csv_util.h"
48 #include "inifile.h"
49 
50 #include <QtCore/QString>
51 #include <cctype>
52 #include <cstdio>
53 #include <cstdlib>
54 
55 using guid_t = unsigned long long;
56 
57 static inifile_t* fin;
58 static gbfile* fout;
59 static Waypoint** waypt_table;
60 static short_handle hshort_wpt, hshort_rte;
61 static int waypt_table_sz, waypt_table_ct;
62 static int rte_index, rte_wpt_index;
63 static char* opt_location;
64 
65 #define MYNAME "raymarine"
66 
67 static
68 QVector<arglist_t> raymarine_args = {
69   { "location", &opt_location, "Default location", "My Waypoints", ARGTYPE_STRING, ARG_NOMINMAX , nullptr},
70 };
71 
72 /* from csv_util.c: convert excel time (days since 1900) to time_t and back again */
73 
74 #define EXCEL_TO_TIMET(a) ((a - 25569.0) * 86400.0)
75 #define TIMET_TO_EXCEL(a) ((a / 86400.0) + 25569.0)
76 
77 #define LINE_FEED "\r\n"
78 
79 /* Bitmaps */
80 
81 struct raymarine_symbol_mapping_t {
82   const char* name;
83   const char* mps_name;
84 };
85 
86 static const raymarine_symbol_mapping_t raymarine_symbols[] = {
87   { /* 0 */  "Unknown Symbol 0", nullptr },
88   { /* 1 */  "Unknown Symbol 1", nullptr },
89   { /* 2 */  "Unknown Symbol 2", nullptr },
90   { /* 3 */  "Red Square", nullptr },
91   { /* 4 */  "Big Fish", nullptr },
92   { /* 5 */  "Anchor", nullptr },
93   { /* 6 */  "Smiley", "Contact, Smiley" },
94   { /* 7 */  "Sad", nullptr },
95   { /* 8 */  "Red Button", "Navaid, Red" },
96   { /* 9 */  "Sailfish", nullptr },
97   { /* 10 */ "Danger", "Skull and Crossbones" },
98   { /* 11 */ "Attention", nullptr },
99   { /* 12 */ "Black Square", nullptr },
100   { /* 13 */ "Intl. Dive Flag", "Diver Down Flag 2" },
101   { /* 14 */ "Vessel", "Marina" },
102   { /* 15 */ "Lobster", nullptr },
103   { /* 16 */ "Buoy", "Buoy, White" },
104   { /* 17 */ "Exclamation", nullptr },
105   { /* 18 */ "Red X", nullptr },
106   { /* 19 */ "Check Mark", nullptr },
107   { /* 20 */ "Black Plus", nullptr },
108   { /* 21 */ "Black Cross", nullptr },
109   { /* 22 */ "MOB", nullptr },
110   { /* 23 */ "Billfish", nullptr },
111   { /* 24 */ "Bottom Mark", nullptr },
112   { /* 25 */ "Circle", "Circle, Red" },
113   { /* 26 */ "Diamond", "Block, Red" },
114   { /* 27 */ "Diamond Quarters", "Diamond, Red" },
115   { /* 28 */ "U.S. Dive Flag", "Diver Down Flag 1" },
116   { /* 29 */ "Dolphin", nullptr },
117   { /* 30 */ "Few Fish", nullptr },
118   { /* 31 */ "Multiple Fish", nullptr },
119   { /* 32 */ "Many Fish", nullptr },
120   { /* 33 */ "Single Fish", nullptr },
121   { /* 34 */ "Small Fish", nullptr },
122   { /* 35 */ "Marker", nullptr },
123   { /* 36 */ "Cocktails", "Bar" },
124   { /* 37 */ "Red Box Marker", nullptr },
125   { /* 38 */ "Reef", nullptr },
126   { /* 39 */ "Rocks", nullptr },
127   { /* 40 */ "Fish School", nullptr },
128   { /* 41 */ "Seaweed", "Weed Bed" },
129   { /* 42 */ "Shark", nullptr },
130   { /* 43 */ "Sportfisher", nullptr },
131   { /* 44 */ "Swimmer", "Swimming Area" },
132   { /* 45 */ "Top Mark", nullptr },
133   { /* 46 */ "Trawler", nullptr },
134   { /* 47 */ "Tree", nullptr },
135   { /* 48 */ "Triangle", "Triangle, Red" },
136   { /* 49 */ "Wreck", "Shipwreck" }
137 };
138 
139 #define RAYMARINE_SYMBOL_CT  sizeof(raymarine_symbols) / sizeof(raymarine_symbol_mapping_t)
140 #define RAYMARINE_STD_SYMBOL 3
141 
142 static int
find_symbol_num(const QString & descr)143 find_symbol_num(const QString& descr)
144 {
145   if (!descr.isNull()) {
146     const raymarine_symbol_mapping_t* a = &raymarine_symbols[0];
147 
148     for (unsigned int i = 0; i < RAYMARINE_SYMBOL_CT; i++, a++) {
149       if (descr.compare(a->name, Qt::CaseInsensitive) == 0) {
150         return i;
151       }
152       if (a->mps_name && (descr.compare(a->mps_name, Qt::CaseInsensitive) == 0)) {
153         return i;
154       }
155     }
156   }
157 
158   return RAYMARINE_STD_SYMBOL;
159 }
160 
161 /* ============================================= */
162 /* %%%    R A Y M A R I N E   R E A D E R    %%% */
163 /* ============================================= */
164 
165 static void
raymarine_rd_init(const QString & fname)166 raymarine_rd_init(const QString& fname)
167 {
168   fin = inifile_init(fname, MYNAME);
169 }
170 
171 static void
raymarine_rd_done()172 raymarine_rd_done()
173 {
174   inifile_done(fin);
175 }
176 
177 static void
raymarine_read()178 raymarine_read()
179 {
180   /* Read all waypoints */
181 
182   for (unsigned int ix = 0; ix < 0x3FFF; ix++) {
183     char sect[10];
184     QString str, name, lat, lon;
185 
186     /* built section identifier */
187     snprintf(sect, sizeof(sect), "Wp%u", ix);
188 
189     /* try to read our most expected values */
190     name = inifile_readstr(fin, sect, "Name");
191     if (name.isNull()) {
192       break;
193     }
194     lat = inifile_readstr(fin, sect, "Lat");
195     if (lat.isNull()) {
196       break;
197     }
198     lon = inifile_readstr(fin, sect, "Long");
199     if (lon.isNull()) {
200       break;
201     }
202 
203     auto* wpt = new Waypoint;
204     wpt->shortname = name;
205     wpt->latitude = lat.toDouble();
206     wpt->longitude = lon.toDouble();
207     waypt_add(wpt);
208 
209     /* try to read optional values */
210     str = inifile_readstr(fin, sect, "Notes");
211     if (!str.isEmpty()) {
212       wpt->notes = str;
213     }
214     str = inifile_readstr(fin, sect, "Time");
215     if (!str.isEmpty()) {
216       wpt->SetCreationTime(EXCEL_TO_TIMET(str.toDouble()));
217     }
218     str = inifile_readstr(fin, sect, "Bmp");
219     if (!str.isEmpty()) {
220       unsigned int symbol = str.toInt();
221 
222       if ((symbol < 3) && (symbol >= RAYMARINE_SYMBOL_CT)) {
223         symbol = RAYMARINE_STD_SYMBOL;
224       }
225       wpt->icon_descr = raymarine_symbols[symbol].name;
226     }
227   }
228 
229   /* Read all routes */
230 
231   for (unsigned int rx = 0; rx < 0x3FFF; rx++) {
232     char sect[10];
233     QString name;
234 
235     snprintf(sect, sizeof(sect), "Rt%u", rx);
236     name = inifile_readstr(fin, sect, "Name");
237     if (name.isNull()) {
238       break;
239     }
240 
241     auto* rte = new route_head;
242     rte->rte_name = name;
243     route_add_head(rte);
244 
245     for (int wx = 0; wx < 0x3FFF; wx++) {
246       char buff[32];
247 
248       snprintf(buff, sizeof(buff), "Mk%d", wx);
249       QString str = inifile_readstr(fin, sect, buff);
250       if (str.isEmpty()) {
251         break;
252       }
253 
254       Waypoint* wpt = find_waypt_by_name(str);
255       if (wpt == nullptr)
256         fatal(MYNAME ": No associated waypoint for route point %s (Route %s)!\n",
257               qPrintable(str), qPrintable(rte->rte_name));
258 
259       route_add_wpt(rte, new Waypoint(*wpt));
260     }
261   }
262 }
263 
264 /* ============================================= */
265 /* %%%    R A Y M A R I N E   W R I T E R    %%% */
266 /* ============================================= */
267 
268 /* make waypoint shortnames unique */
269 
270 static char
same_points(const Waypoint * A,const Waypoint * B)271 same_points(const Waypoint* A, const Waypoint* B)
272 {
273   return ( /* !!! We are case-sensitive !!! */
274            (A->shortname == B->shortname) &&
275            (A->latitude == B->latitude) &&
276            (A->longitude == B->longitude));
277 }
278 
279 static void
register_waypt(const Waypoint * ref,const char)280 register_waypt(const Waypoint* ref, const char)
281 {
282   auto* wpt = const_cast<Waypoint*>(ref);
283 
284   for (int i = 0; i < waypt_table_ct; i++) {
285     Waypoint* cmp = waypt_table[i];
286 
287     if (same_points(wpt, cmp)) {
288       wpt->extra_data = cmp->extra_data;
289       return;
290     }
291   }
292 
293   if (waypt_table_ct >= waypt_table_sz) {
294     waypt_table_sz += 32;
295     if (waypt_table) {
296       waypt_table = (Waypoint**) xrealloc(waypt_table, waypt_table_sz * sizeof(wpt));
297     } else {
298       waypt_table = (Waypoint**) xmalloc(waypt_table_sz * sizeof(wpt));
299     }
300   }
301 
302   wpt->extra_data = (void*)mkshort(hshort_wpt, CSTRc(wpt->shortname), false);
303 
304   waypt_table[waypt_table_ct] = wpt;
305   waypt_table_ct++;
306 }
307 
308 static void
enum_waypt_cb(const Waypoint * wpt)309 enum_waypt_cb(const Waypoint* wpt)
310 {
311   register_waypt(wpt, 0);
312 }
313 
314 static void
enum_rtept_cb(const Waypoint * wpt)315 enum_rtept_cb(const Waypoint* wpt)
316 {
317   register_waypt(wpt, 1);
318 }
319 
320 static int
qsort_cb(const void * a,const void * b)321 qsort_cb(const void* a, const void* b)
322 {
323   const Waypoint* wa = *(Waypoint**)a;
324   const Waypoint* wb = *(Waypoint**)b;
325   return wa->shortname.compare(wb->shortname);
326 }
327 
328 // TODO: this first arg is both a global and a param. That's weird.
329 static void
write_waypoint(gbfile * fileout,const Waypoint * wpt,const int waypt_no,const char * location)330 write_waypoint(gbfile* fileout, const Waypoint* wpt, const int waypt_no, const char* location)
331 {
332   QString notes = wpt->notes;
333   if (notes == nullptr) {
334     notes = wpt->description;
335     if (notes == nullptr) {
336       notes = "";
337     }
338   }
339   notes = csv_stringclean(notes, LINE_FEED);
340   double time = wpt->creation_time.isValid() ? TIMET_TO_EXCEL(wpt->GetCreationTime().toTime_t()) : TIMET_TO_EXCEL(gpsbabel_time);
341   char* name = (char*)wpt->extra_data;
342 
343   gbfprintf(fileout, "[Wp%d]" LINE_FEED
344             "Loc=%s" LINE_FEED
345             "Name=%s" LINE_FEED
346             "Lat=%.15f" LINE_FEED
347             "Long=%.15f" LINE_FEED,
348             waypt_no, location, name, wpt->latitude, wpt->longitude
349            );
350   gbfprintf(fileout, "Rng=%.15f" LINE_FEED
351             "Bear=%.15f" LINE_FEED
352             "Bmp=%d" LINE_FEED
353             "Fixed=1" LINE_FEED
354             "Locked=0" LINE_FEED
355             "Notes=%s" LINE_FEED,
356             0.0, 0.0,
357             find_symbol_num(wpt->icon_descr),
358             CSTR(notes)
359            );
360   gbfprintf(fileout, "Rel=" LINE_FEED
361             "RelSet=0" LINE_FEED
362             "RcCount=0" LINE_FEED
363             "RcRadius=%.15f" LINE_FEED
364             "Show=1" LINE_FEED
365             "RcShow=0" LINE_FEED
366             "SeaTemp=%.15f" LINE_FEED
367             "Depth=%.15f" LINE_FEED
368             "Time=%.10f00000" LINE_FEED,
369             0.0, -32678.0, 65535.0, time
370            );
371 }
372 
373 static void
write_route_head_cb(const route_head * rte)374 write_route_head_cb(const route_head* rte)
375 {
376   QString name = rte->rte_name;
377   if (name.isEmpty()) {
378     name=QString("Route%1").arg(rte_index);
379   }
380   name = mkshort(hshort_rte, name);
381   gbfprintf(fout, "[Rt%d]" LINE_FEED
382             "Name=%s" LINE_FEED
383             "Visible=1" LINE_FEED,
384             rte_index,
385             CSTR(name)
386            );
387   rte_index++;
388   rte_wpt_index = 0;
389 }
390 
391 static void
write_route_wpt_cb(const Waypoint * wpt)392 write_route_wpt_cb(const Waypoint* wpt)
393 {
394   static const char* items[] = {
395     "Cog",
396     "Eta",
397     "Length",
398     "PredictedDrift",
399     "PredictedSet",
400     "PredictedSog",
401     "PredictedTime",
402     "PredictedTwa",
403     "PredictedTwd",
404     "PredictedTws"
405   };
406 
407   gbfprintf(fout, "Mk%d=%s" LINE_FEED, rte_wpt_index, (char*)wpt->extra_data);
408   for (auto & item : items) {
409     gbfprintf(fout, "%s%d=%.15f" LINE_FEED, item, rte_wpt_index, 0.0);
410   }
411 
412   rte_wpt_index++;
413 }
414 
415 static void
enum_route_hdr_cb(const route_head * rte)416 enum_route_hdr_cb(const route_head* rte)
417 {
418   is_fatal(rte->rte_waypt_ct > 50,
419            MYNAME ": Routes with more than 50 points are not supported by Raymarine!");
420 }
421 
422 static short_handle
raymarine_new_short_handle()423 raymarine_new_short_handle()
424 {
425   short_handle res = mkshort_new_handle();
426 
427   setshort_length(res, 16);
428   setshort_badchars(res, ",");
429   setshort_mustupper(res, 0);
430   setshort_mustuniq(res, 1);
431   setshort_whitespace_ok(res, 1);
432   setshort_repeating_whitespace_ok(res, 1);
433 
434   return res;
435 }
436 
437 static void
raymarine_wr_init(const QString & fname)438 raymarine_wr_init(const QString& fname)
439 {
440   fout = gbfopen(fname, "wb", MYNAME);
441 
442   hshort_wpt = raymarine_new_short_handle();
443   hshort_rte = raymarine_new_short_handle();
444 }
445 
446 static void
raymarine_wr_done()447 raymarine_wr_done()
448 {
449   mkshort_del_handle(&hshort_wpt);
450   mkshort_del_handle(&hshort_rte);
451 
452   gbfclose(fout);
453 }
454 
455 static void
raymarine_write()456 raymarine_write()
457 {
458   int i;
459 
460   waypt_table_sz = 0;
461   waypt_table_ct = 0;
462   waypt_table = nullptr;
463 
464   /* enumerate all possible waypoints */
465   waypt_disp_all(enum_waypt_cb);
466   route_disp_all(enum_route_hdr_cb, nullptr, enum_rtept_cb);
467 
468   if (waypt_table_ct == 0) {
469     return;
470   }
471 
472   qsort(waypt_table, waypt_table_ct, sizeof(*waypt_table), qsort_cb);
473 
474   /* write out waypoint summary */
475   for (i = 0; i < waypt_table_ct; i++) {
476     Waypoint* wpt = waypt_table[i];
477     write_waypoint(fout, wpt, i, opt_location);
478   }
479 
480   /* write out all routes with their waypoints */
481   rte_index = 0;
482   route_disp_all(write_route_head_cb, nullptr, write_route_wpt_cb);
483 
484   /* release local used data */
485   for (i = 0; i < waypt_table_ct; i++) {
486     Waypoint* wpt = waypt_table[i];
487     xfree(wpt->extra_data);
488     wpt->extra_data = nullptr;
489   }
490   xfree(waypt_table);
491 }
492 
493 /* ================================================== */
494 /* %%%    M O D U L E   R E G I S T R A T I O N   %%% */
495 /* ================================================== */
496 
497 ff_vecs_t raymarine_vecs = {
498   ff_type_file,
499   {
500     (ff_cap)(ff_cap_read | ff_cap_write)	/* waypoints */,
501     ff_cap_none 			/* tracks */,
502     (ff_cap)(ff_cap_read | ff_cap_write) 	/* routes */,
503   },
504   raymarine_rd_init,
505   raymarine_wr_init,
506   raymarine_rd_done,
507   raymarine_wr_done,
508   raymarine_read,
509   raymarine_write,
510   nullptr,
511   &raymarine_args,
512   CET_CHARSET_ASCII, 0	/* should we force this to 1 ? */
513   , NULL_POS_OPS,
514   nullptr
515 };
516