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