liujiusheng / blog

个人博客,blog
19 stars 0 forks source link

ESRI File Geodatabase (FileGDB)标准规范的翻译 #247

Open liujiusheng opened 2 years ago

liujiusheng commented 2 years ago

本文档是根据FileGDB逆向工程推导出的标准规范,还在不断完善中。

包括:.gdbtable, .gdbtablx, .gdbindexes, .atx, .spx 和 .freelist文件。

除非另有说明,本文档针对的是FileGDB v10及其以下版本。

原文地址:https://github.com/rouault/dump_gdbtable/wiki/FGDB-Spec

在线进制转换工具: https://tool.oschina.net/hexconvert/

在线ASCII码与10进制和字符对比表:https://zhuanlan.zhihu.com/p/408357733?ivk_sa=1024320u

关于字节序:https://www.ruanyifeng.com/blog/2016/11/byte-order.html

shape file格式:https://www.esri.com/content/dam/esrisites/sitecore-archive/Files/Pdfs/library/whitepapers/pdfs/shapefile.pdf

geojson格式:https://www.rfc-editor.org/rfc/rfc7946#section-3.1.7

gdb内要素的组织方式与shp文件有些类似

在做这件事之前你需要对数据类型、进制、编码类型等概念有清析的认识

约定规范:

在本文档中row和feature是同义词。

一个bit是一个0或1,中文叫做一个二进制位。一个byte是8个bit,中文名称叫一个字节

GDB的文件结构

文件命名格式为a[number in lowercase hex].[extension], a00000001 是第一个文件, a00000002是第二个文件,且数字可能被跳过。

FileGDB v10

在 FileGDB v10中,前8个文件 (a00000001 to a00000008) 是固定不变的内置文件,被预留来保存数据库本身的元数据,后续的文件被用来存放实际的要素信息(a00000009, a0000000a, ...)

(议者注:GDB看作一个整体,将整个数据库的文件列表、整个数据库的配置信息、每张数据表的坐标系、每张表的metadata、GDB_ItemRelationships、GDB_ItemRelationshipTypes、GDB_ItemTypes等单独抽离出来存储。从a000000009开始的表才真正存放用户的实际数据,且a000000009之后的所有文件内不存放坐标系和metadata,只存放字段信息和每一行数据。暂时还不清楚GDB_ItemRelationships、GDB_ItemRelationshipTypes、GDB_ItemTypes到底是什么,可能是拓扑检查需要的东西)

例如,FID 37的记录(这里采用的FID编号惯例是从1开始的)将在文件a00000025中(译者注:10进制的37用16进制表示为25)。在这个目录表中可能有被删除的行,因此在FID编号中存在空白。

在.a00000001.gdbtable中暂时没找到FID这一个属性列,里面的ID列实际类型是objectid,所以不能直接拿来对比使用。

这个表里还包含NameFileFormat字段。FileFormat字段的值多数时候是0,在少部分内置保留表中是2。

所有行都是唯一的,所以如果有3个Feature类,它们都具有相同的坐标系,但其中一个具有不同的ZTolerance,那么就会有两行记录。

  1. UUID (UUID) : UUID
  2. Type (UUID) : item type
  3. Name (string) : item/layer name. Matches the Name field of the GDB_SystemCatalog
  4. PhysicalName (string) : item/layer name in upper case characters.
  5. Path (string) : "\mylayername" for top-level layers or "\myfeaturedataset\mylayername" for layers attached to a feature dataset "myfeaturedataset"
  6. DatasetSubType1 (int32) : 1 for user tables (TBC)
  7. DatasetSubType2 (int32) : layer geometry type. 1 for point layer, 2 for multipoint layers, 3 for linestring layers, 4 for polygon layers
  8. DatasetInfo1 (string) : "SHAPE" for user tables (TBC)
  9. DatasetInfo2 (string) : NULL for user tables (TBC)
  10. URL (string) : empty string (TBC)
  11. Definition (XML) : DEFeatureClassInfo XML element. Contains an XML version of the information that can be obtained by parsing the header of a table : fields, SRS, ...
  12. Documentation (XML) : metadata XML element
  13. ItemInfo (XML) : NULL for user tables (TBC)
  14. Properties (int32) : 1 for user tables (TBC)
  15. Defaults (binary) : absent for user tables (TBC)
  16. Shape (geometry) : 5 point polygon listing the corner of the bounding box of the layer reprojected into EPSG:4326 (even if the layer SRS is not EPSG:4326). Or missing if the layer SRS is undefined.

一些特殊记录:

  1. The first record is reserved for a kind of root item ( Name = "", Path = "" ).
  2. The second record is reserved for a Name = "Workspace" item, Path = "", Definition containing a DEWorkspace XML element
  3. When there are feature datatesets, they also appear as records : e.g. Name = "featuredataset", PhysicalName = "FEATUREDATASET", Path = "\FEATUREDATASET", Definition containing a DEFeatureDataset XML element

FileGDBv9

暂时不想写......

.gdbtable文件规范

.gdbtable文件描述字段并包含行数据。

包括header、field、row三部分内容。

Header (40 bytes)


Field 部分


固定部分

重复部分(每一个field都有)

紧接着是:字段的描述(重复次数与字段的数量相同)

字段说明的下一个字节取决于字段类型

field type = 4 (string),

field type = 6 (objectid),

If geometry has z values (bit 31 of layer geometry type flags):

If geometry has m values (bit 30 of layer geometry type flags):

Then, values relating to the spatial index for the field:

field type = 8 (binary),

field type = 9 (raster),

field type = 10, 11 (UUID)

field type = 12

其它field types,

如果标志字段的lsb(当存在时)设置为1,那么记录中该字段可以为空

Rows


行部分不一定紧跟着最后一个字段说明,它通常在几个字节之后开始,但不是以一种可预测的方式。

注意:

对于ESRI FGDB SDK API创建的FGDB layers,字段描述部分的结束和行部分的开始之间有4个字节:0xDE 0xAD 0xBE 0xEF

rows部分是一个X行的序列(其中X是. gdbtablex中发现的features的总数,可能与.gdbtable头文件中发现的有效行数不同)

Row具体描述

int32: length in bytes of the row blob ( this field excluded) ceil(number_nullable_fields / 8) * ubyte: 通过一个flags来标记哪些字段是空的,number_nullable_fields指可以为空的字段,这在arcgis里面能看到哪些字段可以为空,objectid不能为空所以不能参与这里的运算,shape字段可以为空所以要参与这里的运算,数出有多少个可以为空的字段后除以8然后向上取整,就知道应该保留多少个bytes来记录这些信息了。指具体内容如下。

Null fields flags

这个地方记录方法是使用n个bytes来存放字段为空的信息,n的计算方法ceil(number_nullable_fields / 8),但实际存放是通过8位的二进制bit来控制的,如:11111100表示前两个字段不为空。1代表该字段没有值,0代表该字段有值,而且排序是从后面往前排的,通常第一个字段是shape空间数据字段。如果字段比较多是用两个或多个bytes来存放这些信息的也需要整体从最后开始倒排。我们在用flexhex调试查看时是看到的16进制的数据而不是二进制的bit。

Each bit of the flags field encode for the presence or absence of the field content, for a nullable field, for the row. The flag is set to 1 if the field is missing/null (1 is used as well for spare bits), or 0 if the field is present/non-null. The flag for the first field, in the order of the fields of the field description section (typically the geometry), is the least significant bit of the first byte of the flags field.

There are no bits reserved for non-nullable fields.

If all fields are non-nullable, the flag field is absent.

Note: there's no explicit data for OBJECTID and no reserved flag bit for it.

For each non-null field, the field content is appended in the order of the fields of the field description section.

string类型字段值是用utf-8进行编码的(这一点在英文版文档中没有注明)

.gdbtablx文件规范

.gdbtablx文件包含.gdbtable的row的偏移信息。

Header (16 bytes)

Offset section

image

image

6D 02 00 00 00 是一个以16进制编码的int32类型little-endian数值,实际16进制可表示为0x26D,转换为10为621,与.gdbtable中的实际一至

.atx文件规范

数据的逻辑表名和实际存放数据的文件名的对应关系就是存放在a00000001.TablesByName.atx文件中的。

.atx 记录了 .gdbtable文件某一字段的索引。 通常,该字段在.gdbtable中接受的值按照相关FID的升序列出。.atx 文件以4096 bytes进行分页,并且根据字段值的size和.gdbtable表中features的个数进行分层组织。 The first page is 1, so page N is located at offset (N-1)*4096.

The reading of .atx files must start with its trailing section.

Trailing section (22 bytes)

这一部分在文件末尾处,可以直接取文件末尾22 bytes。

The maximum number of features (or sub-pages references) in a page is : nMaxPerPages = (4096 - 12) / (4 + size_value)

The offset at which field values are found in a page is : nOffsetFirstValInPage = 12 + nMaxPerPages * 4

Page referencing features (4096 bytes)

For a given field value, if found in several features, the features are sorted by ascending ID. The structure of such a page is header section (12 bytes), followed by FID numbers (maximum of 4 nMaxPerPages bytes), a few potential padding bytes, and finally field values (maximum of size_value nMaxPerPages bytes)

Header section structure (offset 0 in the page) :

FID section structure (offset 12 in the page) :

Padding section of zeroes (size: nOffsetFirstValInPage - 12 - 4 * nFeatures)

Values section structure (offset nOffsetFirstValInPage in the page):

Page referencing other pages (4096 bytes)

The structure of such a page is header section (4 bytes), followed by sub-pages numbers (maximum of 4 (1 + nMaxPerPages) bytes), a few potential padding bytes, and finally field values (maximum of size_value nMaxPerPages bytes)

Header section structure (offset 0 in the page) :

Sub-pages number section (offset 8 in the page):

Padding section of zeroes( size: nOffsetFirstValInPage - 8 - 4 * (nSubPages+1))

Values section structure (offset nOffsetFirstValInPage in the page):

空间信息

首先,从TOC中寻求SINFO偏移

如果z或m存在,看起来就像是两组双精度组合——可能是z/m min/max,但目前还不知道是哪个顺序

Number of bytes of the string as a varuint,这句话比较难以理解,实际上表示将我们看到的字符(字母需要查ascii码对照表)或数字(数字默认为10进制所以不用查了),转换为二进制,然后求bytes的个数。实际上二进制中8bit为一个bytes,所以拿二进制的长度除8,向上取整就是最终要用的varuint。另外,varuint可以理解为var uint,即可变个数的uint。

在geometry编码时,xorigin和yorigin表示该字段的坐标原点,xyscale表示gis概念中的scale。

objectid列的属性值并没有存放在.gdbtable文件的row->Field content部分。

用Go lang解析时可能遇到的问题

用Go解析最大的问题就是数据类型的变换不熟悉。

当使用bufio库读取到数据时是一个[]byte数组数据,我们需要的是它10进制的值,虽然源文件中存的是16进制数据,但读出来时默认看到的已经是10进制的值了,所以想办法取出来直接用就行了,我们这里想取出为uint64的类型,就直接uint64(bytedata[0)就可以了。如果想取它的ascii码值则可用string(bytedata[0])

在官方python脚本中,read_uint8就是返回uint8类型的数据,read_float32就是返回float32位的数据,以此类推

本工程除必要情况外,暂不考虑z和m的问题。