OpenCPN Partial API docs
TCDS_Binary_Harmonic.cpp
1 /***************************************************************************
2  *
3  * Project: OpenCPN
4  *
5  ***************************************************************************
6  * Copyright (C) 2013 by David S. Register *
7  * *
8  * This program is free software; you can redistribute it and/or modify *
9  * it under the terms of the GNU General Public License as published by *
10  * the Free Software Foundation; either version 2 of the License, or *
11  * (at your option) any later version. *
12  * *
13  * This program is distributed in the hope that it will be useful, *
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16  * GNU General Public License for more details. *
17  * *
18  * You should have received a copy of the GNU General Public License *
19  * along with this program; if not, write to the *
20  * Free Software Foundation, Inc., *
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
22  **************************************************************************/
23 
24 #include "TCDS_Binary_Harmonic.h"
25 #include "tcmgr.h"
26 #include <string>
27 /* Declarations for zoneinfo compatibility */
28 
29 /* Most of these entries are loaded from the tzdata.h include file. That
30  * file was generated from tzdata200c. */
31 
32 static const char *tz_names[][2] = {
33 #include "tzdata.h"
34 
35  /* Terminator */
36  {NULL, NULL}};
37 
38 /* Timelib Time services.
39  * Original XTide source code date: 1997-08-15
40  * Last modified 1998-09-07 by Mike Hopper for WXTide32
41  *
42  * Copyright (C) 1997 David Flater.
43  * Also starring: Geoff Kuenning; Rob Miracle; Dean Pentcheff;
44  * Eric Rosen.
45  *
46  * This program is free software; you can redistribute it and/or modify
47  * it under the terms of the GNU General Public License as published by
48  * the Free Software Foundation; either version 2 of the License, or
49  * (at your option) any later version.
50  *
51  * This program is distributed in the hope that it will be useful,
52  * but WITHOUT ANY WARRANTY; without even the implied warranty of
53  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
54  * GNU General Public License for more details.
55  *
56  * You should have received a copy of the GNU General Public License
57  * along with this program; if not, write to the Free Software
58  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
59  *
60  * Changes by Mike Hopper for WXTide32:
61  * Changed do_timestamp to shorten NT's LONG time zone names just the CAPS
62  * Changed _hpux selector to WIN32 to select HP timezone values.
63  * Added a whole set of remote TZ handler routines, all starting with "tz".
64  */
65 
66 #ifndef __WXMSW__
67 typedef unsigned short WORD;
68 typedef long LONG;
69 typedef char WCHAR;
70 
71 typedef struct _SYSTEMTIME {
72  WORD wYear;
73  WORD wMonth;
74  WORD wDayOfWeek;
75  WORD wDay;
76  WORD wHour;
77  WORD wMinute;
78  WORD wSecond;
79  WORD wMilliseconds;
81 
82 typedef struct _TIME_ZONE_INFORMATION {
83  LONG Bias;
84  WCHAR StandardName[32];
85  SYSTEMTIME StandardDate;
86  LONG StandardBias;
87  WCHAR DaylightName[32];
88  SYSTEMTIME DaylightDate;
89  LONG DaylightBias;
91 #else
92 #include <windows.h>
93 #endif
94 
95 /*-----------------9/24/2002 4:30PM-----------------
96  * An attempt to get Windoz to work with non-US timezones...
97  * --------------------------------------------------*/
98 typedef struct {
100  time_t year_beg;
101  time_t year_end;
102  time_t enter_std;
103  time_t enter_dst;
104  int isdst;
105 } tz_info_entry;
106 
107 tz_info_entry tz_info_local, tz_info_remote, *tz_info = &tz_info_local;
108 
109 /*-----------------9/24/2002 8:12AM-----------------
110  * Parse time string in the form [-][hh][:mm][:ss] into seconds.
111  * Returns updated string pointer and signed seconds.
112  * --------------------------------------------------*/
113 char *tz_time2sec(char *psrc, long *timesec) {
114  int neg;
115  long temp, mpy;
116  *timesec = 0;
117  mpy = 3600;
118  while (*psrc == ' ') psrc++; /* Skip leading blanks */
119  if (*psrc == '+') psrc++; /* Gobble leading + */
120  if (*psrc == '-') {
121  neg = TRUE;
122  psrc++;
123  } else
124  neg = FALSE;
125 
126  do {
127  temp = 0;
128  while (isdigit(*psrc)) temp = temp * 10 + (*(psrc++) - '0');
129 
130  *timesec = *timesec + temp * mpy;
131 
132  if (*psrc == ':') {
133  mpy /= 60;
134  psrc++;
135  }
136  } while (isdigit(*psrc));
137 
138  if (neg) *timesec = 0 - *timesec;
139 
140  return (psrc);
141 }
142 
143 /*-----------------9/24/2002 8:16AM-----------------
144  * Parse timezone name string.
145  * Returns string at psrc, updated psrc, and chars copied.
146  * --------------------------------------------------*/
147 static char *tz_parse_name(char *psrc, char *pdst, int maxlen) {
148  int nReturn;
149 
150  nReturn = 0;
151  while (*psrc == ' ') psrc++; /* Skip leading blanks */
152 
153  while (isalpha(*psrc) && nReturn < maxlen) {
154  *(pdst++) = *(psrc++);
155  nReturn++;
156  }
157 
158  *pdst = 0;
159  return (psrc);
160 }
161 
162 /*-----------------9/24/2002 8:38AM-----------------
163  * Parse tz rule string into SYSTEMTIME structure.
164  * --------------------------------------------------*/
165 static char *tz_parse_rule(char *psrc, SYSTEMTIME *st) {
166  int mol[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
167  long temp, mo;
168  if (*psrc == ',') psrc++; /* Gobble leading comma */
169 
170  while (*psrc == ' ') psrc++; /* Skip leading blanks */
171 
172  st->wYear = 0;
173  st->wMonth = 0;
174  st->wDay = 0;
175  st->wDayOfWeek = 0;
176  st->wHour = 0;
177  st->wMinute = 0;
178  st->wSecond = 0;
179  st->wMilliseconds = 0;
180 
181  if (*psrc == 'J') { /* Julian day (1 <= n <= 365) no leap */
182  psrc++; /* Gobble 'J' */
183  temp = 0;
184  while (isdigit(*psrc)) temp = temp * 10 + (*(psrc++) - '0');
185 
186  if (temp < 1 || temp > 365) return (0);
187  temp--;
188  for (mo = 0; temp >= mol[mo]; mo++) temp -= mol[mo];
189  st->wMonth = mo + 1;
190  st->wDay = temp + 1;
191  st->wYear = 1;
192  }
193 
194  else if (*psrc == 'M') {
195  psrc++; /* Gobble 'M' */
196 
197  temp = 0;
198  while (isdigit(*psrc)) temp = temp * 10 + (*(psrc++) - '0'); /* Get month */
199  if (temp < 1 || temp > 12 || *psrc != '.') return (0);
200  st->wMonth = (unsigned short)temp;
201 
202  psrc++; /* Gobble '.' */
203  temp = 0;
204  while (isdigit(*psrc))
205  temp = temp * 10 + (*(psrc++) - '0'); /* Get week number */
206  if (temp < 1 || temp > 5 || *psrc != '.') return (0);
207  st->wDay = (unsigned short)temp;
208 
209  psrc++; /* Gobble '.' */
210  temp = 0;
211  while (isdigit(*psrc))
212  temp = temp * 10 + (*(psrc++) - '0'); /* Get day of week number */
213  if (temp < 0 || temp > 6) return (0);
214  st->wDayOfWeek = (unsigned short)temp;
215  }
216 
217  if (*psrc == '/') { /* time is specified */
218  psrc++; /* Gobble '/' */
219  psrc = tz_time2sec(psrc, &temp);
220  if (temp < 0 || temp >= 86400) return (0);
221  st->wHour = temp / 3600;
222  temp = temp % 3600;
223  st->wMinute = temp / 60;
224  st->wSecond = temp % 60;
225  }
226  return (psrc);
227 }
228 
229 /*-----------------9/24/2002 3:38PM-----------------
230  * Load tz rule into timezone info data block.
231  * --------------------------------------------------*/
232 static void tz_load_rule(char *prule, tz_info_entry *tz_info_remote) {
233  prule = tz_parse_name(prule, (char *)tz_info_remote->tzi.StandardName, 30);
234  prule = tz_time2sec(prule, &tz_info_remote->tzi.Bias);
235  tz_info_remote->tzi.Bias /= 60;
236  tz_info_remote->tzi.StandardBias = 0;
237 
238  prule = tz_parse_name(prule, (char *)tz_info_remote->tzi.DaylightName, 30);
239  if (*(char *)tz_info_remote->tzi.DaylightName != '\0') {
240  prule = tz_time2sec(prule, &tz_info_remote->tzi.DaylightBias);
241  tz_info_remote->tzi.DaylightBias /= 60;
242  if (tz_info_remote->tzi.DaylightBias == 0)
243  tz_info_remote->tzi.DaylightBias = -60;
244  else
245  tz_info_remote->tzi.DaylightBias -= tz_info_remote->tzi.Bias;
246 
247  if (*prule == ',') {
248  prule = tz_parse_rule(prule, &tz_info_remote->tzi.DaylightDate);
249  if (prule && *prule == ',')
250  tz_parse_rule(prule, &tz_info_remote->tzi.StandardDate);
251  else
252  tz_parse_rule((char *)"M10.5.0/02:00:00",
253  &tz_info_remote->tzi.StandardDate);
254  } else { /* Default is US style tz change */
255  tz_parse_rule((char *)"M4.1.0/02:00:00",
256  &tz_info_remote->tzi.DaylightDate);
257  tz_parse_rule((char *)"M10.5.0/02:00:00",
258  &tz_info_remote->tzi.StandardDate);
259  }
260  } else { /* No DST */
261  tz_info_remote->tzi.DaylightDate.wMonth = 0;
262  tz_info_remote->isdst = 0;
263  }
264 }
265 
266 /* Attempt to load up the local time zone of the location. Moof! */
267 static void change_time_zone(const char *tz) {
268  // static char env_string[MAXARGLEN+1];
269  int index;
270 
271  if (*tz == ':') tz++; /* Gobble lead-in char */
272  /* Find the translation for the timezone string */
273  index = 0;
274  while (1) {
275  if (tz_names[index][0] == NULL) {
276  tz_info = &tz_info_local;
277  /* Not found. */
278  break;
279  }
280  if (!strcmp(tz_names[index][0], (tz))) {
281  char tz[40];
282  strncpy(tz, tz_names[index][1], 39);
283  tz_load_rule(tz, &tz_info_remote);
284  tz_info = &tz_info_remote;
285  /* Force compute next time this data is used */
286  tz_info->year_beg = 0; // Begin date/time is Jan 1, 1970
287  tz_info->year_end = 0; // End date/time is Jan 1, 1970
288  // sprintf (env_string, "TZ=%s", tz_names[index][1]);
289  break;
290  }
291  index++;
292  }
293 }
294 
295 TCDS_Binary_Harmonic::TCDS_Binary_Harmonic() {
296  m_cst_speeds = NULL;
297  m_cst_nodes = NULL;
298  m_cst_epochs = NULL;
299 
300  num_IDX = 0;
301  num_nodes = 0;
302  num_csts = 0;
303  num_epochs = 0;
304 
305  // Build the units array
306 }
307 
308 TCDS_Binary_Harmonic::~TCDS_Binary_Harmonic()
309 {
310  for (int i = 0; i < num_csts; i++) {
311  free(m_cst_nodes[i]);
312  free(m_cst_epochs[i]);
313  }
314  free(m_work_buffer);
315  free(m_cst_epochs);
316  free(m_cst_nodes);
317  free(m_cst_speeds);
318 }
319 
320 TC_Error_Code TCDS_Binary_Harmonic::LoadData(const wxString &data_file_path) {
321  if (!open_tide_db(data_file_path.mb_str())) return TC_TCD_FILE_CORRUPT;
322 
323  // Build the tables of constituent data
324 
325  DB_HEADER_PUBLIC hdr = get_tide_db_header();
326 
327  source_ident = wxString(hdr.version, wxConvUTF8);
328 
329  num_csts = hdr.constituents;
330  if (0 == num_csts) return TC_GENERIC_ERROR;
331 
332  num_nodes = hdr.number_of_years;
333  if (0 == num_nodes) return TC_GENERIC_ERROR;
334 
335  // Allocate a working buffer
336  m_work_buffer = (double *)malloc(num_csts * sizeof(double));
337 
338  // Constituent speeds
339  m_cst_speeds = (double *)malloc(num_csts * sizeof(double));
340 
341  for (int a = 0; a < num_csts; a++) {
342  m_cst_speeds[a] = get_speed(a);
343  m_cst_speeds[a] *= M_PI / 648000; /* Convert to radians per second */
344  }
345 
346  // Equilibrium tables by year
347  m_first_year = hdr.start_year;
348  num_epochs = hdr.number_of_years;
349 
350  m_cst_epochs = (double **)malloc(num_csts * sizeof(double *));
351  for (int i = 0; i < num_csts; i++)
352  m_cst_epochs[i] = (double *)malloc(num_epochs * sizeof(double));
353 
354  for (int i = 0; i < num_csts; i++) {
355  for (int year = 0; year < num_epochs; year++) {
356  m_cst_epochs[i][year] = get_equilibrium(i, year);
357  m_cst_epochs[i][year] *= M_PI / 180.0;
358  }
359  }
360 
361  // Node factors
362 
363  m_cst_nodes = (double **)malloc(num_csts * sizeof(double *));
364  for (int a = 0; a < num_csts; a++)
365  m_cst_nodes[a] = (double *)malloc(num_nodes * sizeof(double));
366 
367  for (int a = 0; a < num_csts; a++) {
368  for (int year = 0; year < num_nodes; year++)
369  m_cst_nodes[a][year] = get_node_factor(a, year);
370  }
371 
372  // now load and create the index
373 
374  TIDE_RECORD *ptiderec = (TIDE_RECORD *)calloc(sizeof(TIDE_RECORD), 1);
375  for (unsigned int i = 0; i < hdr.number_of_records; i++) {
376  read_tide_record(i, ptiderec);
377 
378  num_IDX++; // Keep counting entries for harmonic file stuff
379  IDX_entry *pIDX = new IDX_entry;
380  pIDX->source_data_type = SOURCE_TYPE_BINARY_HARMONIC;
381  pIDX->pDataSource = NULL;
382 
383  pIDX->Valid15 = 0;
384 
385  pIDX->pref_sta_data = NULL; // no reference data yet
386  pIDX->IDX_Useable = 1; // but assume data is OK
387  pIDX->IDX_tzname = NULL;
388 
389  pIDX->IDX_lon = ptiderec->header.longitude;
390  pIDX->IDX_lat = ptiderec->header.latitude;
391 
392  const char *tz = get_tzfile(ptiderec->header.tzfile);
393  change_time_zone((char *)tz);
394  if (tz_info) pIDX->IDX_time_zone = -tz_info->tzi.Bias;
395 
396  strncpy(pIDX->IDX_station_name, ptiderec->header.name, MAXNAMELEN);
397 
398  // Extract a "depth" value from name string, if present.
399  // Name string will contain "(depth xx ft)"
400  std::string name(ptiderec->header.name);
401  size_t n = name.find("depth");
402  if (n != std::string::npos) {
403  std::string d = name.substr(n);
404  std::string dp = d.substr(6);
405  size_t nd = dp.find_first_of(' ');
406  std::string sval = dp.substr(0, nd);
407  int depth = std::stoi(sval);
408  pIDX->current_depth = depth;
409  }
410 
411  pIDX->IDX_flood_dir = ptiderec->max_direction;
412  pIDX->IDX_ebb_dir = ptiderec->min_direction;
413 
414  if (REFERENCE_STATION == ptiderec->header.record_type) {
415  // Establish Station Type
416  wxString caplin(pIDX->IDX_station_name, wxConvUTF8);
417  caplin.MakeUpper();
418  if (caplin.Contains(_T("CURRENT")))
419  pIDX->IDX_type = 'C';
420  else
421  pIDX->IDX_type = 'T';
422 
423  int t1 = ptiderec->zone_offset;
424  double zone_offset = (double)(t1 / 100) + ((double)(t1 % 100)) / 60.;
425  // pIDX->IDX_time_zone = t1a;
426 
427  pIDX->IDX_ht_time_off = pIDX->IDX_lt_time_off = 0;
428  pIDX->IDX_ht_mpy = pIDX->IDX_lt_mpy = 1.0;
429  pIDX->IDX_ht_off = pIDX->IDX_lt_off = 0.0;
430  pIDX->IDX_ref_dbIndex = ptiderec->header.reference_station; // will be -1
431 
432  // build a Station_Data class, and add to member array
433 
434  Station_Data *psd = new Station_Data;
435 
436  psd->amplitude = (double *)malloc(num_csts * sizeof(double));
437  psd->epoch = (double *)malloc(num_csts * sizeof(double));
438  psd->station_name = (char *)malloc(ONELINER_LENGTH);
439 
440  strncpy(psd->station_name, ptiderec->header.name, MAXNAMELEN);
441  psd->station_type = pIDX->IDX_type;
442 
443  // Get meridian, which is seconds difference from UTC, not figuring DST,
444  // so that New York is always (-300 * 60)
445  psd->meridian = -(tz_info->tzi.Bias * 60);
446  psd->zone_offset = zone_offset;
447 
448  // Get units
449  strncpy(psd->unit, get_level_units(ptiderec->level_units), 40 - 1);
450  psd->unit[40 - 1] = '\0';
451 
452  psd->have_BOGUS = (findunit(psd->unit) != -1) &&
453  (known_units[findunit(psd->unit)].type == BOGUS);
454 
455  int unit_c;
456  if (psd->have_BOGUS)
457  unit_c = findunit("knots");
458  else
459  unit_c = findunit(psd->unit);
460 
461  if (unit_c != -1) {
462  strncpy(psd->units_conv, known_units[unit_c].name,
463  sizeof(psd->units_conv) - 1);
464  strncpy(psd->units_abbrv, known_units[unit_c].abbrv,
465  sizeof(psd->units_abbrv) - 1);
466  } else {
467  strncpy(psd->units_conv, psd->unit, 40 - 1);
468  psd->units_conv[40 - 1] = '\0';
469  strncpy(psd->units_abbrv, psd->unit, 20 - 1);
470  psd->units_abbrv[20 - 1] = '\0';
471  }
472 
473  // Get constituents
474  for (int a = 0; a < num_csts; a++) {
475  psd->amplitude[a] = ptiderec->amplitude[a];
476  psd->epoch[a] = ptiderec->epoch[a] * M_PI / 180.;
477  }
478 
479  psd->DATUM = ptiderec->datum_offset;
480 
481  m_msd_array.Add(psd); // add it to the member array
482  pIDX->pref_sta_data = psd;
483  pIDX->IDX_ref_dbIndex = i;
484  pIDX->have_offsets = 0;
485  } else if (SUBORDINATE_STATION == ptiderec->header.record_type) {
486  // Establish Station Type
487  wxString caplin(pIDX->IDX_station_name, wxConvUTF8);
488  caplin.MakeUpper();
489  if (caplin.Contains(_T("CURRENT")))
490  pIDX->IDX_type = 'c';
491  else
492  pIDX->IDX_type = 't';
493 
494  int t1 = ptiderec->max_time_add;
495  double t1a = (double)(t1 / 100) + ((double)(t1 % 100)) / 60.;
496  t1a *= 60; // Minutes
497  pIDX->IDX_ht_time_off = t1a;
498  pIDX->IDX_ht_mpy = ptiderec->max_level_multiply;
499  if (0. == pIDX->IDX_ht_mpy) pIDX->IDX_ht_mpy = 1.0;
500  pIDX->IDX_ht_off = ptiderec->max_level_add;
501 
502  t1 = ptiderec->min_time_add;
503  t1a = (double)(t1 / 100) + ((double)(t1 % 100)) / 60.;
504  t1a *= 60; // Minutes
505  pIDX->IDX_lt_time_off = t1a;
506  pIDX->IDX_lt_mpy = ptiderec->min_level_multiply;
507  if (0. == pIDX->IDX_lt_mpy) pIDX->IDX_lt_mpy = 1.0;
508  pIDX->IDX_lt_off = ptiderec->min_level_add;
509 
510  pIDX->IDX_ref_dbIndex = ptiderec->header.reference_station;
511  // strncpy(pIDX->IDX_reference_name, ptiderec->header.name,
512  // MAXNAMELEN);
513 
514  if (pIDX->IDX_ht_time_off || pIDX->IDX_ht_off != 0.0 ||
515  pIDX->IDX_lt_off != 0.0 || pIDX->IDX_ht_mpy != 1.0 ||
516  pIDX->IDX_lt_mpy != 1.0)
517  pIDX->have_offsets = 1;
518  }
519 
520  m_IDX_array.Add(pIDX);
521  }
522 
523  // Mark the index entries individually with invariant harmonic constants
524  unsigned int max_index = GetMaxIndex();
525  for (unsigned int i = 0; i < max_index; i++) {
526  IDX_entry *pIDX = GetIndexEntry(i);
527  if (pIDX) {
528  pIDX->num_nodes = num_nodes;
529  pIDX->num_csts = num_csts;
530  pIDX->num_epochs = num_epochs;
531  pIDX->m_cst_speeds = m_cst_speeds;
532  pIDX->m_cst_nodes = m_cst_nodes;
533  pIDX->m_cst_epochs = m_cst_epochs;
534  pIDX->first_year = m_first_year;
535  pIDX->m_work_buffer = m_work_buffer;
536  }
537  }
538  free(ptiderec);
539 
540  return TC_NO_ERROR;
541 }
542 
543 IDX_entry *TCDS_Binary_Harmonic::GetIndexEntry(int n_index) {
544  return &m_IDX_array[n_index];
545 }
546 
547 TC_Error_Code TCDS_Binary_Harmonic::LoadHarmonicData(IDX_entry *pIDX) {
548  // Find the indicated Master station
549  if (!strlen(pIDX->IDX_reference_name)) {
550  strncpy(pIDX->IDX_reference_name, get_station(pIDX->IDX_ref_dbIndex),
551  MAXNAMELEN - 1);
552  pIDX->IDX_reference_name[MAXNAMELEN - 1] = '\0';
553 
554  // TIDE_RECORD *ptiderec = (TIDE_RECORD *)calloc(sizeof(TIDE_RECORD),
555  // 1); read_tide_record (pIDX->IDX_ref_dbIndex, ptiderec); free(
556  // ptiderec );
557 
558  IDX_entry *pIDX_Ref = &m_IDX_array.Item(pIDX->IDX_ref_dbIndex);
559  Station_Data *pRefSta = pIDX_Ref->pref_sta_data;
560  pIDX->pref_sta_data = pRefSta;
561  pIDX->station_tz_offset =
562  -pRefSta->meridian + (pRefSta->zone_offset * 3600);
563  }
564  return TC_NO_ERROR;
565 }
Definition: IDX_entry.h:41