OpenCPN Partial API docs
Loading...
Searching...
No Matches
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 "chartdata_input_stream.h"
59
60#ifndef __WXMSW__
61#include <signal.h>
62#include <setjmp.h>
63
64#define OCPN_USE_CONFIG 1
65
66struct sigaction sa_all_chart;
67struct sigaction sa_all_previous;
68
69sigjmp_buf env_chart; // the context saved by sigsetjmp();
70
71void 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
86typedef __int32 int32_t;
87typedef unsigned __int32 uint32_t;
88typedef __int64 int64_t;
89typedef unsigned __int64 uint64_t;
90#endif
91
92// ----------------------------------------------------------------------------
93// Random Prototypes
94// ----------------------------------------------------------------------------
95
96#ifdef OCPN_USE_CONFIG
97class MyConfig;
98extern MyConfig *pConfig;
99#endif
100
101typedef struct {
102 float y;
103 float x;
104} MyFlPoint;
105
106bool G_FloatPtInPolygon(MyFlPoint *rgpts, int wnumpts, float x, float y);
107
108// ----------------------------------------------------------------------------
109// private classes
110// ----------------------------------------------------------------------------
111
112// ============================================================================
113// ThumbData implementation
114// ============================================================================
115
116ThumbData::ThumbData() { pDIBThumb = NULL; }
117
118ThumbData::~ThumbData() { delete pDIBThumb; }
119
120// ============================================================================
121// Palette implementation
122// ============================================================================
123opncpnPalette::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
133opncpnPalette::~opncpnPalette() {
134 if (NULL != FwdPalette) free(FwdPalette);
135 if (NULL != RevPalette) free(RevPalette);
136}
137
138// ============================================================================
139// ChartBase implementation
140// ============================================================================
141ChartBase::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
171ChartBase::~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
191wxString 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/*
200int ChartBase::Continue_BackgroundHiDefRender(void)
201{
202 return BR_DONE_NOP; // signal "done, no refresh"
203}
204*/
205
206// ============================================================================
207// ChartDummy implementation
208// ============================================================================
209
210ChartDummy::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
220ChartDummy::~ChartDummy() { delete m_pBM; }
221
222InitReturn ChartDummy::Init(const wxString &name, ChartInitFlag init_flags) {
223 return INIT_OK;
224}
225
226void ChartDummy::SetColorScheme(ColorScheme cs, bool bApplyImmediate) {}
227
228ThumbData *ChartDummy::GetThumbData(int tnx, int tny, float lat, float lon) {
229 return (ThumbData *)NULL;
230}
231
232bool ChartDummy::UpdateThumbData(double lat, double lon) { return FALSE; }
233
234bool 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
243bool ChartDummy::RenderRegionViewOnGL(const wxGLContext &glc,
244 const ViewPort &VPoint,
245 const OCPNRegion &RectRegion,
246 const LLRegion &Region) {
247 return true;
248}
249
250bool ChartDummy::RenderRegionViewOnDC(wxMemoryDC &dc, const ViewPort &VPoint,
251 const OCPNRegion &Region) {
252 return RenderViewOnDC(dc, VPoint);
253}
254
255bool 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
280bool ChartDummy::AdjustVP(ViewPort &vp_last, ViewPort &vp_proposed) {
281 return false;
282}
283
284void ChartDummy::GetValidCanvasRegion(const ViewPort &VPoint,
285 OCPNRegion *pValidRegion) {
286 pValidRegion->Clear();
287 pValidRegion->Union(0, 0, 1, 1);
288}
289
290LLRegion ChartDummy::GetValidRegion() { return LLRegion(); }
291
292// ============================================================================
293// ChartGEO implementation
294// ============================================================================
295ChartGEO::ChartGEO() { m_ChartType = CHART_TYPE_GEO; }
296
297ChartGEO::~ChartGEO() {}
298
299InitReturn 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
727ChartKAP::ChartKAP() { m_ChartType = CHART_TYPE_KAP; }
728
729ChartKAP::~ChartKAP() {}
730
731InitReturn 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
1525ChartBaseBSB::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
1593ChartBaseBSB::~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
1628void 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
1645bool 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
1656double 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
1666double 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
1672double 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
1678double 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
1721InitReturn ChartBaseBSB::Init(const wxString &name, ChartInitFlag init_flags) {
1722 m_global_color_scheme = GLOBAL_COLOR_SCHEME_RGB;
1723 return INIT_OK;
1724}
1725
1726InitReturn ChartBaseBSB::PreInit(const wxString &name, ChartInitFlag init_flags,
1727 ColorScheme cs) {
1728 m_global_color_scheme = cs;
1729 return INIT_OK;
1730}
1731
1732void 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
1759InitReturn 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
1984bool 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
2026void 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
2042bool 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
2051bool 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
2162void 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
2197wxBitmap *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
2290ThumbData *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
2324bool 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//-----------------------------------------------------------------------
2358static double polytrans(double *coeff, double lon, double lat);
2359
2360int 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
2506int 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
2664void 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
2731void 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
2798void 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
2816double 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
2822void 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
2849bool 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
2888bool 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
2914void 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
2935LLRegion 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
2956bool 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
3162int s_dc;
3163
3164bool 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
3178bool ChartBaseBSB::RenderRegionViewOnGL(const wxGLContext &glc,
3179 const ViewPort &VPoint,
3180 const OCPNRegion &RectRegion,
3181 const LLRegion &Region) {
3182 return true;
3183}
3184
3185bool 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
3354wxImage *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
3373bool 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
3411bool 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
3700bool 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
3766int 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//-----------------------------------------------------------------------
3836int 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
3887inline 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
3910class OCPNStopWatch {
3911public:
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
3922private:
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//-----------------------------------------------------------------------
3940int 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
3954 if (bUseLineCache && pLineCache) {
3955 // Is the requested line in the cache, and valid?
3956 pt = &pLineCache[y];
3957 } else {
3958 pt = &cached_line;
3959 pt->bValid = false;
3960 }
3961
3962#ifdef PRINT_TIMINGS
3963 OCPNStopWatch sw;
3964 static double ttime;
3965 static int cnt;
3966 cnt++;
3967#endif
3968
3969 if (!pt->bValid) // not valid, allocate
3970 {
3971 int thisline_size = pline_table[y + 1] - pline_table[y];
3972
3973#ifdef USE_OLD_CACHE
3974 pt->pPix = (unsigned char *)malloc(Size_X);
3975#else
3976 pt->pTileOffset = (TileOffsetCache *)calloc(
3977 sizeof(TileOffsetCache) * (Size_X / TILE_SIZE + 1), 1);
3978 pt->pPix = (unsigned char *)malloc(thisline_size);
3979#endif
3980 if (pline_table[y] == 0 || pline_table[y + 1] == 0) FAIL;
3981
3982 // as of 2015, in wxWidgets buffered streams don't test for a zero seek
3983 // so we check here to possibly avoid this seek with a measured performance
3984 // gain
3985 if (ifs_bitmap->TellI() != pline_table[y] &&
3986 wxInvalidOffset == ifs_bitmap->SeekI(pline_table[y], wxFromStart))
3987 FAIL;
3988
3989#ifdef USE_OLD_CACHE
3990 if (thisline_size > ifs_bufsize) {
3991 unsigned char *tmp = ifs_buf;
3992 if (!(ifs_buf = (unsigned char *)realloc(ifs_buf, thisline_size))) {
3993 free(tmp);
3994 FAIL;
3995 }
3996 ifs_bufsize = thisline_size;
3997 }
3998
3999 lp = ifs_buf;
4000#else
4001 lp = pt->pPix;
4002#endif
4003 ifs_bitmap->Read(lp, thisline_size);
4004
4005#ifdef USE_OLD_CACHE
4006 pCL = pt->pPix;
4007#else
4008 if (!bUseLineCache) {
4009 ix = 0;
4010 // skip the line number.
4011 do byNext = *lp++;
4012 while ((byNext & 0x80) != 0);
4013 goto nocachestart;
4014 }
4015 pCL = ifs_buf;
4016
4017 if (Size_X > ifs_bufsize) {
4018 unsigned char *tmp = ifs_buf;
4019 if (!(ifs_buf = (unsigned char *)realloc(ifs_buf, Size_X))) {
4020 free(tmp);
4021 FAIL;
4022 }
4023 ifs_bufsize = Size_X;
4024 }
4025#endif
4026 // At this point, the unexpanded, raw line is at *lp, and the expansion
4027 // destination is pCL
4028
4029 // skip the line number.
4030 do byNext = *lp++;
4031 while ((byNext & 0x80) != 0);
4032
4033 // Setup masking values.
4034 nValueShift = 7 - nColorSize;
4035 byValueMask = (((1 << nColorSize)) - 1) << nValueShift;
4036 byCountMask = (1 << (7 - nColorSize)) - 1;
4037
4038 // Read and expand runs.
4039 unsigned int iPixel = 0;
4040
4041#ifndef USE_OLD_CACHE
4042 pt->pTileOffset[0].offset = lp - pt->pPix;
4043 pt->pTileOffset[0].pixel = 0;
4044 unsigned int tileindex = 1, nextTile = TILE_SIZE;
4045#endif
4046 unsigned int nRunCount;
4047 unsigned char *end = pt->pPix + thisline_size;
4048 while (iPixel < (unsigned int)Size_X)
4049#ifdef USE_OLD_CACHE
4050 {
4051 nPixValue = (byNext & byValueMask) >> nValueShift;
4052
4053 nRunCount = byNext & byCountMask;
4054
4055 while ((byNext & 0x80) != 0) {
4056 byNext = *lp++;
4057 nRunCount = nRunCount * 128 + (byNext & 0x7f);
4058 }
4059
4060 nRunCount++;
4061
4062 if (iPixel + nRunCount >
4063 (unsigned int)Size_X) // protection against corrupt data
4064 nRunCount = nRunCount - iPixel;
4065
4066 // Store nPixValue in the destination
4067 memset_short(pCL + iPixel, nPixValue, nRunCount);
4068 iPixel += nRunCount;
4069 }
4070#else
4071 // build tile offset table for faster random access
4072 {
4073 byNext = *lp++;
4074 unsigned char *offset = lp - 1;
4075 if (byNext == 0 || lp == end) {
4076 // finished early...corrupt?
4077 while (tileindex < (unsigned int)Size_X / TILE_SIZE + 1) {
4078 pt->pTileOffset[tileindex].offset = pt->pTileOffset[0].offset;
4079 pt->pTileOffset[tileindex].pixel = 0;
4080 tileindex++;
4081 }
4082 break;
4083 }
4084
4085 nRunCount = byNext & byCountMask;
4086
4087 while ((byNext & 0x80) != 0) {
4088 byNext = *lp++;
4089 nRunCount = nRunCount * 128 + (byNext & 0x7f);
4090 }
4091
4092 nRunCount++;
4093
4094 if (iPixel + nRunCount >
4095 (unsigned int)Size_X) // protection against corrupt data
4096 nRunCount = Size_X - iPixel;
4097
4098 while (iPixel + nRunCount > nextTile) {
4099 pt->pTileOffset[tileindex].offset = offset - pt->pPix;
4100 pt->pTileOffset[tileindex].pixel = iPixel;
4101 tileindex++;
4102 nextTile += TILE_SIZE;
4103 }
4104 iPixel += nRunCount;
4105 }
4106#endif
4107
4108 pt->bValid = true;
4109 }
4110
4111 // Line is valid, de-reference thru proper pallete directly to target
4112
4113 if (xl > Size_X) xl = Size_X;
4114
4115#ifdef USE_OLD_CACHE
4116 pCL = pt->pPix + xs;
4117
4118 // Optimization for most usual case
4119 if ((BPP == 24) && (1 == sub_samp)) {
4120 ix = xs;
4121 while (ix < xl - 1) {
4122 unsigned char cur_by = *pCL;
4123 rgbval = (int)(pPalette[cur_by]);
4124 while ((ix < xl - 1)) {
4125 if (cur_by != *pCL) break;
4126 *((int *)prgb) = rgbval;
4127 prgb += 3;
4128 pCL++;
4129 ix++;
4130 }
4131 }
4132 } else {
4133 int dest_inc_val_bytes = (BPP / 8) * sub_samp;
4134 ix = xs;
4135 while (ix < xl - 1) {
4136 unsigned char cur_by = *pCL;
4137 rgbval = (int)(pPalette[cur_by]);
4138 while ((ix < xl - 1)) {
4139 if (cur_by != *pCL) break;
4140 *((int *)prgb) = rgbval;
4141 prgb += dest_inc_val_bytes;
4142 pCL += sub_samp;
4143 ix += sub_samp;
4144 }
4145 }
4146 }
4147
4148 // Get the last pixel explicitely
4149 // irrespective of the sub_sampling factor
4150
4151 if (xs < xl - 1) {
4152 unsigned char *pCLast = pt->pPix + (xl - 1);
4153 unsigned char *prgb_last = pLineBuf + ((xl - 1) - xs) * BPP / 8;
4154
4155 rgbval = (int)(pPalette[*pCLast]); // last pixel
4156 unsigned char a = rgbval & 0xff;
4157 *prgb_last++ = a;
4158 a = (rgbval >> 8) & 0xff;
4159 *prgb_last++ = a;
4160 a = (rgbval >> 16) & 0xff;
4161 *prgb_last = a;
4162 }
4163#else
4164 {
4165 int tileindex = xs / TILE_SIZE;
4166 int tileoffset = pt->pTileOffset[tileindex].offset;
4167
4168 lp = pt->pPix + tileoffset;
4169 ix = pt->pTileOffset[tileindex].pixel;
4170 }
4171
4172nocachestart:
4173 nValueShift = 7 - nColorSize;
4174 byValueMask = (((1 << nColorSize)) - 1) << nValueShift;
4175 byCountMask = (1 << (7 - nColorSize)) - 1;
4176 int nPixValue = 0; // satisfy stupid compiler warning
4177 bool bLastPixValueValid = false;
4178
4179 while (ix < xl - 1) {
4180 byNext = *lp++;
4181
4182 nPixValue = (byNext & byValueMask) >> nValueShift;
4183 unsigned int nRunCount;
4184
4185 if (byNext == 0)
4186 nRunCount = xl - ix; // corrupted chart, just run to the end
4187 else {
4188 nRunCount = byNext & byCountMask;
4189 while ((byNext & 0x80) != 0) {
4190 byNext = *lp++;
4191 nRunCount = nRunCount * 128 + (byNext & 0x7f);
4192 }
4193
4194 nRunCount++;
4195 }
4196
4197 if (ix < xs) {
4198 if (ix + nRunCount <= (unsigned int)xs) {
4199 ix += nRunCount;
4200 continue;
4201 }
4202 nRunCount -= xs - ix;
4203 ix = xs;
4204 }
4205
4206 if (ix + nRunCount >= (unsigned int)xl) {
4207 nRunCount = xl - 1 - ix;
4208 bLastPixValueValid = true;
4209 }
4210
4211 rgbval = (int)(pPalette[nPixValue]);
4212
4213 // Optimization for most usual case
4214 // currently this is the only case possible...
4215 // if((BPP == 24) && (1 == sub_samp))
4216 {
4217 int count = nRunCount;
4218 if (count < 16) {
4219 // for short runs, use simple loop
4220 while (count--) {
4221 *(uint32_t *)prgb = rgbval;
4222 prgb += 3;
4223 }
4224 } else if (rgbval == 0 || rgbval == 0xffffff) {
4225 // optimization for black or white (could work for any gray too)
4226 memset(prgb, rgbval, nRunCount * 3);
4227 prgb += nRunCount * 3;
4228 } else {
4229 // note: this may not be optimal for all processors and compilers
4230 // I optimized for x86_64 using gcc with -O3
4231 // it is probably possible to gain even faster performance by ensuring
4232 // alignment to 16 or 32byte boundary (depending on processor) then
4233 // using inline assembly
4234
4235#ifdef __ARM_ARCH
4236 // ARM needs 8 byte alignment for *(uint64_T *x) = *(uint64_T *y)
4237 // because the compiler will (probably) use the ldrd/strd instuction
4238 // pair. So, advance the prgb pointer until it is 8-byte aligned, and
4239 // then carry on if enough bytes are left to process as 64 bit elements
4240
4241 if ((long)prgb & 7) {
4242 while (count--) {
4243 *(uint32_t *)prgb = rgbval;
4244 prgb += 3;
4245 if (!((long)prgb & 7)) {
4246 if (count >= 8) break;
4247 }
4248 }
4249 }
4250#endif
4251
4252 // fill first 24 bytes
4253 uint64_t *b = (uint64_t *)prgb;
4254 for (int i = 0; i < 8; i++) {
4255 *(uint32_t *)prgb = rgbval;
4256 prgb += 3;
4257 }
4258 count -= 8;
4259
4260 // fill in blocks of 24 bytes
4261 uint64_t *y = (uint64_t *)prgb;
4262 int count_d8 = count >> 3;
4263 prgb += 24 * count_d8;
4264 while (count_d8--) {
4265 *y++ = b[0];
4266 *y++ = b[1];
4267 *y++ = b[2];
4268 }
4269
4270 // fill remaining bytes
4271 int rcount = count & 0x7;
4272 while (rcount--) {
4273 *(uint32_t *)prgb = rgbval;
4274 prgb += 3;
4275 }
4276 }
4277 }
4278
4279 ix += nRunCount;
4280 }
4281
4282 // Get the last pixel explicitely
4283 // irrespective of the sub_sampling factor
4284
4285 if (ix < xl) {
4286 if (!bLastPixValueValid) {
4287 byNext = *lp++;
4288 nPixValue = (byNext & byValueMask) >> nValueShift;
4289 }
4290 rgbval = (int)(pPalette[nPixValue]); // last pixel
4291 unsigned char a = rgbval & 0xff;
4292
4293 *prgb++ = a;
4294 a = (rgbval >> 8) & 0xff;
4295 *prgb++ = a;
4296 a = (rgbval >> 16) & 0xff;
4297 *prgb = a;
4298 }
4299#endif
4300
4301#ifdef PRINT_TIMINGS
4302 ttime += sw.Time();
4303
4304 if (cnt == 500000) {
4305 static int d;
4306 printf("cache time: %d %f\n", d, ttime / 1000.0);
4307 cnt = 0;
4308 d++;
4309 // ttime = 0;
4310 }
4311#endif
4312
4313 if (!bUseLineCache) {
4314#ifndef USE_OLD_CACHE
4315 free(pt->pTileOffset);
4316#endif
4317 free(pt->pPix);
4318 }
4319
4320 return 1;
4321}
4322
4323int *ChartBaseBSB::GetPalettePtr(BSB_Color_Capability color_index) {
4324 if (pPalettes[color_index]) {
4325 if (palette_direction == PaletteFwd)
4326 return (int *)(pPalettes[color_index]->FwdPalette);
4327 else
4328 return (int *)(pPalettes[color_index]->RevPalette);
4329 } else
4330 return NULL;
4331}
4332
4333PaletteDir ChartBaseBSB::GetPaletteDir(void) {
4334 // make a pixel cache
4335 PixelCache *pc = new PixelCache(4, 4, BPP);
4336 RGBO r = pc->GetRGBO();
4337 delete pc;
4338
4339 if (r == RGB)
4340 return PaletteFwd;
4341 else
4342 return PaletteRev;
4343}
4344
4345bool ChartBaseBSB::AnalyzeSkew(void) {
4346 double lonmin = 1000;
4347 double lonmax = -1000;
4348 double latmin = 90.;
4349 double latmax = -90.;
4350
4351 int nlonmin, nlonmax;
4352 nlonmin = 0;
4353 nlonmax = 0;
4354
4355 if (0 == nRefpoint) // bad chart georef...
4356 return (1);
4357
4358 for (int n = 0; n < nRefpoint; n++) {
4359 // Longitude
4360 if (pRefTable[n].lonr > lonmax) {
4361 lonmax = pRefTable[n].lonr;
4362 nlonmax = n;
4363 }
4364 if (pRefTable[n].lonr < lonmin) {
4365 lonmin = pRefTable[n].lonr;
4366 nlonmin = n;
4367 }
4368
4369 // Latitude
4370 if (pRefTable[n].latr < latmin) {
4371 latmin = pRefTable[n].latr;
4372 }
4373 if (pRefTable[n].latr > latmax) {
4374 latmax = pRefTable[n].latr;
4375 }
4376 }
4377
4378 // Special case for charts which cross the IDL
4379 if ((lonmin * lonmax) < 0) {
4380 if (pRefTable[nlonmin].xr > pRefTable[nlonmax].xr) {
4381 // walk the reference table and add 360 to any longitude which is < 0
4382 for (int n = 0; n < nRefpoint; n++) {
4383 if (pRefTable[n].lonr < 0.0) pRefTable[n].lonr += 360.;
4384 }
4385
4386 // And recalculate the min/max
4387 lonmin = 1000;
4388 lonmax = -1000;
4389
4390 for (int n = 0; n < nRefpoint; n++) {
4391 // Longitude
4392 if (pRefTable[n].lonr > lonmax) {
4393 lonmax = pRefTable[n].lonr;
4394 nlonmax = n;
4395 }
4396 if (pRefTable[n].lonr < lonmin) {
4397 lonmin = pRefTable[n].lonr;
4398 nlonmin = n;
4399 }
4400
4401 // Latitude
4402 if (pRefTable[n].latr < latmin) {
4403 latmin = pRefTable[n].latr;
4404 }
4405 if (pRefTable[n].latr > latmax) {
4406 latmax = pRefTable[n].latr;
4407 }
4408 }
4409 }
4410 }
4411
4412 // Find the two REF points that are farthest apart
4413 double dist_max = 0.;
4414 int imax = 0;
4415 int jmax = 0;
4416
4417 for (int i = 0; i < nRefpoint; i++) {
4418 for (int j = i + 1; j < nRefpoint; j++) {
4419 double dx = pRefTable[i].xr - pRefTable[j].xr;
4420 double dy = pRefTable[i].yr - pRefTable[j].yr;
4421 double dist = (dx * dx) + (dy * dy);
4422 if (dist > dist_max) {
4423 dist_max = dist;
4424 imax = i;
4425 jmax = j;
4426 }
4427 }
4428 }
4429
4430 double apparent_skew = 0;
4431
4432 if (m_projection == PROJECTION_MERCATOR) {
4433 double easting0, easting1, northing0, northing1;
4434 // Get the Merc projection of the two REF points
4435 toSM_ECC(pRefTable[imax].latr, pRefTable[imax].lonr, m_proj_lat, m_proj_lon,
4436 &easting0, &northing0);
4437 toSM_ECC(pRefTable[jmax].latr, pRefTable[jmax].lonr, m_proj_lat, m_proj_lon,
4438 &easting1, &northing1);
4439
4440 double skew_proj =
4441 atan2((easting1 - easting0), (northing1 - northing0)) * 180. / PI;
4442 double skew_points = atan2((pRefTable[jmax].yr - pRefTable[imax].yr),
4443 (pRefTable[jmax].xr - pRefTable[imax].xr)) *
4444 180. / PI;
4445
4446 apparent_skew = skew_points - skew_proj + 90.;
4447
4448 // normalize to +/- 180.
4449 if (fabs(apparent_skew) > 180.) {
4450 if (apparent_skew < 0.)
4451 apparent_skew += 360.;
4452 else
4453 apparent_skew -= 360.;
4454 }
4455 }
4456
4457 else if (m_projection == PROJECTION_TRANSVERSE_MERCATOR) {
4458 double easting0, easting1, northing0, northing1;
4459 // Get the TMerc projection of the two REF points
4460 toTM(pRefTable[imax].latr, pRefTable[imax].lonr, m_proj_lat, m_proj_lon,
4461 &easting0, &northing0);
4462 toTM(pRefTable[jmax].latr, pRefTable[jmax].lonr, m_proj_lat, m_proj_lon,
4463 &easting1, &northing1);
4464
4465 double skew_proj =
4466 atan2((easting1 - easting0), (northing1 - northing0)) * 180. / PI;
4467 double skew_points = atan2((pRefTable[jmax].yr - pRefTable[imax].yr),
4468 (pRefTable[jmax].xr - pRefTable[imax].xr)) *
4469 180. / PI;
4470
4471 apparent_skew = skew_points - skew_proj + 90.;
4472
4473 // normalize to +/- 180.
4474 if (fabs(apparent_skew) > 180.) {
4475 if (apparent_skew < 0.)
4476 apparent_skew += 360.;
4477 else
4478 apparent_skew -= 360.;
4479 }
4480
4481 if (fabs(apparent_skew - m_Chart_Skew) > 2) { // measured skew OK?
4482 // it may be that the projection longitude is simply wrong.
4483 // Check it.
4484 double dskew = fabs(apparent_skew - m_Chart_Skew);
4485 if ((m_proj_lon < lonmin) || (m_proj_lon > lonmax)) {
4486 // Try a projection longitude that is mid-meridian in the chart.
4487 double tentative_proj_lon = (lonmin + lonmax) / 2.;
4488
4489 toTM(pRefTable[imax].latr, pRefTable[imax].lonr, m_proj_lat,
4490 tentative_proj_lon, &easting0, &northing0);
4491 toTM(pRefTable[jmax].latr, pRefTable[jmax].lonr, m_proj_lat,
4492 tentative_proj_lon, &easting1, &northing1);
4493
4494 skew_proj =
4495 atan2((easting1 - easting0), (northing1 - northing0)) * 180. / PI;
4496 skew_points = atan2((pRefTable[jmax].yr - pRefTable[imax].yr),
4497 (pRefTable[jmax].xr - pRefTable[imax].xr)) *
4498 180. / PI;
4499
4500 apparent_skew = skew_points - skew_proj + 90.;
4501
4502 // normalize to +/- 180.
4503 if (fabs(apparent_skew) > 180.) {
4504 if (apparent_skew < 0.)
4505 apparent_skew += 360.;
4506 else
4507 apparent_skew -= 360.;
4508 }
4509
4510 // Better? If so, adopt the adjusted projection longitude
4511 if (fabs(apparent_skew - m_Chart_Skew) < dskew) {
4512 m_proj_lon = tentative_proj_lon;
4513 }
4514 }
4515 }
4516 } else // For all other projections, assume that skew specified in header is
4517 // correct
4518 apparent_skew = m_Chart_Skew;
4519
4520 if (fabs(apparent_skew - m_Chart_Skew) >
4521 2) { // measured skew is more than 2 degrees
4522 m_Chart_Skew = apparent_skew; // different from stated skew
4523
4524 wxString msg = _T(" Warning: Skew override on chart ");
4525 msg.Append(m_FullPath);
4526 wxString msg1;
4527 msg1.Printf(_T(" is %5g degrees"), apparent_skew);
4528 msg.Append(msg1);
4529
4530 wxLogMessage(msg);
4531
4532 return false;
4533 }
4534
4535 return true;
4536}
4537
4538int ChartBaseBSB::AnalyzeRefpoints(bool b_testSolution) {
4539 int i, n;
4540 double elt, elg;
4541
4542 // Calculate the max/min reference points
4543
4544 float lonmin = 1000;
4545 float lonmax = -1000;
4546 float latmin = 90.;
4547 float latmax = -90.;
4548
4549 int plonmin = 100000;
4550 int plonmax = 0;
4551 int platmin = 100000;
4552 int platmax = 0;
4553 int nlonmin = 0, nlonmax = 0;
4554
4555 if (0 == nRefpoint) // bad chart georef...
4556 return (1);
4557
4558 for (n = 0; n < nRefpoint; n++) {
4559 // Longitude
4560 if (pRefTable[n].lonr > lonmax) {
4561 lonmax = pRefTable[n].lonr;
4562 plonmax = (int)pRefTable[n].xr;
4563 nlonmax = n;
4564 }
4565 if (pRefTable[n].lonr < lonmin) {
4566 lonmin = pRefTable[n].lonr;
4567 plonmin = (int)pRefTable[n].xr;
4568 nlonmin = n;
4569 }
4570
4571 // Latitude
4572 if (pRefTable[n].latr < latmin) {
4573 latmin = pRefTable[n].latr;
4574 platmin = (int)pRefTable[n].yr;
4575 }
4576 if (pRefTable[n].latr > latmax) {
4577 latmax = pRefTable[n].latr;
4578 platmax = (int)pRefTable[n].yr;
4579 }
4580 }
4581
4582 // Special case for charts which cross the IDL
4583 if ((lonmin * lonmax) < 0) {
4584 if (pRefTable[nlonmin].xr > pRefTable[nlonmax].xr) {
4585 // walk the reference table and add 360 to any longitude which is < 0
4586 for (n = 0; n < nRefpoint; n++) {
4587 if (pRefTable[n].lonr < 0.0) pRefTable[n].lonr += 360.;
4588 }
4589
4590 // And recalculate the min/max
4591 lonmin = 1000;
4592 lonmax = -1000;
4593
4594 for (n = 0; n < nRefpoint; n++) {
4595 // Longitude
4596 if (pRefTable[n].lonr > lonmax) {
4597 lonmax = pRefTable[n].lonr;
4598 plonmax = (int)pRefTable[n].xr;
4599 nlonmax = n;
4600 }
4601 if (pRefTable[n].lonr < lonmin) {
4602 lonmin = pRefTable[n].lonr;
4603 plonmin = (int)pRefTable[n].xr;
4604 nlonmin = n;
4605 }
4606
4607 // Latitude
4608 if (pRefTable[n].latr < latmin) {
4609 latmin = pRefTable[n].latr;
4610 platmin = (int)pRefTable[n].yr;
4611 }
4612 if (pRefTable[n].latr > latmax) {
4613 latmax = pRefTable[n].latr;
4614 platmax = (int)pRefTable[n].yr;
4615 }
4616 }
4617 }
4618 }
4619
4620 // Build the Control Point Structure, etc
4621 cPoints.count = nRefpoint;
4622 if (cPoints.status) {
4623 // AnalyzeRefpoints can be called twice
4624 free(cPoints.tx);
4625 free(cPoints.ty);
4626 free(cPoints.lon);
4627 free(cPoints.lat);
4628
4629 free(cPoints.pwx);
4630 free(cPoints.wpx);
4631 free(cPoints.pwy);
4632 free(cPoints.wpy);
4633 }
4634
4635 cPoints.tx = (double *)malloc(nRefpoint * sizeof(double));
4636 cPoints.ty = (double *)malloc(nRefpoint * sizeof(double));
4637 cPoints.lon = (double *)malloc(nRefpoint * sizeof(double));
4638 cPoints.lat = (double *)malloc(nRefpoint * sizeof(double));
4639
4640 cPoints.pwx = (double *)malloc(12 * sizeof(double));
4641 cPoints.wpx = (double *)malloc(12 * sizeof(double));
4642 cPoints.pwy = (double *)malloc(12 * sizeof(double));
4643 cPoints.wpy = (double *)malloc(12 * sizeof(double));
4644 cPoints.status = 1;
4645
4646 // Find the two REF points that are farthest apart
4647 double dist_max = 0.;
4648 int imax = 0;
4649 int jmax = 0;
4650
4651 for (i = 0; i < nRefpoint; i++) {
4652 for (int j = i + 1; j < nRefpoint; j++) {
4653 double dx = pRefTable[i].xr - pRefTable[j].xr;
4654 double dy = pRefTable[i].yr - pRefTable[j].yr;
4655 double dist = (dx * dx) + (dy * dy);
4656 if (dist > dist_max) {
4657 dist_max = dist;
4658 imax = i;
4659 jmax = j;
4660 }
4661 }
4662 }
4663
4664 // Georef solution depends on projection type
4665
4666 if (m_projection == PROJECTION_TRANSVERSE_MERCATOR) {
4667 double easting0, easting1, northing0, northing1;
4668 // Get the TMerc projection of the two REF points
4669 toTM(pRefTable[imax].latr, pRefTable[imax].lonr, m_proj_lat, m_proj_lon,
4670 &easting0, &northing0);
4671 toTM(pRefTable[jmax].latr, pRefTable[jmax].lonr, m_proj_lat, m_proj_lon,
4672 &easting1, &northing1);
4673
4674 // Calculate the scale factor using exact REF point math
4675 double dx2 = (pRefTable[jmax].xr - pRefTable[imax].xr) *
4676 (pRefTable[jmax].xr - pRefTable[imax].xr);
4677 double dy2 = (pRefTable[jmax].yr - pRefTable[imax].yr) *
4678 (pRefTable[jmax].yr - pRefTable[imax].yr);
4679 double dn2 = (northing1 - northing0) * (northing1 - northing0);
4680 double de2 = (easting1 - easting0) * (easting1 - easting0);
4681
4682 m_ppm_avg = sqrt(dx2 + dy2) / sqrt(dn2 + de2);
4683
4684 // Set up and solve polynomial solution for pix<->east/north as projected
4685 // Fill the cpoints structure with pixel points and transformed lat/lon
4686
4687 for (int n = 0; n < nRefpoint; n++) {
4688 double easting, northing;
4689 toTM(pRefTable[n].latr, pRefTable[n].lonr, m_proj_lat, m_proj_lon,
4690 &easting, &northing);
4691
4692 cPoints.tx[n] = pRefTable[n].xr;
4693 cPoints.ty[n] = pRefTable[n].yr;
4694 cPoints.lon[n] = easting;
4695 cPoints.lat[n] = northing;
4696 }
4697
4698 // Helper parameters
4699 cPoints.txmax = plonmax;
4700 cPoints.txmin = plonmin;
4701 cPoints.tymax = platmax;
4702 cPoints.tymin = platmin;
4703 toTM(latmax, lonmax, m_proj_lat, m_proj_lon, &cPoints.lonmax,
4704 &cPoints.latmax);
4705 toTM(latmin, lonmin, m_proj_lat, m_proj_lon, &cPoints.lonmin,
4706 &cPoints.latmin);
4707
4708 Georef_Calculate_Coefficients_Proj(&cPoints);
4709
4710 }
4711
4712 else if (m_projection == PROJECTION_MERCATOR) {
4713 double easting0, easting1, northing0, northing1;
4714 // Get the Merc projection of the two REF points
4715 toSM_ECC(pRefTable[imax].latr, pRefTable[imax].lonr, m_proj_lat, m_proj_lon,
4716 &easting0, &northing0);
4717 toSM_ECC(pRefTable[jmax].latr, pRefTable[jmax].lonr, m_proj_lat, m_proj_lon,
4718 &easting1, &northing1);
4719
4720 // Calculate the scale factor using exact REF point math
4721 // double dx = (pRefTable[jmax].xr - pRefTable[imax].xr);
4722 // double de = (easting1 - easting0);
4723 // m_ppm_avg = fabs(dx / de);
4724
4725 double dx2 = (pRefTable[jmax].xr - pRefTable[imax].xr) *
4726 (pRefTable[jmax].xr - pRefTable[imax].xr);
4727 double dy2 = (pRefTable[jmax].yr - pRefTable[imax].yr) *
4728 (pRefTable[jmax].yr - pRefTable[imax].yr);
4729 double dn2 = (northing1 - northing0) * (northing1 - northing0);
4730 double de2 = (easting1 - easting0) * (easting1 - easting0);
4731
4732 m_ppm_avg = sqrt(dx2 + dy2) / sqrt(dn2 + de2);
4733
4734 // Set up and solve polynomial solution for pix<->east/north as projected
4735 // Fill the cpoints structure with pixel points and transformed lat/lon
4736
4737 for (int n = 0; n < nRefpoint; n++) {
4738 double easting, northing;
4739 toSM_ECC(pRefTable[n].latr, pRefTable[n].lonr, m_proj_lat, m_proj_lon,
4740 &easting, &northing);
4741
4742 cPoints.tx[n] = pRefTable[n].xr;
4743 cPoints.ty[n] = pRefTable[n].yr;
4744 cPoints.lon[n] = easting;
4745 cPoints.lat[n] = northing;
4746 // printf(" x: %g y: %g east: %g north:
4747 // %g\n",pRefTable[n].xr, pRefTable[n].yr, easting,
4748 // northing);
4749 }
4750
4751 // Helper parameters
4752 cPoints.txmax = plonmax;
4753 cPoints.txmin = plonmin;
4754 cPoints.tymax = platmax;
4755 cPoints.tymin = platmin;
4756 toSM_ECC(latmax, lonmax, m_proj_lat, m_proj_lon, &cPoints.lonmax,
4757 &cPoints.latmax);
4758 toSM_ECC(latmin, lonmin, m_proj_lat, m_proj_lon, &cPoints.lonmin,
4759 &cPoints.latmin);
4760
4761 Georef_Calculate_Coefficients_Proj(&cPoints);
4762
4763 // for(int h=0 ; h < 10 ; h++)
4764 // printf("pix to east %d %g\n", h, cPoints.pwx[h]); //
4765 // pix to lon
4766 // for(int h=0 ; h < 10 ; h++)
4767 // printf("east to pix %d %g\n", h, cPoints.wpx[h]); //
4768 // lon to pix
4769
4770 /*
4771 if ((0 != m_Chart_DU ) && (0 != m_Chart_Scale))
4772 {
4773 double m_ppm_avg1 = m_Chart_DU * 39.37 / m_Chart_Scale;
4774 m_ppm_avg1 *= cos(m_proj_lat * PI / 180.); // correct to
4775 chart centroid
4776
4777 printf("BSB chart ppm_avg:%g ppm_avg1:%g\n", m_ppm_avg,
4778 m_ppm_avg1); m_ppm_avg = m_ppm_avg1;
4779 }
4780 */
4781 }
4782
4783 else if (m_projection == PROJECTION_POLYCONIC) {
4784 // This is interesting
4785 // On some BSB V 1.0 Polyconic charts (e.g. 14500_1, 1995), the projection
4786 // parameter Which is taken to be the central meridian of the projection
4787 // is of the wrong sign....
4788
4789 // We check for this case, and make a correction if necessary.....
4790 // Obviously, the projection meridian should be on the chart, i.e. between
4791 // the min and max longitudes....
4792 double proj_meridian = m_proj_lon;
4793
4794 if ((pRefTable[nlonmax].lonr >= -proj_meridian) &&
4795 (-proj_meridian >= pRefTable[nlonmin].lonr))
4796 m_proj_lon = -m_proj_lon;
4797
4798 double easting0, easting1, northing0, northing1;
4799 // Get the Poly projection of the two REF points
4800 toPOLY(pRefTable[imax].latr, pRefTable[imax].lonr, m_proj_lat, m_proj_lon,
4801 &easting0, &northing0);
4802 toPOLY(pRefTable[jmax].latr, pRefTable[jmax].lonr, m_proj_lat, m_proj_lon,
4803 &easting1, &northing1);
4804
4805 // Calculate the scale factor using exact REF point math
4806 double dx2 = (pRefTable[jmax].xr - pRefTable[imax].xr) *
4807 (pRefTable[jmax].xr - pRefTable[imax].xr);
4808 double dy2 = (pRefTable[jmax].yr - pRefTable[imax].yr) *
4809 (pRefTable[jmax].yr - pRefTable[imax].yr);
4810 double dn2 = (northing1 - northing0) * (northing1 - northing0);
4811 double de2 = (easting1 - easting0) * (easting1 - easting0);
4812
4813 m_ppm_avg = sqrt(dx2 + dy2) / sqrt(dn2 + de2);
4814
4815 // Sanity check
4816 // double ref_dist = DistGreatCircle(pRefTable[imax].latr,
4817 // pRefTable[imax].lonr, pRefTable[jmax].latr,
4818 // pRefTable[jmax].lonr); ref_dist *= 1852; //To Meters double
4819 // ref_dist_transform = sqrt(dn2 + de2); //Also meters
4820 // double error = (ref_dist - ref_dist_transform)/ref_dist;
4821
4822 // Set up and solve polynomial solution for pix<->cartesian east/north as
4823 // projected
4824 // Fill the cpoints structure with pixel points and transformed lat/lon
4825
4826 for (int n = 0; n < nRefpoint; n++) {
4827 double easting, northing;
4828 toPOLY(pRefTable[n].latr, pRefTable[n].lonr, m_proj_lat, m_proj_lon,
4829 &easting, &northing);
4830
4831 // Round trip check for debugging....
4832 // double lat, lon;
4833 // fromPOLY(easting, northing, m_proj_lat, m_proj_lon,
4834 // &lat, &lon);
4835
4836 cPoints.tx[n] = pRefTable[n].xr;
4837 cPoints.ty[n] = pRefTable[n].yr;
4838 cPoints.lon[n] = easting;
4839 cPoints.lat[n] = northing;
4840 // printf(" x: %g y: %g east: %g north:
4841 // %g\n",pRefTable[n].xr, pRefTable[n].yr, easting,
4842 // northing);
4843 }
4844
4845 // Helper parameters
4846 cPoints.txmax = plonmax;
4847 cPoints.txmin = plonmin;
4848 cPoints.tymax = platmax;
4849 cPoints.tymin = platmin;
4850 toPOLY(latmax, lonmax, m_proj_lat, m_proj_lon, &cPoints.lonmax,
4851 &cPoints.latmax);
4852 toPOLY(latmin, lonmin, m_proj_lat, m_proj_lon, &cPoints.lonmin,
4853 &cPoints.latmin);
4854
4855 Georef_Calculate_Coefficients_Proj(&cPoints);
4856
4857 // for(int h=0 ; h < 10 ; h++)
4858 // printf("pix to east %d %g\n", h, cPoints.pwx[h]); //
4859 // pix to lon
4860 // for(int h=0 ; h < 10 ; h++)
4861 // printf("east to pix %d %g\n", h, cPoints.wpx[h]); //
4862 // lon to pix
4863
4864 }
4865
4866 // Any other projection had better have embedded coefficients
4867 else if (bHaveEmbeddedGeoref) {
4868 // Use a Mercator Projection to get a rough ppm.
4869 double easting0, easting1, northing0, northing1;
4870 // Get the Merc projection of the two REF points
4871 toSM_ECC(pRefTable[imax].latr, pRefTable[imax].lonr, m_proj_lat, m_proj_lon,
4872 &easting0, &northing0);
4873 toSM_ECC(pRefTable[jmax].latr, pRefTable[jmax].lonr, m_proj_lat, m_proj_lon,
4874 &easting1, &northing1);
4875
4876 // Calculate the scale factor using exact REF point math in x(longitude)
4877 // direction
4878
4879 double dx = (pRefTable[jmax].xr - pRefTable[imax].xr);
4880 double de = (easting1 - easting0);
4881
4882 m_ppm_avg = fabs(dx / de);
4883
4884 m_ExtraInfo =
4885 _T("---<<< Warning: Chart georef accuracy may be poor. >>>---");
4886 }
4887
4888 else
4889 m_ppm_avg = 1.0; // absolute fallback to prevent div-0 errors
4890
4891#if 0
4892 // Alternate Skew verification
4893 ViewPort vps;
4894 vps.clat = pRefTable[0].latr;
4895 vps.clon = pRefTable[0].lonr;
4896 vps.view_scale_ppm = m_ppm_avg;
4897 vps.skew = 0.;
4898 vps.pix_width = 1000;
4899 vps.pix_height = 1000;
4900
4901 int x1, y1, x2, y2;
4902 latlong_to_pix_vp(latmin, (lonmax + lonmin)/2., x1, y1, vps);
4903 latlong_to_pix_vp(latmax, (lonmax + lonmin)/2., x2, y2, vps);
4904
4905 double apparent_skew = (atan2( (y2-y1), (x2-x1) ) * 180./PI) + 90.;
4906 if(apparent_skew < 0.)
4907 apparent_skew += 360;
4908 if(apparent_skew > 360.)
4909 apparent_skew -= 360;
4910
4911 if(fabs( apparent_skew - m_Chart_Skew ) > 2) { // measured skew is more than 2 degress different
4912 m_Chart_Skew = apparent_skew;
4913 }
4914#endif
4915
4916 if (!b_testSolution) return (0);
4917
4918 // Do a last little test using a synthetic ViewPort of nominal size.....
4919 ViewPort vp;
4920 vp.clat = pRefTable[0].latr;
4921 vp.clon = pRefTable[0].lonr;
4922 vp.view_scale_ppm = m_ppm_avg;
4923 vp.skew = 0.;
4924 vp.pix_width = 1000;
4925 vp.pix_height = 1000;
4926 // vp.rv_rect = wxRect(0,0, vp.pix_width, vp.pix_height);
4927 SetVPRasterParms(vp);
4928
4929 double xpl_err_max = 0;
4930 double ypl_err_max = 0;
4931 int px, py;
4932
4933 int pxref, pyref;
4934 pxref = (int)pRefTable[0].xr;
4935 pyref = (int)pRefTable[0].yr;
4936
4937 for (i = 0; i < nRefpoint; i++) {
4938 px = (int)(vp.pix_width / 2 + pRefTable[i].xr) - pxref;
4939 py = (int)(vp.pix_height / 2 + pRefTable[i].yr) - pyref;
4940
4941 vp_pix_to_latlong(vp, px, py, &elt, &elg);
4942
4943 double lat_error = elt - pRefTable[i].latr;
4944 pRefTable[i].ypl_error = lat_error;
4945
4946 double lon_error = elg - pRefTable[i].lonr;
4947
4948 // Longitude errors could be compounded by prior adjustment to ref points
4949 if (fabs(lon_error) > 180.) {
4950 if (lon_error > 0.)
4951 lon_error -= 360.;
4952 else if (lon_error < 0.)
4953 lon_error += 360.;
4954 }
4955 pRefTable[i].xpl_error = lon_error;
4956
4957 if (fabs(pRefTable[i].ypl_error) > fabs(ypl_err_max))
4958 ypl_err_max = pRefTable[i].ypl_error;
4959 if (fabs(pRefTable[i].xpl_error) > fabs(xpl_err_max))
4960 xpl_err_max = pRefTable[i].xpl_error;
4961 }
4962
4963 Chart_Error_Factor = fmax(fabs(xpl_err_max / (lonmax - lonmin)),
4964 fabs(ypl_err_max / (latmax - latmin)));
4965 double chart_error_meters =
4966 fmax(fabs(xpl_err_max * 60. * 1852.), fabs(ypl_err_max * 60. * 1852.));
4967 // calculate a nominal pixel error
4968 // Assume a modern display has about 4000 pixels/meter.
4969 // Assume the chart is to be displayed at nominal printed scale
4970 double chart_error_pixels = chart_error_meters * 4000. / m_Chart_Scale;
4971
4972 // Good enough for navigation?
4973 int max_pixel_error = 4;
4974
4975 if (chart_error_pixels > max_pixel_error) {
4976 wxString msg =
4977 _T(" VP Final Check: Georeference Chart_Error_Factor on chart ");
4978 msg.Append(m_FullPath);
4979 wxString msg1;
4980 msg1.Printf(_T(" is %5g \n nominal pixel error is: %5g"),
4981 Chart_Error_Factor, chart_error_pixels);
4982 msg.Append(msg1);
4983
4984 wxLogMessage(msg);
4985
4986 m_ExtraInfo = _T("---<<< Warning: Chart georef accuracy is poor. >>>---");
4987 }
4988
4989 // Try again with my calculated georef
4990 // This problem was found on NOAA 514_1.KAP. The embedded coefficients are
4991 // just wrong....
4992 if ((chart_error_pixels > max_pixel_error) && bHaveEmbeddedGeoref) {
4993 wxString msg =
4994 _T(" Trying again with internally calculated georef solution ");
4995 wxLogMessage(msg);
4996
4997 bHaveEmbeddedGeoref = false;
4998 SetVPRasterParms(vp);
4999
5000 xpl_err_max = 0;
5001 ypl_err_max = 0;
5002
5003 pxref = (int)pRefTable[0].xr;
5004 pyref = (int)pRefTable[0].yr;
5005
5006 for (i = 0; i < nRefpoint; i++) {
5007 px = (int)(vp.pix_width / 2 + pRefTable[i].xr) - pxref;
5008 py = (int)(vp.pix_height / 2 + pRefTable[i].yr) - pyref;
5009
5010 vp_pix_to_latlong(vp, px, py, &elt, &elg);
5011
5012 double lat_error = elt - pRefTable[i].latr;
5013 pRefTable[i].ypl_error = lat_error;
5014
5015 double lon_error = elg - pRefTable[i].lonr;
5016
5017 // Longitude errors could be compounded by prior adjustment to ref points
5018 if (fabs(lon_error) > 180.) {
5019 if (lon_error > 0.)
5020 lon_error -= 360.;
5021 else if (lon_error < 0.)
5022 lon_error += 360.;
5023 }
5024 pRefTable[i].xpl_error = lon_error;
5025
5026 if (fabs(pRefTable[i].ypl_error) > fabs(ypl_err_max))
5027 ypl_err_max = pRefTable[i].ypl_error;
5028 if (fabs(pRefTable[i].xpl_error) > fabs(xpl_err_max))
5029 xpl_err_max = pRefTable[i].xpl_error;
5030 }
5031
5032 Chart_Error_Factor = fmax(fabs(xpl_err_max / (lonmax - lonmin)),
5033 fabs(ypl_err_max / (latmax - latmin)));
5034
5035 chart_error_meters =
5036 fmax(fabs(xpl_err_max * 60. * 1852.), fabs(ypl_err_max * 60. * 1852.));
5037 chart_error_pixels = chart_error_meters * 4000. / m_Chart_Scale;
5038
5039 // Good enough for navigation?
5040 if (chart_error_pixels > max_pixel_error) {
5041 wxString msg =
5042 _T(" VP Final Check with internal georef: Georeference ")
5043 _T("Chart_Error_Factor on chart ");
5044 msg.Append(m_FullPath);
5045 wxString msg1;
5046 msg1.Printf(_T(" is %5g\n nominal pixel error is: %5g"),
5047 Chart_Error_Factor, chart_error_pixels);
5048 msg.Append(msg1);
5049
5050 wxLogMessage(msg);
5051
5052 m_ExtraInfo =
5053 _T("---<<< Warning: Chart georef accuracy is poor. >>>---");
5054 } else {
5055 wxString msg = _T(" Result: OK, Internal georef solution used.");
5056
5057 wxLogMessage(msg);
5058
5059 m_ExtraInfo = _T("");
5060 }
5061 }
5062
5063 return (0);
5064}
5065
5066double ChartBaseBSB::AdjustLongitude(double lon) {
5067 double lond = (m_LonMin + m_LonMax) / 2 - lon;
5068 if (lond > 180)
5069 return lon + 360;
5070 else if (lond < -180)
5071 return lon - 360;
5072 return lon;
5073}
5074
5075/*
5076 * Extracted from bsb_io.c - implementation of libbsb reading and writing
5077 *
5078 * Copyright (C) 2000 Stuart Cunningham <stuart_hc@users.sourceforge.net>
5079 *
5080 * This library is free software; you can redistribute it and/or
5081 * modify it under the terms of the GNU Lesser General Public
5082 * License as published by the Free Software Foundation; either
5083 * version 2.1 of the License, or (at your option) any later version.
5084 *
5085 * This library is distributed in the hope that it will be useful,
5086 * but WITHOUT ANY WARRANTY; without even the implied warranty of
5087 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
5088 * Lesser General Public License for more details.
5089 *
5090 * You should have received a copy of the GNU Lesser General Public
5091 * License along with this library; if not, write to the Free Software
5092 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
5093 *
5094 *
5095 */
5096
5106static double polytrans(double *coeff, double lon, double lat) {
5107 double ret = coeff[0] + coeff[1] * lon + coeff[2] * lat;
5108 ret += coeff[3] * lon * lon;
5109 ret += coeff[4] * lon * lat;
5110 ret += coeff[5] * lat * lat;
5111 ret += coeff[6] * lon * lon * lon;
5112 ret += coeff[7] * lon * lon * lat;
5113 ret += coeff[8] * lon * lat * lat;
5114 ret += coeff[9] * lat * lat * lat;
5115 // ret += coeff[10]*lat*lat*lat*lat;
5116 // ret += coeff[11]*lat*lat*lat*lat*lat;
5117
5118 // for(int n = 0 ; n < 10 ; n++)
5119 // printf(" %g\n", coeff[n]);
5120
5121 return ret;
5122}
5123
5124#if 0
5136extern int bsb_LLtoXY(BSBImage *p, double lon, double lat, int* x, int* y)
5137{
5138 /* change longitude phase (CPH) */
5139 lon = (lon < 0) ? lon + p->cph : lon - p->cph;
5140 double xd = polytrans( p->wpx, lon, lat );
5141 double yd = polytrans( p->wpy, lon, lat );
5142 *x = (int)(xd + 0.5);
5143 *y = (int)(yd + 0.5);
5144 return 1;
5145}
5146
5158extern int bsb_XYtoLL(BSBImage *p, int x, int y, double* lonout, double* latout)
5159{
5160 double lon = polytrans( p->pwx, x, y );
5161 lon = (lon < 0) ? lon + p->cph : lon - p->cph;
5162 *lonout = lon;
5163 *latout = polytrans( p->pwy, x, y );
5164 return 1;
5165}
5166
5167#endif