EVOLUTION-MANAGER
Edit File: ntffilereader.cpp
/****************************************************************************** * * Project: NTF Translator * Purpose: NTFFileReader class implementation. * Author: Frank Warmerdam, warmerdam@pobox.com * ****************************************************************************** * Copyright (c) 1999, Frank Warmerdam * Copyright (c) 2013, Even Rouault <even dot rouault at mines-paris dot org> * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include <stdarg.h> #include "ntf.h" #include "cpl_conv.h" #include "cpl_string.h" #include "ogr_api.h" #include <algorithm> CPL_CVSID("$Id: ntffilereader.cpp 39006 2017-06-09 07:55:52Z rouault $"); static int DefaultNTFRecordGrouper( NTFFileReader *, NTFRecord **, NTFRecord * ); /************************************************************************/ /* NTFFileReader */ /************************************************************************/ NTFFileReader::NTFFileReader( OGRNTFDataSource * poDataSource ) : pszFilename(NULL), poDS(poDataSource), fp(NULL), nFCCount(0), papszFCNum(NULL), papszFCName(NULL), nAttCount(0), pasAttDesc(NULL), pszTileName(NULL), nCoordWidth(6), nZWidth(6), nNTFLevel(0), dfXYMult(1.0), dfZMult(1.0), dfXOrigin(0), dfYOrigin(0), dfTileXSize(0), dfTileYSize(0), dfScale(0.0), dfPaperToGround(0.0), nStartPos(0), nPreSavedPos(0), nPostSavedPos(0), poSavedRecord(NULL), nSavedFeatureId(1), nBaseFeatureId(1), nFeatureCount(-1), pszProduct(NULL), pszPVName(NULL), nProduct(NPC_UNKNOWN), pfnRecordGrouper(DefaultNTFRecordGrouper), bIndexBuilt(FALSE), bIndexNeeded(FALSE), nRasterXSize(1), nRasterYSize(1), nRasterDataType(1), poRasterLayer(NULL), panColumnOffset(NULL), bCacheLines(TRUE), nLineCacheSize(0), papoLineCache(NULL) { apoCGroup[0] = NULL; apoCGroup[1] = NULL; apoCGroup[MAX_REC_GROUP] = NULL; memset( adfGeoTransform, 0, sizeof(adfGeoTransform) ); memset( apoTypeTranslation, 0, sizeof(apoTypeTranslation) ); for( int i = 0; i < 100; i++ ) { anIndexSize[i] = 0; apapoRecordIndex[i] = NULL; } if( poDS->GetOption("CACHE_LINES") != NULL && EQUAL(poDS->GetOption("CACHE_LINES"),"OFF") ) bCacheLines = FALSE; } /************************************************************************/ /* ~NTFFileReader() */ /************************************************************************/ NTFFileReader::~NTFFileReader() { CacheClean(); DestroyIndex(); ClearDefs(); CPLFree( pszFilename ); CPLFree( panColumnOffset ); } /************************************************************************/ /* SetBaseFID() */ /************************************************************************/ void NTFFileReader::SetBaseFID( long nNewBase ) { CPLAssert( nSavedFeatureId == 1 ); nBaseFeatureId = nNewBase; nSavedFeatureId = nBaseFeatureId; } /************************************************************************/ /* ClearDefs() */ /* */ /* Clear attribute definitions and feature classes. All the */ /* stuff that would have to be cleaned up by Open(), and the */ /* destructor. */ /************************************************************************/ void NTFFileReader::ClearDefs() { Close(); ClearCGroup(); CSLDestroy( papszFCNum ); papszFCNum = NULL; CSLDestroy( papszFCName ); papszFCName = NULL; nFCCount = 0; for( int i = 0; i < nAttCount; i++ ) { if( pasAttDesc[i].poCodeList != NULL ) delete pasAttDesc[i].poCodeList; } CPLFree( pasAttDesc ); nAttCount = 0; pasAttDesc = NULL; CPLFree( pszProduct ); pszProduct = NULL; CPLFree( pszPVName ); pszPVName = NULL; CPLFree( pszTileName ); pszTileName = NULL; } /************************************************************************/ /* Close() */ /* */ /* Close the file, but don't wipe out our knowledge about this */ /* file. */ /************************************************************************/ void NTFFileReader::Close() { if( poSavedRecord != NULL ) delete poSavedRecord; poSavedRecord = NULL; nPreSavedPos = nPostSavedPos = 0; nSavedFeatureId = nBaseFeatureId; if( fp != NULL ) { VSIFClose( fp ); fp = NULL; } CacheClean(); } /************************************************************************/ /* Open() */ /************************************************************************/ int NTFFileReader::Open( const char * pszFilenameIn ) { if( pszFilenameIn != NULL ) { ClearDefs(); CPLFree( pszFilename ); pszFilename = CPLStrdup( pszFilenameIn ); } else Close(); /* -------------------------------------------------------------------- */ /* Open the file. */ /* -------------------------------------------------------------------- */ fp = VSIFOpen( pszFilename, "rb" ); // notdef: we should likely issue a proper CPL error message based // based on errno here. if( fp == NULL ) { CPLError( CE_Failure, CPLE_OpenFailed, "Unable to open file `%s' for read access.\n", pszFilename ); return FALSE; } /* -------------------------------------------------------------------- */ /* If we are just reopening an existing file we will just scan */ /* past the section header ... no need to reform all the definitions.*/ /* -------------------------------------------------------------------- */ if( pszFilenameIn == NULL ) { NTFRecord *poRecord = NULL; for( poRecord = new NTFRecord( fp ); poRecord->GetType() != NRT_VTR && poRecord->GetType() != NRT_SHR; poRecord = new NTFRecord( fp ) ) { delete poRecord; } delete poRecord; return TRUE; } /* -------------------------------------------------------------------- */ /* Read the first record, and verify it is a proper volume header. */ /* -------------------------------------------------------------------- */ NTFRecord oVHR( fp ); if( oVHR.GetType() != NRT_VHR ) { CPLError( CE_Failure, CPLE_AppDefined, "File `%s' appears to not be a UK NTF file.\n", pszFilename ); return FALSE; } nNTFLevel = atoi(oVHR.GetField( 57, 57 )); if( !( nNTFLevel >= 1 && nNTFLevel <= 5 ) ) { CPLError( CE_Failure, CPLE_AppDefined, "Invalid value : nNTFLevel = %d", nNTFLevel ); return FALSE; } /* -------------------------------------------------------------------- */ /* Read records till we get the section header. */ /* -------------------------------------------------------------------- */ NTFRecord *poRecord = NULL; for( poRecord = new NTFRecord( fp ); poRecord->GetType() != NRT_VTR && poRecord->GetType() != NRT_SHR; poRecord = new NTFRecord( fp ) ) { /* -------------------------------------------------------------------- */ /* Handle feature class name records. */ /* -------------------------------------------------------------------- */ if( poRecord->GetType() == NRT_FCR && poRecord->GetLength() >= 37 ) { nFCCount++; papszFCNum = CSLAddString( papszFCNum, poRecord->GetField(3,6) ); CPLString osFCName; const char *pszData = poRecord->GetData(); // CODE_COM int iChar = 15; for( ; pszData[iChar] == ' ' && iChar > 5; iChar-- ) {} if( iChar > 6 ) osFCName += poRecord->GetField(7,iChar+1); // STCLASS for( iChar = 35; pszData[iChar] == ' ' && iChar > 15; iChar-- ) {} if( iChar > 15 ) { if( !osFCName.empty() ) osFCName += " : " ; osFCName += poRecord->GetField(17,iChar+1); } // FEATDES for( iChar = 36; pszData[iChar] != '\0' && pszData[iChar] != '\\'; iChar++ ) {} if( iChar > 37 ) { if( !osFCName.empty() ) osFCName += " : " ; osFCName += poRecord->GetField(37,iChar); } papszFCName = CSLAddString(papszFCName, osFCName ); } /* -------------------------------------------------------------------- */ /* Handle attribute description records. */ /* -------------------------------------------------------------------- */ else if( poRecord->GetType() == NRT_ADR ) { nAttCount++; pasAttDesc = (NTFAttDesc *) CPLRealloc( pasAttDesc, sizeof(NTFAttDesc) * nAttCount ); ProcessAttDesc( poRecord, pasAttDesc + nAttCount - 1 ); } /* -------------------------------------------------------------------- */ /* Handle attribute description records. */ /* -------------------------------------------------------------------- */ else if( poRecord->GetType() == NRT_CODELIST ) { NTFCodeList *poCodeList = new NTFCodeList( poRecord ); NTFAttDesc *psAttDesc = GetAttDesc( poCodeList->szValType ); if( psAttDesc == NULL ) { CPLDebug( "NTF", "Got CODELIST for %s without ATTDESC.", poCodeList->szValType ); delete poCodeList; } else if( psAttDesc->poCodeList != NULL ) { // Should not happen on sane files. delete poCodeList; } else { psAttDesc->poCodeList = poCodeList; } } /* -------------------------------------------------------------------- */ /* Handle database header record. */ /* -------------------------------------------------------------------- */ else if( poRecord->GetType() == NRT_DHR ) { pszProduct = CPLStrdup(poRecord->GetField(3,22)); for( int iChar = static_cast<int>(strlen(pszProduct))-1; iChar > 0 && pszProduct[iChar] == ' '; pszProduct[iChar--] = '\0' ) {} pszPVName = CPLStrdup(poRecord->GetField(76+3,76+22)); for( int iChar = static_cast<int>(strlen(pszPVName))-1; iChar > 0 && pszPVName[iChar] == ' '; pszPVName[iChar--] = '\0' ) {} } delete poRecord; } /* -------------------------------------------------------------------- */ /* Did we fall off the end without finding what we were looking */ /* for? */ /* -------------------------------------------------------------------- */ if( poRecord->GetType() == NRT_VTR ) { delete poRecord; CPLError( CE_Failure, CPLE_AppDefined, "Cound not find section header record in %s.\n", pszFilename ); return FALSE; } if( pszProduct == NULL ) { delete poRecord; CPLError( CE_Failure, CPLE_AppDefined, "Cound not find product type in %s.\n", pszFilename ); return FALSE; } /* -------------------------------------------------------------------- */ /* Classify the product type. */ /* -------------------------------------------------------------------- */ if( STARTS_WITH_CI(pszProduct, "LAND-LINE") && strlen(pszPVName) > 5 && CPLAtof(pszPVName+5) < 1.3 ) nProduct = NPC_LANDLINE; else if( STARTS_WITH_CI(pszProduct, "LAND-LINE") ) nProduct = NPC_LANDLINE99; else if( EQUAL(pszProduct,"OS_LANDRANGER_CONT") ) // Panorama nProduct = NPC_LANDRANGER_CONT; else if( EQUAL(pszProduct,"L-F_PROFILE_CON") ) // Panorama nProduct = NPC_LANDFORM_PROFILE_CONT; else if( STARTS_WITH_CI(pszProduct, "Strategi") ) nProduct = NPC_STRATEGI; else if( STARTS_WITH_CI(pszProduct, "Meridian_02") ) nProduct = NPC_MERIDIAN2; else if( STARTS_WITH_CI(pszProduct, "Meridian_01") ) nProduct = NPC_MERIDIAN; else if( EQUAL(pszProduct,NTF_BOUNDARYLINE) && STARTS_WITH_CI(pszPVName, "A10N_FC") ) nProduct = NPC_BOUNDARYLINE; else if( EQUAL(pszProduct,NTF_BOUNDARYLINE) && STARTS_WITH_CI(pszPVName, "A20N_FC") ) nProduct = NPC_BL2000; else if( STARTS_WITH_CI(pszProduct, "BaseData.GB") ) nProduct = NPC_BASEDATA; else if( STARTS_WITH_CI(pszProduct, "OSCAR_ASSET") ) nProduct = NPC_OSCAR_ASSET; else if( STARTS_WITH_CI(pszProduct, "OSCAR_TRAFF") ) nProduct = NPC_OSCAR_TRAFFIC; else if( STARTS_WITH_CI(pszProduct, "OSCAR_ROUTE") ) nProduct = NPC_OSCAR_ROUTE; else if( STARTS_WITH_CI(pszProduct, "OSCAR_NETWO") ) nProduct = NPC_OSCAR_NETWORK; else if( STARTS_WITH_CI(pszProduct, "ADDRESS_POI") ) nProduct = NPC_ADDRESS_POINT; else if( STARTS_WITH_CI(pszProduct, "CODE_POINT") ) { if( GetAttDesc( "RH" ) == NULL ) nProduct = NPC_CODE_POINT; else nProduct = NPC_CODE_POINT_PLUS; } else if( STARTS_WITH_CI(pszProduct, "OS_LANDRANGER_DTM") ) nProduct = NPC_LANDRANGER_DTM; else if( STARTS_WITH_CI(pszProduct, "L-F_PROFILE_DTM") ) nProduct = NPC_LANDFORM_PROFILE_DTM; else if( STARTS_WITH_CI(pszProduct, "NEXTMap Britain DTM") ) nProduct = NPC_LANDFORM_PROFILE_DTM; // Treat as landform if( poDS->GetOption("FORCE_GENERIC") != NULL && !EQUAL(poDS->GetOption("FORCE_GENERIC"),"OFF") ) nProduct = NPC_UNKNOWN; // No point in caching lines if there are no polygons. if( nProduct != NPC_BOUNDARYLINE && nProduct != NPC_BL2000 ) bCacheLines = FALSE; /* -------------------------------------------------------------------- */ /* Handle the section header record. */ /* -------------------------------------------------------------------- */ nSavedFeatureId = nBaseFeatureId; nStartPos = VSIFTell(fp); pszTileName = CPLStrdup(poRecord->GetField(3,12)); // SECT_REF size_t nTileNameLen = strlen(pszTileName); while( nTileNameLen > 0 && pszTileName[nTileNameLen-1] == ' ' ) { pszTileName[nTileNameLen-1] = '\0'; nTileNameLen --; } nCoordWidth = atoi(poRecord->GetField(15,19)); // XYLEN if( nCoordWidth <= 0 ) nCoordWidth = 10; nZWidth = atoi(poRecord->GetField(31,35)); // ZLEN if( nZWidth <= 0 ) nZWidth = 10; dfXYMult = atoi(poRecord->GetField(21,30)) / 1000.0; // XY_MULT dfXOrigin = atoi(poRecord->GetField(47,56)); dfYOrigin = atoi(poRecord->GetField(57,66)); dfTileXSize = atoi(poRecord->GetField(23+74,32+74)); dfTileYSize = atoi(poRecord->GetField(33+74,42+74)); dfZMult = atoi(poRecord->GetField(37,46)) / 1000.0; /* -------------------------------------------------------------------- */ /* Setup scale and transformation factor for text height. */ /* -------------------------------------------------------------------- */ if( poRecord->GetLength() >= 187 ) dfScale = atoi(poRecord->GetField(148+31,148+39)); else if( nProduct == NPC_STRATEGI ) dfScale = 250000; else if( nProduct == NPC_MERIDIAN || nProduct == NPC_MERIDIAN2 ) dfScale = 100000; else if( nProduct == NPC_LANDFORM_PROFILE_CONT ) dfScale = 10000; else if( nProduct == NPC_LANDRANGER_CONT ) dfScale = 50000; else if( nProduct == NPC_OSCAR_ASSET || nProduct == NPC_OSCAR_TRAFFIC || nProduct == NPC_OSCAR_NETWORK || nProduct == NPC_OSCAR_ROUTE ) dfScale = 10000; else if( nProduct == NPC_BASEDATA ) dfScale = 625000; else /*if( nProduct == NPC_BOUNDARYLINE ) or default case */ dfScale = 10000; if( dfScale != 0.0 ) dfPaperToGround = dfScale / 1000.0; else dfPaperToGround = 0.0; delete poRecord; /* -------------------------------------------------------------------- */ /* Ensure we have appropriate layers defined. */ /* -------------------------------------------------------------------- */ CPLErrorReset(); if( !IsRasterProduct() ) EstablishLayers(); else EstablishRasterAccess(); return CPLGetLastErrorType() != CE_Failure; } /************************************************************************/ /* DumpReadable() */ /************************************************************************/ void NTFFileReader::DumpReadable( FILE *fpLog ) { fprintf( fpLog, "Tile Name = %s\n", pszTileName ); fprintf( fpLog, "Product = %s\n", pszProduct ); fprintf( fpLog, "NTFLevel = %d\n", nNTFLevel ); fprintf( fpLog, "XYLEN = %d\n", nCoordWidth ); fprintf( fpLog, "XY_MULT = %g\n", dfXYMult ); fprintf( fpLog, "X_ORIG = %g\n", dfXOrigin ); fprintf( fpLog, "Y_ORIG = %g\n", dfYOrigin ); fprintf( fpLog, "XMAX = %g\n", dfTileXSize ); fprintf( fpLog, "YMAX = %g\n", dfTileYSize ); } /************************************************************************/ /* ProcessGeometry() */ /* */ /* Drop duplicate vertices from line strings ... they mess up */ /* FME's polygon handling sometimes. */ /************************************************************************/ OGRGeometry *NTFFileReader::ProcessGeometry( NTFRecord * poRecord, int * pnGeomId ) { if( poRecord->GetType() == NRT_GEOMETRY3D ) return ProcessGeometry3D( poRecord, pnGeomId ); else if( poRecord->GetType() != NRT_GEOMETRY ) return NULL; const int nGType = atoi(poRecord->GetField(9,9)); // GTYPE const int nNumCoord = atoi(poRecord->GetField(10,13)); // NUM_COORD if( pnGeomId != NULL ) *pnGeomId = atoi(poRecord->GetField(3,8)); // GEOM_ID /* -------------------------------------------------------------------- */ /* Point */ /* -------------------------------------------------------------------- */ OGRGeometry *poGeometry = NULL; if( nGType == 1 ) { const double dfX = atoi(poRecord->GetField(14,14+GetXYLen()-1)) * GetXYMult() + GetXOrigin(); const double dfY = atoi(poRecord->GetField(14+GetXYLen(),14+GetXYLen()*2-1)) * GetXYMult() + GetYOrigin(); poGeometry = new OGRPoint( dfX, dfY ); } /* -------------------------------------------------------------------- */ /* Line (or arc) */ /* -------------------------------------------------------------------- */ else if( nGType == 2 || nGType == 3 || nGType == 4 ) { OGRLineString *poLine = new OGRLineString; double dfXLast = 0.0; double dfYLast = 0.0; int nOutCount = 0; poGeometry = poLine; poLine->setNumPoints( nNumCoord ); for( int iCoord = 0; iCoord < nNumCoord; iCoord++ ) { const int iStart = 14 + iCoord * (GetXYLen()*2+1); const double dfX = atoi(poRecord->GetField(iStart+0, iStart+GetXYLen()-1)) * GetXYMult() + GetXOrigin(); const double dfY = atoi(poRecord->GetField(iStart+GetXYLen(), iStart+GetXYLen()*2-1)) * GetXYMult() + GetYOrigin(); if( iCoord == 0 ) { dfXLast = dfX; dfYLast = dfY; poLine->setPoint( nOutCount++, dfX, dfY ); } else if( dfXLast != dfX || dfYLast != dfY ) { dfXLast = dfX; dfYLast = dfY; poLine->setPoint( nOutCount++, dfX, dfY ); } } poLine->setNumPoints( nOutCount ); CacheAddByGeomId( atoi(poRecord->GetField(3,8)), poLine ); } /* -------------------------------------------------------------------- */ /* Arc defined by three points on the arc. */ /* -------------------------------------------------------------------- */ else if( nGType == 5 && nNumCoord == 3 ) { double adfX[3] = { 0.0, 0.0, 0.0 }; double adfY[3] = { 0.0, 0.0, 0.0 }; for( int iCoord = 0; iCoord < nNumCoord; iCoord++ ) { const int iStart = 14 + iCoord * (GetXYLen()*2+1); adfX[iCoord] = atoi(poRecord->GetField(iStart+0, iStart+GetXYLen()-1)) * GetXYMult() + GetXOrigin(); adfY[iCoord] = atoi(poRecord->GetField(iStart+GetXYLen(), iStart+GetXYLen()*2-1)) * GetXYMult() + GetYOrigin(); } poGeometry = NTFStrokeArcToOGRGeometry_Points( adfX[0], adfY[0], adfX[1], adfY[1], adfX[2], adfY[2], 72 ); } /* -------------------------------------------------------------------- */ /* Circle */ /* -------------------------------------------------------------------- */ else if( nGType == 7 ) { const int iCenterStart = 14; const int iArcStart = 14 + 2 * GetXYLen() + 1; const double dfCenterX = atoi(poRecord->GetField(iCenterStart, iCenterStart+GetXYLen()-1)) * GetXYMult() + GetXOrigin(); const double dfCenterY = atoi(poRecord->GetField(iCenterStart+GetXYLen(), iCenterStart+GetXYLen()*2-1)) * GetXYMult() + GetYOrigin(); const double dfArcX = atoi(poRecord->GetField(iArcStart, iArcStart+GetXYLen()-1)) * GetXYMult() + GetXOrigin(); const double dfArcY = atoi(poRecord->GetField(iArcStart+GetXYLen(), iArcStart+GetXYLen()*2-1)) * GetXYMult() + GetYOrigin(); const double dfRadius = sqrt( (dfCenterX - dfArcX) * (dfCenterX - dfArcX) + (dfCenterY - dfArcY) * (dfCenterY - dfArcY) ); poGeometry = NTFStrokeArcToOGRGeometry_Angles( dfCenterX, dfCenterY, dfRadius, 0.0, 360.0, 72 ); } else { CPLError(CE_Failure, CPLE_AppDefined, "Unhandled GType = %d", nGType ); } if( poGeometry != NULL ) poGeometry->assignSpatialReference( poDS->GetSpatialRef() ); return poGeometry; } /************************************************************************/ /* ProcessGeometry3D() */ /************************************************************************/ OGRGeometry *NTFFileReader::ProcessGeometry3D( NTFRecord * poRecord, int * pnGeomId ) { OGRGeometry *poGeometry = NULL; if( poRecord->GetType() != NRT_GEOMETRY3D ) return NULL; const int nGType = atoi(poRecord->GetField(9,9)); // GTYPE const int nNumCoord = atoi(poRecord->GetField(10,13)); // NUM_COORD if( pnGeomId != NULL ) *pnGeomId = atoi(poRecord->GetField(3,8)); // GEOM_ID if( nGType == 1 ) { const double dfX = atoi(poRecord->GetField(14,14+GetXYLen()-1)) * GetXYMult() + GetXOrigin(); const double dfY = atoi(poRecord->GetField(14+GetXYLen(),14+GetXYLen()*2-1)) * GetXYMult() + GetYOrigin(); const double dfZ = atoi(poRecord->GetField(14+1+2*GetXYLen(), 14+1+2*GetXYLen()+nZWidth-1)) * dfZMult; poGeometry = new OGRPoint( dfX, dfY, dfZ ); } else if( nGType == 2 ) { OGRLineString *poLine = new OGRLineString; double dfXLast = 0.0; double dfYLast = 0.0; int nOutCount = 0; poGeometry = poLine; poLine->setNumPoints( nNumCoord ); for( int iCoord = 0; iCoord < nNumCoord; iCoord++ ) { const int iStart = 14 + iCoord * (GetXYLen()*2+nZWidth+2); const double dfX = atoi(poRecord->GetField(iStart+0, iStart+GetXYLen()-1)) * GetXYMult() + GetXOrigin(); const double dfY = atoi(poRecord->GetField(iStart+GetXYLen(), iStart+GetXYLen()*2-1)) * GetXYMult() + GetYOrigin(); const double dfZ = atoi(poRecord->GetField(iStart+1+2*GetXYLen(), iStart+1+2*GetXYLen()+nZWidth-1)) * dfZMult; if( iCoord == 0 ) { dfXLast = dfX; dfYLast = dfY; poLine->setPoint( nOutCount++, dfX, dfY, dfZ ); } else if( dfXLast != dfX || dfYLast != dfY ) { dfXLast = dfX; dfYLast = dfY; poLine->setPoint( nOutCount++, dfX, dfY, dfZ ); } } poLine->setNumPoints( nOutCount ); CacheAddByGeomId( atoi(poRecord->GetField(3,8)), poLine ); } if( poGeometry != NULL ) poGeometry->assignSpatialReference( poDS->GetSpatialRef() ); return poGeometry; } /************************************************************************/ /* ProcessAttDesc() */ /************************************************************************/ int NTFFileReader::ProcessAttDesc( NTFRecord * poRecord, NTFAttDesc* psAD ) { psAD->poCodeList = NULL; if( poRecord->GetType() != NRT_ADR || poRecord->GetLength() < 13 ) return FALSE; snprintf( psAD->val_type, sizeof(psAD->val_type), "%s", poRecord->GetField( 3, 4 )); snprintf( psAD->fwidth, sizeof(psAD->fwidth), "%s", poRecord->GetField( 5, 7 )); snprintf( psAD->finter, sizeof(psAD->finter), "%s", poRecord->GetField( 8, 12 )); const char *pszData = poRecord->GetData(); int iChar = 12; // Used after for. for( ; pszData[iChar] != '\0' && pszData[iChar] != '\\'; iChar++ ) {} snprintf( psAD->att_name, sizeof(psAD->att_name), "%s", poRecord->GetField( 13, iChar )); return TRUE; } /************************************************************************/ /* ProcessAttRecGroup() */ /* */ /* Extract attribute values from all attribute records in a */ /* record set. */ /************************************************************************/ int NTFFileReader::ProcessAttRecGroup( NTFRecord **papoRecords, char ***ppapszTypes, char ***ppapszValues ) { *ppapszTypes = NULL; *ppapszValues = NULL; for( int iRec = 0; papoRecords[iRec] != NULL; iRec++ ) { if( papoRecords[iRec]->GetType() != NRT_ATTREC ) continue; char **papszTypes1 = NULL; char **papszValues1 = NULL; if( !ProcessAttRec( papoRecords[iRec], NULL, &papszTypes1, &papszValues1 ) ) { CSLDestroy(*ppapszTypes); CSLDestroy(*ppapszValues); *ppapszTypes = NULL; *ppapszValues = NULL; return FALSE; } if( *ppapszTypes == NULL ) { *ppapszTypes = papszTypes1; *ppapszValues = papszValues1; } else { for( int i=0; papszTypes1[i] != NULL; i++ ) { *ppapszTypes = CSLAddString( *ppapszTypes, papszTypes1[i] ); *ppapszValues = CSLAddString( *ppapszValues, papszValues1[i] ); } CSLDestroy( papszTypes1 ); CSLDestroy( papszValues1 ); } } return TRUE; } /************************************************************************/ /* ProcessAttRec() */ /************************************************************************/ int NTFFileReader::ProcessAttRec( NTFRecord * poRecord, int *pnAttId, char *** ppapszTypes, char *** ppapszValues ) { if( pnAttId != NULL ) *pnAttId = 0; *ppapszTypes = NULL; *ppapszValues = NULL; if( poRecord->GetType() != NRT_ATTREC || poRecord->GetLength() < 8 ) return FALSE; /* -------------------------------------------------------------------- */ /* Extract the attribute id. */ /* -------------------------------------------------------------------- */ if( pnAttId != NULL ) *pnAttId = atoi(poRecord->GetField(3,8)); /* ==================================================================== */ /* Loop handling attribute till we get a '0' indicating the end */ /* of the record. */ /* ==================================================================== */ int iOffset = 8; const char *pszData = poRecord->GetData(); bool bError = false; while( iOffset < poRecord->GetLength() && pszData[iOffset] != '0' ) { /* -------------------------------------------------------------------- */ /* Extract the two letter code name for the attribute, and use */ /* it to find the correct ATTDESC info. */ /* -------------------------------------------------------------------- */ NTFAttDesc *psAttDesc = GetAttDesc(pszData + iOffset ); if( psAttDesc == NULL ) { CPLDebug( "NTF", "Couldn't translate attrec type `%2.2s'.", pszData + iOffset ); bError = true; break; } *ppapszTypes = CSLAddString( *ppapszTypes, poRecord->GetField(iOffset+1,iOffset+2) ); /* -------------------------------------------------------------------- */ /* Establish the width of the value. Zero width fields are */ /* terminated by a backslash. */ /* -------------------------------------------------------------------- */ const int nFWidth = atoi(psAttDesc->fwidth); if( nFWidth < 0 ) { bError = true; break; } int nEnd = 0; if( nFWidth == 0 ) { const char * pszData2 = poRecord->GetData(); if( iOffset + 2 >= poRecord->GetLength() ) { bError = true; break; } for( nEnd = iOffset + 2; pszData2[nEnd] != '\\' && pszData2[nEnd] != '\0'; nEnd++ ) {} } else { nEnd = iOffset + 3 + nFWidth - 1; } /* -------------------------------------------------------------------- */ /* Extract the value. If it is formatted as fixed point real */ /* we reprocess it to insert the decimal point. */ /* -------------------------------------------------------------------- */ const char * pszRawValue = poRecord->GetField(iOffset+3,nEnd); *ppapszValues = CSLAddString( *ppapszValues, pszRawValue ); /* -------------------------------------------------------------------- */ /* Establish new offset position. */ /* -------------------------------------------------------------------- */ if( nFWidth == 0 ) { iOffset = nEnd; if( iOffset >= poRecord->GetLength() ) { bError = true; break; } if( pszData[iOffset] == '\\' ) iOffset++; } else iOffset += 2 + nFWidth; } if( bError ) { CSLDestroy(*ppapszTypes); CSLDestroy(*ppapszValues); *ppapszTypes = NULL; *ppapszValues = NULL; } return( *ppapszTypes != NULL ); } /************************************************************************/ /* GetAttDesc() */ /************************************************************************/ NTFAttDesc * NTFFileReader::GetAttDesc( const char * pszType ) { for( int i = 0; i < nAttCount; i++ ) { if( EQUALN(pszType, pasAttDesc[i].val_type, 2) ) return pasAttDesc + i; } return NULL; } /************************************************************************/ /* ProcessAttValue() */ /* */ /* Take an attribute type/value pair and transform into a */ /* meaningful attribute name, and value. The source can be an */ /* ATTREC or the VAL_TYPE/VALUE pair of a POINTREC or LINEREC. */ /* The name is transformed from the two character short form to */ /* the long user name. The value will be transformed from */ /* fixed point (with the decimal implicit) to fixed point with */ /* an explicit decimal point if it has a "R" format. */ /* Note: the returned *ppszAttValue has a very short lifetime */ /* and should immediately be used. Further calls to */ /* ProcessAttValue or CPLSPrintf() will invalidate it. */ /************************************************************************/ int NTFFileReader::ProcessAttValue( const char *pszValType, const char *pszRawValue, const char **ppszAttName, const char **ppszAttValue, const char **ppszCodeDesc ) { /* -------------------------------------------------------------------- */ /* Find the ATTDESC for this attribute, and assign return name value.*/ /* -------------------------------------------------------------------- */ NTFAttDesc *psAttDesc = GetAttDesc(pszValType); if( psAttDesc == NULL ) return FALSE; if( ppszAttName != NULL ) *ppszAttName = psAttDesc->att_name; /* -------------------------------------------------------------------- */ /* Extract the value. If it is formatted as fixed point real */ /* we reprocess it to insert the decimal point. */ /* -------------------------------------------------------------------- */ if( psAttDesc->finter[0] == 'R' ) { const char *pszDecimalPortion = NULL; // Used after for. for( pszDecimalPortion = psAttDesc->finter; *pszDecimalPortion != ',' && *pszDecimalPortion != '\0'; pszDecimalPortion++ ) {} if( *pszDecimalPortion == '\0' ) { *ppszAttValue = ""; } else { const int nWidth = static_cast<int>(strlen(pszRawValue)); const int nPrecision = atoi(pszDecimalPortion+1); if( nPrecision < 0 || nPrecision >= nWidth ) { *ppszAttValue = ""; } else { CPLString osResult(pszRawValue); osResult.resize(nWidth - nPrecision); osResult += "."; osResult += pszRawValue+nWidth-nPrecision; *ppszAttValue = CPLSPrintf("%s", osResult.c_str()); } } } /* -------------------------------------------------------------------- */ /* If it is an integer, we just reformat to get rid of leading */ /* zeros. */ /* -------------------------------------------------------------------- */ else if( psAttDesc->finter[0] == 'I' ) { *ppszAttValue = CPLSPrintf("%d", atoi(pszRawValue) ); } /* -------------------------------------------------------------------- */ /* Otherwise we take the value directly. */ /* -------------------------------------------------------------------- */ else { *ppszAttValue = pszRawValue; } /* -------------------------------------------------------------------- */ /* Handle processing code values into code descriptions, if */ /* applicable. */ /* -------------------------------------------------------------------- */ if( ppszCodeDesc == NULL ) { } else if( psAttDesc->poCodeList != NULL ) { *ppszCodeDesc = psAttDesc->poCodeList->Lookup( *ppszAttValue ); } else { *ppszCodeDesc = NULL; } return TRUE; } /************************************************************************/ /* ApplyAttributeValues() */ /* */ /* Apply a series of attribute values to a feature from generic */ /* attribute records. */ /************************************************************************/ void NTFFileReader::ApplyAttributeValues( OGRFeature * poFeature, NTFRecord ** papoGroup, ... ) { /* -------------------------------------------------------------------- */ /* Extract attribute values from record group. */ /* -------------------------------------------------------------------- */ char **papszTypes = NULL; char **papszValues = NULL; if( !ProcessAttRecGroup( papoGroup, &papszTypes, &papszValues ) ) return; /* -------------------------------------------------------------------- */ /* Handle attribute pairs */ /* -------------------------------------------------------------------- */ va_list hVaArgs; va_start(hVaArgs, papoGroup); const char *pszAttName = NULL; while( (pszAttName = va_arg(hVaArgs, const char *)) != NULL ) { const int iField = va_arg(hVaArgs, int); ApplyAttributeValue( poFeature, iField, pszAttName, papszTypes, papszValues ); } va_end(hVaArgs); /* -------------------------------------------------------------------- */ /* Cleanup. */ /* -------------------------------------------------------------------- */ CSLDestroy( papszTypes ); CSLDestroy( papszValues ); } /************************************************************************/ /* ApplyAttributeValue() */ /* */ /* Apply the indicated attribute value to an OGRFeature field */ /* if it exists in the attribute value list given. */ /************************************************************************/ int NTFFileReader::ApplyAttributeValue( OGRFeature * poFeature, int iField, const char * pszAttName, char ** papszTypes, char ** papszValues ) { /* -------------------------------------------------------------------- */ /* Find the requested attribute in the name/value pair */ /* provided. If not found that's fine, just return with */ /* notification. */ /* -------------------------------------------------------------------- */ const int iValue = CSLFindString( papszTypes, pszAttName ); if( iValue < 0 ) return FALSE; CPLAssert(papszValues != NULL); /* -------------------------------------------------------------------- */ /* Process the attribute value ... this really only has a */ /* useful effect for real numbers. */ /* -------------------------------------------------------------------- */ const char *pszAttLongName = NULL; const char *pszAttValue = NULL; const char *pszCodeDesc = NULL; if( !ProcessAttValue( pszAttName, papszValues[iValue], &pszAttLongName, &pszAttValue, &pszCodeDesc ) ) return FALSE; /* -------------------------------------------------------------------- */ /* Apply the value to the field using the simple set string */ /* method. Leave it to the OGRFeature::SetField() method to */ /* take care of translation to other types. */ /* -------------------------------------------------------------------- */ poFeature->SetField( iField, pszAttValue ); /* -------------------------------------------------------------------- */ /* Apply the code description if we found one. */ /* -------------------------------------------------------------------- */ if( pszCodeDesc != NULL ) { char szDescFieldName[256]; snprintf( szDescFieldName, sizeof(szDescFieldName), "%s_DESC", poFeature->GetDefnRef()->GetFieldDefn(iField)->GetNameRef() ); poFeature->SetField( szDescFieldName, pszCodeDesc ); } return TRUE; } /************************************************************************/ /* SaveRecord() */ /************************************************************************/ void NTFFileReader::SaveRecord( NTFRecord * poRecord ) { CPLAssert( poSavedRecord == NULL ); poSavedRecord = poRecord; } /************************************************************************/ /* ReadRecord() */ /************************************************************************/ NTFRecord *NTFFileReader::ReadRecord() { if( poSavedRecord != NULL ) { NTFRecord *poReturn = poSavedRecord; poSavedRecord = NULL; return poReturn; } else { CPLErrorReset(); if( fp != NULL ) nPreSavedPos = VSIFTell( fp ); NTFRecord *poRecord = new NTFRecord( fp ); if( fp != NULL ) nPostSavedPos = VSIFTell( fp ); /* ensure termination if we fail to read a record */ if( CPLGetLastErrorType() == CE_Failure ) { delete poRecord; poRecord = NULL; } return poRecord; } } /************************************************************************/ /* GetFPPos() */ /* */ /* Return the current file pointer position. */ /************************************************************************/ void NTFFileReader::GetFPPos( long *pnPos, long *pnFID ) { if( poSavedRecord != NULL ) *pnPos = nPreSavedPos; else *pnPos = nPostSavedPos; if( pnFID != NULL ) *pnFID = nSavedFeatureId; } /************************************************************************/ /* SetFPPos() */ /************************************************************************/ int NTFFileReader::SetFPPos( long nNewPos, long nNewFID ) { if( nNewFID == nSavedFeatureId ) return TRUE; if( poSavedRecord != NULL ) { delete poSavedRecord; poSavedRecord = NULL; } if( fp != NULL && VSIFSeek( fp, nNewPos, SEEK_SET ) == 0 ) { nPreSavedPos = nPostSavedPos = nNewPos; nSavedFeatureId = nNewFID; return TRUE; } else return FALSE; } /************************************************************************/ /* Reset() */ /* */ /* Reset reading to the first feature record. */ /************************************************************************/ void NTFFileReader::Reset() { SetFPPos( nStartPos, nBaseFeatureId ); ClearCGroup(); } /************************************************************************/ /* ClearCGroup() */ /* */ /* Clear the currently loaded record group. */ /************************************************************************/ void NTFFileReader::ClearCGroup() { for( int i = 0; apoCGroup[i] != NULL; i++ ) delete apoCGroup[i]; apoCGroup[0] = NULL; apoCGroup[1] = NULL; } /************************************************************************/ /* DefaultNTFRecordGrouper() */ /* */ /* Default rules for figuring out if a new candidate record */ /* belongs to a group of records that together form a feature */ /* (a record group). */ /************************************************************************/ int DefaultNTFRecordGrouper( NTFFileReader *, NTFRecord ** papoGroup, NTFRecord * poCandidate ) { /* -------------------------------------------------------------------- */ /* Is this group going to be a CPOLY set? We can recognise */ /* this because we get repeating POLY/CHAIN sets without an */ /* intermediate attribute record. This is a rather special case! */ /* -------------------------------------------------------------------- */ if( papoGroup[0] != NULL && papoGroup[1] != NULL && papoGroup[0]->GetType() == NRT_POLYGON && papoGroup[1]->GetType() == NRT_CHAIN ) { // We keep going till we get the seed geometry. int iRec, bGotCPOLY=FALSE; for( iRec = 0; papoGroup[iRec] != NULL; iRec++ ) { if( papoGroup[iRec]->GetType() == NRT_CPOLY ) bGotCPOLY = TRUE; } if( bGotCPOLY && poCandidate->GetType() != NRT_GEOMETRY && poCandidate->GetType() != NRT_ATTREC ) return FALSE; /* * this logic assumes we always get a point geometry with a CPOLY * but that isn't always true, for instance with BL2000 data. The * preceding check will handle this case. */ if( papoGroup[iRec-1]->GetType() != NRT_GEOMETRY ) return TRUE; else return FALSE; } /* -------------------------------------------------------------------- */ /* Is this a "feature" defining record? If so break out if it */ /* isn't the first record in the group. */ /* -------------------------------------------------------------------- */ if( papoGroup[0] != NULL && (poCandidate->GetType() == NRT_NAMEREC || poCandidate->GetType() == NRT_NODEREC || poCandidate->GetType() == NRT_LINEREC || poCandidate->GetType() == NRT_POINTREC || poCandidate->GetType() == NRT_POLYGON || poCandidate->GetType() == NRT_CPOLY || poCandidate->GetType() == NRT_COLLECT || poCandidate->GetType() == NRT_TEXTREC || poCandidate->GetType() == NRT_COMMENT) ) { return FALSE; } /* -------------------------------------------------------------------- */ /* Do we already have a record of this type? If so, it likely */ /* doesn't belong in the group. Attribute records do repeat in */ /* some products. */ /* -------------------------------------------------------------------- */ if (poCandidate->GetType() != NRT_ATTREC ) { int iRec = 0; // Used after for. for( ; papoGroup[iRec] != NULL; iRec++ ) { if( poCandidate->GetType() == papoGroup[iRec]->GetType() ) break; } if( papoGroup[iRec] != NULL ) return FALSE; } return TRUE; } /************************************************************************/ /* ReadRecordGroup() */ /* */ /* Read a group of records that form a single feature. */ /************************************************************************/ NTFRecord **NTFFileReader::ReadRecordGroup() { ClearCGroup(); /* -------------------------------------------------------------------- */ /* Loop, reading records till we think we have a grouping. */ /* -------------------------------------------------------------------- */ int nRecordCount = 0; NTFRecord *poRecord = NULL; while( (poRecord = ReadRecord()) != NULL && poRecord->GetType() != NRT_VTR ) { if( nRecordCount >= MAX_REC_GROUP ) { CPLError( CE_Failure, CPLE_AppDefined, "Maximum record group size (%d) exceeded.\n", MAX_REC_GROUP ); break; } if( !pfnRecordGrouper( this, apoCGroup, poRecord ) ) break; apoCGroup[nRecordCount++] = poRecord; apoCGroup[nRecordCount] = NULL; } /* -------------------------------------------------------------------- */ /* Push the last record back on the input queue. */ /* -------------------------------------------------------------------- */ if( poRecord != NULL ) SaveRecord( poRecord ); /* -------------------------------------------------------------------- */ /* Return the list, or NULL if we didn't get any records. */ /* -------------------------------------------------------------------- */ if( nRecordCount == 0 ) return NULL; else return apoCGroup; } /************************************************************************/ /* GetFeatureClass() */ /************************************************************************/ int NTFFileReader::GetFeatureClass( int iFCIndex, char ** ppszFCId, char ** ppszFCName ) { if( iFCIndex < 0 || iFCIndex >= nFCCount ) { *ppszFCId = NULL; *ppszFCName = NULL; return FALSE; } else { *ppszFCId = papszFCNum[iFCIndex]; *ppszFCName = papszFCName[iFCIndex]; return TRUE; } } /************************************************************************/ /* ReadOGRFeature() */ /************************************************************************/ OGRFeature * NTFFileReader::ReadOGRFeature( OGRNTFLayer * poTargetLayer ) { /* -------------------------------------------------------------------- */ /* If this is a raster file, use a custom method to read the */ /* feature. */ /* -------------------------------------------------------------------- */ if( IsRasterProduct() ) return poRasterLayer->GetNextFeature(); /* -------------------------------------------------------------------- */ /* Loop looking for a group we can translate, and that if */ /* needed matches our layer request. */ /* -------------------------------------------------------------------- */ OGRNTFLayer *poLayer = NULL; OGRFeature *poFeature = NULL; while( true ) { NTFRecord **papoGroup = NULL; if( GetProductId() == NPC_UNKNOWN && nNTFLevel > 2 ) papoGroup = GetNextIndexedRecordGroup( apoCGroup + 1 ); else papoGroup = ReadRecordGroup(); if( papoGroup == NULL ) break; int nType = papoGroup[0]->GetType(); if( nType < 0 || nType >= (int)(sizeof(apoTypeTranslation) / sizeof(apoTypeTranslation[0])) ) continue; poLayer = apoTypeTranslation[nType]; if( poLayer == NULL ) continue; if( poTargetLayer != NULL && poTargetLayer != poLayer ) { CacheLineGeometryInGroup( papoGroup ); nSavedFeatureId++; continue; } poFeature = poLayer->FeatureTranslate( this, papoGroup ); if( poFeature == NULL ) { // should this be a real error? CPLDebug( "NTF", "FeatureTranslate() failed for a type %d record group\n" "in a %s type file.\n", papoGroup[0]->GetType(), GetProduct() ); } else { break; } } /* -------------------------------------------------------------------- */ /* If we got a feature, set the TILE_REF on it. */ /* -------------------------------------------------------------------- */ if( poFeature != NULL ) { int iTileRefField = poLayer->GetLayerDefn()->GetFieldCount()-1; CPLAssert( EQUAL(poLayer->GetLayerDefn()->GetFieldDefn(iTileRefField)-> GetNameRef(), "TILE_REF") ); poFeature->SetField( iTileRefField, GetTileName() ); poFeature->SetFID( nSavedFeatureId ); nSavedFeatureId++; } /* -------------------------------------------------------------------- */ /* If we got to the end we can establish our feature count for */ /* the file. */ /* -------------------------------------------------------------------- */ else { // This assertion was triggered by https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=1982 // it doesn't look critical enough to be enabled. //CPLAssert( nFeatureCount == -1 // || nFeatureCount == nSavedFeatureId - nBaseFeatureId ); nFeatureCount = nSavedFeatureId - nBaseFeatureId; } return poFeature; } /************************************************************************/ /* TestForLayer() */ /* */ /* Return indicator of whether this file contains any features */ /* of the indicated layer type. */ /************************************************************************/ int NTFFileReader::TestForLayer( OGRNTFLayer * poLayer ) { for( int i = 0; i < 100; i++ ) { if( apoTypeTranslation[i] == poLayer ) return TRUE; } return FALSE; } /************************************************************************/ /* FreshenIndex() */ /* */ /* Rebuild the index if it is needed, and currently missing. */ /************************************************************************/ void NTFFileReader::FreshenIndex() { if( !bIndexBuilt && bIndexNeeded ) IndexFile(); } /************************************************************************/ /* IndexFile() */ /* */ /* Read all records beyond the section header and build an */ /* internal index of them. */ /************************************************************************/ void NTFFileReader::IndexFile() { Reset(); DestroyIndex(); bIndexNeeded = TRUE; bIndexBuilt = TRUE; bCacheLines = FALSE; /* -------------------------------------------------------------------- */ /* Process all records after the section header, and before 99 */ /* to put them in the index. */ /* -------------------------------------------------------------------- */ NTFRecord *poRecord = NULL; while( (poRecord = ReadRecord()) != NULL && poRecord->GetType() != 99 ) { const int iType = poRecord->GetType(); const int iId = atoi(poRecord->GetField( 3, 8 )); if( iType < 0 || iType >= 100 ) { CPLError( CE_Failure, CPLE_AppDefined, "Illegal type %d record, skipping.", iType ); delete poRecord; continue; } if( iId < 0 ) { CPLError( CE_Failure, CPLE_AppDefined, "Illegal id %d record, skipping.", iId ); delete poRecord; continue; } /* -------------------------------------------------------------------- */ /* Grow type specific subindex if needed. */ /* -------------------------------------------------------------------- */ if( anIndexSize[iType] <= iId ) { const int nNewSize = std::max(iId+1, anIndexSize[iType] * 2 + 10); apapoRecordIndex[iType] = (NTFRecord **) CPLRealloc(apapoRecordIndex[iType], sizeof(void *) * nNewSize); for( int i = anIndexSize[iType]; i < nNewSize; i++ ) (apapoRecordIndex[iType])[i] = NULL; anIndexSize[iType] = nNewSize; } /* -------------------------------------------------------------------- */ /* Put record into type specific subindex based on it's id as */ /* the key. */ /* -------------------------------------------------------------------- */ if( apapoRecordIndex[iType][iId] != NULL ) { CPLDebug( "OGR_NTF", "Duplicate record with index %d and type %d\n" "in NTFFileReader::IndexFile().", iId, iType ); delete apapoRecordIndex[iType][iId]; } (apapoRecordIndex[iType])[iId] = poRecord; } if( poRecord != NULL ) delete poRecord; } /************************************************************************/ /* DestroyIndex() */ /************************************************************************/ void NTFFileReader::DestroyIndex() { for( int i = 0; i < 100; i++ ) { for( int iId = 0; iId < anIndexSize[i]; iId++ ) { if( (apapoRecordIndex[i])[iId] != NULL ) delete (apapoRecordIndex[i])[iId]; } CPLFree( apapoRecordIndex[i] ); apapoRecordIndex[i] = NULL; anIndexSize[i] = 0; } bIndexBuilt = FALSE; } /************************************************************************/ /* GetIndexedRecord() */ /************************************************************************/ NTFRecord * NTFFileReader::GetIndexedRecord( int iType, int iId ) { if( (iType < 0 || iType > 99) || (iId < 0 || iId >= anIndexSize[iType]) || (apapoRecordIndex[iType])[iId] == NULL ) { /* If NRT_GEOMETRY3D is an acceptable alternative to 2D */ if( iType == NRT_GEOMETRY ) return GetIndexedRecord( NRT_GEOMETRY3D, iId ); else return NULL; } return (apapoRecordIndex[iType])[iId]; } /************************************************************************/ /* AddToIndexGroup() */ /************************************************************************/ void NTFFileReader::AddToIndexGroup( NTFRecord * poRecord ) { int i = 1; // Used after for. for( ; apoCGroup[i] != NULL; i++ ) { if( apoCGroup[i] == poRecord ) { CPLError(CE_Failure, CPLE_AppDefined, "Record already inserted in group"); return; } } if( i == MAX_REC_GROUP ) { CPLError(CE_Failure, CPLE_AppDefined, "Maximum number of records in group reached"); delete poRecord; return; } apoCGroup[i] = poRecord; apoCGroup[i+1] = NULL; } /************************************************************************/ /* GetNextIndexedRecordGroup() */ /************************************************************************/ NTFRecord **NTFFileReader::GetNextIndexedRecordGroup( NTFRecord ** papoPrevGroup ) { int nPrevType, nPrevId; /* -------------------------------------------------------------------- */ /* What was the identify of our previous anchor record? */ /* -------------------------------------------------------------------- */ if( papoPrevGroup == NULL || papoPrevGroup[0] == NULL ) { nPrevType = NRT_POINTREC; nPrevId = 0; FreshenIndex(); } else { nPrevType = papoPrevGroup[0]->GetType(); nPrevId = atoi(papoPrevGroup[0]->GetField(3,8)); if( nPrevId < 0 ) return NULL; } /* -------------------------------------------------------------------- */ /* Find the next anchor record. */ /* -------------------------------------------------------------------- */ NTFRecord *poAnchor = NULL; while( nPrevType != 99 && poAnchor == NULL ) { nPrevId++; if( nPrevId >= anIndexSize[nPrevType] ) { do { nPrevType++; } while( nPrevType != NRT_VTR && nPrevType != NRT_NODEREC && nPrevType != NRT_TEXTREC && nPrevType != NRT_NAMEREC && nPrevType != NRT_COLLECT && nPrevType != NRT_POLYGON && nPrevType != NRT_CPOLY && nPrevType != NRT_POINTREC && nPrevType != NRT_LINEREC ); nPrevId = 0; } else { poAnchor = (apapoRecordIndex[nPrevType])[nPrevId]; } } if( poAnchor == NULL ) { return NULL; } /* -------------------------------------------------------------------- */ /* Build record group depending on type of anchor and what it */ /* refers to. */ /* -------------------------------------------------------------------- */ apoCGroup[0] = NULL; apoCGroup[1] = poAnchor; apoCGroup[2] = NULL; /* -------------------------------------------------------------------- */ /* Handle POINTREC/LINEREC */ /* -------------------------------------------------------------------- */ if( poAnchor->GetType() == NRT_POINTREC || poAnchor->GetType() == NRT_LINEREC ) { int l_nAttCount = 0; AddToIndexGroup( GetIndexedRecord( NRT_GEOMETRY, atoi(poAnchor->GetField(9,14)) ) ); if( poAnchor->GetLength() >= 16 ) l_nAttCount = atoi(poAnchor->GetField(15,16)); for( int iAtt = 0; iAtt < l_nAttCount; iAtt++ ) { AddToIndexGroup( GetIndexedRecord( NRT_ATTREC, atoi(poAnchor->GetField(17+6*iAtt, 22+6*iAtt)) ) ); } } /* -------------------------------------------------------------------- */ /* Handle TEXTREC */ /* -------------------------------------------------------------------- */ else if( poAnchor->GetType() == NRT_TEXTREC ) { int l_nAttCount = 0; int nSelCount = 0; // Add all the text position records. nSelCount = atoi(poAnchor->GetField(9,10)); if( nSelCount < 0 ) return NULL; for( int iSel = 0; iSel < nSelCount; iSel++ ) { int iStart = 11 + 12*iSel + 6; AddToIndexGroup( GetIndexedRecord( NRT_TEXTPOS, atoi(poAnchor->GetField(iStart,iStart+5)) )); } // Add all geometry and TEXR records pointed to by text position // records. for( int iRec = 1; apoCGroup[iRec] != NULL; iRec++ ) { NTFRecord *poRecord = apoCGroup[iRec]; if( poRecord->GetType() != NRT_TEXTPOS ) continue; const int nNumTEXR = atoi(poRecord->GetField(9,10)); for( int iTEXR = 0; iTEXR < nNumTEXR; iTEXR++ ) { AddToIndexGroup( GetIndexedRecord( NRT_TEXTREP, atoi(poRecord->GetField(11+iTEXR*12, 16+iTEXR*12)))); AddToIndexGroup( GetIndexedRecord( NRT_GEOMETRY, atoi(poRecord->GetField(17+iTEXR*12, 22+iTEXR*12)))); } } // Add all the attribute records. if( poAnchor->GetLength() >= 10 + nSelCount*12 + 2 ) l_nAttCount = atoi(poAnchor->GetField(11+nSelCount*12, 12+nSelCount*12)); for( int iAtt = 0; iAtt < l_nAttCount; iAtt++ ) { int iStart = 13 + nSelCount*12 + 6 * iAtt; AddToIndexGroup( GetIndexedRecord( NRT_ATTREC, atoi(poAnchor->GetField(iStart,iStart+5)) )); } } /* -------------------------------------------------------------------- */ /* Handle NODEREC. */ /* -------------------------------------------------------------------- */ else if( poAnchor->GetType() == NRT_NODEREC ) { AddToIndexGroup( GetIndexedRecord( NRT_GEOMETRY, atoi(poAnchor->GetField(9,14)) ) ); } /* -------------------------------------------------------------------- */ /* Handle COLLECT. */ /* -------------------------------------------------------------------- */ else if( poAnchor->GetType() == NRT_COLLECT ) { const int nParts = atoi(poAnchor->GetField(9,12)); if( nParts < 0 ) return NULL; const int nAttOffset = 13 + nParts * 8; int l_nAttCount = 0; if( poAnchor->GetLength() > nAttOffset + 2 ) l_nAttCount = atoi(poAnchor->GetField(nAttOffset,nAttOffset+1)); for( int iAtt = 0; iAtt < l_nAttCount; iAtt++ ) { const int iStart = nAttOffset + 2 + iAtt * 6; AddToIndexGroup( GetIndexedRecord( NRT_ATTREC, atoi(poAnchor->GetField(iStart,iStart+5)) )); } } /* -------------------------------------------------------------------- */ /* Handle POLYGON */ /* -------------------------------------------------------------------- */ else if( poAnchor->GetType() == NRT_POLYGON ) { AddToIndexGroup( GetIndexedRecord( NRT_CHAIN, atoi(poAnchor->GetField(9,14)) ) ); if( poAnchor->GetLength() >= 20 ) AddToIndexGroup( GetIndexedRecord( NRT_GEOMETRY, atoi(poAnchor->GetField(15,20)) ) ); // Attributes int l_nAttCount = 0; if( poAnchor->GetLength() >= 22 ) l_nAttCount = atoi(poAnchor->GetField(21,22)); for( int iAtt = 0; iAtt < l_nAttCount; iAtt++ ) { AddToIndexGroup( GetIndexedRecord( NRT_ATTREC, atoi(poAnchor->GetField(23+6*iAtt, 28+6*iAtt)) ) ); } } /* -------------------------------------------------------------------- */ /* Handle CPOLY */ /* -------------------------------------------------------------------- */ else if( poAnchor->GetType() == NRT_CPOLY ) { int nPolyCount = atoi(poAnchor->GetField(9,12)); if( nPolyCount < 0 ) return NULL; int nPostPoly = nPolyCount*7 + 12; if( poAnchor->GetLength() >= nPostPoly + 6 ) { int nGeomId = atoi(poAnchor->GetField(nPostPoly+1,nPostPoly+6)); AddToIndexGroup( GetIndexedRecord( NRT_GEOMETRY, nGeomId) ); } if( poAnchor->GetLength() >= nPostPoly + 8 ) { int l_nAttCount = atoi(poAnchor->GetField(nPostPoly+7,nPostPoly+8)); for( int iAtt = 0; iAtt < l_nAttCount; iAtt++ ) { int nAttId = atoi(poAnchor->GetField(nPostPoly+9+iAtt*6, nPostPoly+14+iAtt*6)); AddToIndexGroup( GetIndexedRecord( NRT_ATTREC, nAttId) ); } } } return apoCGroup + 1; } /************************************************************************/ /* OverrideTileName() */ /************************************************************************/ void NTFFileReader::OverrideTileName( const char *pszNewName ) { CPLFree( pszTileName ); pszTileName = CPLStrdup( pszNewName ); } /************************************************************************/ /* CacheAddByGeomId() */ /* */ /* Add a geometry to the geometry cache given it's GEOMID as */ /* the index. */ /************************************************************************/ void NTFFileReader::CacheAddByGeomId( int nGeomId, OGRGeometry *poGeometry ) { if( !bCacheLines ) return; CPLAssert( nGeomId >= 0 ); /* -------------------------------------------------------------------- */ /* Grow the cache if it isn't large enough to hold the newly */ /* requested geometry id. */ /* -------------------------------------------------------------------- */ if( nGeomId >= nLineCacheSize ) { int nNewSize = nGeomId + 100; papoLineCache = (OGRGeometry **) CPLRealloc( papoLineCache, sizeof(void*) * nNewSize ); memset( papoLineCache + nLineCacheSize, 0, sizeof(void*) * (nNewSize - nLineCacheSize) ); nLineCacheSize = nNewSize; } /* -------------------------------------------------------------------- */ /* Make a cloned copy of the geometry for the cache. */ /* -------------------------------------------------------------------- */ if( papoLineCache[nGeomId] != NULL ) return; papoLineCache[nGeomId] = poGeometry->clone(); } /************************************************************************/ /* CacheGetByGeomId() */ /************************************************************************/ OGRGeometry *NTFFileReader::CacheGetByGeomId( int nGeomId ) { if( nGeomId < 0 || nGeomId >= nLineCacheSize ) return NULL; else return papoLineCache[nGeomId]; } /************************************************************************/ /* CacheClean() */ /************************************************************************/ void NTFFileReader::CacheClean() { for( int i = 0; i < nLineCacheSize; i++ ) { if( papoLineCache[i] != NULL ) delete papoLineCache[i]; } if( papoLineCache != NULL ) CPLFree( papoLineCache ); nLineCacheSize = 0; papoLineCache = NULL; } /************************************************************************/ /* CacheLineGeometryInGroup() */ /* */ /* Run any line geometries in this group through the */ /* ProcessGeometry() call just to ensure the line geometry will */ /* be cached. */ /************************************************************************/ void NTFFileReader::CacheLineGeometryInGroup( NTFRecord **papoGroup ) { if( !bCacheLines ) return; for( int iRec = 0; papoGroup[iRec] != NULL; iRec++ ) { if( papoGroup[iRec]->GetType() == NRT_GEOMETRY || papoGroup[iRec]->GetType() == NRT_GEOMETRY3D ) { OGRGeometry *poGeom = ProcessGeometry( papoGroup[iRec], NULL ); if( poGeom != NULL ) delete poGeom; } } } /************************************************************************/ /* FormPolygonFromCache() */ /* */ /* This method will attempt to find the line geometries */ /* referenced by the GEOM_ID_OF_LINK ids of a feature in the */ /* line cache (if available), and if so, assemble them into a */ /* polygon. */ /************************************************************************/ int NTFFileReader::FormPolygonFromCache( OGRFeature * poFeature ) { if( !bCacheLines ) return FALSE; /* -------------------------------------------------------------------- */ /* Collect all the linked lines. */ /* -------------------------------------------------------------------- */ int nLinkCount = 0; const int *panLinks = poFeature->GetFieldAsIntegerList( "GEOM_ID_OF_LINK", &nLinkCount ); if( panLinks == NULL ) return FALSE; OGRGeometryCollection oLines; for( int i = 0; i < nLinkCount; i++ ) { OGRGeometry *poLine = CacheGetByGeomId( panLinks[i] ); if( poLine == NULL ) { oLines.removeGeometry( -1, FALSE ); return FALSE; } oLines.addGeometryDirectly( poLine ); } /* -------------------------------------------------------------------- */ /* Assemble into a polygon geometry. */ /* -------------------------------------------------------------------- */ OGRPolygon *poPoly = (OGRPolygon *) OGRBuildPolygonFromEdges( (OGRGeometryH) &oLines, FALSE, FALSE, 0.1, NULL ); poFeature->SetGeometryDirectly( poPoly ); oLines.removeGeometry( -1, FALSE ); return poPoly != NULL; }