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