shp文件的读取

转载自:http://blog.csdn.net/gisfarmer/article/details/3861554
做GIS开发的朋友可能对shp并不陌生,但是看到CSDN网友不断提问关于shp文件的一些问题,利用闲暇我对shp文件的一些知识加以总结,共享CSDN网友。

首先了解一下shp文件的一些简单知识

Shapefile文件是美国环境系统研究所(ESRI)所研制的GIS文件系统格式文件,是工业标准的矢量数据文件。 Shapefile将空间特征表中的非拓扑几何对象和属性信息存储在数据集中,特征表中的几何对象存为以坐标点集表示的图形文件—SHP文件,Shapefile文件并不含拓扑(Topological)数据结构。一个Shape文件包括三个文件:一个主文件(*.shp),一个索引文件(*.shx),和一个dBASE(*.dbf)表。主文件是一个直接存取,变长度记录的文件,其中每个记录描述构成一个地理特征(Feature)的所有vertices坐标值。在索引文件中,每条记录包含对应主文件记录距离主文件头开始的偏移量,dBASE表包含SHP文件中每一个Feature的特征属性,表中几何记录和属性数据之间的一一对应关系是基于记录数目的ID。在dBASE文件中的属性记录必须和主文件中的记录顺序是相同的。图形数据和属性数据通过索引号建立一一对应的关系。  id="tanx-a-mm_10249644_1605763_5018464" frameborder="0" scrolling="no" marginwidth="0" marginheight="0" src="http://z.alimama.com/alimama.php?i=mm_10249644_1605763_5018464&w=104&h=22&sz=tl_1x1_8&re=1366x768&cah=728&caw=1366&ccd=32&ctz=8&chl=1&cja=1&cpl=20&cmm=48&cf=11.7&cg=57350d4c2c6b3cbd05c96b448120c85c&ac=2698&prp=88436705&cas=prp&cbh=3282&cbw=1349&sx=0&sy=0&refpos=,null,null&t=f&tc=313131&bgc=FFFFFF&bdc=FFFFFF&tlfs=12&pf=1&p4p_ai=1&dx=&iss=0&u=http%3A%2F%2Fblog.csdn.net%2Fgisfarmer%2Farticle%2Fdetails%2F3861554&k=&tt=shp%E6%96%87%E4%BB%B6%E7%9A%84%E8%AF%BB%E5%8F%96%20-%20%E5%AE%9E%E5%AE%9E%E5%9C%A8%E5%9C%A8%20-%20%E5%8D%9A%E5%AE%A2%E9%A2%91%E9%81%93%20-%20CSDN.NET&r=&fu=-1&pageid=5ed453dca2443dc2707b6c03334897f6" style="width: 104px; height: 22px; border-width: 0px;">


Shapefile中坐标文件(.shp)由固定长度的文件头和接着的变长度空间数据记录组成。文件头由100字节的说明信息组成的,主要说明文件的长度、Shape类型、整个Shape图层的范围等等,这些信息构成了空间数据的元数据。在导入空间数据时首先要读入文件头获取Shape文件的基本信息,并以此信息为基础建立相应的元数据表。而变长度空间数据记录是由固定长度的记录头和变长度记录内容组成,其记录结构基本类似,每条记录都有记录头和记录内容组成(空间坐标对)。记录头的内容包括记录号(Record Number)和坐标记录长度(Content Length)两个记录项,Shapefile文件中的记录号都是从1开始的,坐标记录长度是按16位字来衡量的。记录内容包括目标的几何类型(ShapeType)和具体的坐标记录(X,Y),记录内容因要素几何类型的不同,其具体的内容和格式都有所不同。对于具体的记录主要包括空Shape记录,点记录,线记录和多边形记录。


属性文件(.dbf)用于记录属性信息。它是一个标准的DBF文件,也是由头文件和实体信息两部分构成。其中文件头部分的长度是不定长的,它主要对DBF文件作了一些总体说明,其中最主要的是对这个DBF文件的记录项的信息进行了详细的描述,比如对每个记录项的名称,数据类型,长度等信息都有具体的说明。属性文件的实体信息部分就是一条条属性记录,每条记录都是由若干个记录项构成,因此只要依次循环读取每条记录就可以了。


索引文件(.shx)主要包含坐标文件的索引信息,文件中每个记录包含对应的坐标文件记录距离坐标文件的文件头的偏移量。通过索引文件可以很方便地在坐标文件中定位到指定目标地坐标信息。索引文件也是由文件头和实体信息两部分构成的,其中文件头部分是一个长度固定(100 bytes)的记录段,其内容与坐标文件的文件头基本一致。它的实体信息以记录为基本单位,每一条记录包括偏移量(Offset)和记录段长度(Content Length)两个记录项。

接下来我们再看一下代码(c++):
SHP文件的读取

[cpp]  view plain copy
  1. FeatureClass* CGISMapDoc::ImportShapeFileData( FILE* fpShp, FILE* fpDbf )  
  2. {  
  3.     //读Shp文件头开始  
  4.     int fileCode = -1;  
  5.     int fileLength = -1;  
  6.     int version = -1;  
  7.     int shapeType = -1;  
  8.     fread(&fileCode , sizeof(int) , 1 , fpShp) ;  
  9.     fileCode = ReverseBytes(fileCode) ;  
  10.   
  11.     if (fileCode != 9994)  
  12.     {  
  13.         CString strTemp ;  
  14.         strTemp.Format(" WARNING filecode %d ", fileCode );  
  15.         AfxMessageBox(strTemp);  
  16.     }  
  17.   
  18.     forint i = 0 ; i < 5 ; i ++ )  
  19.         fread(&fileCode , sizeof(int) , 1 , fpShp) ;  
  20.   
  21.     fread(&fileLength , sizeof(int) , 1 , fpShp) ;  
  22.     fileLength = ReverseBytes(fileLength) ;  
  23.   
  24.     fread(&version , sizeof(int) , 1 , fpShp) ;  
  25.     fread(&shapeType , sizeof(int) , 1 , fpShp) ;  
  26.   
  27.     double tempOriginX , tempOriginY ;  
  28.     fread( &tempOriginX , sizeof(double) , 1 , fpShp ) ;  
  29.     fread( &tempOriginY , sizeof(double) , 1 , fpShp ) ;  
  30.   
  31.     double xMaxLayer , yMaxLayer ;  
  32.     fread( &xMaxLayer , sizeof(double) , 1 , fpShp ) ;  
  33.     fread( &yMaxLayer , sizeof(double) , 1 , fpShp ) ;  
  34.   
  35.     double* skip = new double[4] ;  
  36.     fread( skip , sizeof(double) , 4 , fpShp ) ;  
  37.     delete []skip ;  
  38.     skip = 0 ;  
  39.     //读Shp文件头结束  
  40.   
  41.     int uniqueID = this->m_pDataSource->GetUniqueID() ;  
  42.     FeatureClass* pShpDataSet = 0 ;  
  43.     //根据目标类型创建相应的图层DataSet。  
  44.     switch( shapeType )  
  45.     {  
  46.       case 1 :  
  47.         pShpDataSet = (FeatureClass*)&(m_pDataSource->CreateDataSet(uniqueID , POINTDATASET , layerName)) ;  
  48.         break ;  
  49.       case 3 :  
  50.       case 23 :  
  51.         pShpDataSet = (FeatureClass*)&(m_pDataSource->CreateDataSet(uniqueID , LINEDATASET , layerName)) ;  
  52.         break ;  
  53.       case 5 :  
  54.         pShpDataSet = (FeatureClass*)&(m_pDataSource->CreateDataSet(uniqueID , POLYGONDATASET , layerName)) ;  
  55.         break ;  
  56.     }  
  57.   
  58.     if ( pShpDataSet == 0 ) return 0;  
  59.   
  60.     // 读DBF文件头---------begin------------  
  61.     struct DBFHeader  
  62.     {  
  63.        char m_nValid;  
  64.        char m_aDate[3];  
  65.        char m_nNumRecords[4];  
  66.        char m_nHeaderBytes[2];  
  67.        char m_nRecordBytes[2];  
  68.        char m_nReserved1[3];  
  69.        char m_nReserved2[13];  
  70.        char m_nReserved3[4];  
  71.     }dbfheader;  
  72.   
  73.     struct DBFFIELDDescriptor  
  74.     {  
  75.        char m_sName[10];//应该为char m_sName[11]  
  76.        char m_nType;  
  77.        char m_nAddress[4];  
  78.        char m_nFieldLength;  
  79.        char m_nFieldDecimal;  
  80.        char m_nReserved1[2];  
  81.        char m_nWorkArea;  
  82.        char m_nReserved2[2];  
  83.        char m_nSetFieldsFlag;  
  84.        char m_nReserved3[8];  
  85.     };  
  86.   
  87.     fread(&dbfheader,sizeof(DBFHeader),1,fpDbf);  
  88.     /*int recordsNum = *((int*)dbfheader.m_nNumRecords); 
  89.     int headLen = *((short*)dbfheader.m_nHeaderBytes); 
  90.     int everyRecordLen = *((short*)dbfheader.m_nRecordBytes); 
  91.  
  92.     if ( recordsNum == 0 ||  headLen == 0 || everyRecordLen == 0 ) 
  93.         return 0 ; 
  94.  
  95.     int fieldCount = (headLen - 1 - sizeof(DBFHeader))/sizeof(DBFFIELDDescriptor); 
  96.  
  97.     DBFFIELDDescriptor *pFields = new DBFFIELDDescriptor[fieldCount]; 
  98.     for ( i = 0; i < fieldCount; i ++ ) 
  99.         fread(&pFields[i],sizeof(DBFFIELDDescriptor),1,fpDbf); 
  100.  
  101.     char endByte; 
  102.     fread(&endByte,sizeof(char),1,fpDbf); 
  103.      
  104.     if ( endByte != 0x0D) 
  105.     { 
  106.         delete []pFields; 
  107.         pFields = 0; 
  108.         return 0; 
  109.     }*/  
  110.   
  111.   
  112.   
  113.     Fields& fields = pShpDataSet->GetFields();  
  114.     DBFFIELDDescriptor field ;  
  115.     BYTE endByte = ' ';  
  116.     char fieldName[12];  
  117.     int fieldDecimal, fieldLen, everyRecordLen = 0 ;  
  118.     while ( !feof(fpDbf) )  
  119.     {  
  120.         fread(&endByte,sizeof(BYTE),1,fpDbf);  
  121.         if ( endByte == 0x0D)   break ;  
  122.         fread(&field,sizeof(DBFFIELDDescriptor),1,fpDbf);  
  123.           
  124.         fieldName[0] = endByte;  
  125.         for ( i = 0; i < 10; i ++ )  
  126.             fieldName[i+1] = field.m_sName[i];  
  127.         fieldName[11] = '/0';  
  128.   
  129.         fieldDecimal = field.m_nFieldDecimal;  
  130.         fieldLen = field.m_nFieldLength;  
  131.         switch( field.m_nType )  
  132.         {  
  133.             case 'C':  
  134.                 fields.AddField(fieldName,fieldName,FIELD_STRING,fieldLen);  
  135.                 break;  
  136.             case 'F':  
  137.                 fields.AddField(fieldName,fieldName,FIELD_DOUBLE,fieldLen);  
  138.                 break;  
  139.             case 'N':  
  140.                 {  
  141.                     if ( fieldDecimal == 0 )   
  142.                         fields.AddField(fieldName,fieldName,FIELD_INT,fieldLen);  
  143.                     else fields.AddField(fieldName,fieldName,FIELD_DOUBLE,fieldLen);  
  144.                 }  
  145.                 break;  
  146.         }  
  147.         everyRecordLen += fieldLen ;  
  148.     }  
  149.     // 读DBF文件头---------end------------  
  150.   
  151.       while( !feof(fpShp) )  
  152.       {  
  153.         //读记录头开始  
  154.         int recordNumber = -1 ;  
  155.         int contentLength = -1 ;  
  156.         fread(&recordNumber , sizeof(int) , 1 , fpShp) ;  
  157.         fread(&contentLength , sizeof(int) , 1 , fpShp) ;  
  158.         recordNumber = ReverseBytes(recordNumber) ;  
  159.         contentLength = ReverseBytes(contentLength) ;  
  160.         //读记录头结束  
  161.   
  162.         switch( shapeType )  
  163.         {  
  164.           case 1: // '/001'  
  165.           //读取点目标开始  
  166.           {  
  167.             Fields &featureFields = pShpDataSet->GetFields();  
  168.             Feature *pFeature = new Feature(recordNumber , 1 , &featureFields) ;  
  169.   
  170.             int pointShapeType ;  
  171.             fread(&pointShapeType , sizeof(int) , 1 , fpShp) ;  
  172.             double xValue , yValue ;  
  173.             fread(&xValue , sizeof(double) , 1 , fpShp) ;  
  174.             fread(&yValue , sizeof(double) , 1 , fpShp) ;  
  175.   
  176.             GeoPoint *pNewGeoPoint = new GeoPoint( xValue , yValue ) ;  
  177.             pFeature->SetBound(xValue , yValue , 0 , 0 ) ;  
  178.             pFeature->SetGeometry(pNewGeoPoint) ;  
  179.             this->LoadAttributeData(pFeature,fpDbf,everyRecordLen);  
  180.             pShpDataSet->AddRow(pFeature) ;  
  181.           }  
  182.           break ;  
  183.           //读取点目标结束  
  184.   
  185.           case 3: // '/003'  
  186.           //读取线目标开始  
  187.           {  
  188.             Fields &featureFields = pShpDataSet->GetFields();  
  189.             Feature *pFeature = new Feature(recordNumber , 1 , &featureFields) ;  
  190.   
  191.             int arcShapeType ;  
  192.             fread(&arcShapeType , sizeof(int) , 1 , fpShp) ;  
  193.   
  194.             double objMinX , objMinY , objMaxX , objMaxY ;  
  195.             fread(&objMinX , sizeof(double) , 1 , fpShp) ;  
  196.             fread(&objMinY , sizeof(double) , 1 , fpShp) ;  
  197.             fread(&objMaxX , sizeof(double) , 1 , fpShp) ;  
  198.             fread(&objMaxY , sizeof(double) , 1 , fpShp) ;  
  199.   
  200.             GeoPolyline *pNewGeoLine = new GeoPolyline();  
  201.             double width = objMaxX - objMinX ;  
  202.             double height = objMaxY - objMinY ;  
  203.             pFeature->SetBound(objMinX , objMinY , width , height) ;  
  204.   
  205.             int numParts , numPoints ;  
  206.             fread(&numParts , sizeof(int) , 1 , fpShp) ;  
  207.             fread(&numPoints , sizeof(int) , 1 , fpShp) ;  
  208.             //存储各段线的起点索引  
  209.             int* startOfPart = new int[numParts] ;  
  210.             forint i = 0 ; i < numParts ; i++ )  
  211.             {  
  212.               int indexFirstPoint ;  
  213.               fread(&indexFirstPoint , sizeof(int) , 1 , fpShp) ;  
  214.               startOfPart[i] = indexFirstPoint ;  
  215.             }  
  216.   
  217.             //处理单个目标有多条线的问题  
  218.             pNewGeoLine->SetPointsCount( numParts ) ;  
  219.   
  220.             for( i = 0 ; i < numParts ; i++ )  
  221.             {  
  222.               GeoPoints& points = pNewGeoLine->GetPoints(i) ;  
  223.               int curPosIndex = startOfPart[i] ;  
  224.               int nextPosIndex = 0 ;  
  225.               int curPointCount = 0 ;  
  226.               if( i == numParts - 1 )  
  227.                 curPointCount = numPoints - curPosIndex ;  
  228.               else  
  229.               {  
  230.                 nextPosIndex = startOfPart[i + 1] ;  
  231.                 curPointCount = nextPosIndex - curPosIndex ;  
  232.               }  
  233.               points.SetPointCount( curPointCount ) ;  
  234.               //加载一条线段的坐标  
  235.               forint iteratorPoint = 0 ; iteratorPoint < curPointCount ; iteratorPoint ++ )  
  236.               {  
  237.                   double x , y ;  
  238.                   fread(&x , sizeof(double) , 1 , fpShp) ;  
  239.                   fread(&y , sizeof(double) , 1 , fpShp) ;  
  240.                 GeoPoint newVertex(x, y);  
  241.                 points.SetPoint(iteratorPoint, newVertex);  
  242.               }  
  243.             }  
  244.             delete []startOfPart ;  
  245.             startOfPart = 0 ;  
  246.             pFeature->SetGeometry(pNewGeoLine) ;  
  247.             this->LoadAttributeData(pFeature,fpDbf,everyRecordLen);  
  248.             pShpDataSet->AddRow(pFeature) ;  
  249.           }  
  250.           break ;  
  251.           //读取线目标结束  
  252.   
  253.           case 5: // '/005'  
  254.           //读取面目标开始  
  255.           {  
  256.             Fields &featureFields = pShpDataSet->GetFields();  
  257.             Feature *pFeature = new Feature(recordNumber , 1 , &featureFields) ;  
  258.             int polygonShapeType ;  
  259.             fread(&polygonShapeType  , sizeof(int) , 1 ,fpShp) ;  
  260.             if( polygonShapeType != 5 )  
  261.                AfxMessageBox( "Error: Attempt to load non polygon shape as polygon." ) ;  
  262.   
  263.             double objMinX , objMinY , objMaxX , objMaxY ;  
  264.             fread(&objMinX , sizeof(double) , 1 , fpShp) ;  
  265.             fread(&objMinY , sizeof(double) , 1 , fpShp) ;  
  266.             fread(&objMaxX , sizeof(double) , 1 , fpShp) ;  
  267.             fread(&objMaxY , sizeof(double) , 1 , fpShp) ;  
  268.   
  269.             GeoPolygon *pNewGeoPolygon = new GeoPolygon();  
  270.             double width = objMaxX - objMinX ;  
  271.             double height = objMaxY - objMinY ;  
  272.             pFeature->SetBound(objMinX , objMinY , width , height) ;  
  273.   
  274.             int numParts , numPoints ;  
  275.             fread(&numParts , sizeof(int) , 1 , fpShp) ;  
  276.             fread(&numPoints , sizeof(int) , 1 , fpShp) ;  
  277.             //存储各个面的起点索引  
  278.             int* startOfPart = new int[numParts] ;  
  279.             forint i = 0 ; i < numParts ; i++ )  
  280.             {  
  281.               int indexFirstPoint ;  
  282.               fread(&indexFirstPoint , sizeof(int) , 1 , fpShp) ;  
  283.               startOfPart[i] = indexFirstPoint ;  
  284.             }  
  285.   
  286.             //处理单个目标有多面问题  
  287.             pNewGeoPolygon->SetPointsCount( numParts ) ;  
  288.   
  289.             for( i = 0 ; i < numParts ; i++ )  
  290.             {  
  291.               GeoPoints& points = pNewGeoPolygon->GetPoints(i) ;  
  292.               int curPosIndex = startOfPart[i] ;  
  293.               int nextPosIndex = 0 ;  
  294.               int curPointCount = 0 ;  
  295.               if( i == numParts - 1 )  
  296.                 curPointCount = numPoints - curPosIndex ;  
  297.               else  
  298.               {  
  299.                 nextPosIndex = startOfPart[i + 1];  
  300.                 curPointCount = nextPosIndex - curPosIndex ;  
  301.               }  
  302.               points.SetPointCount( curPointCount ) ;  
  303.               //加载一个面(多边形)的坐标  
  304.               forint iteratorPoint = 0 ; iteratorPoint < curPointCount ; iteratorPoint ++ )  
  305.               {  
  306.                 double x , y ;  
  307.                 fread(&x , sizeof(double) , 1 , fpShp) ;  
  308.                 fread(&y , sizeof(double) , 1 , fpShp) ;  
  309.                 GeoPoint newVertex(x, y);  
  310.                 points.SetPoint(iteratorPoint, newVertex);  
  311.               }  
  312.             }  
  313.             delete []startOfPart ;  
  314.             startOfPart = 0 ;  
  315.             pFeature->SetGeometry(pNewGeoPolygon) ;  
  316.             this->LoadAttributeData(pFeature,fpDbf,everyRecordLen);  
  317.             pShpDataSet->AddRow(pFeature) ;  
  318.           }  
  319.           break ;  
  320.           //读取面目标结束  
  321.   
  322.           case 23: // '/027'  
  323.           //读取Measure形线目标开始  
  324.           {  
  325.             Fields &featureFields = pShpDataSet->GetFields();  
  326.             Feature *pFeature = new Feature(recordNumber , 1 , &featureFields) ;  
  327.             int arcMShapeType ;  
  328.             fread(&arcMShapeType , sizeof(int) , 1 , fpShp) ;  
  329.   
  330.             double objMinX , objMinY , objMaxX , objMaxY ;  
  331.             fread(&objMinX , sizeof(double) , 1 , fpShp) ;  
  332.             fread(&objMinY , sizeof(double) , 1 , fpShp) ;  
  333.             fread(&objMaxX , sizeof(double) , 1 , fpShp) ;  
  334.             fread(&objMaxY , sizeof(double) , 1 , fpShp) ;  
  335.   
  336.             GeoPolyline *pNewGeoLine = new GeoPolyline();  
  337.             double width = objMaxX - objMinX ;  
  338.             double height = objMaxY - objMinY ;  
  339.             pFeature->SetBound(objMinX , objMinY , width , height) ;  
  340.   
  341.             int numParts , numPoints ;  
  342.             fread(&numParts , sizeof(int) , 1 , fpShp) ;  
  343.             fread(&numPoints , sizeof(int) , 1 , fpShp) ;  
  344.             //存储各段线的起点索引  
  345.             int* startOfPart = new int[numParts] ;  
  346.             forint i = 0 ; i < numParts ; i++ )  
  347.             {  
  348.               int indexFirstPoint ;  
  349.               fread(&indexFirstPoint , sizeof(int) , 1 , fpShp) ;  
  350.               startOfPart[i] = indexFirstPoint ;  
  351.             }  
  352.   
  353.             //处理单个目标有多条线的问题  
  354.             pNewGeoLine->SetPointsCount( numParts ) ;  
  355.   
  356.             for( i = 0 ; i < numParts ; i++ )  
  357.             {  
  358.               GeoPoints& points = pNewGeoLine->GetPoints(i) ;  
  359.               int curPosIndex = startOfPart[i] ;  
  360.               int nextPosIndex = 0 ;  
  361.               int curPointCount = 0 ;  
  362.               if( i == numParts - 1 )  
  363.                 curPointCount = numPoints - curPosIndex ;  
  364.               else  
  365.               {  
  366.                 nextPosIndex = startOfPart[i + 1] ;  
  367.                 curPointCount = nextPosIndex - curPosIndex ;  
  368.               }  
  369.               points.SetPointCount( curPointCount ) ;  
  370.               //加载一条线段的坐标  
  371.               forint iteratorPoint = 0 ; iteratorPoint < curPointCount ; iteratorPoint ++ )  
  372.               {  
  373.                   double x , y ;  
  374.                   fread(&x , sizeof(double) , 1 , fpShp) ;  
  375.                   fread(&y , sizeof(double) , 1 , fpShp) ;  
  376.                 GeoPoint newVertex(x, y);  
  377.                 points.SetPoint(iteratorPoint, newVertex);  
  378.               }  
  379.             }  
  380.             delete []startOfPart ;  
  381.             startOfPart = 0 ;  
  382.   
  383.             double* value = new double[2 + numPoints] ;  
  384.             fread( value , sizeof(double) , 2+numPoints, fpShp) ;  
  385.             delete []value ;  
  386.             value = 0 ;  
  387.   
  388.             pFeature->SetGeometry(pNewGeoLine) ;  
  389.             this->LoadAttributeData(pFeature,fpDbf,everyRecordLen);  
  390.             pShpDataSet->AddRow(pFeature);  
  391.           }  
  392.           break ;  
  393.           //读取Measure形线目标结束  
  394.         }  
  395.       }  
  396.       return pShpDataSet ;  
  397. }  

另:我手头上有一个读取shp文件的一个小程序(C++),如果需要的朋友请留下邮箱。我会第一时间发送给你

转载的朋友请注明出处:半支烟阿杰  http://blog.csnd.net/gisfarmer

  • 10
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 97
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 97
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值