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