OpenCPN Partial API docs
s57chart.cpp
1 /***************************************************************************
2  *
3  * Project: OpenCPN
4  * Purpose: S57 Chart Object
5  * Author: David Register
6  *
7  ***************************************************************************
8  * Copyright (C) 2010 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 // For compilers that support precompilation, includes "wx.h".
27 #include "wx/wxprec.h"
28 
29 #ifndef WX_PRECOMP
30 #include "wx/wx.h"
31 #endif // precompiled headers
32 
33 #include "wx/image.h" // for some reason, needed for msvc???
34 #include "wx/tokenzr.h"
35 #include <wx/textfile.h>
36 #include <wx/filename.h>
37 
38 #include "dychart.h"
39 #include "OCPNPlatform.h"
40 
41 #include "s52s57.h"
42 #include "s52plib.h"
43 
44 #include "s57chart.h"
45 
46 #include "mygeom.h"
47 #include "model/cutil.h"
48 #include "model/georef.h"
49 #include "navutil.h" // for LogMessageOnce
50 #include "model/navutil_base.h"
51 #include "ocpn_pixel.h"
52 #include "ocpndc.h"
53 #include "s52utils.h"
54 #include "model/wx28compat.h"
55 #include "model/chartdata_input_stream.h"
56 
57 #include "gdal/cpl_csv.h"
58 #include "setjmp.h"
59 
60 #include "ogr_s57.h"
61 
62 #include "pluginmanager.h" // for S57 lights overlay
63 
64 #include "Osenc.h"
65 #include "chcanv.h"
66 #include "SencManager.h"
67 #include "gui_lib.h"
68 #include "model/logger.h"
69 #include "Quilt.h"
70 #include "ocpn_frame.h"
71 
72 #ifdef __MSVC__
73 #define _CRTDBG_MAP_ALLOC
74 #include <stdlib.h>
75 #include <crtdbg.h>
76 #define DEBUG_NEW new (_NORMAL_BLOCK, __FILE__, __LINE__)
77 #define new DEBUG_NEW
78 #endif
79 
80 #ifdef ocpnUSE_GL
81 #include "glChartCanvas.h"
82 #include "linmath.h"
83 #endif
84 
85 #include <algorithm> // for std::sort
86 #include <map>
87 
88 #include "ssl/sha1.h"
89 #ifdef ocpnUSE_GL
90  #include "shaders.h"
91 #endif
92 #include "chart_ctx_factory.h"
93 
94 #ifdef __MSVC__
95 #define strncasecmp(x, y, z) _strnicmp(x, y, z)
96 #endif
97 
98 extern bool GetDoubleAttr(S57Obj *obj, const char *AttrName,
99  double &val); // found in s52cnsy
100 
101 void OpenCPN_OGRErrorHandler(
102  CPLErr eErrClass, int nError,
103  const char *pszErrorMsg); // installed GDAL OGR library error handler
104 
105 
106 extern s52plib *ps52plib;
107 extern S57ClassRegistrar *g_poRegistrar;
108 extern wxString g_csv_locn;
109 extern wxString g_SENCPrefix;
110 extern bool g_bGDAL_Debug;
111 extern bool g_bDebugS57;
112 extern MyFrame *gFrame;
113 extern PlugInManager *g_pi_manager;
114 extern bool g_b_overzoom_x;
115 extern bool g_b_EnableVBO;
116 extern OCPNPlatform *g_Platform;
117 extern SENCThreadManager *g_SencThreadManager;
118 
119 int g_SENC_LOD_pixels;
120 
121 static jmp_buf env_ogrf; // the context saved by setjmp();
122 
123 #include <wx/arrimpl.cpp> // Implement an array of S57 Objects
124 WX_DEFINE_OBJARRAY(ArrayOfS57Obj);
125 
126 #include <wx/listimpl.cpp>
127 WX_DEFINE_LIST(ListOfPI_S57Obj);
128 
129 WX_DEFINE_LIST(ListOfObjRazRules); // Implement a list ofObjRazRules
130 
131 #define S57_THUMB_SIZE 200
132 
133 static int s_bInS57; // Exclusion flag to prvent recursion in this class init
134  // call. Init() is not reentrant due to static
135  // wxProgressDialog callback....
136 int s_cnt;
137 
138 static uint64_t hash_fast64(const void *buf, size_t len, uint64_t seed) {
139  const uint64_t m = 0x880355f21e6d1965ULL;
140  const uint64_t *pos = (const uint64_t *)buf;
141  const uint64_t *end = pos + (len >> 3);
142  const unsigned char *pc;
143  uint64_t h = len * m ^ seed;
144  uint64_t v;
145  while (pos != end) {
146  v = *pos++;
147  v ^= v >> 23;
148  v *= 0x2127599bf4325c37ULL;
149  h ^= v ^ (v >> 47);
150  h *= m;
151  }
152  pc = (const unsigned char *)pos;
153  v = 0;
154  switch (len & 7) {
155  case 7:
156  v ^= (uint64_t)pc[6] << 48; // FALL THROUGH
157  case 6:
158  v ^= (uint64_t)pc[5] << 40; // FALL THROUGH
159  case 5:
160  v ^= (uint64_t)pc[4] << 32; // FALL THROUGH
161  case 4:
162  v ^= (uint64_t)pc[3] << 24; // FALL THROUGH
163  case 3:
164  v ^= (uint64_t)pc[2] << 16; // FALL THROUGH
165  case 2:
166  v ^= (uint64_t)pc[1] << 8; // FALL THROUGH
167  case 1:
168  v ^= (uint64_t)pc[0];
169  v ^= v >> 23;
170  v *= 0x2127599bf4325c37ULL;
171  h ^= v ^ (v >> 47);
172  h *= m;
173  }
174 
175  h ^= h >> 23;
176  h *= 0x2127599bf4325c37ULL;
177  h ^= h >> 47;
178  return h;
179 }
180 
181 static unsigned int hash_fast32(const void *buf, size_t len,
182  unsigned int seed) {
183  uint64_t h = hash_fast64(buf, len, seed);
184  /* The following trick converts the 64-bit hashcode to a
185  * residue over a Fermat Number, in which information from
186  * both the higher and lower parts of hashcode shall be
187  * retained. */
188  return h - (h >> 32);
189 }
190 
191 unsigned long connector_key::hash() const {
192  return hash_fast32(k, sizeof k, 0);
193 }
194 
195 
196 //----------------------------------------------------------------------------------
197 // render_canvas_parms Implementation
198 //----------------------------------------------------------------------------------
199 
200 render_canvas_parms::render_canvas_parms() { pix_buff = NULL; }
201 
202 render_canvas_parms::~render_canvas_parms(void) {}
203 
204 static void PrepareForRender(ViewPort *pvp, s52plib *plib) {
205  if(!plib)
206  return;
207 
208  plib->SetVPointCompat(
209  pvp->pix_width,
210  pvp->pix_height,
211  pvp->view_scale_ppm,
212  pvp->rotation,
213  pvp->clat,
214  pvp->clon,
215  pvp->chart_scale,
216  pvp->rv_rect,
217  pvp->GetBBox(),
218  pvp->ref_scale,
219  GetOCPNCanvasWindow()->GetContentScaleFactor()
220  );
221  plib->PrepareForRender();
222 
223 }
224 
225 //----------------------------------------------------------------------------------
226 // s57chart Implementation
227 //----------------------------------------------------------------------------------
228 
229 s57chart::s57chart() {
230  m_ChartType = CHART_TYPE_S57;
231  m_ChartFamily = CHART_FAMILY_VECTOR;
232 
233  for (int i = 0; i < PRIO_NUM; i++)
234  for (int j = 0; j < LUPNAME_NUM; j++) razRules[i][j] = NULL;
235 
236  m_Chart_Scale = 1; // Will be fetched during Init()
237  m_Chart_Skew = 0.0;
238 
239  pDIB = NULL;
240  m_pCloneBM = NULL;
241 
242  // Create ATON arrays, needed by S52PLIB
243  pFloatingATONArray = new wxArrayPtrVoid;
244  pRigidATONArray = new wxArrayPtrVoid;
245 
246  m_tmpup_array = NULL;
247 
248  m_DepthUnits = _T("METERS");
249  m_depth_unit_id = DEPTH_UNIT_METERS;
250 
251  bGLUWarningSent = false;
252 
253  m_pENCDS = NULL;
254 
255  m_nvaldco = 0;
256  m_nvaldco_alloc = 0;
257  m_pvaldco_array = NULL;
258 
259  m_bExtentSet = false;
260 
261  m_pDIBThumbDay = NULL;
262  m_pDIBThumbDim = NULL;
263  m_pDIBThumbOrphan = NULL;
264  m_bbase_file_attr_known = false;
265 
266  m_bLinePrioritySet = false;
267  m_plib_state_hash = 0;
268 
269  m_btex_mem = false;
270 
271  ref_lat = 0.0;
272  ref_lon = 0.0;
273 
274  m_b2pointLUPS = false;
275  m_b2lineLUPS = false;
276 
277  m_next_safe_cnt = 1e6;
278  m_LineVBO_name = -1;
279  m_line_vertex_buffer = 0;
280  m_this_chart_context = 0;
281  m_Chart_Skew = 0;
282  m_vbo_byte_length = 0;
283  m_SENCthreadStatus = THREAD_INACTIVE;
284  bReadyToRender = false;
285  m_RAZBuilt = false;
286  m_disableBackgroundSENC = false;
287 }
288 
289 s57chart::~s57chart() {
290  FreeObjectsAndRules();
291 
292  delete pDIB;
293 
294  delete m_pCloneBM;
295  // delete pFullPath;
296 
297  delete pFloatingATONArray;
298  delete pRigidATONArray;
299 
300  delete m_pENCDS;
301 
302  free(m_pvaldco_array);
303 
304  free(m_line_vertex_buffer);
305 
306  delete m_pDIBThumbOrphan;
307 
308  for (unsigned i = 0; i < m_pcs_vector.size(); i++) delete m_pcs_vector.at(i);
309 
310  for (unsigned i = 0; i < m_pve_vector.size(); i++) delete m_pve_vector.at(i);
311 
312  m_pcs_vector.clear();
313  m_pve_vector.clear();
314 
315  for (const auto &it : m_ve_hash) {
316  VE_Element *pedge = it.second;
317  if (pedge) {
318  free(pedge->pPoints);
319  delete pedge;
320  }
321  }
322  m_ve_hash.clear();
323 
324  for (const auto &it : m_vc_hash) {
325  VC_Element *pcs = it.second;
326  if (pcs) {
327  free(pcs->pPoint);
328  delete pcs;
329  }
330  }
331  m_vc_hash.clear();
332 
333 #ifdef ocpnUSE_GL
334  if ((m_LineVBO_name > 0))
335  glDeleteBuffers(1, (GLuint *)&m_LineVBO_name);
336 #endif
337  free(m_this_chart_context);
338 
339  if (m_TempFilePath.Length() && (m_FullPath != m_TempFilePath)) {
340  if (::wxFileExists(m_TempFilePath)) wxRemoveFile(m_TempFilePath);
341  }
342 
343  // Check the SENCThreadManager to see if this chart is queued or active
344  if (g_SencThreadManager) {
345  if (g_SencThreadManager->IsChartInTicketlist(this)) {
346  g_SencThreadManager->SetChartPointer(this, NULL);
347  }
348  }
349 }
350 
351 void s57chart::GetValidCanvasRegion(const ViewPort &VPoint,
352  OCPNRegion *pValidRegion) {
353  int rxl, rxr;
354  int ryb, ryt;
355  double easting, northing;
356  double epix, npix;
357 
358  toSM(m_FullExtent.SLAT, m_FullExtent.WLON, VPoint.clat, VPoint.clon, &easting,
359  &northing);
360  epix = easting * VPoint.view_scale_ppm;
361  npix = northing * VPoint.view_scale_ppm;
362 
363  rxl = (int)round((VPoint.pix_width / 2) + epix);
364  ryb = (int)round((VPoint.pix_height / 2) - npix);
365 
366  toSM(m_FullExtent.NLAT, m_FullExtent.ELON, VPoint.clat, VPoint.clon, &easting,
367  &northing);
368  epix = easting * VPoint.view_scale_ppm;
369  npix = northing * VPoint.view_scale_ppm;
370 
371  rxr = (int)round((VPoint.pix_width / 2) + epix);
372  ryt = (int)round((VPoint.pix_height / 2) - npix);
373 
374  pValidRegion->Clear();
375  pValidRegion->Union(rxl, ryt, rxr - rxl, ryb - ryt);
376 }
377 
378 LLRegion s57chart::GetValidRegion() {
379  double ll[8] = {m_FullExtent.SLAT, m_FullExtent.WLON, m_FullExtent.SLAT,
380  m_FullExtent.ELON, m_FullExtent.NLAT, m_FullExtent.ELON,
381  m_FullExtent.NLAT, m_FullExtent.WLON};
382  return LLRegion(4, ll);
383 }
384 
385 void s57chart::SetColorScheme(ColorScheme cs, bool bApplyImmediate) {
386  if (!ps52plib) return;
387  // Here we convert (subjectively) the Global ColorScheme
388  // to an appropriate S52 Color scheme, by name.
389 
390  switch (cs) {
391  case GLOBAL_COLOR_SCHEME_DAY:
392  ps52plib->SetPLIBColorScheme("DAY", ChartCtxFactory());
393  break;
394  case GLOBAL_COLOR_SCHEME_DUSK:
395  ps52plib->SetPLIBColorScheme("DUSK", ChartCtxFactory());
396  break;
397  case GLOBAL_COLOR_SCHEME_NIGHT:
398  ps52plib->SetPLIBColorScheme("NIGHT", ChartCtxFactory());
399  break;
400  default:
401  ps52plib->SetPLIBColorScheme("DAY", ChartCtxFactory());
402  break;
403  }
404 
405  m_global_color_scheme = cs;
406 
407  if (bApplyImmediate) {
408  delete pDIB; // Toss any current cache
409  pDIB = NULL;
410  }
411 
412  // Clear out any cached bitmaps in the text cache
413  ClearRenderedTextCache();
414 
415  // Setup the proper thumbnail bitmap pointer
416  ChangeThumbColor(cs);
417 }
418 
419 void s57chart::ChangeThumbColor(ColorScheme cs) {
420  if (0 == m_pDIBThumbDay) return;
421 
422  switch (cs) {
423  default:
424  case GLOBAL_COLOR_SCHEME_DAY:
425  pThumbData->pDIBThumb = m_pDIBThumbDay;
426  m_pDIBThumbOrphan = m_pDIBThumbDim;
427  break;
428  case GLOBAL_COLOR_SCHEME_DUSK:
429  case GLOBAL_COLOR_SCHEME_NIGHT: {
430  if (NULL == m_pDIBThumbDim) {
431  wxImage img = m_pDIBThumbDay->ConvertToImage();
432 
433 #if wxCHECK_VERSION(2, 8, 0)
434  wxImage gimg = img.ConvertToGreyscale(
435  0.1, 0.1, 0.1); // factors are completely subjective
436 #else
437  wxImage gimg = img;
438 #endif
439 
440  //#ifdef ocpnUSE_ocpnBitmap
441  // ocpnBitmap *pBMP = new ocpnBitmap(gimg,
442  // m_pDIBThumbDay->GetDepth());
443  //#else
444  wxBitmap *pBMP = new wxBitmap(gimg);
445  //#endif
446  m_pDIBThumbDim = pBMP;
447  m_pDIBThumbOrphan = m_pDIBThumbDay;
448  }
449 
450  pThumbData->pDIBThumb = m_pDIBThumbDim;
451  break;
452  }
453  }
454 }
455 
456 bool s57chart::GetChartExtent(Extent *pext) {
457  if (m_bExtentSet) {
458  *pext = m_FullExtent;
459  return true;
460  } else
461  return false;
462 }
463 
464 static void free_mps(mps_container *mps) {
465  if (mps == 0) return;
466  if (ps52plib && mps->cs_rules) {
467  for (unsigned int i = 0; i < mps->cs_rules->GetCount(); i++) {
468  Rules *rule_chain_top = mps->cs_rules->Item(i);
469  ps52plib->DestroyRulesChain(rule_chain_top);
470  }
471  delete mps->cs_rules;
472  }
473  free(mps);
474 }
475 
476 void s57chart::FreeObjectsAndRules() {
477  // Delete the created ObjRazRules, including the S57Objs
478  // and any child lists
479  // The LUPs of base elements are deleted elsewhere ( void
480  // s52plib::DestroyLUPArray ( wxArrayOfLUPrec *pLUPArray )) But we need
481  // to manually destroy any LUPS related to children
482 
483  ObjRazRules *top;
484  ObjRazRules *nxx;
485  for (int i = 0; i < PRIO_NUM; ++i) {
486  for (int j = 0; j < LUPNAME_NUM; j++) {
487  top = razRules[i][j];
488  while (top != NULL) {
489  top->obj->nRef--;
490  if (0 == top->obj->nRef) delete top->obj;
491 
492  if (top->child) {
493  ObjRazRules *ctop = top->child;
494  while (ctop) {
495  delete ctop->obj;
496 
497  if (ps52plib) ps52plib->DestroyLUP(ctop->LUP);
498 
499  ObjRazRules *cnxx = ctop->next;
500  delete ctop;
501  ctop = cnxx;
502  }
503  }
504  free_mps(top->mps);
505 
506  nxx = top->next;
507  free(top);
508  top = nxx;
509  }
510  }
511  }
512 }
513 
514 void s57chart::ClearRenderedTextCache() {
515  ObjRazRules *top;
516  for (int i = 0; i < PRIO_NUM; ++i) {
517  for (int j = 0; j < LUPNAME_NUM; j++) {
518  top = razRules[i][j];
519  while (top != NULL) {
520  if (top->obj->bFText_Added) {
521  top->obj->bFText_Added = false;
522  delete top->obj->FText;
523  top->obj->FText = NULL;
524  }
525 
526  if (top->child) {
527  ObjRazRules *ctop = top->child;
528  while (ctop) {
529  if (ctop->obj->bFText_Added) {
530  ctop->obj->bFText_Added = false;
531  delete ctop->obj->FText;
532  ctop->obj->FText = NULL;
533  }
534  ctop = ctop->next;
535  }
536  }
537 
538  top = top->next;
539  }
540  }
541  }
542 }
543 
544 double s57chart::GetNormalScaleMin(double canvas_scale_factor,
545  bool b_allow_overzoom) {
546  // if( b_allow_overzoom )
547  return m_Chart_Scale * 0.125;
548  // else
549  // return m_Chart_Scale * 0.25;
550 }
551 double s57chart::GetNormalScaleMax(double canvas_scale_factor,
552  int canvas_width) {
553  return m_Chart_Scale * 4.0;
554 }
555 
556 //-----------------------------------------------------------------------
557 // Pixel to Lat/Long Conversion helpers
558 //-----------------------------------------------------------------------
559 
560 void s57chart::GetPointPix(ObjRazRules *rzRules, float north, float east,
561  wxPoint *r) {
562  r->x = roundint(((east - m_easting_vp_center) * m_view_scale_ppm) +
563  m_pixx_vp_center);
564  r->y = roundint(m_pixy_vp_center -
565  ((north - m_northing_vp_center) * m_view_scale_ppm));
566 }
567 
568 void s57chart::GetPointPix(ObjRazRules *rzRules, wxPoint2DDouble *en,
569  wxPoint *r, int nPoints) {
570  for (int i = 0; i < nPoints; i++) {
571  r[i].x = roundint(((en[i].m_x - m_easting_vp_center) * m_view_scale_ppm) +
572  m_pixx_vp_center);
573  r[i].y = roundint(m_pixy_vp_center -
574  ((en[i].m_y - m_northing_vp_center) * m_view_scale_ppm));
575  }
576 }
577 
578 void s57chart::GetPixPoint(int pixx, int pixy, double *plat, double *plon,
579  ViewPort *vpt) {
580  if (vpt->m_projection_type != PROJECTION_MERCATOR)
581  printf("s57chart unhandled projection\n");
582 
583  // Use Mercator estimator
584  int dx = pixx - (vpt->pix_width / 2);
585  int dy = (vpt->pix_height / 2) - pixy;
586 
587  double xp = (dx * cos(vpt->skew)) - (dy * sin(vpt->skew));
588  double yp = (dy * cos(vpt->skew)) + (dx * sin(vpt->skew));
589 
590  double d_east = xp / vpt->view_scale_ppm;
591  double d_north = yp / vpt->view_scale_ppm;
592 
593  double slat, slon;
594  fromSM(d_east, d_north, vpt->clat, vpt->clon, &slat, &slon);
595 
596  *plat = slat;
597  *plon = slon;
598 }
599 
600 //-----------------------------------------------------------------------
601 // Calculate and Set ViewPoint Constants
602 //-----------------------------------------------------------------------
603 
604 void s57chart::SetVPParms(const ViewPort &vpt) {
605  // Set up local SM rendering constants
606  m_pixx_vp_center = vpt.pix_width / 2.0;
607  m_pixy_vp_center = vpt.pix_height / 2.0;
608  m_view_scale_ppm = vpt.view_scale_ppm;
609 
610  toSM(vpt.clat, vpt.clon, ref_lat, ref_lon, &m_easting_vp_center,
611  &m_northing_vp_center);
612 
613  vp_transform.easting_vp_center = m_easting_vp_center;
614  vp_transform.northing_vp_center = m_northing_vp_center;
615 }
616 
617 bool s57chart::AdjustVP(ViewPort &vp_last, ViewPort &vp_proposed) {
618  if (IsCacheValid()) {
619  // If this viewpoint is same scale as last...
620  if (vp_last.view_scale_ppm == vp_proposed.view_scale_ppm) {
621  double prev_easting_c, prev_northing_c;
622  toSM(vp_last.clat, vp_last.clon, ref_lat, ref_lon, &prev_easting_c,
623  &prev_northing_c);
624 
625  double easting_c, northing_c;
626  toSM(vp_proposed.clat, vp_proposed.clon, ref_lat, ref_lon, &easting_c,
627  &northing_c);
628 
629  // then require this viewport to be exact integral pixel difference from
630  // last adjusting clat/clat and SM accordingly
631 
632  double delta_pix_x =
633  (easting_c - prev_easting_c) * vp_proposed.view_scale_ppm;
634  int dpix_x = (int)round(delta_pix_x);
635  double dpx = dpix_x;
636 
637  double delta_pix_y =
638  (northing_c - prev_northing_c) * vp_proposed.view_scale_ppm;
639  int dpix_y = (int)round(delta_pix_y);
640  double dpy = dpix_y;
641 
642  double c_east_d = (dpx / vp_proposed.view_scale_ppm) + prev_easting_c;
643  double c_north_d = (dpy / vp_proposed.view_scale_ppm) + prev_northing_c;
644 
645  double xlat, xlon;
646  fromSM(c_east_d, c_north_d, ref_lat, ref_lon, &xlat, &xlon);
647 
648  vp_proposed.clon = xlon;
649  vp_proposed.clat = xlat;
650 
651  return true;
652  }
653  }
654 
655  return false;
656 }
657 
658 /*
659  bool s57chart::IsRenderDelta(ViewPort &vp_last, ViewPort &vp_proposed)
660  {
661  double last_center_easting, last_center_northing, this_center_easting,
662  this_center_northing; toSM ( vp_proposed.clat, vp_proposed.clon, ref_lat,
663  ref_lon, &this_center_easting, &this_center_northing ); toSM ( vp_last.clat,
664  vp_last.clon, ref_lat, ref_lon, &last_center_easting, &last_center_northing
665  );
666 
667  int dx = (int)round((last_center_easting - this_center_easting) *
668  vp_proposed.view_scale_ppm); int dy = (int)round((last_center_northing -
669  this_center_northing) * vp_proposed.view_scale_ppm);
670 
671  return((dx != 0) || (dy != 0) || !(IsCacheValid()) ||
672  (vp_proposed.view_scale_ppm != vp_last.view_scale_ppm));
673  }
674  */
675 
676 void s57chart::LoadThumb() {
677  wxFileName fn(m_FullPath);
678  wxString SENCdir = g_SENCPrefix;
679 
680  if (SENCdir.Last() != fn.GetPathSeparator())
681  SENCdir.Append(fn.GetPathSeparator());
682 
683  wxFileName tsfn(SENCdir);
684  tsfn.SetFullName(fn.GetFullName());
685 
686  wxFileName ThumbFileNameLook(tsfn);
687  ThumbFileNameLook.SetExt(_T("BMP"));
688 
689  wxBitmap *pBMP;
690  if (ThumbFileNameLook.FileExists()) {
691  pBMP = new wxBitmap;
692 
693  pBMP->LoadFile(ThumbFileNameLook.GetFullPath(), wxBITMAP_TYPE_BMP);
694  m_pDIBThumbDay = pBMP;
695  m_pDIBThumbOrphan = 0;
696  m_pDIBThumbDim = 0;
697  }
698 }
699 
700 ThumbData *s57chart::GetThumbData(int tnx, int tny, float lat, float lon) {
701  // Plot the passed lat/lon at the thumbnail bitmap scale
702  // Using simple linear algorithm.
703  if (pThumbData->pDIBThumb == 0) {
704  LoadThumb();
705  ChangeThumbColor(m_global_color_scheme);
706  }
707 
708  UpdateThumbData(lat, lon);
709 
710  return pThumbData;
711 }
712 
713 bool s57chart::UpdateThumbData(double lat, double lon) {
714  // Plot the passed lat/lon at the thumbnail bitmap scale
715  // Using simple linear algorithm.
716  int test_x, test_y;
717  if (pThumbData->pDIBThumb) {
718  double lat_top = m_FullExtent.NLAT;
719  double lat_bot = m_FullExtent.SLAT;
720  double lon_left = m_FullExtent.WLON;
721  double lon_right = m_FullExtent.ELON;
722 
723  // Build the scale factors just as the thumbnail was built
724  double ext_max = fmax((lat_top - lat_bot), (lon_right - lon_left));
725 
726  double thumb_view_scale_ppm = (S57_THUMB_SIZE / ext_max) / (1852 * 60);
727  double east, north;
728  toSM(lat, lon, (lat_top + lat_bot) / 2., (lon_left + lon_right) / 2., &east,
729  &north);
730 
731  test_x = pThumbData->pDIBThumb->GetWidth() / 2 +
732  (int)(east * thumb_view_scale_ppm);
733  test_y = pThumbData->pDIBThumb->GetHeight() / 2 -
734  (int)(north * thumb_view_scale_ppm);
735 
736  } else {
737  test_x = 0;
738  test_y = 0;
739  }
740 
741  if ((test_x != pThumbData->ShipX) || (test_y != pThumbData->ShipY)) {
742  pThumbData->ShipX = test_x;
743  pThumbData->ShipY = test_y;
744  return TRUE;
745  } else
746  return FALSE;
747 }
748 
749 void s57chart::SetFullExtent(Extent &ext) {
750  m_FullExtent.NLAT = ext.NLAT;
751  m_FullExtent.SLAT = ext.SLAT;
752  m_FullExtent.WLON = ext.WLON;
753  m_FullExtent.ELON = ext.ELON;
754 
755  m_bExtentSet = true;
756 }
757 
758 void s57chart::ForceEdgePriorityEvaluate(void) { m_bLinePrioritySet = false; }
759 
760 void s57chart::SetLinePriorities(void) {
761  if (!ps52plib) return;
762 
763  // If necessary.....
764  // Establish line feature rendering priorities
765 
766  if (!m_bLinePrioritySet) {
767  ObjRazRules *top;
768  ObjRazRules *crnt;
769 
770  for (int i = 0; i < PRIO_NUM; ++i) {
771  top = razRules[i][2]; // LINES
772  while (top != NULL) {
773  ObjRazRules *crnt = top;
774  top = top->next;
775  ps52plib->SetLineFeaturePriority(crnt, i);
776  }
777 
778  // In the interest of speed, choose only the one necessary area
779  // boundary style index
780  int j;
781  if (ps52plib->m_nBoundaryStyle == SYMBOLIZED_BOUNDARIES)
782  j = 4;
783  else
784  j = 3;
785 
786  top = razRules[i][j];
787  while (top != NULL) {
788  crnt = top;
789  top = top->next; // next object
790  ps52plib->SetLineFeaturePriority(crnt, i);
791  }
792  }
793 
794  // Traverse the entire object list again, setting the priority of each
795  // line_segment_element to the maximum priority seen for that segment
796  for (int i = 0; i < PRIO_NUM; ++i) {
797  for (int j = 0; j < LUPNAME_NUM; j++) {
798  ObjRazRules *top = razRules[i][j];
799  while (top != NULL) {
800  S57Obj *obj = top->obj;
801 
802  VE_Element *pedge;
803  connector_segment *pcs;
804  line_segment_element *list = obj->m_ls_list;
805  while (list) {
806  switch (list->ls_type) {
807  case TYPE_EE:
808  case TYPE_EE_REV:
809  pedge = list->pedge; // (VE_Element *)list->private0;
810  if (pedge) list->priority = pedge->max_priority;
811  break;
812 
813  default:
814  pcs = list->pcs; //(connector_segment *)list->private0;
815  if (pcs) list->priority = pcs->max_priority_cs;
816  break;
817  }
818 
819  list = list->next;
820  }
821 
822  top = top->next;
823  }
824  }
825  }
826  }
827 
828  // Mark the priority as set.
829  // Generally only reset by Options Dialog post processing
830  m_bLinePrioritySet = true;
831 }
832 
833 #if 0
834 void s57chart::SetLinePriorities( void )
835 {
836  if( !ps52plib ) return;
837 
838  // If necessary.....
839  // Establish line feature rendering priorities
840 
841  if( !m_bLinePrioritySet ) {
842  ObjRazRules *top;
843  ObjRazRules *crnt;
844 
845  for( int i = 0; i < PRIO_NUM; ++i ) {
846 
847  top = razRules[i][2]; //LINES
848  while( top != NULL ) {
849  ObjRazRules *crnt = top;
850  top = top->next;
851  ps52plib->SetLineFeaturePriority( crnt, i );
852  }
853 
854  // In the interest of speed, choose only the one necessary area boundary style index
855  int j;
856  if( ps52plib->m_nBoundaryStyle == SYMBOLIZED_BOUNDARIES )
857  j = 4;
858  else
859  j = 3;
860 
861  top = razRules[i][j];
862  while( top != NULL ) {
863  crnt = top;
864  top = top->next; // next object
865  ps52plib->SetLineFeaturePriority( crnt, i );
866  }
867 
868  }
869 
870 
871  // Traverse the entire object list again, setting the priority of each line_segment_element
872  // to the maximum priority seen for that segment
873  for( int i = 0; i < PRIO_NUM; ++i ) {
874  for( int j = 0; j < LUPNAME_NUM; j++ ) {
875  ObjRazRules *top = razRules[i][j];
876  while( top != NULL ) {
877  S57Obj *obj = top->obj;
878 
879  VE_Element *pedge;
880  connector_segment *pcs;
881  line_segment_element *list = obj->m_ls_list;
882  while( list ){
883  switch (list->type){
884  case TYPE_EE:
885 
886  pedge = (VE_Element *)list->private0;
887  if(pedge)
888  list->priority = pedge->max_priority;
889  break;
890 
891  default:
892  pcs = (connector_segment *)list->private0;
893  if(pcs)
894  list->priority = pcs->max_priority;
895  break;
896  }
897 
898  list = list->next;
899  }
900 
901  top = top->next;
902  }
903  }
904  }
905  }
906 
907  // Mark the priority as set.
908  // Generally only reset by Options Dialog post processing
909  m_bLinePrioritySet = true;
910 }
911 #endif
912 
913 int s57chart::GetLineFeaturePointArray(S57Obj *obj, void **ret_array) {
914  // Walk the line segment list once to get the required array size
915 
916  int nPoints = 0;
917  line_segment_element *ls_list = obj->m_ls_list;
918  while (ls_list) {
919  if ((ls_list->ls_type == TYPE_EE) || (ls_list->ls_type == TYPE_EE_REV))
920  nPoints += ls_list->pedge->nCount;
921  else
922  nPoints += 2;
923  ls_list = ls_list->next;
924  }
925 
926  if (!nPoints) {
927  *ret_array = 0;
928  return 0;
929  }
930 
931  // Allocate the buffer
932  float *br = (float *)malloc(nPoints * 2 * sizeof(float));
933  *ret_array = br;
934 
935  // populate the buffer
936  unsigned char *source_buffer = (unsigned char *)GetLineVertexBuffer();
937  ls_list = obj->m_ls_list;
938  while (ls_list) {
939  size_t vbo_offset = 0;
940  size_t count = 0;
941  if ((ls_list->ls_type == TYPE_EE) || (ls_list->ls_type == TYPE_EE_REV)) {
942  vbo_offset = ls_list->pedge->vbo_offset;
943  count = ls_list->pedge->nCount;
944  } else {
945  vbo_offset = ls_list->pcs->vbo_offset;
946  count = 2;
947  }
948 
949  memcpy(br, source_buffer + vbo_offset, count * 2 * sizeof(float));
950  br += count * 2;
951  ls_list = ls_list->next;
952  }
953 
954  return nPoints;
955 }
956 
957 #if 0
958 int s57chart::GetLineFeaturePointArray(S57Obj *obj, void **ret_array)
959 {
960  // Walk the line segment list once to get the required array size
961 
962  int nPoints = 0;
963  line_segment_element *ls_list = obj->m_ls_list;
964  while( ls_list){
965  nPoints += ls_list->n_points;
966  ls_list = ls_list->next;
967  }
968 
969  if(!nPoints){
970  *ret_array = 0;
971  return 0;
972  }
973 
974  // Allocate the buffer
975  float *br = (float *)malloc(nPoints * 2 * sizeof(float));
976  *ret_array = br;
977 
978  // populate the buffer
979  unsigned char *source_buffer = (unsigned char *)GetLineVertexBuffer();
980  ls_list = obj->m_ls_list;
981  while( ls_list){
982  memcpy(br, source_buffer + ls_list->vbo_offset, ls_list->n_points * 2 * sizeof(float));
983  br += ls_list->n_points * 2;
984  ls_list = ls_list->next;
985  }
986 
987  return nPoints;
988 
989 }
990 #endif
991 
992 typedef struct segment_pair {
993  float e0, n0, e1, n1;
994 } _segment_pair;
995 
996 void s57chart::AssembleLineGeometry(void) {
997  // Walk the hash tables to get the required buffer size
998 
999  // Start with the edge hash table
1000  size_t nPoints = 0;
1001  for (const auto &it : m_ve_hash) {
1002  VE_Element *pedge = it.second;
1003  if (pedge) {
1004  nPoints += pedge->nCount;
1005  }
1006  }
1007 
1008  // printf("time0 %f\n", sw.GetTime());
1009 
1010  std::map<long long, connector_segment *> ce_connector_hash;
1011  std::map<long long, connector_segment *> ec_connector_hash;
1012  std::map<long long, connector_segment *> cc_connector_hash;
1013 
1014  std::map<long long, connector_segment *>::iterator csit;
1015 
1016  int ndelta = 0;
1017 
1018  // Define a vector to temporarily hold the geometry for the created pcs
1019  // elements
1020 
1021  std::vector<segment_pair> connector_segment_vector;
1022  size_t seg_pair_index = 0;
1023 
1024  // Get the end node connected segments. To do this, we
1025  // walk the Feature array and process each feature that potentially has a
1026  // LINE type element
1027  for (int i = 0; i < PRIO_NUM; ++i) {
1028  for (int j = 0; j < LUPNAME_NUM; j++) {
1029  ObjRazRules *top = razRules[i][j];
1030  while (top != NULL) {
1031  S57Obj *obj = top->obj;
1032 
1033  if ((!obj->m_ls_list) &&
1034  (obj->m_n_lsindex)) // object has not been processed yet
1035  {
1036  line_segment_element list_top;
1037  list_top.next = 0;
1038 
1039  line_segment_element *le_current = &list_top;
1040 
1041  for (int iseg = 0; iseg < obj->m_n_lsindex; iseg++) {
1042  if (!obj->m_lsindex_array) continue;
1043 
1044  int seg_index = iseg * 3;
1045  int *index_run = &obj->m_lsindex_array[seg_index];
1046 
1047  // Get first connected node
1048  unsigned int inode = *index_run++;
1049 
1050  // Get the edge
1051  bool edge_dir = true;
1052  int venode = *index_run++;
1053  if (venode < 0) {
1054  venode = -venode;
1055  edge_dir = false;
1056  }
1057 
1058  VE_Element *pedge = 0;
1059  if (venode) {
1060  if (m_ve_hash.find(venode) != m_ve_hash.end())
1061  pedge = m_ve_hash[venode];
1062  }
1063 
1064  // Get end connected node
1065  unsigned int enode = *index_run++;
1066 
1067  // Get first connected node
1068  VC_Element *ipnode = 0;
1069  ipnode = m_vc_hash[inode];
1070 
1071  // Get end connected node
1072  VC_Element *epnode = 0;
1073  epnode = m_vc_hash[enode];
1074 
1075  if (ipnode) {
1076  if (pedge && pedge->nCount) {
1077  // The initial node exists and connects to the start of an
1078  // edge
1079 
1080  long long key = ((unsigned long long)inode << 32) + venode;
1081 
1082  connector_segment *pcs = NULL;
1083  csit = ce_connector_hash.find(key);
1084  if (csit == ce_connector_hash.end()) {
1085  ndelta += 2;
1086  pcs = new connector_segment;
1087  ce_connector_hash[key] = pcs;
1088 
1089  // capture and store geometry
1090  segment_pair pair;
1091  float *ppt = ipnode->pPoint;
1092  pair.e0 = *ppt++;
1093  pair.n0 = *ppt;
1094 
1095  if (edge_dir) {
1096  pair.e1 = pedge->pPoints[0];
1097  pair.n1 = pedge->pPoints[1];
1098  } else {
1099  int last_point_index = (pedge->nCount - 1) * 2;
1100  pair.e1 = pedge->pPoints[last_point_index];
1101  pair.n1 = pedge->pPoints[last_point_index + 1];
1102  }
1103 
1104  connector_segment_vector.push_back(pair);
1105  pcs->vbo_offset = seg_pair_index; // use temporarily
1106  seg_pair_index++;
1107 
1108  // calculate the centroid of this connector segment, used for
1109  // viz testing
1110  double lat, lon;
1111  fromSM_Plugin((pair.e0 + pair.e1) / 2,
1112  (pair.n0 + pair.n1) / 2, ref_lat, ref_lon, &lat,
1113  &lon);
1114  pcs->cs_lat_avg = lat;
1115  pcs->cs_lon_avg = lon;
1116 
1117  } else
1118  pcs = csit->second;
1119 
1120  line_segment_element *pls = new line_segment_element;
1121  pls->next = 0;
1122  // pls->n_points = 2;
1123  pls->priority = 0;
1124  pls->pcs = pcs;
1125  pls->ls_type = TYPE_CE;
1126 
1127  le_current->next = pls; // hook it up
1128  le_current = pls;
1129  }
1130  }
1131 
1132  if (pedge && pedge->nCount) {
1133  line_segment_element *pls = new line_segment_element;
1134  pls->next = 0;
1135  // pls->n_points = pedge->nCount;
1136  pls->priority = 0;
1137  pls->pedge = pedge;
1138  pls->ls_type = TYPE_EE;
1139  if (!edge_dir) pls->ls_type = TYPE_EE_REV;
1140 
1141  le_current->next = pls; // hook it up
1142  le_current = pls;
1143 
1144  } // pedge
1145 
1146  // end node
1147  if (epnode) {
1148  if (ipnode) {
1149  if (pedge && pedge->nCount) {
1150  long long key = ((unsigned long long)venode << 32) + enode;
1151 
1152  connector_segment *pcs = NULL;
1153  csit = ec_connector_hash.find(key);
1154  if (csit == ec_connector_hash.end()) {
1155  ndelta += 2;
1156  pcs = new connector_segment;
1157  ec_connector_hash[key] = pcs;
1158 
1159  // capture and store geometry
1160  segment_pair pair;
1161 
1162  if (!edge_dir) {
1163  pair.e0 = pedge->pPoints[0];
1164  pair.n0 = pedge->pPoints[1];
1165  } else {
1166  int last_point_index = (pedge->nCount - 1) * 2;
1167  pair.e0 = pedge->pPoints[last_point_index];
1168  pair.n0 = pedge->pPoints[last_point_index + 1];
1169  }
1170 
1171  float *ppt = epnode->pPoint;
1172  pair.e1 = *ppt++;
1173  pair.n1 = *ppt;
1174 
1175  connector_segment_vector.push_back(pair);
1176  pcs->vbo_offset = seg_pair_index; // use temporarily
1177  seg_pair_index++;
1178 
1179  // calculate the centroid of this connector segment, used
1180  // for viz testing
1181  double lat, lon;
1182  fromSM_Plugin((pair.e0 + pair.e1) / 2,
1183  (pair.n0 + pair.n1) / 2, ref_lat, ref_lon,
1184  &lat, &lon);
1185  pcs->cs_lat_avg = lat;
1186  pcs->cs_lon_avg = lon;
1187 
1188  } else
1189  pcs = csit->second;
1190 
1191  line_segment_element *pls = new line_segment_element;
1192  pls->next = 0;
1193  pls->priority = 0;
1194  pls->pcs = pcs;
1195  pls->ls_type = TYPE_EC;
1196 
1197  le_current->next = pls; // hook it up
1198  le_current = pls;
1199 
1200  } else {
1201  long long key = ((unsigned long long)inode << 32) + enode;
1202 
1203  connector_segment *pcs = NULL;
1204  csit = cc_connector_hash.find(key);
1205  if (csit == cc_connector_hash.end()) {
1206  ndelta += 2;
1207  pcs = new connector_segment;
1208  cc_connector_hash[key] = pcs;
1209 
1210  // capture and store geometry
1211  segment_pair pair;
1212 
1213  float *ppt = ipnode->pPoint;
1214  pair.e0 = *ppt++;
1215  pair.n0 = *ppt;
1216 
1217  ppt = epnode->pPoint;
1218  pair.e1 = *ppt++;
1219  pair.n1 = *ppt;
1220 
1221  connector_segment_vector.push_back(pair);
1222  pcs->vbo_offset = seg_pair_index; // use temporarily
1223  seg_pair_index++;
1224 
1225  // calculate the centroid of this connector segment, used
1226  // for viz testing
1227  double lat, lon;
1228  fromSM_Plugin((pair.e0 + pair.e1) / 2,
1229  (pair.n0 + pair.n1) / 2, ref_lat, ref_lon,
1230  &lat, &lon);
1231  pcs->cs_lat_avg = lat;
1232  pcs->cs_lon_avg = lon;
1233 
1234  } else
1235  pcs = csit->second;
1236 
1237  line_segment_element *pls = new line_segment_element;
1238  pls->next = 0;
1239  pls->priority = 0;
1240  pls->pcs = pcs;
1241  pls->ls_type = TYPE_CC;
1242 
1243  le_current->next = pls; // hook it up
1244  le_current = pls;
1245  }
1246  }
1247  }
1248 
1249  } // for
1250 
1251  // All done, so assign the list to the object
1252  obj->m_ls_list =
1253  list_top.next; // skipping the empty first placeholder element
1254 
1255  // Rarely, some objects are improperly coded, e.g. cm93
1256  // If found, signal this downstream for NIL processing
1257  if (obj->m_ls_list == NULL) {
1258  obj->m_n_lsindex = 0;
1259  }
1260 
1261  // we are all finished with the line segment index array, per object
1262  free(obj->m_lsindex_array);
1263  obj->m_lsindex_array = NULL;
1264  }
1265 
1266  top = top->next;
1267  }
1268  }
1269  }
1270  // printf("time1 %f\n", sw.GetTime());
1271 
1272  // We have the total VBO point count, and a nice hashmap of the connector
1273  // segments
1274  nPoints += ndelta; // allow for the connector segments
1275 
1276  size_t vbo_byte_length = 2 * nPoints * sizeof(float);
1277 
1278  unsigned char *buffer_offset;
1279  size_t offset;
1280 
1281  bool grow_buffer = false;
1282 
1283  if (0 == m_vbo_byte_length) {
1284  m_line_vertex_buffer = (float *)malloc(vbo_byte_length);
1285  m_vbo_byte_length = vbo_byte_length;
1286  buffer_offset = (unsigned char *)m_line_vertex_buffer;
1287  offset = 0;
1288  } else {
1289  m_line_vertex_buffer = (float *)realloc(
1290  m_line_vertex_buffer, m_vbo_byte_length + vbo_byte_length);
1291  buffer_offset = (unsigned char *)m_line_vertex_buffer + m_vbo_byte_length;
1292  offset = m_vbo_byte_length;
1293  m_vbo_byte_length = m_vbo_byte_length + vbo_byte_length;
1294  grow_buffer = true;
1295  }
1296 
1297  float *lvr = (float *)buffer_offset;
1298 
1299  // Copy and edge points as floats,
1300  // and recording each segment's offset in the array
1301  for (const auto &it : m_ve_hash) {
1302  VE_Element *pedge = it.second;
1303  if (pedge) {
1304  memcpy(lvr, pedge->pPoints, pedge->nCount * 2 * sizeof(float));
1305  lvr += pedge->nCount * 2;
1306 
1307  pedge->vbo_offset = offset;
1308  offset += pedge->nCount * 2 * sizeof(float);
1309  }
1310  // else
1311  // int yyp = 4; //TODO Why are zero elements being
1312  // inserted into m_ve_hash?
1313  }
1314 
1315  // Now iterate on the hashmaps, adding the connector segments in the
1316  // temporary vector to the VBO buffer At the same time, populate a
1317  // vector, storing the pcs pointers to allow destruction at this class
1318  // dtor. This will allow us to destroy (automatically) the pcs hashmaps,
1319  // and save some storage
1320 
1321  for (csit = ce_connector_hash.begin(); csit != ce_connector_hash.end();
1322  ++csit) {
1323  connector_segment *pcs = csit->second;
1324  m_pcs_vector.push_back(pcs);
1325 
1326  segment_pair pair = connector_segment_vector.at(pcs->vbo_offset);
1327  *lvr++ = pair.e0;
1328  *lvr++ = pair.n0;
1329  *lvr++ = pair.e1;
1330  *lvr++ = pair.n1;
1331 
1332  pcs->vbo_offset = offset;
1333  offset += 4 * sizeof(float);
1334  }
1335 
1336  for (csit = ec_connector_hash.begin(); csit != ec_connector_hash.end();
1337  ++csit) {
1338  connector_segment *pcs = csit->second;
1339  m_pcs_vector.push_back(pcs);
1340 
1341  segment_pair pair = connector_segment_vector.at(pcs->vbo_offset);
1342  *lvr++ = pair.e0;
1343  *lvr++ = pair.n0;
1344  *lvr++ = pair.e1;
1345  *lvr++ = pair.n1;
1346 
1347  pcs->vbo_offset = offset;
1348  offset += 4 * sizeof(float);
1349  }
1350 
1351  for (csit = cc_connector_hash.begin(); csit != cc_connector_hash.end();
1352  ++csit) {
1353  connector_segment *pcs = csit->second;
1354  m_pcs_vector.push_back(pcs);
1355 
1356  segment_pair pair = connector_segment_vector.at(pcs->vbo_offset);
1357  *lvr++ = pair.e0;
1358  *lvr++ = pair.n0;
1359  *lvr++ = pair.e1;
1360  *lvr++ = pair.n1;
1361 
1362  pcs->vbo_offset = offset;
1363  offset += 4 * sizeof(float);
1364  }
1365 
1366  // And so we can empty the temp buffer
1367  connector_segment_vector.clear();
1368 
1369  // We can convert the edge hashmap to a vector, to allow us to destroy the
1370  // hashmap and at the same time free up the point storage in the VE_Elements,
1371  // since all the points are now in the VBO buffer
1372  for (const auto &it : m_ve_hash) {
1373  VE_Element *pedge = it.second;
1374  if (pedge) {
1375  m_pve_vector.push_back(pedge);
1376  free(pedge->pPoints);
1377  }
1378  }
1379  m_ve_hash.clear();
1380 
1381  // and we can empty the connector hashmap,
1382  // and at the same time free up the point storage in the VC_Elements, since
1383  // all the points are now in the VBO buffer
1384  for (const auto &it : m_vc_hash) {
1385  VC_Element *pcs = it.second;
1386  if (pcs) free(pcs->pPoint);
1387  delete pcs;
1388  }
1389  m_vc_hash.clear();
1390 
1391 #ifdef ocpnUSE_GL
1392  if (g_b_EnableVBO) {
1393  if (grow_buffer) {
1394  if (m_LineVBO_name > 0){
1395  glDeleteBuffers(1, (GLuint *)&m_LineVBO_name);
1396  m_LineVBO_name = -1;
1397  }
1398  }
1399  }
1400 #endif
1401 
1402 
1403  }
1404 
1405 void s57chart::BuildLineVBO(void) {
1406 #ifdef ocpnUSE_GL
1407  if (!g_b_EnableVBO) return;
1408 
1409  if (m_LineVBO_name == -1) {
1410  // Create the VBO
1411  GLuint vboId;
1412  glGenBuffers(1, &vboId);
1413 
1414  // bind VBO in order to use
1415  glBindBuffer(GL_ARRAY_BUFFER, vboId);
1416 
1417  // upload data to VBO
1418  // Choice: Line VBO only, or full VBO with areas.
1419 
1420 #if 1
1421 #ifndef USE_ANDROID_GLES2
1422  glEnableClientState(GL_VERTEX_ARRAY); // activate vertex coords array
1423 #endif
1424  glBufferData(GL_ARRAY_BUFFER, m_vbo_byte_length, m_line_vertex_buffer,
1425  GL_STATIC_DRAW);
1426 
1427 #else
1428  // get the size of VBO data block needed for all AREA objects
1429  ObjRazRules *top, *crnt;
1430  int vbo_area_size_bytes = 0;
1431  for (int i = 0; i < PRIO_NUM; ++i) {
1432  if (ps52plib->m_nBoundaryStyle == SYMBOLIZED_BOUNDARIES)
1433  top = razRules[i][4]; // Area Symbolized Boundaries
1434  else
1435  top = razRules[i][3]; // Area Plain Boundaries
1436 
1437  while (top != NULL) {
1438  crnt = top;
1439  top = top->next; // next object
1440 
1441  // Get the vertex data for this object
1442  PolyTriGroup *ppg_vbo = crnt->obj->pPolyTessGeo->Get_PolyTriGroup_head();
1443  //add the byte length
1444  vbo_area_size_bytes += ppg_vbo->single_buffer_size;
1445  }
1446  }
1447 
1448  glGetError(); //clear it
1449 
1450  // Allocate the VBO
1451  glBufferData(GL_ARRAY_BUFFER, m_vbo_byte_length + vbo_area_size_bytes,
1452  NULL, GL_STATIC_DRAW);
1453 
1454  GLenum err = glGetError();
1455  if (err) {
1456  wxString msg;
1457  msg.Printf(_T("S57 VBO Error 1: %d"), err);
1458  wxLogMessage(msg);
1459  printf("S57 VBO Error 1: %d", err);
1460  }
1461 
1462  // Upload the line vertex data
1463  glBufferSubData(GL_ARRAY_BUFFER, 0, m_vbo_byte_length, m_line_vertex_buffer);
1464 
1465  err = glGetError();
1466  if (err) {
1467  wxString msg;
1468  msg.Printf(_T("S57 VBO Error 2: %d"), err);
1469  wxLogMessage(msg);
1470  printf("S57 VBO Error 2: %d", err);
1471  }
1472 
1473 
1474  // Get the Area Object vertices, and add to the VBO, one by one
1475  int vbo_load_offset = m_vbo_byte_length;
1476 
1477  for (int i = 0; i < PRIO_NUM; ++i) {
1478  if (ps52plib->m_nBoundaryStyle == SYMBOLIZED_BOUNDARIES)
1479  top = razRules[i][4]; // Area Symbolized Boundaries
1480  else
1481  top = razRules[i][3]; // Area Plain Boundaries
1482 
1483  while (top != NULL) {
1484  crnt = top;
1485  top = top->next; // next object
1486 
1487  // Get the vertex data for this object
1488  PolyTriGroup *ppg_vbo = crnt->obj->pPolyTessGeo->Get_PolyTriGroup_head();
1489 
1490  // append data to VBO
1491  glBufferSubData(GL_ARRAY_BUFFER, vbo_load_offset,
1492  ppg_vbo->single_buffer_size,
1493  ppg_vbo->single_buffer);
1494  // store the VBO offset in the object
1495  crnt->obj->vboAreaOffset = vbo_load_offset;
1496  vbo_load_offset += ppg_vbo->single_buffer_size;
1497  }
1498  }
1499 
1500  err = glGetError();
1501  if (err) {
1502  wxString msg;
1503  msg.Printf(_T("S57 VBO Error 3: %d"), err);
1504  wxLogMessage(msg);
1505  printf("S57 VBO Error 3: %d", err);
1506  }
1507 
1508 #endif
1509 
1510 #ifndef USE_ANDROID_GLES2
1511  glDisableClientState(GL_VERTEX_ARRAY); // deactivate vertex array
1512 #endif
1513  glBindBuffer(GL_ARRAY_BUFFER, 0);
1514 
1515  // Loop and populate all the objects
1516  // with the name of the line/area vertex VBO
1517  for (int i = 0; i < PRIO_NUM; ++i) {
1518  for (int j = 0; j < LUPNAME_NUM; j++) {
1519  ObjRazRules *top = razRules[i][j];
1520  while (top != NULL) {
1521  S57Obj *obj = top->obj;
1522  obj->auxParm2 = vboId;
1523  top = top->next;
1524  }
1525  }
1526  }
1527 
1528  m_LineVBO_name = vboId;
1529  m_this_chart_context->vboID = vboId;
1530  }
1531 
1532 #endif
1533 }
1534 
1535 /* RectRegion:
1536  * This is the Screen region desired to be updated. Will
1537  * be either 1 rectangle(full screen) or two rectangles (panning with FBO
1538  * accelerated pan logic)
1539  *
1540  * Region:
1541  * This is the LLRegion describing the quilt active region
1542  * for this chart.
1543  *
1544  * So, Actual rendering area onscreen should be clipped to the
1545  * intersection of the two regions.
1546  */
1547 
1548 bool s57chart::RenderRegionViewOnGL(const wxGLContext &glc,
1549  const ViewPort &VPoint,
1550  const OCPNRegion &RectRegion,
1551  const LLRegion &Region) {
1552  if (!m_RAZBuilt) return false;
1553 
1554  return DoRenderRegionViewOnGL(glc, VPoint, RectRegion, Region, false);
1555 }
1556 
1557 bool s57chart::RenderOverlayRegionViewOnGL(const wxGLContext &glc,
1558  const ViewPort &VPoint,
1559  const OCPNRegion &RectRegion,
1560  const LLRegion &Region) {
1561  if (!m_RAZBuilt) return false;
1562 
1563  return DoRenderRegionViewOnGL(glc, VPoint, RectRegion, Region, true);
1564 }
1565 
1566 bool s57chart::RenderRegionViewOnGLNoText(const wxGLContext &glc,
1567  const ViewPort &VPoint,
1568  const OCPNRegion &RectRegion,
1569  const LLRegion &Region) {
1570  if (!m_RAZBuilt) return false;
1571 
1572  bool b_text = ps52plib->GetShowS57Text();
1573  ps52plib->m_bShowS57Text = false;
1574  bool b_ret = DoRenderRegionViewOnGL(glc, VPoint, RectRegion, Region, false);
1575  ps52plib->m_bShowS57Text = b_text;
1576 
1577  return b_ret;
1578 }
1579 
1580 bool s57chart::RenderViewOnGLTextOnly(const wxGLContext &glc,
1581  const ViewPort &VPoint) {
1582  if (!m_RAZBuilt) return false;
1583 
1584 #ifdef ocpnUSE_GL
1585 
1586  if (!ps52plib) return false;
1587 
1588  SetVPParms(VPoint);
1589  PrepareForRender((ViewPort *)&VPoint, ps52plib);
1590 
1591  glChartCanvas::DisableClipRegion();
1592  DoRenderOnGLText(glc, VPoint);
1593 
1594 #endif
1595  return true;
1596 }
1597 
1598 bool s57chart::DoRenderRegionViewOnGL(const wxGLContext &glc,
1599  const ViewPort &VPoint,
1600  const OCPNRegion &RectRegion,
1601  const LLRegion &Region, bool b_overlay) {
1602  if (!m_RAZBuilt) return false;
1603 
1604 #ifdef ocpnUSE_GL
1605 
1606  if (!ps52plib) return false;
1607 
1608  if (g_bDebugS57) printf("\n");
1609 
1610  SetVPParms(VPoint);
1611 
1612  PrepareForRender((ViewPort *)&VPoint, ps52plib);
1613 
1614  if (m_plib_state_hash != ps52plib->GetStateHash()) {
1615  m_bLinePrioritySet = false; // need to reset line priorities
1616  UpdateLUPs(this); // and update the LUPs
1617  ClearRenderedTextCache(); // and reset the text renderer,
1618  // for the case where depth(height) units change
1619  ResetPointBBoxes(m_last_vp, VPoint);
1620  SetSafetyContour();
1621 
1622  m_plib_state_hash = ps52plib->GetStateHash();
1623  }
1624 
1625  if (VPoint.view_scale_ppm != m_last_vp.view_scale_ppm) {
1626  ResetPointBBoxes(m_last_vp, VPoint);
1627  }
1628 
1629  BuildLineVBO();
1630  SetLinePriorities();
1631 
1632  // Clear the text declutter list
1633  ps52plib->ClearTextList();
1634 
1635  ViewPort vp = VPoint;
1636 
1637 // printf("\n");
1638  // region always has either 1 or 2 rectangles (full screen or panning
1639  // rectangles)
1640  for (OCPNRegionIterator upd(RectRegion); upd.HaveRects(); upd.NextRect()) {
1641  wxRect upr = upd.GetRect();
1642  //printf("updRect: %d %d %d %d\n",upr.x, upr.y, upr.width, upr.height);
1643 
1644  LLRegion chart_region = vp.GetLLRegion(upd.GetRect());
1645  chart_region.Intersect(Region);
1646 
1647  if (!chart_region.Empty()) {
1648  // TODO I think this needs nore work for alternate Projections...
1649  // cm93 vpoint crossing Greenwich, panning east, was rendering areas
1650  // incorrectly.
1651  ViewPort cvp = glChartCanvas::ClippedViewport(VPoint, chart_region);
1652 // printf("CVP: %g %g %g %g\n",
1653 // cvp.GetBBox().GetMinLat(),
1654 // cvp.GetBBox().GetMaxLat(),
1655 // cvp.GetBBox().GetMinLon(),
1656 // cvp.GetBBox().GetMaxLon());
1657 
1658  if (CHART_TYPE_CM93 == GetChartType()) {
1659  // for now I will revert to the faster rectangle clipping now that
1660  // rendering order is resolved
1661  // glChartCanvas::SetClipRegion(cvp, chart_region);
1662  glChartCanvas::SetClipRect(cvp, upd.GetRect(), false);
1663  //ps52plib->m_last_clip_rect = upd.GetRect();
1664  } else {
1665 #ifdef OPT_USE_ANDROID_GLES2
1666 
1667  // GLES2 will be faster if we setup and use a smaller viewport for each
1668  // rectangle render. This is because when using shaders, clip operations
1669  // (e.g. scissor, stencil) happen after the fragment shader executes.
1670  // However, with a smaller viewport, the fragment shader will not be
1671  // invoked if the vertices are all outside the vieport.
1672 
1673  wxRect r = upd.GetRect();
1674  ViewPort *vp = &cvp;
1675  glViewport(r.x, vp->pix_height - (r.y + r.height), r.width, r.height);
1676 
1677  // mat4x4 m;
1678  // mat4x4_identity(m);
1679 
1680  mat4x4 I, Q;
1681  mat4x4_identity(I);
1682 
1683  float yp = vp->pix_height - (r.y + r.height);
1684  // Translate
1685  I[3][0] = (-r.x - (float)r.width / 2) * (2.0 / (float)r.width);
1686  I[3][1] = (r.y + (float)r.height / 2) * (2.0 / (float)r.height);
1687 
1688  // Scale
1689  I[0][0] *= 2.0 / (float)r.width;
1690  I[1][1] *= -2.0 / (float)r.height;
1691 
1692  // Rotate
1693  float angle = 0;
1694  mat4x4_rotate_Z(Q, I, angle);
1695 
1696  mat4x4_dup((float(*)[4])vp->vp_transform, Q);
1697 
1698 #else
1699  ps52plib->SetReducedBBox(cvp.GetBBox());
1700  glChartCanvas::SetClipRect(cvp, upd.GetRect(), false);
1701 
1702 #endif
1703  }
1704 
1705  DoRenderOnGL(glc, cvp);
1706 
1707  glChartCanvas::DisableClipRegion();
1708  }
1709  }
1710 
1711  // Update last_vp to reflect current state
1712  m_last_vp = VPoint;
1713 
1714  // CALLGRIND_STOP_INSTRUMENTATION
1715 
1716 #endif
1717  return true;
1718 }
1719 
1720 
1721 bool s57chart::DoRenderOnGL(const wxGLContext &glc, const ViewPort &VPoint) {
1722 #ifdef ocpnUSE_GL
1723 
1724  int i;
1725  ObjRazRules *top;
1726  ObjRazRules *crnt;
1727  ViewPort tvp = VPoint; // undo const TODO fix this in PLIB
1728 
1729 #if 1
1730 
1731  // Render the areas quickly
1732  // bind VBO in order to use
1733 
1734  for (i = 0; i < PRIO_NUM; ++i) {
1735  if (ps52plib->m_nBoundaryStyle == SYMBOLIZED_BOUNDARIES)
1736  top = razRules[i][4]; // Area Symbolized Boundaries
1737  else
1738  top = razRules[i][3]; // Area Plain Boundaries
1739 
1740  while (top != NULL) {
1741  crnt = top;
1742  top = top->next; // next object
1743  crnt->sm_transform_parms = &vp_transform;
1744  ps52plib->RenderAreaToGL(glc, crnt);
1745  }
1746  }
1747 
1748 #else
1749  // Render the areas quickly
1750  for (i = 0; i < PRIO_NUM; ++i) {
1751  if (PI_GetPLIBBoundaryStyle() == SYMBOLIZED_BOUNDARIES)
1752  top = razRules[i][4]; // Area Symbolized Boundaries
1753  else
1754  top = razRules[i][3]; // Area Plain Boundaries
1755 
1756  while (top != NULL) {
1757  crnt = top;
1758  top = top->next; // next object
1759  crnt->sm_transform_parms = &vp_transform;
1760 
1761  // This may be a deferred tesselation
1762  // Don't pre-process the geometry unless the object is to be actually
1763  // rendered
1764  if (!crnt->obj->pPolyTessGeo->IsOk()) {
1765  if (ps52plib->ObjectRenderCheckRules(crnt, &tvp, true)) {
1766  if (!crnt->obj->pPolyTessGeo->m_pxgeom)
1767  crnt->obj->pPolyTessGeo->m_pxgeom = buildExtendedGeom(crnt->obj);
1768  }
1769  }
1770  ps52plib->RenderAreaToGL(glc, crnt, &tvp);
1771  }
1772  }
1773 #endif
1774  // qDebug() << "Done areas" << sw.GetTime();
1775 
1776  // Render the lines and points
1777  for (i = 0; i < PRIO_NUM; ++i) {
1778  if (ps52plib->m_nBoundaryStyle == SYMBOLIZED_BOUNDARIES)
1779  top = razRules[i][4]; // Area Symbolized Boundaries
1780  else
1781  top = razRules[i][3]; // Area Plain Boundaries
1782  while (top != NULL) {
1783  crnt = top;
1784  top = top->next; // next object
1785  crnt->sm_transform_parms = &vp_transform;
1786  ps52plib->RenderObjectToGL(glc, crnt);
1787  }
1788  }
1789  // qDebug() << "Done Boundaries" << sw.GetTime();
1790 
1791  for (i = 0; i < PRIO_NUM; ++i) {
1792  top = razRules[i][2]; // LINES
1793  while (top != NULL) {
1794  crnt = top;
1795  top = top->next;
1796  crnt->sm_transform_parms = &vp_transform;
1797  ps52plib->RenderObjectToGL(glc, crnt);
1798  }
1799  }
1800 
1801  // qDebug() << "Done Lines" << sw.GetTime();
1802 
1803  for (i = 0; i < PRIO_NUM; ++i) {
1804  if (ps52plib->m_nSymbolStyle == SIMPLIFIED)
1805  top = razRules[i][0]; // SIMPLIFIED Points
1806  else
1807  top = razRules[i][1]; // Paper Chart Points Points
1808 
1809  while (top != NULL) {
1810  crnt = top;
1811  top = top->next;
1812  crnt->sm_transform_parms = &vp_transform;
1813  ps52plib->RenderObjectToGL(glc, crnt);
1814  }
1815  }
1816  // qDebug() << "Done Points" << sw.GetTime();
1817 
1818 #endif //#ifdef ocpnUSE_GL
1819 
1820  return true;
1821 }
1822 
1823 bool s57chart::DoRenderOnGLText(const wxGLContext &glc,
1824  const ViewPort &VPoint) {
1825 #ifdef ocpnUSE_GL
1826 
1827  int i;
1828  ObjRazRules *top;
1829  ObjRazRules *crnt;
1830  ViewPort tvp = VPoint; // undo const TODO fix this in PLIB
1831 
1832 #if 0
1833  // Render the areas quickly
1834  for( i = 0; i < PRIO_NUM; ++i ) {
1835  if( ps52plib->m_nBoundaryStyle == SYMBOLIZED_BOUNDARIES )
1836  top = razRules[i][4]; // Area Symbolized Boundaries
1837  else
1838  top = razRules[i][3]; // Area Plain Boundaries
1839 
1840  while( top != NULL ) {
1841  crnt = top;
1842  top = top->next; // next object
1843  crnt->sm_transform_parms = &vp_transform;
1845  }
1846  }
1847 #endif
1848 
1849  // Render the lines and points
1850  for (i = 0; i < PRIO_NUM; ++i) {
1851  if (ps52plib->m_nBoundaryStyle == SYMBOLIZED_BOUNDARIES)
1852  top = razRules[i][4]; // Area Symbolized Boundaries
1853  else
1854  top = razRules[i][3]; // Area Plain Boundaries
1855 
1856  while (top != NULL) {
1857  crnt = top;
1858  top = top->next; // next object
1859  crnt->sm_transform_parms = &vp_transform;
1860  ps52plib->RenderObjectToGLText(glc, crnt);
1861  }
1862 
1863  top = razRules[i][2]; // LINES
1864  while (top != NULL) {
1865  crnt = top;
1866  top = top->next;
1867  crnt->sm_transform_parms = &vp_transform;
1868  ps52plib->RenderObjectToGLText(glc, crnt);
1869  }
1870 
1871  if (ps52plib->m_nSymbolStyle == SIMPLIFIED)
1872  top = razRules[i][0]; // SIMPLIFIED Points
1873  else
1874  top = razRules[i][1]; // Paper Chart Points Points
1875 
1876  while (top != NULL) {
1877  crnt = top;
1878  top = top->next;
1879  crnt->sm_transform_parms = &vp_transform;
1880  ps52plib->RenderObjectToGLText(glc, crnt);
1881  }
1882  }
1883 
1884 #endif //#ifdef ocpnUSE_GL
1885 
1886  return true;
1887 }
1888 
1889 bool s57chart::RenderRegionViewOnDCNoText(wxMemoryDC &dc,
1890  const ViewPort &VPoint,
1891  const OCPNRegion &Region) {
1892  if (!m_RAZBuilt) return false;
1893 
1894  bool b_text = ps52plib->GetShowS57Text();
1895  ps52plib->m_bShowS57Text = false;
1896  bool b_ret = DoRenderRegionViewOnDC(dc, VPoint, Region, false);
1897  ps52plib->m_bShowS57Text = b_text;
1898 
1899  return true;
1900 }
1901 
1902 bool s57chart::RenderRegionViewOnDCTextOnly(wxMemoryDC &dc,
1903  const ViewPort &VPoint,
1904  const OCPNRegion &Region) {
1905  if (!dc.IsOk()) return false;
1906 
1907  SetVPParms(VPoint);
1908  PrepareForRender((ViewPort *)&VPoint, ps52plib);
1909 
1910  // If the viewport is rotated, there will only be one rectangle in the region
1911  // so we can take a shortcut...
1912  if (fabs(VPoint.rotation) > .01) {
1913  DCRenderText(dc, VPoint);
1914  } else {
1915  ViewPort temp_vp = VPoint;
1916  double temp_lon_left, temp_lat_bot, temp_lon_right, temp_lat_top;
1917 
1918  // Decompose the region into rectangles,
1919  OCPNRegionIterator upd(Region); // get the requested rect list
1920  while (upd.HaveRects()) {
1921  wxRect rect = upd.GetRect();
1922 
1923  wxPoint p;
1924  p.x = rect.x;
1925  p.y = rect.y;
1926 
1927  temp_vp.GetLLFromPix(p, &temp_lat_top, &temp_lon_left);
1928 
1929  p.x += rect.width;
1930  p.y += rect.height;
1931  temp_vp.GetLLFromPix(p, &temp_lat_bot, &temp_lon_right);
1932 
1933  if (temp_lon_right < temp_lon_left) // presumably crossing Greenwich
1934  temp_lon_right += 360.;
1935 
1936  temp_vp.GetBBox().Set(temp_lat_bot, temp_lon_left, temp_lat_top,
1937  temp_lon_right);
1938 
1939  wxDCClipper clip(dc, rect);
1940  DCRenderText(dc, temp_vp);
1941 
1942  upd.NextRect();
1943  }
1944  }
1945 
1946  return true;
1947 }
1948 
1949 bool s57chart::RenderRegionViewOnDC(wxMemoryDC &dc, const ViewPort &VPoint,
1950  const OCPNRegion &Region) {
1951  if (!m_RAZBuilt) return false;
1952 
1953  return DoRenderRegionViewOnDC(dc, VPoint, Region, false);
1954 }
1955 
1956 bool s57chart::RenderOverlayRegionViewOnDC(wxMemoryDC &dc,
1957  const ViewPort &VPoint,
1958  const OCPNRegion &Region) {
1959  if (!m_RAZBuilt) return false;
1960  return DoRenderRegionViewOnDC(dc, VPoint, Region, true);
1961 }
1962 
1963 bool s57chart::DoRenderRegionViewOnDC(wxMemoryDC &dc, const ViewPort &VPoint,
1964  const OCPNRegion &Region,
1965  bool b_overlay) {
1966  SetVPParms(VPoint);
1967 
1968  bool force_new_view = false;
1969 
1970  if (Region != m_last_Region) force_new_view = true;
1971 
1972  PrepareForRender((ViewPort *)&VPoint, ps52plib);
1973 
1974  if (m_plib_state_hash != ps52plib->GetStateHash()) {
1975  m_bLinePrioritySet = false; // need to reset line priorities
1976  UpdateLUPs(this); // and update the LUPs
1977  ClearRenderedTextCache(); // and reset the text renderer,
1978  // for the case where depth(height) units change
1979  ResetPointBBoxes(m_last_vp, VPoint);
1980  SetSafetyContour();
1981  }
1982 
1983  if (VPoint.view_scale_ppm != m_last_vp.view_scale_ppm) {
1984  ResetPointBBoxes(m_last_vp, VPoint);
1985  }
1986 
1987  SetLinePriorities();
1988 
1989  bool bnew_view = DoRenderViewOnDC(dc, VPoint, DC_RENDER_ONLY, force_new_view);
1990 
1991  // If quilting, we need to return a cloned bitmap instead of the original
1992  // golden item
1993  if (VPoint.b_quilt) {
1994  if (m_pCloneBM) {
1995  if ((m_pCloneBM->GetWidth() != VPoint.pix_width) ||
1996  (m_pCloneBM->GetHeight() != VPoint.pix_height)) {
1997  delete m_pCloneBM;
1998  m_pCloneBM = NULL;
1999  }
2000  }
2001  if (NULL == m_pCloneBM)
2002  m_pCloneBM = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
2003 
2004  wxMemoryDC dc_clone;
2005  dc_clone.SelectObject(*m_pCloneBM);
2006 
2007 #ifdef ocpnUSE_DIBSECTION
2008  ocpnMemDC memdc, dc_org;
2009 #else
2010  wxMemoryDC memdc, dc_org;
2011 #endif
2012 
2013  pDIB->SelectIntoDC(dc_org);
2014 
2015  // Decompose the region into rectangles, and fetch them into the target
2016  // dc
2017  OCPNRegionIterator upd(Region); // get the requested rect list
2018  while (upd.HaveRects()) {
2019  wxRect rect = upd.GetRect();
2020  dc_clone.Blit(rect.x, rect.y, rect.width, rect.height, &dc_org, rect.x,
2021  rect.y);
2022  upd.NextRect();
2023  }
2024 
2025  dc_clone.SelectObject(wxNullBitmap);
2026  dc_org.SelectObject(wxNullBitmap);
2027 
2028  // Create a mask
2029  if (b_overlay) {
2030  wxColour nodat = GetGlobalColor(_T ( "NODTA" ));
2031  wxColour nodat_sub = nodat;
2032 
2033 #ifdef ocpnUSE_ocpnBitmap
2034  nodat_sub = wxColour(nodat.Blue(), nodat.Green(), nodat.Red());
2035 #endif
2036  m_pMask = new wxMask(*m_pCloneBM, nodat_sub);
2037  m_pCloneBM->SetMask(m_pMask);
2038  }
2039 
2040  dc.SelectObject(*m_pCloneBM);
2041  } else
2042  pDIB->SelectIntoDC(dc);
2043 
2044  m_last_Region = Region;
2045 
2046  return true;
2047 }
2048 
2049 bool s57chart::RenderViewOnDC(wxMemoryDC &dc, const ViewPort &VPoint) {
2050  // CALLGRIND_START_INSTRUMENTATION
2051 
2052  SetVPParms(VPoint);
2053 
2054  PrepareForRender((ViewPort *)&VPoint, ps52plib);
2055 
2056  if (m_plib_state_hash != ps52plib->GetStateHash()) {
2057  m_bLinePrioritySet = false; // need to reset line priorities
2058  UpdateLUPs(this); // and update the LUPs
2059  ClearRenderedTextCache(); // and reset the text renderer
2060  SetSafetyContour();
2061  }
2062 
2063  SetLinePriorities();
2064 
2065  bool bnew_view = DoRenderViewOnDC(dc, VPoint, DC_RENDER_ONLY, false);
2066 
2067  pDIB->SelectIntoDC(dc);
2068 
2069  return bnew_view;
2070 
2071  // CALLGRIND_STOP_INSTRUMENTATION
2072 }
2073 
2074 bool s57chart::DoRenderViewOnDC(wxMemoryDC &dc, const ViewPort &VPoint,
2075  RenderTypeEnum option, bool force_new_view) {
2076  bool bnewview = false;
2077  wxPoint rul, rlr;
2078  bool bNewVP = false;
2079 
2080  bool bReallyNew = false;
2081 
2082  double easting_ul, northing_ul;
2083  double easting_lr, northing_lr;
2084  double prev_easting_ul = 0., prev_northing_ul = 0.;
2085 
2086  if (ps52plib->GetPLIBColorScheme() != m_lastColorScheme) bReallyNew = true;
2087  m_lastColorScheme = ps52plib->GetPLIBColorScheme();
2088 
2089  if (VPoint.view_scale_ppm != m_last_vp.view_scale_ppm) bReallyNew = true;
2090 
2091  // If the scale is very small, do not use the cache to avoid harmonic
2092  // difficulties...
2093  if (VPoint.chart_scale > 1e8) bReallyNew = true;
2094 
2095  wxRect dest(0, 0, VPoint.pix_width, VPoint.pix_height);
2096  if (m_last_vprect != dest) bReallyNew = true;
2097  m_last_vprect = dest;
2098 
2099  if (m_plib_state_hash != ps52plib->GetStateHash()) {
2100  bReallyNew = true;
2101  m_plib_state_hash = ps52plib->GetStateHash();
2102  }
2103 
2104  if (bReallyNew) {
2105  bNewVP = true;
2106  delete pDIB;
2107  pDIB = NULL;
2108  bnewview = true;
2109  }
2110 
2111  // Calculate the desired rectangle in the last cached image space
2112  if (m_last_vp.IsValid()) {
2113  easting_ul =
2114  m_easting_vp_center - ((VPoint.pix_width / 2) / m_view_scale_ppm);
2115  northing_ul =
2116  m_northing_vp_center + ((VPoint.pix_height / 2) / m_view_scale_ppm);
2117  easting_lr = easting_ul + (VPoint.pix_width / m_view_scale_ppm);
2118  northing_lr = northing_ul - (VPoint.pix_height / m_view_scale_ppm);
2119 
2120  double last_easting_vp_center, last_northing_vp_center;
2121  toSM(m_last_vp.clat, m_last_vp.clon, ref_lat, ref_lon,
2122  &last_easting_vp_center, &last_northing_vp_center);
2123 
2124  prev_easting_ul =
2125  last_easting_vp_center - ((m_last_vp.pix_width / 2) / m_view_scale_ppm);
2126  prev_northing_ul = last_northing_vp_center +
2127  ((m_last_vp.pix_height / 2) / m_view_scale_ppm);
2128 
2129  double dx = (easting_ul - prev_easting_ul) * m_view_scale_ppm;
2130  double dy = (prev_northing_ul - northing_ul) * m_view_scale_ppm;
2131 
2132  rul.x = (int)round((easting_ul - prev_easting_ul) * m_view_scale_ppm);
2133  rul.y = (int)round((prev_northing_ul - northing_ul) * m_view_scale_ppm);
2134 
2135  rlr.x = (int)round((easting_lr - prev_easting_ul) * m_view_scale_ppm);
2136  rlr.y = (int)round((prev_northing_ul - northing_lr) * m_view_scale_ppm);
2137 
2138  if ((fabs(dx - wxRound(dx)) > 1e-5) || (fabs(dy - wxRound(dy)) > 1e-5)) {
2139  if (g_bDebugS57)
2140  printf(
2141  "s57chart::DoRender Cache miss on non-integer pixel delta %g %g\n",
2142  dx, dy);
2143  rul.x = 0;
2144  rul.y = 0;
2145  rlr.x = 0;
2146  rlr.y = 0;
2147  bNewVP = true;
2148  }
2149 
2150  else if ((rul.x != 0) || (rul.y != 0)) {
2151  if (g_bDebugS57) printf("newvp due to rul\n");
2152  bNewVP = true;
2153  }
2154  } else {
2155  rul.x = 0;
2156  rul.y = 0;
2157  rlr.x = 0;
2158  rlr.y = 0;
2159  bNewVP = true;
2160  }
2161 
2162  if (force_new_view) bNewVP = true;
2163 
2164  // Using regions, calculate re-usable area of pDIB
2165 
2166  OCPNRegion rgn_last(0, 0, VPoint.pix_width, VPoint.pix_height);
2167  OCPNRegion rgn_new(rul.x, rul.y, rlr.x - rul.x, rlr.y - rul.y);
2168  rgn_last.Intersect(rgn_new); // intersection is reusable portion
2169 
2170  if (bNewVP && (NULL != pDIB) && !rgn_last.IsEmpty()) {
2171  int xu, yu, wu, hu;
2172  rgn_last.GetBox(xu, yu, wu, hu);
2173 
2174  int desx = 0;
2175  int desy = 0;
2176  int srcx = xu;
2177  int srcy = yu;
2178 
2179  if (rul.x < 0) {
2180  srcx = 0;
2181  desx = -rul.x;
2182  }
2183  if (rul.y < 0) {
2184  srcy = 0;
2185  desy = -rul.y;
2186  }
2187 
2188  ocpnMemDC dc_last;
2189  pDIB->SelectIntoDC(dc_last);
2190 
2191  ocpnMemDC dc_new;
2192  PixelCache *pDIBNew =
2193  new PixelCache(VPoint.pix_width, VPoint.pix_height, BPP);
2194  pDIBNew->SelectIntoDC(dc_new);
2195 
2196  // printf("reuse blit %d %d %d %d %d %d\n",desx, desy, wu, hu, srcx,
2197  // srcy);
2198  dc_new.Blit(desx, desy, wu, hu, (wxDC *)&dc_last, srcx, srcy, wxCOPY);
2199 
2200  // Ask the plib to adjust the persistent text rectangle list for this
2201  // canvas shift This ensures that, on pans, the list stays in
2202  // registration with the new text renders to come
2203  ps52plib->AdjustTextList(desx - srcx, desy - srcy, VPoint.pix_width,
2204  VPoint.pix_height);
2205 
2206  dc_new.SelectObject(wxNullBitmap);
2207  dc_last.SelectObject(wxNullBitmap);
2208 
2209  delete pDIB;
2210  pDIB = pDIBNew;
2211 
2212  // OK, now have the re-useable section in place
2213  // Next, build the new sections
2214 
2215  pDIB->SelectIntoDC(dc);
2216 
2217  OCPNRegion rgn_delta(0, 0, VPoint.pix_width, VPoint.pix_height);
2218  OCPNRegion rgn_reused(desx, desy, wu, hu);
2219  rgn_delta.Subtract(rgn_reused);
2220 
2221  OCPNRegionIterator upd(rgn_delta); // get the update rect list
2222  while (upd.HaveRects()) {
2223  wxRect rect = upd.GetRect();
2224 
2225  // Build temp ViewPort on this region
2226 
2227  ViewPort temp_vp = VPoint;
2228  double temp_lon_left, temp_lat_bot, temp_lon_right, temp_lat_top;
2229 
2230  double temp_northing_ul = prev_northing_ul - (rul.y / m_view_scale_ppm) -
2231  (rect.y / m_view_scale_ppm);
2232  double temp_easting_ul = prev_easting_ul + (rul.x / m_view_scale_ppm) +
2233  (rect.x / m_view_scale_ppm);
2234  fromSM(temp_easting_ul, temp_northing_ul, ref_lat, ref_lon, &temp_lat_top,
2235  &temp_lon_left);
2236 
2237  double temp_northing_lr =
2238  temp_northing_ul - (rect.height / m_view_scale_ppm);
2239  double temp_easting_lr =
2240  temp_easting_ul + (rect.width / m_view_scale_ppm);
2241  fromSM(temp_easting_lr, temp_northing_lr, ref_lat, ref_lon, &temp_lat_bot,
2242  &temp_lon_right);
2243 
2244  temp_vp.GetBBox().Set(temp_lat_bot, temp_lon_left, temp_lat_top,
2245  temp_lon_right);
2246 
2247  // Allow some slop in the viewport
2248  // TODO Investigate why this fails if greater than 5 percent
2249  double margin = wxMin(temp_vp.GetBBox().GetLonRange(),
2250  temp_vp.GetBBox().GetLatRange()) *
2251  0.05;
2252  temp_vp.GetBBox().EnLarge(margin);
2253 
2254  // And Render it new piece on the target dc
2255  // printf("New Render, rendering %d %d %d %d \n", rect.x, rect.y,
2256  // rect.width, rect.height);
2257 
2258  DCRenderRect(dc, temp_vp, &rect);
2259 
2260  upd.NextRect();
2261  }
2262 
2263  dc.SelectObject(wxNullBitmap);
2264 
2265  bnewview = true;
2266 
2267  // Update last_vp to reflect the current cached bitmap
2268  m_last_vp = VPoint;
2269 
2270  }
2271 
2272  else if (bNewVP || (NULL == pDIB)) {
2273  delete pDIB;
2274  pDIB = new PixelCache(VPoint.pix_width, VPoint.pix_height,
2275  BPP); // destination
2276 
2277  wxRect full_rect(0, 0, VPoint.pix_width, VPoint.pix_height);
2278  pDIB->SelectIntoDC(dc);
2279 
2280  // Clear the text declutter list
2281  ps52plib->ClearTextList();
2282 
2283  DCRenderRect(dc, VPoint, &full_rect);
2284 
2285  dc.SelectObject(wxNullBitmap);
2286 
2287  bnewview = true;
2288 
2289  // Update last_vp to reflect the current cached bitmap
2290  m_last_vp = VPoint;
2291  }
2292 
2293  return bnewview;
2294 }
2295 
2296 int s57chart::DCRenderRect(wxMemoryDC &dcinput, const ViewPort &vp,
2297  wxRect *rect) {
2298  int i;
2299  ObjRazRules *top;
2300  ObjRazRules *crnt;
2301 
2302  wxASSERT(rect);
2303  ViewPort tvp = vp; // undo const TODO fix this in PLIB
2304 
2305  // This does not work due to some issue with ref data of allocated
2306  // buffer..... render_canvas_parms pb_spec( rect->x, rect->y, rect->width,
2307  // rect->height, GetGlobalColor ( _T ( "NODTA" ) ));
2308 
2309  render_canvas_parms pb_spec;
2310 
2311  pb_spec.depth = BPP;
2312  pb_spec.pb_pitch = ((rect->width * pb_spec.depth / 8));
2313  pb_spec.lclip = rect->x;
2314  pb_spec.rclip = rect->x + rect->width - 1;
2315  pb_spec.pix_buff = (unsigned char *)malloc(rect->height * pb_spec.pb_pitch);
2316  pb_spec.width = rect->width;
2317  pb_spec.height = rect->height;
2318  pb_spec.x = rect->x;
2319  pb_spec.y = rect->y;
2320 
2321 #ifdef ocpnUSE_ocpnBitmap
2322  pb_spec.b_revrgb = true;
2323 #else
2324  pb_spec.b_revrgb = false;
2325 #endif
2326 
2327  // Preset background
2328  wxColour color = GetGlobalColor(_T ( "NODTA" ));
2329  unsigned char r, g, b;
2330  if (color.IsOk()) {
2331  r = color.Red();
2332  g = color.Green();
2333  b = color.Blue();
2334  } else
2335  r = g = b = 0;
2336 
2337  if (pb_spec.depth == 24) {
2338  for (int i = 0; i < pb_spec.height; i++) {
2339  unsigned char *p = pb_spec.pix_buff + (i * pb_spec.pb_pitch);
2340  for (int j = 0; j < pb_spec.width; j++) {
2341  *p++ = r;
2342  *p++ = g;
2343  *p++ = b;
2344  }
2345  }
2346  } else {
2347  int color_int = ((r) << 16) + ((g) << 8) + (b);
2348 
2349  for (int i = 0; i < pb_spec.height; i++) {
2350  int *p = (int *)(pb_spec.pix_buff + (i * pb_spec.pb_pitch));
2351  for (int j = 0; j < pb_spec.width; j++) {
2352  *p++ = color_int;
2353  }
2354  }
2355  }
2356 
2357  // Render the areas quickly
2358  for (i = 0; i < PRIO_NUM; ++i) {
2359  if (ps52plib->m_nBoundaryStyle == SYMBOLIZED_BOUNDARIES)
2360  top = razRules[i][4]; // Area Symbolized Boundaries
2361  else
2362  top = razRules[i][3]; // Area Plain Boundaries
2363 
2364  while (top != NULL) {
2365  crnt = top;
2366  top = top->next; // next object
2367  crnt->sm_transform_parms = &vp_transform;
2368  ps52plib->RenderAreaToDC(&dcinput, crnt, &pb_spec);
2369  }
2370  }
2371 
2372 // Convert the Private render canvas into a bitmap
2373 #ifdef ocpnUSE_ocpnBitmap
2374  ocpnBitmap *pREN = new ocpnBitmap(pb_spec.pix_buff, pb_spec.width,
2375  pb_spec.height, pb_spec.depth);
2376 #else
2377  wxImage *prender_image = new wxImage(pb_spec.width, pb_spec.height, false);
2378  prender_image->SetData((unsigned char *)pb_spec.pix_buff);
2379  wxBitmap *pREN = new wxBitmap(*prender_image);
2380 
2381 #endif
2382 
2383  // Map it into a temporary DC
2384  wxMemoryDC dc_ren;
2385  dc_ren.SelectObject(*pREN);
2386 
2387  // Blit it onto the target dc
2388  dcinput.Blit(pb_spec.x, pb_spec.y, pb_spec.width, pb_spec.height,
2389  (wxDC *)&dc_ren, 0, 0);
2390 
2391  // And clean up the mess
2392  dc_ren.SelectObject(wxNullBitmap);
2393 
2394 #ifdef ocpnUSE_ocpnBitmap
2395  free(pb_spec.pix_buff);
2396 #else
2397  delete prender_image; // the image owns the data
2398  // and so will free it in due course
2399 #endif
2400 
2401  delete pREN;
2402 
2403  // Render the rest of the objects/primitives
2404  DCRenderLPB(dcinput, vp, rect);
2405 
2406  return 1;
2407 }
2408 
2409 bool s57chart::DCRenderLPB(wxMemoryDC &dcinput, const ViewPort &vp,
2410  wxRect *rect) {
2411  int i;
2412  ObjRazRules *top;
2413  ObjRazRules *crnt;
2414  ViewPort tvp = vp; // undo const TODO fix this in PLIB
2415 
2416  for (i = 0; i < PRIO_NUM; ++i) {
2417  // Set up a Clipper for Lines
2418  wxDCClipper *pdcc = NULL;
2419  // if( rect ) {
2420  // wxRect nr = *rect;
2421  // pdcc = new wxDCClipper(dcinput, nr);
2422  // }
2423 
2424  if (ps52plib->m_nBoundaryStyle == SYMBOLIZED_BOUNDARIES)
2425  top = razRules[i][4]; // Area Symbolized Boundaries
2426  else
2427  top = razRules[i][3]; // Area Plain Boundaries
2428  while (top != NULL) {
2429  crnt = top;
2430  top = top->next; // next object
2431  crnt->sm_transform_parms = &vp_transform;
2432  ps52plib->RenderObjectToDC(&dcinput, crnt);
2433  }
2434 
2435  top = razRules[i][2]; // LINES
2436  while (top != NULL) {
2437  crnt = top;
2438  top = top->next;
2439  crnt->sm_transform_parms = &vp_transform;
2440  ps52plib->RenderObjectToDC(&dcinput, crnt);
2441  }
2442 
2443  if (ps52plib->m_nSymbolStyle == SIMPLIFIED)
2444  top = razRules[i][0]; // SIMPLIFIED Points
2445  else
2446  top = razRules[i][1]; // Paper Chart Points Points
2447 
2448  while (top != NULL) {
2449  crnt = top;
2450  top = top->next;
2451  crnt->sm_transform_parms = &vp_transform;
2452  ps52plib->RenderObjectToDC(&dcinput, crnt);
2453  }
2454 
2455  // Destroy Clipper
2456  if (pdcc) delete pdcc;
2457  }
2458 
2459  /*
2460  printf("Render Lines %ldms\n", stlines.Time());
2461  printf("Render Simple Points %ldms\n", stsim_pt.Time());
2462  printf("Render Paper Points %ldms\n", stpap_pt.Time());
2463  printf("Render Symbolized Boundaries %ldms\n", stasb.Time());
2464  printf("Render Plain Boundaries %ldms\n\n", stapb.Time());
2465  */
2466  return true;
2467 }
2468 
2469 bool s57chart::DCRenderText(wxMemoryDC &dcinput, const ViewPort &vp) {
2470  int i;
2471  ObjRazRules *top;
2472  ObjRazRules *crnt;
2473  ViewPort tvp = vp; // undo const TODO fix this in PLIB
2474 
2475  for (i = 0; i < PRIO_NUM; ++i) {
2476  if (ps52plib->m_nBoundaryStyle == SYMBOLIZED_BOUNDARIES)
2477  top = razRules[i][4]; // Area Symbolized Boundaries
2478  else
2479  top = razRules[i][3]; // Area Plain Boundaries
2480 
2481  while (top != NULL) {
2482  crnt = top;
2483  top = top->next; // next object
2484  crnt->sm_transform_parms = &vp_transform;
2485  ps52plib->RenderObjectToDCText(&dcinput, crnt);
2486  }
2487 
2488  top = razRules[i][2]; // LINES
2489  while (top != NULL) {
2490  crnt = top;
2491  top = top->next;
2492  crnt->sm_transform_parms = &vp_transform;
2493  ps52plib->RenderObjectToDCText(&dcinput, crnt);
2494  }
2495 
2496  if (ps52plib->m_nSymbolStyle == SIMPLIFIED)
2497  top = razRules[i][0]; // SIMPLIFIED Points
2498  else
2499  top = razRules[i][1]; // Paper Chart Points Points
2500 
2501  while (top != NULL) {
2502  crnt = top;
2503  top = top->next;
2504  crnt->sm_transform_parms = &vp_transform;
2505  ps52plib->RenderObjectToDCText(&dcinput, crnt);
2506  }
2507  }
2508 
2509  return true;
2510 }
2511 
2512 bool s57chart::IsCellOverlayType(const wxString &FullPath) {
2513  wxFileName fn(FullPath);
2514  // Get the "Usage" character
2515  wxString cname = fn.GetName();
2516  if (cname.Length() >= 3)
2517  return ((cname[2] == 'L') || (cname[2] == 'A'));
2518  else
2519  return false;
2520 }
2521 
2522 InitReturn s57chart::Init(const wxString &name, ChartInitFlag flags) {
2523  // Really can only Init and use S57 chart if the S52 Presentation Library is
2524  // present and OK
2525  if ((NULL == ps52plib) || !(ps52plib->m_bOK)) return INIT_FAIL_REMOVE;
2526 
2527  wxString ext;
2528  if (name.Upper().EndsWith(".XZ")) {
2529  ext = wxFileName(name.Left(name.Length() - 3)).GetExt();
2530 
2531  // decompress to temp file to allow seeking
2532  m_TempFilePath = wxFileName::GetTempDir() + wxFileName::GetPathSeparator() +
2533  wxFileName(name).GetName();
2534 
2535  if (!wxFileExists(m_TempFilePath) &&
2536  !DecompressXZFile(name, m_TempFilePath)) {
2537  wxRemoveFile(m_TempFilePath);
2538  return INIT_FAIL_REMOVE;
2539  }
2540  } else {
2541  m_TempFilePath = name;
2542  ext = wxFileName(name).GetExt();
2543  }
2544  m_FullPath = name;
2545 
2546  // Use a static semaphore flag to prevent recursion
2547  if (s_bInS57) {
2548  // printf("s57chart::Init() recursion..., retry\n");
2549  // wxLogMessage(_T("Recursion"));
2550  return INIT_FAIL_NOERROR;
2551  }
2552 
2553  s_bInS57++;
2554 
2555  InitReturn ret_value = INIT_OK;
2556 
2557  m_Description = name;
2558 
2559  wxFileName fn(m_TempFilePath);
2560 
2561  // Get the "Usage" character
2562  wxString cname = fn.GetName();
2563  m_usage_char = cname[2];
2564 
2565  // Establish a common reference point for the chart
2566  ref_lat = (m_FullExtent.NLAT + m_FullExtent.SLAT) / 2.;
2567  ref_lon = (m_FullExtent.WLON + m_FullExtent.ELON) / 2.;
2568 
2569  if (flags == THUMB_ONLY) {
2570  // Look for Thumbnail
2571  // LoadThumb();
2572 
2573  s_bInS57--;
2574  return INIT_OK;
2575  }
2576 
2577  if (flags == HEADER_ONLY) {
2578  if (ext == _T("000")) {
2579  if (!GetBaseFileAttr(fn.GetFullPath()))
2580  ret_value = INIT_FAIL_REMOVE;
2581  else {
2582  if (!CreateHeaderDataFromENC())
2583  ret_value = INIT_FAIL_REMOVE;
2584  else
2585  ret_value = INIT_OK;
2586  }
2587  } else if (ext == _T("S57")) {
2588  m_SENCFileName = m_TempFilePath;
2589  if (!CreateHeaderDataFromSENC())
2590  ret_value = INIT_FAIL_REMOVE;
2591  else
2592  ret_value = INIT_OK;
2593  }
2594 
2595  s_bInS57--;
2596  return ret_value;
2597  }
2598 
2599  // Full initialization from here
2600 
2601  if (!m_bbase_file_attr_known) {
2602  if (!GetBaseFileAttr(m_TempFilePath))
2603  ret_value = INIT_FAIL_REMOVE;
2604  else
2605  m_bbase_file_attr_known = true;
2606  }
2607 
2608  if (ext == _T("000")) {
2609  if (m_bbase_file_attr_known) {
2610  int sret = FindOrCreateSenc(m_FullPath);
2611  if (sret == BUILD_SENC_PENDING) {
2612  s_bInS57--;
2613  return INIT_OK;
2614  }
2615 
2616  if (sret != BUILD_SENC_OK) {
2617  if (sret == BUILD_SENC_NOK_RETRY)
2618  ret_value = INIT_FAIL_RETRY;
2619  else
2620  ret_value = INIT_FAIL_REMOVE;
2621  } else
2622  ret_value = PostInit(flags, m_global_color_scheme);
2623  }
2624 
2625  }
2626 
2627  else if (ext == _T("S57")) {
2628  m_SENCFileName = m_TempFilePath;
2629  ret_value = PostInit(flags, m_global_color_scheme);
2630  }
2631 
2632  s_bInS57--;
2633  return ret_value;
2634 }
2635 
2636 wxString s57chart::buildSENCName(const wxString &name) {
2637  wxFileName fn(name);
2638  fn.SetExt(_T("S57"));
2639  wxString file_name = fn.GetFullName();
2640 
2641  // Set the proper directory for the SENC files
2642  wxString SENCdir = g_SENCPrefix;
2643 
2644  if (SENCdir.Last() != wxFileName::GetPathSeparator())
2645  SENCdir.Append(wxFileName::GetPathSeparator());
2646 
2647 #if 1
2648  wxString source_dir = fn.GetPath(wxPATH_GET_SEPARATOR);
2649  wxCharBuffer buf = source_dir.ToUTF8();
2650  unsigned char sha1_out[20];
2651  sha1((unsigned char *)buf.data(), strlen(buf.data()), sha1_out);
2652 
2653  wxString sha1;
2654  for (unsigned int i = 0; i < 6; i++) {
2655  wxString s;
2656  s.Printf(_T("%02X"), sha1_out[i]);
2657  sha1 += s;
2658  }
2659  sha1 += _T("_");
2660  file_name.Prepend(sha1);
2661 #endif
2662 
2663  wxFileName tsfn(SENCdir);
2664  tsfn.SetFullName(file_name);
2665 
2666  return tsfn.GetFullPath();
2667 }
2668 
2669 //-----------------------------------------------------------------------------------------------
2670 // Find or Create a relevent SENC file from a given .000 ENC file
2671 // Returns with error code, and associated SENC file name in m_S57FileName
2672 //-----------------------------------------------------------------------------------------------
2673 int s57chart::FindOrCreateSenc(const wxString &name, bool b_progress) {
2674  // This method may be called for a compressed .000 cell, so check and
2675  // decompress if necessary
2676  wxString ext;
2677  if (name.Upper().EndsWith(".XZ")) {
2678  ext = wxFileName(name.Left(name.Length() - 3)).GetExt();
2679 
2680  // decompress to temp file to allow seeking
2681  m_TempFilePath = wxFileName::GetTempDir() + wxFileName::GetPathSeparator() +
2682  wxFileName(name).GetName();
2683 
2684  if (!wxFileExists(m_TempFilePath) &&
2685  !DecompressXZFile(name, m_TempFilePath)) {
2686  wxRemoveFile(m_TempFilePath);
2687  return INIT_FAIL_REMOVE;
2688  }
2689  } else {
2690  m_TempFilePath = name;
2691  ext = wxFileName(name).GetExt();
2692  }
2693  m_FullPath = name;
2694 
2695  if (!m_bbase_file_attr_known) {
2696  if (!GetBaseFileAttr(m_TempFilePath))
2697  return INIT_FAIL_REMOVE;
2698  else
2699  m_bbase_file_attr_known = true;
2700  }
2701 
2702  // Establish location for SENC files
2703  m_SENCFileName = buildSENCName(name);
2704 
2705  int build_ret_val = 1;
2706 
2707  bool bbuild_new_senc = false;
2708  m_bneed_new_thumbnail = false;
2709 
2710  wxFileName FileName000(m_TempFilePath);
2711 
2712  // Look for SENC file in the target directory
2713 
2714  wxString msg(_T("S57chart::Checking SENC file: "));
2715  msg.Append(m_SENCFileName);
2716  wxLogMessage(msg);
2717 
2718  {
2719  int force_make_senc = 0;
2720 
2721  if (::wxFileExists(m_SENCFileName)) { // SENC file exists
2722 
2723  Osenc senc;
2724  if (senc.ingestHeader(m_SENCFileName)) {
2725  bbuild_new_senc = true;
2726  wxLogMessage(_T(" Rebuilding SENC due to ingestHeader failure."));
2727  } else {
2728  int senc_file_version = senc.getSencReadVersion();
2729 
2730  int last_update = senc.getSENCReadLastUpdate();
2731 
2732  wxString str = senc.getSENCFileCreateDate();
2733  wxDateTime SENCCreateDate;
2734  SENCCreateDate.ParseFormat(str, _T("%Y%m%d"));
2735 
2736  if (SENCCreateDate.IsValid())
2737  SENCCreateDate.ResetTime(); // to midnight
2738 
2739  // wxULongLong size000 = senc.getFileSize000();
2740  // wxString ssize000 = senc.getsFileSize000();
2741 
2742  wxString senc_base_edtn = senc.getSENCReadBaseEdition();
2743  long isenc_edition;
2744  senc_base_edtn.ToLong(&isenc_edition);
2745  long ifile_edition;
2746  m_edtn000.ToLong(&ifile_edition);
2747 
2748  // Anything to do?
2749  // force_make_senc = 1;
2750  // SENC file version has to be correct for other tests to make sense
2751  if (senc_file_version != CURRENT_SENC_FORMAT_VERSION) {
2752  bbuild_new_senc = true;
2753  wxLogMessage(_T(" Rebuilding SENC due to SENC format update."));
2754  }
2755 
2756  // Senc EDTN must be the same as .000 file EDTN.
2757  // This test catches the usual case where the .000 file is updated from
2758  // the web, and all updates (.001, .002, etc.) are subsumed.
2759 
2760  else if (ifile_edition > isenc_edition) {
2761  bbuild_new_senc = true;
2762  wxLogMessage(_T(" Rebuilding SENC due to cell edition update."));
2763  wxString msg;
2764  msg = _T(" Last edition recorded in SENC: ");
2765  msg += senc_base_edtn;
2766  msg += _T(" most recent edition cell file: ");
2767  msg += m_edtn000;
2768  wxLogMessage(msg);
2769  } else {
2770  // See if there are any new update files in the ENC directory
2771  int most_recent_update_file =
2772  GetUpdateFileArray(FileName000, NULL, m_date000, m_edtn000);
2773 
2774  if (ifile_edition == isenc_edition) {
2775  if (most_recent_update_file > last_update) {
2776  bbuild_new_senc = true;
2777  wxLogMessage(
2778  _T(" Rebuilding SENC due to incremental cell update."));
2779  wxString msg;
2780  msg.Printf(
2781  _T(" Last update recorded in SENC: %d most recent ")
2782  _T("update file: %d"),
2783  last_update, most_recent_update_file);
2784  wxLogMessage(msg);
2785  }
2786  }
2787 
2788  // Make simple tests to see if the .000 file is "newer" than
2789  // the SENC file representation These tests may be redundant,
2790  // since the DSID:EDTN test above should catch new base files
2791  wxDateTime OModTime000;
2792  FileName000.GetTimes(NULL, &OModTime000, NULL);
2793  OModTime000.ResetTime(); // to midnight
2794  if (SENCCreateDate.IsValid()) {
2795  if (OModTime000.IsLaterThan(SENCCreateDate)) {
2796  wxLogMessage(
2797  _T(" Rebuilding SENC due to Senc vs cell file time ")
2798  _T("check."));
2799  bbuild_new_senc = true;
2800  }
2801  } else {
2802  bbuild_new_senc = true;
2803  wxLogMessage(
2804  _T(" Rebuilding SENC due to SENC create time invalid."));
2805  }
2806 
2807  // int Osize000l = FileName000.GetSize().GetLo();
2808  // int Osize000h = FileName000.GetSize().GetHi();
2809  // wxString t;
2810  // t.Printf(_T("%d%d"), Osize000h, Osize000l);
2811  // if( !t.IsSameAs( ssize000) )
2812  // bbuild_new_senc = true;
2813  }
2814 
2815  if (force_make_senc) bbuild_new_senc = true;
2816  }
2817  } else if (!::wxFileExists(m_SENCFileName)) // SENC file does not exist
2818  {
2819  wxLogMessage(_T(" Rebuilding SENC due to missing SENC file."));
2820  bbuild_new_senc = true;
2821  }
2822  }
2823 
2824  if (bbuild_new_senc) {
2825  m_bneed_new_thumbnail =
2826  true; // force a new thumbnail to be built in PostInit()
2827  build_ret_val = BuildSENCFile(m_TempFilePath, m_SENCFileName, b_progress);
2828 
2829  if (BUILD_SENC_PENDING == build_ret_val) return BUILD_SENC_PENDING;
2830  if (BUILD_SENC_NOK_PERMANENT == build_ret_val) return INIT_FAIL_REMOVE;
2831  if (BUILD_SENC_NOK_RETRY == build_ret_val) return INIT_FAIL_RETRY;
2832  }
2833 
2834  return INIT_OK;
2835 }
2836 
2837 InitReturn s57chart::PostInit(ChartInitFlag flags, ColorScheme cs) {
2838  // SENC file is ready, so build the RAZ structure
2839  if (0 != BuildRAZFromSENCFile(m_SENCFileName)) {
2840  wxString msg(_T(" Cannot load SENC file "));
2841  msg.Append(m_SENCFileName);
2842  wxLogMessage(msg);
2843 
2844  return INIT_FAIL_RETRY;
2845  }
2846 
2847 // Check for and if necessary rebuild Thumbnail
2848 // Going to be in the global (user) SENC file directory
2849 #if 0
2850  wxString SENCdir = g_SENCPrefix;
2851  if (SENCdir.Last() != wxFileName::GetPathSeparator())
2852  SENCdir.Append(wxFileName::GetPathSeparator());
2853 
2854  wxFileName s57File(m_SENCFileName);
2855  wxFileName ThumbFileName(SENCdir, s57File.GetName().Mid(13), _T("BMP"));
2856 
2857  if (!ThumbFileName.FileExists() || m_bneed_new_thumbnail) {
2858  BuildThumbnail(ThumbFileName.GetFullPath());
2859 
2860  // Update the member thumbdata structure
2861  if (ThumbFileName.FileExists()) {
2862  wxBitmap *pBMP_NEW;
2863 #ifdef ocpnUSE_ocpnBitmap
2864  pBMP_NEW = new ocpnBitmap;
2865 #else
2866  pBMP_NEW = new wxBitmap;
2867 #endif
2868  if (pBMP_NEW->LoadFile(ThumbFileName.GetFullPath(), wxBITMAP_TYPE_BMP)) {
2869  delete pThumbData;
2870  pThumbData = new ThumbData;
2871  m_pDIBThumbDay = pBMP_NEW;
2872  // pThumbData->pDIBThumb = pBMP_NEW;
2873  }
2874  }
2875  }
2876 #endif
2877 
2878  // Set the color scheme
2879  m_global_color_scheme = cs;
2880  SetColorScheme(cs, false);
2881 
2882  // Build array of contour values for later use by conditional symbology
2883  BuildDepthContourArray();
2884 
2885  CreateChartContext();
2886  PopulateObjectsWithContext();
2887 
2888  m_RAZBuilt = true;
2889  bReadyToRender = true;
2890 
2891  return INIT_OK;
2892 }
2893 
2894 void s57chart::ClearDepthContourArray(void) {
2895  if (m_nvaldco_alloc) {
2896  free(m_pvaldco_array);
2897  }
2898  m_nvaldco_alloc = 5;
2899  m_nvaldco = 0;
2900  m_pvaldco_array = (double *)calloc(m_nvaldco_alloc, sizeof(double));
2901 }
2902 
2903 void s57chart::BuildDepthContourArray(void) {
2904  // Build array of contour values for later use by conditional symbology
2905 
2906  if (0 == m_nvaldco_alloc) {
2907  m_nvaldco_alloc = 5;
2908  m_pvaldco_array = (double *)calloc(m_nvaldco_alloc, sizeof(double));
2909  }
2910 
2911  ObjRazRules *top;
2912  // some ENC have a lot of DEPCNT objects but they seem to store them
2913  // in VALDCO order, try to take advantage of that.
2914  double prev_valdco = 0.0;
2915 
2916  for (int i = 0; i < PRIO_NUM; ++i) {
2917  for (int j = 0; j < LUPNAME_NUM; j++) {
2918  top = razRules[i][j];
2919  while (top != NULL) {
2920  if (!strncmp(top->obj->FeatureName, "DEPCNT", 6)) {
2921  double valdco = 0.0;
2922  if (GetDoubleAttr(top->obj, "VALDCO", valdco)) {
2923  if (valdco != prev_valdco) {
2924  prev_valdco = valdco;
2925  m_nvaldco++;
2926  if (m_nvaldco > m_nvaldco_alloc) {
2927  void *tr = realloc((void *)m_pvaldco_array,
2928  m_nvaldco_alloc * 2 * sizeof(double));
2929  m_pvaldco_array = (double *)tr;
2930  m_nvaldco_alloc *= 2;
2931  }
2932  m_pvaldco_array[m_nvaldco - 1] = valdco;
2933  }
2934  }
2935  }
2936  ObjRazRules *nxx = top->next;
2937  top = nxx;
2938  }
2939  }
2940  }
2941  std::sort(m_pvaldco_array, m_pvaldco_array + m_nvaldco);
2942  SetSafetyContour();
2943 }
2944 
2945 void s57chart::SetSafetyContour(void) {
2946  // Iterate through the array of contours in this cell, choosing the best one
2947  // to render as a bold "safety contour" in the PLIB.
2948 
2949  // This method computes the smallest chart DEPCNT:VALDCO value which
2950  // is greater than or equal to the current PLIB mariner parameter
2951  // S52_MAR_SAFETY_CONTOUR
2952 
2953  double mar_safety_contour = S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR);
2954 
2955  int i = 0;
2956  if (NULL != m_pvaldco_array) {
2957  for (i = 0; i < m_nvaldco; i++) {
2958  if (m_pvaldco_array[i] >= mar_safety_contour) break;
2959  }
2960 
2961  if (i < m_nvaldco)
2962  m_next_safe_cnt = m_pvaldco_array[i];
2963  else
2964  m_next_safe_cnt = (double)1e6;
2965  } else {
2966  m_next_safe_cnt = (double)1e6;
2967  }
2968 
2969  // A safety contour greater than "Deep Depth" makes no sense...
2970  // So, declare "no suitable safety depth contour"
2971  if (m_next_safe_cnt > S52_getMarinerParam(S52_MAR_DEEP_CONTOUR))
2972  m_next_safe_cnt = (double)1e6;
2973 }
2974 
2975 void s57chart::CreateChartContext(){
2976  // Set up the chart context
2977  m_this_chart_context = (chart_context *)calloc(sizeof(chart_context), 1);
2978 }
2979 
2980 void s57chart::PopulateObjectsWithContext(){
2981 
2982  m_this_chart_context->chart = this;
2983  m_this_chart_context->chart_type = GetChartType();
2984  m_this_chart_context->vertex_buffer = GetLineVertexBuffer();
2985  m_this_chart_context->chart_scale = GetNativeScale();
2986  m_this_chart_context->pFloatingATONArray = pFloatingATONArray;
2987  m_this_chart_context->pRigidATONArray = pRigidATONArray;
2988  m_this_chart_context->safety_contour = m_next_safe_cnt;
2989  m_this_chart_context->pt2GetAssociatedObjects = &s57chart::GetAssociatedObjects;
2990 
2991 
2992  // Loop and populate all the objects
2993  ObjRazRules *top;
2994  for (int i = 0; i < PRIO_NUM; ++i) {
2995  for (int j = 0; j < LUPNAME_NUM; j++) {
2996  top = razRules[i][j];
2997  while (top != NULL) {
2998  S57Obj *obj = top->obj;
2999  obj->m_chart_context = m_this_chart_context;
3000  top = top->next;
3001  }
3002  }
3003  }
3004 }
3005 
3006 
3007 void s57chart::InvalidateCache() {
3008  delete pDIB;
3009  pDIB = NULL;
3010 }
3011 
3012 bool s57chart::BuildThumbnail(const wxString &bmpname) {
3013  bool ret_code;
3014 
3015  wxFileName ThumbFileName(bmpname);
3016 
3017  // Make the target directory if needed
3018  if (true != ThumbFileName.DirExists(ThumbFileName.GetPath())) {
3019  if (!ThumbFileName.Mkdir(ThumbFileName.GetPath())) {
3020  wxLogMessage(_T(" Cannot create BMP file directory for ") +
3021  ThumbFileName.GetFullPath());
3022  return false;
3023  }
3024  }
3025 
3026  // Set up a private ViewPort
3027  ViewPort vp;
3028 
3029  vp.clon = (m_FullExtent.ELON + m_FullExtent.WLON) / 2.;
3030  vp.clat = (m_FullExtent.NLAT + m_FullExtent.SLAT) / 2.;
3031 
3032  float ext_max = fmax((m_FullExtent.NLAT - m_FullExtent.SLAT),
3033  (m_FullExtent.ELON - m_FullExtent.WLON));
3034 
3035  vp.view_scale_ppm = (S57_THUMB_SIZE / ext_max) / (1852 * 60);
3036 
3037  vp.pix_height = S57_THUMB_SIZE;
3038  vp.pix_width = S57_THUMB_SIZE;
3039 
3040  vp.m_projection_type = PROJECTION_MERCATOR;
3041 
3042  vp.GetBBox().Set(m_FullExtent.SLAT, m_FullExtent.WLON, m_FullExtent.NLAT,
3043  m_FullExtent.ELON);
3044 
3045  vp.chart_scale = 10000000 - 1;
3046  vp.ref_scale = vp.chart_scale;
3047  vp.Validate();
3048 
3049  // cause a clean new render
3050  delete pDIB;
3051  pDIB = NULL;
3052 
3053  SetVPParms(vp);
3054 
3055  // Borrow the OBJLArray temporarily to set the object type visibility for
3056  // this render First, make a copy for the curent OBJLArray viz settings,
3057  // setting current value to invisible
3058 
3059  unsigned int OBJLCount = ps52plib->pOBJLArray->GetCount();
3060  // int *psave_viz = new int[OBJLCount];
3061  int *psave_viz = (int *)malloc(OBJLCount * sizeof(int));
3062 
3063  int *psvr = psave_viz;
3064  OBJLElement *pOLE;
3065  unsigned int iPtr;
3066 
3067  for (iPtr = 0; iPtr < OBJLCount; iPtr++) {
3068  pOLE = (OBJLElement *)(ps52plib->pOBJLArray->Item(iPtr));
3069  *psvr++ = pOLE->nViz;
3070  pOLE->nViz = 0;
3071  }
3072 
3073  // Also, save some other settings
3074  bool bsavem_bShowSoundgp = ps52plib->m_bShowSoundg;
3075  bool bsave_text = ps52plib->m_bShowS57Text;
3076 
3077  // SetDisplayCategory may clear Noshow array
3078  ps52plib->SaveObjNoshow();
3079 
3080  // Now, set up what I want for this render
3081  for (iPtr = 0; iPtr < OBJLCount; iPtr++) {
3082  pOLE = (OBJLElement *)(ps52plib->pOBJLArray->Item(iPtr));
3083  if (!strncmp(pOLE->OBJLName, "LNDARE", 6)) pOLE->nViz = 1;
3084  if (!strncmp(pOLE->OBJLName, "DEPARE", 6)) pOLE->nViz = 1;
3085  }
3086 
3087  ps52plib->m_bShowSoundg = false;
3088  ps52plib->m_bShowS57Text = false;
3089 
3090  // Use display category MARINERS_STANDARD to force use of OBJLArray
3091  DisCat dsave = ps52plib->GetDisplayCategory();
3092  ps52plib->SetDisplayCategory(MARINERS_STANDARD);
3093 
3094  ps52plib->AddObjNoshow("BRIDGE");
3095  ps52plib->AddObjNoshow("GATCON");
3096 
3097  double safety_depth = S52_getMarinerParam(S52_MAR_SAFETY_DEPTH);
3098  S52_setMarinerParam(S52_MAR_SAFETY_DEPTH, -100);
3099  double safety_contour = S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR);
3100  S52_setMarinerParam(S52_MAR_SAFETY_CONTOUR, -100);
3101 
3102 #ifdef ocpnUSE_DIBSECTION
3103  ocpnMemDC memdc, dc_org;
3104 #else
3105  wxMemoryDC memdc, dc_org;
3106 #endif
3107 
3108  // set the color scheme
3109  ps52plib->SaveColorScheme();
3110  ps52plib->SetPLIBColorScheme("DAY", ChartCtxFactory());
3111  // Do the render
3112  DoRenderViewOnDC(memdc, vp, DC_RENDER_ONLY, true);
3113 
3114  // Release the DIB
3115  memdc.SelectObject(wxNullBitmap);
3116 
3117  // Restore the plib to previous state
3118  psvr = psave_viz;
3119  for (iPtr = 0; iPtr < OBJLCount; iPtr++) {
3120  pOLE = (OBJLElement *)(ps52plib->pOBJLArray->Item(iPtr));
3121  pOLE->nViz = *psvr++;
3122  }
3123 
3124  ps52plib->SetDisplayCategory(dsave);
3125  ps52plib->RestoreObjNoshow();
3126 
3127  ps52plib->RemoveObjNoshow("BRIDGE");
3128  ps52plib->RemoveObjNoshow("GATCON");
3129 
3130  ps52plib->m_bShowSoundg = bsavem_bShowSoundgp;
3131  ps52plib->m_bShowS57Text = bsave_text;
3132 
3133  S52_setMarinerParam(S52_MAR_SAFETY_DEPTH, safety_depth);
3134  S52_setMarinerParam(S52_MAR_SAFETY_CONTOUR, safety_contour);
3135 
3136  // Reset the color scheme
3137  ps52plib->RestoreColorScheme();
3138 
3139  // delete psave_viz;
3140  free(psave_viz);
3141 
3142  // Clone pDIB into pThumbData;
3143  wxBitmap *pBMP;
3144 
3145  pBMP = new wxBitmap(vp.pix_width, vp.pix_height /*, BPP*/);
3146 
3147  wxMemoryDC dc_clone;
3148  dc_clone.SelectObject(*pBMP);
3149 
3150  pDIB->SelectIntoDC(dc_org);
3151 
3152  dc_clone.Blit(0, 0, vp.pix_width, vp.pix_height, (wxDC *)&dc_org, 0, 0);
3153 
3154  dc_clone.SelectObject(wxNullBitmap);
3155  dc_org.SelectObject(wxNullBitmap);
3156 
3157  // Save the file
3158  ret_code = pBMP->SaveFile(ThumbFileName.GetFullPath(), wxBITMAP_TYPE_BMP);
3159 
3160  delete pBMP;
3161 
3162  return ret_code;
3163 }
3164 
3165 #include <wx/arrimpl.cpp>
3166 WX_DEFINE_ARRAY_PTR(float *, MyFloatPtrArray);
3167 
3168 // Read the .000 ENC file and create required Chartbase data structures
3169 bool s57chart::CreateHeaderDataFromENC(void) {
3170  if (!InitENCMinimal(m_TempFilePath)) {
3171  wxString msg(_T(" Cannot initialize ENC file "));
3172  msg.Append(m_TempFilePath);
3173  wxLogMessage(msg);
3174 
3175  return false;
3176  }
3177 
3178  OGRFeature *pFeat;
3179  int catcov;
3180  float LatMax, LatMin, LonMax, LonMin;
3181  LatMax = -90.;
3182  LatMin = 90.;
3183  LonMax = -179.;
3184  LonMin = 179.;
3185 
3186  m_pCOVRTablePoints = NULL;
3187  m_pCOVRTable = NULL;
3188 
3189  // Create arrays to hold geometry objects temporarily
3190  MyFloatPtrArray *pAuxPtrArray = new MyFloatPtrArray;
3191  std::vector<int> auxCntArray, noCovrCntArray;
3192 
3193  MyFloatPtrArray *pNoCovrPtrArray = new MyFloatPtrArray;
3194 
3195  // Get the first M_COVR object
3196  pFeat = GetChartFirstM_COVR(catcov);
3197 
3198  while (pFeat) {
3199  // Get the next M_COVR feature, and create possible additional entries
3200  // for COVR
3201  OGRPolygon *poly = (OGRPolygon *)(pFeat->GetGeometryRef());
3202  OGRLinearRing *xring = poly->getExteriorRing();
3203 
3204  int npt = xring->getNumPoints();
3205  int usedpts = 0;
3206 
3207  float *pf = NULL;
3208  float *pfr = NULL;
3209 
3210  if (npt >= 3) {
3211  // pf = (float *) malloc( 2 * sizeof(float) );
3212 
3213  OGRPoint last_p;
3214  OGRPoint p;
3215  for (int i = 0; i < npt; i++) {
3216  xring->getPoint(i, &p);
3217  if (i >
3218  3) { // We need at least 3 points, so make sure the first 3 pass
3219  float xdelta =
3220  fmax(last_p.getX(), p.getX()) - fmin(last_p.getX(), p.getX());
3221  float ydelta =
3222  fmax(last_p.getY(), p.getY()) - fmin(last_p.getY(), p.getY());
3223  if (xdelta < 0.001 &&
3224  ydelta < 0.001) { // Magic number, 0.001 degrees ~= 111 meters on
3225  // the equator...
3226  continue;
3227  }
3228  }
3229  last_p = p;
3230  usedpts++;
3231  pf = (float *)realloc(pf, 2 * usedpts * sizeof(float));
3232  pfr = &pf[2 * (usedpts - 1)];
3233 
3234  if (catcov == 1) {
3235  LatMax = fmax(LatMax, p.getY());
3236  LatMin = fmin(LatMin, p.getY());
3237  LonMax = fmax(LonMax, p.getX());
3238  LonMin = fmin(LonMin, p.getX());
3239  }
3240 
3241  pfr[0] = p.getY(); // lat
3242  pfr[1] = p.getX(); // lon
3243  }
3244 
3245  if (catcov == 1) {
3246  pAuxPtrArray->Add(pf);
3247  auxCntArray.push_back(usedpts);
3248  } else if (catcov == 2) {
3249  pNoCovrPtrArray->Add(pf);
3250  noCovrCntArray.push_back(usedpts);
3251  }
3252  }
3253 
3254  delete pFeat;
3255  pFeat = GetChartNextM_COVR(catcov);
3256  DEBUG_LOG << "used " << usedpts << " points";
3257  } // while
3258 
3259  // Allocate the storage
3260 
3261  m_nCOVREntries = auxCntArray.size();
3262 
3263  // Create new COVR entries
3264 
3265  if (m_nCOVREntries >= 1) {
3266  m_pCOVRTablePoints = (int *)malloc(m_nCOVREntries * sizeof(int));
3267  m_pCOVRTable = (float **)malloc(m_nCOVREntries * sizeof(float *));
3268 
3269  for (unsigned int j = 0; j < (unsigned int)m_nCOVREntries; j++) {
3270  m_pCOVRTablePoints[j] = auxCntArray[j];
3271  m_pCOVRTable[j] = pAuxPtrArray->Item(j);
3272  }
3273  }
3274 
3275  else // strange case, found no CATCOV=1 M_COVR objects
3276  {
3277  wxString msg(_T(" ENC contains no useable M_COVR, CATCOV=1 features: "));
3278  msg.Append(m_TempFilePath);
3279  wxLogMessage(msg);
3280  }
3281 
3282  // And for the NoCovr regions
3283  m_nNoCOVREntries = noCovrCntArray.size();
3284 
3285  if (m_nNoCOVREntries) {
3286  // Create new NoCOVR entries
3287  m_pNoCOVRTablePoints = (int *)malloc(m_nNoCOVREntries * sizeof(int));
3288  m_pNoCOVRTable = (float **)malloc(m_nNoCOVREntries * sizeof(float *));
3289 
3290  for (unsigned int j = 0; j < (unsigned int)m_nNoCOVREntries; j++) {
3291  m_pNoCOVRTablePoints[j] = noCovrCntArray[j];
3292  m_pNoCOVRTable[j] = pNoCovrPtrArray->Item(j);
3293  }
3294  } else {
3295  m_pNoCOVRTablePoints = NULL;
3296  m_pNoCOVRTable = NULL;
3297  }
3298 
3299  delete pAuxPtrArray;
3300  delete pNoCovrPtrArray;
3301 
3302  if (0 == m_nCOVREntries) { // fallback
3303  wxString msg(_T(" ENC contains no M_COVR features: "));
3304  msg.Append(m_TempFilePath);
3305  wxLogMessage(msg);
3306 
3307  msg = _T(" Calculating Chart Extents as fallback.");
3308  wxLogMessage(msg);
3309 
3310  OGREnvelope Env;
3311 
3312  // Get the reader
3313  S57Reader *pENCReader = m_pENCDS->GetModule(0);
3314 
3315  if (pENCReader->GetExtent(&Env, true) == OGRERR_NONE) {
3316  LatMax = Env.MaxY;
3317  LonMax = Env.MaxX;
3318  LatMin = Env.MinY;
3319  LonMin = Env.MinX;
3320 
3321  m_nCOVREntries = 1;
3322  m_pCOVRTablePoints = (int *)malloc(sizeof(int));
3323  *m_pCOVRTablePoints = 4;
3324  m_pCOVRTable = (float **)malloc(sizeof(float *));
3325  float *pf = (float *)malloc(2 * 4 * sizeof(float));
3326  *m_pCOVRTable = pf;
3327  float *pfe = pf;
3328 
3329  *pfe++ = LatMax;
3330  *pfe++ = LonMin;
3331 
3332  *pfe++ = LatMax;
3333  *pfe++ = LonMax;
3334 
3335  *pfe++ = LatMin;
3336  *pfe++ = LonMax;
3337 
3338  *pfe++ = LatMin;
3339  *pfe++ = LonMin;
3340 
3341  } else {
3342  wxString msg(_T(" Cannot calculate Extents for ENC: "));
3343  msg.Append(m_TempFilePath);
3344  wxLogMessage(msg);
3345 
3346  return false; // chart is completely unusable
3347  }
3348  }
3349 
3350  // Populate the chart's extent structure
3351  m_FullExtent.NLAT = LatMax;
3352  m_FullExtent.SLAT = LatMin;
3353  m_FullExtent.ELON = LonMax;
3354  m_FullExtent.WLON = LonMin;
3355  m_bExtentSet = true;
3356 
3357  // Set the chart scale
3358  m_Chart_Scale = GetENCScale();
3359 
3360  wxString nice_name;
3361  GetChartNameFromTXT(m_TempFilePath, nice_name);
3362  m_Name = nice_name;
3363 
3364  return true;
3365 }
3366 
3367 // Read the .S57 oSENC file (CURRENT_SENC_FORMAT_VERSION >= 200) and create
3368 // required Chartbase data structures
3369 bool s57chart::CreateHeaderDataFromoSENC(void) {
3370  bool ret_val = true;
3371 
3372  wxFFileInputStream fpx(m_SENCFileName);
3373  if (!fpx.IsOk()) {
3374  if (!::wxFileExists(m_SENCFileName)) {
3375  wxString msg(_T(" Cannot open SENC file "));
3376  msg.Append(m_SENCFileName);
3377  wxLogMessage(msg);
3378  }
3379  return false;
3380  }
3381 
3382  Osenc senc;
3383  if (senc.ingestHeader(m_SENCFileName)) {
3384  return false;
3385  } else {
3386  // Get Chartbase member elements from the oSENC file records in the header
3387 
3388  // Scale
3389  m_Chart_Scale = senc.getSENCReadScale();
3390 
3391  // Nice Name
3392  m_Name = senc.getReadName();
3393 
3394  // ID
3395  m_ID = senc.getReadID();
3396 
3397  // Extents
3398  Extent &ext = senc.getReadExtent();
3399 
3400  m_FullExtent.ELON = ext.ELON;
3401  m_FullExtent.WLON = ext.WLON;
3402  m_FullExtent.NLAT = ext.NLAT;
3403  m_FullExtent.SLAT = ext.SLAT;
3404  m_bExtentSet = true;
3405 
3406  // Coverage areas
3407  SENCFloatPtrArray &AuxPtrArray = senc.getSENCReadAuxPointArray();
3408  std::vector<int> &AuxCntArray = senc.getSENCReadAuxPointCountArray();
3409 
3410  m_nCOVREntries = AuxCntArray.size();
3411 
3412  m_pCOVRTablePoints = (int *)malloc(m_nCOVREntries * sizeof(int));
3413  m_pCOVRTable = (float **)malloc(m_nCOVREntries * sizeof(float *));
3414 
3415  for (unsigned int j = 0; j < (unsigned int)m_nCOVREntries; j++) {
3416  m_pCOVRTablePoints[j] = AuxCntArray[j];
3417  m_pCOVRTable[j] = (float *)malloc(AuxCntArray[j] * 2 * sizeof(float));
3418  memcpy(m_pCOVRTable[j], AuxPtrArray[j],
3419  AuxCntArray[j] * 2 * sizeof(float));
3420  }
3421 
3422  // NoCoverage areas
3423  SENCFloatPtrArray &NoCovrPtrArray = senc.getSENCReadNOCOVRPointArray();
3424  std::vector<int> &NoCovrCntArray = senc.getSENCReadNOCOVRPointCountArray();
3425 
3426  m_nNoCOVREntries = NoCovrCntArray.size();
3427 
3428  if (m_nNoCOVREntries) {
3429  // Create new NoCOVR entries
3430  m_pNoCOVRTablePoints = (int *)malloc(m_nNoCOVREntries * sizeof(int));
3431  m_pNoCOVRTable = (float **)malloc(m_nNoCOVREntries * sizeof(float *));
3432 
3433  for (unsigned int j = 0; j < (unsigned int)m_nNoCOVREntries; j++) {
3434  int npoints = NoCovrCntArray[j];
3435  m_pNoCOVRTablePoints[j] = npoints;
3436  m_pNoCOVRTable[j] = (float *)malloc(npoints * 2 * sizeof(float));
3437  memcpy(m_pNoCOVRTable[j], NoCovrPtrArray[j],
3438  npoints * 2 * sizeof(float));
3439  }
3440  }
3441 
3442  // Misc
3443  m_SE = m_edtn000;
3444  m_datum_str = _T("WGS84");
3445  m_SoundingsDatum = _T("MEAN LOWER LOW WATER");
3446 
3447  int senc_file_version = senc.getSencReadVersion();
3448 
3449  int last_update = senc.getSENCReadLastUpdate();
3450 
3451  wxString str = senc.getSENCFileCreateDate();
3452  wxDateTime SENCCreateDate;
3453  SENCCreateDate.ParseFormat(str, _T("%Y%m%d"));
3454 
3455  if (SENCCreateDate.IsValid()) SENCCreateDate.ResetTime(); // to midnight
3456 
3457  wxString senc_base_edtn = senc.getSENCReadBaseEdition();
3458  }
3459 
3460  return ret_val;
3461 }
3462 
3463 // Read the .S57 SENC file and create required Chartbase data structures
3464 bool s57chart::CreateHeaderDataFromSENC(void) {
3465  if (CURRENT_SENC_FORMAT_VERSION >= 200) return CreateHeaderDataFromoSENC();
3466 
3467  return false;
3468 }
3469 
3470 /* This method returns the smallest chart DEPCNT:VALDCO value which
3471  is greater than or equal to the specified value
3472  */
3473 bool s57chart::GetNearestSafeContour(double safe_cnt, double &next_safe_cnt) {
3474  int i = 0;
3475  if (NULL != m_pvaldco_array) {
3476  for (i = 0; i < m_nvaldco; i++) {
3477  if (m_pvaldco_array[i] >= safe_cnt) break;
3478  }
3479 
3480  if (i < m_nvaldco)
3481  next_safe_cnt = m_pvaldco_array[i];
3482  else
3483  next_safe_cnt = (double)1e6;
3484  return true;
3485  } else {
3486  next_safe_cnt = (double)1e6;
3487  return false;
3488  }
3489 }
3490 
3491 /*
3492  --------------------------------------------------------------------------
3493  Build a list of "associated" DEPARE and DRGARE objects from a given
3494  object. to be "associated" means to be physically intersecting,
3495  overlapping, or contained within, depending upon the geometry type
3496  of the given object.
3497  --------------------------------------------------------------------------
3498  */
3499 
3500 std::list<S57Obj*> *s57chart::GetAssociatedObjects(S57Obj *obj) {
3501  int disPrioIdx;
3502  bool gotit;
3503 
3504  std::list<S57Obj*> *pobj_list = new std::list<S57Obj*>();
3505 
3506  double lat, lon;
3507  fromSM((obj->x * obj->x_rate) + obj->x_origin,
3508  (obj->y * obj->y_rate) + obj->y_origin, ref_lat, ref_lon, &lat, &lon);
3509  // What is the entry object geometry type?
3510 
3511  switch (obj->Primitive_type) {
3512  case GEO_POINT:
3513  // n.b. This logic not perfectly right for LINE and AREA features
3514  // It uses the object reference point for testing, instead of the
3515  // decomposed line or boundary geometry. Thus, it may fail on some
3516  // intersecting relationships. Judged acceptable, in favor of performance
3517  // implications. DSR
3518  case GEO_LINE:
3519  case GEO_AREA:
3520  ObjRazRules *top;
3521  disPrioIdx = 1; // PRIO_GROUP1:S57 group 1 filled areas
3522 
3523  gotit = false;
3524  top = razRules[disPrioIdx][3]; // PLAIN_BOUNDARIES
3525  while (top != NULL) {
3526  if (top->obj->bIsAssociable) {
3527  if (top->obj->BBObj.Contains(lat, lon)) {
3528  if (IsPointInObjArea(lat, lon, 0.0, top->obj)) {
3529  pobj_list->push_back(top->obj);
3530  gotit = true;
3531  break;
3532  }
3533  }
3534  }
3535 
3536  ObjRazRules *nxx = top->next;
3537  top = nxx;
3538  }
3539 
3540  if (!gotit) {
3541  top = razRules[disPrioIdx][4]; // SYMBOLIZED_BOUNDARIES
3542  while (top != NULL) {
3543  if (top->obj->bIsAssociable) {
3544  if (top->obj->BBObj.Contains(lat, lon)) {
3545  if (IsPointInObjArea(lat, lon, 0.0, top->obj)) {
3546  pobj_list->push_back(top->obj);
3547  break;
3548  }
3549  }
3550  }
3551 
3552  ObjRazRules *nxx = top->next;
3553  top = nxx;
3554  }
3555  }
3556 
3557  break;
3558 
3559  default:
3560  break;
3561  }
3562 
3563  return pobj_list;
3564 }
3565 
3566 void s57chart::GetChartNameFromTXT(const wxString &FullPath, wxString &Name) {
3567  wxFileName fn(FullPath);
3568 
3569  wxString target_name = fn.GetName();
3570  target_name.RemoveLast();
3571 
3572  wxString dir_name = fn.GetPath();
3573 
3574  wxDir dir(dir_name); // The directory containing the file
3575 
3576  wxArrayString FileList;
3577 
3578  dir.GetAllFiles(fn.GetPath(), &FileList); // list all the files
3579 
3580  // Iterate on the file list...
3581 
3582  bool found_name = false;
3583  wxString name;
3584  name.Clear();
3585 
3586  for (unsigned int j = 0; j < FileList.GetCount(); j++) {
3587  wxFileName file(FileList[j]);
3588  if (((file.GetExt()).MakeUpper()) == _T("TXT")) {
3589  // Look for the line beginning with the name of the .000 file
3590  wxTextFile text_file(file.GetFullPath());
3591 
3592  bool file_ok = true;
3593  // Suppress log messages on bad file reads
3594  {
3595  wxLogNull logNo;
3596  if (!text_file.Open()) {
3597  if (!text_file.Open(wxConvISO8859_1)) file_ok = false;
3598  }
3599  }
3600 
3601  if (file_ok) {
3602  wxString str = text_file.GetFirstLine();
3603  while (!text_file.Eof()) {
3604  if (0 == target_name.CmpNoCase(
3605  str.Mid(0, target_name.Len()))) { // found it
3606  wxString tname = str.AfterFirst('-');
3607  name = tname.AfterFirst(' ');
3608  found_name = true;
3609  break;
3610  } else {
3611  str = text_file.GetNextLine();
3612  }
3613  }
3614  } else {
3615  wxString msg(_T(" Error Reading ENC .TXT file: "));
3616  msg.Append(file.GetFullPath());
3617  wxLogMessage(msg);
3618  }
3619 
3620  text_file.Close();
3621 
3622  if (found_name) break;
3623  }
3624  }
3625 
3626  Name = name;
3627 }
3628 
3629 //---------------------------------------------------------------------------------
3630 // S57 Database methods
3631 //---------------------------------------------------------------------------------
3632 
3633 //-------------------------------
3634 //
3635 // S57 OBJECT ACCESSOR SECTION
3636 //
3637 //-------------------------------
3638 
3639 const char *s57chart::getName(OGRFeature *feature) {
3640  return feature->GetDefnRef()->GetName();
3641 }
3642 
3643 static int ExtensionCompare(const wxString &first, const wxString &second) {
3644  wxFileName fn1(first);
3645  wxFileName fn2(second);
3646  wxString ext1(fn1.GetExt());
3647  wxString ext2(fn2.GetExt());
3648 
3649  return ext1.Cmp(ext2);
3650 }
3651 
3652 int s57chart::GetUpdateFileArray(const wxFileName file000,
3653  wxArrayString *UpFiles, wxDateTime date000,
3654  wxString edtn000) {
3655  wxString DirName000 =
3656  file000.GetPath((int)(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME));
3657  wxDir dir(DirName000);
3658  if (!dir.IsOpened()) {
3659  DirName000.Prepend(wxFileName::GetPathSeparator());
3660  DirName000.Prepend(_T("."));
3661  dir.Open(DirName000);
3662  if (!dir.IsOpened()) {
3663  return 0;
3664  }
3665  }
3666 
3667  int flags = wxDIR_DEFAULT;
3668 
3669  // Check dir structure
3670  // We look to see if the directory one level above where the .000 file is
3671  // located happens to be "perfectly numeric" in name. If so, the dataset is
3672  // presumed to be organized with each update in its own directory. So, we
3673  // search for updates from this level, recursing into subdirs.
3674  wxFileName fnDir(DirName000);
3675  fnDir.RemoveLastDir();
3676  wxString sdir = fnDir.GetPath();
3677  wxFileName fnTest(sdir);
3678  wxString sname = fnTest.GetName();
3679  long tmps;
3680  if (sname.ToLong(&tmps)) {
3681  dir.Open(sdir);
3682  DirName000 = sdir;
3683  flags |= wxDIR_DIRS;
3684  }
3685 
3686  wxString ext;
3687  wxArrayString *dummy_array;
3688  int retval = 0;
3689 
3690  if (UpFiles == NULL)
3691  dummy_array = new wxArrayString;
3692  else
3693  dummy_array = UpFiles;
3694 
3695  wxArrayString possibleFiles;
3696  wxDir::GetAllFiles(DirName000, &possibleFiles, wxEmptyString, flags);
3697 
3698  for (unsigned int i = 0; i < possibleFiles.GetCount(); i++) {
3699  wxString filename(possibleFiles[i]);
3700 
3701  wxFileName file(filename);
3702  ext = file.GetExt();
3703 
3704  long tmp;
3705  // Files of interest have the same base name is the target .000 cell,
3706  // and have numeric extension
3707  if (ext.ToLong(&tmp) && (file.GetName() == file000.GetName())) {
3708  wxString FileToAdd = filename;
3709 
3710  wxCharBuffer buffer =
3711  FileToAdd.ToUTF8(); // Check file namme for convertability
3712 
3713  if (buffer.data() && !filename.IsSameAs(_T("CATALOG.031"),
3714  false)) // don't process catalogs
3715  {
3716  // We must check the update file for validity
3717  // 1. Is update field DSID:EDTN equal to base .000 file
3718  // DSID:EDTN?
3719  // 2. Is update file DSID.ISDT greater than or equal to base
3720  // .000 file DSID:ISDT
3721 
3722  wxDateTime umdate;
3723  wxString sumdate;
3724  wxString umedtn;
3725  DDFModule *poModule = new DDFModule();
3726  if (!poModule->Open(FileToAdd.mb_str())) {
3727  wxString msg(
3728  _T(" s57chart::BuildS57File Unable to open update file "));
3729  msg.Append(FileToAdd);
3730  wxLogMessage(msg);
3731  } else {
3732  poModule->Rewind();
3733 
3734  // Read and parse DDFRecord 0 to get some interesting data
3735  // n.b. assumes that the required fields will be in Record 0.... Is
3736  // this always true?
3737 
3738  DDFRecord *pr = poModule->ReadRecord(); // Record 0
3739  // pr->Dump(stdout);
3740 
3741  // Fetch ISDT(Issue Date)
3742  char *u = NULL;
3743  if (pr) {
3744  u = (char *)(pr->GetStringSubfield("DSID", 0, "ISDT", 0));
3745 
3746  if (u) {
3747  if (strlen(u)) sumdate = wxString(u, wxConvUTF8);
3748  }
3749  } else {
3750  wxString msg(
3751  _T(" s57chart::BuildS57File DDFRecord 0 does not contain ")
3752  _T("DSID:ISDT in update file "));
3753  msg.Append(FileToAdd);
3754  wxLogMessage(msg);
3755 
3756  sumdate = _T("20000101"); // backstop, very early, so wont be used
3757  }
3758 
3759  umdate.ParseFormat(sumdate, _T("%Y%m%d"));
3760  if (!umdate.IsValid())
3761  umdate.ParseFormat(_T("20000101"), _T("%Y%m%d"));
3762 
3763  umdate.ResetTime();
3764  if (!umdate.IsValid())
3765  int yyp = 4;
3766 
3767  // Fetch the EDTN(Edition) field
3768  if (pr) {
3769  u = NULL;
3770  u = (char *)(pr->GetStringSubfield("DSID", 0, "EDTN", 0));
3771  if (u) {
3772  if (strlen(u)) umedtn = wxString(u, wxConvUTF8);
3773  }
3774  } else {
3775  wxString msg(
3776  _T(" s57chart::BuildS57File DDFRecord 0 does not contain ")
3777  _T("DSID:EDTN in update file "));
3778  msg.Append(FileToAdd);
3779  wxLogMessage(msg);
3780 
3781  umedtn = _T("1"); // backstop
3782  }
3783  }
3784 
3785  delete poModule;
3786 
3787  if ((!umdate.IsEarlierThan(date000)) &&
3788  (umedtn.IsSameAs(edtn000))) // Note polarity on Date compare....
3789  dummy_array->Add(FileToAdd); // Looking for umdate >= m_date000
3790  }
3791  }
3792  }
3793 
3794  // Sort the candidates
3795  dummy_array->Sort(ExtensionCompare);
3796 
3797  // Get the update number of the last in the list
3798  if (dummy_array->GetCount()) {
3799  wxString Last = dummy_array->Last();
3800  wxFileName fnl(Last);
3801  ext = fnl.GetExt();
3802  wxCharBuffer buffer = ext.ToUTF8();
3803  if (buffer.data()) retval = atoi(buffer.data());
3804  }
3805 
3806  if (UpFiles == NULL) delete dummy_array;
3807 
3808  return retval;
3809 }
3810 
3811 int s57chart::ValidateAndCountUpdates(const wxFileName file000,
3812  const wxString CopyDir,
3813  wxString &LastUpdateDate,
3814  bool b_copyfiles) {
3815  int retval = 0;
3816 
3817  // wxString DirName000 = file000.GetPath((int)(wxPATH_GET_SEPARATOR |
3818  // wxPATH_GET_VOLUME)); wxDir dir(DirName000);
3819  wxArrayString *UpFiles = new wxArrayString;
3820  retval = GetUpdateFileArray(file000, UpFiles, m_date000, m_edtn000);
3821 
3822  if (UpFiles->GetCount()) {
3823  // The s57reader of ogr requires that update set be sequentially
3824  // complete to perform all the updates. However, some NOAA ENC
3825  // distributions are not complete, as apparently some interim updates
3826  // have been withdrawn. Example: as of 20 Dec, 2005, the update set
3827  // for US5MD11M.000 includes US5MD11M.017, ...018, and ...019. Updates
3828  // 001 through 016 are missing.
3829  //
3830  // Workaround.
3831  // Create temporary dummy update files to fill out the set before
3832  // invoking ogr file open/ingest. Delete after SENC file create
3833  // finishes. Set starts with .000, which has the effect of copying the
3834  // base file to the working dir
3835 
3836  bool chain_broken_mssage_shown = false;
3837 
3838  if (b_copyfiles) {
3839  m_tmpup_array =
3840  new wxArrayString; // save a list of created files for later erase
3841 
3842  for (int iff = 0; iff < retval + 1; iff++) {
3843  wxFileName ufile(m_TempFilePath);
3844  wxString sext;
3845  sext.Printf(_T("%03d"), iff);
3846  ufile.SetExt(sext);
3847 
3848  // Create the target update file name
3849  wxString cp_ufile = CopyDir;
3850  if (cp_ufile.Last() != ufile.GetPathSeparator())
3851  cp_ufile.Append(ufile.GetPathSeparator());
3852 
3853  cp_ufile.Append(ufile.GetFullName());
3854 
3855  // Explicit check for a short update file, possibly left over from
3856  // a crash...
3857  int flen = 0;
3858  if (ufile.FileExists()) {
3859  wxFile uf(ufile.GetFullPath());
3860  if (uf.IsOpened()) {
3861  flen = uf.Length();
3862  uf.Close();
3863  }
3864  }
3865 
3866  if (ufile.FileExists() &&
3867  (flen > 25)) // a valid update file or base file
3868  {
3869  // Copy the valid file to the SENC directory
3870  bool cpok = wxCopyFile(ufile.GetFullPath(), cp_ufile);
3871  if (!cpok) {
3872  wxString msg(_T(" Cannot copy temporary working ENC file "));
3873  msg.Append(ufile.GetFullPath());
3874  msg.Append(_T(" to "));
3875  msg.Append(cp_ufile);
3876  wxLogMessage(msg);
3877  }
3878  }
3879 
3880  else {
3881  // Create a dummy ISO8211 file with no real content
3882  // Correct this. We should break the walk, and notify the user See
3883  // FS#1406
3884 
3885  if (!chain_broken_mssage_shown) {
3886  OCPNMessageBox(
3887  NULL,
3888  _("S57 Cell Update chain incomplete.\nENC features may be "
3889  "incomplete or inaccurate.\nCheck the logfile for details."),
3890  _("OpenCPN Create SENC Warning"), wxOK | wxICON_EXCLAMATION,
3891  30);
3892  chain_broken_mssage_shown = true;
3893  }
3894 
3895  wxString msg(
3896  _T("WARNING---ENC Update chain incomplete. Substituting NULL ")
3897  _T("update file: "));
3898  msg += ufile.GetFullName();
3899  wxLogMessage(msg);
3900  wxLogMessage(_T(" Subsequent ENC updates may produce errors."));
3901  wxLogMessage(
3902  _T(" This ENC exchange set should be updated and SENCs ")
3903  _T("rebuilt."));
3904 
3905  bool bstat;
3906  DDFModule *dupdate = new DDFModule;
3907  dupdate->Initialize('3', 'L', 'E', '1', '0', "!!!", 3, 4, 4);
3908  bstat = !(dupdate->Create(cp_ufile.mb_str()) == 0);
3909  delete dupdate;
3910 
3911  if (!bstat) {
3912  wxString msg(_T(" Error creating dummy update file: "));
3913  msg.Append(cp_ufile);
3914  wxLogMessage(msg);
3915  }
3916  }
3917 
3918  m_tmpup_array->Add(cp_ufile);
3919  }
3920  }
3921 
3922  // Extract the date field from the last of the update files
3923  // which is by definition a valid, present update file....
3924 
3925  wxFileName lastfile(m_TempFilePath);
3926  wxString last_sext;
3927  last_sext.Printf(_T("%03d"), retval);
3928  lastfile.SetExt(last_sext);
3929 
3930  bool bSuccess;
3931  DDFModule oUpdateModule;
3932 
3933  // bSuccess = !(oUpdateModule.Open(
3934  // m_tmpup_array->Last().mb_str(), TRUE ) == 0);
3935  bSuccess =
3936  !(oUpdateModule.Open(lastfile.GetFullPath().mb_str(), TRUE) == 0);
3937 
3938  if (bSuccess) {
3939  // Get publish/update date
3940  oUpdateModule.Rewind();
3941  DDFRecord *pr = oUpdateModule.ReadRecord(); // Record 0
3942 
3943  int nSuccess;
3944  char *u = NULL;
3945 
3946  if (pr)
3947  u = (char *)(pr->GetStringSubfield("DSID", 0, "ISDT", 0, &nSuccess));
3948 
3949  if (u) {
3950  if (strlen(u)) {
3951  LastUpdateDate = wxString(u, wxConvUTF8);
3952  }
3953  } else {
3954  wxDateTime now = wxDateTime::Now();
3955  LastUpdateDate = now.Format(_T("%Y%m%d"));
3956  }
3957  }
3958  }
3959 
3960  delete UpFiles;
3961  return retval;
3962 }
3963 
3964 wxString s57chart::GetISDT(void) {
3965  if (m_date000.IsValid())
3966  return m_date000.Format(_T("%Y%m%d"));
3967  else
3968  return _T("Unknown");
3969 }
3970 
3971 bool s57chart::GetBaseFileAttr(const wxString &file000) {
3972  if (!wxFileName::FileExists(file000)) return false;
3973 
3974  wxString FullPath000 = file000;
3975  DDFModule *poModule = new DDFModule();
3976  if (!poModule->Open(FullPath000.mb_str())) {
3977  wxString msg(_T(" s57chart::BuildS57File Unable to open "));
3978  msg.Append(FullPath000);
3979  wxLogMessage(msg);
3980  delete poModule;
3981  return false;
3982  }
3983 
3984  poModule->Rewind();
3985 
3986  // Read and parse DDFRecord 0 to get some interesting data
3987  // n.b. assumes that the required fields will be in Record 0.... Is this
3988  // always true?
3989 
3990  DDFRecord *pr = poModule->ReadRecord(); // Record 0
3991  // pr->Dump(stdout);
3992 
3993  // Fetch the Geo Feature Count, or something like it....
3994  m_nGeoRecords = pr->GetIntSubfield("DSSI", 0, "NOGR", 0);
3995  if (!m_nGeoRecords) {
3996  wxString msg(
3997  _T(" s57chart::BuildS57File DDFRecord 0 does not contain ")
3998  _T("DSSI:NOGR "));
3999  wxLogMessage(msg);
4000 
4001  m_nGeoRecords = 1; // backstop
4002  }
4003 
4004  // Use ISDT(Issue Date) here, which is the same as UADT(Updates Applied) for
4005  // .000 files
4006  wxString date000;
4007  char *u = (char *)(pr->GetStringSubfield("DSID", 0, "ISDT", 0));
4008  if (u)
4009  date000 = wxString(u, wxConvUTF8);
4010  else {
4011  wxString msg(
4012  _T(" s57chart::BuildS57File DDFRecord 0 does not contain ")
4013  _T("DSID:ISDT "));
4014  wxLogMessage(msg);
4015 
4016  date000 =
4017  _T("20000101"); // backstop, very early, so any new files will update?
4018  }
4019  m_date000.ParseFormat(date000, _T("%Y%m%d"));
4020  if (!m_date000.IsValid()) m_date000.ParseFormat(_T("20000101"), _T("%Y%m%d"));
4021 
4022  m_date000.ResetTime();
4023 
4024  // Fetch the EDTN(Edition) field
4025  u = (char *)(pr->GetStringSubfield("DSID", 0, "EDTN", 0));
4026  if (u)
4027  m_edtn000 = wxString(u, wxConvUTF8);
4028  else {
4029  wxString msg(
4030  _T(" s57chart::BuildS57File DDFRecord 0 does not contain ")
4031  _T("DSID:EDTN "));
4032  wxLogMessage(msg);
4033 
4034  m_edtn000 = _T("1"); // backstop
4035  }
4036 
4037  m_SE = m_edtn000;
4038 
4039  // Fetch the Native Scale by reading more records until DSPM is found
4040  m_native_scale = 0;
4041  for (; pr != NULL; pr = poModule->ReadRecord()) {
4042  if (pr->FindField("DSPM") != NULL) {
4043  m_native_scale = pr->GetIntSubfield("DSPM", 0, "CSCL", 0);
4044  break;
4045  }
4046  }
4047  if (!m_native_scale) {
4048  wxString msg(_T(" s57chart::BuildS57File ENC not contain DSPM:CSCL "));
4049  wxLogMessage(msg);
4050 
4051  m_native_scale = 1000; // backstop
4052  }
4053 
4054  delete poModule;
4055 
4056  return true;
4057 }
4058 
4059 int s57chart::BuildSENCFile(const wxString &FullPath000,
4060  const wxString &SENCFileName, bool b_progress) {
4061  // LOD calculation
4062  double display_pix_per_meter = g_Platform->GetDisplayDPmm() * 1000;
4063  double meters_per_pixel_max_scale =
4064  GetNormalScaleMin(0, g_b_overzoom_x) / display_pix_per_meter;
4065  m_LOD_meters = meters_per_pixel_max_scale * g_SENC_LOD_pixels;
4066 
4067  // Establish a common reference point for the chart
4068  ref_lat = (m_FullExtent.NLAT + m_FullExtent.SLAT) / 2.;
4069  ref_lon = (m_FullExtent.WLON + m_FullExtent.ELON) / 2.;
4070 
4071  if (!m_disableBackgroundSENC) {
4072  if (g_SencThreadManager) {
4073  SENCJobTicket *ticket = new SENCJobTicket();
4074  ticket->m_LOD_meters = m_LOD_meters;
4075  ticket->ref_lat = ref_lat;
4076  ticket->ref_lon = ref_lon;
4077  ticket->m_FullPath000 = FullPath000;
4078  ticket->m_SENCFileName = SENCFileName;
4079  ticket->m_chart = this;
4080 
4081  m_SENCthreadStatus = g_SencThreadManager->ScheduleJob(ticket);
4082  bReadyToRender = true;
4083  return BUILD_SENC_PENDING;
4084 
4085  } else
4086  return BUILD_SENC_NOK_RETRY;
4087 
4088  } else {
4089  Osenc senc;
4090 
4091  senc.setRegistrar(g_poRegistrar);
4092  senc.setRefLocn(ref_lat, ref_lon);
4093  senc.SetLODMeters(m_LOD_meters);
4094 
4095  AbstractPlatform::ShowBusySpinner();
4096 
4097  int ret = senc.createSenc200(FullPath000, SENCFileName, b_progress);
4098 
4099  AbstractPlatform::HideBusySpinner();
4100 
4101  if (ret == ERROR_INGESTING000)
4102  return BUILD_SENC_NOK_PERMANENT;
4103  else
4104  return ret;
4105  }
4106 }
4107 
4108 int s57chart::BuildRAZFromSENCFile(const wxString &FullPath) {
4109  int ret_val = 0; // default is OK
4110 
4111  Osenc sencfile;
4112 
4113  // Set up the containers for ingestion results.
4114  // These will be populated by Osenc, and owned by the caller (this).
4115  S57ObjVector Objects;
4116  VE_ElementVector VEs;
4117  VC_ElementVector VCs;
4118 
4119  sencfile.setRefLocn(ref_lat, ref_lon);
4120 
4121  int srv = sencfile.ingest200(FullPath, &Objects, &VEs, &VCs);
4122 
4123  if (srv != SENC_NO_ERROR) {
4124  wxLogMessage(sencfile.getLastError());
4125  // TODO Clean up here, or massive leaks result
4126  return 1;
4127  }
4128 
4129  // Get the cell Ref point as recorded in the SENC
4130  Extent ext = sencfile.getReadExtent();
4131 
4132  m_FullExtent.ELON = ext.ELON;
4133  m_FullExtent.WLON = ext.WLON;
4134  m_FullExtent.NLAT = ext.NLAT;
4135  m_FullExtent.SLAT = ext.SLAT;
4136  m_bExtentSet = true;
4137 
4138  ref_lat = (ext.NLAT + ext.SLAT) / 2.;
4139  ref_lon = (ext.ELON + ext.WLON) / 2.;
4140 
4141  // Process the Edge feature arrays.
4142 
4143  // Create a hash map of VE_Element pointers as a chart class member
4144  int n_ve_elements = VEs.size();
4145 
4146  double scale = gFrame->GetBestVPScale(this);
4147  int nativescale = GetNativeScale();
4148 
4149  for (int i = 0; i < n_ve_elements; i++) {
4150  VE_Element *vep = VEs.at(i);
4151  if (vep && vep->nCount) {
4152  // Get a bounding box for the edge
4153  double east_max = -1e7;
4154  double east_min = 1e7;
4155  double north_max = -1e7;
4156  double north_min = 1e7;
4157 
4158  float *vrun = vep->pPoints;
4159  for (size_t i = 0; i < vep->nCount; i++) {
4160  east_max = wxMax(east_max, *vrun);
4161  east_min = wxMin(east_min, *vrun);
4162  vrun++;
4163 
4164  north_max = wxMax(north_max, *vrun);
4165  north_min = wxMin(north_min, *vrun);
4166  vrun++;
4167  }
4168 
4169  double lat1, lon1, lat2, lon2;
4170  fromSM(east_min, north_min, ref_lat, ref_lon, &lat1, &lon1);
4171  fromSM(east_max, north_max, ref_lat, ref_lon, &lat2, &lon2);
4172  vep->edgeBBox.Set(lat1, lon1, lat2, lon2);
4173  }
4174 
4175  m_ve_hash[vep->index] = vep;
4176  }
4177 
4178  // Create a hash map VC_Element pointers as a chart class member
4179  int n_vc_elements = VCs.size();
4180 
4181  for (int i = 0; i < n_vc_elements; i++) {
4182  VC_Element *vcp = VCs.at(i);
4183  m_vc_hash[vcp->index] = vcp;
4184  }
4185 
4186  VEs.clear(); // destroy contents, no longer needed
4187  VCs.clear();
4188 
4189  // Walk the vector of S57Objs, associating LUPS, instructions, etc...
4190 
4191  for (unsigned int i = 0; i < Objects.size(); i++) {
4192  S57Obj *obj = Objects[i];
4193 
4194  // This is where Simplified or Paper-Type point features are selected
4195  LUPrec *LUP;
4196  LUPname LUP_Name = PAPER_CHART;
4197 
4198  const wxString objnam = obj->GetAttrValueAsString("OBJNAM");
4199  if (objnam.Len() > 0) {
4200  const wxString fe_name = wxString(obj->FeatureName, wxConvUTF8);
4201  g_pi_manager->SendVectorChartObjectInfo(FullPath, fe_name, objnam,
4202  obj->m_lat, obj->m_lon, scale,
4203  nativescale);
4204  }
4205  // If there is a localized object name and it actually is different from the
4206  // object name, send it as well...
4207  const wxString nobjnam = obj->GetAttrValueAsString("NOBJNM");
4208  if (nobjnam.Len() > 0 && nobjnam != objnam) {
4209  const wxString fe_name = wxString(obj->FeatureName, wxConvUTF8);
4210  g_pi_manager->SendVectorChartObjectInfo(FullPath, fe_name, nobjnam,
4211  obj->m_lat, obj->m_lon, scale,
4212  nativescale);
4213  }
4214 
4215  switch (obj->Primitive_type) {
4216  case GEO_POINT:
4217  case GEO_META:
4218  case GEO_PRIM:
4219 
4220  if (PAPER_CHART == ps52plib->m_nSymbolStyle)
4221  LUP_Name = PAPER_CHART;
4222  else
4223  LUP_Name = SIMPLIFIED;
4224 
4225  break;
4226 
4227  case GEO_LINE:
4228  LUP_Name = LINES;
4229  break;
4230 
4231  case GEO_AREA:
4232  if (PLAIN_BOUNDARIES == ps52plib->m_nBoundaryStyle)
4233  LUP_Name = PLAIN_BOUNDARIES;
4234  else
4235  LUP_Name = SYMBOLIZED_BOUNDARIES;
4236 
4237  break;
4238  }
4239 
4240  LUP = ps52plib->S52_LUPLookup(LUP_Name, obj->FeatureName, obj);
4241 
4242  if (NULL == LUP) {
4243  if (g_bDebugS57) {
4244  wxString msg(obj->FeatureName, wxConvUTF8);
4245  msg.Prepend(_T(" Could not find LUP for "));
4246  LogMessageOnce(msg);
4247  }
4248  delete obj;
4249  obj = NULL;
4250  Objects[i] = NULL;
4251  } else {
4252  // Convert LUP to rules set
4253  ps52plib->_LUP2rules(LUP, obj);
4254 
4255  // Add linked object/LUP to the working set
4256  _insertRules(obj, LUP, this);
4257 
4258  // Establish Object's Display Category
4259  obj->m_DisplayCat = LUP->DISC;
4260 
4261  // Establish objects base display priority
4262  obj->m_DPRI = LUP->DPRI - '0';
4263 
4264  // Is this a category-movable object?
4265  if (!strncmp(obj->FeatureName, "OBSTRN", 6) ||
4266  !strncmp(obj->FeatureName, "WRECKS", 6) ||
4267  !strncmp(obj->FeatureName, "DEPCNT", 6) ||
4268  !strncmp(obj->FeatureName, "UWTROC", 6)) {
4269  obj->m_bcategory_mutable = true;
4270  } else {
4271  obj->m_bcategory_mutable = false;
4272  }
4273  }
4274 
4275  // Build/Maintain the ATON floating/rigid arrays
4276  if (obj && (GEO_POINT == obj->Primitive_type)) {
4277  // set floating platform
4278  if ((!strncmp(obj->FeatureName, "LITFLT", 6)) ||
4279  (!strncmp(obj->FeatureName, "LITVES", 6)) ||
4280  (!strncasecmp(obj->FeatureName, "BOY", 3))) {
4281  pFloatingATONArray->Add(obj);
4282  }
4283 
4284  // set rigid platform
4285  if (!strncasecmp(obj->FeatureName, "BCN", 3)) {
4286  pRigidATONArray->Add(obj);
4287  }
4288 
4289  // Mark the object as an ATON
4290  if ((!strncmp(obj->FeatureName, "LIT", 3)) ||
4291  (!strncmp(obj->FeatureName, "LIGHTS", 6)) ||
4292  (!strncasecmp(obj->FeatureName, "BCN", 3)) ||
4293  (!strncasecmp(obj->FeatureName, "BOY", 3))) {
4294  obj->bIsAton = true;
4295  }
4296  }
4297 
4298  } // Objects iterator
4299 
4300  // Decide on pub date to show
4301 
4302  wxDateTime d000;
4303  d000.ParseFormat(sencfile.getBaseDate(), _T("%Y%m%d"));
4304  if (!d000.IsValid()) d000.ParseFormat(_T("20000101"), _T("%Y%m%d"));
4305 
4306  wxDateTime updt;
4307  updt.ParseFormat(sencfile.getUpdateDate(), _T("%Y%m%d"));
4308  if (!updt.IsValid()) updt.ParseFormat(_T("20000101"), _T("%Y%m%d"));
4309 
4310  if (updt.IsLaterThan(d000))
4311  m_PubYear.Printf(_T("%4d"), updt.GetYear());
4312  else
4313  m_PubYear.Printf(_T("%4d"), d000.GetYear());
4314 
4315  // Set some base class values
4316  wxDateTime upd = updt;
4317  if (!upd.IsValid()) upd.ParseFormat(_T("20000101"), _T("%Y%m%d"));
4318 
4319  upd.ResetTime();
4320  m_EdDate = upd;
4321 
4322  m_SE = sencfile.getSENCReadBaseEdition();
4323 
4324  wxString supdate;
4325  supdate.Printf(_T(" / %d"), sencfile.getSENCReadLastUpdate());
4326  m_SE += supdate;
4327 
4328  m_datum_str = _T("WGS84");
4329 
4330  m_SoundingsDatum = _T("MEAN LOWER LOW WATER");
4331  m_ID = sencfile.getReadID();
4332  m_Name = sencfile.getReadName();
4333 
4334  ObjRazRules *top;
4335 
4336  AssembleLineGeometry();
4337 
4338  return ret_val;
4339 }
4340 
4341 int s57chart::_insertRules(S57Obj *obj, LUPrec *LUP, s57chart *pOwner) {
4342  ObjRazRules *rzRules = NULL;
4343  int disPrioIdx = 0;
4344  int LUPtypeIdx = 0;
4345 
4346  if (LUP == NULL) {
4347  // printf("SEQuencer:_insertRules(): ERROR no rules to insert!!\n");
4348  return 0;
4349  }
4350 
4351  // find display priority index --talky version
4352  switch (LUP->DPRI) {
4353  case PRIO_NODATA:
4354  disPrioIdx = 0;
4355  break; // no data fill area pattern
4356  case PRIO_GROUP1:
4357  disPrioIdx = 1;
4358  break; // S57 group 1 filled areas
4359  case PRIO_AREA_1:
4360  disPrioIdx = 2;
4361  break; // superimposed areas
4362  case PRIO_AREA_2:
4363  disPrioIdx = 3;
4364  break; // superimposed areas also water features
4365  case PRIO_SYMB_POINT:
4366  disPrioIdx = 4;
4367  break; // point symbol also land features
4368  case PRIO_SYMB_LINE:
4369  disPrioIdx = 5;
4370  break; // line symbol also restricted areas
4371  case PRIO_SYMB_AREA:
4372  disPrioIdx = 6;
4373  break; // area symbol also traffic areas
4374  case PRIO_ROUTEING:
4375  disPrioIdx = 7;
4376  break; // routeing lines
4377  case PRIO_HAZARDS:
4378  disPrioIdx = 8;
4379  break; // hazards
4380  case PRIO_MARINERS:
4381  disPrioIdx = 9;
4382  break; // VRM & EBL, own ship
4383  default:
4384  printf("SEQuencer:_insertRules():ERROR no display priority!!!\n");
4385  }
4386 
4387  // find look up type index
4388  switch (LUP->TNAM) {
4389  case SIMPLIFIED:
4390  LUPtypeIdx = 0;
4391  break; // points
4392  case PAPER_CHART:
4393  LUPtypeIdx = 1;
4394  break; // points
4395  case LINES:
4396  LUPtypeIdx = 2;
4397  break; // lines
4398  case PLAIN_BOUNDARIES:
4399  LUPtypeIdx = 3;
4400  break; // areas
4401  case SYMBOLIZED_BOUNDARIES:
4402  LUPtypeIdx = 4;
4403  break; // areas
4404  default:
4405  printf("SEQuencer:_insertRules():ERROR no look up type !!!\n");
4406  }
4407 
4408  // insert rules
4409  rzRules = (ObjRazRules *)malloc(sizeof(ObjRazRules));
4410  rzRules->obj = obj;
4411  obj->nRef++; // Increment reference counter for delete check;
4412  rzRules->LUP = LUP;
4413  rzRules->child = NULL;
4414  rzRules->mps = NULL;
4415 
4416 #if 0
4417  rzRules->next = razRules[disPrioIdx][LUPtypeIdx];
4418  razRules[disPrioIdx][LUPtypeIdx] = rzRules;
4419 #else
4420  // Find the end of the list, and append the object
4421  // This is required to honor the "natural order" priority rules for objects of
4422  // same Display Priority
4423  ObjRazRules *rNext = NULL;
4424  ObjRazRules *rPrevious = NULL;
4425  if (razRules[disPrioIdx][LUPtypeIdx]) {
4426  rPrevious = razRules[disPrioIdx][LUPtypeIdx];
4427  rNext = rPrevious->next;
4428  }
4429  while (rNext) {
4430  rPrevious = rNext;
4431  rNext = rPrevious->next;
4432  }
4433 
4434  rzRules->next = NULL;
4435  if (rPrevious)
4436  rPrevious->next = rzRules;
4437  else
4438  razRules[disPrioIdx][LUPtypeIdx] = rzRules;
4439 
4440 #endif
4441 
4442  return 1;
4443 }
4444 
4445 void s57chart::ResetPointBBoxes(const ViewPort &vp_last,
4446  const ViewPort &vp_this) {
4447  ObjRazRules *top;
4448  ObjRazRules *nxx;
4449 
4450  if (vp_last.view_scale_ppm == 1.0) // Skip the startup case
4451  return;
4452 
4453  double d = vp_last.view_scale_ppm / vp_this.view_scale_ppm;
4454 
4455  for (int i = 0; i < PRIO_NUM; ++i) {
4456  for (int j = 0; j < 2; ++j) {
4457  top = razRules[i][j];
4458 
4459  while (top != NULL) {
4460  if (!top->obj->geoPtMulti) // do not reset multipoints
4461  {
4462  if (top->obj->BBObj.GetValid()) { // scale bbobj
4463  double lat = top->obj->m_lat, lon = top->obj->m_lon;
4464 
4465  double lat1 = (lat - top->obj->BBObj.GetMinLat()) * d;
4466  double lat2 = (lat - top->obj->BBObj.GetMaxLat()) * d;
4467 
4468  double minlon = top->obj->BBObj.GetMinLon();
4469  double maxlon = top->obj->BBObj.GetMaxLon();
4470 
4471  double lon1 = (lon - minlon) * d;
4472  double lon2 = (lon - maxlon) * d;
4473 
4474  top->obj->BBObj.Set(lat - lat1, lon - lon1, lat - lat2, lon - lon2);
4475 
4476  // this method is very close, but errors accumulate
4477  top->obj->BBObj.Invalidate();
4478  }
4479  }
4480 
4481  nxx = top->next;
4482  top = nxx;
4483  }
4484  }
4485  }
4486 }
4487 
4488 // Traverse the ObjRazRules tree, and fill in
4489 // any Lups/rules not linked on initial chart load.
4490 // For example, if chart was loaded with PAPER_CHART symbols,
4491 // locate and load the equivalent SIMPLIFIED symbology.
4492 // Likewise for PLAIN/SYMBOLIZED boundaries.
4493 //
4494 // This method is usually called after a chart display style
4495 // change via the "Options" dialog, to ensure all symbology is
4496 // present iff needed.
4497 
4498 void s57chart::UpdateLUPs(s57chart *pOwner) {
4499  ObjRazRules *top;
4500  ObjRazRules *nxx;
4501  LUPrec *LUP;
4502  for (int i = 0; i < PRIO_NUM; ++i) {
4503  // SIMPLIFIED is set, PAPER_CHART is bare
4504  if ((razRules[i][0]) && (NULL == razRules[i][1])) {
4505  m_b2pointLUPS = true;
4506  top = razRules[i][0];
4507 
4508  while (top != NULL) {
4509  LUP = ps52plib->S52_LUPLookup(PAPER_CHART, top->obj->FeatureName,
4510  top->obj);
4511  if (LUP) {
4512  // A POINT object can only appear in two places in the table,
4513  // SIMPLIFIED or PAPER_CHART although it is allowed for the Display
4514  // priority to be different for each
4515  if (top->obj->nRef < 2) {
4516  ps52plib->_LUP2rules(LUP, top->obj);
4517  _insertRules(top->obj, LUP, pOwner);
4518  top->obj->m_DisplayCat = LUP->DISC;
4519  }
4520  }
4521 
4522  nxx = top->next;
4523  top = nxx;
4524  }
4525  }
4526 
4527  // PAPER_CHART is set, SIMPLIFIED is bare
4528  if ((razRules[i][1]) && (NULL == razRules[i][0])) {
4529  m_b2pointLUPS = true;
4530  top = razRules[i][1];
4531 
4532  while (top != NULL) {
4533  LUP = ps52plib->S52_LUPLookup(SIMPLIFIED, top->obj->FeatureName,
4534  top->obj);
4535  if (LUP) {
4536  if (top->obj->nRef < 2) {
4537  ps52plib->_LUP2rules(LUP, top->obj);
4538  _insertRules(top->obj, LUP, pOwner);
4539  top->obj->m_DisplayCat = LUP->DISC;
4540  }
4541  }
4542 
4543  nxx = top->next;
4544  top = nxx;
4545  }
4546  }
4547 
4548  // PLAIN_BOUNDARIES is set, SYMBOLIZED_BOUNDARIES is bare
4549  if ((razRules[i][3]) && (NULL == razRules[i][4])) {
4550  m_b2lineLUPS = true;
4551  top = razRules[i][3];
4552 
4553  while (top != NULL) {
4554  LUP = ps52plib->S52_LUPLookup(SYMBOLIZED_BOUNDARIES,
4555  top->obj->FeatureName, top->obj);
4556  if (LUP) {
4557  ps52plib->_LUP2rules(LUP, top->obj);
4558  _insertRules(top->obj, LUP, pOwner);
4559  top->obj->m_DisplayCat = LUP->DISC;
4560  }
4561 
4562  nxx = top->next;
4563  top = nxx;
4564  }
4565  }
4566 
4567  // SYMBOLIZED_BOUNDARIES is set, PLAIN_BOUNDARIES is bare
4568  if ((razRules[i][4]) && (NULL == razRules[i][3])) {
4569  m_b2lineLUPS = true;
4570  top = razRules[i][4];
4571 
4572  while (top != NULL) {
4573  LUP = ps52plib->S52_LUPLookup(PLAIN_BOUNDARIES, top->obj->FeatureName,
4574  top->obj);
4575  if (LUP) {
4576  ps52plib->_LUP2rules(LUP, top->obj);
4577  _insertRules(top->obj, LUP, pOwner);
4578  top->obj->m_DisplayCat = LUP->DISC;
4579  }
4580 
4581  nxx = top->next;
4582  top = nxx;
4583  }
4584  }
4585 
4586  // Traverse this priority level again,
4587  // clearing any object CS rules and flags,
4588  // so that the next render operation will re-evaluate the CS
4589 
4590  for (int j = 0; j < LUPNAME_NUM; j++) {
4591  top = razRules[i][j];
4592  while (top != NULL) {
4593  top->obj->bCS_Added = 0;
4594  free_mps(top->mps);
4595  top->mps = 0;
4596  if (top->LUP) top->obj->m_DisplayCat = top->LUP->DISC;
4597 
4598  nxx = top->next;
4599  top = nxx;
4600  }
4601  }
4602 
4603  // Traverse this priority level again,
4604  // clearing any object CS rules and flags of any child list,
4605  // so that the next render operation will re-evaluate the CS
4606 
4607  for (int j = 0; j < LUPNAME_NUM; j++) {
4608  top = razRules[i][j];
4609  while (top != NULL) {
4610  if (top->child) {
4611  ObjRazRules *ctop = top->child;
4612  while (NULL != ctop) {
4613  ctop->obj->bCS_Added = 0;
4614  free_mps(ctop->mps);
4615  ctop->mps = 0;
4616 
4617  if (ctop->LUP) ctop->obj->m_DisplayCat = ctop->LUP->DISC;
4618  ctop = ctop->next;
4619  }
4620  }
4621  nxx = top->next;
4622  top = nxx;
4623  }
4624  }
4625  }
4626 
4627  // Clear the dynamically created Conditional Symbology LUP Array
4628  // This can not be done on a per-chart basis, since the plib services all
4629  // charts
4630  // TODO really should make the dynamic LUPs belong to the chart class that
4631  // created them
4632 }
4633 
4634 ListOfObjRazRules *s57chart::GetLightsObjRuleListVisibleAtLatLon(
4635  float lat, float lon, ViewPort *VPoint) {
4636  ListOfObjRazRules *ret_ptr = new ListOfObjRazRules;
4637  std::vector<ObjRazRules *> selected_rules;
4638 
4639  // Iterate thru the razRules array, by object/rule type
4640 
4641  ObjRazRules *top;
4642  char *curr_att = NULL;
4643  int n_attr = 0;
4644  wxArrayOfS57attVal *attValArray = NULL;
4645  bool bleading_attribute = false;
4646 
4647  for (int i = 0; i < PRIO_NUM; ++i) {
4648  {
4649  // Points by type, array indices [0..1]
4650 
4651  int point_type = (ps52plib->m_nSymbolStyle == SIMPLIFIED) ? 0 : 1;
4652  top = razRules[i][point_type];
4653 
4654  while (top != NULL) {
4655  if (top->obj->npt == 1) {
4656  if (!strncmp(top->obj->FeatureName, "LIGHTS", 6)) {
4657  double sectrTest;
4658  bool hasSectors = GetDoubleAttr(top->obj, "SECTR1", sectrTest);
4659  if (hasSectors) {
4660  if (ps52plib->ObjectRenderCheckCat(top)) {
4661  int attrCounter;
4662  double valnmr = -1;
4663  wxString curAttrName;
4664  curr_att = top->obj->att_array;
4665  n_attr = top->obj->n_attr;
4666  attValArray = top->obj->attVal;
4667 
4668  if (curr_att) {
4669  bool bviz = true;
4670 
4671  attrCounter = 0;
4672  int noAttr = 0;
4673 
4674  bleading_attribute = false;
4675 
4676  while (attrCounter < n_attr) {
4677  curAttrName = wxString(curr_att, wxConvUTF8, 6);
4678  noAttr++;
4679 
4680  S57attVal *pAttrVal = NULL;
4681  if (attValArray) {
4682  // if(Chs57)
4683  pAttrVal = attValArray->Item(attrCounter);
4684  // else if( target_plugin_chart )
4685  // pAttrVal = attValArray->Item(attrCounter);
4686  }
4687  wxString value = s57chart::GetAttributeValueAsString(
4688  pAttrVal, curAttrName);
4689 
4690  if (curAttrName == _T("LITVIS")) {
4691  if (value.StartsWith(_T("obsc"))) bviz = false;
4692  } else if (curAttrName == _T("VALNMR"))
4693  value.ToDouble(&valnmr);
4694 
4695  attrCounter++;
4696  curr_att += 6;
4697  }
4698 
4699  if (bviz && (valnmr > 0.1)) {
4700  // As a quick check, compare the mercator-manhattan distance
4701  double olon, olat;
4702  fromSM(
4703  (top->obj->x * top->obj->x_rate) + top->obj->x_origin,
4704  (top->obj->y * top->obj->y_rate) + top->obj->y_origin,
4705  ref_lat, ref_lon, &olat, &olon);
4706 
4707  double dlat = lat - olat;
4708  double dy = dlat * 60 / cos(olat * PI / 180.);
4709  double dlon = lon - olon;
4710  double dx = dlon * 60;
4711  double manhat = abs(dy) + abs(dx);
4712  if (1 /*(abs(dy) + abs(dx)) < valnmr*/) {
4713  // close...Check precisely
4714  double br, dd;
4715  DistanceBearingMercator(lat, lon, olat, olon, &br, &dd);
4716  if (dd < valnmr) {
4717  selected_rules.push_back(top);
4718  }
4719  }
4720  }
4721  }
4722  }
4723  }
4724  }
4725  }
4726 
4727  top = top->next;
4728  }
4729  }
4730  }
4731 
4732  // Copy the rules in order into a wxList so the function returns the correct type
4733  for(std::size_t i = 0; i < selected_rules.size(); ++i) {
4734  ret_ptr->Append(selected_rules[i]);
4735  }
4736 
4737  return ret_ptr;
4738 }
4739 
4740 ListOfObjRazRules *s57chart::GetObjRuleListAtLatLon(float lat, float lon,
4741  float select_radius,
4742  ViewPort *VPoint,
4743  int selection_mask) {
4744 
4745  ListOfObjRazRules *ret_ptr = new ListOfObjRazRules;
4746  std::vector<ObjRazRules *> selected_rules;
4747 
4748  PrepareForRender(VPoint, ps52plib);
4749 
4750  // Iterate thru the razRules array, by object/rule type
4751 
4752  ObjRazRules *top;
4753 
4754  for (int i = 0; i < PRIO_NUM; ++i) {
4755  if (selection_mask & MASK_POINT) {
4756  // Points by type, array indices [0..1]
4757 
4758  int point_type = (ps52plib->m_nSymbolStyle == SIMPLIFIED) ? 0 : 1;
4759  top = razRules[i][point_type];
4760 
4761  while (top != NULL) {
4762  if (top->obj->npt ==
4763  1) // Do not select Multipoint objects (SOUNDG) yet.
4764  {
4765  if (ps52plib->ObjectRenderCheck(top)) {
4766  if (DoesLatLonSelectObject(lat, lon, select_radius, top->obj))
4767  selected_rules.push_back(top);
4768  }
4769  }
4770 
4771 
4772 
4773  // Check the child branch, if any.
4774  // This is where Multipoint soundings are captured individually
4775  if (top->child) {
4776  ObjRazRules *child_item = top->child;
4777  while (child_item != NULL) {
4778  if (ps52plib->ObjectRenderCheck(child_item)) {
4779  if (DoesLatLonSelectObject(lat, lon, select_radius,
4780  child_item->obj))
4781  selected_rules.push_back(child_item);
4782  }
4783 
4784  child_item = child_item->next;
4785  }
4786  }
4787 
4788  top = top->next;
4789  }
4790  }
4791 
4792  if (selection_mask & MASK_AREA) {
4793  // Areas by boundary type, array indices [3..4]
4794 
4795  int area_boundary_type =
4796  (ps52plib->m_nBoundaryStyle == PLAIN_BOUNDARIES) ? 3 : 4;
4797  top = razRules[i][area_boundary_type]; // Area nnn Boundaries
4798  while (top != NULL) {
4799  if (ps52plib->ObjectRenderCheck(top)) {
4800  if (DoesLatLonSelectObject(lat, lon, select_radius, top->obj))
4801  selected_rules.push_back(top);
4802  }
4803 
4804  top = top->next;
4805  } // while
4806  }
4807 
4808  if (selection_mask & MASK_LINE) {
4809  // Finally, lines
4810  top = razRules[i][2]; // Lines
4811 
4812  while (top != NULL) {
4813  if (ps52plib->ObjectRenderCheck(top)) {
4814  if (DoesLatLonSelectObject(lat, lon, select_radius, top->obj))
4815  selected_rules.push_back(top);
4816  }
4817 
4818  top = top->next;
4819  }
4820  }
4821  }
4822 
4823 
4824  // Sort Point objects by distance to searched lat/lon
4825  // This lambda function could be modified to also sort GEO_LINES and GEO_AREAS if needed
4826  auto sortObjs = [lat, lon, this] (const ObjRazRules* obj1, const ObjRazRules* obj2) -> bool
4827  {
4828  double br1, dd1, br2, dd2;
4829 
4830  if(obj1->obj->Primitive_type == GEO_POINT && obj2->obj->Primitive_type == GEO_POINT){
4831  double lat1, lat2, lon1, lon2;
4832  fromSM((obj1->obj->x * obj1->obj->x_rate) + obj1->obj->x_origin,
4833  (obj1->obj->y * obj1->obj->y_rate) + obj1->obj->y_origin,
4834  ref_lat, ref_lon, &lat1, &lon1);
4835 
4836  if (lon1 > 180.0) lon1 -= 360.;
4837 
4838  fromSM((obj2->obj->x * obj2->obj->x_rate) + obj2->obj->x_origin,
4839  (obj2->obj->y * obj2->obj->y_rate) + obj2->obj->y_origin,
4840  ref_lat, ref_lon, &lat2, &lon2);
4841 
4842  if (lon2 > 180.0) lon2 -= 360.;
4843 
4844  DistanceBearingMercator(lat, lon, lat1, lon1, &br1, &dd1);
4845  DistanceBearingMercator(lat, lon, lat2, lon2, &br2, &dd2);
4846  return dd1>dd2;
4847  }
4848  return false;
4849 
4850  };
4851 
4852  // Sort the selected rules by using the lambda sort function defined above
4853  std::sort(selected_rules.begin(), selected_rules.end(), sortObjs);
4854 
4855  // Copy the rules in order into a wxList so the function returns the correct type
4856  for(std::size_t i = 0; i < selected_rules.size(); ++i) {
4857  ret_ptr->Append(selected_rules[i]);
4858  }
4859 
4860  return ret_ptr;
4861 }
4862 
4863 bool s57chart::DoesLatLonSelectObject(float lat, float lon, float select_radius,
4864  S57Obj *obj) {
4865  switch (obj->Primitive_type) {
4866  // For single Point objects, the integral object bounding box contains the
4867  // lat/lon of the object, possibly expanded by text or symbol rendering
4868  case GEO_POINT: {
4869  if (!obj->BBObj.GetValid()) return false;
4870 
4871  if (1 == obj->npt) {
4872  // Special case for LIGHTS
4873  // Sector lights have had their BBObj expanded to include the entire
4874  // drawn sector This is too big for pick area, can be confusing.... So
4875  // make a temporary box at the light's lat/lon, with select_radius size
4876  if (!strncmp(obj->FeatureName, "LIGHTS", 6)) {
4877  double sectrTest;
4878  bool hasSectors = GetDoubleAttr(obj, "SECTR1", sectrTest);
4879  if (hasSectors) {
4880  double olon, olat;
4881  fromSM((obj->x * obj->x_rate) + obj->x_origin,
4882  (obj->y * obj->y_rate) + obj->y_origin, ref_lat, ref_lon,
4883  &olat, &olon);
4884 
4885  // Double the select radius to adjust for the fact that LIGHTS has
4886  // a 0x0 BBox to start with, which makes it smaller than all other
4887  // rendered objects.
4888  LLBBox sbox;
4889  sbox.Set(olat, olon, olat, olon);
4890 
4891  if (sbox.ContainsMarge(lat, lon, select_radius)) return true;
4892  } else if (obj->BBObj.ContainsMarge(lat, lon, select_radius))
4893  return true;
4894 
4895  }
4896 
4897  else if (obj->BBObj.ContainsMarge(lat, lon, select_radius))
4898  return true;
4899  }
4900 
4901  // For MultiPoint objects, make a bounding box from each point's lat/lon
4902  // and check it
4903  else {
4904  if (!obj->BBObj.GetValid()) return false;
4905 
4906  // Coarse test first
4907  if (!obj->BBObj.ContainsMarge(lat, lon, select_radius)) return false;
4908  // Now decomposed soundings, one by one
4909  double *pdl = obj->geoPtMulti;
4910  for (int ip = 0; ip < obj->npt; ip++) {
4911  double lon_point = *pdl++;
4912  double lat_point = *pdl++;
4913  LLBBox BB_point;
4914  BB_point.Set(lat_point, lon_point, lat_point, lon_point);
4915  if (BB_point.ContainsMarge(lat, lon, select_radius)) {
4916  // index = ip;
4917  return true;
4918  }
4919  }
4920  }
4921 
4922  break;
4923  }
4924  case GEO_AREA: {
4925  // Coarse test first
4926  if (!obj->BBObj.ContainsMarge(lat, lon, select_radius))
4927  return false;
4928  else
4929  return IsPointInObjArea(lat, lon, select_radius, obj);
4930  }
4931 
4932  case GEO_LINE: {
4933  // Coarse test first
4934  if (!obj->BBObj.ContainsMarge(lat, lon, select_radius)) return false;
4935 
4936  float sel_rad_meters = select_radius * 1852 * 60; // approximately
4937  double easting, northing;
4938  toSM(lat, lon, ref_lat, ref_lon, &easting, &northing);
4939 
4940  if (obj->geoPt) {
4941  // Line geometry is carried in SM or CM93 coordinates, so...
4942  // make the hit test using SM coordinates, converting from object
4943  // points to SM using per-object conversion factors.
4944 
4945  pt *ppt = obj->geoPt;
4946  int npt = obj->npt;
4947 
4948  double xr = obj->x_rate;
4949  double xo = obj->x_origin;
4950  double yr = obj->y_rate;
4951  double yo = obj->y_origin;
4952 
4953  double north0 = (ppt->y * yr) + yo;
4954  double east0 = (ppt->x * xr) + xo;
4955  ppt++;
4956 
4957  for (int ip = 1; ip < npt; ip++) {
4958  double north = (ppt->y * yr) + yo;
4959  double east = (ppt->x * xr) + xo;
4960 
4961  // A slightly less coarse segment bounding box check
4962  if (northing >= (fmin(north, north0) - sel_rad_meters))
4963  if (northing <= (fmax(north, north0) + sel_rad_meters))
4964  if (easting >= (fmin(east, east0) - sel_rad_meters))
4965  if (easting <= (fmax(east, east0) + sel_rad_meters)) {
4966  return true;
4967  }
4968 
4969  north0 = north;
4970  east0 = east;
4971  ppt++;
4972  }
4973  } else { // in oSENC V2, Array of points is stored in prearranged VBO
4974  // array.
4975  if (obj->m_ls_list) {
4976  float *ppt;
4977  unsigned char *vbo_point =
4978  (unsigned char *)
4979  obj->m_chart_context->vertex_buffer; //chart->GetLineVertexBuffer();
4980  line_segment_element *ls = obj->m_ls_list;
4981 
4982  while (ls && vbo_point) {
4983  int nPoints;
4984  if ((ls->ls_type == TYPE_EE) || (ls->ls_type == TYPE_EE_REV)) {
4985  ppt = (float *)(vbo_point + ls->pedge->vbo_offset);
4986  nPoints = ls->pedge->nCount;
4987  } else {
4988  ppt = (float *)(vbo_point + ls->pcs->vbo_offset);
4989  nPoints = 2;
4990  }
4991 
4992  float north0 = ppt[1];
4993  float east0 = ppt[0];
4994 
4995  ppt += 2;
4996 
4997  for (int ip = 0; ip < nPoints - 1; ip++) {
4998  float north = ppt[1];
4999  float east = ppt[0];
5000 
5001  if (northing >= (fmin(north, north0) - sel_rad_meters))
5002  if (northing <= (fmax(north, north0) + sel_rad_meters))
5003  if (easting >= (fmin(east, east0) - sel_rad_meters))
5004  if (easting <= (fmax(east, east0) + sel_rad_meters)) {
5005  return true;
5006  }
5007 
5008  north0 = north;
5009  east0 = east;
5010 
5011  ppt += 2;
5012  }
5013 
5014  ls = ls->next;
5015  }
5016  }
5017  }
5018 
5019  break;
5020  }
5021 
5022  case GEO_META:
5023  case GEO_PRIM:
5024 
5025  break;
5026  }
5027 
5028  return false;
5029 }
5030 
5031 wxString s57chart::GetAttributeDecode(wxString &att, int ival) {
5032  wxString ret_val = _T("");
5033 
5034  // Get the attribute code from the acronym
5035  const char *att_code;
5036 
5037  wxString file(g_csv_locn);
5038  file.Append(_T("/s57attributes.csv"));
5039 
5040  if (!wxFileName::FileExists(file)) {
5041  wxString msg(_T(" Could not open "));
5042  msg.Append(file);
5043  wxLogMessage(msg);
5044 
5045  return ret_val;
5046  }
5047 
5048  att_code = MyCSVGetField(file.mb_str(), "Acronym", // match field
5049  att.mb_str(), // match value
5050  CC_ExactString, "Code"); // return field
5051 
5052  // Now, get a nice description from s57expectedinput.csv
5053  // This will have to be a 2-d search, using ID field and Code field
5054 
5055  // Ingest, and get a pointer to the ingested table for "Expected Input" file
5056  wxString ei_file(g_csv_locn);
5057  ei_file.Append(_T("/s57expectedinput.csv"));
5058 
5059  if (!wxFileName::FileExists(ei_file)) {
5060  wxString msg(_T(" Could not open "));
5061  msg.Append(ei_file);
5062  wxLogMessage(msg);
5063 
5064  return ret_val;
5065  }
5066 
5067  CSVTable *psTable = CSVAccess(ei_file.mb_str());
5068  CSVIngest(ei_file.mb_str());
5069 
5070  char **papszFields = NULL;
5071  int bSelected = FALSE;
5072 
5073  /* -------------------------------------------------------------------- */
5074  /* Scan from in-core lines. */
5075  /* -------------------------------------------------------------------- */
5076  int iline = 0;
5077  while (!bSelected && iline + 1 < psTable->nLineCount) {
5078  iline++;
5079  papszFields = CSVSplitLine(psTable->papszLines[iline]);
5080 
5081  if (!strcmp(papszFields[0], att_code)) {
5082  if (atoi(papszFields[1]) == ival) {
5083  ret_val = wxString(papszFields[2], wxConvUTF8);
5084  bSelected = TRUE;
5085  }
5086  }
5087 
5088  CSLDestroy(papszFields);
5089  }
5090 
5091  return ret_val;
5092 }
5093 
5094 //----------------------------------------------------------------------------------
5095 
5096 bool s57chart::IsPointInObjArea(float lat, float lon, float select_radius,
5097  S57Obj *obj) {
5098  bool ret = false;
5099 
5100  if (obj->pPolyTessGeo) {
5101  if (!obj->pPolyTessGeo->IsOk()) obj->pPolyTessGeo->BuildDeferredTess();
5102 
5103  PolyTriGroup *ppg = obj->pPolyTessGeo->Get_PolyTriGroup_head();
5104 
5105  TriPrim *pTP = ppg->tri_prim_head;
5106 
5107  MyPoint pvert_list[3];
5108 
5109  // Polygon geometry is carried in SM coordinates, so...
5110  // make the hit test thus.
5111  double easting, northing;
5112  toSM(lat, lon, ref_lat, ref_lon, &easting, &northing);
5113 
5114  // On some chart types (e.g. cm93), the tesseleated coordinates are stored
5115  // differently. Adjust the pick point (easting/northing) to correspond.
5116  if (!ppg->m_bSMSENC) {
5117  double y_rate = obj->y_rate;
5118  double y_origin = obj->y_origin;
5119  double x_rate = obj->x_rate;
5120  double x_origin = obj->x_origin;
5121 
5122  double northing_scaled = (northing - y_origin) / y_rate;
5123  double easting_scaled = (easting - x_origin) / x_rate;
5124  northing = northing_scaled;
5125  easting = easting_scaled;
5126  }
5127 
5128  while (pTP) {
5129  // Coarse test
5130  if (pTP->tri_box.Contains(lat, lon)) {
5131  if (ppg->data_type == DATA_TYPE_DOUBLE) {
5132  double *p_vertex = pTP->p_vertex;
5133 
5134  switch (pTP->type) {
5135  case PTG_TRIANGLE_FAN: {
5136  for (int it = 0; it < pTP->nVert - 2; it++) {
5137  pvert_list[0].x = p_vertex[0];
5138  pvert_list[0].y = p_vertex[1];
5139 
5140  pvert_list[1].x = p_vertex[(it * 2) + 2];
5141  pvert_list[1].y = p_vertex[(it * 2) + 3];
5142 
5143  pvert_list[2].x = p_vertex[(it * 2) + 4];
5144  pvert_list[2].y = p_vertex[(it * 2) + 5];
5145 
5146  if (G_PtInPolygon((MyPoint *)pvert_list, 3, easting,
5147  northing)) {
5148  ret = true;
5149  break;
5150  }
5151  }
5152  break;
5153  }
5154  case PTG_TRIANGLE_STRIP: {
5155  for (int it = 0; it < pTP->nVert - 2; it++) {
5156  pvert_list[0].x = p_vertex[(it * 2)];
5157  pvert_list[0].y = p_vertex[(it * 2) + 1];
5158 
5159  pvert_list[1].x = p_vertex[(it * 2) + 2];
5160  pvert_list[1].y = p_vertex[(it * 2) + 3];
5161 
5162  pvert_list[2].x = p_vertex[(it * 2) + 4];
5163  pvert_list[2].y = p_vertex[(it * 2) + 5];
5164 
5165  if (G_PtInPolygon((MyPoint *)pvert_list, 3, easting,
5166  northing)) {
5167  ret = true;
5168  break;
5169  }
5170  }
5171  break;
5172  }
5173  case PTG_TRIANGLES: {
5174  for (int it = 0; it < pTP->nVert; it += 3) {
5175  pvert_list[0].x = p_vertex[(it * 2)];
5176  pvert_list[0].y = p_vertex[(it * 2) + 1];
5177 
5178  pvert_list[1].x = p_vertex[(it * 2) + 2];
5179  pvert_list[1].y = p_vertex[(it * 2) + 3];
5180 
5181  pvert_list[2].x = p_vertex[(it * 2) + 4];
5182  pvert_list[2].y = p_vertex[(it * 2) + 5];
5183 
5184  if (G_PtInPolygon((MyPoint *)pvert_list, 3, easting,
5185  northing)) {
5186  ret = true;
5187  break;
5188  }
5189  }
5190  break;
5191  }
5192  }
5193  } else if (ppg->data_type == DATA_TYPE_FLOAT) {
5194  float *p_vertex = (float *)pTP->p_vertex;
5195 
5196  switch (pTP->type) {
5197  case PTG_TRIANGLE_FAN: {
5198  for (int it = 0; it < pTP->nVert - 2; it++) {
5199  pvert_list[0].x = p_vertex[0];
5200  pvert_list[0].y = p_vertex[1];
5201 
5202  pvert_list[1].x = p_vertex[(it * 2) + 2];
5203  pvert_list[1].y = p_vertex[(it * 2) + 3];
5204 
5205  pvert_list[2].x = p_vertex[(it * 2) + 4];
5206  pvert_list[2].y = p_vertex[(it * 2) + 5];
5207 
5208  if (G_PtInPolygon((MyPoint *)pvert_list, 3, easting,
5209  northing)) {
5210  ret = true;
5211  break;
5212  }
5213  }
5214  break;
5215  }
5216  case PTG_TRIANGLE_STRIP: {
5217  for (int it = 0; it < pTP->nVert - 2; it++) {
5218  pvert_list[0].x = p_vertex[(it * 2)];
5219  pvert_list[0].y = p_vertex[(it * 2) + 1];
5220 
5221  pvert_list[1].x = p_vertex[(it * 2) + 2];
5222  pvert_list[1].y = p_vertex[(it * 2) + 3];
5223 
5224  pvert_list[2].x = p_vertex[(it * 2) + 4];
5225  pvert_list[2].y = p_vertex[(it * 2) + 5];
5226 
5227  if (G_PtInPolygon((MyPoint *)pvert_list, 3, easting,
5228  northing)) {
5229  ret = true;
5230  break;
5231  }
5232  }
5233  break;
5234  }
5235  case PTG_TRIANGLES: {
5236  for (int it = 0; it < pTP->nVert; it += 3) {
5237  pvert_list[0].x = p_vertex[(it * 2)];
5238  pvert_list[0].y = p_vertex[(it * 2) + 1];
5239 
5240  pvert_list[1].x = p_vertex[(it * 2) + 2];
5241  pvert_list[1].y = p_vertex[(it * 2) + 3];
5242 
5243  pvert_list[2].x = p_vertex[(it * 2) + 4];
5244  pvert_list[2].y = p_vertex[(it * 2) + 5];
5245 
5246  if (G_PtInPolygon((MyPoint *)pvert_list, 3, easting,
5247  northing)) {
5248  ret = true;
5249  break;
5250  }
5251  }
5252  break;
5253  }
5254  }
5255  } else {
5256  ret = true; // Unknown data type, accept the entire TriPrim via
5257  // coarse test.
5258  break;
5259  }
5260  }
5261  pTP = pTP->p_next;
5262  }
5263 
5264  } // if pPolyTessGeo
5265 
5266  return ret;
5267 }
5268 
5269 wxString s57chart::GetObjectAttributeValueAsString(S57Obj *obj, int iatt,
5270  wxString curAttrName) {
5271  wxString value;
5272  S57attVal *pval;
5273 
5274  pval = obj->attVal->Item(iatt);
5275  switch (pval->valType) {
5276  case OGR_STR: {
5277  if (pval->value) {
5278  wxString val_str((char *)(pval->value), wxConvUTF8);
5279  long ival;
5280  if (val_str.ToLong(&ival)) {
5281  if (0 == ival)
5282  value = _T("Unknown");
5283  else {
5284  wxString decode_val = GetAttributeDecode(curAttrName, ival);
5285  if (!decode_val.IsEmpty()) {
5286  value = decode_val;
5287  wxString iv;
5288  iv.Printf(_T(" (%d)"), (int)ival);
5289  value.Append(iv);
5290  } else
5291  value.Printf(_T("%d"), (int)ival);
5292  }
5293  }
5294 
5295  else if (val_str.IsEmpty())
5296  value = _T("Unknown");
5297 
5298  else {
5299  value.Clear();
5300  wxString value_increment;
5301  wxStringTokenizer tk(val_str, wxT(","));
5302  int iv = 0;
5303  if (tk.HasMoreTokens()) {
5304  while (tk.HasMoreTokens()) {
5305  wxString token = tk.GetNextToken();
5306  long ival;
5307  if (token.ToLong(&ival)) {
5308  wxString decode_val = GetAttributeDecode(curAttrName, ival);
5309 
5310  value_increment.Printf(_T(" (%d)"), (int)ival);
5311 
5312  if (!decode_val.IsEmpty()) value_increment.Prepend(decode_val);
5313 
5314  if (iv) value_increment.Prepend(wxT(", "));
5315  value.Append(value_increment);
5316 
5317  } else {
5318  if (iv) value.Append(_T(","));
5319  value.Append(token);
5320  }
5321 
5322  iv++;
5323  }
5324  } else
5325  value.Append(val_str);
5326  }
5327  } else
5328  value = _T("[NULL VALUE]");
5329 
5330  break;
5331  }
5332 
5333  case OGR_INT: {
5334  int ival = *((int *)pval->value);
5335  wxString decode_val = GetAttributeDecode(curAttrName, ival);
5336 
5337  if (!decode_val.IsEmpty()) {
5338  value = decode_val;
5339  wxString iv;
5340  iv.Printf(_T("(%d)"), ival);
5341  value.Append(iv);
5342  } else
5343  value.Printf(_T("(%d)"), ival);
5344 
5345  break;
5346  }
5347  case OGR_INT_LST:
5348  break;
5349 
5350  case OGR_REAL: {
5351  double dval = *((double *)pval->value);
5352  wxString val_suffix = _T(" m");
5353 
5354  // As a special case, convert some attribute values to feet.....
5355  if ((curAttrName == _T("VERCLR")) || (curAttrName == _T("VERCCL")) ||
5356  (curAttrName == _T("VERCOP")) || (curAttrName == _T("HEIGHT")) ||
5357  (curAttrName == _T("HORCLR"))) {
5358  switch (ps52plib->m_nDepthUnitDisplay) {
5359  case 0: // feet
5360  case 2: // fathoms
5361  dval = dval * 3 * 39.37 / 36; // feet
5362  val_suffix = _T(" ft");
5363  break;
5364  default:
5365  break;
5366  }
5367  }
5368 
5369  else if ((curAttrName == _T("VALSOU")) || (curAttrName == _T("DRVAL1")) ||
5370  (curAttrName == _T("DRVAL2")) || (curAttrName == _T("VALDCO"))) {
5371  switch (ps52plib->m_nDepthUnitDisplay) {
5372  case 0: // feet
5373  dval = dval * 3 * 39.37 / 36; // feet
5374  val_suffix = _T(" ft");
5375  break;
5376  case 2: // fathoms
5377  dval = dval * 3 * 39.37 / 36; // fathoms
5378  dval /= 6.0;
5379  val_suffix = _T(" fathoms");
5380  break;
5381  default:
5382  break;
5383  }
5384  }
5385 
5386  else if (curAttrName == _T("SECTR1"))
5387  val_suffix = _T("&deg;");
5388  else if (curAttrName == _T("SECTR2"))
5389  val_suffix = _T("&deg;");
5390  else if (curAttrName == _T("ORIENT"))
5391  val_suffix = _T("&deg;");
5392  else if (curAttrName == _T("VALNMR"))
5393  val_suffix = _T(" Nm");
5394  else if (curAttrName == _T("SIGPER"))
5395  val_suffix = _T("s");
5396  else if (curAttrName == _T("VALACM"))
5397  val_suffix = _T(" Minutes/year");
5398  else if (curAttrName == _T("VALMAG"))
5399  val_suffix = _T("&deg;");
5400  else if (curAttrName == _T("CURVEL"))
5401  val_suffix = _T(" kt");
5402 
5403  if (dval - floor(dval) < 0.01)
5404  value.Printf(_T("%2.0f"), dval);
5405  else
5406  value.Printf(_T("%4.1f"), dval);
5407 
5408  value << val_suffix;
5409 
5410  break;
5411  }
5412 
5413  case OGR_REAL_LST: {
5414  break;
5415  }
5416  }
5417  return value;
5418 }
5419 
5420 wxString s57chart::GetAttributeValueAsString(S57attVal *pAttrVal,
5421  wxString AttrName) {
5422  if (NULL == pAttrVal) return _T("");
5423 
5424  wxString value;
5425  switch (pAttrVal->valType) {
5426  case OGR_STR: {
5427  if (pAttrVal->value) {
5428  wxString val_str((char *)(pAttrVal->value), wxConvUTF8);
5429  long ival;
5430  if (val_str.ToLong(&ival)) {
5431  if (0 == ival)
5432  value = _T("Unknown");
5433  else {
5434  wxString decode_val = GetAttributeDecode(AttrName, ival);
5435  if (!decode_val.IsEmpty()) {
5436  value = decode_val;
5437  wxString iv;
5438  iv.Printf(_T("(%d)"), (int)ival);
5439  value.Append(iv);
5440  } else
5441  value.Printf(_T("%d"), (int)ival);
5442  }
5443  }
5444 
5445  else if (val_str.IsEmpty())
5446  value = _T("Unknown");
5447 
5448  else {
5449  value.Clear();
5450  wxString value_increment;
5451  wxStringTokenizer tk(val_str, wxT(","));
5452  int iv = 0;
5453  while (tk.HasMoreTokens()) {
5454  wxString token = tk.GetNextToken();
5455  long ival;
5456  if (token.ToLong(&ival)) {
5457  wxString decode_val = GetAttributeDecode(AttrName, ival);
5458  if (!decode_val.IsEmpty())
5459  value_increment = decode_val;
5460  else
5461  value_increment.Printf(_T(" %d"), (int)ival);
5462 
5463  if (iv) value_increment.Prepend(wxT(", "));
5464  }
5465  value.Append(value_increment);
5466 
5467  iv++;
5468  }
5469  value.Append(val_str);
5470  }
5471  } else
5472  value = _T("[NULL VALUE]");
5473 
5474  break;
5475  }
5476 
5477  case OGR_INT: {
5478  int ival = *((int *)pAttrVal->value);
5479  wxString decode_val = GetAttributeDecode(AttrName, ival);
5480 
5481  if (!decode_val.IsEmpty()) {
5482  value = decode_val;
5483  wxString iv;
5484  iv.Printf(_T("(%d)"), ival);
5485  value.Append(iv);
5486  } else
5487  value.Printf(_T("(%d)"), ival);
5488 
5489  break;
5490  }
5491  case OGR_INT_LST:
5492  break;
5493 
5494  case OGR_REAL: {
5495  double dval = *((double *)pAttrVal->value);
5496  wxString val_suffix = _T(" m");
5497 
5498  // As a special case, convert some attribute values to feet.....
5499  if ((AttrName == _T("VERCLR")) || (AttrName == _T("VERCCL")) ||
5500  (AttrName == _T("VERCOP")) || (AttrName == _T("HEIGHT")) ||
5501  (AttrName == _T("HORCLR"))) {
5502  switch (ps52plib->m_nDepthUnitDisplay) {
5503  case 0: // feet
5504  case 2: // fathoms
5505  dval = dval * 3 * 39.37 / 36; // feet
5506  val_suffix = _T(" ft");
5507  break;
5508  default:
5509  break;
5510  }
5511  }
5512 
5513  else if ((AttrName == _T("VALSOU")) || (AttrName == _T("DRVAL1")) ||
5514  (AttrName == _T("DRVAL2"))) {
5515  switch (ps52plib->m_nDepthUnitDisplay) {
5516  case 0: // feet
5517  dval = dval * 3 * 39.37 / 36; // feet
5518  val_suffix = _T(" ft");
5519  break;
5520  case 2: // fathoms
5521  dval = dval * 3 * 39.37 / 36; // fathoms
5522  dval /= 6.0;
5523  val_suffix = _T(" fathoms");
5524  break;
5525  default:
5526  break;
5527  }
5528  }
5529 
5530  else if (AttrName == _T("SECTR1"))
5531  val_suffix = _T("&deg;");
5532  else if (AttrName == _T("SECTR2"))
5533  val_suffix = _T("&deg;");
5534  else if (AttrName == _T("ORIENT"))
5535  val_suffix = _T("&deg;");
5536  else if (AttrName == _T("VALNMR"))
5537  val_suffix = _T(" Nm");
5538  else if (AttrName == _T("SIGPER"))
5539  val_suffix = _T("s");
5540  else if (AttrName == _T("VALACM"))
5541  val_suffix = _T(" Minutes/year");
5542  else if (AttrName == _T("VALMAG"))
5543  val_suffix = _T("&deg;");
5544  else if (AttrName == _T("CURVEL"))
5545  val_suffix = _T(" kt");
5546 
5547  if (dval - floor(dval) < 0.01)
5548  value.Printf(_T("%2.0f"), dval);
5549  else
5550  value.Printf(_T("%4.1f"), dval);
5551 
5552  value << val_suffix;
5553 
5554  break;
5555  }
5556 
5557  case OGR_REAL_LST: {
5558  break;
5559  }
5560  }
5561  return value;
5562 }
5563 
5564 bool s57chart::CompareLights(const S57Light *l1, const S57Light *l2) {
5565  int positionDiff = l1->position.Cmp(l2->position);
5566  if (positionDiff < 0) return false;
5567 
5568  int attrIndex1 = l1->attributeNames.Index(_T("SECTR1"));
5569  int attrIndex2 = l2->attributeNames.Index(_T("SECTR1"));
5570 
5571  // This should put Lights without sectors last in the list.
5572  if (attrIndex1 == wxNOT_FOUND && attrIndex2 == wxNOT_FOUND) return false;
5573  if (attrIndex1 != wxNOT_FOUND && attrIndex2 == wxNOT_FOUND) return true;
5574  if (attrIndex1 == wxNOT_FOUND && attrIndex2 != wxNOT_FOUND) return false;
5575 
5576  double angle1, angle2;
5577  l1->attributeValues.Item(attrIndex1).ToDouble(&angle1);
5578  l2->attributeValues.Item(attrIndex2).ToDouble(&angle2);
5579 
5580  return angle1 < angle2;
5581 }
5582 
5583 static const char *type2str(GeoPrim_t type) {
5584  const char *r = "Unknown";
5585  switch (type) {
5586  case GEO_POINT:
5587  return "Point";
5588  break;
5589  case GEO_LINE:
5590  return "Line";
5591  break;
5592  case GEO_AREA:
5593  return "Area";
5594  break;
5595  case GEO_META:
5596  return "Meta";
5597  break;
5598  case GEO_PRIM:
5599  return "Prim";
5600  break;
5601  }
5602  return r;
5603 }
5604 
5605 wxString s57chart::CreateObjDescriptions(ListOfObjRazRules *rule_list) {
5606  wxString ret_val;
5607  int attrCounter;
5608  wxString curAttrName, value;
5609  bool isLight = false;
5610  wxString className;
5611  wxString classDesc;
5612  wxString classAttributes;
5613  wxString objText;
5614  wxString lightsHtml;
5615  wxString positionString;
5616  std::vector<S57Light *> lights;
5617  S57Light *curLight = nullptr;
5618  wxFileName file;
5619 
5620  for (ListOfObjRazRules::Node *node = rule_list->GetLast(); node;
5621  node = node->GetPrevious()) {
5622  ObjRazRules *current = node->GetData();
5623  positionString.Clear();
5624  objText.Clear();
5625 
5626  // Soundings have no information, so don't show them
5627  if (0 == strncmp(current->LUP->OBCL, "SOUND", 5)) continue;
5628 
5629  if (current->obj->Primitive_type == GEO_META) continue;
5630  if (current->obj->Primitive_type == GEO_PRIM) continue;
5631 
5632  className = wxString(current->obj->FeatureName, wxConvUTF8);
5633 
5634  // Lights get grouped together to make display look nicer.
5635  isLight = !strcmp(current->obj->FeatureName, "LIGHTS");
5636 
5637  // Get the object's nice description from s57objectclasses.csv
5638  // using cpl_csv from the gdal library
5639 
5640  const char *name_desc;
5641  if (g_csv_locn.Len()) {
5642  wxString oc_file(g_csv_locn);
5643  oc_file.Append(_T("/s57objectclasses.csv"));
5644  name_desc = MyCSVGetField(oc_file.mb_str(), "Acronym", // match field
5645  current->obj->FeatureName, // match value
5646  CC_ExactString, "ObjectClass"); // return field
5647  } else
5648  name_desc = "";
5649 
5650  // In case there is no nice description for this object class, use the 6
5651  // char class name
5652  if (0 == strlen(name_desc)) {
5653  name_desc = current->obj->FeatureName;
5654  classDesc = wxString(name_desc, wxConvUTF8, 1);
5655  classDesc << wxString(name_desc + 1, wxConvUTF8).MakeLower();
5656  } else {
5657  classDesc = wxString(name_desc, wxConvUTF8);
5658  }
5659 
5660  // Show LUP
5661  if (g_bDebugS57) {
5662  wxString index;
5663 
5664  classAttributes = _T("");
5665  index.Printf(_T("Feature Index: %d<br>"), current->obj->Index);
5666  classAttributes << index;
5667 
5668  wxString LUPstring;
5669  LUPstring.Printf(_T("LUP RCID: %d<br>"), current->LUP->RCID);
5670  classAttributes << LUPstring;
5671 
5672  wxString Bbox;
5673  LLBBox bbox = current->obj->BBObj;
5674  Bbox.Printf(_T("Lat/Lon box: %g %g %g %g<br>"), bbox.GetMinLat(),
5675  bbox.GetMaxLat(), bbox.GetMinLon(), bbox.GetMaxLon());
5676  classAttributes << Bbox;
5677 
5678  wxString Type;
5679  Type.Printf(_T(" Type: %s<br>"), type2str(current->obj->Primitive_type));
5680  classAttributes << Type;
5681 
5682  LUPstring = _T(" LUP ATTC: ");
5683  if (current->LUP->ATTArray.size())
5684  LUPstring += wxString(current->LUP->ATTArray[0].c_str(), wxConvUTF8);
5685  LUPstring += _T("<br>");
5686  classAttributes << LUPstring;
5687 
5688  LUPstring = _T(" LUP INST: ");
5689  LUPstring += current->LUP->INST;
5690  LUPstring += _T("<br><br>");
5691  classAttributes << LUPstring;
5692  }
5693 
5694  if (GEO_POINT == current->obj->Primitive_type) {
5695  double lon, lat;
5696  fromSM((current->obj->x * current->obj->x_rate) + current->obj->x_origin,
5697  (current->obj->y * current->obj->y_rate) + current->obj->y_origin,
5698  ref_lat, ref_lon, &lat, &lon);
5699 
5700  if (lon > 180.0) lon -= 360.;
5701 
5702  positionString.Clear();
5703  positionString += toSDMM(1, lat);
5704  positionString << _T(" ");
5705  positionString += toSDMM(2, lon);
5706 
5707  if (isLight) {
5708  curLight = new S57Light;
5709  curLight->position = positionString;
5710  curLight->hasSectors = false;
5711  lights.push_back(curLight);
5712  }
5713  }
5714 
5715  // Get the Attributes and values, making sure they can be converted from
5716  // UTF8
5717  if (current->obj->att_array) {
5718  char *curr_att = current->obj->att_array;
5719 
5720  attrCounter = 0;
5721 
5722  wxString attribStr;
5723  int noAttr = 0;
5724  attribStr << _T("<table border=0 cellspacing=0 cellpadding=0>");
5725 
5726  if (g_bDebugS57) {
5727  ret_val << _T("<p>") << classAttributes;
5728  }
5729 
5730  bool inDepthRange = false;
5731 
5732  while (attrCounter < current->obj->n_attr) {
5733  // Attribute name
5734  curAttrName = wxString(curr_att, wxConvUTF8, 6);
5735  noAttr++;
5736 
5737  // Sort out how some kinds of attibutes are displayed to get a more
5738  // readable look. DEPARE gets just its range. Lights are grouped.
5739 
5740  if (isLight) {
5741  assert(curLight != nullptr);
5742  curLight->attributeNames.Add(curAttrName);
5743  if (curAttrName.StartsWith(_T("SECTR"))) curLight->hasSectors = true;
5744  } else {
5745  if (curAttrName == _T("DRVAL1")) {
5746  attribStr << _T("<tr><td><font size=-1>");
5747  inDepthRange = true;
5748  } else if (curAttrName == _T("DRVAL2")) {
5749  attribStr << _T(" - ");
5750  inDepthRange = false;
5751  } else {
5752  if (inDepthRange) {
5753  attribStr << _T("</font></td></tr>\n");
5754  inDepthRange = false;
5755  }
5756  attribStr << _T("<tr><td valign=top><font size=-2>");
5757  if (curAttrName == _T("catgeo"))
5758  attribStr << _T("CATGEO");
5759  else
5760  attribStr << curAttrName;
5761  attribStr << _T("</font></td><td>&nbsp;&nbsp;</td><td ")
5762  _T("valign=top><font size=-1>");
5763  }
5764  }
5765 
5766  // What we need to do...
5767  // Change senc format, instead of (S), (I), etc, use the attribute types
5768  // fetched from the S57attri...csv file This will be like (E), (L), (I),
5769  // (F)
5770  // will affect lots of other stuff. look for S57attVal.valType
5771  // need to do this in creatsencrecord above, and update the senc format.
5772 
5773  value = GetObjectAttributeValueAsString(current->obj, attrCounter,
5774  curAttrName);
5775 
5776  // If the atribute value is a filename, change the value into a link to
5777  // that file
5778  wxString AttrNamesFiles =
5779  _T("PICREP,TXTDSC,NTXTDS"); // AttrNames that might have a filename
5780  // as value
5781  if (AttrNamesFiles.Find(curAttrName) != wxNOT_FOUND)
5782  if (value.Find(_T(".XML")) == wxNOT_FOUND) { // Don't show xml files
5783  file.Assign(GetFullPath());
5784  file.Assign(file.GetPath(), value);
5785  file.Normalize();
5786  if (file.IsOk()) {
5787  if (file.Exists())
5788  value =
5789  wxString::Format(_T("<a href=\"%s\">%s</a>"),
5790  file.GetFullPath(), file.GetFullName());
5791  else
5792  value = value + _T("&nbsp;&nbsp;<font color=\"red\">[ ") +
5793  _("this file is not available") + _T(" ]</font>");
5794  }
5795  }
5796  AttrNamesFiles =
5797  _T("DATEND,DATSTA,PEREND,PERSTA"); // AttrNames with date info
5798  if (AttrNamesFiles.Find(curAttrName) != wxNOT_FOUND) {
5799  bool d = true;
5800  bool m = true;
5801  wxString ts = value;
5802 
5803  ts.Replace(wxT("--"),
5804  wxT("0000")); // make a valid year entry if not available
5805  if (ts.Length() < 5) { //(no month set)
5806  m = false;
5807  ts.Append(
5808  wxT("01")); // so we add a fictive month to get a valid date
5809  }
5810  if (ts.Length() < 7) { //(no day set)
5811  d = false;
5812  ts.Append(
5813  wxT("01")); // so we add a fictive day to get a valid date
5814  }
5815  wxString::const_iterator end;
5816  wxDateTime dt;
5817  if (dt.ParseFormat(ts, "%Y%m%d", &end)) {
5818  ts.Empty();
5819  if (m) ts = wxDateTime::GetMonthName(dt.GetMonth());
5820  if (d) ts.Append(wxString::Format(wxT(" %d"), dt.GetDay()));
5821  if (dt.GetYear() > 0)
5822  ts.Append(wxString::Format(wxT(", %i"), dt.GetYear()));
5823  if (curAttrName == _T("PEREND"))
5824  ts = _("Period ends: ") + ts + wxT(" (") + value + wxT(")");
5825  if (curAttrName == _T("PERSTA"))
5826  ts = _("Period starts: ") + ts + wxT(" (") + value + wxT(")");
5827  if (curAttrName == _T("DATEND"))
5828  ts = _("Date ending: ") + ts + wxT(" (") + value + wxT(")");
5829  if (curAttrName == _T("DATSTA"))
5830  ts = _("Date starting: ") + ts + wxT(" (") + value + wxT(")");
5831  value = ts;
5832  }
5833  }
5834  if (curAttrName == _T("TS_TSP")) { // Tidal current applet
5835  wxArrayString as;
5836  wxString ts, ts1;
5837  // value does look like: , 310, 310, 44, 44, 116, 116, 119, 119, 122,
5838  // 122, 125, 125, 130, 130, 270, 270, 299, 299, 300, 300, 301, 301,
5839  // 303, 303, 307,307509A,Helgoland,HW,310,0.9,044,0.2,116,1.5,
5840  // 119,2.2,122,1.9,125,1.5,130,0.9,270,0.1,299,1.4,300,2.1,301,2.0,303,1.7,307,1.2
5841  wxStringTokenizer tk(value, wxT(","));
5842  ts1 =
5843  tk.GetNextToken(); // get first token this will be skipped always
5844  long l;
5845  do { // Skip up upto the first non number. This is Port Name
5846  ts1 = tk.GetNextToken().Trim(false);
5847  // some harbourID do have an alpha extension, therefore only check
5848  // the left(2)
5849  } while ((ts1.Left(2).ToLong(&l)));
5850  ts = _T("Tidal Streams referred to<br><b>");
5851  ts.Append(tk.GetNextToken()).Append(_T("</b> at <b>")).Append(ts1);
5852  ts.Append(_T("</b><br><table >"));
5853  int i = -6;
5854  while (tk.HasMoreTokens()) { // fill the current table
5855  ts.Append(_T("<tr><td>"));
5856  wxString s1(wxString::Format(_T("%+dh "), i));
5857  ts.Append(s1);
5858  ts.Append(_T("</td><td>"));
5859  s1 = tk.GetNextToken();
5860  ts.Append(s1);
5861  s1 = "&#176</td><td>";
5862  ts.Append(s1);
5863  s1 = tk.GetNextToken();
5864  ts.Append(s1);
5865  ts.Append(" kn");
5866  ts.Append(_T("</td></tr>"));
5867  i++;
5868  }
5869  ts.Append(_T("</table>"));
5870  value = ts;
5871  }
5872 
5873  if (isLight) {
5874  assert(curLight != nullptr);
5875  curLight->attributeValues.Add(value);
5876  } else {
5877  if (curAttrName == _T("INFORM") || curAttrName == _T("NINFOM"))
5878  value.Replace(_T("|"), _T("<br>"));
5879 
5880  if (curAttrName == _T("catgeo"))
5881  attribStr << type2str(current->obj->Primitive_type);
5882  else
5883  attribStr << value;
5884 
5885  if (!(curAttrName == _T("DRVAL1"))) {
5886  attribStr << _T("</font></td></tr>\n");
5887  }
5888  }
5889 
5890  attrCounter++;
5891  curr_att += 6;
5892 
5893  } // while attrCounter < current->obj->n_attr
5894 
5895  if (!isLight) {
5896  attribStr << _T("</table>\n");
5897 
5898  objText += _T("<b>") + classDesc + _T("</b> <font size=-2>(") +
5899  className + _T(")</font>") + _T("<br>");
5900 
5901  if (positionString.Length())
5902  objText << _T("<font size=-2>") << positionString
5903  << _T("</font><br>\n");
5904 
5905  if (noAttr > 0) objText << attribStr;
5906 
5907  if (node != rule_list->GetFirst()) objText += _T("<hr noshade>");
5908  objText += _T("<br>");
5909  ret_val << objText;
5910  }
5911  }
5912  } // Object for loop
5913 
5914  if (!lights.empty()) {
5915  assert(curLight != nullptr);
5916 
5917  // For lights we now have all the info gathered but no HTML output yet, now
5918  // run through the data and build a merged table for all lights.
5919 
5920  std::sort(lights.begin(), lights.end(), s57chart::CompareLights);
5921 
5922  wxString lastPos;
5923 
5924  for (auto const &thisLight : lights) {
5925  int attrIndex;
5926 
5927  if (thisLight->position != lastPos) {
5928  lastPos = thisLight->position;
5929 
5930  if (thisLight != *lights.begin())
5931  lightsHtml << _T("</table>\n<hr noshade>\n");
5932 
5933  lightsHtml << _T("<b>Light</b> <font size=-2>(LIGHTS)</font><br>");
5934  lightsHtml << _T("<font size=-2>") << thisLight->position
5935  << _T("</font><br>\n");
5936 
5937  if (curLight->hasSectors)
5938  lightsHtml << _(
5939  "<font size=-2>(Sector angles are True Bearings from "
5940  "Seaward)</font><br>");
5941 
5942  lightsHtml << _T("<table>");
5943  }
5944 
5945  lightsHtml << _T("<tr>");
5946  lightsHtml << _T("<td><font size=-1>");
5947 
5948  wxString colorStr;
5949  attrIndex = thisLight->attributeNames.Index(_T("COLOUR"));
5950  if (attrIndex != wxNOT_FOUND) {
5951  wxString color = thisLight->attributeValues.Item(attrIndex);
5952  if (color == _T("red (3)") || color == _T("red(3)"))
5953  colorStr =
5954  _T("<table border=0><tr><td ")
5955  _T("bgcolor=red>&nbsp;&nbsp;&nbsp;</td></tr></table> ");
5956  else if (color == _T("green (4)") || color == _T("green(4)"))
5957  colorStr =
5958  _T("<table border=0><tr><td ")
5959  _T("bgcolor=green>&nbsp;&nbsp;&nbsp;</td></tr></table> ");
5960  else if (color == _T("white (1)") || color == _T("white(1)"))
5961  colorStr =
5962  _T("<table border=0><tr><td ")
5963  _T("bgcolor=white>&nbsp;&nbsp;&nbsp;</td></tr></table> ");
5964  else if (color == _T("yellow (6)") || color == _T("yellow(6)"))
5965  colorStr =
5966  _T("<table border=0><tr><td ")
5967  _T("bgcolor=yellow>&nbsp;&nbsp;&nbsp;</td></tr></table> ");
5968  else if (color == _T("blue (5)") || color == _T("blue(5)"))
5969  colorStr =
5970  _T("<table border=0><tr><td ")
5971  _T("bgcolor=blue>&nbsp;&nbsp;&nbsp;</td></tr></table> ");
5972  else if (color == _T("magenta (12)") || color == _T("magenta(12)"))
5973  colorStr =
5974  _T("<table border=0><tr><td ")
5975  _T("bgcolor=magenta>&nbsp;&nbsp;&nbsp;</td></tr></table> ");
5976  else
5977  colorStr =
5978  _T("<table border=0><tr><td ")
5979  _T("bgcolor=grey>&nbsp;?&nbsp;</td></tr></table> ");
5980  }
5981 
5982  int visIndex = thisLight->attributeNames.Index(_T("LITVIS"));
5983  if (visIndex != wxNOT_FOUND) {
5984  wxString vis = thisLight->attributeValues.Item(visIndex);
5985  if (vis.Contains(_T("8"))) {
5986  if (attrIndex != wxNOT_FOUND) {
5987  wxString color = thisLight->attributeValues.Item(attrIndex);
5988  if (( color == _T("red (3)") || color == _T("red(3)")))
5989  colorStr =
5990  _T("<table border=0><tr><td ")
5991  _T("bgcolor=DarkRed>&nbsp;&nbsp;&nbsp;</td></tr></table> ");
5992  if (( color == _T("green (4)") || color == _T("green(4)")))
5993  colorStr =
5994  _T("<table border=0><tr><td ")
5995  _T("bgcolor=DarkGreen>&nbsp;&nbsp;&nbsp;</td></tr></table> ");
5996  if (( color == _T("white (1)") || color == _T("white(1)")))
5997  colorStr =
5998  _T("<table border=0><tr><td ")
5999  _T("bgcolor=GoldenRod>&nbsp;&nbsp;&nbsp;</td></tr></table> ");
6000  }
6001  }
6002  }
6003 
6004  lightsHtml << colorStr;
6005 
6006  lightsHtml << _T("</font></td><td><font size=-1><nobr><b>");
6007 
6008  attrIndex = thisLight->attributeNames.Index(_T("LITCHR"));
6009  if (attrIndex != wxNOT_FOUND) {
6010  wxString character = thisLight->attributeValues[attrIndex];
6011  lightsHtml << character.BeforeFirst(wxChar('(')) << _T(" ");
6012  }
6013 
6014  attrIndex = thisLight->attributeNames.Index(_T("SIGGRP"));
6015  if (attrIndex != wxNOT_FOUND) {
6016  lightsHtml << thisLight->attributeValues[attrIndex];
6017  lightsHtml << _T(" ");
6018  }
6019 
6020  attrIndex = thisLight->attributeNames.Index( _T("COLOUR") );
6021  if( attrIndex != wxNOT_FOUND ) {
6022  lightsHtml << _T(" ") << thisLight->attributeValues.Item( attrIndex ).Upper()[0];
6023  lightsHtml << _T(" ");
6024  }
6025 
6026  attrIndex = thisLight->attributeNames.Index(_T("SIGPER"));
6027  if (attrIndex != wxNOT_FOUND) {
6028  lightsHtml << thisLight->attributeValues[attrIndex];
6029  lightsHtml << _T(" ");
6030  }
6031 
6032  attrIndex = thisLight->attributeNames.Index(_T("HEIGHT"));
6033  if (attrIndex != wxNOT_FOUND) {
6034  lightsHtml << thisLight->attributeValues[attrIndex];
6035  lightsHtml << _T(" ");
6036  }
6037 
6038  attrIndex = thisLight->attributeNames.Index(_T("VALNMR"));
6039  if (attrIndex != wxNOT_FOUND) {
6040  lightsHtml << thisLight->attributeValues[attrIndex];
6041  lightsHtml << _T(" ");
6042  }
6043 
6044  lightsHtml << _T("</b>");
6045 
6046  attrIndex = thisLight->attributeNames.Index(_T("SECTR1"));
6047  if (attrIndex != wxNOT_FOUND) {
6048  lightsHtml << _T("(") << thisLight->attributeValues[attrIndex];
6049  lightsHtml << _T(" - ");
6050  attrIndex = thisLight->attributeNames.Index(_T("SECTR2"));
6051  lightsHtml << thisLight->attributeValues[attrIndex] << _T(") ");
6052  }
6053 
6054  lightsHtml << _T("</nobr>");
6055 
6056  attrIndex = thisLight->attributeNames.Index(_T("CATLIT"));
6057  if (attrIndex != wxNOT_FOUND) {
6058  lightsHtml << _T("<nobr>");
6059  lightsHtml << thisLight->attributeValues[attrIndex].BeforeFirst(
6060  wxChar('('));
6061  lightsHtml << _T("</nobr> ");
6062  }
6063 
6064  attrIndex = thisLight->attributeNames.Index(_T("EXCLIT"));
6065  if (attrIndex != wxNOT_FOUND) {
6066  lightsHtml << _T("<nobr>");
6067  lightsHtml << thisLight->attributeValues[attrIndex].BeforeFirst(
6068  wxChar('('));
6069  lightsHtml << _T("</nobr> ");
6070  }
6071 
6072  attrIndex = thisLight->attributeNames.Index(_T("OBJNAM"));
6073  if (attrIndex != wxNOT_FOUND) {
6074  lightsHtml << _T("<br><nobr>");
6075  lightsHtml << thisLight->attributeValues[attrIndex].Left(1).Upper();
6076  lightsHtml << thisLight->attributeValues[attrIndex].Mid(1);
6077  lightsHtml << _T("</nobr> ");
6078  }
6079 
6080  lightsHtml << _T("</font></td>");
6081  lightsHtml << _T("</tr>");
6082 
6083  thisLight->attributeNames.Clear();
6084  thisLight->attributeValues.Clear();
6085  delete thisLight;
6086  }
6087  lightsHtml << _T("</table><hr noshade>\n");
6088  ret_val = lightsHtml << ret_val;
6089 
6090  lights.clear();
6091  }
6092 
6093  return ret_val;
6094 }
6095 
6096 //------------------------------------------------------------------------
6097 //
6098 // S57 ENC (i.e. "raw") DataSet support functions
6099 // Not bulletproof, so call carefully
6100 //
6101 //------------------------------------------------------------------------
6102 bool s57chart::InitENCMinimal(const wxString &FullPath) {
6103  if (NULL == g_poRegistrar) {
6104  wxLogMessage(_T(" Error: No ClassRegistrar in InitENCMinimal."));
6105  return false;
6106  }
6107 
6108  m_pENCDS = new OGRS57DataSource;
6109 
6110  m_pENCDS->SetS57Registrar(g_poRegistrar);
6111 
6112  if (!m_pENCDS->OpenMin(FullPath.mb_str(), TRUE))
6113  return false;
6114 
6115  S57Reader *pENCReader = m_pENCDS->GetModule(0);
6116  pENCReader->SetClassBased(g_poRegistrar);
6117 
6118  pENCReader->Ingest();
6119 
6120  return true;
6121 }
6122 
6123 OGRFeature *s57chart::GetChartFirstM_COVR(int &catcov) {
6124  // Get the reader
6125  S57Reader *pENCReader = m_pENCDS->GetModule(0);
6126 
6127  if ((NULL != pENCReader) && (NULL != g_poRegistrar)) {
6128  // Select the proper class
6129  g_poRegistrar->SelectClass("M_COVR");
6130 
6131  // Build a new feature definition for this class
6132  OGRFeatureDefn *poDefn = S57GenerateObjectClassDefn(
6133  g_poRegistrar, g_poRegistrar->GetOBJL(), pENCReader->GetOptionFlags());
6134 
6135  // Add this feature definition to the reader
6136  pENCReader->AddFeatureDefn(poDefn);
6137 
6138  // Also, add as a Layer to Datasource to ensure proper deletion
6139  m_pENCDS->AddLayer(new OGRS57Layer(m_pENCDS, poDefn, 1));
6140 
6141  // find this feature
6142  OGRFeature *pobjectDef = pENCReader->ReadNextFeature(poDefn);
6143  if (pobjectDef) {
6144  // Fetch the CATCOV attribute
6145  catcov = pobjectDef->GetFieldAsInteger("CATCOV");
6146  return pobjectDef;
6147  }
6148 
6149  else {
6150  return NULL;
6151  }
6152  } else
6153  return NULL;
6154 }
6155 
6156 OGRFeature *s57chart::GetChartNextM_COVR(int &catcov) {
6157  catcov = -1;
6158 
6159  // Get the reader
6160  S57Reader *pENCReader = m_pENCDS->GetModule(0);
6161 
6162  // Get the Feature Definition, stored in Layer 0
6163  OGRFeatureDefn *poDefn = m_pENCDS->GetLayer(0)->GetLayerDefn();
6164 
6165  if (pENCReader) {
6166  OGRFeature *pobjectDef = pENCReader->ReadNextFeature(poDefn);
6167 
6168  if (pobjectDef) {
6169  catcov = pobjectDef->GetFieldAsInteger("CATCOV");
6170  return pobjectDef;
6171  }
6172 
6173  return NULL;
6174  } else
6175  return NULL;
6176 }
6177 
6178 int s57chart::GetENCScale(void) {
6179  if (NULL == m_pENCDS) return 0;
6180 
6181  // Assume that chart has been initialized for minimal ENC access
6182  // which implies that the ENC has been fully ingested, and some
6183  // interesting values have been extracted thereby.
6184 
6185  // Get the reader
6186  S57Reader *pENCReader = m_pENCDS->GetModule(0);
6187 
6188  if (pENCReader)
6189  return pENCReader->GetCSCL();
6190  else
6191  return 1;
6192 }
6193 
6194 /************************************************************************/
6195 /* OpenCPN_OGRErrorHandler() */
6196 /* Use Global wxLog Class */
6197 /************************************************************************/
6198 
6199 void OpenCPN_OGRErrorHandler(CPLErr eErrClass, int nError,
6200  const char *pszErrorMsg) {
6201 #define ERR_BUF_LEN 2000
6202 
6203  char buf[ERR_BUF_LEN + 1];
6204 
6205  if (eErrClass == CE_Debug)
6206  sprintf(buf, " %s", pszErrorMsg);
6207  else if (eErrClass == CE_Warning)
6208  sprintf(buf, " Warning %d: %s\n", nError, pszErrorMsg);
6209  else
6210  sprintf(buf, " ERROR %d: %s\n", nError, pszErrorMsg);
6211 
6212  if (g_bGDAL_Debug || (CE_Debug != eErrClass)) { // log every warning or error
6213  wxString msg(buf, wxConvUTF8);
6214  wxLogMessage(msg);
6215  }
6216 
6217  // Do not simply return on CE_Fatal errors, as we don't want to abort()
6218 
6219  if (eErrClass == CE_Fatal) {
6220  longjmp(env_ogrf, 1); // jump back to the setjmp() point
6221  }
6222 }
6223 
6224 // In GDAL-1.2.0, CSVGetField is not exported.......
6225 // So, make my own simplified copy
6226 /************************************************************************/
6227 /* MyCSVGetField() */
6228 /* */
6229 /************************************************************************/
6230 
6231 const char *MyCSVGetField(const char *pszFilename, const char *pszKeyFieldName,
6232  const char *pszKeyFieldValue,
6233  CSVCompareCriteria eCriteria,
6234  const char *pszTargetField)
6235 
6236 {
6237  char **papszRecord;
6238  int iTargetField;
6239 
6240  /* -------------------------------------------------------------------- */
6241  /* Find the correct record. */
6242  /* -------------------------------------------------------------------- */
6243  papszRecord = CSVScanFileByName(pszFilename, pszKeyFieldName,
6244  pszKeyFieldValue, eCriteria);
6245 
6246  if (papszRecord == NULL) return "";
6247 
6248  /* -------------------------------------------------------------------- */
6249  /* Figure out which field we want out of this. */
6250  /* -------------------------------------------------------------------- */
6251  iTargetField = CSVGetFileFieldId(pszFilename, pszTargetField);
6252  if (iTargetField < 0) return "";
6253 
6254  if (iTargetField >= CSLCount(papszRecord)) return "";
6255 
6256  return (papszRecord[iTargetField]);
6257 }
6258 
6259 //------------------------------------------------------------------------
6260 //
6261 // Some s57 Utilities
6262 // Meant to be called "bare", usually with no class instance.
6263 //
6264 //------------------------------------------------------------------------
6265 
6266 //----------------------------------------------------------------------------------
6267 // Get Chart Extents
6268 //----------------------------------------------------------------------------------
6269 
6270 bool s57_GetChartExtent(const wxString &FullPath, Extent *pext) {
6271  // Fix this find extents of which?? layer??
6272  /*
6273  OGRS57DataSource *poDS = new OGRS57DataSource;
6274  poDS->Open(pFullPath, TRUE);
6275 
6276  if( poDS == NULL )
6277  return false;
6278 
6279  OGREnvelope Env;
6280  S57Reader *poReader = poDS->GetModule(0);
6281  poReader->GetExtent(&Env, true);
6282 
6283  pext->NLAT = Env.MaxY;
6284  pext->ELON = Env.MaxX;
6285  pext->SLAT = Env.MinY;
6286  pext->WLON = Env.MinX;
6287 
6288  delete poDS;
6289  */
6290  return false;
6291 }
6292 
6293 void s57_DrawExtendedLightSectors(ocpnDC &dc, ViewPort &viewport,
6294  std::vector<s57Sector_t> &sectorlegs) {
6295  float rangeScale = 0.0;
6296 
6297  if (sectorlegs.size() > 0) {
6298  std::vector<int> sectorangles;
6299  for (unsigned int i = 0; i < sectorlegs.size(); i++) {
6300  if (fabs(sectorlegs[i].sector1 - sectorlegs[i].sector2) < 0.3) continue;
6301 
6302  double endx, endy;
6303  ll_gc_ll(sectorlegs[i].pos.m_y, sectorlegs[i].pos.m_x,
6304  sectorlegs[i].sector1 + 180.0, sectorlegs[i].range, &endy,
6305  &endx);
6306 
6307  wxPoint end1 = viewport.GetPixFromLL(endy, endx);
6308 
6309  ll_gc_ll(sectorlegs[i].pos.m_y, sectorlegs[i].pos.m_x,
6310  sectorlegs[i].sector2 + 180.0, sectorlegs[i].range, &endy,
6311  &endx);
6312 
6313  wxPoint end2 = viewport.GetPixFromLL(endy, endx);
6314 
6315  wxPoint lightPos =
6316  viewport.GetPixFromLL(sectorlegs[i].pos.m_y, sectorlegs[i].pos.m_x);
6317 
6318  // Make sure arcs are well inside viewport.
6319  float rangePx = sqrtf(powf((float)(lightPos.x - end1.x), 2) +
6320  powf((float)(lightPos.y - end1.y), 2));
6321  rangePx /= 3.0;
6322  if (rangeScale == 0.0) {
6323  rangeScale = 1.0;
6324  if (rangePx > viewport.pix_height / 3) {
6325  rangeScale *= (viewport.pix_height / 3) / rangePx;
6326  }
6327  }
6328 
6329  rangePx = rangePx * rangeScale;
6330 
6331  int penWidth = rangePx / 8;
6332  penWidth = wxMin(20, penWidth);
6333  penWidth = wxMax(5, penWidth);
6334 
6335 
6336  int legOpacity;
6337  wxPen *arcpen = wxThePenList->FindOrCreatePen(sectorlegs[i].color, penWidth,
6338  wxPENSTYLE_SOLID);
6339  arcpen->SetCap(wxCAP_BUTT);
6340  dc.SetPen(*arcpen);
6341 
6342  float angle1, angle2;
6343  angle1 = -(sectorlegs[i].sector2 + 90.0) - viewport.rotation * 180.0 / PI;
6344  angle2 = -(sectorlegs[i].sector1 + 90.0) - viewport.rotation * 180.0 / PI;
6345  if (angle1 > angle2) {
6346  angle2 += 360.0;
6347  }
6348  int lpx = lightPos.x;
6349  int lpy = lightPos.y;
6350  int npoints = 0;
6351  wxPoint arcpoints[150]; // Size relates to "step" below.
6352 
6353  float step = 3.0;
6354  while ((step < 15) && ((rangePx * sin(step * PI / 180.)) < 10))
6355  step += 2.0; // less points on small arcs
6356 
6357  // Make sure we start and stop exactly on the leg lines.
6358  int narc = (angle2 - angle1) / step;
6359  narc++;
6360  step = (angle2 - angle1) / (float)narc;
6361 
6362  if (sectorlegs[i].isleading && (angle2 - angle1 < 60)) {
6363  wxPoint yellowCone[3];
6364  yellowCone[0] = lightPos;
6365  yellowCone[1] = end1;
6366  yellowCone[2] = end2;
6367  arcpen = wxThePenList->FindOrCreatePen(wxColor(0, 0, 0, 0), 1,
6368  wxPENSTYLE_SOLID);
6369  dc.SetPen(*arcpen);
6370  wxColor c = sectorlegs[i].color;
6371  c.Set(c.Red(), c.Green(), c.Blue(), 0.6 * c.Alpha());
6372  dc.SetBrush(wxBrush(c));
6373  dc.StrokePolygon(3, yellowCone, 0, 0);
6374  legOpacity = 50;
6375  } else {
6376  for (float a = angle1; a <= angle2 + 0.1; a += step) {
6377  int x = lpx + (int)(rangePx * cos(a * PI / 180.));
6378  int y = lpy - (int)(rangePx * sin(a * PI / 180.));
6379  arcpoints[npoints].x = x;
6380  arcpoints[npoints].y = y;
6381  npoints++;
6382  }
6383  dc.StrokeLines(npoints, arcpoints);
6384  legOpacity = 128;
6385  }
6386 
6387  arcpen = wxThePenList->FindOrCreatePen(wxColor(0, 0, 0, legOpacity), 1,
6388  wxPENSTYLE_SOLID);
6389  dc.SetPen(*arcpen);
6390 
6391  // Only draw each leg line once.
6392 
6393  bool haveAngle1 = false;
6394  bool haveAngle2 = false;
6395  int sec1 = (int)sectorlegs[i].sector1;
6396  int sec2 = (int)sectorlegs[i].sector2;
6397  if (sec1 > 360) sec1 -= 360;
6398  if (sec2 > 360) sec2 -= 360;
6399 
6400  if ((sec2 == 360) && (sec1 == 0)) // FS#1437
6401  continue;
6402 
6403  for (unsigned int j = 0; j < sectorangles.size(); j++) {
6404  if (sectorangles[j] == sec1) haveAngle1 = true;
6405  if (sectorangles[j] == sec2) haveAngle2 = true;
6406  }
6407 
6408  if (!haveAngle1) {
6409  dc.StrokeLine(lightPos, end1);
6410  sectorangles.push_back(sec1);
6411  }
6412 
6413  if (!haveAngle2) {
6414  dc.StrokeLine(lightPos, end2);
6415  sectorangles.push_back(sec2);
6416  }
6417  }
6418  }
6419 }
6420 
6421 #ifdef ocpnUSE_GL
6422 void s57_DrawExtendedLightSectorsGL(ocpnDC &dc, ViewPort &viewport,
6423  std::vector<s57Sector_t> &sectorlegs) {
6424  float rangeScale = 0.0;
6425 
6426  if (sectorlegs.size() > 0) {
6427  std::vector<int> sectorangles;
6428  for (unsigned int i = 0; i < sectorlegs.size(); i++) {
6429  if (fabs(sectorlegs[i].sector1 - sectorlegs[i].sector2) < 0.3) continue;
6430 
6431  double endx, endy;
6432  ll_gc_ll(sectorlegs[i].pos.m_y, sectorlegs[i].pos.m_x,
6433  sectorlegs[i].sector1 + 180.0, sectorlegs[i].range, &endy,
6434  &endx);
6435 
6436  wxPoint end1 = viewport.GetPixFromLL(endy, endx);
6437 
6438  ll_gc_ll(sectorlegs[i].pos.m_y, sectorlegs[i].pos.m_x,
6439  sectorlegs[i].sector2 + 180.0, sectorlegs[i].range, &endy,
6440  &endx);
6441 
6442  wxPoint end2 = viewport.GetPixFromLL(endy, endx);
6443 
6444  wxPoint lightPos =
6445  viewport.GetPixFromLL(sectorlegs[i].pos.m_y, sectorlegs[i].pos.m_x);
6446 
6447 
6448  // Make sure arcs are well inside viewport.
6449  float rangePx = sqrtf(powf((float)(lightPos.x - end1.x), 2) +
6450  powf((float)(lightPos.y - end1.y), 2));
6451  rangePx /= 3.0;
6452  if (rangeScale == 0.0) {
6453  rangeScale = 1.0;
6454  if (rangePx > viewport.pix_height / 3) {
6455  rangeScale *= (viewport.pix_height / 3) / rangePx;
6456  }
6457  }
6458 
6459  rangePx = rangePx * rangeScale;
6460 
6461  float arcw = rangePx / 10;
6462  arcw = wxMin(20, arcw);
6463  arcw = wxMax(5, arcw);
6464 
6465  int legOpacity;
6466 
6467  float angle1, angle2;
6468  angle1 = -(sectorlegs[i].sector2 + 90.0) - viewport.rotation * 180.0 / PI;
6469  angle2 = -(sectorlegs[i].sector1 + 90.0) - viewport.rotation * 180.0 / PI;
6470  if (angle1 > angle2) {
6471  angle2 += 360.0;
6472  }
6473  int lpx = lightPos.x;
6474  int lpy = lightPos.y;
6475 
6476  if (sectorlegs[i].isleading && (angle2 - angle1 < 60)) {
6477  wxPoint yellowCone[3];
6478  yellowCone[0] = lightPos;
6479  yellowCone[1] = end1;
6480  yellowCone[2] = end2;
6481  wxPen *arcpen = wxThePenList->FindOrCreatePen(wxColor(0, 0, 0, 0), 1,
6482  wxPENSTYLE_SOLID);
6483  dc.SetPen(*arcpen);
6484  wxColor c = sectorlegs[i].color;
6485  c.Set(c.Red(), c.Green(), c.Blue(), 0.6 * c.Alpha());
6486  dc.SetBrush(wxBrush(c));
6487  dc.StrokePolygon(3, yellowCone, 0, 0);
6488  legOpacity = 50;
6489  } else {
6490  // Center point
6491  wxPoint r(lpx, lpy);
6492 
6493  // radius scaled to display
6494  float rad = rangePx;
6495 
6496  //float arcw = arc_width * canvas_pix_per_mm;
6497  // On larger screens, make the arc_width 1.0 mm
6498  //if ( m_display_size_mm > 200) //200 mm, about 8 inches
6499  //arcw = canvas_pix_per_mm;
6500 
6501 
6502  // Enable anti-aliased lines, at best quality
6503  glEnable(GL_BLEND);
6504 
6505  float coords[8];
6506  coords[0] = -rad;
6507  coords[1] = rad;
6508  coords[2] = rad;
6509  coords[3] = rad;
6510  coords[4] = -rad;
6511  coords[5] = -rad;
6512  coords[6] = rad;
6513  coords[7] = -rad;
6514 
6515  GLShaderProgram *shader = pring_shader_program[0/*GetCanvasIndex()*/];
6516  shader->Bind();
6517 
6518  // Get pointers to the attributes in the program.
6519  GLint mPosAttrib = glGetAttribLocation(shader->programId(), "aPos");
6520 
6521  // Disable VBO's (vertex buffer objects) for attributes.
6522  glBindBuffer(GL_ARRAY_BUFFER, 0);
6523  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
6524 
6525  glVertexAttribPointer(mPosAttrib, 2, GL_FLOAT, GL_FALSE, 0, coords);
6526  glEnableVertexAttribArray(mPosAttrib);
6527 
6528  // Circle radius
6529  GLint radiusloc =
6530  glGetUniformLocation(shader->programId(), "circle_radius");
6531  glUniform1f(radiusloc, rad);
6532 
6533  // Circle center point, physical
6534  GLint centerloc =
6535  glGetUniformLocation(shader->programId(), "circle_center");
6536  float ctrv[2];
6537  ctrv[0] = r.x;
6538  ctrv[1] = viewport.pix_height - r.y;
6539  glUniform2fv(centerloc, 1, ctrv);
6540 
6541  // Circle color
6542  wxColour colorb = sectorlegs[i].color;
6543  float colorv[4];
6544  colorv[0] = colorb.Red() / float(256);
6545  colorv[1] = colorb.Green() / float(256);
6546  colorv[2] = colorb.Blue() / float(256);
6547  colorv[3] = colorb.Alpha() / float(256);
6548 
6549  GLint colloc = glGetUniformLocation(shader->programId(), "circle_color");
6550  glUniform4fv(colloc, 1, colorv);
6551 
6552  // Border color
6553  float bcolorv[4];
6554  bcolorv[0] = 0;
6555  bcolorv[1] = 0;
6556  bcolorv[2] = 0;
6557  bcolorv[3] = 0;
6558 
6559  GLint bcolloc = glGetUniformLocation(shader->programId(), "border_color");
6560  glUniform4fv(bcolloc, 1, bcolorv);
6561 
6562  // Border Width
6563  GLint borderWidthloc =
6564  glGetUniformLocation(shader->programId(), "border_width");
6565  glUniform1f(borderWidthloc, 2);
6566 
6567  // Ring width
6568  GLint ringWidthloc =
6569  glGetUniformLocation(shader->programId(), "ring_width");
6570  glUniform1f(ringWidthloc, arcw);
6571 
6572  // Visible sectors, rotated to vp orientation
6573  float sr1 = sectorlegs[i].sector1 + (viewport.rotation * 180 / PI) + 180;
6574  if (sr1 > 360.) sr1 -= 360.;
6575  float sr2 = sectorlegs[i].sector2 + (viewport.rotation * 180 / PI) + 180;
6576  if (sr2 > 360.) sr2 -= 360.;
6577 
6578  float sb, se;
6579  if (sr2 > sr1) {
6580  sb = sr1;
6581  se = sr2;
6582  } else {
6583  sb = sr1;
6584  se = sr2 + 360;
6585  }
6586 
6587  // Shader can handle angles > 360.
6588  if ((sb < 0) || (se < 0)) {
6589  sb += 360.;
6590  se += 360.;
6591  }
6592 
6593  GLint sector1loc = glGetUniformLocation(shader->programId(), "sector_1");
6594  glUniform1f(sector1loc, (sb * PI / 180.));
6595  GLint sector2loc = glGetUniformLocation(shader->programId(), "sector_2");
6596  glUniform1f(sector2loc, (se * PI / 180.));
6597 
6598  // Rotate and translate
6599  mat4x4 I;
6600  mat4x4_identity(I);
6601  mat4x4_translate_in_place(I, r.x, r.y, 0);
6602 
6603  GLint matloc =
6604  glGetUniformLocation(shader->programId(), "TransformMatrix");
6605  glUniformMatrix4fv(matloc, 1, GL_FALSE, (const GLfloat *)I);
6606 
6607  // Perform the actual drawing.
6608  glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
6609 
6610  // Restore the per-object transform to Identity Matrix
6611  mat4x4 IM;
6612  mat4x4_identity(IM);
6613  GLint matlocf =
6614  glGetUniformLocation(shader->programId(), "TransformMatrix");
6615  glUniformMatrix4fv(matlocf, 1, GL_FALSE, (const GLfloat *)IM);
6616 
6617  glDisableVertexAttribArray(mPosAttrib);
6618  shader->UnBind();
6619 
6620  }
6621 
6622 #if 1
6623 
6624  wxPen *arcpen = wxThePenList->FindOrCreatePen(wxColor(0, 0, 0, 128), 1,
6625  wxPENSTYLE_SOLID);
6626  dc.SetPen(*arcpen);
6627 
6628  // Only draw each leg line once.
6629  bool haveAngle1 = false;
6630  bool haveAngle2 = false;
6631  int sec1 = (int)sectorlegs[i].sector1;
6632  int sec2 = (int)sectorlegs[i].sector2;
6633  if (sec1 > 360) sec1 -= 360;
6634  if (sec2 > 360) sec2 -= 360;
6635 
6636  if ((sec2 == 360) && (sec1 == 0)) // FS#1437
6637  continue;
6638 
6639  for (unsigned int j = 0; j < sectorangles.size(); j++) {
6640  if (sectorangles[j] == sec1) haveAngle1 = true;
6641  if (sectorangles[j] == sec2) haveAngle2 = true;
6642  }
6643 
6644  if (!haveAngle1) {
6645  dc.StrokeLine(lightPos, end1);
6646  sectorangles.push_back(sec1);
6647  }
6648 
6649  if (!haveAngle2) {
6650  dc.StrokeLine(lightPos, end2);
6651  sectorangles.push_back(sec2);
6652  }
6653 #endif
6654  }
6655  }
6656 }
6657 #endif
6658 
6659 bool s57_ProcessExtendedLightSectors(ChartCanvas *cc,
6660  ChartPlugInWrapper *target_plugin_chart,
6661  s57chart *Chs57,
6662  ListOfObjRazRules *rule_list,
6663  ListOfPI_S57Obj *pi_rule_list,
6664  std::vector<s57Sector_t> &sectorlegs) {
6665  bool newSectorsNeedDrawing = false;
6666 
6667  bool bhas_red_green = false;
6668  bool bleading_attribute = false;
6669 
6670  int opacity = 100;
6671  if (cc->GetColorScheme() == GLOBAL_COLOR_SCHEME_DUSK) opacity = 50;
6672  if (cc->GetColorScheme() == GLOBAL_COLOR_SCHEME_NIGHT) opacity = 20;
6673 
6674  int yOpacity = (float)opacity *
6675  1.3; // Matched perception of white/yellow with red/green
6676 
6677  if (target_plugin_chart || Chs57) {
6678  sectorlegs.clear();
6679 
6680  wxPoint2DDouble objPos;
6681 
6682  char *curr_att = NULL;
6683  int n_attr = 0;
6684  wxArrayOfS57attVal *attValArray = NULL;
6685 
6686  ListOfObjRazRules::Node *snode = NULL;
6687  ListOfPI_S57Obj::Node *pnode = NULL;
6688 
6689  if (Chs57 && rule_list)
6690  snode = rule_list->GetLast();
6691  else if (target_plugin_chart && pi_rule_list)
6692  pnode = pi_rule_list->GetLast();
6693 
6694  while (1) {
6695  wxPoint2DDouble lightPosD(0, 0);
6696  bool is_light = false;
6697  if (Chs57) {
6698  if (!snode) break;
6699 
6700  ObjRazRules *current = snode->GetData();
6701  S57Obj *light = current->obj;
6702  if (!strcmp(light->FeatureName, "LIGHTS")) {
6703  objPos = wxPoint2DDouble(light->m_lat, light->m_lon);
6704  curr_att = light->att_array;
6705  n_attr = light->n_attr;
6706  attValArray = light->attVal;
6707  is_light = true;
6708  }
6709  } else if (target_plugin_chart) {
6710  if (!pnode) break;
6711  PI_S57Obj *light = pnode->GetData();
6712  if (!strcmp(light->FeatureName, "LIGHTS")) {
6713  objPos = wxPoint2DDouble(light->m_lat, light->m_lon);
6714  curr_att = light->att_array;
6715  n_attr = light->n_attr;
6716  attValArray = light->attVal;
6717  is_light = true;
6718  }
6719  }
6720 
6721  // Ready to go
6722  int attrCounter;
6723  double sectr1 = -1;
6724  double sectr2 = -1;
6725  double valnmr = -1;
6726  wxString curAttrName;
6727  wxColor color;
6728 
6729  if (lightPosD.m_x == 0 && lightPosD.m_y == 0.0) lightPosD = objPos;
6730 
6731  if (is_light && (lightPosD == objPos)) {
6732  if (curr_att) {
6733  bool bviz = true;
6734 
6735  attrCounter = 0;
6736  int noAttr = 0;
6737  s57Sector_t sector;
6738 
6739  bleading_attribute = false;
6740 
6741  while (attrCounter < n_attr) {
6742  curAttrName = wxString(curr_att, wxConvUTF8, 6);
6743  noAttr++;
6744 
6745  S57attVal *pAttrVal = NULL;
6746  if (attValArray) {
6747  if (Chs57)
6748  pAttrVal = attValArray->Item(attrCounter);
6749  else if (target_plugin_chart)
6750  pAttrVal = attValArray->Item(attrCounter);
6751  }
6752 
6753  wxString value =
6754  s57chart::GetAttributeValueAsString(pAttrVal, curAttrName);
6755 
6756  if (curAttrName == _T("LITVIS")) {
6757  if (value.StartsWith(_T("obsc"))) bviz = false;
6758  }
6759  if (curAttrName == _T("SECTR1")) value.ToDouble(&sectr1);
6760  if (curAttrName == _T("SECTR2")) value.ToDouble(&sectr2);
6761  if (curAttrName == _T("VALNMR")) value.ToDouble(&valnmr);
6762  if (curAttrName == _T("COLOUR")) {
6763  if (value == _T("red(3)")) {
6764  color = wxColor(255, 0, 0, opacity);
6765  sector.iswhite = false;
6766  bhas_red_green = true;
6767  }
6768 
6769  if (value == _T("green(4)")) {
6770  color = wxColor(0, 255, 0, opacity);
6771  sector.iswhite = false;
6772  bhas_red_green = true;
6773  }
6774  }
6775 
6776  if (curAttrName == _T("EXCLIT")) {
6777  if (value.Find(_T("(3)"))) valnmr = 1.0; // Fog lights.
6778  }
6779 
6780  if (curAttrName == _T("CATLIT")) {
6781  if (value.Upper().StartsWith(_T("DIRECT")) ||
6782  value.Upper().StartsWith(_T("LEAD")))
6783  bleading_attribute = true;
6784  }
6785 
6786  attrCounter++;
6787  curr_att += 6;
6788  }
6789 
6790  if ((sectr1 >= 0) && (sectr2 >= 0)) {
6791  if (sectr1 > sectr2) { // normalize
6792  sectr2 += 360.0;
6793  }
6794 
6795  sector.pos.m_x = objPos.m_y; // lon
6796  sector.pos.m_y = objPos.m_x;
6797 
6798  sector.range =
6799  (valnmr > 0.0) ? valnmr : 2.5; // Short default range.
6800  sector.sector1 = sectr1;
6801  sector.sector2 = sectr2;
6802 
6803  if (!color.IsOk()) {
6804  color = wxColor(255, 255, 0, yOpacity);
6805  sector.iswhite = true;
6806  }
6807  sector.color = color;
6808  sector.isleading = false; // tentative judgment, check below
6809 
6810  if (bleading_attribute) sector.isleading = true;
6811 
6812  bool newsector = true;
6813  for (unsigned int i = 0; i < sectorlegs.size(); i++) {
6814  if (sectorlegs[i].pos == sector.pos &&
6815  sectorlegs[i].sector1 == sector.sector1 &&
6816  sectorlegs[i].sector2 == sector.sector2) {
6817  newsector = false;
6818  // In the case of duplicate sectors, choose the instance with
6819  // largest range. This applies to the case where day and night
6820  // VALNMR are different, and so makes the vector result
6821  // independent of the order of day/night light features.
6822  sectorlegs[i].range = wxMax(sectorlegs[i].range, sector.range);
6823  }
6824  }
6825 
6826  if (!bviz) newsector = false;
6827 
6828  if ((sector.sector2 == 360) && (sector.sector1 == 0)) // FS#1437
6829  newsector = false;
6830 
6831  if (newsector) {
6832  sectorlegs.push_back(sector);
6833  newSectorsNeedDrawing = true;
6834  }
6835  }
6836  }
6837  }
6838 
6839  if (Chs57)
6840  snode = snode->GetPrevious();
6841  else if (target_plugin_chart)
6842  pnode = pnode->GetPrevious();
6843 
6844  } // end of while
6845  }
6846 
6847  // Work with the sector legs vector to identify and mark "Leading Lights"
6848  // Sectors with CATLIT "Leading" or "Directional" attribute set have already
6849  // been marked
6850  for (unsigned int i = 0; i < sectorlegs.size(); i++) {
6851  if (((sectorlegs[i].sector2 - sectorlegs[i].sector1) < 15)) {
6852  if (sectorlegs[i].iswhite && bhas_red_green)
6853  sectorlegs[i].isleading = true;
6854  }
6855  }
6856 
6857  return newSectorsNeedDrawing;
6858 }
6859 
6860 bool s57_GetVisibleLightSectors(ChartCanvas *cc, double lat, double lon,
6861  ViewPort &viewport,
6862  std::vector<s57Sector_t> &sectorlegs) {
6863  if (!cc) return false;
6864 
6865  static float lastLat, lastLon;
6866 
6867  if (!ps52plib) return false;
6868 
6869  ChartPlugInWrapper *target_plugin_chart = NULL;
6870  s57chart *Chs57 = NULL;
6871 
6872  // Find the chart that is currently shown at the given lat/lon
6873  wxPoint calcPoint = viewport.GetPixFromLL(lat, lon);
6874  ChartBase *target_chart;
6875  if (cc->m_singleChart && (cc->m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
6876  target_chart = cc->m_singleChart;
6877  else if (viewport.b_quilt)
6878  target_chart = cc->m_pQuilt->GetChartAtPix(viewport, calcPoint);
6879  else
6880  target_chart = NULL;
6881 
6882  if (target_chart) {
6883  if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
6884  (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
6885  target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
6886  else
6887  Chs57 = dynamic_cast<s57chart *>(target_chart);
6888  }
6889 
6890  bool newSectorsNeedDrawing = false;
6891 
6892  if (target_plugin_chart || Chs57) {
6893  ListOfObjRazRules *rule_list = NULL;
6894  ListOfPI_S57Obj *pi_rule_list = NULL;
6895 
6896  // Go get the array of all objects at the cursor lat/lon
6897  float selectRadius = 16 / (viewport.view_scale_ppm * 1852 * 60);
6898 
6899  if (Chs57)
6900  rule_list =
6901  Chs57->GetLightsObjRuleListVisibleAtLatLon(lat, lon, &viewport);
6902  else if (target_plugin_chart)
6903  pi_rule_list = g_pi_manager->GetLightsObjRuleListVisibleAtLatLon(
6904  target_plugin_chart, lat, lon, viewport);
6905 
6906  newSectorsNeedDrawing = s57_ProcessExtendedLightSectors(
6907  cc, target_plugin_chart, Chs57, rule_list, pi_rule_list, sectorlegs);
6908 
6909  if (rule_list) {
6910  rule_list->Clear();
6911  delete rule_list;
6912  }
6913 
6914  if (pi_rule_list) {
6915  pi_rule_list->Clear();
6916  delete pi_rule_list;
6917  }
6918  }
6919 
6920  return newSectorsNeedDrawing;
6921 }
6922 
6923 bool s57_CheckExtendedLightSectors(ChartCanvas *cc, int mx, int my,
6924  ViewPort &viewport,
6925  std::vector<s57Sector_t> &sectorlegs) {
6926  if (!cc) return false;
6927 
6928  double cursor_lat, cursor_lon;
6929  static float lastLat, lastLon;
6930 
6931  if (!ps52plib || !ps52plib->m_bExtendLightSectors) return false;
6932 
6933  ChartPlugInWrapper *target_plugin_chart = NULL;
6934  s57chart *Chs57 = NULL;
6935 
6936  ChartBase *target_chart = cc->GetChartAtCursor();
6937  if (target_chart) {
6938  if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
6939  (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
6940  target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
6941  else
6942  Chs57 = dynamic_cast<s57chart *>(target_chart);
6943  }
6944 
6945  cc->GetCanvasPixPoint(mx, my, cursor_lat, cursor_lon);
6946 
6947  if (lastLat == cursor_lat && lastLon == cursor_lon) return false;
6948 
6949  lastLat = cursor_lat;
6950  lastLon = cursor_lon;
6951  bool newSectorsNeedDrawing = false;
6952 
6953  if (target_plugin_chart || Chs57) {
6954  ListOfObjRazRules *rule_list = NULL;
6955  ListOfPI_S57Obj *pi_rule_list = NULL;
6956 
6957  // Go get the array of all objects at the cursor lat/lon
6958  float selectRadius = 16 / (viewport.view_scale_ppm * 1852 * 60);
6959 
6960  if (Chs57)
6961  rule_list = Chs57->GetObjRuleListAtLatLon(
6962  cursor_lat, cursor_lon, selectRadius, &viewport, MASK_POINT);
6963  else if (target_plugin_chart)
6964  pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
6965  target_plugin_chart, cursor_lat, cursor_lon, selectRadius, viewport);
6966 
6967  newSectorsNeedDrawing = s57_ProcessExtendedLightSectors(
6968  cc, target_plugin_chart, Chs57, rule_list, pi_rule_list, sectorlegs);
6969 
6970  if (rule_list) {
6971  rule_list->Clear();
6972  delete rule_list;
6973  }
6974 
6975  if (pi_rule_list) {
6976  pi_rule_list->Clear();
6977  delete pi_rule_list;
6978  }
6979  }
6980 
6981  return newSectorsNeedDrawing;
6982 }
Definition: Osenc.h:401
Definition: ocpndc.h:58
General purpose GUI support.
Definition: Quilt.cpp:867