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