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