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