X-Git-Url: http://git.salome-platform.org/gitweb/?a=blobdiff_plain;f=src%2FHYDROData%2FHYDROData_ShapeFile.cxx;h=357ceedd567be540bd9e3d841d982ea8bab8f91f;hb=9c947f35615e69e9e54a8c4b074dd1f2be13689c;hp=5dfc724a7426b80a81cd8822d3372e94520a71b9;hpb=5f19fc7af832224fdb587fbcfe0eed4c41d0d6aa;p=modules%2Fhydro.git diff --git a/src/HYDROData/HYDROData_ShapeFile.cxx b/src/HYDROData/HYDROData_ShapeFile.cxx index 5dfc724a..357ceedd 100644 --- a/src/HYDROData/HYDROData_ShapeFile.cxx +++ b/src/HYDROData/HYDROData_ShapeFile.cxx @@ -24,8 +24,8 @@ #include #include #include -#include #include +#include #include #include @@ -48,6 +48,12 @@ #include #include #include +#include +#include + +#ifdef WIN32 + #pragma warning( disable: 4996 ) +#endif HYDROData_ShapeFile::HYDROData_ShapeFile() : myHSHP(NULL) { @@ -61,9 +67,10 @@ HYDROData_ShapeFile::~HYDROData_ShapeFile() void HYDROData_ShapeFile::Export(const QString& aFileName, NCollection_Sequence aPolyXYSeq, NCollection_Sequence aPoly3DSeq, - NCollection_Sequence aLCSeq, + const Handle_HYDROData_LandCoverMap& aLCSeq, QStringList& aNonExpList) { + /*TODO SHPHandle hSHPHandle; if (!aPolyXYSeq.IsEmpty() && aPoly3DSeq.IsEmpty()) { @@ -87,8 +94,15 @@ void HYDROData_ShapeFile::Export(const QString& aFileName, if (WriteObjectLC(hSHPHandle, aLCSeq(i)) != 1) aNonExpList.append(aLCSeq(i)->GetName()); } - SHPClose( hSHPHandle ); - + if (hSHPHandle->nRecords > 0) + SHPClose( hSHPHandle ); + else + { + SHPClose( hSHPHandle ); + QString aFN = aFileName.simplified(); + remove (aFN.toStdString().c_str()); + remove (aFN.replace( ".shp", ".shx", Qt::CaseInsensitive).toStdString().c_str()); + }*/ } int HYDROData_ShapeFile::WriteObjectPolyXY(SHPHandle theShpHandle, Handle_HYDROData_PolylineXY thePoly ) @@ -158,7 +172,7 @@ int HYDROData_ShapeFile::WriteObjectPoly3D(SHPHandle theShpHandle, Handle_HYDROD return 1; } -int HYDROData_ShapeFile::WriteObjectLC(SHPHandle theShpHandle, Handle_HYDROData_LandCover theLC ) +/*TODO:int HYDROData_ShapeFile::WriteObjectLC(SHPHandle theShpHandle, Handle_HYDROData_LandCover theLC ) { TopoDS_Shape aSh = theLC->GetShape(); if (aSh.IsNull()) @@ -205,7 +219,7 @@ int HYDROData_ShapeFile::WriteObjectLC(SHPHandle theShpHandle, Handle_HYDROData_ return 1; } - +*/ void HYDROData_ShapeFile::ProcessFace(TopoDS_Face theFace, SHPHandle theShpHandle) { SHPObject *aSHPObj; @@ -258,29 +272,34 @@ void HYDROData_ShapeFile::ProcessFace(TopoDS_Face theFace, SHPHandle theShpHandl return; } -void HYDROData_ShapeFile::Parse(SHPHandle theHandle) +bool HYDROData_ShapeFile::Parse(SHPHandle theHandle, ShapeType theType, int& theShapeTypeOfFile) { int aShapeType; mySHPObjects.clear(); SHPGetInfo( theHandle, NULL, &aShapeType, NULL, NULL ); - if (aShapeType == 5) + theShapeTypeOfFile = aShapeType; + bool ToRead = (theType == ShapeType_Polyline && (aShapeType == 3 || aShapeType == 13 || aShapeType == 23)) || + (theType == ShapeType_Polygon && aShapeType == 5); + if (ToRead) { for (int i = 0; i < theHandle->nRecords; i++) mySHPObjects.push_back(SHPReadObject(theHandle, i)); + return true; } + else + return false; } -void HYDROData_ShapeFile::ProcessSHP(SHPObject* anObj, int i, TopoDS_Face& F) +void HYDROData_ShapeFile::ReadSHPPolygon(SHPObject* anObj, int i, TopoDS_Face& F) { TopoDS_Wire W; TopoDS_Edge E; int nParts = anObj->nParts; gp_Pln pln(gp_Pnt(0,0,0), gp_Dir(0,0,1)); - BRepBuilderAPI_MakeFace aFBuilder(pln); //Handle(ShapeFix_Shape) sfs = new ShapeFix_Shape; //sfs->FixFaceTool()->FixOrientationMode() = 1; - + TopTools_SequenceOfShape aWires; for ( int i = 0 ; i < nParts ; i++ ) { BRepBuilderAPI_MakeWire aBuilder; @@ -303,14 +322,24 @@ void HYDROData_ShapeFile::ProcessSHP(SHPObject* anObj, int i, TopoDS_Face& F) aBuilder.Build(); W = TopoDS::Wire(aBuilder.Shape()); - W.Reverse(); - aFBuilder.Add(W); + W.Orientation(TopAbs_FORWARD); + BRepBuilderAPI_MakeFace aDB(pln, W); + TopoDS_Face aDummyFace = TopoDS::Face(aDB.Shape()); + BRepTopAdaptor_FClass2d FClass(aDummyFace, Precision::PConfusion()); + if ( i == 0 && FClass.PerformInfinitePoint() == TopAbs_OUT) + W.Reverse(); + if ( i > 0 && FClass.PerformInfinitePoint() != TopAbs_IN) + W.Reverse(); + + aWires.Append(W); } + + BRepBuilderAPI_MakeFace aFBuilder(pln, TopoDS::Wire(aWires(1))); + for (int i = 2; i <= aWires.Length(); i++) + aFBuilder.Add(TopoDS::Wire(aWires(i))); + TopoDS_Face DF = TopoDS::Face(aFBuilder.Shape()); - aFBuilder.Build(); - TopoDS_Face DF = aFBuilder.Face(); BRepLib::BuildCurves3d(DF); - bool IsInf = DF.Infinite(); if(!DF.IsNull()) { //sfs->Init ( DF ); @@ -319,26 +348,30 @@ void HYDROData_ShapeFile::ProcessSHP(SHPObject* anObj, int i, TopoDS_Face& F) } } -bool HYDROData_ShapeFile::ImportLandCovers(const QString theFileName, QStringList& thePolygonsList, TopTools_SequenceOfShape& theFaces) +int HYDROData_ShapeFile::ImportLandCovers(const QString theFileName, QStringList& thePolygonsList, TopTools_SequenceOfShape& theFaces, int& theShapeTypeOfFile) { Free(); + int Stat = TryOpenShapeFile(theFileName); + if (Stat != 0) + return Stat; myHSHP = SHPOpen( theFileName.toAscii().data(), "rb" ); - Parse(myHSHP); - for (int i = 0; i < mySHPObjects.size(); i++) + if (!Parse(myHSHP, HYDROData_ShapeFile::ShapeType_Polygon, theShapeTypeOfFile)) + return 0; + for (size_t i = 0; i < mySHPObjects.size(); i++) thePolygonsList.append("polygon_" + QString::number(i + 1)); TopoDS_Face aF; if (myHSHP->nShapeType == 5) { - for (int i = 0; i < mySHPObjects.size(); i++) + for (size_t i = 0; i < mySHPObjects.size(); i++) { - ProcessSHP(mySHPObjects[i], i, aF); + ReadSHPPolygon(mySHPObjects[i], i, aF); theFaces.Append(aF); } - return true; + return 1; } else - return false; + return 0; } void HYDROData_ShapeFile::Free() @@ -352,4 +385,526 @@ void HYDROData_ShapeFile::Free() SHPClose(myHSHP); myHSHP = NULL; } +} + + +void HYDROData_ShapeFile::ReadSHPPolyXY(Handle(HYDROData_Document) theDocument, SHPObject* anObj, QString theFileName, + int theInd, NCollection_Sequence& theEntities) +{ + + Handle(HYDROData_PolylineXY) aPolylineXY = Handle(HYDROData_PolylineXY)::DownCast( theDocument->CreateObject( KIND_POLYLINEXY ) ); + + int nParts = anObj->nParts; + for ( int i = 0 ; i < nParts ; i++ ) + { + int StartIndex = anObj->panPartStart[i]; + int EndIndex; + if (i != nParts - 1) + EndIndex = anObj->panPartStart[i + 1]; + else + EndIndex = anObj->nVertices; + + bool IsClosed = false; + HYDROData_PolylineXY::SectionType aSectType = HYDROData_PolylineXY::SECTION_POLYLINE; + if (anObj->padfX[StartIndex] == anObj->padfX[EndIndex - 1] && + anObj->padfY[StartIndex] == anObj->padfY[EndIndex - 1] ) + { + IsClosed = true; + aPolylineXY->AddSection( TCollection_AsciiString( ("poly_section_" + QString::number(i)).data()->toAscii()), aSectType, true); + } + else + aPolylineXY->AddSection( TCollection_AsciiString( ("poly_section_" + QString::number(i)).data()->toAscii()), aSectType, false); + + if (IsClosed) + EndIndex--; + for ( int k = StartIndex; k < EndIndex ; k++ ) + { + HYDROData_PolylineXY::Point aSectPoint; + aSectPoint.SetX( anObj->padfX[k] ); + aSectPoint.SetY( anObj->padfY[k] ); + aPolylineXY->AddPoint( i, aSectPoint ); + } + + } + + aPolylineXY->SetWireColor( HYDROData_PolylineXY::DefaultWireColor() ); + aPolylineXY->SetName( theFileName + "_PolyXY_" + QString::number(theInd) ); + + aPolylineXY->Update(); + theEntities.Append(aPolylineXY); + +} + +void HYDROData_ShapeFile::ReadSHPPoly3D(Handle(HYDROData_Document) theDocument, SHPObject* anObj, QString theFileName, + int theInd, NCollection_Sequence& theEntities) +{ + Handle(HYDROData_PolylineXY) aPolylineXY = Handle(HYDROData_PolylineXY)::DownCast( theDocument->CreateObject( KIND_POLYLINEXY ) ); + + Handle(HYDROData_Polyline3D) aPolylineObj = Handle(HYDROData_Polyline3D)::DownCast( theDocument->CreateObject( KIND_POLYLINE ) ); + + Handle(HYDROData_Bathymetry) aBath = Handle(HYDROData_Bathymetry)::DownCast( theDocument->CreateObject( KIND_BATHYMETRY ) ); + HYDROData_Bathymetry::AltitudePoints aAPoints; + + int nParts = anObj->nParts; + for ( int i = 0 ; i < nParts ; i++ ) + { + //bool aSectClosure = true; + int StartIndex = anObj->panPartStart[i]; + int EndIndex; + if (i != nParts - 1) + EndIndex = anObj->panPartStart[i + 1]; + else + EndIndex = anObj->nVertices; + + bool IsClosed = false; + HYDROData_PolylineXY::SectionType aSectType = HYDROData_PolylineXY::SECTION_POLYLINE; + if (anObj->padfX[StartIndex] == anObj->padfX[EndIndex - 1] && + anObj->padfY[StartIndex] == anObj->padfY[EndIndex - 1] && + anObj->padfZ[StartIndex] == anObj->padfZ[EndIndex - 1]) + { + IsClosed = true; + aPolylineXY->AddSection( TCollection_AsciiString( ("poly_section_" + QString::number(i)).data()->toAscii()), aSectType, true ); + } + else + aPolylineXY->AddSection( TCollection_AsciiString( ("poly_section_" + QString::number(i)).data()->toAscii()), aSectType, false ); + + if (IsClosed) + EndIndex--; + for ( int k = StartIndex ; k < EndIndex ; k++ ) + { + HYDROData_PolylineXY::Point aSectPoint; + aSectPoint.SetX( anObj->padfX[k] ); + aSectPoint.SetY( anObj->padfY[k] ); + aPolylineXY->AddPoint( i, aSectPoint ); + aAPoints.Append(gp_XYZ (anObj->padfX[k], anObj->padfY[k], anObj->padfZ[k])); + } + } + + + QString aBathName = theFileName + "_bath_" + QString::number(theInd); + QString aPolyXYName = theFileName + "_polyXY_" + QString::number(theInd); + QString aPoly3DName = theFileName + "_poly3D_" + QString::number(theInd); + + aPolylineXY->SetName( aPolyXYName ); + aPolylineXY->SetWireColor(HYDROData_PolylineXY::DefaultWireColor()); + aPolylineXY->Update(); + + aBath->SetAltitudePoints(aAPoints); + aBath->SetName( aBathName ); + + aPolylineObj->SetPolylineXY (aPolylineXY, false); + aPolylineObj->SetAltitudeObject(aBath); + + aPolylineObj->SetBorderColor( aPolylineObj->DefaultBorderColor() ); + aPolylineObj->SetName( aPoly3DName ); + + aPolylineObj->Update(); + theEntities.Append(aPolylineXY); + theEntities.Append(aPolylineObj); + +} + + + +int HYDROData_ShapeFile::ImportPolylines(Handle(HYDROData_Document) theDocument, const QString& theFileName, + NCollection_Sequence& theEntities, int& theShapeTypeOfFile) +{ + //Free(); + int aStat = TryOpenShapeFile(theFileName); + if (aStat != 0) + return aStat; + + HYDROData_Iterator anIter( theDocument ); + int anInd = 0; + QStringList anExistingNames; + std::vector anAllowedIndexes; + for( ; anIter.More(); anIter.Next() ) + anExistingNames.push_back(anIter.Current()->GetName()); + + SHPHandle aHSHP; + aHSHP = SHPOpen( theFileName.toAscii().data(), "rb" ); + + QFileInfo aFileInfo(theFileName); + QString aBaseFileName = aFileInfo.baseName(); + + if (!Parse(aHSHP, HYDROData_ShapeFile::ShapeType_Polyline, theShapeTypeOfFile)) + return 0; + if (aHSHP->nShapeType == 3 || aHSHP->nShapeType == 23) + { + anInd = 0; + for (;anAllowedIndexes.size() < mySHPObjects.size();) + { + if (!anExistingNames.contains(aBaseFileName + "_PolyXY_" + QString::number(anInd))) + { + anAllowedIndexes.push_back(anInd); + anInd++; + } + else + anInd++; + } + + for (size_t i = 0; i < mySHPObjects.size(); i++ ) + { + ReadSHPPolyXY(theDocument, mySHPObjects[i], aBaseFileName, anAllowedIndexes[i], theEntities); + } + aStat = 1; + } + else if (aHSHP->nShapeType == 13) + { + anInd = 0; + for (;anAllowedIndexes.size() < mySHPObjects.size();) + { + if (!anExistingNames.contains(aBaseFileName + "_PolyXY_" + QString::number(anInd)) && + !anExistingNames.contains(aBaseFileName + "_Poly3D_" + QString::number(anInd)) && + !anExistingNames.contains(aBaseFileName + "_Bath_" + QString::number(anInd))) + { + anAllowedIndexes.push_back(anInd); + anInd++; + } + else + anInd++; + } + for (size_t i = 0; i < mySHPObjects.size(); i++ ) + ReadSHPPoly3D(theDocument, mySHPObjects[i], aBaseFileName, anAllowedIndexes[i], theEntities); + aStat = 1; + } + else + { + aStat = 0; + } + + for (size_t i = 0; i < mySHPObjects.size(); i++ ) + free (mySHPObjects[i]); + + mySHPObjects.clear(); + SHPClose(aHSHP); + return aStat; +} + +QString HYDROData_ShapeFile::GetShapeTypeName(int theType) +{ + switch (theType) + { + case 0: + return "null shape"; + case 1: + return "point (unsupported by HYDRO)"; + case 3: + return "arc/polyline (supported by HYDRO)"; + case 5: + return "polygon (supported by HYDRO)"; + case 8: + return "multipoint (unsupported by HYDRO)"; + case 11: + return "pointZ (unsupported by HYDRO)"; + case 13: + return "arcZ/polyline (supported by HYDRO)"; + case 15: + return "polygonZ (unsupported by HYDRO)"; + case 18: + return "multipointZ (unsupported by HYDRO)"; + case 21: + return "pointM (unsupported by HYDRO)"; + case 23: + return "arcM/polyline (supported by HYDRO)"; + case 25: + return "polygonM (unsupported by HYDRO)"; + case 28: + return "multipointM (unsupported by HYDRO)"; + case 31: + return "multipatch (unsupported by HYDRO)"; + default: + return "unknown"; + } +} + +int HYDROData_ShapeFile::TryOpenShapeFile(QString theFileName) +{ + QString aSHPfile = theFileName.simplified(); + QString aSHXfile = theFileName.simplified().replace( ".shp", ".shx", Qt::CaseInsensitive); + FILE* pFileSHP = NULL; + pFileSHP = fopen (aSHPfile.toAscii().data(), "r"); + FILE* pFileSHX = NULL; + pFileSHX = fopen (aSHXfile.toAscii().data(), "r"); + + if (pFileSHP == NULL || pFileSHX == NULL) + { + if (pFileSHP == NULL) + return -1; + if (pFileSHX == NULL) + return -2; + } + + fclose (pFileSHP); + fclose (pFileSHX); + return 0; +} + + +bool HYDROData_ShapeFile::CheckDBFFileExisting(const QString& theSHPFilePath, QString& thePathToDBFFile) +{ + QString aSHPfile = theSHPFilePath.simplified(); + QString aDBFfile = theSHPFilePath.simplified().replace( ".shp", ".dbf", Qt::CaseInsensitive); + FILE* pFileDBF = NULL; + pFileDBF = fopen (aDBFfile.toAscii().data(), "r"); + + if (pFileDBF == NULL) + { + return false; + } + + fclose (pFileDBF); + thePathToDBFFile = aDBFfile; + return true; +} + + +bool HYDROData_ShapeFile::DBF_OpenDBF(const QString& thePathToDBFFile) +{ + myHDBF = DBFOpen( thePathToDBFFile.toAscii().data(), "r" ); + if(myHDBF != NULL) + return true; + else + return false; +} + +int HYDROData_ShapeFile::DBF_GetNbFields() +{ + if(myHDBF == NULL) + return 0; + return DBFGetFieldCount(myHDBF); +} + +void HYDROData_ShapeFile::DBF_CloseDBF() +{ + if(myHDBF != NULL) + DBFClose( myHDBF ); +} + +QStringList HYDROData_ShapeFile::DBF_GetFieldList() +{ + QStringList FieldList; + int nWidth, nDecimals; + char chField[12]; + + for( int i = 0; i < DBFGetFieldCount(myHDBF); i++ ) + { + DBFFieldType eType; + eType = DBFGetFieldInfo( myHDBF, i, chField, &nWidth, &nDecimals ); + FieldList.append(QString(chField)); + } + + return FieldList; +} + +void HYDROData_ShapeFile::DBF_GetFieldTypeList(std::vector& FTVect) +{ + int nWidth, nDecimals; + char chField[12]; + DBF_FieldType FT; + for( int i = 0; i < DBFGetFieldCount(myHDBF); i++ ) + { + DBFFieldType eType; + eType = DBFGetFieldInfo( myHDBF, i, chField, &nWidth, &nDecimals ); + if( eType == FTString ) + FT = DBF_FieldType_String; + else if( eType == FTInteger ) + FT = DBF_FieldType_Integer; + else if( eType == FTDouble ) + FT = DBF_FieldType_Double; + else if( eType == FTInvalid ) + FT = DBF_FieldType_Invalid; + + FTVect.push_back(FT); + } + +} + +int HYDROData_ShapeFile::DBF_GetNbRecords() +{ + if(myHDBF == NULL) + return 0; + return DBFGetRecordCount(myHDBF); +} + +void HYDROData_ShapeFile::DBF_GetAttributeList(int theIndexOfField, std::vector& theAttrV) +{ + int nWidth, nDecimals; + char chField[12]; + + for( int i = 0; i < DBFGetRecordCount(myHDBF); i++ ) + { + DBFFieldType eType; + DBF_AttrValue anAttr; + eType = DBFGetFieldInfo( myHDBF, theIndexOfField, chField, &nWidth, &nDecimals ); + + if( DBFIsAttributeNULL( myHDBF, i, theIndexOfField ) ) + { + anAttr.myIsNull = true; + DBF_FieldType FT = DBF_FieldType_None; + if( eType == FTString ) + FT = DBF_FieldType_String; + else if( eType == FTInteger ) + FT = DBF_FieldType_Integer; + else if( eType == FTDouble ) + FT = DBF_FieldType_Double; + else if( eType == FTInvalid ) + FT = DBF_FieldType_Invalid; + anAttr.myFieldType = FT; + } + else + { + switch( eType ) + { + case FTString: + { + const char* chAttr = DBFReadStringAttribute( myHDBF, i, theIndexOfField ); + anAttr.myIsNull = false; + anAttr.myFieldType = DBF_FieldType_String; + anAttr.myRawValue = chAttr; + anAttr.myStrVal = QString(chAttr); + break; + } + + case FTInteger: + { + int iAttr = DBFReadIntegerAttribute( myHDBF, i, theIndexOfField ); + anAttr.myIsNull = false; + anAttr.myFieldType = DBF_FieldType_Integer; + anAttr.myRawValue = DBFReadStringAttribute( myHDBF, i, theIndexOfField ); + anAttr.myIntVal = iAttr; + break; + } + + case FTDouble: + { + double dAttr = DBFReadDoubleAttribute( myHDBF, i, theIndexOfField ); + anAttr.myIsNull = false; + anAttr.myFieldType = DBF_FieldType_Double; + anAttr.myRawValue = DBFReadStringAttribute( myHDBF, i, theIndexOfField ); + anAttr.myDoubleVal = dAttr; + break; + } + default: + break; + } + } + theAttrV.push_back(anAttr); + } + +} + +bool HYDROData_ShapeFile::DBF_WriteFieldAndValues(const QString& theFileName, const QString& theFieldName, DBF_FieldType theType, const std::vector& theAttrV, bool bUseRawValue) +{ + // Check that given field type is equal to field types of attributes values + for (size_t i = 0; i < theAttrV.size(); i++) + { + if (theAttrV[i].myFieldType != theType) + return false; + } + + DBFHandle hDBF; + hDBF = DBFCreate( theFileName.toStdString().c_str() ); + if( hDBF == NULL ) + return false; + + if (theType != DBF_FieldType_String && theType != DBF_FieldType_Integer && theType != DBF_FieldType_Double) + { + DBFClose( hDBF ); + return false; //cant handle any other cases + } + + int nWidth = 20; + switch( theType ) + { + case DBF_FieldType_String: + { + DBFAddField (hDBF, theFieldName.toStdString().c_str(), FTString, nWidth, 0); + break; + } + + case DBF_FieldType_Integer: + { + DBFAddField (hDBF, theFieldName.toStdString().c_str(), FTInteger, nWidth, 0); + break; + } + + case DBF_FieldType_Double: + { + DBFAddField (hDBF, theFieldName.toStdString().c_str(), FTDouble, nWidth, 0); + break; + } + default: + break; + } + + if (DBFGetFieldCount( hDBF ) != 1) + { + DBFClose( hDBF ); + return false; + } + if (DBFGetRecordCount( hDBF ) != 0) + { + DBFClose( hDBF ); + return false; + } + int stat = -1; + + if (bUseRawValue) + { + for (size_t i = 0; i < theAttrV.size(); i++) + { + if (!theAttrV[i].myIsNull) + stat = DBFWriteStringAttribute(hDBF, (int)i, 0, theAttrV[i].myRawValue.c_str()); + else + stat = DBFWriteNULLAttribute(hDBF, (int)i, 0 ); + + if (stat != 1) + { + DBFClose( hDBF ); + return false; + } + } + } + else + { + for (size_t i = 0; i < theAttrV.size(); i++) + { + if (!theAttrV[i].myIsNull) + switch( theType ) + { + case DBF_FieldType_String: + { + stat = DBFWriteStringAttribute(hDBF, (int)i, 0, theAttrV[i].myStrVal.toStdString().c_str()); + break; + } + + case DBF_FieldType_Integer: + { + stat = DBFWriteIntegerAttribute(hDBF, (int)i, 0, theAttrV[i].myIntVal); + break; + } + + case DBF_FieldType_Double: + { + stat = DBFWriteDoubleAttribute(hDBF, (int)i, 0, theAttrV[i].myDoubleVal); + break; + } + default: + break; + } + else + stat = DBFWriteNULLAttribute(hDBF, (int)i, 0 ); + + if (stat != 1) + { + DBFClose( hDBF ); + return false; + } + } + } + + DBFClose( hDBF ); + return true; + } \ No newline at end of file