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