OpenCPN Partial API docs
chartimg.cpp
1 /******************************************************************************
2  *
3  * Project: OpenCPN
4  * Purpose: ChartBase, ChartBaseBSB and Friends
5  * Author: David Register
6  *
7  ***************************************************************************
8  * Copyright (C) 2015 by David S. Register *
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  * This program is distributed in the hope that it will be useful, *
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18  * GNU General Public License for more details. *
19  * *
20  * You should have received a copy of the GNU General Public License *
21  * along with this program; if not, write to the *
22  * Free Software Foundation, Inc., *
23  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24  ***************************************************************************
25  *
26  */
27 
28 // ============================================================================
29 // declarations
30 // ============================================================================
31 
32 // ----------------------------------------------------------------------------
33 // headers
34 // ----------------------------------------------------------------------------
35 
36 #include <assert.h>
37 
38 // For compilers that support precompilation, includes "wx.h".
39 #include <wx/wxprec.h>
40 
41 #ifndef WX_PRECOMP
42 #include <wx/wx.h>
43 #endif // precompiled headers
44 
45 // Why are these not in wx/prec.h?
46 #include <wx/dir.h>
47 #include <wx/stream.h>
48 #include <wx/wfstream.h>
49 #include <wx/tokenzr.h>
50 #include <wx/filename.h>
51 #include <wx/image.h>
52 #include <wx/fileconf.h>
53 #include <sys/stat.h>
54 
55 #include "config.h"
56 #include "chartimg.h"
57 #include "ocpn_pixel.h"
58 #include "model/chartdata_input_stream.h"
59 
60 #ifndef __WXMSW__
61 #include <signal.h>
62 #include <setjmp.h>
63 
64 #define OCPN_USE_CONFIG 1
65 
66 struct sigaction sa_all_chart;
67 struct sigaction sa_all_previous;
68 
69 sigjmp_buf env_chart; // the context saved by sigsetjmp();
70 
71 void catch_signals_chart(int signo) {
72  switch (signo) {
73  case SIGSEGV:
74  siglongjmp(env_chart, 1); // jump back to the setjmp() point
75  break;
76 
77  default:
78  break;
79  }
80 }
81 
82 #endif
83 
84 // Missing from MSW include files
85 #ifdef _MSC_VER
86 typedef __int32 int32_t;
87 typedef unsigned __int32 uint32_t;
88 typedef __int64 int64_t;
89 typedef unsigned __int64 uint64_t;
90 #endif
91 
92 // ----------------------------------------------------------------------------
93 // Random Prototypes
94 // ----------------------------------------------------------------------------
95 
96 #ifdef OCPN_USE_CONFIG
97 class MyConfig;
98 extern MyConfig *pConfig;
99 #endif
100 
101 typedef struct {
102  float y;
103  float x;
104 } MyFlPoint;
105 
106 bool G_FloatPtInPolygon(MyFlPoint *rgpts, int wnumpts, float x, float y);
107 
108 // ----------------------------------------------------------------------------
109 // private classes
110 // ----------------------------------------------------------------------------
111 
112 // ============================================================================
113 // ThumbData implementation
114 // ============================================================================
115 
116 ThumbData::ThumbData() { pDIBThumb = NULL; }
117 
118 ThumbData::~ThumbData() { delete pDIBThumb; }
119 
120 // ============================================================================
121 // Palette implementation
122 // ============================================================================
123 opncpnPalette::opncpnPalette() {
124  // Index into palette is 1-based, so predefine the first entry as null
125  nFwd = 1;
126  nRev = 1;
127  FwdPalette = (int *)malloc(sizeof(int));
128  RevPalette = (int *)malloc(sizeof(int));
129  FwdPalette[0] = 0;
130  RevPalette[0] = 0;
131 }
132 
133 opncpnPalette::~opncpnPalette() {
134  if (NULL != FwdPalette) free(FwdPalette);
135  if (NULL != RevPalette) free(RevPalette);
136 }
137 
138 // ============================================================================
139 // ChartBase implementation
140 // ============================================================================
141 ChartBase::ChartBase() {
142  m_depth_unit_id = DEPTH_UNIT_UNKNOWN;
143 
144  pThumbData = new ThumbData;
145 
146  m_global_color_scheme = GLOBAL_COLOR_SCHEME_RGB;
147 
148  bReadyToRender = false;
149 
150  Chart_Error_Factor = 0;
151 
152  m_Chart_Scale = 10000; // a benign value
153  m_Chart_Skew = 0.0;
154 
155  m_nCOVREntries = 0;
156  m_pCOVRTable = NULL;
157  m_pCOVRTablePoints = NULL;
158 
159  m_nNoCOVREntries = 0;
160  m_pNoCOVRTable = NULL;
161  m_pNoCOVRTablePoints = NULL;
162 
163  m_EdDate = wxInvalidDateTime;
164 
165  m_lon_datum_adjust = 0.;
166  m_lat_datum_adjust = 0.;
167 
168  m_projection = PROJECTION_MERCATOR; // default
169 }
170 
171 ChartBase::~ChartBase() {
172  delete pThumbData;
173 
174  // Free the COVR tables
175 
176  for (unsigned int j = 0; j < (unsigned int)m_nCOVREntries; j++)
177  free(m_pCOVRTable[j]);
178 
179  free(m_pCOVRTable);
180  free(m_pCOVRTablePoints);
181 
182  // Free the No COVR tables
183 
184  for (unsigned int j = 0; j < (unsigned int)m_nNoCOVREntries; j++)
185  free(m_pNoCOVRTable[j]);
186 
187  free(m_pNoCOVRTable);
188  free(m_pNoCOVRTablePoints);
189 }
190 
191 wxString ChartBase::GetHashKey() const {
192  wxString key = GetFullPath();
193  wxChar separator = wxFileName::GetPathSeparator();
194  for (unsigned int pos = 0; pos < key.size(); pos = key.find(separator, pos))
195  key.replace(pos, 1, _T("!"));
196  return key;
197 }
198 
199 /*
200 int ChartBase::Continue_BackgroundHiDefRender(void)
201 {
202  return BR_DONE_NOP; // signal "done, no refresh"
203 }
204 */
205 
206 // ============================================================================
207 // ChartDummy implementation
208 // ============================================================================
209 
210 ChartDummy::ChartDummy() {
211  m_pBM = NULL;
212  m_ChartType = CHART_TYPE_DUMMY;
213  m_ChartFamily = CHART_FAMILY_UNKNOWN;
214  m_Chart_Scale = 22000000;
215 
216  m_FullPath = _T("No Chart Available");
217  m_Description = m_FullPath;
218 }
219 
220 ChartDummy::~ChartDummy() { delete m_pBM; }
221 
222 InitReturn ChartDummy::Init(const wxString &name, ChartInitFlag init_flags) {
223  return INIT_OK;
224 }
225 
226 void ChartDummy::SetColorScheme(ColorScheme cs, bool bApplyImmediate) {}
227 
228 ThumbData *ChartDummy::GetThumbData(int tnx, int tny, float lat, float lon) {
229  return (ThumbData *)NULL;
230 }
231 
232 bool ChartDummy::UpdateThumbData(double lat, double lon) { return FALSE; }
233 
234 bool ChartDummy::GetChartExtent(Extent *pext) {
235  pext->NLAT = 80;
236  pext->SLAT = -80;
237  pext->ELON = 179;
238  pext->WLON = -179;
239 
240  return true;
241 }
242 
243 bool ChartDummy::RenderRegionViewOnGL(const wxGLContext &glc,
244  const ViewPort &VPoint,
245  const OCPNRegion &RectRegion,
246  const LLRegion &Region) {
247  return true;
248 }
249 
250 bool ChartDummy::RenderRegionViewOnDC(wxMemoryDC &dc, const ViewPort &VPoint,
251  const OCPNRegion &Region) {
252  return RenderViewOnDC(dc, VPoint);
253 }
254 
255 bool ChartDummy::RenderViewOnDC(wxMemoryDC &dc, const ViewPort &VPoint) {
256  if (m_pBM && m_pBM->IsOk()) {
257  if ((m_pBM->GetWidth() != VPoint.pix_width) ||
258  (m_pBM->GetHeight() != VPoint.pix_height)) {
259  delete m_pBM;
260  m_pBM = NULL;
261  }
262  } else {
263  delete m_pBM;
264  m_pBM = NULL;
265  }
266 
267  if (VPoint.pix_width && VPoint.pix_height) {
268  if (NULL == m_pBM)
269  m_pBM = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
270 
271  dc.SelectObject(*m_pBM);
272 
273  dc.SetBackground(*wxBLACK_BRUSH);
274  dc.Clear();
275  }
276 
277  return true;
278 }
279 
280 bool ChartDummy::AdjustVP(ViewPort &vp_last, ViewPort &vp_proposed) {
281  return false;
282 }
283 
284 void ChartDummy::GetValidCanvasRegion(const ViewPort &VPoint,
285  OCPNRegion *pValidRegion) {
286  pValidRegion->Clear();
287  pValidRegion->Union(0, 0, 1, 1);
288 }
289 
290 LLRegion ChartDummy::GetValidRegion() { return LLRegion(); }
291 
292 // ============================================================================
293 // ChartGEO implementation
294 // ============================================================================
295 ChartGEO::ChartGEO() { m_ChartType = CHART_TYPE_GEO; }
296 
297 ChartGEO::~ChartGEO() {}
298 
299 InitReturn ChartGEO::Init(const wxString &name, ChartInitFlag init_flags) {
300 #define BUF_LEN_MAX 4096
301 
302  PreInit(name, init_flags, GLOBAL_COLOR_SCHEME_DAY);
303 
304  char buffer[BUF_LEN_MAX];
305 
306  ifs_hdr =
307  new wxFFileInputStream(name); // open the file as a read-only stream
308 
309  m_filesize = wxFileName::GetSize(name);
310 
311  if (!ifs_hdr->IsOk()) return INIT_FAIL_REMOVE;
312 
313  int nPlypoint = 0;
314  Plypoint *pPlyTable = (Plypoint *)malloc(sizeof(Plypoint));
315 
316  m_FullPath = name;
317  m_Description = m_FullPath;
318 
319  wxFileName GEOFile(m_FullPath);
320 
321  wxString Path;
322  Path = GEOFile.GetPath(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME);
323 
324  // Read the GEO file, extracting useful information
325 
326  ifs_hdr->SeekI(0, wxFromStart); // rewind
327 
328  Size_X = Size_Y = 0;
329 
330  while ((ReadBSBHdrLine(ifs_hdr, &buffer[0], BUF_LEN_MAX)) != 0) {
331  wxString str_buf(buffer, wxConvUTF8);
332  if (!strncmp(buffer, "Bitmap", 6)) {
333  wxStringTokenizer tkz(str_buf, _T("="));
334  wxString token = tkz.GetNextToken();
335  if (token.IsSameAs(_T("Bitmap"), TRUE)) {
336  pBitmapFilePath = new wxString();
337 
338  int i;
339  i = tkz.GetPosition();
340  pBitmapFilePath->Clear();
341  while (buffer[i]) {
342  pBitmapFilePath->Append(buffer[i]);
343  i++;
344  }
345  }
346  }
347 
348  else if (!strncmp(buffer, "Scale", 5)) {
349  wxStringTokenizer tkz(str_buf, _T("="));
350  wxString token = tkz.GetNextToken();
351  if (token.IsSameAs(_T("Scale"), TRUE)) // extract Scale
352  {
353  int i;
354  i = tkz.GetPosition();
355  m_Chart_Scale = atoi(&buffer[i]);
356  }
357  }
358 
359  else if (!strncmp(buffer, "Depth", 5)) {
360  wxStringTokenizer tkz(str_buf, _T("="));
361  wxString token = tkz.GetNextToken();
362  if (token.IsSameAs(_T("Depth Units"), FALSE)) // extract Depth Units
363  {
364  int i;
365  i = tkz.GetPosition();
366  wxString str(&buffer[i], wxConvUTF8);
367  m_DepthUnits = str.Trim();
368  }
369  }
370 
371  else if (!strncmp(buffer, "Point", 5)) // Extract RefPoints
372  {
373  int i, xr, yr;
374  float ltr, lnr;
375  sscanf(&buffer[0], "Point%d=%f %f %d %d", &i, &lnr, &ltr, &yr, &xr);
376  pRefTable =
377  (Refpoint *)realloc(pRefTable, sizeof(Refpoint) * (nRefpoint + 1));
378  pRefTable[nRefpoint].xr = xr;
379  pRefTable[nRefpoint].yr = yr;
380  pRefTable[nRefpoint].latr = ltr;
381  pRefTable[nRefpoint].lonr = lnr;
382  pRefTable[nRefpoint].bXValid = 1;
383  pRefTable[nRefpoint].bYValid = 1;
384 
385  nRefpoint++;
386 
387  }
388 
389  else if (!strncmp(buffer, "Vertex", 6)) {
390  int i;
391  float ltp, lnp;
392  sscanf(buffer, "Vertex%d=%f %f", &i, &ltp, &lnp);
393  Plypoint *tmp = pPlyTable;
394  pPlyTable =
395  (Plypoint *)realloc(pPlyTable, sizeof(Plypoint) * (nPlypoint + 1));
396  if (NULL == pPlyTable) {
397  free(tmp);
398  tmp = NULL;
399  } else {
400  pPlyTable[nPlypoint].ltp = ltp;
401  pPlyTable[nPlypoint].lnp = lnp;
402  nPlypoint++;
403  }
404  }
405 
406  else if (!strncmp(buffer, "Date Pub", 8)) {
407  char date_string[40];
408  char date_buf[10];
409  sscanf(buffer, "Date Published=%s\r\n", &date_string[0]);
410  wxString date_wxstr(date_string, wxConvUTF8);
411  wxDateTime dt;
412  if (dt.ParseDate(date_wxstr)) // successful parse?
413  {
414  sprintf(date_buf, "%d", dt.GetYear());
415  } else {
416  sscanf(date_string, "%s", date_buf);
417  }
418  m_PubYear = wxString(date_buf, wxConvUTF8);
419  }
420 
421  else if (!strncmp(buffer, "Skew", 4)) {
422  wxStringTokenizer tkz(str_buf, _T("="));
423  wxString token = tkz.GetNextToken();
424  if (token.IsSameAs(_T("Skew Angle"), FALSE)) // extract Skew Angle
425  {
426  int i;
427  i = tkz.GetPosition();
428  float fcs;
429  sscanf(&buffer[i], "%f,", &fcs);
430  m_Chart_Skew = fcs;
431  }
432  }
433 
434  else if (!strncmp(buffer, "Latitude Offset", 15)) {
435  wxStringTokenizer tkz(str_buf, _T("="));
436  wxString token = tkz.GetNextToken();
437  if (token.IsSameAs(_T("Latitude Offset"), FALSE)) {
438  int i;
439  i = tkz.GetPosition();
440  float lto;
441  sscanf(&buffer[i], "%f,", &lto);
442  m_dtm_lat = lto;
443  }
444  }
445 
446  else if (!strncmp(buffer, "Longitude Offset", 16)) {
447  wxStringTokenizer tkz(str_buf, _T("="));
448  wxString token = tkz.GetNextToken();
449  if (token.IsSameAs(_T("Longitude Offset"), FALSE)) {
450  int i;
451  i = tkz.GetPosition();
452  float lno;
453  sscanf(&buffer[i], "%f,", &lno);
454  m_dtm_lon = lno;
455  }
456  }
457 
458  else if (!strncmp(buffer, "Datum", 5)) {
459  wxStringTokenizer tkz(str_buf, _T("="));
460  wxString token = tkz.GetNextToken();
461  if (token.IsSameAs(_T("Datum"), FALSE)) {
462  token = tkz.GetNextToken();
463  m_datum_str = token;
464  }
465  }
466 
467  else if (!strncmp(buffer, "Name", 4)) {
468  wxStringTokenizer tkz(str_buf, _T("="));
469  wxString token = tkz.GetNextToken();
470  if (token.IsSameAs(_T("Name"), FALSE)) // Name
471  {
472  int i;
473  i = tkz.GetPosition();
474  m_Name.Clear();
475  while (isprint(buffer[i]) && (i < 80)) m_Name.Append(buffer[i++]);
476  }
477  }
478  } // while
479 
480  // Extract the remaining data from .NOS Bitmap file
481  ifs_bitmap = NULL;
482 
483  // Something wrong with the .geo file, there is no Bitmap reference
484  // This is where the arbitrarily bad file is caught, such as
485  // a file with.GEO extension that is not really a chart
486 
487  if (pBitmapFilePath == NULL) {
488  free(pPlyTable);
489  return INIT_FAIL_REMOVE;
490  }
491 
492  wxString NOS_Name(*pBitmapFilePath); // take a copy
493 
494  wxDir target_dir(Path);
495  wxArrayString file_array;
496  int nfiles = wxDir::GetAllFiles(Path, &file_array);
497  int ifile;
498 
499  pBitmapFilePath->Prepend(Path);
500 
501  wxFileName NOS_filename(*pBitmapFilePath);
502  if (!NOS_filename.FileExists()) {
503  // File as fetched verbatim from the .geo file doesn't exist.
504  // Try all possible upper/lower cases
505  // Extract the filename and extension
506  wxString fname(NOS_filename.GetName());
507  wxString fext(NOS_filename.GetExt());
508 
509  // Try all four combinations, the hard way
510  // case 1
511  fname.MakeLower();
512  fext.MakeLower();
513  NOS_filename.SetName(fname);
514  NOS_filename.SetExt(fext);
515 
516  if (NOS_filename.FileExists()) goto found_uclc_file;
517 
518  // case 2
519  fname.MakeLower();
520  fext.MakeUpper();
521  NOS_filename.SetName(fname);
522  NOS_filename.SetExt(fext);
523 
524  if (NOS_filename.FileExists()) goto found_uclc_file;
525 
526  // case 3
527  fname.MakeUpper();
528  fext.MakeLower();
529  NOS_filename.SetName(fname);
530  NOS_filename.SetExt(fext);
531 
532  if (NOS_filename.FileExists()) goto found_uclc_file;
533 
534  // case 4
535  fname.MakeUpper();
536  fext.MakeUpper();
537  NOS_filename.SetName(fname);
538  NOS_filename.SetExt(fext);
539 
540  if (NOS_filename.FileExists()) goto found_uclc_file;
541 
542  // Search harder
543 
544  for (ifile = 0; ifile < nfiles; ifile++) {
545  wxString file_up = file_array[ifile];
546  file_up.MakeUpper();
547 
548  wxString target_up = *pBitmapFilePath;
549  target_up.MakeUpper();
550 
551  if (file_up.IsSameAs(target_up)) {
552  NOS_filename.Clear();
553  NOS_filename.Assign(file_array[ifile]);
554  goto found_uclc_file;
555  }
556  }
557 
558  free(pPlyTable);
559  return INIT_FAIL_REMOVE; // not found at all
560 
561  found_uclc_file:
562 
563  delete pBitmapFilePath; // fix up the member element
564  pBitmapFilePath = new wxString(NOS_filename.GetFullPath());
565  }
566  ifss_bitmap =
567  new wxFFileInputStream(*pBitmapFilePath); // open the bitmap file
568  ifs_bitmap = new wxBufferedInputStream(*ifss_bitmap);
569 
570  if (!ifss_bitmap->IsOk()) {
571  free(pPlyTable);
572  return INIT_FAIL_REMOVE;
573  }
574 
575  while ((ReadBSBHdrLine(ifss_bitmap, &buffer[0], BUF_LEN_MAX)) != 0) {
576  wxString str_buf(buffer, wxConvUTF8);
577 
578  if (!strncmp(buffer, "NOS", 3)) {
579  wxStringTokenizer tkz(str_buf, _T(",="));
580  while (tkz.HasMoreTokens()) {
581  wxString token = tkz.GetNextToken();
582  if (token.IsSameAs(_T("RA"), TRUE)) // extract RA=x,y
583  {
584  int i;
585  tkz.GetNextToken();
586  tkz.GetNextToken();
587  i = tkz.GetPosition();
588  Size_X = atoi(&buffer[i]);
589  wxString token = tkz.GetNextToken();
590  i = tkz.GetPosition();
591  Size_Y = atoi(&buffer[i]);
592  } else if (token.IsSameAs(_T("DU"), TRUE)) // extract DU=n
593  {
594  token = tkz.GetNextToken();
595  long temp_du;
596  if (token.ToLong(&temp_du)) m_Chart_DU = temp_du;
597  }
598  }
599 
600  }
601 
602  else if (!strncmp(buffer, "RGB", 3))
603  CreatePaletteEntry(buffer, COLOR_RGB_DEFAULT);
604 
605  else if (!strncmp(buffer, "DAY", 3))
606  CreatePaletteEntry(buffer, DAY);
607 
608  else if (!strncmp(buffer, "DSK", 3))
609  CreatePaletteEntry(buffer, DUSK);
610 
611  else if (!strncmp(buffer, "NGT", 3))
612  CreatePaletteEntry(buffer, NIGHT);
613 
614  else if (!strncmp(buffer, "NGR", 3))
615  CreatePaletteEntry(buffer, NIGHTRED);
616 
617  else if (!strncmp(buffer, "GRY", 3))
618  CreatePaletteEntry(buffer, GRAY);
619 
620  else if (!strncmp(buffer, "PRC", 3))
621  CreatePaletteEntry(buffer, PRC);
622 
623  else if (!strncmp(buffer, "PRG", 3))
624  CreatePaletteEntry(buffer, PRG);
625  }
626 
627  // Validate some of the header data
628  if (Size_X <= 0 || Size_Y <= 0) {
629  free(pPlyTable);
630  return INIT_FAIL_REMOVE;
631  }
632 
633  if (nPlypoint < 3) {
634  wxString msg(_T(" Chart File contains less than 3 PLY points: "));
635  msg.Append(m_FullPath);
636  wxLogMessage(msg);
637  free(pPlyTable);
638 
639  return INIT_FAIL_REMOVE;
640  }
641 
642  if (m_datum_str.IsEmpty()) {
643  wxString msg(_T(" Chart datum not specified on chart "));
644  msg.Append(m_FullPath);
645  wxLogMessage(msg);
646  wxLogMessage(_T(" Default datum (WGS84) substituted."));
647 
648  // return INIT_FAIL_REMOVE;
649  } else {
650  char d_str[100];
651  strncpy(d_str, m_datum_str.mb_str(), 99);
652  d_str[99] = 0;
653 
654  int datum_index = GetDatumIndex(d_str);
655 
656  if (datum_index < 0) {
657  wxString msg(_T(" Chart datum {"));
658  msg += m_datum_str;
659  msg += _T("} invalid on chart ");
660  msg.Append(m_FullPath);
661  wxLogMessage(msg);
662  wxLogMessage(_T(" Default datum (WGS84) substituted."));
663 
664  datum_index = DATUM_INDEX_WGS84;
665  }
666  m_datum_index = datum_index;
667  }
668 
669  // Convert captured plypoint information into chart COVR structures
670  m_nCOVREntries = 1;
671  m_pCOVRTablePoints = (int *)malloc(sizeof(int));
672  *m_pCOVRTablePoints = nPlypoint;
673  m_pCOVRTable = (float **)malloc(sizeof(float *));
674  *m_pCOVRTable = (float *)malloc(nPlypoint * 2 * sizeof(float));
675  memcpy(*m_pCOVRTable, pPlyTable, nPlypoint * 2 * sizeof(float));
676 
677  free(pPlyTable);
678 
679  if (!SetMinMax()) return INIT_FAIL_REMOVE; // have to bail here
680 
681  AnalyzeSkew();
682 
683  if (init_flags == HEADER_ONLY) return INIT_OK;
684 
685  // Advance to the data
686  char c;
687  if ((c = ifs_bitmap->GetC()) != 0x1a) {
688  return INIT_FAIL_REMOVE;
689  }
690  if ((c = ifs_bitmap->GetC()) == 0x0d) {
691  if ((c = ifs_bitmap->GetC()) != 0x0a) {
692  return INIT_FAIL_REMOVE;
693  }
694  if ((c = ifs_bitmap->GetC()) != 0x1a) {
695  return INIT_FAIL_REMOVE;
696  }
697  if ((c = ifs_bitmap->GetC()) != 0x00) {
698  return INIT_FAIL_REMOVE;
699  }
700  }
701 
702  else if (c != 0x00) {
703  return INIT_FAIL_REMOVE;
704  }
705 
706  // Read the Color table bit size
707  nColorSize = ifs_bitmap->GetC();
708  if (nColorSize == wxEOF || nColorSize <= 0 || nColorSize > 7) {
709  wxString msg(_T(" Invalid nColorSize data, corrupt on chart "));
710  msg.Append(m_FullPath);
711  wxLogMessage(msg);
712  return INIT_FAIL_REMOVE;
713  }
714 
715  // Perform common post-init actions in ChartBaseBSB
716  InitReturn pi_ret = PostInit();
717  if (pi_ret != INIT_OK)
718  return pi_ret;
719  else
720  return INIT_OK;
721 }
722 
723 // ============================================================================
724 // ChartKAP implementation
725 // ============================================================================
726 
727 ChartKAP::ChartKAP() { m_ChartType = CHART_TYPE_KAP; }
728 
729 ChartKAP::~ChartKAP() {}
730 
731 InitReturn ChartKAP::Init(const wxString &name, ChartInitFlag init_flags) {
732 #define BUF_LEN_MAX 4096
733 
734  ifs_hdr = new ChartDataNonSeekableInputStream(
735  name); // open the Header file as a read-only stream
736 
737  if (!ifs_hdr->IsOk()) return INIT_FAIL_REMOVE;
738 
739  int nPlypoint = 0;
740  Plypoint *pPlyTable = (Plypoint *)malloc(sizeof(Plypoint));
741 
742  PreInit(name, init_flags, GLOBAL_COLOR_SCHEME_DAY);
743 
744  char buffer[BUF_LEN_MAX];
745 
746  m_FullPath = name;
747  m_Description = m_FullPath;
748 
749  // Clear georeferencing coefficients
750  for (int icl = 0; icl < 12; icl++) {
751  wpx[icl] = 0;
752  wpy[icl] = 0;
753  pwx[icl] = 0;
754  pwy[icl] = 0;
755  }
756 
757  // Validate the BSB header
758  // by reading some characters into a buffer and looking for BSB\ keyword
759 
760  unsigned int TestBlockSize = 1999;
761  ifs_hdr->Read(buffer, TestBlockSize);
762 
763  if (ifs_hdr->LastRead() != TestBlockSize) {
764  wxString msg;
765  msg.Printf(
766  _T(" Could not read first %d bytes of header for chart file: "),
767  TestBlockSize);
768  msg.Append(name);
769  wxLogMessage(msg);
770  free(pPlyTable);
771  return INIT_FAIL_REMOVE;
772  }
773 
774  unsigned int i;
775  for (i = 0; i < TestBlockSize - 4; i++) {
776  // Test for "BSB/"
777  if (buffer[i + 0] == 'B' && buffer[i + 1] == 'S' && buffer[i + 2] == 'B' &&
778  buffer[i + 3] == '/')
779  break;
780 
781  // Test for "NOS/"
782  if (buffer[i + 0] == 'N' && buffer[i + 1] == 'O' && buffer[i + 2] == 'S' &&
783  buffer[i + 3] == '/')
784  break;
785  }
786  if (i == TestBlockSize - 4) {
787  wxString msg(_T(" Chart file has no BSB header, cannot Init."));
788  msg.Append(name);
789  wxLogMessage(msg);
790  free(pPlyTable);
791  return INIT_FAIL_REMOVE;
792  }
793 
794  // Read and Parse Chart Header, line by line
795  ifs_hdr->SeekI(0, wxFromStart); // rewind
796 
797  Size_X = Size_Y = 0;
798 
799  int done_header_parse = 0;
800  wxCSConv iso_conv(wxT("ISO-8859-1")); // we will need a converter
801 
802  while (done_header_parse == 0) {
803  if (ReadBSBHdrLine(ifs_hdr, buffer, BUF_LEN_MAX) == 0) {
804  unsigned char c;
805  c = ifs_hdr->GetC();
806  ifs_hdr->Ungetch(c);
807 
808  if (0x1a == c)
809  done_header_parse = 1;
810  else {
811  free(pPlyTable);
812  return INIT_FAIL_REMOVE;
813  }
814 
815  continue;
816  }
817 
818  wxString str_buf(buffer, wxConvUTF8);
819  if (!str_buf.Len()) // failed conversion
820  str_buf = wxString(buffer, iso_conv);
821 
822  if (str_buf.Find(_T("SHOM")) != wxNOT_FOUND) m_b_SHOM = true;
823 
824  if (!strncmp(buffer, "BSB", 3)) {
825  wxString clip_str_buf(
826  &buffer[0],
827  iso_conv); // for single byte French encodings of NAme field
828  wxStringTokenizer tkz(clip_str_buf, _T("/,="));
829  while (tkz.HasMoreTokens()) {
830  wxString token = tkz.GetNextToken();
831  if (token.IsSameAs(_T("RA"), TRUE)) // extract RA=x,y
832  {
833  int i;
834  i = tkz.GetPosition();
835  Size_X = atoi(&buffer[i]);
836  wxString token = tkz.GetNextToken();
837  i = tkz.GetPosition();
838  Size_Y = atoi(&buffer[i]);
839  } else if (token.IsSameAs(_T("NA"), TRUE)) // extract NA=str
840  {
841  int i = tkz.GetPosition();
842  char nbuf[81];
843  int j = 0;
844  while ((buffer[i] != ',') && (i < 80)) nbuf[j++] = buffer[i++];
845  nbuf[j] = 0;
846  wxString n_str(nbuf, iso_conv);
847  m_Name = n_str;
848  } else if (token.IsSameAs(_T("NU"), TRUE)) // extract NU=str
849  {
850  int i = tkz.GetPosition();
851  char nbuf[81];
852  int j = 0;
853  while ((buffer[i] != ',') && (i < 80)) nbuf[j++] = buffer[i++];
854  nbuf[j] = 0;
855  wxString n_str(nbuf, iso_conv);
856  m_ID = n_str;
857  } else if (token.IsSameAs(_T("DU"), TRUE)) // extract DU=n
858  {
859  token = tkz.GetNextToken();
860  long temp_du;
861  if (token.ToLong(&temp_du)) m_Chart_DU = temp_du;
862  }
863  }
864  }
865 
866  else if (!strncmp(buffer, "KNP", 3)) {
867  wxString conv_buf(buffer, iso_conv);
868  wxStringTokenizer tkz(conv_buf, _T("/,="));
869  while (tkz.HasMoreTokens()) {
870  wxString token = tkz.GetNextToken();
871  if (token.IsSameAs(_T("SC"), TRUE)) // extract Scale
872  {
873  int i;
874  i = tkz.GetPosition();
875  m_Chart_Scale = atoi(&buffer[i]);
876  if (0 == m_Chart_Scale) m_Chart_Scale = 100000000;
877  } else if (token.IsSameAs(_T("SK"), TRUE)) // extract Skew
878  {
879  int i;
880  i = tkz.GetPosition();
881  float fcs;
882  sscanf(&buffer[i], "%f,", &fcs);
883  m_Chart_Skew = fcs;
884  } else if (token.IsSameAs(_T("UN"), TRUE)) // extract Depth Units
885  {
886  int i;
887  i = tkz.GetPosition();
888  wxString str(&buffer[i], iso_conv);
889  m_DepthUnits = str.BeforeFirst(',');
890  } else if (token.IsSameAs(_T("GD"), TRUE)) // extract Datum
891  {
892  int i;
893  i = tkz.GetPosition();
894  wxString str(&buffer[i], iso_conv);
895  m_datum_str = str.BeforeFirst(',').Trim();
896  } else if (token.IsSameAs(_T("SD"), TRUE)) // extract Soundings Datum
897  {
898  int i;
899  i = tkz.GetPosition();
900  wxString str(&buffer[i], iso_conv);
901  m_SoundingsDatum = str.BeforeFirst(',').Trim();
902  } else if (token.IsSameAs(_T("PP"),
903  TRUE)) // extract Projection Parameter
904  {
905  int i;
906  i = tkz.GetPosition();
907  double fcs;
908  wxString str(&buffer[i], iso_conv);
909  wxString str1 = str.BeforeFirst(',').Trim();
910  if (str1.ToDouble(&fcs)) m_proj_parameter = fcs;
911  } else if (token.IsSameAs(_T("PR"), TRUE)) // extract Projection Type
912  {
913  int i;
914  i = tkz.GetPosition();
915  wxString str(&buffer[i], iso_conv);
916  wxString stru = str.MakeUpper();
917  bool bp_set = false;
918  ;
919 
920  if (stru.Matches(_T("*MERCATOR*"))) {
921  m_projection = PROJECTION_MERCATOR;
922  bp_set = true;
923  }
924 
925  if (stru.Matches(_T("*TRANSVERSE*"))) {
926  m_projection = PROJECTION_TRANSVERSE_MERCATOR;
927  bp_set = true;
928  }
929 
930  if (stru.Matches(_T("*CONIC*"))) {
931  m_projection = PROJECTION_POLYCONIC;
932  bp_set = true;
933  }
934 
935  if (stru.Matches(_T("*TM*"))) {
936  m_projection = PROJECTION_TRANSVERSE_MERCATOR;
937  bp_set = true;
938  }
939 
940  if (stru.Matches(_T("*GAUSS CONFORMAL*"))) {
941  m_projection = PROJECTION_TRANSVERSE_MERCATOR;
942  bp_set = true;
943  }
944 
945  if (!bp_set) {
946  m_projection = PROJECTION_UNKNOWN;
947  wxString msg(_T(" Chart projection is "));
948  msg += tkz.GetNextToken();
949  msg += _T(" which is unsupported. Disabling chart ");
950  msg += m_FullPath;
951  wxLogMessage(msg);
952  free(pPlyTable);
953  return INIT_FAIL_REMOVE;
954  }
955  } else if (token.IsSameAs(
956  _T("DX"),
957  TRUE)) // extract Pixel scale parameter, if present
958  {
959  int i;
960  i = tkz.GetPosition();
961  float x;
962  sscanf(&buffer[i], "%f,", &x);
963  m_dx = x;
964  } else if (token.IsSameAs(
965  _T("DY"),
966  TRUE)) // extract Pixel scale parameter, if present
967  {
968  int i;
969  i = tkz.GetPosition();
970  float x;
971  sscanf(&buffer[i], "%f,", &x);
972  m_dy = x;
973  }
974  }
975  }
976 
977  else if (!strncmp(buffer, "RGB", 3))
978  CreatePaletteEntry(buffer, COLOR_RGB_DEFAULT);
979 
980  else if (!strncmp(buffer, "DAY", 3))
981  CreatePaletteEntry(buffer, DAY);
982 
983  else if (!strncmp(buffer, "DSK", 3))
984  CreatePaletteEntry(buffer, DUSK);
985 
986  else if (!strncmp(buffer, "NGT", 3))
987  CreatePaletteEntry(buffer, NIGHT);
988 
989  else if (!strncmp(buffer, "NGR", 3))
990  CreatePaletteEntry(buffer, NIGHTRED);
991 
992  else if (!strncmp(buffer, "GRY", 3))
993  CreatePaletteEntry(buffer, GRAY);
994 
995  else if (!strncmp(buffer, "PRC", 3))
996  CreatePaletteEntry(buffer, PRC);
997 
998  else if (!strncmp(buffer, "PRG", 3))
999  CreatePaletteEntry(buffer, PRG);
1000 
1001  else if (!strncmp(buffer, "REF", 3)) {
1002  int i, xr, yr;
1003  float ltr, lnr;
1004  sscanf(&buffer[4], "%d,%d,%d,%f,%f", &i, &xr, &yr, &ltr, &lnr);
1005  pRefTable =
1006  (Refpoint *)realloc(pRefTable, sizeof(Refpoint) * (nRefpoint + 1));
1007  pRefTable[nRefpoint].xr = xr;
1008  pRefTable[nRefpoint].yr = yr;
1009  pRefTable[nRefpoint].latr = ltr;
1010  pRefTable[nRefpoint].lonr = lnr;
1011  pRefTable[nRefpoint].bXValid = 1;
1012  pRefTable[nRefpoint].bYValid = 1;
1013 
1014  nRefpoint++;
1015 
1016  }
1017 
1018  else if (!strncmp(buffer, "WPX", 3)) {
1019  int idx = 0;
1020  double d;
1021  wxStringTokenizer tkz(str_buf.Mid(4), _T(","));
1022  wxString token = tkz.GetNextToken();
1023 
1024  if (token.ToLong((long int *)&wpx_type)) {
1025  while (tkz.HasMoreTokens() && (idx < 12)) {
1026  token = tkz.GetNextToken();
1027  if (token.ToDouble(&d)) {
1028  wpx[idx] = d;
1029  idx++;
1030  }
1031  }
1032  }
1033  n_wpx = idx;
1034  }
1035 
1036  else if (!strncmp(buffer, "WPY", 3)) {
1037  int idx = 0;
1038  double d;
1039  wxStringTokenizer tkz(str_buf.Mid(4), _T(","));
1040  wxString token = tkz.GetNextToken();
1041 
1042  if (token.ToLong((long int *)&wpy_type)) {
1043  while (tkz.HasMoreTokens() && (idx < 12)) {
1044  token = tkz.GetNextToken();
1045  if (token.ToDouble(&d)) {
1046  wpy[idx] = d;
1047  idx++;
1048  }
1049  }
1050  }
1051  n_wpy = idx;
1052  }
1053 
1054  else if (!strncmp(buffer, "PWX", 3)) {
1055  int idx = 0;
1056  double d;
1057  wxStringTokenizer tkz(str_buf.Mid(4), _T(","));
1058  wxString token = tkz.GetNextToken();
1059 
1060  if (token.ToLong((long int *)&pwx_type)) {
1061  while (tkz.HasMoreTokens() && (idx < 12)) {
1062  token = tkz.GetNextToken();
1063  if (token.ToDouble(&d)) {
1064  pwx[idx] = d;
1065  idx++;
1066  }
1067  }
1068  }
1069  n_pwx = idx;
1070  }
1071 
1072  else if (!strncmp(buffer, "PWY", 3)) {
1073  int idx = 0;
1074  double d;
1075  wxStringTokenizer tkz(str_buf.Mid(4), _T(","));
1076  wxString token = tkz.GetNextToken();
1077 
1078  if (token.ToLong((long int *)&pwy_type)) {
1079  while (tkz.HasMoreTokens() && (idx < 12)) {
1080  token = tkz.GetNextToken();
1081  if (token.ToDouble(&d)) {
1082  pwy[idx] = d;
1083  idx++;
1084  }
1085  }
1086  }
1087  n_pwy = idx;
1088  }
1089 
1090  else if (!strncmp(buffer, "CPH", 3)) {
1091  float float_cph;
1092  sscanf(&buffer[4], "%f", &float_cph);
1093  m_cph = float_cph;
1094  }
1095 
1096  else if (!strncmp(buffer, "VER", 3)) {
1097  wxStringTokenizer tkz(str_buf, _T("/,="));
1098  wxString token = tkz.GetNextToken();
1099 
1100  m_bsb_ver = tkz.GetNextToken();
1101  }
1102 
1103  else if (!strncmp(buffer, "DTM", 3)) {
1104  double val;
1105  wxStringTokenizer tkz(str_buf, _T("/,="));
1106  wxString token = tkz.GetNextToken();
1107 
1108  token = tkz.GetNextToken();
1109  if (token.ToDouble(&val)) m_dtm_lat = val;
1110 
1111  token = tkz.GetNextToken();
1112  if (token.ToDouble(&val)) m_dtm_lon = val;
1113 
1114  // float fdtmlat, fdtmlon;
1115  // sscanf(&buffer[4], "%f,%f", &fdtmlat, &fdtmlon);
1116  // m_dtm_lat = fdtmlat;
1117  // m_dtm_lon = fdtmlon;
1118  }
1119 
1120  else if (!strncmp(buffer, "PLY", 3)) {
1121  int i;
1122  float ltp, lnp;
1123  if (sscanf(&buffer[4], "%d,%f,%f", &i, &ltp, &lnp) != 3) {
1124  free(pPlyTable);
1125  return INIT_FAIL_REMOVE;
1126  }
1127  Plypoint *tmp = pPlyTable;
1128  pPlyTable =
1129  (Plypoint *)realloc(pPlyTable, sizeof(Plypoint) * (nPlypoint + 1));
1130  if (NULL == pPlyTable) {
1131  free(tmp);
1132  tmp = NULL;
1133  } else {
1134  pPlyTable[nPlypoint].ltp = ltp;
1135  pPlyTable[nPlypoint].lnp = lnp;
1136  nPlypoint++;
1137  }
1138  if (NULL == pPlyTable || nPlypoint > 1000000) {
1139  // arbitrary 8MB for pPlyTable
1140  nPlypoint = 0;
1141  break;
1142  }
1143  }
1144 
1145  else if (!strncmp(buffer, "CED", 3)) {
1146  wxStringTokenizer tkz(str_buf, _T("/,="));
1147  while (tkz.HasMoreTokens()) {
1148  wxString token = tkz.GetNextToken();
1149  if (token.IsSameAs(_T("ED"), TRUE)) // extract Edition Date
1150  {
1151  int i;
1152  i = tkz.GetPosition();
1153 
1154  char date_string[40];
1155  char date_buf[16];
1156  date_string[0] = 0;
1157  date_buf[0] = 0;
1158  sscanf(&buffer[i], "%s\r\n", date_string);
1159  wxString date_wxstr(date_string, wxConvUTF8);
1160 
1161  wxDateTime dt;
1162  if (dt.ParseDate(date_wxstr)) // successful parse?
1163  {
1164  int iyear =
1165  dt.GetYear(); // GetYear() fails on W98, DMC compiler, wx2.8.3
1166  // BSB charts typically list publish date as xx/yy/zz
1167  // This our own little version of the Y2K problem.
1168  // Just apply some sensible logic
1169 
1170  if (iyear < 50) {
1171  iyear += 2000;
1172  dt.SetYear(iyear);
1173  } else if ((iyear >= 50) && (iyear < 100)) {
1174  iyear += 1900;
1175  dt.SetYear(iyear);
1176  }
1177  assert(iyear <= 9999);
1178  sprintf(date_buf, "%d", iyear);
1179 
1180  // Initialize the wxDateTime menber for Edition Date
1181  m_EdDate = dt;
1182  } else {
1183  sscanf(date_string, "%s", date_buf);
1184  m_EdDate.Set(1, wxDateTime::Jan,
1185  2000); // Todo this could be smarter
1186  }
1187 
1188  m_PubYear = wxString(date_buf, wxConvUTF8);
1189  } else if (token.IsSameAs(_T("SE"), TRUE)) // extract Source Edition
1190  {
1191  int i;
1192  i = tkz.GetPosition();
1193  wxString str(&buffer[i], iso_conv);
1194  m_SE = str.BeforeFirst(',');
1195  }
1196  }
1197  }
1198  } // while
1199 
1200  // Some charts improperly encode the DTM parameters.
1201  // Identify them as necessary, for further processing
1202  if (m_b_SHOM && (m_bsb_ver == _T("1.1"))) m_b_apply_dtm = false;
1203 
1204  // If imbedded coefficients are found,
1205  // then use the polynomial georeferencing algorithms
1206  if (n_pwx && n_pwy && n_pwx && n_pwy) bHaveEmbeddedGeoref = true;
1207 
1208  // Set up the projection point according to the projection parameter
1209  if (m_projection == PROJECTION_MERCATOR)
1210  m_proj_lat = m_proj_parameter;
1211  else if (m_projection == PROJECTION_TRANSVERSE_MERCATOR)
1212  m_proj_lon = m_proj_parameter;
1213  else if (m_projection == PROJECTION_POLYCONIC)
1214  m_proj_lon = m_proj_parameter;
1215 
1216  // We have seen improperly coded charts, with non-sense value of PP
1217  // parameter FS#1251 Check and override if necessary
1218  if (m_proj_lat > 82.0 || m_proj_lat < -82.0) m_proj_lat = 0.0;
1219 
1220  // Validate some of the header data
1221  if (Size_X <= 0 || Size_Y <= 0) {
1222  free(pPlyTable);
1223  return INIT_FAIL_REMOVE;
1224  }
1225 
1226  if (nPlypoint < 3) {
1227  wxString msg(
1228  _T(" Chart File contains less than 3 or too many PLY points: "));
1229  msg.Append(m_FullPath);
1230  wxLogMessage(msg);
1231  free(pPlyTable);
1232  return INIT_FAIL_REMOVE;
1233  }
1234 
1235  if (m_datum_str.IsEmpty()) {
1236  wxString msg(_T(" Chart datum not specified on chart "));
1237  msg.Append(m_FullPath);
1238  wxLogMessage(msg);
1239  wxLogMessage(_T(" Default datum (WGS84) substituted."));
1240 
1241  // return INIT_FAIL_REMOVE;
1242  } else {
1243  char d_str[100];
1244  strncpy(d_str, m_datum_str.mb_str(), 99);
1245  d_str[99] = 0;
1246 
1247  int datum_index = GetDatumIndex(d_str);
1248 
1249  if (datum_index < 0) {
1250  wxString msg(_T(" Chart datum {"));
1251  msg += m_datum_str;
1252  msg += _T("} invalid on chart ");
1253  msg.Append(m_FullPath);
1254  wxLogMessage(msg);
1255  wxLogMessage(_T(" Default datum (WGS84) substituted."));
1256 
1257  // return INIT_FAIL_REMOVE;
1258  }
1259  }
1260 
1261  /* Augment ply points
1262  This is needed for example on polyconic charts or skewed charts because
1263  straight lines in the chart coordinates can not use simple
1264  interpolation in lat/lon or mercator coordinate space to draw the
1265  borders or be used for quilting operation.
1266  TODO: should this be added as a subroutine for GEO chartso? */
1267  if ((m_projection != PROJECTION_MERCATOR &&
1268  m_projection != PROJECTION_TRANSVERSE_MERCATOR) ||
1269  m_Chart_Skew > 2) {
1270  // Analyze Refpoints early because we need georef coefficient here.
1271  AnalyzeRefpoints(false); // no post test needed
1272 
1273  // We need to compute a tentative min/max lat/lon to perform georefs
1274  // These lat/lon extents will be more accurately updated later.
1275  m_LonMax = -360.0;
1276  m_LonMin = 360.0;
1277  m_LatMax = -90.0;
1278  m_LatMin = 90.0;
1279 
1280  for (int i = 0; i < nPlypoint; i++) {
1281  m_LatMax = wxMax(m_LatMax, pPlyTable[i].ltp);
1282  m_LatMin = wxMin(m_LatMin, pPlyTable[i].ltp);
1283  m_LonMax = wxMax(m_LonMax, pPlyTable[i].lnp);
1284  m_LonMin = wxMin(m_LonMin, pPlyTable[i].lnp);
1285  }
1286 
1287  int count = nPlypoint;
1288  nPlypoint = 0;
1289  Plypoint *pOldPlyTable = pPlyTable;
1290  pPlyTable = NULL;
1291  double lastplylat = 0.0, lastplylon = 0.0, x1 = 0.0, y1 = 0.0, x2, y2;
1292  double plylat, plylon;
1293  for (int i = 0; i < count + 1; i++) {
1294  plylat = pOldPlyTable[i % count].ltp;
1295  plylon = pOldPlyTable[i % count].lnp;
1296  latlong_to_chartpix(plylat, plylon, x2, y2);
1297  if (i > 0) {
1298  if (lastplylon - plylon > 180.)
1299  lastplylon -= 360.;
1300  else if (lastplylon - plylon < -180.)
1301  lastplylon += 360.;
1302 
1303  // use 2 degree steps
1304  double steps =
1305  ceil((fabs(lastplylat - plylat) + fabs(lastplylon - plylon)) / 2);
1306  for (double c = 0; c < steps; c++) {
1307  double d = c / steps, lat, lon;
1308  wxPoint2DDouble s;
1309  double x = (1 - d) * x1 + d * x2, y = (1 - d) * y1 + d * y2;
1310  chartpix_to_latlong(x, y, &lat, &lon);
1311  pPlyTable = (Plypoint *)realloc(pPlyTable,
1312  sizeof(Plypoint) * (nPlypoint + 1));
1313  pPlyTable[nPlypoint].ltp = lat;
1314  pPlyTable[nPlypoint].lnp = lon;
1315  nPlypoint++;
1316  }
1317  }
1318  x1 = x2, y1 = y2;
1319  lastplylat = plylat, lastplylon = plylon;
1320  }
1321  free(pOldPlyTable);
1322  }
1323 
1324  // Convert captured plypoint information into chart COVR structures
1325 
1326  // A special-case test for poorly formatted charts
1327  // We look for cases where the declared PlyPoints are far outside of the
1328  // chart raster bitmap If found, we change the COVR region to the valid
1329  // bitmap region, instead of the default PlyPoints region
1330  // Set a tentative lat/lon range.
1331  m_LonMax = -360.;
1332  m_LonMin = 360.;
1333  for (int i = 0; i < nPlypoint; i++) {
1334  m_LonMin = wxMin(m_LonMin, pPlyTable[i].lnp);
1335  m_LonMax = wxMax(m_LonMax, pPlyTable[i].lnp);
1336  }
1337  // This test does not really work for charts that cross IDL
1338  bool b_test = true;
1339  bool b_adjusted = false;
1340  if (m_LonMax * m_LonMin < 0) {
1341  if ((m_LonMax - m_LonMin) > 180.) b_test = false;
1342  }
1343 
1344  if (b_test) {
1345  if (!bHaveEmbeddedGeoref) {
1346  // Analyze Refpoints early because we might need georef coefficient
1347  // here.
1348  AnalyzeRefpoints(false); // no post test needed
1349  }
1350 
1351  bool bAdjustPly = false;
1352  wxRect bitRect(0, 0, Size_X, Size_Y);
1353  bitRect.Inflate(5); // allow for a little roundoff error
1354  for (int i = 0; i < nPlypoint; i++) {
1355  double pix_x, pix_y;
1356  latlong_to_chartpix(pPlyTable[i].ltp, pPlyTable[i].lnp, pix_x, pix_y);
1357  if (!bitRect.Contains(pix_x, pix_y)) {
1358  bAdjustPly = true;
1359  if (m_b_cdebug)
1360  printf("Adjusting COVR region on: %s\n", name.ToUTF8().data());
1361  break;
1362  }
1363  }
1364 
1365  if (bAdjustPly) {
1366  float *points = new float[2 * nPlypoint];
1367  for (int i = 0; i < nPlypoint; i++)
1368  points[2 * i + 0] = pPlyTable[i].ltp,
1369  points[2 * i + 1] = pPlyTable[i].lnp;
1370  LLRegion covrRegion(nPlypoint, points);
1371  delete[] points;
1372  covrRegion.Intersect(GetValidRegion());
1373 
1374  if (covrRegion.contours.size()) { // Check for no intersection caused by
1375  // bogus georef....
1376  m_nCOVREntries = covrRegion.contours.size();
1377  m_pCOVRTablePoints = (int *)malloc(m_nCOVREntries * sizeof(int));
1378  m_pCOVRTable = (float **)malloc(m_nCOVREntries * sizeof(float *));
1379  std::list<poly_contour>::iterator it = covrRegion.contours.begin();
1380  for (int i = 0; i < m_nCOVREntries; i++) {
1381  m_pCOVRTablePoints[i] = it->size();
1382  m_pCOVRTable[i] =
1383  (float *)malloc(m_pCOVRTablePoints[i] * 2 * sizeof(float));
1384  std::list<contour_pt>::iterator jt = it->begin();
1385  for (int j = 0; j < m_pCOVRTablePoints[i]; j++) {
1386  m_pCOVRTable[i][2 * j + 0] = jt->y;
1387  m_pCOVRTable[i][2 * j + 1] = jt->x;
1388  jt++;
1389  }
1390  it++;
1391  }
1392  b_adjusted = true;
1393  }
1394  }
1395  }
1396 
1397  if (!b_adjusted) {
1398  m_nCOVREntries = 1;
1399  m_pCOVRTablePoints = (int *)malloc(sizeof(int));
1400  *m_pCOVRTablePoints = nPlypoint;
1401  m_pCOVRTable = (float **)malloc(sizeof(float *));
1402  *m_pCOVRTable = (float *)malloc(nPlypoint * 2 * sizeof(float));
1403  memcpy(*m_pCOVRTable, pPlyTable, nPlypoint * 2 * sizeof(float));
1404  }
1405 
1406  free(pPlyTable);
1407 
1408  // Setup the datum transform parameters
1409  char d_str[100];
1410  strncpy(d_str, m_datum_str.mb_str(), 99);
1411  d_str[99] = 0;
1412 
1413  int datum_index = GetDatumIndex(d_str);
1414  m_datum_index = datum_index;
1415 
1416  if (datum_index < 0)
1417  m_ExtraInfo = _T("---<<< Warning: Chart Datum may be incorrect. >>>---");
1418 
1419  // Establish defaults, may be overridden later
1420  m_lon_datum_adjust = (-m_dtm_lon) / 3600.;
1421  m_lat_datum_adjust = (-m_dtm_lat) / 3600.;
1422 
1423  // Adjust the PLY points to WGS84 datum
1424  Plypoint *ppp = (Plypoint *)GetCOVRTableHead(0);
1425  int cnPlypoint = GetCOVRTablenPoints(0);
1426 
1427  for (int u = 0; u < cnPlypoint; u++) {
1428  double dlat = 0;
1429  double dlon = 0;
1430 
1431  if (m_datum_index == DATUM_INDEX_WGS84 ||
1432  m_datum_index == DATUM_INDEX_UNKNOWN) {
1433  dlon = m_dtm_lon / 3600.;
1434  dlat = m_dtm_lat / 3600.;
1435  }
1436 
1437  else {
1438  double to_lat, to_lon;
1439  MolodenskyTransform(ppp->ltp, ppp->lnp, &to_lat, &to_lon, m_datum_index,
1440  DATUM_INDEX_WGS84);
1441  dlon = (to_lon - ppp->lnp);
1442  dlat = (to_lat - ppp->ltp);
1443  if (m_b_apply_dtm) {
1444  dlon += m_dtm_lon / 3600.;
1445  dlat += m_dtm_lat / 3600.;
1446  }
1447  }
1448 
1449  ppp->lnp += dlon;
1450  ppp->ltp += dlat;
1451  ppp++;
1452  }
1453 
1454  if (!SetMinMax()) return INIT_FAIL_REMOVE; // have to bail here
1455 
1456  AnalyzeSkew();
1457 
1458  if (init_flags == HEADER_ONLY) return INIT_OK;
1459 
1460  // Advance to the data
1461  unsigned char c;
1462  bool bcorrupt = false;
1463 
1464  if ((c = ifs_hdr->GetC()) != 0x1a) {
1465  bcorrupt = true;
1466  }
1467  if ((c = ifs_hdr->GetC()) == 0x0d) {
1468  if ((c = ifs_hdr->GetC()) != 0x0a) {
1469  bcorrupt = true;
1470  }
1471  if ((c = ifs_hdr->GetC()) != 0x1a) {
1472  bcorrupt = true;
1473  }
1474  if ((c = ifs_hdr->GetC()) != 0x00) {
1475  bcorrupt = true;
1476  }
1477  }
1478 
1479  else if (c != 0x00) {
1480  bcorrupt = true;
1481  }
1482 
1483  if (bcorrupt) {
1484  wxString msg(_T(" Chart File RLL data corrupt on chart "));
1485  msg.Append(m_FullPath);
1486  wxLogMessage(msg);
1487 
1488  return INIT_FAIL_REMOVE;
1489  }
1490 
1491  // Read the Color table bit size
1492  nColorSize = ifs_hdr->GetC();
1493  if (nColorSize == wxEOF || nColorSize <= 0 || nColorSize > 7) {
1494  wxString msg(_T(" Invalid nColorSize data, corrupt on chart "));
1495  msg.Append(m_FullPath);
1496  wxLogMessage(msg);
1497  return INIT_FAIL_REMOVE;
1498  }
1499 
1500  nFileOffsetDataStart = ifs_hdr->TellI();
1501  delete ifs_hdr;
1502  ifs_hdr = NULL;
1503 
1504  ChartDataInputStream *stream =
1505  new ChartDataInputStream(name); // Open again, as the bitmap
1506  wxString tempfile;
1507 #ifdef OCPN_USE_LZMA
1508  tempfile = stream->TempFileName();
1509 #endif
1510  m_filesize = wxFileName::GetSize(tempfile.empty() ? name : tempfile);
1511 
1512  ifss_bitmap = stream;
1513  ifs_bitmap = new wxBufferedInputStream(*ifss_bitmap);
1514 
1515  // Perform common post-init actions in ChartBaseBSB
1516  InitReturn pi_ret = PostInit();
1517  if (pi_ret != INIT_OK) return pi_ret;
1518  return INIT_OK;
1519 }
1520 
1521 // ============================================================================
1522 // ChartBaseBSB implementation
1523 // ============================================================================
1524 
1525 ChartBaseBSB::ChartBaseBSB() {
1526  // Init some private data
1527  m_ChartFamily = CHART_FAMILY_RASTER;
1528 
1529  pBitmapFilePath = NULL;
1530 
1531  pline_table = NULL;
1532  ifs_buf = NULL;
1533 
1534  cached_image_ok = 0;
1535 
1536  pRefTable = (Refpoint *)malloc(sizeof(Refpoint));
1537  nRefpoint = 0;
1538  cPoints.status = 0;
1539  bHaveEmbeddedGeoref = false;
1540  n_wpx = 0;
1541  n_wpy = 0;
1542  n_pwx = 0;
1543  n_pwy = 0;
1544 
1545 #ifdef __OCPN__ANDROID__
1546  bUseLineCache = false;
1547 #else
1548  bUseLineCache = true;
1549 #endif
1550 
1551  m_Chart_Skew = 0.0;
1552 
1553  pPixCache = NULL;
1554 
1555  pLineCache = NULL;
1556 
1557  m_bilinear_limit = 8; // bilinear scaling only up to n
1558 
1559  ifs_bitmap = NULL;
1560  ifss_bitmap = NULL;
1561  ifs_hdr = NULL;
1562 
1563  for (int i = 0; i < N_BSB_COLORS; i++) pPalettes[i] = NULL;
1564 
1565  bGeoErrorSent = false;
1566  m_Chart_DU = 0;
1567  m_cph = 0.;
1568 
1569  m_mapped_color_index = COLOR_RGB_DEFAULT;
1570 
1571  m_datum_str = _T("WGS84"); // assume until proven otherwise
1572 
1573  m_dtm_lat = 0.;
1574  m_dtm_lon = 0.;
1575 
1576  m_dx = 0.;
1577  m_dy = 0.;
1578  m_proj_lat = 0.;
1579  m_proj_lon = 0.;
1580  m_proj_parameter = 0.;
1581  m_b_SHOM = false;
1582  m_b_apply_dtm = true;
1583 
1584  m_b_cdebug = 0;
1585 
1586 #ifdef OCPN_USE_CONFIG
1587  wxFileConfig *pfc = (wxFileConfig *)pConfig;
1588  pfc->SetPath(_T ( "/Settings" ));
1589  pfc->Read(_T ( "DebugBSBImg" ), &m_b_cdebug, 0);
1590 #endif
1591 }
1592 
1593 ChartBaseBSB::~ChartBaseBSB() {
1594  if (pBitmapFilePath) delete pBitmapFilePath;
1595 
1596  if (pline_table) free(pline_table);
1597 
1598  if (ifs_buf) free(ifs_buf);
1599 
1600  free(pRefTable);
1601  // free(pPlyTable);
1602 
1603  delete ifs_bitmap;
1604  delete ifs_hdr;
1605  delete ifss_bitmap;
1606 
1607  if (cPoints.status) {
1608  free(cPoints.tx);
1609  free(cPoints.ty);
1610  free(cPoints.lon);
1611  free(cPoints.lat);
1612 
1613  free(cPoints.pwx);
1614  free(cPoints.wpx);
1615  free(cPoints.pwy);
1616  free(cPoints.wpy);
1617  }
1618 
1619  // Free the line cache
1620  FreeLineCacheRows();
1621  free(pLineCache);
1622 
1623  delete pPixCache;
1624 
1625  for (int i = 0; i < N_BSB_COLORS; i++) delete pPalettes[i];
1626 }
1627 
1628 void ChartBaseBSB::FreeLineCacheRows(int start, int end) {
1629  if (pLineCache) {
1630  if (end < 0)
1631  end = Size_Y;
1632  else
1633  end = wxMin(end, Size_Y);
1634  for (int ylc = start; ylc < end; ylc++) {
1635  CachedLine *pt = &pLineCache[ylc];
1636  if (pt->bValid) {
1637  free(pt->pTileOffset);
1638  free(pt->pPix);
1639  pt->bValid = false;
1640  }
1641  }
1642  }
1643 }
1644 
1645 bool ChartBaseBSB::HaveLineCacheRow(int row) {
1646  if (pLineCache) {
1647  CachedLine *pt = &pLineCache[row];
1648  return pt->bValid;
1649  }
1650  return false;
1651 }
1652 
1653 // Report recommended minimum and maximum scale values for which use of this
1654 // chart is valid
1655 
1656 double ChartBaseBSB::GetNormalScaleMin(double canvas_scale_factor,
1657  bool b_allow_overzoom) {
1658  // if(b_allow_overzoom)
1659  return (canvas_scale_factor / m_ppm_avg) /
1660  32; // allow wide range overzoom overscale
1661  // else
1662  // return (canvas_scale_factor / m_ppm_avg) / 2; // don't
1663  // suggest too much overscale
1664 }
1665 
1666 double ChartBaseBSB::GetNormalScaleMax(double canvas_scale_factor,
1667  int canvas_width) {
1668  return (canvas_scale_factor / m_ppm_avg) *
1669  4.0; // excessive underscale is slow, and unreadable
1670 }
1671 
1672 double ChartBaseBSB::GetNearestPreferredScalePPM(double target_scale_ppm) {
1673  return GetClosestValidNaturalScalePPM(
1674  target_scale_ppm, .01, 64.); // changed from 32 to 64 to allow super
1675  // small scale BSB charts as quilt base
1676 }
1677 
1678 double ChartBaseBSB::GetClosestValidNaturalScalePPM(double target_scale,
1679  double scale_factor_min,
1680  double scale_factor_max) {
1681  double chart_1x_scale = GetPPM();
1682 
1683  double binary_scale_factor = 1.;
1684 
1685  // Overzoom....
1686  if (chart_1x_scale > target_scale) {
1687  double binary_scale_factor_max = 1 / scale_factor_min;
1688 
1689  while (binary_scale_factor < binary_scale_factor_max) {
1690  if (fabs((chart_1x_scale / binary_scale_factor) - target_scale) <
1691  (target_scale * 0.05))
1692  break;
1693  if ((chart_1x_scale / binary_scale_factor) < target_scale)
1694  break;
1695  else
1696  binary_scale_factor *= 2.;
1697  }
1698  }
1699 
1700  // Underzoom.....
1701  else {
1702  int ibsf = 1;
1703  int isf_max = (int)scale_factor_max;
1704  while (ibsf < isf_max) {
1705  if (fabs((chart_1x_scale * ibsf) - target_scale) < (target_scale * 0.05))
1706  break;
1707 
1708  else if ((chart_1x_scale * ibsf) > target_scale) {
1709  if (ibsf > 1) ibsf /= 2;
1710  break;
1711  } else
1712  ibsf *= 2;
1713  }
1714 
1715  binary_scale_factor = 1. / ibsf;
1716  }
1717 
1718  return chart_1x_scale / binary_scale_factor;
1719 }
1720 
1721 InitReturn ChartBaseBSB::Init(const wxString &name, ChartInitFlag init_flags) {
1722  m_global_color_scheme = GLOBAL_COLOR_SCHEME_RGB;
1723  return INIT_OK;
1724 }
1725 
1726 InitReturn ChartBaseBSB::PreInit(const wxString &name, ChartInitFlag init_flags,
1727  ColorScheme cs) {
1728  m_global_color_scheme = cs;
1729  return INIT_OK;
1730 }
1731 
1732 void ChartBaseBSB::CreatePaletteEntry(char *buffer, int palette_index) {
1733  if (palette_index < N_BSB_COLORS) {
1734  if (!pPalettes[palette_index]) pPalettes[palette_index] = new opncpnPalette;
1735  opncpnPalette *pp = pPalettes[palette_index];
1736 
1737  pp->FwdPalette =
1738  (int *)realloc(pp->FwdPalette, (pp->nFwd + 1) * sizeof(int));
1739  pp->RevPalette =
1740  (int *)realloc(pp->RevPalette, (pp->nRev + 1) * sizeof(int));
1741  pp->nFwd++;
1742  pp->nRev++;
1743 
1744  int i;
1745  int n, r, g, b;
1746  sscanf(&buffer[4], "%d,%d,%d,%d", &n, &r, &g, &b);
1747 
1748  i = n;
1749 
1750  int fcolor, rcolor;
1751  fcolor = (b << 16) + (g << 8) + r;
1752  rcolor = (r << 16) + (g << 8) + b;
1753 
1754  pp->RevPalette[i] = rcolor;
1755  pp->FwdPalette[i] = fcolor;
1756  }
1757 }
1758 
1759 InitReturn ChartBaseBSB::PostInit(void) {
1760  // catch undefined shift if not already done in derived classes
1761  if (nColorSize == wxEOF || nColorSize <= 0 || nColorSize > 7) {
1762  wxString msg(
1763  _T(" Invalid nColorSize data, corrupt in PostInit() on chart "));
1764  msg.Append(m_FullPath);
1765  wxLogMessage(msg);
1766  return INIT_FAIL_REMOVE;
1767  }
1768 
1769  if (Size_X <= 0 || Size_X > INT_MAX / 4 || Size_Y <= 0 ||
1770  Size_Y - 1 > INT_MAX / 4) {
1771  wxString msg(
1772  _T(" Invalid Size_X/Size_Y data, corrupt in PostInit() on chart "));
1773  msg.Append(m_FullPath);
1774  wxLogMessage(msg);
1775  return INIT_FAIL_REMOVE;
1776  }
1777 
1778  // Validate the palette array, substituting DEFAULT for missing entries
1779  int nfwd_def = 1;
1780  int nrev_def = 1;
1781  if (pPalettes[COLOR_RGB_DEFAULT]) {
1782  nrev_def = pPalettes[COLOR_RGB_DEFAULT]->nRev;
1783  nfwd_def = pPalettes[COLOR_RGB_DEFAULT]->nFwd;
1784  }
1785 
1786  for (int i = 0; i < N_BSB_COLORS; i++) {
1787  if (pPalettes[i] == NULL) {
1788  opncpnPalette *pNullSubPal = new opncpnPalette;
1789 
1790  pNullSubPal->nFwd = nfwd_def; // copy the palette count
1791  pNullSubPal->nRev = nrev_def; // copy the palette count
1792  // Deep copy the palette rgb tables
1793  free(pNullSubPal->FwdPalette);
1794  pNullSubPal->FwdPalette = (int *)malloc(pNullSubPal->nFwd * sizeof(int));
1795  if (pPalettes[COLOR_RGB_DEFAULT])
1796  memcpy(pNullSubPal->FwdPalette,
1797  pPalettes[COLOR_RGB_DEFAULT]->FwdPalette,
1798  pNullSubPal->nFwd * sizeof(int));
1799 
1800  free(pNullSubPal->RevPalette);
1801  pNullSubPal->RevPalette = (int *)malloc(pNullSubPal->nRev * sizeof(int));
1802  if (pPalettes[COLOR_RGB_DEFAULT])
1803  memcpy(pNullSubPal->RevPalette,
1804  pPalettes[COLOR_RGB_DEFAULT]->RevPalette,
1805  pNullSubPal->nRev * sizeof(int));
1806 
1807  pPalettes[i] = pNullSubPal;
1808  }
1809  }
1810 
1811  // Establish the palette type and default palette
1812  palette_direction = GetPaletteDir();
1813 
1814  SetColorScheme(m_global_color_scheme, false);
1815 
1816  // Allocate memory for ifs file buffering
1817  ifs_bufsize = Size_X * 4;
1818  ifs_buf = (unsigned char *)malloc(ifs_bufsize);
1819  if (!ifs_buf) return INIT_FAIL_REMOVE;
1820 
1821  ifs_bufend = ifs_buf + ifs_bufsize;
1822  ifs_lp = ifs_bufend;
1823  ifs_file_offset = -ifs_bufsize;
1824 
1825  // Create and load the line offset index table
1826  pline_table = NULL;
1827  pline_table = (int *)malloc((Size_Y + 1) * sizeof(int)); // Ugly....
1828  if (!pline_table) return INIT_FAIL_REMOVE;
1829 
1830  ifs_bitmap->SeekI((Size_Y + 1) * -4,
1831  wxFromEnd); // go to Beginning of offset table
1832  pline_table[Size_Y] = ifs_bitmap->TellI(); // fill in useful last table entry
1833 
1834  unsigned char *tmp = (unsigned char *)malloc(Size_Y * sizeof(int));
1835  ifs_bitmap->Read(tmp, Size_Y * sizeof(int));
1836  if (ifs_bitmap->LastRead() != Size_Y * sizeof(int)) {
1837  wxString msg(_T(" Chart File corrupt in PostInit() on chart "));
1838  msg.Append(m_FullPath);
1839  wxLogMessage(msg);
1840  free(tmp);
1841 
1842  return INIT_FAIL_REMOVE;
1843  }
1844 
1845  int offset;
1846  unsigned char *b = tmp;
1847  for (int ifplt = 0; ifplt < Size_Y; ifplt++) {
1848  offset = 0;
1849  offset += *b++ * 256 * 256 * 256;
1850  offset += *b++ * 256 * 256;
1851  offset += *b++ * 256;
1852  offset += *b++;
1853 
1854  pline_table[ifplt] = offset;
1855  }
1856  free(tmp);
1857  // Try to validate the line index
1858 
1859  bool bline_index_ok = true;
1860  m_nLineOffset = 0;
1861 
1862  wxULongLong bitmap_filesize = m_filesize;
1863  if ((m_ChartType == CHART_TYPE_GEO) && pBitmapFilePath)
1864  bitmap_filesize = wxFileName::GetSize(*pBitmapFilePath);
1865 
1866  // look logically at the line offset table
1867  for (int iplt = 0; iplt < Size_Y - 1; iplt++) {
1868  if (pline_table[iplt] > bitmap_filesize) {
1869  wxString msg(_T(" Chart File corrupt in PostInit() on chart "));
1870  msg.Append(m_FullPath);
1871  wxLogMessage(msg);
1872 
1873  return INIT_FAIL_REMOVE;
1874  }
1875 
1876  int thisline_size = pline_table[iplt + 1] - pline_table[iplt];
1877  if (thisline_size < 0) {
1878  wxString msg(_T(" Chart File corrupt in PostInit() on chart "));
1879  msg.Append(m_FullPath);
1880  wxLogMessage(msg);
1881 
1882  return INIT_FAIL_REMOVE;
1883  }
1884  }
1885 
1886  // For older charts, say Version 1.x, we will try to read the chart and check
1887  // the lines for coherence These older charts are more likely to have index
1888  // troubles.... We only need to check a few lines. Errors are quickly
1889  // apparent.
1890  double ver;
1891  m_bsb_ver.ToDouble(&ver);
1892  if (ver < 2.0) {
1893  for (int iplt = 0; iplt < 10; iplt++) {
1894  if (wxInvalidOffset ==
1895  ifs_bitmap->SeekI(pline_table[iplt], wxFromStart)) {
1896  wxString msg(_T(" Chart File corrupt in PostInit() on chart "));
1897  msg.Append(m_FullPath);
1898  wxLogMessage(msg);
1899 
1900  return INIT_FAIL_REMOVE;
1901  }
1902 
1903  int thisline_size = pline_table[iplt + 1] - pline_table[iplt];
1904  ifs_bitmap->Read(ifs_buf, thisline_size);
1905 
1906  unsigned char *lp = ifs_buf;
1907 
1908  unsigned char byNext;
1909  int nLineMarker = 0;
1910  do {
1911  byNext = *lp++;
1912  nLineMarker = nLineMarker * 128 + (byNext & 0x7f);
1913  } while ((byNext & 0x80) != 0);
1914 
1915  // Linemarker Correction factor needed here
1916  // Some charts start with LineMarker = 0, some with LineMarker = 1
1917  // Assume the first LineMarker found is the index base, and use
1918  // as a correction offset
1919 
1920  if (iplt == 0) m_nLineOffset = nLineMarker;
1921 
1922  if (nLineMarker != iplt + m_nLineOffset) {
1923  bline_index_ok = false;
1924  break;
1925  }
1926  }
1927  }
1928 
1929  // Recreate the scan line index if the embedded version seems corrupt
1930  if (!bline_index_ok) {
1931  wxString msg(_T(" Line Index corrupt, recreating Index for chart "));
1932  msg.Append(m_FullPath);
1933  wxLogMessage(msg);
1934  if (!CreateLineIndex()) {
1935  wxString msg(_T(" Error creating Line Index for chart "));
1936  msg.Append(m_FullPath);
1937  wxLogMessage(msg);
1938  return INIT_FAIL_REMOVE;
1939  }
1940  }
1941 
1942  // Allocate the Line Cache
1943  if (bUseLineCache) {
1944  pLineCache = (CachedLine *)malloc(Size_Y * sizeof(CachedLine));
1945  CachedLine *pt;
1946 
1947  for (int ylc = 0; ylc < Size_Y; ylc++) {
1948  pt = &pLineCache[ylc];
1949  pt->bValid = false;
1950  pt->pPix = NULL; //(unsigned char *)malloc(1);
1951  pt->pTileOffset = NULL;
1952  }
1953  } else
1954  pLineCache = NULL;
1955 
1956  // Validate/Set Depth Unit Type
1957  wxString test_str = m_DepthUnits.Upper();
1958  if (test_str.IsSameAs(_T("FEET"), FALSE))
1959  m_depth_unit_id = DEPTH_UNIT_FEET;
1960  else if (test_str.IsSameAs(_T("METERS"), FALSE))
1961  m_depth_unit_id = DEPTH_UNIT_METERS;
1962  else if (test_str.IsSameAs(_T("METRES"),
1963  FALSE)) // Special case for alternate spelling
1964  m_depth_unit_id = DEPTH_UNIT_METERS;
1965  else if (test_str.IsSameAs(_T("METRIC"), FALSE))
1966  m_depth_unit_id = DEPTH_UNIT_METERS;
1967  else if (test_str.IsSameAs(_T("FATHOMS"), FALSE))
1968  m_depth_unit_id = DEPTH_UNIT_FATHOMS;
1969  else if (test_str.Find(_T("FATHOMS")) !=
1970  wxNOT_FOUND) // Special case for "Fathoms and Feet"
1971  m_depth_unit_id = DEPTH_UNIT_FATHOMS;
1972  else if (test_str.Find(_T("METERS")) !=
1973  wxNOT_FOUND) // Special case for "Meters and decimeters"
1974  m_depth_unit_id = DEPTH_UNIT_METERS;
1975 
1976  // Analyze Refpoints
1977  int analyze_ret_val = AnalyzeRefpoints();
1978  if (0 != analyze_ret_val) return INIT_FAIL_REMOVE;
1979 
1980  bReadyToRender = true;
1981  return INIT_OK;
1982 }
1983 
1984 bool ChartBaseBSB::CreateLineIndex() {
1985  // Assumes file stream ifs_bitmap is currently open
1986 
1987  // wxBufferedInputStream *pbis = new wxBufferedInputStream(*ifss_bitmap);
1988 
1989  // Seek to start of data
1990  ifs_bitmap->SeekI(nFileOffsetDataStart); // go to Beginning of data
1991 
1992  for (int iplt = 0; iplt < Size_Y; iplt++) {
1993  int offset = ifs_bitmap->TellI();
1994 
1995  int iscan = BSBScanScanline(ifs_bitmap);
1996 
1997  // There is no sense reporting an error here, since we are recreating after
1998  // an error
1999  /*
2000  if(iscan > Size_Y)
2001  {
2002 
2003  wxString msg(_T("CreateLineIndex() failed on chart "));
2004  msg.Append(m_FullPath);
2005  wxLogMessage(msg);
2006  return false;
2007  }
2008 
2009  // Skipped lines?
2010  if(iscan != iplt)
2011  {
2012  while((iplt < iscan) && (iplt < Size_Y))
2013  {
2014  pline_table[iplt] = 0;
2015  iplt++;
2016  }
2017  }
2018  */
2019  pline_table[iplt] = offset;
2020  }
2021 
2022  return true;
2023 }
2024 
2025 // Invalidate and Free the line cache contents
2026 void ChartBaseBSB::InvalidateLineCache(void) {
2027  if (pLineCache) {
2028  CachedLine *pt;
2029  for (int ylc = 0; ylc < Size_Y; ylc++) {
2030  pt = &pLineCache[ylc];
2031  if (pt) {
2032  free(pt->pPix);
2033  pt->pPix = NULL;
2034  free(pt->pTileOffset);
2035  pt->pTileOffset = NULL;
2036  pt->bValid = false;
2037  }
2038  }
2039  }
2040 }
2041 
2042 bool ChartBaseBSB::GetChartExtent(Extent *pext) {
2043  pext->NLAT = m_LatMax;
2044  pext->SLAT = m_LatMin;
2045  pext->ELON = m_LonMax;
2046  pext->WLON = m_LonMin;
2047 
2048  return true;
2049 }
2050 
2051 bool ChartBaseBSB::SetMinMax(void) {
2052  // Calculate the Chart Extents(M_LatMin, M_LonMin, etc.)
2053  // from the COVR data, for fast database search
2054  m_LonMax = -360.0;
2055  m_LonMin = 360.0;
2056  m_LatMax = -90.0;
2057  m_LatMin = 90.0;
2058 
2059  Plypoint *ppp = (Plypoint *)GetCOVRTableHead(0);
2060  int cnPlypoint = GetCOVRTablenPoints(0);
2061 
2062  for (int u = 0; u < cnPlypoint; u++) {
2063  if (ppp->lnp > m_LonMax) m_LonMax = ppp->lnp;
2064  if (ppp->lnp < m_LonMin) m_LonMin = ppp->lnp;
2065 
2066  if (ppp->ltp > m_LatMax) m_LatMax = ppp->ltp;
2067  if (ppp->ltp < m_LatMin) m_LatMin = ppp->ltp;
2068 
2069  ppp++;
2070  }
2071 
2072  // Check for special cases
2073 
2074  // Case 1: Chart spans International Date Line or Greenwich, Longitude
2075  // min/max is non-obvious.
2076  if ((m_LonMax * m_LonMin) < 0) // min/max are opposite signs
2077  {
2078  // Georeferencing is not yet available, so find the reference points
2079  // closest to min/max ply points
2080 
2081  if (0 == nRefpoint) return false; // have to bail here
2082 
2083  // for m_LonMax
2084  double min_dist_x = 360;
2085  int imaxclose = 0;
2086  for (int ic = 0; ic < nRefpoint; ic++) {
2087  double dist = sqrt(
2088  ((m_LatMax - pRefTable[ic].latr) * (m_LatMax - pRefTable[ic].latr)) +
2089  ((m_LonMax - pRefTable[ic].lonr) * (m_LonMax - pRefTable[ic].lonr)));
2090 
2091  if (dist < min_dist_x) {
2092  min_dist_x = dist;
2093  imaxclose = ic;
2094  }
2095  }
2096 
2097  // for m_LonMin
2098  double min_dist_n = 360;
2099  int iminclose = 0;
2100  for (int id = 0; id < nRefpoint; id++) {
2101  double dist = sqrt(
2102  ((m_LatMin - pRefTable[id].latr) * (m_LatMin - pRefTable[id].latr)) +
2103  ((m_LonMin - pRefTable[id].lonr) * (m_LonMin - pRefTable[id].lonr)));
2104 
2105  if (dist < min_dist_n) {
2106  min_dist_n = dist;
2107  iminclose = id;
2108  }
2109  }
2110 
2111  // Is this chart crossing IDL or Greenwich?
2112  // Make the check
2113  if (pRefTable[imaxclose].xr < pRefTable[iminclose].xr) {
2114  // This chart crosses IDL and needs a flip, meaning that all negative
2115  // longitudes need to be normalized and the min/max relcalculated This
2116  // code added to correct non-rectangular charts crossing IDL, such as
2117  // nz14605.kap
2118 
2119  m_LonMax = -360.0;
2120  m_LonMin = 360.0;
2121  m_LatMax = -90.0;
2122  m_LatMin = 90.0;
2123 
2124  Plypoint *ppp =
2125  (Plypoint *)GetCOVRTableHead(0); // Normalize the plypoints
2126  int cnPlypoint = GetCOVRTablenPoints(0);
2127 
2128  for (int u = 0; u < cnPlypoint; u++) {
2129  if (ppp->lnp < 0.) ppp->lnp += 360.;
2130 
2131  if (ppp->lnp > m_LonMax) m_LonMax = ppp->lnp;
2132  if (ppp->lnp < m_LonMin) m_LonMin = ppp->lnp;
2133 
2134  if (ppp->ltp > m_LatMax) m_LatMax = ppp->ltp;
2135  if (ppp->ltp < m_LatMin) m_LatMin = ppp->ltp;
2136 
2137  ppp++;
2138  }
2139  }
2140  }
2141 
2142  // Case 2 Lons are both < -180, which means the extent will be reported
2143  // incorrectly and the plypoint structure will be wrong This case is seen
2144  // first on 81004_1.KAP, (Mariannas)
2145 
2146  if ((m_LonMax < -180.) && (m_LonMin < -180.)) {
2147  m_LonMin += 360.; // Normalize the extents
2148  m_LonMax += 360.;
2149 
2150  Plypoint *ppp = (Plypoint *)GetCOVRTableHead(0); // Normalize the plypoints
2151  int cnPlypoint = GetCOVRTablenPoints(0);
2152 
2153  for (int u = 0; u < cnPlypoint; u++) {
2154  ppp->lnp += 360.;
2155  ppp++;
2156  }
2157  }
2158 
2159  return true;
2160 }
2161 
2162 void ChartBaseBSB::SetColorScheme(ColorScheme cs, bool bApplyImmediate) {
2163  // Here we convert (subjectively) the Global ColorScheme
2164  // to an appropriate BSB_Color_Capability index.
2165 
2166  switch (cs) {
2167  case GLOBAL_COLOR_SCHEME_RGB:
2168  m_mapped_color_index = COLOR_RGB_DEFAULT;
2169  break;
2170  case GLOBAL_COLOR_SCHEME_DAY:
2171  m_mapped_color_index = DAY;
2172  break;
2173  case GLOBAL_COLOR_SCHEME_DUSK:
2174  m_mapped_color_index = DUSK;
2175  break;
2176  case GLOBAL_COLOR_SCHEME_NIGHT:
2177  m_mapped_color_index = NIGHT;
2178  break;
2179  default:
2180  m_mapped_color_index = DAY;
2181  break;
2182  }
2183 
2184  pPalette = GetPalettePtr(m_mapped_color_index);
2185 
2186  m_global_color_scheme = cs;
2187 
2188  // Force a cache dump in a simple sideways manner
2189  if (bApplyImmediate) {
2190  m_cached_scale_ppm = 1.0;
2191  }
2192 
2193  // Force a new thumbnail
2194  if (pThumbData) pThumbData->pDIBThumb = NULL;
2195 }
2196 
2197 wxBitmap *ChartBaseBSB::CreateThumbnail(int tnx, int tny, ColorScheme cs) {
2198  // Calculate the size and divisors
2199 
2200  int divx = wxMax(1, Size_X / (4 * tnx));
2201  int divy = wxMax(1, Size_Y / (4 * tny));
2202 
2203  int div_factor = std::min(divx, divy);
2204 
2205  int des_width = Size_X / div_factor;
2206  int des_height = Size_Y / div_factor;
2207 
2208  wxRect gts;
2209  gts.x = 0; // full chart
2210  gts.y = 0;
2211  gts.width = Size_X;
2212  gts.height = Size_Y;
2213 
2214  int this_bpp = 24; // for wxImage
2215  // Allocate the pixel storage needed for one line of chart bits
2216  unsigned char *pLineT = (unsigned char *)malloc((Size_X + 1) * BPP / 8);
2217 
2218  // Scale the data quickly
2219  unsigned char *pPixTN =
2220  (unsigned char *)malloc(des_width * des_height * this_bpp / 8);
2221 
2222  int ix = 0;
2223  int iy = 0;
2224  int iyd = 0;
2225  int ixd = 0;
2226  int yoffd;
2227  unsigned char *pxs;
2228  unsigned char *pxd;
2229 
2230  // Temporarily set the color scheme
2231  ColorScheme cs_tmp = m_global_color_scheme;
2232  SetColorScheme(cs, false);
2233 
2234  while (iyd < des_height) {
2235  if (0 == BSBGetScanline(pLineT, iy, 0, Size_X, 1)) // get a line
2236  {
2237  free(pLineT);
2238  free(pPixTN);
2239  return NULL;
2240  }
2241 
2242  yoffd = iyd * des_width * this_bpp / 8; // destination y
2243 
2244  ix = 0;
2245  ixd = 0;
2246  while (ixd < des_width) {
2247  pxs = pLineT + (ix * BPP / 8);
2248  pxd = pPixTN + (yoffd + (ixd * this_bpp / 8));
2249  *pxd++ = *pxs++;
2250  *pxd++ = *pxs++;
2251  *pxd = *pxs;
2252 
2253  ix += div_factor;
2254  ixd++;
2255  }
2256 
2257  iy += div_factor;
2258  iyd++;
2259  }
2260 
2261  free(pLineT);
2262 
2263  // Reset ColorScheme
2264  SetColorScheme(cs_tmp, false);
2265 
2266  wxBitmap *retBMP;
2267 
2268 #ifdef ocpnUSE_ocpnBitmap
2269  wxBitmap *bmx2 = new ocpnBitmap(pPixTN, des_width, des_height, -1);
2270  wxImage imgx2 = bmx2->ConvertToImage();
2271  imgx2.Rescale(des_width / 4, des_height / 4, wxIMAGE_QUALITY_HIGH);
2272  retBMP = new wxBitmap(imgx2);
2273  delete bmx2;
2274 #else
2275  wxImage thumb_image(des_width, des_height, pPixTN, true);
2276  thumb_image.Rescale(des_width / 4, des_height / 4, wxIMAGE_QUALITY_HIGH);
2277  retBMP = new wxBitmap(thumb_image);
2278 #endif
2279 
2280  free(pPixTN);
2281 
2282  return retBMP;
2283 }
2284 
2285 //-------------------------------------------------------------------------------------------------
2286 // Get the Chart thumbnail data structure
2287 // Creating the thumbnail bitmap as required
2288 //-------------------------------------------------------------------------------------------------
2289 
2290 ThumbData *ChartBaseBSB::GetThumbData(int tnx, int tny, float lat, float lon) {
2291  // Create the bitmap if needed
2292  if (!pThumbData->pDIBThumb)
2293  pThumbData->pDIBThumb = CreateThumbnail(tnx, tny, m_global_color_scheme);
2294 
2295  pThumbData->Thumb_Size_X = tnx;
2296  pThumbData->Thumb_Size_Y = tny;
2297 
2298  // Plot the supplied Lat/Lon on the thumbnail
2299  int divx = Size_X / tnx;
2300  int divy = Size_Y / tny;
2301 
2302  int div_factor = std::min(divx, divy);
2303 
2304  double pixx, pixy;
2305 
2306  // Using a temporary synthetic ViewPort and source rectangle,
2307  // calculate the ships position on the thumbnail
2308  ViewPort tvp;
2309  tvp.pix_width = tnx;
2310  tvp.pix_height = tny;
2311  tvp.view_scale_ppm = GetPPM() / div_factor;
2312  wxRect trex = Rsrc;
2313  Rsrc.x = 0;
2314  Rsrc.y = 0;
2315  latlong_to_pix_vp(lat, lon, pixx, pixy, tvp);
2316  Rsrc = trex;
2317 
2318  pThumbData->ShipX = pixx; // / div_factor;
2319  pThumbData->ShipY = pixy; // / div_factor;
2320 
2321  return pThumbData;
2322 }
2323 
2324 bool ChartBaseBSB::UpdateThumbData(double lat, double lon) {
2325  // Plot the supplied Lat/Lon on the thumbnail
2326  // Return TRUE if the pixel location of ownship has changed
2327 
2328  int divx = Size_X / pThumbData->Thumb_Size_X;
2329  int divy = Size_Y / pThumbData->Thumb_Size_Y;
2330 
2331  int div_factor = std::min(divx, divy);
2332 
2333  double pixx_test, pixy_test;
2334 
2335  // Using a temporary synthetic ViewPort and source rectangle,
2336  // calculate the ships position on the thumbnail
2337  ViewPort tvp;
2338  tvp.pix_width = pThumbData->Thumb_Size_X;
2339  tvp.pix_height = pThumbData->Thumb_Size_Y;
2340  tvp.view_scale_ppm = GetPPM() / div_factor;
2341  wxRect trex = Rsrc;
2342  Rsrc.x = 0;
2343  Rsrc.y = 0;
2344  latlong_to_pix_vp(lat, lon, pixx_test, pixy_test, tvp);
2345  Rsrc = trex;
2346 
2347  if ((pixx_test != pThumbData->ShipX) || (pixy_test != pThumbData->ShipY)) {
2348  pThumbData->ShipX = pixx_test;
2349  pThumbData->ShipY = pixy_test;
2350  return TRUE;
2351  } else
2352  return FALSE;
2353 }
2354 
2355 //-----------------------------------------------------------------------
2356 // Pixel to Lat/Long Conversion helpers
2357 //-----------------------------------------------------------------------
2358 static double polytrans(double *coeff, double lon, double lat);
2359 
2360 int ChartBaseBSB::vp_pix_to_latlong(ViewPort &vp, double pixx, double pixy,
2361  double *plat, double *plon) {
2362  if (bHaveEmbeddedGeoref) {
2363  double raster_scale = GetPPM() / vp.view_scale_ppm;
2364 
2365  double px = pixx * raster_scale + Rsrc.x;
2366  double py = pixy * raster_scale + Rsrc.y;
2367  // pix_to_latlong(px, py, plat, plon);
2368 
2369  if (1) {
2370  double lon = polytrans(pwx, px, py);
2371  lon = (lon < 0) ? lon + m_cph : lon - m_cph;
2372  *plon = lon - m_lon_datum_adjust;
2373  *plat = polytrans(pwy, px, py) - m_lat_datum_adjust;
2374  }
2375 
2376  return 0;
2377  } else {
2378  double slat, slon;
2379  double xp, yp;
2380 
2381  if (m_projection == PROJECTION_TRANSVERSE_MERCATOR) {
2382  // Use Projected Polynomial algorithm
2383 
2384  double raster_scale = GetPPM() / vp.view_scale_ppm;
2385 
2386  // Apply poly solution to vp center point
2387  double easting, northing;
2388  toTM(vp.clat + m_lat_datum_adjust, vp.clon + m_lon_datum_adjust,
2389  m_proj_lat, m_proj_lon, &easting, &northing);
2390  double xc = polytrans(cPoints.wpx, easting, northing);
2391  double yc = polytrans(cPoints.wpy, easting, northing);
2392 
2393  // convert screen pixels to chart pixmap relative
2394  double px = xc + (pixx - (vp.pix_width / 2)) * raster_scale;
2395  double py = yc + (pixy - (vp.pix_height / 2)) * raster_scale;
2396 
2397  // Apply polynomial solution to chart relative pixels to get e/n
2398  double east = polytrans(cPoints.pwx, px, py);
2399  double north = polytrans(cPoints.pwy, px, py);
2400 
2401  // Apply inverse Projection to get lat/lon
2402  double lat, lon;
2403  fromTM(east, north, m_proj_lat, m_proj_lon, &lat, &lon);
2404 
2405  // Datum adjustments.....
2406  //?? lon = (lon < 0) ? lon + m_cph : lon - m_cph;
2407  double slon_p = lon - m_lon_datum_adjust;
2408  double slat_p = lat - m_lat_datum_adjust;
2409 
2410  // printf("%8g %8g %8g %8g %g\n", slat, slat_p, slon,
2411  // slon_p, slon - slon_p);
2412  slon = slon_p;
2413  slat = slat_p;
2414 
2415  } else if (m_projection == PROJECTION_MERCATOR) {
2416  // Use Projected Polynomial algorithm
2417 
2418  double raster_scale = GetPPM() / vp.view_scale_ppm;
2419 
2420  // Apply poly solution to vp center point
2421  double easting, northing;
2422  toSM_ECC(vp.clat + m_lat_datum_adjust, vp.clon + m_lon_datum_adjust,
2423  m_proj_lat, m_proj_lon, &easting, &northing);
2424  double xc = polytrans(cPoints.wpx, easting, northing);
2425  double yc = polytrans(cPoints.wpy, easting, northing);
2426 
2427  // convert screen pixels to chart pixmap relative
2428  double px = xc + (pixx - (vp.pix_width / 2)) * raster_scale;
2429  double py = yc + (pixy - (vp.pix_height / 2)) * raster_scale;
2430 
2431  // Apply polynomial solution to chart relative pixels to get e/n
2432  double east = polytrans(cPoints.pwx, px, py);
2433  double north = polytrans(cPoints.pwy, px, py);
2434 
2435  // Apply inverse Projection to get lat/lon
2436  double lat, lon;
2437  fromSM_ECC(east, north, m_proj_lat, m_proj_lon, &lat, &lon);
2438 
2439  // Make Datum adjustments.....
2440  double slon_p = lon - m_lon_datum_adjust;
2441  double slat_p = lat - m_lat_datum_adjust;
2442 
2443  slon = slon_p;
2444  slat = slat_p;
2445 
2446  // printf("vp.clon %g xc %g px %g east %g
2447  // \n", vp.clon, xc, px, east);
2448 
2449  } else if (m_projection == PROJECTION_POLYCONIC) {
2450  // Use Projected Polynomial algorithm
2451 
2452  double raster_scale = GetPPM() / vp.view_scale_ppm;
2453 
2454  // Apply poly solution to vp center point
2455  double easting, northing;
2456  toPOLY(vp.clat + m_lat_datum_adjust, vp.clon + m_lon_datum_adjust,
2457  m_proj_lat, m_proj_lon, &easting, &northing);
2458  double xc = polytrans(cPoints.wpx, easting, northing);
2459  double yc = polytrans(cPoints.wpy, easting, northing);
2460 
2461  // convert screen pixels to chart pixmap relative
2462  double px = xc + (pixx - (vp.pix_width / 2)) * raster_scale;
2463  double py = yc + (pixy - (vp.pix_height / 2)) * raster_scale;
2464 
2465  // Apply polynomial solution to chart relative pixels to get e/n
2466  double east = polytrans(cPoints.pwx, px, py);
2467  double north = polytrans(cPoints.pwy, px, py);
2468 
2469  // Apply inverse Projection to get lat/lon
2470  double lat, lon;
2471  fromPOLY(east, north, m_proj_lat, m_proj_lon, &lat, &lon);
2472 
2473  // Make Datum adjustments.....
2474  double slon_p = lon - m_lon_datum_adjust;
2475  double slat_p = lat - m_lat_datum_adjust;
2476 
2477  slon = slon_p;
2478  slat = slat_p;
2479 
2480  } else {
2481  // Use a Mercator estimator, with Eccentricity corrrection applied
2482  int dx = pixx - (vp.pix_width / 2);
2483  int dy = (vp.pix_height / 2) - pixy;
2484 
2485  xp = (dx * cos(vp.skew)) - (dy * sin(vp.skew));
2486  yp = (dy * cos(vp.skew)) + (dx * sin(vp.skew));
2487 
2488  double d_east = xp / vp.view_scale_ppm;
2489  double d_north = yp / vp.view_scale_ppm;
2490 
2491  fromSM_ECC(d_east, d_north, vp.clat, vp.clon, &slat, &slon);
2492  }
2493 
2494  *plat = slat;
2495 
2496  if (slon < -180.)
2497  slon += 360.;
2498  else if (slon > 180.)
2499  slon -= 360.;
2500  *plon = slon;
2501 
2502  return 0;
2503  }
2504 }
2505 
2506 int ChartBaseBSB::latlong_to_pix_vp(double lat, double lon, double &pixx,
2507  double &pixy, ViewPort &vp) {
2508  double alat, alon;
2509 
2510  if (bHaveEmbeddedGeoref) {
2511  double alat, alon;
2512 
2513  alon = lon + m_lon_datum_adjust;
2514  alat = lat + m_lat_datum_adjust;
2515 
2516  AdjustLongitude(alon);
2517 
2518  if (1) {
2519  /* change longitude phase (CPH) */
2520  double lonp = (alon < 0) ? alon + m_cph : alon - m_cph;
2521  double xd = polytrans(wpx, lonp, alat);
2522  double yd = polytrans(wpy, lonp, alat);
2523 
2524  double raster_scale = GetPPM() / vp.view_scale_ppm;
2525 
2526  pixx = (xd - Rsrc.x) / raster_scale;
2527  pixy = (yd - Rsrc.y) / raster_scale;
2528 
2529  return 0;
2530  }
2531  } else {
2532  double easting, northing;
2533  double xlon = lon;
2534 
2535  // Make sure lon and lon0 are same phase
2536  /*
2537  if((xlon * vp.clon) < 0.)
2538  {
2539  if(xlon < 0.)
2540  xlon += 360.;
2541  else
2542  xlon -= 360.;
2543  }
2544 
2545  if(fabs(xlon - vp.clon) > 180.)
2546  {
2547  if(xlon > vp.clon)
2548  xlon -= 360.;
2549  else
2550  xlon += 360.;
2551  }
2552  */
2553 
2554  if (m_projection == PROJECTION_TRANSVERSE_MERCATOR) {
2555  // Use Projected Polynomial algorithm
2556 
2557  alon = lon + m_lon_datum_adjust;
2558  alat = lat + m_lat_datum_adjust;
2559 
2560  // Get e/n from TM Projection
2561  toTM(alat, alon, m_proj_lat, m_proj_lon, &easting, &northing);
2562 
2563  // Apply poly solution to target point
2564  double xd = polytrans(cPoints.wpx, easting, northing);
2565  double yd = polytrans(cPoints.wpy, easting, northing);
2566 
2567  // Apply poly solution to vp center point
2568  toTM(vp.clat + m_lat_datum_adjust, vp.clon + m_lon_datum_adjust,
2569  m_proj_lat, m_proj_lon, &easting, &northing);
2570  double xc = polytrans(cPoints.wpx, easting, northing);
2571  double yc = polytrans(cPoints.wpy, easting, northing);
2572 
2573  // Calculate target point relative to vp center
2574  double raster_scale = GetPPM() / vp.view_scale_ppm;
2575 
2576  double xs = xc - vp.pix_width * raster_scale / 2;
2577  double ys = yc - vp.pix_height * raster_scale / 2;
2578 
2579  pixx = (xd - xs) / raster_scale;
2580  pixy = (yd - ys) / raster_scale;
2581 
2582  } else if (m_projection == PROJECTION_MERCATOR) {
2583  // Use Projected Polynomial algorithm
2584 
2585  alon = lon + m_lon_datum_adjust;
2586  alat = lat + m_lat_datum_adjust;
2587 
2588  // Get e/n from Projection
2589  xlon = alon;
2590  AdjustLongitude(xlon);
2591  toSM_ECC(alat, xlon, m_proj_lat, m_proj_lon, &easting, &northing);
2592 
2593  // Apply poly solution to target point
2594  double xd = polytrans(cPoints.wpx, easting, northing);
2595  double yd = polytrans(cPoints.wpy, easting, northing);
2596 
2597  // Apply poly solution to vp center point
2598  double xlonc = vp.clon;
2599  AdjustLongitude(xlonc);
2600 
2601  toSM_ECC(vp.clat + m_lat_datum_adjust, xlonc + m_lon_datum_adjust,
2602  m_proj_lat, m_proj_lon, &easting, &northing);
2603  double xc = polytrans(cPoints.wpx, easting, northing);
2604  double yc = polytrans(cPoints.wpy, easting, northing);
2605 
2606  // Calculate target point relative to vp center
2607  double raster_scale = GetPPM() / vp.view_scale_ppm;
2608 
2609  double xs = xc - vp.pix_width * raster_scale / 2;
2610  double ys = yc - vp.pix_height * raster_scale / 2;
2611 
2612  pixx = (xd - xs) / raster_scale;
2613  pixy = (yd - ys) / raster_scale;
2614 
2615  } else if (m_projection == PROJECTION_POLYCONIC) {
2616  // Use Projected Polynomial algorithm
2617 
2618  alon = lon + m_lon_datum_adjust;
2619  alat = lat + m_lat_datum_adjust;
2620 
2621  // Get e/n from Projection
2622  xlon = AdjustLongitude(alon);
2623  toPOLY(alat, xlon, m_proj_lat, m_proj_lon, &easting, &northing);
2624 
2625  // Apply poly solution to target point
2626  double xd = polytrans(cPoints.wpx, easting, northing);
2627  double yd = polytrans(cPoints.wpy, easting, northing);
2628 
2629  // Apply poly solution to vp center point
2630  double xlonc = AdjustLongitude(vp.clon);
2631 
2632  toPOLY(vp.clat + m_lat_datum_adjust, xlonc + m_lon_datum_adjust,
2633  m_proj_lat, m_proj_lon, &easting, &northing);
2634  double xc = polytrans(cPoints.wpx, easting, northing);
2635  double yc = polytrans(cPoints.wpy, easting, northing);
2636 
2637  // Calculate target point relative to vp center
2638  double raster_scale = GetPPM() / vp.view_scale_ppm;
2639 
2640  double xs = xc - vp.pix_width * raster_scale / 2;
2641  double ys = yc - vp.pix_height * raster_scale / 2;
2642 
2643  pixx = (xd - xs) / raster_scale;
2644  pixy = (yd - ys) / raster_scale;
2645 
2646  } else {
2647  toSM_ECC(lat, xlon, vp.clat, vp.clon, &easting, &northing);
2648 
2649  double epix = easting * vp.view_scale_ppm;
2650  double npix = northing * vp.view_scale_ppm;
2651 
2652  double dx = epix * cos(vp.skew) + npix * sin(vp.skew);
2653  double dy = npix * cos(vp.skew) - epix * sin(vp.skew);
2654 
2655  pixx = ((double)vp.pix_width / 2) + dx;
2656  pixy = ((double)vp.pix_height / 2) - dy;
2657  }
2658  return 0;
2659  }
2660 
2661  return 1;
2662 }
2663 
2664 void ChartBaseBSB::latlong_to_chartpix(double lat, double lon, double &pixx,
2665  double &pixy) {
2666  double alat, alon;
2667  pixx = 0.0;
2668  pixy = 0.0;
2669 
2670  if (bHaveEmbeddedGeoref) {
2671  double alat, alon;
2672 
2673  alon = lon + m_lon_datum_adjust;
2674  alat = lat + m_lat_datum_adjust;
2675 
2676  alon = AdjustLongitude(alon);
2677 
2678  /* change longitude phase (CPH) */
2679  double lonp = (alon < 0) ? alon + m_cph : alon - m_cph;
2680  pixx = polytrans(wpx, lonp, alat);
2681  pixy = polytrans(wpy, lonp, alat);
2682  } else {
2683  double easting, northing;
2684  double xlon = lon;
2685 
2686  if (m_projection == PROJECTION_TRANSVERSE_MERCATOR) {
2687  // Use Projected Polynomial algorithm
2688 
2689  alon = lon + m_lon_datum_adjust;
2690  alat = lat + m_lat_datum_adjust;
2691 
2692  // Get e/n from TM Projection
2693  toTM(alat, alon, m_proj_lat, m_proj_lon, &easting, &northing);
2694 
2695  // Apply poly solution to target point
2696  pixx = polytrans(cPoints.wpx, easting, northing);
2697  pixy = polytrans(cPoints.wpy, easting, northing);
2698 
2699  } else if (m_projection == PROJECTION_MERCATOR) {
2700  // Use Projected Polynomial algorithm
2701 
2702  alon = lon + m_lon_datum_adjust;
2703  alat = lat + m_lat_datum_adjust;
2704 
2705  // Get e/n from Projection
2706  xlon = AdjustLongitude(alon);
2707 
2708  toSM_ECC(alat, xlon, m_proj_lat, m_proj_lon, &easting, &northing);
2709 
2710  // Apply poly solution to target point
2711  pixx = polytrans(cPoints.wpx, easting, northing);
2712  pixy = polytrans(cPoints.wpy, easting, northing);
2713 
2714  } else if (m_projection == PROJECTION_POLYCONIC) {
2715  // Use Projected Polynomial algorithm
2716 
2717  alon = lon + m_lon_datum_adjust;
2718  alat = lat + m_lat_datum_adjust;
2719 
2720  // Get e/n from Projection
2721  xlon = AdjustLongitude(alon);
2722  toPOLY(alat, xlon, m_proj_lat, m_proj_lon, &easting, &northing);
2723 
2724  // Apply poly solution to target point
2725  pixx = polytrans(cPoints.wpx, easting, northing);
2726  pixy = polytrans(cPoints.wpy, easting, northing);
2727  }
2728  }
2729 }
2730 
2731 void ChartBaseBSB::chartpix_to_latlong(double pixx, double pixy, double *plat,
2732  double *plon) {
2733  if (bHaveEmbeddedGeoref) {
2734  double lon = polytrans(pwx, pixx, pixy);
2735  lon = (lon < 0) ? lon + m_cph : lon - m_cph;
2736  *plon = lon - m_lon_datum_adjust;
2737  *plat = polytrans(pwy, pixx, pixy) - m_lat_datum_adjust;
2738  } else {
2739  double slat, slon;
2740  if (m_projection == PROJECTION_TRANSVERSE_MERCATOR) {
2741  // Use Projected Polynomial algorithm
2742 
2743  // Apply polynomial solution to chart relative pixels to get e/n
2744  double east = polytrans(cPoints.pwx, pixx, pixy);
2745  double north = polytrans(cPoints.pwy, pixx, pixy);
2746 
2747  // Apply inverse Projection to get lat/lon
2748  double lat, lon;
2749  fromTM(east, north, m_proj_lat, m_proj_lon, &lat, &lon);
2750 
2751  // Datum adjustments.....
2752  //?? lon = (lon < 0) ? lon + m_cph : lon - m_cph;
2753  slon = lon - m_lon_datum_adjust;
2754  slat = lat - m_lat_datum_adjust;
2755 
2756  } else if (m_projection == PROJECTION_MERCATOR) {
2757  // Use Projected Polynomial algorithm
2758  // Apply polynomial solution to chart relative pixels to get e/n
2759  double east = polytrans(cPoints.pwx, pixx, pixy);
2760  double north = polytrans(cPoints.pwy, pixx, pixy);
2761 
2762  // Apply inverse Projection to get lat/lon
2763  double lat, lon;
2764  fromSM_ECC(east, north, m_proj_lat, m_proj_lon, &lat, &lon);
2765 
2766  // Make Datum adjustments.....
2767  slon = lon - m_lon_datum_adjust;
2768  slat = lat - m_lat_datum_adjust;
2769  } else if (m_projection == PROJECTION_POLYCONIC) {
2770  // Use Projected Polynomial algorithm
2771  // Apply polynomial solution to chart relative pixels to get e/n
2772  double east = polytrans(cPoints.pwx, pixx, pixy);
2773  double north = polytrans(cPoints.pwy, pixx, pixy);
2774 
2775  // Apply inverse Projection to get lat/lon
2776  double lat, lon;
2777  fromPOLY(east, north, m_proj_lat, m_proj_lon, &lat, &lon);
2778 
2779  // Make Datum adjustments.....
2780  slon = lon - m_lon_datum_adjust;
2781  slat = lat - m_lat_datum_adjust;
2782 
2783  } else {
2784  slon = 0.;
2785  slat = 0.;
2786  }
2787 
2788  *plat = slat;
2789 
2790  if (slon < -180.)
2791  slon += 360.;
2792  else if (slon > 180.)
2793  slon -= 360.;
2794  *plon = slon;
2795  }
2796 }
2797 
2798 void ChartBaseBSB::ComputeSourceRectangle(const ViewPort &vp,
2799  wxRect *pSourceRect) {
2800  m_raster_scale_factor = GetRasterScaleFactor(vp);
2801  double xd, yd;
2802  latlong_to_chartpix(vp.clat, vp.clon, xd, yd);
2803 
2804  wxRealPoint pos, size;
2805 
2806  pos.x = xd - (vp.pix_width * m_raster_scale_factor / 2);
2807  pos.y = yd - (vp.pix_height * m_raster_scale_factor / 2);
2808 
2809  size.x = vp.pix_width * m_raster_scale_factor;
2810  size.y = vp.pix_height * m_raster_scale_factor;
2811 
2812  *pSourceRect =
2813  wxRect(wxRound(pos.x), wxRound(pos.y), wxRound(size.x), wxRound(size.y));
2814 }
2815 
2816 double ChartBaseBSB::GetRasterScaleFactor(const ViewPort &vp) {
2817  // This funny contortion is necessary to allow scale factors < 1, i.e.
2818  // overzoom
2819  return (wxRound(100000 * GetPPM() / vp.view_scale_ppm)) / 100000.;
2820 }
2821 
2822 void ChartBaseBSB::SetVPRasterParms(const ViewPort &vpt) {
2823  // Calculate the potential datum offset parameters for this viewport, if
2824  // not WGS84
2825 
2826  if (m_datum_index == DATUM_INDEX_WGS84 ||
2827  m_datum_index == DATUM_INDEX_UNKNOWN) {
2828  m_lon_datum_adjust = (-m_dtm_lon) / 3600.;
2829  m_lat_datum_adjust = (-m_dtm_lat) / 3600.;
2830  }
2831 
2832  else {
2833  double to_lat, to_lon;
2834  MolodenskyTransform(vpt.clat, vpt.clon, &to_lat, &to_lon, m_datum_index,
2835  DATUM_INDEX_WGS84);
2836  m_lon_datum_adjust = -(to_lon - vpt.clon);
2837  m_lat_datum_adjust = -(to_lat - vpt.clat);
2838  if (m_b_apply_dtm) {
2839  m_lon_datum_adjust -= m_dtm_lon / 3600.;
2840  m_lat_datum_adjust -= m_dtm_lat / 3600.;
2841  }
2842  }
2843 
2844  ComputeSourceRectangle(vpt, &Rsrc);
2845 
2846  if (vpt.IsValid()) m_vp_render_last = vpt;
2847 }
2848 
2849 bool ChartBaseBSB::AdjustVP(ViewPort &vp_last, ViewPort &vp_proposed) {
2850  bool bInside = G_FloatPtInPolygon((MyFlPoint *)GetCOVRTableHead(0),
2851  GetCOVRTablenPoints(0), vp_proposed.clon,
2852  vp_proposed.clat);
2853  if (!bInside) return false;
2854 
2855  int ret_val = 0;
2856  double binary_scale_factor = GetPPM() / vp_proposed.view_scale_ppm;
2857 
2858  if (vp_last.IsValid()) {
2859  // We only need to adjust the VP if the cache is valid and potentially
2860  // usable, i.e. the scale factor is integer... The objective here is to
2861  // ensure that the VP center falls on an exact pixel boundary within the
2862  // cache
2863 
2864  if (cached_image_ok && (binary_scale_factor > 1.0) &&
2865  (fabs(binary_scale_factor - wxRound(binary_scale_factor)) < 1e-5)) {
2866  if (m_b_cdebug)
2867  printf(" Possible Adjust VP for integer scale: %g\n",
2868  binary_scale_factor);
2869 
2870  wxRect rprop;
2871  ComputeSourceRectangle(vp_proposed, &rprop);
2872 
2873  double pixx, pixy;
2874  double lon_adj, lat_adj;
2875  latlong_to_pix_vp(vp_proposed.clat, vp_proposed.clon, pixx, pixy,
2876  vp_proposed);
2877  vp_pix_to_latlong(vp_proposed, pixx, pixy, &lat_adj, &lon_adj);
2878 
2879  vp_proposed.clat = lat_adj;
2880  vp_proposed.clon = lon_adj;
2881  ret_val = 1;
2882  }
2883  }
2884 
2885  return (ret_val > 0);
2886 }
2887 
2888 bool ChartBaseBSB::IsRenderCacheable(wxRect &source, wxRect &dest) {
2889  double scale_x = (double)source.width / (double)dest.width;
2890 
2891  if (scale_x <= 1.0) // overzoom
2892  {
2893  // if(m_b_cdebug)printf(" MISS<<<>>>GVUC: Overzoom\n");
2894  return false;
2895  }
2896 
2897  // Using the cache only works for pure binary scale factors......
2898  if ((fabs(scale_x - wxRound(scale_x))) > .0001) {
2899  // if(m_b_cdebug)printf(" MISS<<<>>>GVUC: Not digital scale
2900  // test 1\n");
2901  return false;
2902  }
2903 
2904  // Scale must be exactly digital...
2905  if ((int)(source.width / dest.width) != (int)wxRound(scale_x)) {
2906  // if(m_b_cdebug)printf(" MISS<<<>>>GVUC: Not digital scale
2907  // test 2\n");
2908  return false;
2909  }
2910 
2911  return true;
2912 }
2913 
2914 void ChartBaseBSB::GetValidCanvasRegion(const ViewPort &VPoint,
2915  OCPNRegion *pValidRegion) {
2916  SetVPRasterParms(VPoint);
2917 
2918  double raster_scale = VPoint.view_scale_ppm / GetPPM();
2919 
2920  int rxl, rxr;
2921  int ryb, ryt;
2922 
2923  rxl = wxMax(-Rsrc.x * raster_scale, VPoint.rv_rect.x);
2924  rxr = wxMin((Size_X - Rsrc.x) * raster_scale,
2925  VPoint.rv_rect.width + VPoint.rv_rect.x);
2926 
2927  ryt = wxMax(-Rsrc.y * raster_scale, VPoint.rv_rect.y);
2928  ryb = wxMin((Size_Y - Rsrc.y) * raster_scale,
2929  VPoint.rv_rect.height + VPoint.rv_rect.y);
2930 
2931  pValidRegion->Clear();
2932  pValidRegion->Union(rxl, ryt, rxr - rxl, ryb - ryt);
2933 }
2934 
2935 LLRegion ChartBaseBSB::GetValidRegion() {
2936  // should we cache this?
2937  double ll[8];
2938  chartpix_to_latlong(0, 0, ll + 0, ll + 1);
2939  chartpix_to_latlong(0, Size_Y, ll + 2, ll + 3);
2940  chartpix_to_latlong(Size_X, Size_Y, ll + 4, ll + 5);
2941  chartpix_to_latlong(Size_X, 0, ll + 6, ll + 7);
2942 
2943  // for now don't allow raster charts to cross both 0 meridian and IDL
2944  // (complicated to deal with)
2945  for (int i = 1; i < 6; i += 2)
2946  if (fabs(ll[i] - ll[i + 2]) > 180) {
2947  // we detect crossing idl here, make all longitudes positive
2948  for (int i = 1; i < 8; i += 2)
2949  if (ll[i] < 0) ll[i] += 360;
2950  break;
2951  }
2952 
2953  return LLRegion(4, ll);
2954 }
2955 
2956 bool ChartBaseBSB::GetViewUsingCache(wxRect &source, wxRect &dest,
2957  const OCPNRegion &Region,
2958  ScaleTypeEnum scale_type) {
2959  wxRect s1;
2960  ScaleTypeEnum scale_type_corrected;
2961 
2962  if (m_b_cdebug) printf(" source: %d %d\n", source.x, source.y);
2963  if (m_b_cdebug) printf(" cache: %d %d\n", cache_rect.x, cache_rect.y);
2964 
2965  // Anything to do?
2966  if ((source == cache_rect) /*&& (cache_scale_method == scale_type)*/ &&
2967  (cached_image_ok)) {
2968  if (m_b_cdebug) printf(" GVUC: Cache is good, nothing to do\n");
2969  return false;
2970  }
2971 
2972  double scale_x = (double)source.width / (double)dest.width;
2973 
2974  if (m_b_cdebug) printf("GVUC: scale_x: %g\n", scale_x);
2975 
2976  // Enforce a limit on bilinear scaling, for performance reasons
2977  scale_type_corrected = scale_type; // RENDER_LODEF; //scale_type;
2978  if (scale_x > m_bilinear_limit) scale_type_corrected = RENDER_LODEF;
2979 
2980  {
2981  // if(b_cdebug)printf(" MISS<<<>>>GVUC: Intentional out\n");
2982  // return GetView( source, dest, scale_type_corrected );
2983  }
2984 
2985  // Using the cache only works for pure binary scale factors......
2986  if ((fabs(scale_x - wxRound(scale_x))) > .0001) {
2987  if (m_b_cdebug) printf(" MISS<<<>>>GVUC: Not digital scale test 1\n");
2988  return GetView(source, dest, scale_type_corrected);
2989  }
2990 
2991  // scale_type_corrected = RENDER_LODEF;
2992 
2993  if (!cached_image_ok) {
2994  if (m_b_cdebug) printf(" MISS<<<>>>GVUC: Cache NOk\n");
2995  return GetView(source, dest, scale_type_corrected);
2996  }
2997 
2998  if (scale_x < 1.0) // overzoom
2999  {
3000  if (m_b_cdebug) printf(" MISS<<<>>>GVUC: Overzoom\n");
3001  return GetView(source, dest, scale_type_corrected);
3002  }
3003 
3004  // Scale must be exactly digital...
3005  if ((int)(source.width / dest.width) != (int)wxRound(scale_x)) {
3006  if (m_b_cdebug) printf(" MISS<<<>>>GVUC: Not digital scale test 2\n");
3007  return GetView(source, dest, scale_type_corrected);
3008  }
3009 
3010  // Calculate the digital scale, e.g. 1,2,4,8,,,
3011  int cs1d = source.width / dest.width;
3012  if (abs(source.x - cache_rect.x) % cs1d) {
3013  if (m_b_cdebug)
3014  printf(" source.x: %d cache_rect.x: %d cs1d: %d\n", source.x,
3015  cache_rect.x, cs1d);
3016  if (m_b_cdebug) printf(" MISS<<<>>>GVUC: x mismatch\n");
3017  return GetView(source, dest, scale_type_corrected);
3018  }
3019  if (abs(source.y - cache_rect.y) % cs1d) {
3020  if (m_b_cdebug) printf(" MISS<<<>>>GVUC: y mismatch\n");
3021  return GetView(source, dest, scale_type_corrected);
3022  }
3023 
3024  if (pPixCache && ((pPixCache->GetWidth() != dest.width) ||
3025  (pPixCache->GetHeight() != dest.height))) {
3026  if (m_b_cdebug) printf(" MISS<<<>>>GVUC: dest size mismatch\n");
3027  return GetView(source, dest, scale_type_corrected);
3028  }
3029 
3030  int stride_rows =
3031  (source.y + source.height) - (cache_rect.y + cache_rect.height);
3032  int scaled_stride_rows = (int)(stride_rows / scale_x);
3033 
3034  if (abs(stride_rows) >= source.height) // Pan more than one screen
3035  return GetView(source, dest, scale_type_corrected);
3036 
3037  int stride_pixels =
3038  (source.x + source.width) - (cache_rect.x + cache_rect.width);
3039  int scaled_stride_pixels = (int)(stride_pixels / scale_x);
3040 
3041  if (abs(stride_pixels) >= source.width) // Pan more than one screen
3042  return GetView(source, dest, scale_type_corrected);
3043 
3044  if (m_b_cdebug) printf(" GVUC Using raster data cache\n");
3045 
3046  ScaleTypeEnum pan_scale_type_x = scale_type_corrected;
3047  ScaleTypeEnum pan_scale_type_y = scale_type_corrected;
3048 
3049  // "Blit" the valid pixels out of the way
3050  if (pPixCache) {
3051  int height = pPixCache->GetHeight();
3052  int width = pPixCache->GetWidth();
3053  int buffer_stride_bytes = pPixCache->GetLinePitch();
3054 
3055  unsigned char *ps;
3056  unsigned char *pd;
3057 
3058  if (stride_rows > 0) // pan down
3059  {
3060  ps = pPixCache->GetpData() +
3061  (abs(scaled_stride_rows) * buffer_stride_bytes);
3062  if (stride_pixels > 0) ps += scaled_stride_pixels * BPP / 8;
3063 
3064  pd = pPixCache->GetpData();
3065  if (stride_pixels <= 0) pd += abs(scaled_stride_pixels) * BPP / 8;
3066 
3067  for (int iy = 0; iy < (height - abs(scaled_stride_rows)); iy++) {
3068  memmove(pd, ps, (width - abs(scaled_stride_pixels)) * BPP / 8);
3069  ps += buffer_stride_bytes;
3070  pd += buffer_stride_bytes;
3071  }
3072 
3073  } else {
3074  ps = pPixCache->GetpData() +
3075  ((height - abs(scaled_stride_rows) - 1) * buffer_stride_bytes);
3076  if (stride_pixels > 0) // make a hole on right
3077  ps += scaled_stride_pixels * BPP / 8;
3078 
3079  pd = pPixCache->GetpData() + ((height - 1) * buffer_stride_bytes);
3080  if (stride_pixels <= 0) // make a hole on the left
3081  pd += abs(scaled_stride_pixels) * BPP / 8;
3082 
3083  for (int iy = 0; iy < (height - abs(scaled_stride_rows)); iy++) {
3084  memmove(pd, ps, (width - abs(scaled_stride_pixels)) * BPP / 8);
3085  ps -= buffer_stride_bytes;
3086  pd -= buffer_stride_bytes;
3087  }
3088  }
3089 
3090  // Y Pan
3091  if (source.y != cache_rect.y) {
3092  wxRect sub_dest = dest;
3093  sub_dest.height = abs(scaled_stride_rows);
3094 
3095  if (stride_rows > 0) // pan down
3096  {
3097  sub_dest.y = height - scaled_stride_rows;
3098 
3099  } else {
3100  sub_dest.y = 0;
3101  }
3102 
3103  // Get the new bits needed
3104 
3105  // A little optimization...
3106  // No sense in fetching bits that are not part of the ultimate render
3107  // region
3108  wxRegionContain rc = Region.Contains(sub_dest);
3109  if ((wxPartRegion == rc) || (wxInRegion == rc)) {
3110  GetAndScaleData(pPixCache->GetpData(), pPixCache->GetLength(), source,
3111  source.width, sub_dest, width, cs1d, pan_scale_type_y);
3112  }
3113  pPixCache->Update();
3114 
3115  // Update the cached parameters, Y only
3116 
3117  cache_rect.y = source.y;
3118  // cache_rect = source;
3119  cache_rect_scaled = dest;
3120  cached_image_ok = 1;
3121 
3122  } // Y Pan
3123 
3124  // X Pan
3125  if (source.x != cache_rect.x) {
3126  wxRect sub_dest = dest;
3127  sub_dest.width = abs(scaled_stride_pixels);
3128 
3129  if (stride_pixels > 0) // pan right
3130  {
3131  sub_dest.x = width - scaled_stride_pixels;
3132  } else // pan left
3133  {
3134  sub_dest.x = 0;
3135  }
3136 
3137  // Get the new bits needed
3138 
3139  // A little optimization...
3140  // No sense in fetching bits that are not part of the ultimate render
3141  // region
3142  wxRegionContain rc = Region.Contains(sub_dest);
3143  if ((wxPartRegion == rc) || (wxInRegion == rc)) {
3144  GetAndScaleData(pPixCache->GetpData(), pPixCache->GetLength(), source,
3145  source.width, sub_dest, width, cs1d, pan_scale_type_x);
3146  }
3147 
3148  pPixCache->Update();
3149 
3150  // Update the cached parameters
3151  cache_rect = source;
3152  cache_rect_scaled = dest;
3153  cached_image_ok = 1;
3154 
3155  } // X pan
3156 
3157  return true;
3158  }
3159  return false;
3160 }
3161 
3162 int s_dc;
3163 
3164 bool ChartBaseBSB::RenderViewOnDC(wxMemoryDC &dc, const ViewPort &VPoint) {
3165  SetVPRasterParms(VPoint);
3166 
3167  OCPNRegion rgn(0, 0, VPoint.pix_width, VPoint.pix_height);
3168 
3169  bool bsame_region = (rgn == m_last_region); // only want to do this once
3170 
3171  if (!bsame_region) cached_image_ok = false;
3172 
3173  m_last_region = rgn;
3174 
3175  return RenderRegionViewOnDC(dc, VPoint, rgn);
3176 }
3177 
3178 bool ChartBaseBSB::RenderRegionViewOnGL(const wxGLContext &glc,
3179  const ViewPort &VPoint,
3180  const OCPNRegion &RectRegion,
3181  const LLRegion &Region) {
3182  return true;
3183 }
3184 
3185 bool ChartBaseBSB::RenderRegionViewOnDC(wxMemoryDC &dc, const ViewPort &VPoint,
3186  const OCPNRegion &Region) {
3187  SetVPRasterParms(VPoint);
3188 
3189  wxRect dest(0, 0, VPoint.pix_width, VPoint.pix_height);
3190  // double factor = ((double)Rsrc.width)/((double)dest.width);
3191  double factor = GetRasterScaleFactor(VPoint);
3192  if (m_b_cdebug) {
3193  printf("%d RenderRegion ScaleType: %d factor: %g\n", s_dc++,
3194  RENDER_HIDEF, factor);
3195  printf("Rect list:\n");
3196  OCPNRegionIterator upd(Region); // get the requested rect list
3197  while (upd.HaveRects()) {
3198  wxRect rect = upd.GetRect();
3199  printf(" %d %d %d %d\n", rect.x, rect.y, rect.width, rect.height);
3200  upd.NextRect();
3201  }
3202  }
3203 
3204  // Invalidate the cache if the scale has changed or the viewport size has
3205  // changed....
3206  if ((fabs(m_cached_scale_ppm - VPoint.view_scale_ppm) > 1e-9) ||
3207  (m_last_vprect != dest)) {
3208  cached_image_ok = false;
3209  m_vp_render_last.Invalidate();
3210  }
3211  /*
3212  if(pPixCache)
3213  {
3214  if((pPixCache->GetWidth() != dest.width) ||
3215  (pPixCache->GetHeight() != dest.height))
3216  {
3217  delete pPixCache;
3218  pPixCache = new PixelCache(dest.width, dest.height, BPP);
3219  }
3220  }
3221  else
3222  pPixCache = new PixelCache(dest.width, dest.height, BPP);
3223  */
3224 
3225  m_cached_scale_ppm = VPoint.view_scale_ppm;
3226  m_last_vprect = dest;
3227 
3228  if (cached_image_ok) {
3229  // Anything to do?
3230  bool bsame_region = (Region == m_last_region); // only want to do this once
3231 
3232  if ((bsame_region) && (Rsrc == cache_rect)) {
3233  pPixCache->SelectIntoDC(dc);
3234  if (m_b_cdebug) printf(" Using Current PixelCache\n");
3235  return false;
3236  }
3237  }
3238 
3239  m_last_region = Region;
3240 
3241  // Analyze the region requested
3242  // When rendering complex regions, (more than say 4 rectangles)
3243  // .OR. small proportions, then rectangle rendering may be faster
3244  // Also true if the scale is less than near unity, or overzoom.
3245  // This will be the case for backgrounds of the quilt.
3246 
3247  /* Update for Version 2.4.0
3248  This logic seems flawed, at least for quilts which contain charts having
3249  non-rectangular coverage areas. These quilt regions decompose to ...LOTS... of
3250  rectangles, most of which are 1 pixel in height. This is very slow, due to the
3251  overhead of GetAndScaleData(). However, remember that overzoom never uses the
3252  cache, nor does non-binary scale factors.. So, we check to see if this is a
3253  cacheable render, and that the number of rectangles is "reasonable"
3254  */
3255 
3256  // Get the region rectangle count
3257 
3258  int n_rect = 0;
3259  OCPNRegionIterator upd(Region); // get the requested rect list
3260  while (upd.HaveRects()) {
3261  n_rect++;
3262  upd.NextRect();
3263  }
3264 
3265  if ((!IsRenderCacheable(Rsrc, dest) && (n_rect > 4) && (n_rect < 20)) ||
3266  (factor < 1)) {
3267  if (m_b_cdebug)
3268  printf(" RenderRegion by rect iterator n_rect: %d\n", n_rect);
3269 
3270  // Verify that the persistent pixel cache is at least as large as the
3271  // largest rectangle in the region
3272  wxRect dest_check_rect = dest;
3273  OCPNRegionIterator upd_check(Region); // get the requested rect list
3274  while (upd_check.HaveRects()) {
3275  wxRect rect = upd_check.GetRect();
3276  dest_check_rect.Union(rect);
3277  upd_check.NextRect();
3278  }
3279 
3280  if (pPixCache) {
3281  if ((pPixCache->GetWidth() != dest_check_rect.width) ||
3282  (pPixCache->GetHeight() != dest_check_rect.height)) {
3283  delete pPixCache;
3284  pPixCache =
3285  new PixelCache(dest_check_rect.width, dest_check_rect.height, BPP);
3286  }
3287  } else
3288  pPixCache =
3289  new PixelCache(dest_check_rect.width, dest_check_rect.height, BPP);
3290 
3291  ScaleTypeEnum ren_type = RENDER_LODEF;
3292 
3293  // Decompose the region into rectangles, and fetch them into the target
3294  // dc
3295  OCPNRegionIterator upd(Region); // get the requested rect list
3296  int ir = 0;
3297  while (upd.HaveRects()) {
3298  wxRect rect = upd.GetRect();
3299 
3300  // Floating point math can lead to negative rectangle origin.
3301  // If this happens, we arbitrarily shift the rectangle to be positive
3302  // semidefinite. This will cause at most a 1 pixlel error onscreen.
3303  if (rect.y < 0) rect.Offset(0, -rect.y);
3304  if (rect.x < 0) rect.Offset(-rect.x, 0);
3305 
3306  GetAndScaleData(pPixCache->GetpData(), pPixCache->GetLength(), Rsrc,
3307  Rsrc.width, rect, pPixCache->GetWidth(), factor,
3308  ren_type);
3309 
3310  ir++;
3311  upd.NextRect();
3312  ;
3313  }
3314 
3315  pPixCache->Update();
3316 
3317  // Update cache parameters
3318  cache_rect = Rsrc;
3319  cache_scale_method = ren_type;
3320  cached_image_ok = false; // Never cache this type of render
3321 
3322  // Select the data into the dc
3323  pPixCache->SelectIntoDC(dc);
3324 
3325  return true;
3326  }
3327 
3328  // Default is to try using the cache
3329 
3330  if (pPixCache) {
3331  if ((pPixCache->GetWidth() != dest.width) ||
3332  (pPixCache->GetHeight() != dest.height)) {
3333  delete pPixCache;
3334  pPixCache = new PixelCache(dest.width, dest.height, BPP);
3335  }
3336  } else
3337  pPixCache = new PixelCache(dest.width, dest.height, BPP);
3338 
3339  if (m_b_cdebug) printf(" Render Region By GVUC\n");
3340 
3341  // A performance enhancement.....
3342  ScaleTypeEnum scale_type_zoom = RENDER_HIDEF;
3343  double binary_scale_factor = VPoint.view_scale_ppm / GetPPM();
3344  if (binary_scale_factor < .20) scale_type_zoom = RENDER_LODEF;
3345 
3346  bool bnewview = GetViewUsingCache(Rsrc, dest, Region, scale_type_zoom);
3347 
3348  // Select the data into the dc
3349  pPixCache->SelectIntoDC(dc);
3350 
3351  return bnewview;
3352 }
3353 
3354 wxImage *ChartBaseBSB::GetImage() {
3355  int img_size_x = ((Size_X >> 2) * 4) + 4;
3356  wxImage *img = new wxImage(img_size_x, Size_Y, false);
3357 
3358  unsigned char *ppnx = img->GetData();
3359 
3360  for (int i = 0; i < Size_Y; i++) {
3361  wxRect source_rect(0, i, Size_X, 1);
3362  wxRect dest_rect(0, 0, Size_X, 1);
3363 
3364  GetAndScaleData(img->GetData(), img_size_x * Size_Y * 3, source_rect,
3365  Size_X, dest_rect, Size_X, 1.0, RENDER_HIDEF);
3366 
3367  ppnx += img_size_x * 3;
3368  }
3369 
3370  return img;
3371 }
3372 
3373 bool ChartBaseBSB::GetView(wxRect &source, wxRect &dest,
3374  ScaleTypeEnum scale_type) {
3375  assert(pPixCache != 0);
3376  // PixelCache *pPixCacheTemp = new PixelCache(dest.width, dest.height,
3377  // BPP);
3378 
3379  // Get and Rescale the data directly into the temporary PixelCache data
3380  // buffer
3381  double factor = ((double)source.width) / ((double)dest.width);
3382 
3383  /*
3384  if(!GetAndScaleData(&ppnx, source, source.width, dest, dest.width,
3385  factor, scale_type))
3386  {
3387  delete pPixCacheTemp; // Some error, retain
3388  old cache return false;
3389  }
3390  else
3391  {
3392  delete pPixCache; // new cache is OK
3393  pPixCache = pPixCacheTemp;
3394  }
3395  */
3396  GetAndScaleData(pPixCache->GetpData(), pPixCache->GetLength(), source,
3397  source.width, dest, dest.width, factor, scale_type);
3398  pPixCache->Update();
3399 
3400  // Update cache parameters
3401 
3402  cache_rect = source;
3403  cache_rect_scaled = dest;
3404  cache_scale_method = scale_type;
3405 
3406  cached_image_ok = 1;
3407 
3408  return TRUE;
3409 }
3410 
3411 bool ChartBaseBSB::GetAndScaleData(unsigned char *ppn, size_t data_size,
3412  wxRect &source, int source_stride,
3413  wxRect &dest, int dest_stride,
3414  double scale_factor,
3415  ScaleTypeEnum scale_type) {
3416  unsigned char *s_data = NULL;
3417 
3418  double factor = scale_factor;
3419  int Factor = (int)factor;
3420 
3421  int target_width = (int)wxRound((double)source.width / factor);
3422  int target_height = (int)wxRound((double)source.height / factor);
3423 
3424  int dest_line_length = dest_stride * BPP / 8;
3425 
3426  // On MSW, if using DibSections, each scan line starts on a DWORD boundary.
3427  // The DibSection has been allocated to conform with this requirement.
3428 #ifdef __PIX_CACHE_DIBSECTION__
3429  dest_line_length = (((dest_stride * 24) + 31) & ~31) >> 3;
3430 #endif
3431 
3432  if ((target_height == 0) || (target_width == 0)) return false;
3433 
3434  // `volatile` may be needed with regard to `sigsetjmp()` below;
3435  // at least it prevents compiler warning about clobbering the variable.
3436  unsigned char *volatile target_data = ppn;
3437  unsigned char *data = ppn;
3438 
3439  if (factor > 1) // downsampling
3440  {
3441  if (scale_type == RENDER_HIDEF) {
3442  // Allocate a working buffer based on scale factor
3443  int blur_factor = wxMax(2, Factor);
3444  int wb_size = (source.width) * (blur_factor * 2) * BPP / 8;
3445  s_data = (unsigned char *)malloc(wb_size); // work buffer
3446  unsigned char *pixel;
3447  int y_offset;
3448 
3449  for (int y = dest.y; y < (dest.y + dest.height); y++) {
3450  // Read "blur_factor" lines
3451 
3452  wxRect s1;
3453  s1.x = source.x;
3454  s1.y = source.y + (int)(y * factor);
3455  s1.width = source.width;
3456  s1.height = blur_factor;
3457  GetChartBits(s1, s_data, 1);
3458 
3459  target_data = data + (y * dest_line_length /*dest_stride * BPP/8*/);
3460 
3461  for (int x = 0; x < target_width; x++) {
3462  unsigned int avgRed = 0;
3463  unsigned int avgGreen = 0;
3464  unsigned int avgBlue = 0;
3465  unsigned int pixel_count = 0;
3466  unsigned char *pix0 = s_data + BPP / 8 * ((int)(x * factor));
3467  y_offset = 0;
3468 
3469  if ((x * Factor) < (Size_X - source.x)) {
3470  // determine average
3471  for (int y1 = 0; y1 < blur_factor; ++y1) {
3472  pixel = pix0 + (BPP / 8 * y_offset);
3473  for (int x1 = 0; x1 < blur_factor; ++x1) {
3474  avgRed += pixel[0];
3475  avgGreen += pixel[1];
3476  avgBlue += pixel[2];
3477 
3478  pixel += BPP / 8;
3479 
3480  pixel_count++;
3481  }
3482  y_offset += source.width;
3483  }
3484 
3485  if (0 == pixel_count) // Protect
3486  pixel_count = 1;
3487 
3488  target_data[0] = avgRed / pixel_count; // >> scounter;
3489  target_data[1] = avgGreen / pixel_count; // >> scounter;
3490  target_data[2] = avgBlue / pixel_count; // >> scounter;
3491  target_data += BPP / 8;
3492  } else {
3493  target_data[0] = 0;
3494  target_data[1] = 0;
3495  target_data[2] = 0;
3496  target_data += BPP / 8;
3497  }
3498 
3499  } // for x
3500 
3501  } // for y
3502 
3503  } // SCALE_BILINEAR
3504 
3505  else if (scale_type == RENDER_LODEF) {
3506  int get_bits_submap = 1;
3507 
3508  int scaler = 16;
3509 
3510  if (source.width > 32767) // High underscale can exceed signed math bits
3511  scaler = 8;
3512 
3513  int wb_size = (Size_X) * ((/*Factor +*/ 1) * 2) * BPP / 8;
3514  s_data = (unsigned char *)malloc(wb_size); // work buffer
3515 
3516  long x_delta = (source.width << scaler) / target_width;
3517  long y_delta = (source.height << scaler) / target_height;
3518 
3519  int y = dest.y; // starting here
3520  long ys = dest.y * y_delta;
3521 
3522  while (y < dest.y + dest.height) {
3523  // Read 1 line at the right place from the source
3524 
3525  wxRect s1;
3526  s1.x = 0;
3527  s1.y = source.y + (ys >> scaler);
3528  s1.width = Size_X;
3529  s1.height = 1;
3530  GetChartBits(s1, s_data, get_bits_submap);
3531 
3532  target_data = data + (y * dest_line_length /*dest_stride * BPP/8*/) +
3533  (dest.x * BPP / 8);
3534 
3535  long x = (source.x << scaler) + (dest.x * x_delta);
3536  long sizex16 = Size_X << scaler;
3537  int xt = dest.x;
3538 
3539  while ((xt < dest.x + dest.width) && (x < 0)) {
3540  target_data[0] = 0;
3541  target_data[1] = 0;
3542  target_data[2] = 0;
3543 
3544  target_data += BPP / 8;
3545  x += x_delta;
3546  xt++;
3547  }
3548 
3549  while ((xt < dest.x + dest.width) && (x < sizex16)) {
3550  unsigned char *src_pixel = &s_data[(x >> scaler) * BPP / 8];
3551 
3552  target_data[0] = src_pixel[0];
3553  target_data[1] = src_pixel[1];
3554  target_data[2] = src_pixel[2];
3555 
3556  target_data += BPP / 8;
3557  x += x_delta;
3558  xt++;
3559  }
3560 
3561  while (xt < dest.x + dest.width) {
3562  target_data[0] = 0;
3563  target_data[1] = 0;
3564  target_data[2] = 0;
3565 
3566  target_data += BPP / 8;
3567  xt++;
3568  }
3569 
3570  y++;
3571  ys += y_delta;
3572  }
3573 
3574  } // SCALE_SUBSAMP
3575 
3576  } else // factor < 1, overzoom
3577  {
3578  int i = 0;
3579  int j = 0;
3580  unsigned char *target_line_start = NULL;
3581  unsigned char *target_data_x = NULL;
3582  int y_offset = 0;
3583 
3584 #ifdef __WXGTK__
3585  sigaction(SIGSEGV, NULL,
3586  &sa_all_previous); // save existing action for this signal
3587 
3588  struct sigaction temp;
3589  sigaction(SIGSEGV, NULL, &temp); // inspect existing action for this signal
3590 
3591  temp.sa_handler = catch_signals_chart; // point to my handler
3592  sigemptyset(&temp.sa_mask); // make the blocking set
3593  // empty, so that all
3594  // other signals will be
3595  // unblocked during my handler
3596  temp.sa_flags = 0;
3597  sigaction(SIGSEGV, &temp, NULL);
3598 
3599  if (sigsetjmp(env_chart,
3600  1)) // Something in the below code block faulted....
3601  {
3602  sigaction(SIGSEGV, &sa_all_previous, NULL); // reset signal handler
3603 
3604  wxString msg;
3605  msg.Printf(_T(" Caught SIGSEGV on GetandScaleData, Factor < 1"));
3606  wxLogMessage(msg);
3607 
3608  msg.Printf(
3609  _T(" m_raster_scale_factor: %g source.width: %d dest.y: %d ")
3610  _T("dest.x: %d dest.width: %d dest.height: %d "),
3611  m_raster_scale_factor, source.width, dest.y, dest.x, dest.width,
3612  dest.height);
3613  wxLogMessage(msg);
3614 
3615  msg.Printf(
3616  _T(" i: %d j: %d dest_stride: %d target_line_start: %p ")
3617  _T("target_data_x: %p y_offset: %d"),
3618  i, j, dest_stride, target_line_start, target_data_x, y_offset);
3619  wxLogMessage(msg);
3620 
3621  free(s_data);
3622  return true;
3623 
3624  }
3625 
3626  else
3627 #endif
3628  {
3629 
3630  double xd, yd;
3631  latlong_to_chartpix(m_vp_render_last.clat, m_vp_render_last.clon, xd, yd);
3632  double xrd =
3633  xd - (m_vp_render_last.pix_width * m_raster_scale_factor / 2);
3634  double yrd =
3635  yd - (m_vp_render_last.pix_height * m_raster_scale_factor / 2);
3636  double x_vernier = (xrd - wxRound(xrd));
3637  double y_vernier = (yrd - wxRound(yrd));
3638  int x_vernier_i = (int)wxRound(x_vernier / m_raster_scale_factor);
3639  int y_vernier_i = (int)wxRound(y_vernier / m_raster_scale_factor);
3640 
3641  // Seems safe enough to read all the required data
3642  // Although we must adjust (increase) temporary allocation for negative
3643  // source.x and for vernier
3644  int sx = wxMax(source.x, 0);
3645  s_data = (unsigned char *)malloc((sx + source.width + 2) *
3646  (source.height + 2) * BPP / 8);
3647 
3648  wxRect vsource = source;
3649  vsource.height += 2; // get more bits to allow for vernier
3650  vsource.width += 2;
3651  vsource.x -= 1;
3652  vsource.y -= 1;
3653 
3654  GetChartBits(vsource, s_data, 1);
3655  unsigned char *source_data = s_data;
3656 
3657  j = dest.y;
3658 
3659  while (j < dest.y + dest.height) {
3660  y_offset = (int)((j - y_vernier_i) * m_raster_scale_factor) *
3661  vsource.width; // into the source data
3662 
3663  target_line_start =
3664  target_data + (j * dest_line_length /*dest_stride * BPP / 8*/);
3665  target_data_x = target_line_start + ((dest.x) * BPP / 8);
3666 
3667  i = dest.x;
3668 
3669  // Check data bounds to be sure of not overrunning the upstream buffer
3670  if ((target_data_x + (dest.width * BPP / 8)) >
3671  (target_data + data_size)) {
3672  j = dest.y + dest.height;
3673  } else {
3674  while (i < dest.x + dest.width) {
3675  memcpy(target_data_x,
3676  source_data + BPP / 8 *
3677  (y_offset + (int)((i + x_vernier_i) *
3678  m_raster_scale_factor)),
3679  BPP / 8);
3680 
3681  target_data_x += BPP / 8;
3682 
3683  i++;
3684  }
3685  }
3686 
3687  j++;
3688  }
3689  }
3690 #ifdef __WXGTK__
3691  sigaction(SIGSEGV, &sa_all_previous, NULL); // reset signal handler
3692 #endif
3693  }
3694 
3695  free(s_data);
3696 
3697  return true;
3698 }
3699 
3700 bool ChartBaseBSB::GetChartBits(wxRect &source, unsigned char *pPix,
3701  int sub_samp) {
3702  wxCriticalSectionLocker locker(m_critSect);
3703 
3704  int iy;
3705 #define FILL_BYTE 0
3706 
3707  // Decode the KAP file RLL stream into image pPix
3708 
3709  unsigned char *pCP;
3710  pCP = pPix;
3711 
3712  iy = source.y;
3713 
3714  while (iy < source.y + source.height) {
3715  if ((iy >= 0) && (iy < Size_Y)) {
3716  if (source.x >= 0) {
3717  if ((source.x + source.width) > Size_X) {
3718  if ((Size_X - source.x) < 0)
3719  memset(pCP, FILL_BYTE, source.width * BPP / 8);
3720  else {
3721  BSBGetScanline(pCP, iy, source.x, Size_X, sub_samp);
3722  memset(pCP + (Size_X - source.x) * BPP / 8, FILL_BYTE,
3723  (source.x + source.width - Size_X) * BPP / 8);
3724  }
3725  } else
3726  BSBGetScanline(pCP, iy, source.x, source.x + source.width, sub_samp);
3727  } else {
3728  if ((source.width + source.x) >= 0) {
3729  // Special case, black on left side
3730  // must ensure that (black fill length % sub_samp) == 0
3731 
3732  int xfill_corrected = -source.x + (source.x % sub_samp); //+ve
3733  memset(pCP, FILL_BYTE, (xfill_corrected * BPP / 8));
3734  BSBGetScanline(pCP + (xfill_corrected * BPP / 8), iy, 0,
3735  source.width + source.x, sub_samp);
3736 
3737  } else {
3738  memset(pCP, FILL_BYTE, source.width * BPP / 8);
3739  }
3740  }
3741  }
3742 
3743  else // requested y is off chart
3744  {
3745  memset(pCP, FILL_BYTE, source.width * BPP / 8);
3746  }
3747 
3748  pCP += source.width * BPP / 8 * sub_samp;
3749 
3750  iy += sub_samp;
3751  } // while iy
3752 
3753  return true;
3754 }
3755 
3756 //-----------------------------------------------------------------------------------------------
3757 // BSB File Read Support
3758 //-----------------------------------------------------------------------------------------------
3759 
3760 //-----------------------------------------------------------------------------------------------
3761 // ReadBSBHdrLine
3762 //
3763 // Read and return count of a line of BSB header file
3764 //-----------------------------------------------------------------------------------------------
3765 
3766 int ChartBaseBSB::ReadBSBHdrLine(wxInputStream *ifs, char *buf, int buf_len_max)
3767 
3768 {
3769  char read_char;
3770  char cr_test;
3771  int line_length = 0;
3772  char *lbuf;
3773 
3774  lbuf = buf;
3775 
3776  while (!ifs->Eof() && line_length < buf_len_max) {
3777  int c = ifs->GetC();
3778  if (c < 0) break;
3779  read_char = c;
3780  if (0x1A == read_char) {
3781  ifs->Ungetch(read_char);
3782  return (0);
3783  }
3784 
3785  if (0 == read_char) // embedded erroneous unicode character?
3786  read_char = 0x20;
3787 
3788  // Manage continued lines
3789  if (read_char == 10 || read_char == 13) {
3790  // Check to see if there is an extra CR
3791  cr_test = ifs->GetC();
3792  if (cr_test == 13) cr_test = ifs->GetC(); // skip any extra CR
3793 
3794  if (cr_test != 10 && cr_test != 13) ifs->Ungetch(cr_test);
3795  read_char = '\n';
3796  }
3797 
3798  // Look for continued lines, indicated by ' ' in first position
3799  if (read_char == '\n') {
3800  cr_test = 0;
3801  cr_test = ifs->GetC();
3802 
3803  if (cr_test != ' ') {
3804  ifs->Ungetch(cr_test);
3805  *lbuf = '\0';
3806  return line_length;
3807  }
3808 
3809  // Merge out leading spaces
3810  while (cr_test == ' ') cr_test = ifs->GetC();
3811  ifs->Ungetch(cr_test);
3812 
3813  // Add a comma
3814  *lbuf = ',';
3815  lbuf++;
3816  }
3817 
3818  else {
3819  *lbuf = read_char;
3820  lbuf++;
3821  line_length++;
3822  }
3823 
3824  } // while
3825 
3826  // Terminate line
3827  if (line_length) *(lbuf - 1) = '\0';
3828 
3829  return line_length;
3830 }
3831 
3832 //-----------------------------------------------------------------------
3833 // Scan a BSB Scan Line from raw data
3834 // Leaving stream pointer at start of next line
3835 //-----------------------------------------------------------------------
3836 int ChartBaseBSB::BSBScanScanline(wxInputStream *pinStream) {
3837  int nLineMarker, nValueShift, iPixel = 0;
3838  unsigned char byValueMask, byCountMask;
3839  unsigned char byNext;
3840  int coffset;
3841 
3842  // if(1)
3843  {
3844  // Read the line number.
3845  nLineMarker = 0;
3846  do {
3847  byNext = pinStream->GetC();
3848  nLineMarker = nLineMarker * 128 + (byNext & 0x7f);
3849  } while ((byNext & 0x80) != 0);
3850 
3851  // Setup masking values.
3852  nValueShift = 7 - nColorSize;
3853  byValueMask = (((1 << nColorSize)) - 1) << nValueShift;
3854  byCountMask = (1 << (7 - nColorSize)) - 1;
3855 
3856  // Read and simulate expansion of runs.
3857 
3858  while (((byNext = pinStream->GetC()) != 0) && (iPixel < Size_X)) {
3859  // int nPixValue;
3860  int nRunCount;
3861  // nPixValue = (byNext & byValueMask) >> nValueShift;
3862 
3863  nRunCount = byNext & byCountMask;
3864 
3865  while ((byNext & 0x80) != 0) {
3866  byNext = pinStream->GetC();
3867  nRunCount = nRunCount * 128 + (byNext & 0x7f);
3868  }
3869 
3870  if (iPixel + nRunCount + 1 > Size_X) nRunCount = Size_X - iPixel - 1;
3871 
3872  // Store nPixValue in the destination
3873  // memset(pCL, nPixValue, nRunCount+1);
3874  // pCL += nRunCount+1;
3875  iPixel += nRunCount + 1;
3876  }
3877  coffset = pinStream->TellI();
3878  }
3879 
3880  return nLineMarker;
3881 }
3882 // MSVC compiler makes a bad decision about when to inline (or not) some
3883 // intrinsics, like memset(). So,... Here is a little hand-crafted memset()
3884 // substitue for known short strings. It will be inlined by MSVC compiler
3885 // using /02 settings
3886 
3887 inline void memset_short(unsigned char *dst, unsigned char cbyte, int count) {
3888 #if 0 //def __MSVC__
3889  __asm {
3890  pushf // save Direction flag
3891  cld // set direction "up"
3892  mov edi, dst
3893  mov ecx, count
3894  mov al, cbyte
3895  rep stosb
3896  popf
3897  }
3898 #else
3899  memset(dst, cbyte, count);
3900 #endif
3901 }
3902 // could use a larger value for slightly less ram but slower random access,
3903 // this is chosen as it is also the opengl tile size so should work well
3904 #define TILE_SIZE 512
3905 
3906 //#define USE_OLD_CACHE // removed this (and simplify code below) once the new
3907 //method is verified #define PRINT_TIMINGS // enable for profiling
3908 
3909 #ifdef PRINT_TIMINGS
3910 class OCPNStopWatch {
3911 public:
3912  OCPNStopWatch() { Reset(); }
3913  void Reset() { clock_gettime(CLOCK_REALTIME, &tp); }
3914 
3915  double Time() {
3916  timespec tp_end;
3917  clock_gettime(CLOCK_REALTIME, &tp_end);
3918  return (tp_end.tv_sec - tp.tv_sec) * 1.e3 +
3919  (tp_end.tv_nsec - tp.tv_nsec) / 1.e6;
3920  }
3921 
3922 private:
3923  timespec tp;
3924 };
3925 #endif
3926 
3927 #define FAIL \
3928  do { \
3929  free(pt->pTileOffset); \
3930  pt->pTileOffset = NULL; \
3931  free(pt->pPix); \
3932  pt->pPix = NULL; \
3933  pt->bValid = false; \
3934  return 0; \
3935  } while (0)
3936 
3937 //-----------------------------------------------------------------------
3938 // Get a BSB Scan Line Using Cache and scan line index if available
3939 //-----------------------------------------------------------------------
3940 int ChartBaseBSB::BSBGetScanline(unsigned char *pLineBuf, int y, int xs, int xl,
3941  int sub_samp)
3942 
3943 {
3944  unsigned char *prgb = pLineBuf;
3945  int nValueShift;
3946  unsigned char byValueMask, byCountMask;
3947  unsigned char byNext;
3948  CachedLine *pt = NULL, cached_line;
3949  unsigned char *pCL;
3950  int rgbval;
3951  unsigned char *lp;
3952  int ix = xs;
3953  int pos = 0;
3954 
3955  if (bUseLineCache && pLineCache) {
3956  // Is the requested line in the cache, and valid?
3957  pt = &pLineCache[y];
3958  } else {
3959  pt = &cached_line;
3960  pt->bValid = false;
3961  }
3962 
3963 #ifdef PRINT_TIMINGS
3964  OCPNStopWatch sw;
3965  static double ttime;
3966  static int cnt;
3967  cnt++;
3968 #endif
3969 
3970  if (!pt->bValid) // not valid, allocate
3971  {
3972  pt->size = pline_table[y + 1] - pline_table[y];
3973 
3974 #ifdef USE_OLD_CACHE
3975  pt->pPix = (unsigned char *)malloc(Size_X);
3976 #else
3977  pt->pTileOffset = (TileOffsetCache *)calloc(
3978  sizeof(TileOffsetCache) * (Size_X / TILE_SIZE + 1), 1);
3979  pt->pPix = (unsigned char *)malloc(pt->size );
3980 #endif
3981  if (pline_table[y] == 0 || pline_table[y + 1] == 0) FAIL;
3982 
3983  // as of 2015, in wxWidgets buffered streams don't test for a zero seek
3984  // so we check here to possibly avoid this seek with a measured performance
3985  // gain
3986  if (ifs_bitmap->TellI() != pline_table[y] &&
3987  wxInvalidOffset == ifs_bitmap->SeekI(pline_table[y], wxFromStart))
3988  FAIL;
3989 
3990 #ifdef USE_OLD_CACHE
3991  if (pt->size > ifs_bufsize) {
3992  unsigned char *tmp = ifs_buf;
3993  if (!(ifs_buf = (unsigned char *)realloc(ifs_buf, pt->size ))) {
3994  free(tmp);
3995  FAIL;
3996  }
3997  ifs_bufsize = pt->size ;
3998  }
3999 
4000  lp = ifs_buf;
4001 #else
4002  lp = pt->pPix;
4003 #endif
4004  ifs_bitmap->Read(lp, pt->size );
4005 
4006 #ifdef USE_OLD_CACHE
4007  pCL = pt->pPix;
4008 #else
4009  if (!bUseLineCache) {
4010  ix = 0;
4011  // skip the line number.
4012  do {
4013  if (pos < pt->size) {
4014  byNext = *lp++;
4015  pos++;
4016  } else {
4017  byNext = 0;
4018  }
4019  }
4020  while ((byNext & 0x80) != 0);
4021  goto nocachestart;
4022  }
4023  pCL = ifs_buf;
4024 
4025  if (Size_X > ifs_bufsize) {
4026  unsigned char *tmp = ifs_buf;
4027  if (!(ifs_buf = (unsigned char *)realloc(ifs_buf, Size_X))) {
4028  free(tmp);
4029  FAIL;
4030  }
4031  ifs_bufsize = Size_X;
4032  }
4033 #endif
4034  // At this point, the unexpanded, raw line is at *lp, and the expansion
4035  // destination is pCL
4036 
4037  // skip the line number.
4038  do {
4039  if (pos < pt->size) {
4040  byNext = *lp++;
4041  pos++;
4042  } else {
4043  byNext = 0;
4044  }
4045  }
4046  while ((byNext & 0x80) != 0);
4047 
4048  // Setup masking values.
4049  nValueShift = 7 - nColorSize;
4050  byValueMask = (((1 << nColorSize)) - 1) << nValueShift;
4051  byCountMask = (1 << (7 - nColorSize)) - 1;
4052 
4053  // Read and expand runs.
4054  unsigned int iPixel = 0;
4055 
4056 #ifndef USE_OLD_CACHE
4057  pt->pTileOffset[0].offset = lp - pt->pPix;
4058  pt->pTileOffset[0].pixel = 0;
4059  unsigned int tileindex = 1, nextTile = TILE_SIZE;
4060 #endif
4061  unsigned int nRunCount;
4062  unsigned char *end = pt->pPix + pt->size;
4063  while (iPixel < (unsigned int)Size_X)
4064 #ifdef USE_OLD_CACHE
4065  {
4066  nPixValue = (byNext & byValueMask) >> nValueShift;
4067 
4068  nRunCount = byNext & byCountMask;
4069 
4070  while ((byNext & 0x80) != 0) {
4071  byNext = *lp++;
4072  nRunCount = nRunCount * 128 + (byNext & 0x7f);
4073  }
4074 
4075  nRunCount++;
4076 
4077  if (iPixel + nRunCount >
4078  (unsigned int)Size_X) // protection against corrupt data
4079  nRunCount = nRunCount - iPixel;
4080 
4081  // Store nPixValue in the destination
4082  memset_short(pCL + iPixel, nPixValue, nRunCount);
4083  iPixel += nRunCount;
4084  }
4085 #else
4086  // build tile offset table for faster random access
4087  {
4088  if (pos < pt->size) {
4089  byNext = *lp++;
4090  pos++;
4091  } else {
4092  byNext = 0;
4093  }
4094  unsigned char *offset = lp - 1;
4095  if (byNext == 0 || lp == end) {
4096  // finished early...corrupt?
4097  while (tileindex < (unsigned int)Size_X / TILE_SIZE + 1) {
4098  pt->pTileOffset[tileindex].offset = pt->pTileOffset[0].offset;
4099  pt->pTileOffset[tileindex].pixel = 0;
4100  tileindex++;
4101  }
4102  break;
4103  }
4104 
4105  nRunCount = byNext & byCountMask;
4106 
4107  while ((byNext & 0x80) != 0) {
4108  if (pos < pt->size) {
4109  byNext = *lp++;
4110  pos++;
4111  } else {
4112  byNext = 0;
4113  }
4114  nRunCount = nRunCount * 128 + (byNext & 0x7f);
4115  }
4116 
4117  nRunCount++;
4118 
4119  if (iPixel + nRunCount >
4120  (unsigned int)Size_X) // protection against corrupt data
4121  nRunCount = Size_X - iPixel;
4122 
4123  while (iPixel + nRunCount > nextTile) {
4124  pt->pTileOffset[tileindex].offset = offset - pt->pPix;
4125  pt->pTileOffset[tileindex].pixel = iPixel;
4126  tileindex++;
4127  nextTile += TILE_SIZE;
4128  }
4129  iPixel += nRunCount;
4130  }
4131 #endif
4132 
4133  pt->bValid = true;
4134  }
4135 
4136  // Line is valid, de-reference thru proper pallete directly to target
4137 
4138  if (xl > Size_X) xl = Size_X;
4139 
4140 #ifdef USE_OLD_CACHE
4141  pCL = pt->pPix + xs;
4142 
4143  // Optimization for most usual case
4144  if ((BPP == 24) && (1 == sub_samp)) {
4145  ix = xs;
4146  while (ix < xl - 1) {
4147  unsigned char cur_by = *pCL;
4148  rgbval = (int)(pPalette[cur_by]);
4149  while ((ix < xl - 1)) {
4150  if (cur_by != *pCL) break;
4151  *((int *)prgb) = rgbval;
4152  prgb += 3;
4153  pCL++;
4154  ix++;
4155  }
4156  }
4157  } else {
4158  int dest_inc_val_bytes = (BPP / 8) * sub_samp;
4159  ix = xs;
4160  while (ix < xl - 1) {
4161  unsigned char cur_by = *pCL;
4162  rgbval = (int)(pPalette[cur_by]);
4163  while ((ix < xl - 1)) {
4164  if (cur_by != *pCL) break;
4165  *((int *)prgb) = rgbval;
4166  prgb += dest_inc_val_bytes;
4167  pCL += sub_samp;
4168  ix += sub_samp;
4169  }
4170  }
4171  }
4172 
4173  // Get the last pixel explicitely
4174  // irrespective of the sub_sampling factor
4175 
4176  if (xs < xl - 1) {
4177  unsigned char *pCLast = pt->pPix + (xl - 1);
4178  unsigned char *prgb_last = pLineBuf + ((xl - 1) - xs) * BPP / 8;
4179 
4180  rgbval = (int)(pPalette[*pCLast]); // last pixel
4181  unsigned char a = rgbval & 0xff;
4182  *prgb_last++ = a;
4183  a = (rgbval >> 8) & 0xff;
4184  *prgb_last++ = a;
4185  a = (rgbval >> 16) & 0xff;
4186  *prgb_last = a;
4187  }
4188 #else
4189  {
4190  int tileindex = xs / TILE_SIZE;
4191  int tileoffset = pt->pTileOffset[tileindex].offset;
4192 
4193  lp = pt->pPix + tileoffset;
4194  pos = tileoffset;
4195  ix = pt->pTileOffset[tileindex].pixel;
4196  }
4197 
4198 nocachestart:
4199  nValueShift = 7 - nColorSize;
4200  byValueMask = (((1 << nColorSize)) - 1) << nValueShift;
4201  byCountMask = (1 << (7 - nColorSize)) - 1;
4202  int nPixValue = 0; // satisfy stupid compiler warning
4203  bool bLastPixValueValid = false;
4204  while (ix < xl - 1) {
4205  if (pos < pt->size) {
4206  byNext = *lp++;
4207  pos++;
4208  } else {
4209  break;
4210  }
4211 
4212  nPixValue = (byNext & byValueMask) >> nValueShift;
4213  unsigned int nRunCount;
4214 
4215  if (byNext == 0)
4216  nRunCount = xl - ix; // corrupted chart, just run to the end
4217  else {
4218  nRunCount = byNext & byCountMask;
4219  while ((byNext & 0x80) != 0) {
4220  if (pos < pt->size) {
4221  byNext = *lp++;
4222  pos++;
4223  } else {
4224  nRunCount = xl - ix; // corrupted chart, just run to the end
4225  break;
4226  }
4227  nRunCount = nRunCount * 128 + (byNext & 0x7f);
4228  }
4229 
4230  nRunCount++;
4231  }
4232 
4233  if (ix < xs) {
4234  if (ix + nRunCount <= (unsigned int)xs) {
4235  ix += nRunCount;
4236  continue;
4237  }
4238  nRunCount -= xs - ix;
4239  ix = xs;
4240  }
4241 
4242  if (ix + nRunCount >= (unsigned int)xl) {
4243  nRunCount = xl - 1 - ix;
4244  bLastPixValueValid = true;
4245  }
4246 
4247  rgbval = (int)(pPalette[nPixValue]);
4248 
4249  // Optimization for most usual case
4250  // currently this is the only case possible...
4251  // if((BPP == 24) && (1 == sub_samp))
4252  {
4253  int count = nRunCount;
4254  if (count < 16) {
4255  // for short runs, use simple loop
4256  while (count--) {
4257  *(uint32_t *)prgb = rgbval;
4258  prgb += 3;
4259  }
4260  } else if (rgbval == 0 || rgbval == 0xffffff) {
4261  // optimization for black or white (could work for any gray too)
4262  memset(prgb, rgbval, nRunCount * 3);
4263  prgb += nRunCount * 3;
4264  } else {
4265  // note: this may not be optimal for all processors and compilers
4266  // I optimized for x86_64 using gcc with -O3
4267  // it is probably possible to gain even faster performance by ensuring
4268  // alignment to 16 or 32byte boundary (depending on processor) then
4269  // using inline assembly
4270 
4271 #ifdef __ARM_ARCH
4272  // ARM needs 8 byte alignment for *(uint64_T *x) = *(uint64_T *y)
4273  // because the compiler will (probably) use the ldrd/strd instuction
4274  // pair. So, advance the prgb pointer until it is 8-byte aligned, and
4275  // then carry on if enough bytes are left to process as 64 bit elements
4276 
4277  if ((long)prgb & 7) {
4278  while (count--) {
4279  *(uint32_t *)prgb = rgbval;
4280  prgb += 3;
4281  if (!((long)prgb & 7)) {
4282  if (count >= 8) break;
4283  }
4284  }
4285  }
4286 #endif
4287 
4288  // fill first 24 bytes
4289  uint64_t *b = (uint64_t *)prgb;
4290  for (int i = 0; i < 8; i++) {
4291  *(uint32_t *)prgb = rgbval;
4292  prgb += 3;
4293  }
4294  count -= 8;
4295 
4296  // fill in blocks of 24 bytes
4297  uint64_t *y = (uint64_t *)prgb;
4298  int count_d8 = count >> 3;
4299  prgb += 24 * count_d8;
4300  while (count_d8--) {
4301  *y++ = b[0];
4302  *y++ = b[1];
4303  *y++ = b[2];
4304  }
4305 
4306  // fill remaining bytes
4307  int rcount = count & 0x7;
4308  while (rcount--) {
4309  *(uint32_t *)prgb = rgbval;
4310  prgb += 3;
4311  }
4312  }
4313  }
4314 
4315  ix += nRunCount;
4316  }
4317 
4318  // Get the last pixel explicitely
4319  // irrespective of the sub_sampling factor
4320 
4321  if (ix < xl) {
4322  if (!bLastPixValueValid) {
4323  if (pos < pt->size) {
4324  byNext = *lp++;
4325  pos++;
4326  } else {
4327  byNext = 0;
4328  }
4329  nPixValue = (byNext & byValueMask) >> nValueShift;
4330  }
4331  rgbval = (int)(pPalette[nPixValue]); // last pixel
4332  unsigned char a = rgbval & 0xff;
4333 
4334  *prgb++ = a;
4335  a = (rgbval >> 8) & 0xff;
4336  *prgb++ = a;
4337  a = (rgbval >> 16) & 0xff;
4338  *prgb = a;
4339  }
4340 #endif
4341 
4342 #ifdef PRINT_TIMINGS
4343  ttime += sw.Time();
4344 
4345  if (cnt == 500000) {
4346  static int d;
4347  printf("cache time: %d %f\n", d, ttime / 1000.0);
4348  cnt = 0;
4349  d++;
4350  // ttime = 0;
4351  }
4352 #endif
4353 
4354  if (!bUseLineCache) {
4355 #ifndef USE_OLD_CACHE
4356  free(pt->pTileOffset);
4357 #endif
4358  free(pt->pPix);
4359  }
4360 
4361  return 1;
4362 }
4363 
4364 int *ChartBaseBSB::GetPalettePtr(BSB_Color_Capability color_index) {
4365  if (pPalettes[color_index]) {
4366  if (palette_direction == PaletteFwd)
4367  return (int *)(pPalettes[color_index]->FwdPalette);
4368  else
4369  return (int *)(pPalettes[color_index]->RevPalette);
4370  } else
4371  return NULL;
4372 }
4373 
4374 PaletteDir ChartBaseBSB::GetPaletteDir(void) {
4375  // make a pixel cache
4376  PixelCache *pc = new PixelCache(4, 4, BPP);
4377  RGBO r = pc->GetRGBO();
4378  delete pc;
4379 
4380  if (r == RGB)
4381  return PaletteFwd;
4382  else
4383  return PaletteRev;
4384 }
4385 
4386 bool ChartBaseBSB::AnalyzeSkew(void) {
4387  double lonmin = 1000;
4388  double lonmax = -1000;
4389  double latmin = 90.;
4390  double latmax = -90.;
4391 
4392  int nlonmin, nlonmax;
4393  nlonmin = 0;
4394  nlonmax = 0;
4395 
4396  if (0 == nRefpoint) // bad chart georef...
4397  return (1);
4398 
4399  for (int n = 0; n < nRefpoint; n++) {
4400  // Longitude
4401  if (pRefTable[n].lonr > lonmax) {
4402  lonmax = pRefTable[n].lonr;
4403  nlonmax = n;
4404  }
4405  if (pRefTable[n].lonr < lonmin) {
4406  lonmin = pRefTable[n].lonr;
4407  nlonmin = n;
4408  }
4409 
4410  // Latitude
4411  if (pRefTable[n].latr < latmin) {
4412  latmin = pRefTable[n].latr;
4413  }
4414  if (pRefTable[n].latr > latmax) {
4415  latmax = pRefTable[n].latr;
4416  }
4417  }
4418 
4419  // Special case for charts which cross the IDL
4420  if ((lonmin * lonmax) < 0) {
4421  if (pRefTable[nlonmin].xr > pRefTable[nlonmax].xr) {
4422  // walk the reference table and add 360 to any longitude which is < 0
4423  for (int n = 0; n < nRefpoint; n++) {
4424  if (pRefTable[n].lonr < 0.0) pRefTable[n].lonr += 360.;
4425  }
4426 
4427  // And recalculate the min/max
4428  lonmin = 1000;
4429  lonmax = -1000;
4430 
4431  for (int n = 0; n < nRefpoint; n++) {
4432  // Longitude
4433  if (pRefTable[n].lonr > lonmax) {
4434  lonmax = pRefTable[n].lonr;
4435  nlonmax = n;
4436  }
4437  if (pRefTable[n].lonr < lonmin) {
4438  lonmin = pRefTable[n].lonr;
4439  nlonmin = n;
4440  }
4441 
4442  // Latitude
4443  if (pRefTable[n].latr < latmin) {
4444  latmin = pRefTable[n].latr;
4445  }
4446  if (pRefTable[n].latr > latmax) {
4447  latmax = pRefTable[n].latr;
4448  }
4449  }
4450  }
4451  }
4452 
4453  // Find the two REF points that are farthest apart
4454  double dist_max = 0.;
4455  int imax = 0;
4456  int jmax = 0;
4457 
4458  for (int i = 0; i < nRefpoint; i++) {
4459  for (int j = i + 1; j < nRefpoint; j++) {
4460  double dx = pRefTable[i].xr - pRefTable[j].xr;
4461  double dy = pRefTable[i].yr - pRefTable[j].yr;
4462  double dist = (dx * dx) + (dy * dy);
4463  if (dist > dist_max) {
4464  dist_max = dist;
4465  imax = i;
4466  jmax = j;
4467  }
4468  }
4469  }
4470 
4471  double apparent_skew = 0;
4472 
4473  if (m_projection == PROJECTION_MERCATOR) {
4474  double easting0, easting1, northing0, northing1;
4475  // Get the Merc projection of the two REF points
4476  toSM_ECC(pRefTable[imax].latr, pRefTable[imax].lonr, m_proj_lat, m_proj_lon,
4477  &easting0, &northing0);
4478  toSM_ECC(pRefTable[jmax].latr, pRefTable[jmax].lonr, m_proj_lat, m_proj_lon,
4479  &easting1, &northing1);
4480 
4481  double skew_proj =
4482  atan2((easting1 - easting0), (northing1 - northing0)) * 180. / PI;
4483  double skew_points = atan2((pRefTable[jmax].yr - pRefTable[imax].yr),
4484  (pRefTable[jmax].xr - pRefTable[imax].xr)) *
4485  180. / PI;
4486 
4487  apparent_skew = skew_points - skew_proj + 90.;
4488 
4489  // normalize to +/- 180.
4490  if (fabs(apparent_skew) > 180.) {
4491  if (apparent_skew < 0.)
4492  apparent_skew += 360.;
4493  else
4494  apparent_skew -= 360.;
4495  }
4496  }
4497 
4498  else if (m_projection == PROJECTION_TRANSVERSE_MERCATOR) {
4499  double easting0, easting1, northing0, northing1;
4500  // Get the TMerc projection of the two REF points
4501  toTM(pRefTable[imax].latr, pRefTable[imax].lonr, m_proj_lat, m_proj_lon,
4502  &easting0, &northing0);
4503  toTM(pRefTable[jmax].latr, pRefTable[jmax].lonr, m_proj_lat, m_proj_lon,
4504  &easting1, &northing1);
4505 
4506  double skew_proj =
4507  atan2((easting1 - easting0), (northing1 - northing0)) * 180. / PI;
4508  double skew_points = atan2((pRefTable[jmax].yr - pRefTable[imax].yr),
4509  (pRefTable[jmax].xr - pRefTable[imax].xr)) *
4510  180. / PI;
4511 
4512  apparent_skew = skew_points - skew_proj + 90.;
4513 
4514  // normalize to +/- 180.
4515  if (fabs(apparent_skew) > 180.) {
4516  if (apparent_skew < 0.)
4517  apparent_skew += 360.;
4518  else
4519  apparent_skew -= 360.;
4520  }
4521 
4522  if (fabs(apparent_skew - m_Chart_Skew) > 2) { // measured skew OK?
4523  // it may be that the projection longitude is simply wrong.
4524  // Check it.
4525  double dskew = fabs(apparent_skew - m_Chart_Skew);
4526  if ((m_proj_lon < lonmin) || (m_proj_lon > lonmax)) {
4527  // Try a projection longitude that is mid-meridian in the chart.
4528  double tentative_proj_lon = (lonmin + lonmax) / 2.;
4529 
4530  toTM(pRefTable[imax].latr, pRefTable[imax].lonr, m_proj_lat,
4531  tentative_proj_lon, &easting0, &northing0);
4532  toTM(pRefTable[jmax].latr, pRefTable[jmax].lonr, m_proj_lat,
4533  tentative_proj_lon, &easting1, &northing1);
4534 
4535  skew_proj =
4536  atan2((easting1 - easting0), (northing1 - northing0)) * 180. / PI;
4537  skew_points = atan2((pRefTable[jmax].yr - pRefTable[imax].yr),
4538  (pRefTable[jmax].xr - pRefTable[imax].xr)) *
4539  180. / PI;
4540 
4541  apparent_skew = skew_points - skew_proj + 90.;
4542 
4543  // normalize to +/- 180.
4544  if (fabs(apparent_skew) > 180.) {
4545  if (apparent_skew < 0.)
4546  apparent_skew += 360.;
4547  else
4548  apparent_skew -= 360.;
4549  }
4550 
4551  // Better? If so, adopt the adjusted projection longitude
4552  if (fabs(apparent_skew - m_Chart_Skew) < dskew) {
4553  m_proj_lon = tentative_proj_lon;
4554  }
4555  }
4556  }
4557  } else // For all other projections, assume that skew specified in header is
4558  // correct
4559  apparent_skew = m_Chart_Skew;
4560 
4561  if (fabs(apparent_skew - m_Chart_Skew) >
4562  2) { // measured skew is more than 2 degrees
4563  m_Chart_Skew = apparent_skew; // different from stated skew
4564 
4565  wxString msg = _T(" Warning: Skew override on chart ");
4566  msg.Append(m_FullPath);
4567  wxString msg1;
4568  msg1.Printf(_T(" is %5g degrees"), apparent_skew);
4569  msg.Append(msg1);
4570 
4571  wxLogMessage(msg);
4572 
4573  return false;
4574  }
4575 
4576  return true;
4577 }
4578 
4579 int ChartBaseBSB::AnalyzeRefpoints(bool b_testSolution) {
4580  int i, n;
4581  double elt, elg;
4582 
4583  // Calculate the max/min reference points
4584 
4585  float lonmin = 1000;
4586  float lonmax = -1000;
4587  float latmin = 90.;
4588  float latmax = -90.;
4589 
4590  int plonmin = 100000;
4591  int plonmax = 0;
4592  int platmin = 100000;
4593  int platmax = 0;
4594  int nlonmin = 0, nlonmax = 0;
4595 
4596  if (0 == nRefpoint) // bad chart georef...
4597  return (1);
4598 
4599  for (n = 0; n < nRefpoint; n++) {
4600  // Longitude
4601  if (pRefTable[n].lonr > lonmax) {
4602  lonmax = pRefTable[n].lonr;
4603  plonmax = (int)pRefTable[n].xr;
4604  nlonmax = n;
4605  }
4606  if (pRefTable[n].lonr < lonmin) {
4607  lonmin = pRefTable[n].lonr;
4608  plonmin = (int)pRefTable[n].xr;
4609  nlonmin = n;
4610  }
4611 
4612  // Latitude
4613  if (pRefTable[n].latr < latmin) {
4614  latmin = pRefTable[n].latr;
4615  platmin = (int)pRefTable[n].yr;
4616  }
4617  if (pRefTable[n].latr > latmax) {
4618  latmax = pRefTable[n].latr;
4619  platmax = (int)pRefTable[n].yr;
4620  }
4621  }
4622 
4623  // Special case for charts which cross the IDL
4624  if ((lonmin * lonmax) < 0) {
4625  if (pRefTable[nlonmin].xr > pRefTable[nlonmax].xr) {
4626  // walk the reference table and add 360 to any longitude which is < 0
4627  for (n = 0; n < nRefpoint; n++) {
4628  if (pRefTable[n].lonr < 0.0) pRefTable[n].lonr += 360.;
4629  }
4630 
4631  // And recalculate the min/max
4632  lonmin = 1000;
4633  lonmax = -1000;
4634 
4635  for (n = 0; n < nRefpoint; n++) {
4636  // Longitude
4637  if (pRefTable[n].lonr > lonmax) {
4638  lonmax = pRefTable[n].lonr;
4639  plonmax = (int)pRefTable[n].xr;
4640  nlonmax = n;
4641  }
4642  if (pRefTable[n].lonr < lonmin) {
4643  lonmin = pRefTable[n].lonr;
4644  plonmin = (int)pRefTable[n].xr;
4645  nlonmin = n;
4646  }
4647 
4648  // Latitude
4649  if (pRefTable[n].latr < latmin) {
4650  latmin = pRefTable[n].latr;
4651  platmin = (int)pRefTable[n].yr;
4652  }
4653  if (pRefTable[n].latr > latmax) {
4654  latmax = pRefTable[n].latr;
4655  platmax = (int)pRefTable[n].yr;
4656  }
4657  }
4658  }
4659  }
4660 
4661  // Build the Control Point Structure, etc
4662  cPoints.count = nRefpoint;
4663  if (cPoints.status) {
4664  // AnalyzeRefpoints can be called twice
4665  free(cPoints.tx);
4666  free(cPoints.ty);
4667  free(cPoints.lon);
4668  free(cPoints.lat);
4669 
4670  free(cPoints.pwx);
4671  free(cPoints.wpx);
4672  free(cPoints.pwy);
4673  free(cPoints.wpy);
4674  }
4675 
4676  cPoints.tx = (double *)malloc(nRefpoint * sizeof(double));
4677  cPoints.ty = (double *)malloc(nRefpoint * sizeof(double));
4678  cPoints.lon = (double *)malloc(nRefpoint * sizeof(double));
4679  cPoints.lat = (double *)malloc(nRefpoint * sizeof(double));
4680 
4681  cPoints.pwx = (double *)malloc(12 * sizeof(double));
4682  cPoints.wpx = (double *)malloc(12 * sizeof(double));
4683  cPoints.pwy = (double *)malloc(12 * sizeof(double));
4684  cPoints.wpy = (double *)malloc(12 * sizeof(double));
4685  cPoints.status = 1;
4686 
4687  // Find the two REF points that are farthest apart
4688  double dist_max = 0.;
4689  int imax = 0;
4690  int jmax = 0;
4691 
4692  for (i = 0; i < nRefpoint; i++) {
4693  for (int j = i + 1; j < nRefpoint; j++) {
4694  double dx = pRefTable[i].xr - pRefTable[j].xr;
4695  double dy = pRefTable[i].yr - pRefTable[j].yr;
4696  double dist = (dx * dx) + (dy * dy);
4697  if (dist > dist_max) {
4698  dist_max = dist;
4699  imax = i;
4700  jmax = j;
4701  }
4702  }
4703  }
4704 
4705  // Georef solution depends on projection type
4706 
4707  if (m_projection == PROJECTION_TRANSVERSE_MERCATOR) {
4708  double easting0, easting1, northing0, northing1;
4709  // Get the TMerc projection of the two REF points
4710  toTM(pRefTable[imax].latr, pRefTable[imax].lonr, m_proj_lat, m_proj_lon,
4711  &easting0, &northing0);
4712  toTM(pRefTable[jmax].latr, pRefTable[jmax].lonr, m_proj_lat, m_proj_lon,
4713  &easting1, &northing1);
4714 
4715  // Calculate the scale factor using exact REF point math
4716  double dx2 = (pRefTable[jmax].xr - pRefTable[imax].xr) *
4717  (pRefTable[jmax].xr - pRefTable[imax].xr);
4718  double dy2 = (pRefTable[jmax].yr - pRefTable[imax].yr) *
4719  (pRefTable[jmax].yr - pRefTable[imax].yr);
4720  double dn2 = (northing1 - northing0) * (northing1 - northing0);
4721  double de2 = (easting1 - easting0) * (easting1 - easting0);
4722 
4723  m_ppm_avg = sqrt(dx2 + dy2) / sqrt(dn2 + de2);
4724 
4725  // Set up and solve polynomial solution for pix<->east/north as projected
4726  // Fill the cpoints structure with pixel points and transformed lat/lon
4727 
4728  for (int n = 0; n < nRefpoint; n++) {
4729  double easting, northing;
4730  toTM(pRefTable[n].latr, pRefTable[n].lonr, m_proj_lat, m_proj_lon,
4731  &easting, &northing);
4732 
4733  cPoints.tx[n] = pRefTable[n].xr;
4734  cPoints.ty[n] = pRefTable[n].yr;
4735  cPoints.lon[n] = easting;
4736  cPoints.lat[n] = northing;
4737  }
4738 
4739  // Helper parameters
4740  cPoints.txmax = plonmax;
4741  cPoints.txmin = plonmin;
4742  cPoints.tymax = platmax;
4743  cPoints.tymin = platmin;
4744  toTM(latmax, lonmax, m_proj_lat, m_proj_lon, &cPoints.lonmax,
4745  &cPoints.latmax);
4746  toTM(latmin, lonmin, m_proj_lat, m_proj_lon, &cPoints.lonmin,
4747  &cPoints.latmin);
4748 
4749  Georef_Calculate_Coefficients_Proj(&cPoints);
4750 
4751  }
4752 
4753  else if (m_projection == PROJECTION_MERCATOR) {
4754  double easting0, easting1, northing0, northing1;
4755  // Get the Merc projection of the two REF points
4756  toSM_ECC(pRefTable[imax].latr, pRefTable[imax].lonr, m_proj_lat, m_proj_lon,
4757  &easting0, &northing0);
4758  toSM_ECC(pRefTable[jmax].latr, pRefTable[jmax].lonr, m_proj_lat, m_proj_lon,
4759  &easting1, &northing1);
4760 
4761  // Calculate the scale factor using exact REF point math
4762  // double dx = (pRefTable[jmax].xr - pRefTable[imax].xr);
4763  // double de = (easting1 - easting0);
4764  // m_ppm_avg = fabs(dx / de);
4765 
4766  double dx2 = (pRefTable[jmax].xr - pRefTable[imax].xr) *
4767  (pRefTable[jmax].xr - pRefTable[imax].xr);
4768  double dy2 = (pRefTable[jmax].yr - pRefTable[imax].yr) *
4769  (pRefTable[jmax].yr - pRefTable[imax].yr);
4770  double dn2 = (northing1 - northing0) * (northing1 - northing0);
4771  double de2 = (easting1 - easting0) * (easting1 - easting0);
4772 
4773  m_ppm_avg = sqrt(dx2 + dy2) / sqrt(dn2 + de2);
4774 
4775  // Set up and solve polynomial solution for pix<->east/north as projected
4776  // Fill the cpoints structure with pixel points and transformed lat/lon
4777 
4778  for (int n = 0; n < nRefpoint; n++) {
4779  double easting, northing;
4780  toSM_ECC(pRefTable[n].latr, pRefTable[n].lonr, m_proj_lat, m_proj_lon,
4781  &easting, &northing);
4782 
4783  cPoints.tx[n] = pRefTable[n].xr;
4784  cPoints.ty[n] = pRefTable[n].yr;
4785  cPoints.lon[n] = easting;
4786  cPoints.lat[n] = northing;
4787  // printf(" x: %g y: %g east: %g north:
4788  // %g\n",pRefTable[n].xr, pRefTable[n].yr, easting,
4789  // northing);
4790  }
4791 
4792  // Helper parameters
4793  cPoints.txmax = plonmax;
4794  cPoints.txmin = plonmin;
4795  cPoints.tymax = platmax;
4796  cPoints.tymin = platmin;
4797  toSM_ECC(latmax, lonmax, m_proj_lat, m_proj_lon, &cPoints.lonmax,
4798  &cPoints.latmax);
4799  toSM_ECC(latmin, lonmin, m_proj_lat, m_proj_lon, &cPoints.lonmin,
4800  &cPoints.latmin);
4801 
4802  Georef_Calculate_Coefficients_Proj(&cPoints);
4803 
4804  // for(int h=0 ; h < 10 ; h++)
4805  // printf("pix to east %d %g\n", h, cPoints.pwx[h]); //
4806  // pix to lon
4807  // for(int h=0 ; h < 10 ; h++)
4808  // printf("east to pix %d %g\n", h, cPoints.wpx[h]); //
4809  // lon to pix
4810 
4811  /*
4812  if ((0 != m_Chart_DU ) && (0 != m_Chart_Scale))
4813  {
4814  double m_ppm_avg1 = m_Chart_DU * 39.37 / m_Chart_Scale;
4815  m_ppm_avg1 *= cos(m_proj_lat * PI / 180.); // correct to
4816  chart centroid
4817 
4818  printf("BSB chart ppm_avg:%g ppm_avg1:%g\n", m_ppm_avg,
4819  m_ppm_avg1); m_ppm_avg = m_ppm_avg1;
4820  }
4821  */
4822  }
4823 
4824  else if (m_projection == PROJECTION_POLYCONIC) {
4825  // This is interesting
4826  // On some BSB V 1.0 Polyconic charts (e.g. 14500_1, 1995), the projection
4827  // parameter Which is taken to be the central meridian of the projection
4828  // is of the wrong sign....
4829 
4830  // We check for this case, and make a correction if necessary.....
4831  // Obviously, the projection meridian should be on the chart, i.e. between
4832  // the min and max longitudes....
4833  double proj_meridian = m_proj_lon;
4834 
4835  if ((pRefTable[nlonmax].lonr >= -proj_meridian) &&
4836  (-proj_meridian >= pRefTable[nlonmin].lonr))
4837  m_proj_lon = -m_proj_lon;
4838 
4839  double easting0, easting1, northing0, northing1;
4840  // Get the Poly projection of the two REF points
4841  toPOLY(pRefTable[imax].latr, pRefTable[imax].lonr, m_proj_lat, m_proj_lon,
4842  &easting0, &northing0);
4843  toPOLY(pRefTable[jmax].latr, pRefTable[jmax].lonr, m_proj_lat, m_proj_lon,
4844  &easting1, &northing1);
4845 
4846  // Calculate the scale factor using exact REF point math
4847  double dx2 = (pRefTable[jmax].xr - pRefTable[imax].xr) *
4848  (pRefTable[jmax].xr - pRefTable[imax].xr);
4849  double dy2 = (pRefTable[jmax].yr - pRefTable[imax].yr) *
4850  (pRefTable[jmax].yr - pRefTable[imax].yr);
4851  double dn2 = (northing1 - northing0) * (northing1 - northing0);
4852  double de2 = (easting1 - easting0) * (easting1 - easting0);
4853 
4854  m_ppm_avg = sqrt(dx2 + dy2) / sqrt(dn2 + de2);
4855 
4856  // Sanity check
4857  // double ref_dist = DistGreatCircle(pRefTable[imax].latr,
4858  // pRefTable[imax].lonr, pRefTable[jmax].latr,
4859  // pRefTable[jmax].lonr); ref_dist *= 1852; //To Meters double
4860  // ref_dist_transform = sqrt(dn2 + de2); //Also meters
4861  // double error = (ref_dist - ref_dist_transform)/ref_dist;
4862 
4863  // Set up and solve polynomial solution for pix<->cartesian east/north as
4864  // projected
4865  // Fill the cpoints structure with pixel points and transformed lat/lon
4866 
4867  for (int n = 0; n < nRefpoint; n++) {
4868  double easting, northing;
4869  toPOLY(pRefTable[n].latr, pRefTable[n].lonr, m_proj_lat, m_proj_lon,
4870  &easting, &northing);
4871 
4872  // Round trip check for debugging....
4873  // double lat, lon;
4874  // fromPOLY(easting, northing, m_proj_lat, m_proj_lon,
4875  // &lat, &lon);
4876 
4877  cPoints.tx[n] = pRefTable[n].xr;
4878  cPoints.ty[n] = pRefTable[n].yr;
4879  cPoints.lon[n] = easting;
4880  cPoints.lat[n] = northing;
4881  // printf(" x: %g y: %g east: %g north:
4882  // %g\n",pRefTable[n].xr, pRefTable[n].yr, easting,
4883  // northing);
4884  }
4885 
4886  // Helper parameters
4887  cPoints.txmax = plonmax;
4888  cPoints.txmin = plonmin;
4889  cPoints.tymax = platmax;
4890  cPoints.tymin = platmin;
4891  toPOLY(latmax, lonmax, m_proj_lat, m_proj_lon, &cPoints.lonmax,
4892  &cPoints.latmax);
4893  toPOLY(latmin, lonmin, m_proj_lat, m_proj_lon, &cPoints.lonmin,
4894  &cPoints.latmin);
4895 
4896  Georef_Calculate_Coefficients_Proj(&cPoints);
4897 
4898  // for(int h=0 ; h < 10 ; h++)
4899  // printf("pix to east %d %g\n", h, cPoints.pwx[h]); //
4900  // pix to lon
4901  // for(int h=0 ; h < 10 ; h++)
4902  // printf("east to pix %d %g\n", h, cPoints.wpx[h]); //
4903  // lon to pix
4904 
4905  }
4906 
4907  // Any other projection had better have embedded coefficients
4908  else if (bHaveEmbeddedGeoref) {
4909  // Use a Mercator Projection to get a rough ppm.
4910  double easting0, easting1, northing0, northing1;
4911  // Get the Merc projection of the two REF points
4912  toSM_ECC(pRefTable[imax].latr, pRefTable[imax].lonr, m_proj_lat, m_proj_lon,
4913  &easting0, &northing0);
4914  toSM_ECC(pRefTable[jmax].latr, pRefTable[jmax].lonr, m_proj_lat, m_proj_lon,
4915  &easting1, &northing1);
4916 
4917  // Calculate the scale factor using exact REF point math in x(longitude)
4918  // direction
4919 
4920  double dx = (pRefTable[jmax].xr - pRefTable[imax].xr);
4921  double de = (easting1 - easting0);
4922 
4923  m_ppm_avg = fabs(dx / de);
4924 
4925  m_ExtraInfo =
4926  _T("---<<< Warning: Chart georef accuracy may be poor. >>>---");
4927  }
4928 
4929  else
4930  m_ppm_avg = 1.0; // absolute fallback to prevent div-0 errors
4931 
4932 #if 0
4933  // Alternate Skew verification
4934  ViewPort vps;
4935  vps.clat = pRefTable[0].latr;
4936  vps.clon = pRefTable[0].lonr;
4937  vps.view_scale_ppm = m_ppm_avg;
4938  vps.skew = 0.;
4939  vps.pix_width = 1000;
4940  vps.pix_height = 1000;
4941 
4942  int x1, y1, x2, y2;
4943  latlong_to_pix_vp(latmin, (lonmax + lonmin)/2., x1, y1, vps);
4944  latlong_to_pix_vp(latmax, (lonmax + lonmin)/2., x2, y2, vps);
4945 
4946  double apparent_skew = (atan2( (y2-y1), (x2-x1) ) * 180./PI) + 90.;
4947  if(apparent_skew < 0.)
4948  apparent_skew += 360;
4949  if(apparent_skew > 360.)
4950  apparent_skew -= 360;
4951 
4952  if(fabs( apparent_skew - m_Chart_Skew ) > 2) { // measured skew is more than 2 degress different
4953  m_Chart_Skew = apparent_skew;
4954  }
4955 #endif
4956 
4957  if (!b_testSolution) return (0);
4958 
4959  // Do a last little test using a synthetic ViewPort of nominal size.....
4960  ViewPort vp;
4961  vp.clat = pRefTable[0].latr;
4962  vp.clon = pRefTable[0].lonr;
4963  vp.view_scale_ppm = m_ppm_avg;
4964  vp.skew = 0.;
4965  vp.pix_width = 1000;
4966  vp.pix_height = 1000;
4967  // vp.rv_rect = wxRect(0,0, vp.pix_width, vp.pix_height);
4968  SetVPRasterParms(vp);
4969 
4970  double xpl_err_max = 0;
4971  double ypl_err_max = 0;
4972  int px, py;
4973 
4974  int pxref, pyref;
4975  pxref = (int)pRefTable[0].xr;
4976  pyref = (int)pRefTable[0].yr;
4977 
4978  for (i = 0; i < nRefpoint; i++) {
4979  px = (int)(vp.pix_width / 2 + pRefTable[i].xr) - pxref;
4980  py = (int)(vp.pix_height / 2 + pRefTable[i].yr) - pyref;
4981 
4982  vp_pix_to_latlong(vp, px, py, &elt, &elg);
4983 
4984  double lat_error = elt - pRefTable[i].latr;
4985  pRefTable[i].ypl_error = lat_error;
4986 
4987  double lon_error = elg - pRefTable[i].lonr;
4988 
4989  // Longitude errors could be compounded by prior adjustment to ref points
4990  if (fabs(lon_error) > 180.) {
4991  if (lon_error > 0.)
4992  lon_error -= 360.;
4993  else if (lon_error < 0.)
4994  lon_error += 360.;
4995  }
4996  pRefTable[i].xpl_error = lon_error;
4997 
4998  if (fabs(pRefTable[i].ypl_error) > fabs(ypl_err_max))
4999  ypl_err_max = pRefTable[i].ypl_error;
5000  if (fabs(pRefTable[i].xpl_error) > fabs(xpl_err_max))
5001  xpl_err_max = pRefTable[i].xpl_error;
5002  }
5003 
5004  Chart_Error_Factor = fmax(fabs(xpl_err_max / (lonmax - lonmin)),
5005  fabs(ypl_err_max / (latmax - latmin)));
5006  double chart_error_meters =
5007  fmax(fabs(xpl_err_max * 60. * 1852.), fabs(ypl_err_max * 60. * 1852.));
5008  // calculate a nominal pixel error
5009  // Assume a modern display has about 4000 pixels/meter.
5010  // Assume the chart is to be displayed at nominal printed scale
5011  double chart_error_pixels = chart_error_meters * 4000. / m_Chart_Scale;
5012 
5013  // Good enough for navigation?
5014  int max_pixel_error = 4;
5015 
5016  if (chart_error_pixels > max_pixel_error) {
5017  wxString msg =
5018  _T(" VP Final Check: Georeference Chart_Error_Factor on chart ");
5019  msg.Append(m_FullPath);
5020  wxString msg1;
5021  msg1.Printf(_T(" is %5g \n nominal pixel error is: %5g"),
5022  Chart_Error_Factor, chart_error_pixels);
5023  msg.Append(msg1);
5024 
5025  wxLogMessage(msg);
5026 
5027  m_ExtraInfo = _T("---<<< Warning: Chart georef accuracy is poor. >>>---");
5028  }
5029 
5030  // Try again with my calculated georef
5031  // This problem was found on NOAA 514_1.KAP. The embedded coefficients are
5032  // just wrong....
5033  if ((chart_error_pixels > max_pixel_error) && bHaveEmbeddedGeoref) {
5034  wxString msg =
5035  _T(" Trying again with internally calculated georef solution ");
5036  wxLogMessage(msg);
5037 
5038  bHaveEmbeddedGeoref = false;
5039  SetVPRasterParms(vp);
5040 
5041  xpl_err_max = 0;
5042  ypl_err_max = 0;
5043 
5044  pxref = (int)pRefTable[0].xr;
5045  pyref = (int)pRefTable[0].yr;
5046 
5047  for (i = 0; i < nRefpoint; i++) {
5048  px = (int)(vp.pix_width / 2 + pRefTable[i].xr) - pxref;
5049  py = (int)(vp.pix_height / 2 + pRefTable[i].yr) - pyref;
5050 
5051  vp_pix_to_latlong(vp, px, py, &elt, &elg);
5052 
5053  double lat_error = elt - pRefTable[i].latr;
5054  pRefTable[i].ypl_error = lat_error;
5055 
5056  double lon_error = elg - pRefTable[i].lonr;
5057 
5058  // Longitude errors could be compounded by prior adjustment to ref points
5059  if (fabs(lon_error) > 180.) {
5060  if (lon_error > 0.)
5061  lon_error -= 360.;
5062  else if (lon_error < 0.)
5063  lon_error += 360.;
5064  }
5065  pRefTable[i].xpl_error = lon_error;
5066 
5067  if (fabs(pRefTable[i].ypl_error) > fabs(ypl_err_max))
5068  ypl_err_max = pRefTable[i].ypl_error;
5069  if (fabs(pRefTable[i].xpl_error) > fabs(xpl_err_max))
5070  xpl_err_max = pRefTable[i].xpl_error;
5071  }
5072 
5073  Chart_Error_Factor = fmax(fabs(xpl_err_max / (lonmax - lonmin)),
5074  fabs(ypl_err_max / (latmax - latmin)));
5075 
5076  chart_error_meters =
5077  fmax(fabs(xpl_err_max * 60. * 1852.), fabs(ypl_err_max * 60. * 1852.));
5078  chart_error_pixels = chart_error_meters * 4000. / m_Chart_Scale;
5079 
5080  // Good enough for navigation?
5081  if (chart_error_pixels > max_pixel_error) {
5082  wxString msg =
5083  _T(" VP Final Check with internal georef: Georeference ")
5084  _T("Chart_Error_Factor on chart ");
5085  msg.Append(m_FullPath);
5086  wxString msg1;
5087  msg1.Printf(_T(" is %5g\n nominal pixel error is: %5g"),
5088  Chart_Error_Factor, chart_error_pixels);
5089  msg.Append(msg1);
5090 
5091  wxLogMessage(msg);
5092 
5093  m_ExtraInfo =
5094  _T("---<<< Warning: Chart georef accuracy is poor. >>>---");
5095  } else {
5096  wxString msg = _T(" Result: OK, Internal georef solution used.");
5097 
5098  wxLogMessage(msg);
5099 
5100  m_ExtraInfo = _T("");
5101  }
5102  }
5103 
5104  return (0);
5105 }
5106 
5107 double ChartBaseBSB::AdjustLongitude(double lon) {
5108  double lond = (m_LonMin + m_LonMax) / 2 - lon;
5109  if (lond > 180)
5110  return lon + 360;
5111  else if (lond < -180)
5112  return lon - 360;
5113  return lon;
5114 }
5115 
5116 /*
5117  * Extracted from bsb_io.c - implementation of libbsb reading and writing
5118  *
5119  * Copyright (C) 2000 Stuart Cunningham <stuart_hc@users.sourceforge.net>
5120  *
5121  * This library is free software; you can redistribute it and/or
5122  * modify it under the terms of the GNU Lesser General Public
5123  * License as published by the Free Software Foundation; either
5124  * version 2.1 of the License, or (at your option) any later version.
5125  *
5126  * This library is distributed in the hope that it will be useful,
5127  * but WITHOUT ANY WARRANTY; without even the implied warranty of
5128  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
5129  * Lesser General Public License for more details.
5130  *
5131  * You should have received a copy of the GNU Lesser General Public
5132  * License along with this library; if not, write to the Free Software
5133  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
5134  *
5135  *
5136  */
5137 
5147 static double polytrans(double *coeff, double lon, double lat) {
5148  double ret = coeff[0] + coeff[1] * lon + coeff[2] * lat;
5149  ret += coeff[3] * lon * lon;
5150  ret += coeff[4] * lon * lat;
5151  ret += coeff[5] * lat * lat;
5152  ret += coeff[6] * lon * lon * lon;
5153  ret += coeff[7] * lon * lon * lat;
5154  ret += coeff[8] * lon * lat * lat;
5155  ret += coeff[9] * lat * lat * lat;
5156  // ret += coeff[10]*lat*lat*lat*lat;
5157  // ret += coeff[11]*lat*lat*lat*lat*lat;
5158 
5159  // for(int n = 0 ; n < 10 ; n++)
5160  // printf(" %g\n", coeff[n]);
5161 
5162  return ret;
5163 }
5164 
5165 #if 0
5177 extern int bsb_LLtoXY(BSBImage *p, double lon, double lat, int* x, int* y)
5178 {
5179  /* change longitude phase (CPH) */
5180  lon = (lon < 0) ? lon + p->cph : lon - p->cph;
5181  double xd = polytrans( p->wpx, lon, lat );
5182  double yd = polytrans( p->wpy, lon, lat );
5183  *x = (int)(xd + 0.5);
5184  *y = (int)(yd + 0.5);
5185  return 1;
5186 }
5187 
5199 extern int bsb_XYtoLL(BSBImage *p, int x, int y, double* lonout, double* latout)
5200 {
5201  double lon = polytrans( p->pwx, x, y );
5202  lon = (lon < 0) ? lon + p->cph : lon - p->cph;
5203  *lonout = lon;
5204  *latout = polytrans( p->pwy, x, y );
5205  return 1;
5206 }
5207 
5208 #endif