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