1 /******************************************************************************
2  *
3  * Project:  S-57 Translator
4  * Purpose:  Implements S57ClassRegistrar class for keeping track of
5  *           information on S57 object classes.
6  * Author:   Frank Warmerdam, warmerdam@pobox.com
7  *
8  ******************************************************************************
9  * Copyright (c) 1999, Frank Warmerdam
10  * Copyright (c) 2011-2013, Even Rouault <even dot rouault at spatialys.com>
11  *
12  * Permission is hereby granted, free of charge, to any person obtaining a
13  * copy of this software and associated documentation files (the "Software"),
14  * to deal in the Software without restriction, including without limitation
15  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16  * and/or sell copies of the Software, and to permit persons to whom the
17  * Software is furnished to do so, subject to the following conditions:
18  *
19  * The above copyright notice and this permission notice shall be included
20  * in all copies or substantial portions of the Software.
21  *
22  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
23  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
25  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28  * DEALINGS IN THE SOFTWARE.
29  ****************************************************************************/
30 
31 #include "cpl_conv.h"
32 #include "cpl_string.h"
33 #include "s57.h"
34 
35 CPL_CVSID("$Id: s57classregistrar.cpp b1c9c12ad373e40b955162b45d704070d4ebf7b0 2019-06-19 16:50:15 +0200 Even Rouault $")
36 
37 #ifdef S57_BUILTIN_CLASSES
38 #include "s57tables.h"
39 #endif
40 
41 /************************************************************************/
42 /*                         S57ClassRegistrar()                          */
43 /************************************************************************/
44 
S57ClassRegistrar()45 S57ClassRegistrar::S57ClassRegistrar() :
46     nClasses(0),
47     nAttrCount(0),
48     papszNextLine(nullptr)
49 {}
50 
51 /************************************************************************/
52 /*                         ~S57ClassRegistrar()                         */
53 /************************************************************************/
54 
~S57ClassRegistrar()55 S57ClassRegistrar::~S57ClassRegistrar()
56 
57 {
58     nClasses = 0;
59     for( size_t i = 0; i < aoAttrInfos.size(); i++ )
60         delete aoAttrInfos[i];
61     aoAttrInfos.resize(0);
62     nAttrCount = 0;
63 }
64 
65 /************************************************************************/
66 /*                        S57ClassContentExplorer()                     */
67 /************************************************************************/
68 
S57ClassContentExplorer(S57ClassRegistrar * poRegistrarIn)69 S57ClassContentExplorer::S57ClassContentExplorer(
70     S57ClassRegistrar* poRegistrarIn ) :
71     poRegistrar(poRegistrarIn),
72     papapszClassesFields(nullptr),
73     iCurrentClass(-1),
74     papszCurrentFields(nullptr),
75     papszTempResult(nullptr)
76 {}
77 
78 /************************************************************************/
79 /*                        ~S57ClassContentExplorer()                    */
80 /************************************************************************/
81 
~S57ClassContentExplorer()82 S57ClassContentExplorer::~S57ClassContentExplorer()
83 {
84     CSLDestroy( papszTempResult );
85 
86     if( papapszClassesFields != nullptr )
87     {
88         for( int i = 0; i < poRegistrar->nClasses; i++ )
89             CSLDestroy( papapszClassesFields[i] );
90         CPLFree( papapszClassesFields );
91     }
92 }
93 
94 /************************************************************************/
95 /*                              FindFile()                              */
96 /************************************************************************/
97 
FindFile(const char * pszTarget,const char * pszDirectory,bool bReportErr,VSILFILE ** pfp)98 bool S57ClassRegistrar::FindFile( const char *pszTarget,
99                                   const char *pszDirectory,
100                                   bool bReportErr,
101                                   VSILFILE **pfp )
102 
103 {
104     const char *pszFilename = nullptr;
105 
106     if( pszDirectory == nullptr )
107     {
108         pszFilename = CPLFindFile( "s57", pszTarget );
109         if( pszFilename == nullptr )
110             pszFilename = pszTarget;
111     }
112     else
113     {
114         pszFilename = CPLFormFilename( pszDirectory, pszTarget, nullptr );
115     }
116 
117     *pfp = VSIFOpenL( pszFilename, "rb" );
118 
119 #ifdef S57_BUILTIN_CLASSES
120     if( *pfp == NULL )
121     {
122         if( EQUAL(pszTarget, "s57objectclasses.csv") )
123             papszNextLine = gpapszS57Classes;
124         else
125             papszNextLine = gpapszS57attributes;
126     }
127 #else
128     if( *pfp == nullptr )
129     {
130         if( bReportErr )
131             CPLError( CE_Failure, CPLE_OpenFailed,
132                       "Failed to open %s.\n",
133                       pszFilename );
134         return FALSE;
135     }
136 #endif
137 
138     return TRUE;
139 }
140 
141 /************************************************************************/
142 /*                              ReadLine()                              */
143 /*                                                                      */
144 /*      Read a line from the provided file, or from the "built-in"      */
145 /*      configuration file line list if the file is NULL.               */
146 /************************************************************************/
147 
ReadLine(VSILFILE * fp)148 const char *S57ClassRegistrar::ReadLine( VSILFILE * fp )
149 
150 {
151     if( fp != nullptr )
152         return CPLReadLineL( fp );
153 
154     if( papszNextLine == nullptr )
155         return nullptr;
156 
157     if( *papszNextLine == nullptr )
158     {
159         papszNextLine = nullptr;
160         return nullptr;
161     }
162 
163     return *(papszNextLine++);
164 }
165 
166 /************************************************************************/
167 /*                              LoadInfo()                              */
168 /************************************************************************/
169 
LoadInfo(const char * pszDirectory,const char * pszProfile,bool bReportErr)170 bool S57ClassRegistrar::LoadInfo( const char * pszDirectory,
171                                   const char * pszProfile,
172                                   bool bReportErr )
173 
174 {
175     if( pszDirectory == nullptr )
176         pszDirectory = CPLGetConfigOption("S57_CSV",nullptr);
177 
178 /* ==================================================================== */
179 /*      Read the s57objectclasses file.                                 */
180 /* ==================================================================== */
181     if( pszProfile == nullptr )
182         pszProfile = CPLGetConfigOption( "S57_PROFILE", "" );
183 
184     char szTargetFile[1024];  // TODO: Get this off of the stack.
185     if( EQUAL(pszProfile, "Additional_Military_Layers") )
186     {
187         // Has been suppressed in GDAL data/
188        snprintf( szTargetFile, sizeof(szTargetFile), "s57objectclasses_%s.csv", "aml" );
189     }
190     else if ( EQUAL(pszProfile, "Inland_Waterways") )
191     {
192         // Has been suppressed in GDAL data/
193        snprintf( szTargetFile, sizeof(szTargetFile), "s57objectclasses_%s.csv", "iw" );
194     }
195     else if( strlen(pszProfile) > 0 )
196     {
197        snprintf( szTargetFile, sizeof(szTargetFile), "s57objectclasses_%s.csv", pszProfile );
198     }
199     else
200     {
201        strcpy( szTargetFile, "s57objectclasses.csv" );
202     }
203 
204     VSILFILE *fp = nullptr;
205     if( !FindFile( szTargetFile, pszDirectory, bReportErr, &fp ) )
206     {
207         if( EQUAL(pszProfile, "Additional_Military_Layers") ||
208             EQUAL(pszProfile, "Inland_Waterways") )
209         {
210             strcpy( szTargetFile, "s57objectclasses.csv" );
211             if( !FindFile( szTargetFile, pszDirectory, bReportErr, &fp ) )
212                 return false;
213         }
214         return false;
215     }
216 
217 /* -------------------------------------------------------------------- */
218 /*      Skip the line defining the column titles.                       */
219 /* -------------------------------------------------------------------- */
220     const char * pszLine = ReadLine( fp );
221 
222     if( !EQUAL(pszLine,
223                "\"Code\",\"ObjectClass\",\"Acronym\",\"Attribute_A\","
224                "\"Attribute_B\",\"Attribute_C\",\"Class\",\"Primitives\"" ) )
225     {
226         CPLError( CE_Failure, CPLE_AppDefined,
227                   "s57objectclasses columns don't match expected format!\n" );
228         if( fp != nullptr )
229             VSIFCloseL( fp );
230         return false;
231     }
232 
233 /* -------------------------------------------------------------------- */
234 /*      Read and form string list.                                      */
235 /* -------------------------------------------------------------------- */
236     apszClassesInfo.Clear();
237     while( (pszLine = ReadLine(fp)) != nullptr )
238     {
239         if( strstr(pszLine, "###") != nullptr )
240             continue;
241         apszClassesInfo.AddString(pszLine);
242     }
243 
244 /* -------------------------------------------------------------------- */
245 /*      Cleanup, and establish state.                                   */
246 /* -------------------------------------------------------------------- */
247     if( fp != nullptr )
248         VSIFCloseL( fp );
249 
250     nClasses = apszClassesInfo.size();
251     if( nClasses == 0 )
252         return false;
253 
254 /* ==================================================================== */
255 /*      Read the attributes list.                                       */
256 /* ==================================================================== */
257 
258     if( EQUAL(pszProfile, "Additional_Military_Layers") )
259     {
260         // Has been suppressed in GDAL data/
261       snprintf( szTargetFile, sizeof(szTargetFile), "s57attributes_%s.csv", "aml" );
262     }
263     else if ( EQUAL(pszProfile, "Inland_Waterways") )
264     {
265         // Has been suppressed in GDAL data/
266        snprintf( szTargetFile, sizeof(szTargetFile),"s57attributes_%s.csv", "iw" );
267     }
268     else if( strlen(pszProfile) > 0 )
269     {
270        snprintf( szTargetFile, sizeof(szTargetFile), "s57attributes_%s.csv", pszProfile );
271     }
272     else
273     {
274        strcpy( szTargetFile, "s57attributes.csv" );
275     }
276 
277     if( !FindFile( szTargetFile, pszDirectory, bReportErr, &fp ) )
278     {
279         if( EQUAL(pszProfile, "Additional_Military_Layers") ||
280             EQUAL(pszProfile, "Inland_Waterways") )
281         {
282             strcpy( szTargetFile, "s57attributes.csv" );
283             if( !FindFile( szTargetFile, pszDirectory, bReportErr, &fp ) )
284                 return false;
285         }
286         return false;
287     }
288 
289 /* -------------------------------------------------------------------- */
290 /*      Skip the line defining the column titles.                       */
291 /* -------------------------------------------------------------------- */
292     pszLine = ReadLine( fp );
293 
294     if( !EQUAL(pszLine,
295           "\"Code\",\"Attribute\",\"Acronym\",\"Attributetype\",\"Class\"") )
296     {
297         CPLError( CE_Failure, CPLE_AppDefined,
298                   "s57attributes columns don't match expected format!\n" );
299         if( fp != nullptr )
300             VSIFCloseL( fp );
301         return false;
302     }
303 
304 /* -------------------------------------------------------------------- */
305 /*      Read and form string list.                                      */
306 /* -------------------------------------------------------------------- */
307     while( (pszLine = ReadLine(fp)) != nullptr )
308     {
309         if( strstr(pszLine, "###") != nullptr )
310             continue;
311 
312         char    **papszTokens = CSLTokenizeStringComplex( pszLine, ",",
313                                                           TRUE, TRUE );
314 
315         if( CSLCount(papszTokens) < 5 )
316         {
317             CSLDestroy(papszTokens);
318             continue;
319         }
320 
321         int iAttr = atoi(papszTokens[0]);
322         if( iAttr >= (int) aoAttrInfos.size() )
323             aoAttrInfos.resize(iAttr+1);
324 
325         if( iAttr < 0 || aoAttrInfos[iAttr] != nullptr )
326         {
327             CPLDebug( "S57",
328                       "Duplicate/corrupt definition for attribute %d:%s",
329                       iAttr, papszTokens[2] );
330             CSLDestroy( papszTokens );
331             continue;
332         }
333 
334         aoAttrInfos[iAttr] = new S57AttrInfo();
335         aoAttrInfos[iAttr]->osName = papszTokens[1];
336         aoAttrInfos[iAttr]->osAcronym = papszTokens[2];
337         aoAttrInfos[iAttr]->chType = papszTokens[3][0];
338         aoAttrInfos[iAttr]->chClass = papszTokens[4][0];
339         anAttrIndex.push_back(iAttr);
340         CSLDestroy( papszTokens );
341     }
342 
343     if( fp != nullptr )
344         VSIFCloseL( fp );
345 
346     nAttrCount = static_cast<int>(anAttrIndex.size());
347 
348 /* -------------------------------------------------------------------- */
349 /*      Sort index by acronym.                                          */
350 /* -------------------------------------------------------------------- */
351     bool bModified = false;
352     do
353     {
354         bModified = false;
355         for( int iAttr = 0; iAttr < nAttrCount-1; iAttr++ )
356         {
357             if( strcmp(aoAttrInfos[anAttrIndex[iAttr]]->osAcronym,
358                        aoAttrInfos[anAttrIndex[iAttr+1]]->osAcronym) > 0 )
359             {
360                 int nTemp = anAttrIndex[iAttr];
361                 anAttrIndex[iAttr] = anAttrIndex[iAttr+1];
362                 anAttrIndex[iAttr+1] = nTemp;
363                 bModified = true;
364             }
365         }
366     } while( bModified );
367 
368     return true;
369 }
370 
371 /************************************************************************/
372 /*                         SelectClassByIndex()                         */
373 /************************************************************************/
374 
SelectClassByIndex(int nNewIndex)375 bool S57ClassContentExplorer::SelectClassByIndex( int nNewIndex )
376 
377 {
378     if( nNewIndex < 0 || nNewIndex >= poRegistrar->nClasses )
379         return false;
380 
381 /* -------------------------------------------------------------------- */
382 /*      Do we have our cache of class information field lists?          */
383 /* -------------------------------------------------------------------- */
384     if( papapszClassesFields == nullptr )
385     {
386         papapszClassesFields = (char ***) CPLCalloc(sizeof(void*),poRegistrar->nClasses);
387     }
388 
389 /* -------------------------------------------------------------------- */
390 /*      Has this info been parsed yet?                                  */
391 /* -------------------------------------------------------------------- */
392     if( papapszClassesFields[nNewIndex] == nullptr )
393         papapszClassesFields[nNewIndex] =
394             CSLTokenizeStringComplex( poRegistrar->apszClassesInfo[nNewIndex],
395                                       ",", TRUE, TRUE );
396 
397     papszCurrentFields = papapszClassesFields[nNewIndex];
398 
399     iCurrentClass = nNewIndex;
400 
401     return true;
402 }
403 
404 /************************************************************************/
405 /*                             SelectClass()                            */
406 /************************************************************************/
407 
SelectClass(int nOBJL)408 bool S57ClassContentExplorer::SelectClass( int nOBJL )
409 
410 {
411     for( int i = 0; i < poRegistrar->nClasses; i++ )
412     {
413         if( atoi(poRegistrar->apszClassesInfo[i]) == nOBJL )
414             return SelectClassByIndex( i );
415     }
416 
417     return FALSE;
418 }
419 
420 /************************************************************************/
421 /*                            SelectClass()                             */
422 /************************************************************************/
423 
SelectClass(const char * pszAcronym)424 bool S57ClassContentExplorer::SelectClass( const char *pszAcronym )
425 
426 {
427     for( int i = 0; i < poRegistrar->nClasses; i++ )
428     {
429         if( !SelectClassByIndex( i ) )
430             continue;
431 
432         const char* pszClassAcronym = GetAcronym();
433         if( pszClassAcronym != nullptr && strcmp(pszClassAcronym,pszAcronym) == 0 )
434             return true;
435     }
436 
437     return false;
438 }
439 
440 /************************************************************************/
441 /*                              GetOBJL()                               */
442 /************************************************************************/
443 
GetOBJL()444 int S57ClassContentExplorer::GetOBJL()
445 
446 {
447     if( iCurrentClass >= 0 )
448         return atoi(poRegistrar->apszClassesInfo[iCurrentClass]);
449 
450     return -1;
451 }
452 
453 /************************************************************************/
454 /*                           GetDescription()                           */
455 /************************************************************************/
456 
GetDescription() const457 const char * S57ClassContentExplorer::GetDescription() const
458 
459 {
460     if( iCurrentClass >= 0 && papszCurrentFields[0] != nullptr )
461         return papszCurrentFields[1];
462 
463     return nullptr;
464 }
465 
466 /************************************************************************/
467 /*                             GetAcronym()                             */
468 /************************************************************************/
469 
GetAcronym() const470 const char * S57ClassContentExplorer::GetAcronym() const
471 
472 {
473     if( iCurrentClass >= 0
474         && papszCurrentFields[0] != nullptr
475         && papszCurrentFields[1] != nullptr )
476         return papszCurrentFields[2];
477 
478     return nullptr;
479 }
480 
481 /************************************************************************/
482 /*                          GetAttributeList()                          */
483 /*                                                                      */
484 /*      The passed string can be "a", "b", "c" or NULL for all.  The    */
485 /*      returned list remained owned by this object, not the caller.    */
486 /************************************************************************/
487 
GetAttributeList(const char * pszType)488 char **S57ClassContentExplorer::GetAttributeList( const char * pszType )
489 
490 {
491     if( iCurrentClass < 0 )
492         return nullptr;
493 
494     CSLDestroy( papszTempResult );
495     papszTempResult = nullptr;
496 
497     for( int iColumn = 3; iColumn < 6; iColumn++ )
498     {
499         if( pszType != nullptr && iColumn == 3 && !EQUAL(pszType,"a") )
500             continue;
501 
502         if( pszType != nullptr && iColumn == 4 && !EQUAL(pszType,"b") )
503             continue;
504 
505         if( pszType != nullptr && iColumn == 5 && !EQUAL(pszType,"c") )
506             continue;
507 
508         char **papszTokens =
509             CSLTokenizeStringComplex( papszCurrentFields[iColumn], ";",
510                                       TRUE, FALSE );
511 
512         papszTempResult = CSLInsertStrings( papszTempResult, -1,
513                                             papszTokens );
514 
515         CSLDestroy( papszTokens );
516     }
517 
518     return papszTempResult;
519 }
520 
521 /************************************************************************/
522 /*                            GetClassCode()                            */
523 /************************************************************************/
524 
GetClassCode() const525 char S57ClassContentExplorer::GetClassCode() const
526 
527 {
528     if( iCurrentClass >= 0
529         && papszCurrentFields[0] != nullptr
530         && papszCurrentFields[1] != nullptr
531         && papszCurrentFields[2] != nullptr
532         && papszCurrentFields[3] != nullptr
533         && papszCurrentFields[4] != nullptr
534         && papszCurrentFields[5] != nullptr
535         && papszCurrentFields[6] != nullptr )
536         return papszCurrentFields[6][0];
537 
538     return '\0';
539 }
540 
541 /************************************************************************/
542 /*                           GetPrimitives()                            */
543 /************************************************************************/
544 
GetPrimitives()545 char **S57ClassContentExplorer::GetPrimitives()
546 
547 {
548     if( iCurrentClass >= 0
549         && CSLCount(papszCurrentFields) > 7 )
550     {
551         CSLDestroy( papszTempResult );
552         papszTempResult =
553             CSLTokenizeStringComplex( papszCurrentFields[7], ";",
554                                       TRUE, FALSE );
555         return papszTempResult;
556     }
557 
558     return nullptr;
559 }
560 
561 /************************************************************************/
562 /*                            GetAttrInfo()                             */
563 /************************************************************************/
564 
GetAttrInfo(int iAttr)565 const S57AttrInfo *S57ClassRegistrar::GetAttrInfo(int iAttr)
566 {
567     if( iAttr < 0 || iAttr >= (int) aoAttrInfos.size() )
568         return nullptr;
569 
570     return aoAttrInfos[iAttr];
571 }
572 
573 /************************************************************************/
574 /*                         FindAttrByAcronym()                          */
575 /************************************************************************/
576 
FindAttrByAcronym(const char * pszName)577 int    S57ClassRegistrar::FindAttrByAcronym( const char * pszName )
578 
579 {
580     int iStart = 0;
581     int iEnd = nAttrCount-1;
582 
583     while( iStart <= iEnd )
584     {
585         const int iCandidate = (iStart + iEnd)/2;
586         int nCompareValue =
587             strcmp(pszName, aoAttrInfos[anAttrIndex[iCandidate]]->osAcronym);
588 
589         if( nCompareValue < 0 )
590         {
591             iEnd = iCandidate-1;
592         }
593         else if( nCompareValue > 0 )
594         {
595             iStart = iCandidate+1;
596         }
597         else
598             return anAttrIndex[iCandidate];
599     }
600 
601     return -1;
602 }
603