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