LCOV - code coverage report
Current view: directory - ogr/ogrsf_frmts/ntf - ogrntfdatasource.cpp (source / functions) Found Hit Coverage
Test: gdal_filtered.info Lines: 186 117 62.9 %
Date: 2012-12-26 Functions: 17 9 52.9 %

       1                 : /******************************************************************************
       2                 :  * $Id: ogrntfdatasource.cpp 10645 2007-01-18 02:22:39Z warmerdam $
       3                 :  *
       4                 :  * Project:  UK NTF Reader
       5                 :  * Purpose:  Implements OGRNTFDataSource class
       6                 :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       7                 :  *
       8                 :  ******************************************************************************
       9                 :  * Copyright (c) 1999, Frank Warmerdam
      10                 :  *
      11                 :  * Permission is hereby granted, free of charge, to any person obtaining a
      12                 :  * copy of this software and associated documentation files (the "Software"),
      13                 :  * to deal in the Software without restriction, including without limitation
      14                 :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      15                 :  * and/or sell copies of the Software, and to permit persons to whom the
      16                 :  * Software is furnished to do so, subject to the following conditions:
      17                 :  *
      18                 :  * The above copyright notice and this permission notice shall be included
      19                 :  * in all copies or substantial portions of the Software.
      20                 :  *
      21                 :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      22                 :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      23                 :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      24                 :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      25                 :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      26                 :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      27                 :  * DEALINGS IN THE SOFTWARE.
      28                 :  ****************************************************************************/
      29                 : 
      30                 : #include "ntf.h"
      31                 : #include "cpl_conv.h"
      32                 : #include "cpl_string.h"
      33                 : 
      34                 : CPL_CVSID("$Id: ogrntfdatasource.cpp 10645 2007-01-18 02:22:39Z warmerdam $");
      35                 : 
      36                 : /************************************************************************/
      37                 : /*                          OGRNTFDataSource()                          */
      38                 : /************************************************************************/
      39                 : 
      40             939 : OGRNTFDataSource::OGRNTFDataSource()
      41                 : 
      42                 : {
      43             939 :     nLayers = 0;
      44             939 :     papoLayers = NULL;
      45                 : 
      46             939 :     nNTFFileCount = 0;
      47             939 :     papoNTFFileReader = NULL;
      48                 : 
      49             939 :     pszName = NULL;
      50                 : 
      51             939 :     iCurrentReader = -1;
      52             939 :     iCurrentFC = 0;
      53                 : 
      54             939 :     nFCCount = 0;
      55             939 :     papszFCNum = NULL;
      56             939 :     papszFCName = NULL;
      57                 : 
      58             939 :     poFCLayer = NULL;
      59                 : 
      60             939 :     papszOptions = NULL;
      61                 : 
      62            1878 :     poSpatialRef = new OGRSpatialReference( "PROJCS[\"OSGB 1936 / British National Grid\",GEOGCS[\"OSGB 1936\",DATUM[\"OSGB_1936\",SPHEROID[\"Airy 1830\",6377563.396,299.3249646,AUTHORITY[\"EPSG\",\"7001\"]],AUTHORITY[\"EPSG\",\"6277\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433],AUTHORITY[\"EPSG\",\"4277\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",49],PARAMETER[\"central_meridian\",-2],PARAMETER[\"scale_factor\",0.999601272],PARAMETER[\"false_easting\",400000],PARAMETER[\"false_northing\",-100000],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AUTHORITY[\"EPSG\",\"27700\"]]" );
      63                 : 
      64                 : 
      65                 : /* -------------------------------------------------------------------- */
      66                 : /*      Allow initialization of options from the environment.           */
      67                 : /* -------------------------------------------------------------------- */
      68             939 :     if( getenv("OGR_NTF_OPTIONS") != NULL )
      69                 :     {
      70                 :         papszOptions = 
      71                 :             CSLTokenizeStringComplex( getenv("OGR_NTF_OPTIONS"), ",",
      72               0 :                                       FALSE, FALSE );
      73                 :     }
      74             939 : }
      75                 : 
      76                 : /************************************************************************/
      77                 : /*                         ~OGRNTFDataSource()                          */
      78                 : /************************************************************************/
      79                 : 
      80             939 : OGRNTFDataSource::~OGRNTFDataSource()
      81                 : 
      82                 : {
      83                 :     int         i;
      84                 : 
      85             941 :     for( i = 0; i < nNTFFileCount; i++ )
      86               2 :         delete papoNTFFileReader[i];
      87                 : 
      88             939 :     CPLFree( papoNTFFileReader );
      89                 : 
      90             947 :     for( i = 0; i < nLayers; i++ )
      91               8 :         delete papoLayers[i];
      92                 : 
      93             939 :     if( poFCLayer != NULL )
      94               2 :         delete poFCLayer;
      95                 :     
      96             939 :     CPLFree( papoLayers );
      97                 : 
      98             939 :     CPLFree( pszName );
      99                 : 
     100             939 :     CSLDestroy( papszOptions );
     101                 : 
     102             939 :     CSLDestroy( papszFCNum );
     103             939 :     CSLDestroy( papszFCName );
     104                 : 
     105             939 :     if( poSpatialRef )
     106             939 :         poSpatialRef->Release();
     107             939 : }
     108                 : 
     109                 : /************************************************************************/
     110                 : /*                           TestCapability()                           */
     111                 : /************************************************************************/
     112                 : 
     113               0 : int OGRNTFDataSource::TestCapability( const char * )
     114                 : 
     115                 : {
     116               0 :     return FALSE;
     117                 : }
     118                 : 
     119                 : /************************************************************************/
     120                 : /*                           GetNamedLayer()                            */
     121                 : /************************************************************************/
     122                 : 
     123               8 : OGRNTFLayer * OGRNTFDataSource::GetNamedLayer( const char * pszName )
     124                 : 
     125                 : {
     126              20 :     for( int i = 0; i < nLayers; i++ )
     127                 :     {
     128              12 :         if( EQUAL(papoLayers[i]->GetLayerDefn()->GetName(),pszName) )
     129               0 :             return (OGRNTFLayer *) papoLayers[i];
     130                 :     }
     131                 : 
     132               8 :     return NULL;
     133                 : }
     134                 : 
     135                 : /************************************************************************/
     136                 : /*                              AddLayer()                              */
     137                 : /************************************************************************/
     138                 : 
     139               8 : void OGRNTFDataSource::AddLayer( OGRLayer * poNewLayer )
     140                 : 
     141                 : {
     142                 :     papoLayers = (OGRLayer **)
     143               8 :         CPLRealloc( papoLayers, sizeof(void*) * ++nLayers );
     144                 :     
     145               8 :     papoLayers[nLayers-1] = poNewLayer;
     146               8 : }
     147                 : 
     148                 : /************************************************************************/
     149                 : /*                              GetLayer()                              */
     150                 : /************************************************************************/
     151                 : 
     152              34 : OGRLayer *OGRNTFDataSource::GetLayer( int iLayer )
     153                 : 
     154                 : {
     155              34 :     if( iLayer < 0 || iLayer > nLayers )
     156               0 :         return NULL;
     157              34 :     else if( iLayer == nLayers )
     158               2 :         return poFCLayer;
     159                 :     else
     160              32 :         return papoLayers[iLayer];
     161                 : }
     162                 : 
     163                 : /************************************************************************/
     164                 : /*                           GetLayerCount()                            */
     165                 : /************************************************************************/
     166                 : 
     167              36 : int OGRNTFDataSource::GetLayerCount()
     168                 : 
     169                 : {
     170              36 :     if( poFCLayer == NULL )
     171               0 :         return nLayers;
     172                 :     else
     173              36 :         return nLayers + 1;
     174                 : }
     175                 : 
     176                 : 
     177                 : /************************************************************************/
     178                 : /*                                Open()                                */
     179                 : /************************************************************************/
     180                 : 
     181             939 : int OGRNTFDataSource::Open( const char * pszFilename, int bTestOpen,
     182                 :                             char ** papszLimitedFileList )
     183                 : 
     184                 : {
     185                 :     VSIStatBuf      stat;
     186             939 :     char            **papszFileList = NULL;
     187                 : 
     188             939 :     pszName = CPLStrdup( pszFilename );
     189                 : 
     190                 : /* -------------------------------------------------------------------- */
     191                 : /*      Is the given path a directory or a regular file?                */
     192                 : /* -------------------------------------------------------------------- */
     193             939 :     if( CPLStat( pszFilename, &stat ) != 0 
     194                 :         || (!VSI_ISDIR(stat.st_mode) && !VSI_ISREG(stat.st_mode)) )
     195                 :     {
     196             354 :         if( !bTestOpen )
     197                 :             CPLError( CE_Failure, CPLE_AppDefined,
     198                 :                    "%s is neither a file or directory, NTF access failed.\n",
     199               0 :                       pszFilename );
     200                 : 
     201             354 :         return FALSE;
     202                 :     }
     203                 :     
     204                 : /* -------------------------------------------------------------------- */
     205                 : /*      Build a list of filenames we figure are NTF files.              */
     206                 : /* -------------------------------------------------------------------- */
     207             585 :     if( VSI_ISREG(stat.st_mode) )
     208                 :     {
     209             552 :         papszFileList = CSLAddString( NULL, pszFilename );
     210                 :     }
     211                 :     else
     212                 :     {
     213              33 :         char      **candidateFileList = CPLReadDir( pszFilename );
     214                 :         int         i;
     215                 : 
     216            3200 :         for( i = 0; 
     217            1600 :              candidateFileList != NULL && candidateFileList[i] != NULL; 
     218                 :              i++ ) 
     219                 :         {
     220            1567 :             if( papszLimitedFileList != NULL
     221                 :                 && CSLFindString(papszLimitedFileList,
     222               0 :                                  candidateFileList[i]) == -1 )
     223                 :             {
     224               0 :                 continue;
     225                 :             }
     226                 :             
     227            4535 :             if( strlen(candidateFileList[i]) > 4
     228            2968 :               && EQUALN(candidateFileList[i] + strlen(candidateFileList[i])-4,
     229                 :                        ".ntf",4) )
     230                 :             {
     231                 :                 char       fullFilename[2048];
     232                 : 
     233                 :                 sprintf( fullFilename, "%s%c%s", 
     234                 :                          pszFilename,
     235                 : #ifdef WIN32
     236                 :                          '\\',
     237                 : #else
     238                 :                          '/',
     239                 : #endif
     240               0 :                          candidateFileList[i] );
     241                 : 
     242               0 :                 papszFileList = CSLAddString( papszFileList, fullFilename );
     243                 :             }
     244                 :         }
     245                 : 
     246              33 :         CSLDestroy( candidateFileList );
     247                 : 
     248              33 :         if( CSLCount(papszFileList) == 0 )
     249                 :         {
     250              33 :             if( !bTestOpen )
     251                 :                 CPLError( CE_Failure, CPLE_OpenFailed,
     252                 :                           "No candidate NTF files (.ntf) found in\n"
     253                 :                           "directory: %s",
     254               0 :                           pszFilename );
     255                 : 
     256              33 :             return FALSE;
     257                 :         }
     258                 :     }
     259                 : 
     260                 : /* -------------------------------------------------------------------- */
     261                 : /*      Loop over all these files trying to open them.  In testopen     */
     262                 : /*      mode we first read the first 80 characters, to verify that      */
     263                 : /*      it looks like an NTF file.  Note that we don't keep the file    */
     264                 : /*      open ... we don't want to occupy alot of file handles when      */
     265                 : /*      handling a whole directory.                                     */
     266                 : /* -------------------------------------------------------------------- */
     267                 :     int         i;
     268                 : 
     269                 :     papoNTFFileReader = (NTFFileReader **)
     270             552 :         CPLCalloc(sizeof(void*), CSLCount(papszFileList));
     271                 :     
     272            1104 :     for( i = 0; papszFileList[i] != NULL; i++ )
     273                 :     {
     274             552 :         if( bTestOpen )
     275                 :         {
     276                 :             char        szHeader[80];
     277                 :             FILE        *fp;
     278                 :             int         j;
     279                 : 
     280             552 :             fp = VSIFOpen( papszFileList[i], "rb" );
     281             552 :             if( fp == NULL )
     282               0 :                 continue;
     283                 :             
     284             552 :             if( VSIFRead( szHeader, 80, 1, fp ) < 1 )
     285                 :             {
     286              27 :                 VSIFClose( fp );
     287              27 :                 continue;
     288                 :             }
     289                 : 
     290             525 :             VSIFClose( fp );
     291                 :             
     292             525 :             if( !EQUALN(szHeader,"01",2) )
     293             517 :                 continue;
     294                 : 
     295             620 :             for( j = 0; j < 80; j++ )
     296                 :             {
     297             614 :                 if( szHeader[j] == 10 || szHeader[j] == 13 )
     298               2 :                     break;
     299                 :             }
     300                 : 
     301               8 :             if( j == 80 || szHeader[j-1] != '%' )
     302               6 :                 continue;
     303                 : 
     304                 :         }
     305                 : 
     306                 :         NTFFileReader   *poFR;
     307                 : 
     308               2 :         poFR = new NTFFileReader( this );
     309                 : 
     310               2 :         if( !poFR->Open( papszFileList[i] ) )
     311                 :         {
     312               0 :             delete poFR;
     313               0 :             CSLDestroy( papszFileList );
     314                 :             
     315               0 :             return FALSE;
     316                 :         }
     317                 : 
     318               2 :         poFR->SetBaseFID( nNTFFileCount * 1000000 + 1 );
     319               2 :         poFR->Close();
     320                 : 
     321               2 :         EnsureTileNameUnique( poFR );
     322                 : 
     323               2 :         papoNTFFileReader[nNTFFileCount++] = poFR;
     324                 :     }
     325                 : 
     326             552 :     CSLDestroy( papszFileList );
     327                 : 
     328             552 :     if( nNTFFileCount == 0 )
     329             550 :         return FALSE;
     330                 : 
     331                 : /* -------------------------------------------------------------------- */
     332                 : /*      Establish generic layers.                                       */
     333                 : /* -------------------------------------------------------------------- */
     334               2 :     EstablishGenericLayers();
     335                 :     
     336                 : /* -------------------------------------------------------------------- */
     337                 : /*      Loop over all the files, collecting a unique feature class      */
     338                 : /*      listing.                                                        */
     339                 : /* -------------------------------------------------------------------- */
     340               4 :     for( int iSrcFile = 0; iSrcFile < nNTFFileCount; iSrcFile++ )
     341                 :     {
     342               2 :         NTFFileReader   *poSrcReader = papoNTFFileReader[iSrcFile];
     343                 :         
     344             276 :         for( int iSrcFC = 0; iSrcFC < poSrcReader->GetFCCount(); iSrcFC++ )
     345                 :         {
     346                 :             int         iDstFC;
     347                 :             char       *pszSrcFCName, *pszSrcFCNum;
     348                 : 
     349             274 :             poSrcReader->GetFeatureClass( iSrcFC, &pszSrcFCNum, &pszSrcFCName);
     350                 :             
     351           26475 :             for( iDstFC = 0; iDstFC < nFCCount; iDstFC++ )
     352                 :             {
     353           26201 :                 if( EQUAL(pszSrcFCNum,papszFCNum[iDstFC]) )
     354               0 :                     break;
     355                 :             }
     356                 : 
     357             274 :             if( iDstFC >= nFCCount )
     358                 :             {
     359             274 :                 nFCCount++;
     360             274 :                 papszFCNum = CSLAddString(papszFCNum,pszSrcFCNum);
     361             274 :                 papszFCName = CSLAddString(papszFCName,pszSrcFCName);
     362                 :             }
     363                 :         }
     364                 :     }
     365                 : 
     366                 : /* -------------------------------------------------------------------- */
     367                 : /*      Create a new layer specifically for feature classes.            */
     368                 : /* -------------------------------------------------------------------- */
     369               2 :     if( nFCCount > 0 )
     370               2 :         poFCLayer = new OGRNTFFeatureClassLayer( this );
     371                 :     else
     372               0 :         poFCLayer = NULL;
     373                 :     
     374               2 :     return TRUE;
     375                 : }
     376                 : 
     377                 : /************************************************************************/
     378                 : /*                            ResetReading()                            */
     379                 : /*                                                                      */
     380                 : /*      Cleanup, and start over.                                        */
     381                 : /************************************************************************/
     382                 : 
     383               0 : void OGRNTFDataSource::ResetReading()
     384                 : 
     385                 : {
     386               0 :     for( int i = 0; i < nNTFFileCount; i++ )
     387               0 :         papoNTFFileReader[i]->Close();
     388                 : 
     389               0 :     iCurrentReader = -1;
     390               0 :     nCurrentPos = -1;
     391               0 :     nCurrentFID = 1;
     392               0 :     iCurrentFC = 0;
     393               0 : }
     394                 : 
     395                 : /************************************************************************/
     396                 : /*                           GetNextFeature()                           */
     397                 : /************************************************************************/
     398                 : 
     399               0 : OGRFeature *OGRNTFDataSource::GetNextFeature()
     400                 : 
     401                 : {
     402               0 :     OGRFeature  *poFeature = NULL;
     403                 : 
     404                 : /* -------------------------------------------------------------------- */
     405                 : /*      If we have already read all the conventional features, we       */
     406                 : /*      should try and return feature class features.                   */    
     407                 : /* -------------------------------------------------------------------- */
     408               0 :     if( iCurrentReader == nNTFFileCount )
     409                 :     {
     410               0 :         if( iCurrentFC < nFCCount )
     411               0 :             return poFCLayer->GetFeature( iCurrentFC++ );
     412                 :         else
     413               0 :             return NULL;
     414                 :     }
     415                 : 
     416                 : /* -------------------------------------------------------------------- */
     417                 : /*      Do we need to open a file?                                      */
     418                 : /* -------------------------------------------------------------------- */
     419               0 :     if( iCurrentReader == -1 )
     420                 :     {
     421               0 :         iCurrentReader++;
     422               0 :         nCurrentPos = -1;
     423                 :     }
     424                 : 
     425               0 :     if( papoNTFFileReader[iCurrentReader]->GetFP() == NULL )
     426                 :     {
     427               0 :         papoNTFFileReader[iCurrentReader]->Open();
     428                 :     }
     429                 : 
     430                 : /* -------------------------------------------------------------------- */
     431                 : /*      Ensure we are reading on from the same point we were reading    */
     432                 : /*      from for the last feature, even if some other access            */
     433                 : /*      mechanism has moved the file pointer.                           */
     434                 : /* -------------------------------------------------------------------- */
     435               0 :     if( nCurrentPos != -1 )
     436               0 :         papoNTFFileReader[iCurrentReader]->SetFPPos( nCurrentPos,
     437               0 :                                                      nCurrentFID );
     438                 :         
     439                 : /* -------------------------------------------------------------------- */
     440                 : /*      Read a feature.  If we get NULL the file must be all            */
     441                 : /*      consumed, advance to the next file.                             */
     442                 : /* -------------------------------------------------------------------- */
     443               0 :     poFeature = papoNTFFileReader[iCurrentReader]->ReadOGRFeature();
     444               0 :     if( poFeature == NULL )
     445                 :     {
     446               0 :         papoNTFFileReader[iCurrentReader]->Close();
     447               0 :         if( GetOption("CACHING") != NULL
     448                 :             && EQUAL(GetOption("CACHING"),"OFF") )
     449               0 :             papoNTFFileReader[iCurrentReader]->DestroyIndex();
     450                 : 
     451               0 :         iCurrentReader++;
     452               0 :         nCurrentPos = -1;
     453               0 :         nCurrentFID = 1;
     454                 : 
     455               0 :         poFeature = GetNextFeature();
     456                 :     }
     457                 :     else
     458                 :     {
     459               0 :         papoNTFFileReader[iCurrentReader]->GetFPPos(&nCurrentPos,
     460               0 :                                                     &nCurrentFID);
     461                 :     }
     462                 : 
     463               0 :     return poFeature;
     464                 : }
     465                 : 
     466                 : /************************************************************************/
     467                 : /*                          GetFeatureClass()                           */
     468                 : /************************************************************************/
     469                 : 
     470               0 : int OGRNTFDataSource::GetFeatureClass( int iFCIndex,
     471                 :                                        char ** ppszFCId,
     472                 :                                        char ** ppszFCName )
     473                 : 
     474                 : {
     475               0 :     if( iFCIndex < 0 || iFCIndex >= nFCCount )
     476                 :     {
     477               0 :         *ppszFCId = NULL;
     478               0 :         *ppszFCName = NULL;
     479               0 :         return FALSE;
     480                 :     }
     481                 :     else
     482                 :     {
     483               0 :         *ppszFCId = papszFCNum[iFCIndex];
     484               0 :         *ppszFCName = papszFCName[iFCIndex];
     485               0 :         return TRUE;
     486                 :     }
     487                 : }
     488                 : 
     489                 : /************************************************************************/
     490                 : /*                             SetOptions()                             */
     491                 : /************************************************************************/
     492                 : 
     493               0 : void OGRNTFDataSource::SetOptionList( char ** papszNewOptions )
     494                 : 
     495                 : {
     496               0 :     CSLDestroy( papszOptions );
     497               0 :     papszOptions = CSLDuplicate( papszNewOptions );
     498               0 : }
     499                 : 
     500                 : /************************************************************************/
     501                 : /*                             GetOption()                              */
     502                 : /************************************************************************/
     503                 : 
     504              12 : const char *OGRNTFDataSource::GetOption( const char * pszOption )
     505                 : 
     506                 : {
     507              12 :     return CSLFetchNameValue( papszOptions, pszOption );
     508                 : }
     509                 : 
     510                 : /************************************************************************/
     511                 : /*                        EnsureTileNameUnique()                        */
     512                 : /*                                                                      */
     513                 : /*      This method is called with an NTFFileReader to ensure that      */
     514                 : /*      it's tilename is unique relative to all the readers already     */
     515                 : /*      assigned to this data source.  If not, a unique name is         */
     516                 : /*      selected for it and assigned.  This method should not be        */
     517                 : /*      called with readers that are allready attached to the data      */
     518                 : /*      source.                                                         */
     519                 : /************************************************************************/
     520                 : 
     521               2 : void OGRNTFDataSource::EnsureTileNameUnique( NTFFileReader *poNewReader )
     522                 : 
     523                 : {
     524               2 :     int       iSequenceNumber = -1;
     525                 :     int       bIsUnique;
     526                 :     char      szCandidateName[11];
     527                 : 
     528               2 :     szCandidateName[10] = '\0';
     529               2 :     do
     530                 :     {
     531               2 :         bIsUnique = TRUE;
     532               2 :         if( iSequenceNumber++ == -1 )
     533               2 :             strncpy( szCandidateName, poNewReader->GetTileName(), 10 );
     534                 :         else
     535               0 :             sprintf( szCandidateName, "%010d", iSequenceNumber );
     536                 : 
     537               2 :         for( int iReader = 0; iReader < nNTFFileCount && bIsUnique; iReader++ )
     538                 :         {
     539               0 :             if( strcmp( szCandidateName, 
     540                 :                         GetFileReader( iReader )->GetTileName() ) == 0 )
     541               0 :                 bIsUnique = FALSE;
     542                 :         }
     543                 :     } while( !bIsUnique );
     544                 : 
     545               2 :     if( iSequenceNumber > 0 )
     546                 :     {
     547               0 :         poNewReader->OverrideTileName( szCandidateName );
     548                 :         CPLError( CE_Warning, CPLE_AppDefined, 
     549                 :                   "Forcing TILE_REF to `%s' on file %s\n"
     550                 :                   "to avoid conflict with other tiles in this data source.",
     551               0 :                   szCandidateName, poNewReader->GetFilename() );
     552                 :     }
     553                 : 
     554               2 : }

Generated by: LCOV version 1.7