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