Zh-hans:PBF Format
PBF格式("Protocolbuffer Binary Format,协议缓存二进制格式")主要用于替代XML格式。它的大小约是gzip压缩的一半,比bzip压缩小30%。它的写入速度比gzip压缩快5倍,读取速度比gzip压缩快6倍。这种格式的设计是为了支持未来的可扩展性和灵活性。
这种基础文件格式被选择以支持在“文件块”粒度上的随机访问。每个文件块都是独立可解码的,并包含一系列已编码的原始组,每个原始组在默认配置中包含约8k的OSM实体。这里没有使用标签硬编码,所有键和值都以不透明字符串的形式完整存储。为了将来的可伸缩性,假设node/way/relation的id为64位。当前序列化器Osmosis保存了OSM实体和OSM实体上标签的顺序。为了灵活地处理多种分辨率,用于表示位置和时间戳的粒度或分辨率可以以1毫秒和1纳米度的倍数进行调整。默认的比例因子是1000毫秒和100纳米度,相当于赤道处约1厘米。这些也是OSM数据库的当前分辨率。
PBF格式的文件扩展名为*.osm.pbf。
目前,PBF的参考实现依赖Osmosis实现,分为两个部分,Osmosis专用部分,见[1],以及应用通用部分,见[2]。应用程序通用部分用于构建osmpbf.jar(在osmosis和其他基于java的PBF阅读器中使用),还包含PBF协议缓存定义的主定义(*.proto文件)。
PBF的软件支持
除了原始的XML格式之外,OSM项目中使用的许多软件已经支持PBF,而且还有一些工具可以将PBF转换为OSM XML,反之亦然。 请参阅PBF/软件合规性[3],了解各种程序对PBF文件支持的详细信息。
设计
底层编码
GPB格式(Google Protocol Buffers,谷歌协议缓存)用于底层存储。给定一个包含一条或多条消息的规范文件,协议缓存编译器编写底层序列化代码。消息可以包含其他消息,形成层次结构。协议缓存也支持可扩展性,可以将新字段添加到消息中,旧客户端无需重新编译就可以读取这些消息。欲了解更多细节,请参阅[4]或阅读谷歌开源博客上的相关文章[5]。谷歌官方支持c++、Java和Python,也有针对其他语言的编译器。一个示例消息规范是:
message Node {
required sint64 id = 1;
// 并列数组
repeated uint32 keys = 2 [packed = true]; // 字符串id
repeated uint32 vals = 3 [packed = true]; // 字符串id
optional Info info = 4; // 在omitmeta中可以省略
required sint64 lat = 8;
required sint64 lon = 9;
}
协议缓存对整数使用可变位编码。整数每字节7位编码,其中高位指示是否读取下一个字节。当消息包含小整数时,将最小化文件大小。有两种编码,一种主要用于正整数,另一种用于有符号整数。在标准编码中,整数[0,127]需要一个字节,[128,16383]需要两个字节。在有符号数编码中,将符号位置于最低有效位,数字[-64,63]需要一个字节,[-8192,8191]需要两个字节,以此类推。有关协议缓存消息的序列化格式的进一步细节,请参阅他们的网站。 生成的文件使用crosby.binary的Java包。在其他语言中,生成的文件在OSMPBF包中。
文件格式
一个文件包含一个文件头,后面跟着一系列文件块。该设计旨在允许将来对文件内容的随机访问,并跳过不理解或不需要的数据。
格式是一个重复的序列:
- int4:以网络字节顺序表示的BlobHeader消息的长度
- 序列化BlobHeader消息
- 序列化的Blob消息(大小在文件头中给出)
一个BlobHeader当前定义为:
message BlobHeader {
required string type = 1;
optional bytes indexdata = 2;
required int32 datasize = 3;
}
- type 包含此块消息中数据的类型。
- indexdata indexdata是一些任意的blob,它可能包含关于以下blob的元数据(例如,对于OSM数据,它可能包含一个边界框)。这是一个存根,用于支持索引*.osm.pbf文件的未来设计。
- datasize datasize包含后续Blob消息的序列化大小。
(请注意,BlobHeader曾经被称为BlockHeader。为了避免与下面的HeaderBlock混淆,它在1.1版中被重命名)
Blob用于存储任意的数据Blob,可以是未压缩的,也可以是压缩的。
message Blob {
optional int32 raw_size = 2; // 压缩时未压缩的大小
oneof data {
bytes raw = 1; //没有压缩
// 可能的数据压缩版本
bytes zlib_data = 3;
// LZMA压缩数据(可选)
bytes lzma_data = 4;
// 以前用于bzip2压缩数据,2010年弃用
bytes OBSOLETE_bzip2_data = 5 [deprecated=true]; // 不要重复使用这个标签号
// LZ4压缩数据(可选)
bytes lz4_data = 6;
// ZSTD 压缩数据 (可选)
bytes zstd_data = 7;
}
}
所有的读取器和写入器都必须支持未压缩和zlib压缩的数据。其他压缩格式是可选的,目前没有广泛使用。 为了有效地检测非法或损坏的文件,我限制了BlobHeader和Blob消息的最大大小。BlobHeader的长度应该小于32kib(32*1024字节,kib说明见[6]),并且必须小于64kib。一个Blob的未压缩长度应该小于16mib(16*1024*1024字节,mib说明见[7]),并且必须小于32mib。
OSM实体编码为文件块
目前有两种OSM数据文件块类型。这些文本类型字符串存储在BlobHeader的type字段中:
- OSMHeader: 这个Blob包含一个序列化的HeaderBlock消息(参见osmformat.proto)。每个文件块在第一个“OSMData”块之前必须有一个这样的块。
- OSMData: 包含一个序列化的PrimitiveBlock消息(参见osmformat.proto),它们包含实体信息。
这种设计允许其他软件扩展该格式,以包含其他类型的文件块以满足自己的目的。解析器应该忽略和跳过它们不能识别的文件块类型。
OSMHeader文件块的定义
message HeaderBlock {
optional HeaderBBox bbox = 1;
/* 帮助解析此数据集的附加标记 */
repeated string required_features = 4;
repeated string optional_features = 5;
optional string writingprogram = 16;
optional string source = 17; //来源是boxx字段
/* 标签允许Osmosis持续复制 */
// 复制时间戳,表示从epoch开始的秒数,否则与“timestamp=…”字段中的值相同
// Osmosis使用的state.txt文件
optional int64 osmosis_replication_timestamp = 32;
// 复制序列号(state.txt中的sequenceNumber)
optional int64 osmosis_replication_sequence_number = 33;
//复制 URL (来源是Osmosis的configuration.txt文件)
optional string osmosis_replication_base_url = 34;
}
为了提供向前和向后的兼容性,解析器需要知道它是否能够解析文件。这是通过所需的特性实现的。如果一个文件包含一个解析器不理解的必需特性,它必须拒绝该文件并发出错误报告,报告它不支持哪些必需特性。 目前定义了以下特征:
- "OsmSchema-V0.6" — 文件包含了OSM v0.6模式的数据。
- "DenseNodes" — 文件包含密集节点和密集信息。
此外,一个文件可能有可供解析器利用的可选属性。例如,文件可能是预排序的,在使用之前不需要排序。或者,文件中的方法可能有预先计算好的边界框。如果程序遇到了它不知道的可选特性,它仍然可以安全地读取文件。如果一个程序需要一个可选的特性,而这个特性并不存在,那么它就会出错。下列特征需要被提出:
- "Has_Metadata" – 该文件包含作者和时间戳元数据。
- "Sort.Type_then_ID" – 实体按类型排序,然后按ID排序。
- "Sort.Geographic" – 实体是以某种几何形式存在的,(目前未使用)。
- "timestamp=2011-10-16T15:45:00Z" – 存储文件时间戳的临时解决方案,请使用osmosis_replication_timestamp代替。
复制字段的用途是什么?
osmosis_replication_*字段的目的是允许PBF文件的消费者附加来自osmosis管理的更新服务器的数据,以保持文件的当前状态。Osmosis[8]是一种软件,用于产生planet.openstreetmap.org上每天、每小时、每分钟的差异。要向PBF文件追加更新,必须知道该文件表示的复制状态,以便找到正确的同步点。
- osmosis_replication_timestamp - 复制的时间戳(Unix的epoch值),来自于Osmosis写的state.txt文件(不是Unix的epoch值,而是ISO的时间字符串)。从技术上讲,这是文件中完全包含的最后一个事务的内部数据库时间戳,它并不一定意味着文件中包含时间戳小于或等于这个时间戳的每个对象。
- osmosis_replication_sequence_number - 文件中包含的最后一个数据库事务的序列号。这通常与时间戳相匹配,如果您知道其中一个时间戳,就可以找出另一个时间戳,它使消费者更容易知道这两个时间戳。
- osmosis_replication_base_url - 复制差异的基本URL,例如https://planet.openstreetmap.org/replication/minute/,这样消费者就知道给定的id与哪个服务器(哪个数据库)相关。
在处理PBF文件时,通常会保持这些字段完好无损(即将它们从输入复制到输出),就像复制bbox块一样,除非您后续不想用特定类型的处理方式更新这些文件。
OSMData文件块的定义
为了将OSM实体编码到协议缓存中,我收集了一系列原始组实体来形成一个原始块,它被序列化到一个“OsmData”文件块的Blob部分中。
message PrimitiveBlock {
required StringTable stringtable = 1;
repeated PrimitiveGroup primitivegroup = 2;
// 粒度,以纳米度为单位,用于在此块中存储坐标
optional int32 granularity = 17 [default=100];
// 输出坐标坐标与粒度网格之间的偏移值,以纳米度为单位
optional int64 lat_offset = 19 [default=0];
optional int64 lon_offset = 20 [default=0];
// 日期的粒度,从1970年开始,通常以毫秒为单位表示
optional int32 date_granularity = 18 [default=1000];
// 扩展:
//optional BBox bbox = XX;
}
在创建PBF文件时,需要将所有字符串(key、value、role、user)提取到一个单独的字符串表中。此后,字符串被它们在该表中的索引引用,只是在编码密集节点时使用index=0作为分隔符。这意味着您不能安全地将一个有用的字符串存储在这个位置。因此,一个空字符串被存储在index=0处,并且这个位置永远不会被使用。这不是必须的,但是如果您按照经常使用的字符串有小索引的方式对字符串表进行排序,可能会对性能产生积极的影响。如果按照字典顺序对具有相同频率的字符串进行排序,还可以改进字符串表的可压缩性。 每个原始块都是独立解压的,它包含了解压它所需要的实体的所有信息。它包含一个字符串表,它还为位置戳和时间戳编码粒度。 一个块可以包含任意数量的实体,只要遵守块的大小限制。为了简单起见,某些程序(例如,osmosis 0.38)在编写PBF格式时将每个块中的实体数量限制为8000个。 除了粒度(granularity)之外,该基元块还编码经纬度的偏移值。这些值以纳米度为单位,必须添加到每个坐标中。
latitude = .000000001 * (lat_offset + (granularity * lat)) longitude = .000000001 * (lon_offset + (granularity * lon))
其中,纬度以度表示,粒度是在原始块中给出的粒度,lat_offset是在原始块中给出的偏移量,lat/lon编码在节点中,delta编码在DenseNode中。经度方程的解释是类似的。 lat_offset和lon_offset存在的原因是为了简明地表示等高线数据(等高线)或出现在规则网格中的其他数据。假设我们希望表示100微度网格的数据。我们希望使用100000纳米度的粒度来进行最高的压缩,除非它只能表示形式为(.0001*x,.0001*y)的点,而实际网格数据可能形式为(.00003345+。0001 * x, .00008634 +。* y),通过使用lat_offset=3345和lon_offset=8634,我们可以精确地表示这个100微度的网格。
日期戳:
millisec_stamp = timestamp*date_granularity.
其中timestamp是用Info编码的时间戳,或者在DenseInfo中用delta编码, date_granularity在原始块中给出,而millisec_stamp是实体的日期,用从1970年以来的毫秒数衡量。要获得自1970年以来以秒为单位测量的日期,请将millisec_stamp除以1000。 在每个原始块中,我将实体划分为包含最多8k个相同类型(node/way/relation)的OSM实体的原始组。
message PrimitiveGroup {
repeated Node nodes = 1;
optional DenseNodes dense = 2;
repeated Way ways = 3;
repeated Relation relations = 4;
repeated ChangeSet changesets = 5;
}
一个原始组绝对不能包含不同类型的对象。因此,它要么包含许多节点消息,要么包含一个DenseNode消息,要么包含许多Way消息,要么包含许多Relation消息,要么包含许多ChangeSet消息。但它永远不可能包含这些元素的任何混合物。原因是协议缓存编码的工作方式,将不可能以写入文件的相同顺序取出对象。这可能会让用户感到相当困惑。 在序列化为字符串之后,当存储在Blob文件块中时,每个原始块都是可选的单独压缩的gzip。
Ways和Relations
对于在字段引用中包含其他节点id的ways和relations,我利用连续节点以某种方式或关系的趋势,通过使用delta压缩来拥有附近的节点id,从而得到小整数。(例如,不编码为x_1、x_2, x_3,而编码为x_1, x_2-x_1, x_3-x_2,…)。除此之外,ways和relations大多是按照人们期望的方式编码的。标记被编码为两个并列数组,一个是key的string- id数组,另一个是value的string- id数组。
message Way {
required int64 id = 1;
// 并列数组
repeated uint32 keys = 2 [packed = true];
repeated uint32 vals = 3 [packed = true];
optional Info info = 4;
repeated sint64 refs = 8 [packed = true]; // DELTA 编码
// 以下两个字段是可选的,它们只在特殊场合使用
// 格式中节点位置也被添加到方法中,这使得文件较大,但允许直接创建几何图形
// 如果使用这个,你必须设置optional_features标签为"LocationsOnWays",并且refs, lat和lon的值必须相同
repeated sint64 lat = 9 [packed = true]; // DELTA 编码, 可选
repeated sint64 lon = 10 [packed = true]; // DELTA 编码, 可选
}
relations使用枚举表示成员类型:
message Relation {
enum MemberType {
NODE = 0;
WAY = 1;
RELATION = 2;
}
required int64 id = 1;
// 并列数组
repeated uint32 keys = 2 [packed = true];
repeated uint32 vals = 3 [packed = true];
optional Info info = 4;
// 并列数组
repeated int32 roles_sid = 8 [packed = true];
repeated sint64 memids = 9 [packed = true]; // DELTA 编码
repeated MemberType types = 10 [packed = true];
}
Metadata包括关于对象的非地理信息,例如:
message Info {
optional int32 version = 1 [default = -1];
optional int32 timestamp = 2;
optional int64 changeset = 3;
optional int32 uid = 4;
optional int32 user_sid = 5; // 字符串id
// 可见标志用于存储历史信息,表示当前对象版本已经通过OSM API上的delete操作创建
// 当写入器设置该标记时,它必须在HeaderBlock中添加一个值为"HistoricalInformation"的required_features标记
// 如果该标记对某些对象不可用,则必须假设该文件设置了required_features标记“HistoricalInformation”为真
optional bool visible = 6;
}
Nodes
节点有两种编码方式,一种是节点(上面定义的),另一种是特殊的密集格式。在密集格式中,我以“列方式”存储组,作为id数组、纬度数组和经度数组。每一列都是增量编码的。这减少了头文件的开销,并允许增量编码非常有效地工作。 所有节点的key和value被编码为一个字符串数组。每个节点的标签都是交替的<keyid> <valid>。当一个节点的标记结束而下一个节点的标记开始时,我们使用一个0的stringid来分隔。存储模式为: ((<keyid> <valid>)* '0' )*。异常情况是,如果当前块中没有节点有任何key/value对,这个数组不包含任何分隔符,只是空的。
message DenseNodes {
repeated sint64 id = 1 [packed = true]; // DELTA 编码
//repeated Info info = 4;
optional DenseInfo denseinfo = 5;
repeated sint64 lat = 8 [packed = true]; // DELTA 编码
repeated sint64 lon = 9 [packed = true]; // DELTA 编码
// 将key和value打包到一个数组中。如果该块中的所有节点都是无标记的,则可能为空
repeated int32 keys_vals = 10 [packed = true];
}
DenseInfo对元数据进行类似的增量编码。
message DenseInfo {
repeated int32 version = 1 [packed = true];
repeated sint64 timestamp = 2 [packed = true]; // DELTA 编码
repeated sint64 changeset = 3 [packed = true]; // DELTA 编码
repeated sint32 uid = 4 [packed = true]; // DELTA 编码
repeated sint32 user_sid = 5 [packed = true]; // 用户名的字符串id,DELTA编码
// 可见标志用于存储历史信息。表示当前对象版本已经通过OSM API上的delete操作创建
// 当写入器设置该标记时,它必须在HeaderBlock中添加一个值为"HistoricalInformation"的required_features标记
// 如果该标记对某些对象不可用,则必须假定该文件设置了required_features标记“HistoricalInformation”为真
repeated bool visible = 6 [packed = true];
}
格式示例
I在下面,我们将查看OSM PBF文件的字节。以bremen.osm.pbf ([9], 2011-01-13)为例。 每个数据之前都有一个变量标识符。这个标识符由type和id组成,位0到2表示类型,位3及以上表示id。
以下类型可以使用:
- 0: V (Varint) int32, int64, uint32, uint64, sint32, sint64, bool, enum
- 1: D (64-bit) fixed64, sfixed64, double
- 2: S (Length-delimited) string, bytes, embedded messages, packed repeated fields
- 5: I (32-bit) fixed32, sfixed32, float
00000000 00 00 00 0d - length in bytes of the BlobHeader in network-byte order(以字节为单位的BlobHeader长度,按网络字节顺序) 00000000 __ __ __ __ 0a - S 1 'type' 00000000 __ __ __ __ __ 09 - length 9 bytes 00000000 __ __ __ __ __ __ 4f 53 4d 48 65 61 64 65 72 - "OSMHeader" 00000000 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ 18 - V 3 'datasize' 00000010 7c - 124 bytes long 00000010 __ 10 - V 2 'raw_size' 00000010 __ __ 71 - 113 bytes long 00000010 __ __ __ 1a - S 3 'zlib_data' 00000010 __ __ __ __ 78 - length 120 bytes --- compressed section(压缩部分): 00000010 __ __ __ __ __ 78 9c e3 92 e2 b8 70 eb da 0c 7b ||.q.xx.....p...{| 00000020 81 0b 7b 7a ff 39 49 34 3c 5c bb bd 9f 59 a1 61 |..{z.9I4<\...Y.a| 00000030 ce a2 df 5d cc 4a 7c fe c5 b9 c1 c9 19 a9 b9 89 |...].J|.........| 00000040 ba 61 06 7a 66 4a 5c 2e a9 79 c5 a9 7e f9 29 a9 |.a.zfJ\..y..~.).| 00000050 c5 4d 8c fc c1 7e 8e 01 c1 1e fe 21 ba 45 46 26 |.M...~.....!.EF&| 00000060 96 16 26 5d 8c 2a 19 25 25 05 56 fa fa e5 e5 e5 |..&].*.%%.V.....| 00000070 7a f9 05 40 a5 25 45 a9 a9 25 b9 89 05 7a f9 45 |z..@.%E..%...z.E| 00000080 e9 fa 89 05 99 fa 40 43 00 c0 94 29 0c --- decompressed(解压) ---> 00000000 0a - S 1 'bbox' 00000000 __ 1a - length 26 bytes 00000000 __ __ 08 d0 da d6 98 3f 10 d0 bc 8d fe 42 18 80 00000010 e1 ad b7 8f 03 20 80 9c a2 fb 8a 03 - BBOX (4*Varint) 00000010 __ __ __ __ __ __ __ __ __ __ __ __ 22 - S 4 'required_features' 00000010 __ __ __ __ __ __ __ __ __ __ __ __ __ 0e - length 14 bytes 00000010 __ __ __ __ __ __ __ __ __ __ __ __ __ __ 4f 73 00000020 6d 53 63 68 65 6d 61 2d 56 30 2e 36 - "OsmSchema-V0.6" 00000020 __ __ __ __ __ __ __ __ __ __ __ __ 22 - S 4 'required_features' 00000020 __ __ __ __ __ __ __ __ __ __ __ __ __ 0a - length 10 bytes 00000020 __ __ __ __ __ __ __ __ __ __ __ __ __ __ 44 65 00000030 6e 73 65 4e 6f 64 65 73 - "DenseNodes" 00000030 __ __ __ __ __ __ __ __ 82 01 - S 16 'writingprogram' 00000030 __ __ __ __ __ __ __ __ __ __ 0f - length 15 bytes 00000030 __ __ __ __ __ __ __ __ __ __ __ 53 4e 41 50 53 00000040 48 4f 54 2d 72 32 34 39 38 34 - "SNAPSHOT-r24984" 00000040 __ __ __ __ __ __ __ __ __ __ 8a 01 - S 17 'source' 00000040 __ __ __ __ __ __ __ __ __ __ __ __ 24 - length 36 bytes 00000040 __ __ __ __ __ __ __ __ __ __ __ __ __ 68 74 74 00000050 70 3a 2f 2f 77 77 77 2e 6f 70 65 6e 73 74 72 65 00000060 65 74 6d 61 70 2e 6f 72 67 2f 61 70 69 2f 30 2e 00000070 36 - "https://www.openstreetmap.org/api/0.6" <--- decompressed(解压) --- 00000080 __ __ __ __ __ __ __ __ __ __ __ __ __ 00 00 00 00000090 0d - length in bytes of the BlobHeader in network-byte order 00000090 __ 0a - S 1 'type' 00000090 __ __ 07 - length 7 bytes 00000090 __ __ __ 4f 53 4d 44 61 74 61 "OSMData" 00000090 __ __ __ __ __ __ __ __ __ __ 18 - V 3 'datasize' 00000090 __ __ __ __ __ __ __ __ __ __ __ 90 af 05 - 87952 bytes long 00000090 __ __ __ __ __ __ __ __ __ __ __ __ __ __ 10 - V 2 'raw_size' 00000090 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ 8f 000000a0 84 08 - 131599 bytes long 000000a0 __ __ 1a - S 3 'zlib_data' 000000a0 __ __ __ 88 af 05 - length 87944 bytes --- compressed section(压缩部分): 000000a0 __ __ __ __ __ __ 78 9c b4 bc 09 5c 14 57 ba 28 |......x....\.W.(| 000000b0 5e 75 aa ba ba ba ba 69 16 11 d1 b8 90 b8 1b 41 |^u.....i.......A| 000000c0 10 11 97 98 c4 2d 31 1a 27 b9 9a 49 ee 64 ee 8c |.....-1.'..I.d..| 000000d0 69 a0 95 8e 40 9b 06 62 32 f7 dd f7 5c 00 01 11 |i...@..b2...\...| 000000e0 11 05 11 11 71 43 45 44 40 05 44 54 14 17 44 44 |....qCED@.DT..DD| 000000f0 40 16 15 dc 00 37 50 44 05 c4 05 7d df 39 55 dd |@....7PD...}.9U.| etc. --- decompressed (解压)---> 00000000 0a - S 1 'stringtable' 00000000 __ d4 2e - length 5972 bytes 00000000 __ __ __ 0a - S 1 00000000 __ __ __ __ 00 length 0 bytes 00000000 __ __ __ __ __ 0a - S 1 00000000 __ __ __ __ __ __ 07 length 7 bytes 00000000 __ __ __ __ __ __ __ 44 65 65 6c 6b 61 72 - "Deelkar" 00000000 __ __ __ __ __ __ __ __ __ __ __ __ __ __ 0a 0a |.......Deelkar..| 00000010 63 72 65 61 74 65 64 5f 62 79 0a 04 4a 4f 53 4d |created_by..JOSM| 00000020 0a 0b 45 74 72 69 63 43 65 6c 69 6e 65 0a 04 4b |..EtricCeline..K| 00000030 6f 77 61 0a 05 55 53 63 68 61 0a 0d 4b 61 72 74 |owa..UScha..Kart| 00000040 6f 47 72 61 70 48 69 74 69 0a 05 4d 75 65 63 6b |oGrapHiti..Mueck| etc. --- decompressed part from offset 5975(从偏移量5975开始的解压部分) ---> 00000000 12 - S 2 'primitivegroup' 00000000 __ ad d5 07 - length 125613 bytes 00000000 __ __ __ __ 12 - S 2 -- Tag #2 in a 'PrimitiveGroup', containing a serialized DenseNodes(包含一个序列号的密集节点). 00000000 __ __ __ __ __ a9 d5 07 - of length 125609 bytes 00000000 __ __ __ __ __ __ __ __ 0a - S 1 - Tag #1 in a DenseNodes, which is an array of packed varints(一组包装好的变体). 00000000 __ __ __ __ __ __ __ __ __ df 42 - of length 8543 bytes 00000000 __ __ __ __ __ __ __ __ __ __ __ ce ad 0f 02 02 |..........B.....| 00000010 02 02 04 02 02 02 02 02 02 02 02 02 02 02 02 02 |................| 00000020 02 02 02 c6 8b ef 13 02 02 02 02 02 02 02 02 f0 |................| 00000030 ea 01 02 02 02 02 02 02 02 02 02 02 02 02 02 02 |................| 每个变量都是连续存储的。 我们一直处理,直到读取了8543字节的值,然后继续解析密集节点。 变量是增量编码的节点id号。 00000040 02 02 02 04 02 04 02 02 04 02 02 02 02 02 02 02 |................| 00000050 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 |................| 00000060 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 04 |................| 00000070 02 02 06 44 02 02 02 02 02 02 02 02 02 02 02 02 |...D............| 00000080 68 02 02 02 02 02 02 02 02 02 02 02 04 02 02 06 |h...............| 00000090 02 02 0c 02 02 02 0a 02 02 02 02 06 0c 06 02 04 |................| 000000a0 02 06 02 02 02 02 02 02 02 02 02 02 02 02 02 02 |................| 000000b0 02 02 02 02 02 02 02 02 04 04 02 06 04 04 10 02 |................| 000000c0 04 02 04 18 0a 02 02 02 02 02 02 02 02 02 02 02 |................| 000000d0 04 06 02 02 04 02 02 02 02 04 02 02 02 02 08 02 |................| 000000e0 02 02 02 02 02 02 02 02 02 02 02 cc 06 02 02 02 |................| 000000f0 02 02 02 02 02 02 02 02 04 02 02 02 02 02 02 02 |................| 00000100 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 |................| 00000110 02 02 02 02 02 02 02 02 36 02 02 04 04 04 02 02 |........6.......| 00000120 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 |................| etc. <--- decompressed(解压) --- 00000000 00 00 00 0d - length in bytes of the BlobHeader in network-byte order(以字节为单位的BlobHeader长度,按网络字节顺序) 00000000 __ __ __ __ 0a - S 1 'type' 00000000 __ __ __ __ __ 09 - length 9 bytes 00000000 __ __ __ __ __ __ 4f 53 4d 48 65 61 64 65 72 - "OSMHeader" 00000000 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ 18 - V 3 'datasize' 00000010 7c - 124 bytes long 00000010 __ 10 - V 2 'raw_size' 00000010 __ __ 71 - 113 bytes long 00000010 __ __ __ 1a - S 3 'zlib_data' 00000010 __ __ __ __ 78 - length 120 bytes --- compressed section(压缩部分): 00000010 __ __ __ __ __ 78 9c e3 92 e2 b8 70 eb da 0c 7b ||.q.xx.....p...{| 00000020 81 0b 7b 7a ff 39 49 34 3c 5c bb bd 9f 59 a1 61 |..{z.9I4<\...Y.a| 00000030 ce a2 df 5d cc 4a 7c fe c5 b9 c1 c9 19 a9 b9 89 |...].J|.........| 00000040 ba 61 06 7a 66 4a 5c 2e a9 79 c5 a9 7e f9 29 a9 |.a.zfJ\..y..~.).| 00000050 c5 4d 8c fc c1 7e 8e 01 c1 1e fe 21 ba 45 46 26 |.M...~.....!.EF&| 00000060 96 16 26 5d 8c 2a 19 25 25 05 56 fa fa e5 e5 e5 |..&].*.%%.V.....| 00000070 7a f9 05 40 a5 25 45 a9 a9 25 b9 89 05 7a f9 45 |z..@.%E..%...z.E| 00000080 e9 fa 89 05 99 fa 40 43 00 c0 94 29 0c --- decompressed(解压) ---> 00000000 0a - S 1 'bbox' 00000000 __ 1a - length 26 bytes 00000000 __ __ 08 d0 da d6 98 3f 10 d0 bc 8d fe 42 18 80 00000010 e1 ad b7 8f 03 20 80 9c a2 fb 8a 03 - BBOX (4*Varint) 00000010 __ __ __ __ __ __ __ __ __ __ __ __ 22 - S 4 'required_features' 00000010 __ __ __ __ __ __ __ __ __ __ __ __ __ 0e - length 14 bytes 00000010 __ __ __ __ __ __ __ __ __ __ __ __ __ __ 4f 73 00000020 6d 53 63 68 65 6d 61 2d 56 30 2e 36 - "OsmSchema-V0.6" 00000020 __ __ __ __ __ __ __ __ __ __ __ __ 22 - S 4 'required_features' 00000020 __ __ __ __ __ __ __ __ __ __ __ __ __ 0a - length 10 bytes 00000020 __ __ __ __ __ __ __ __ __ __ __ __ __ __ 44 65 00000030 6e 73 65 4e 6f 64 65 73 - "DenseNodes" 00000030 __ __ __ __ __ __ __ __ 82 01 - S 16 'writingprogram' 00000030 __ __ __ __ __ __ __ __ __ __ 0f - length 15 bytes 00000030 __ __ __ __ __ __ __ __ __ __ __ 53 4e 41 50 53 00000040 48 4f 54 2d 72 32 34 39 38 34 - "SNAPSHOT-r24984" 00000040 __ __ __ __ __ __ __ __ __ __ 8a 01 - S 17 'source' 00000040 __ __ __ __ __ __ __ __ __ __ __ __ 24 - length 36 bytes 00000040 __ __ __ __ __ __ __ __ __ __ __ __ __ 68 74 74 00000050 70 3a 2f 2f 77 77 77 2e 6f 70 65 6e 73 74 72 65 00000060 65 74 6d 61 70 2e 6f 72 67 2f 61 70 69 2f 30 2e 00000070 36 - "https://www.openstreetmap.org/api/0.6" <--- decompressed(解压) --- 00000080 __ __ __ __ __ __ __ __ __ __ __ __ __ 00 00 00 00000090 0d - length in bytes of the BlobHeader in network-byte order 00000090 __ 0a - S 1 'type' 00000090 __ __ 07 - length 7 bytes 00000090 __ __ __ 4f 53 4d 44 61 74 61 "OSMData" 00000090 __ __ __ __ __ __ __ __ __ __ 18 - V 3 'datasize' 00000090 __ __ __ __ __ __ __ __ __ __ __ 90 af 05 - 87952 bytes long 00000090 __ __ __ __ __ __ __ __ __ __ __ __ __ __ 10 - V 2 'raw_size' 00000090 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ 8f 000000a0 84 08 - 131599 bytes long 000000a0 __ __ 1a - S 3 'zlib_data' 000000a0 __ __ __ 88 af 05 - length 87944 bytes --- compressed section(压缩部分): 000000a0 __ __ __ __ __ __ 78 9c b4 bc 09 5c 14 57 ba 28 |......x....\.W.(| 000000b0 5e 75 aa ba ba ba ba 69 16 11 d1 b8 90 b8 1b 41 |^u.....i.......A| 000000c0 10 11 97 98 c4 2d 31 1a 27 b9 9a 49 ee 64 ee 8c |.....-1.'..I.d..| 000000d0 69 a0 95 8e 40 9b 06 62 32 f7 dd f7 5c 00 01 11 |i...@..b2...\...| 000000e0 11 05 11 11 71 43 45 44 40 05 44 54 14 17 44 44 |....qCED@.DT..DD| 000000f0 40 16 15 dc 00 37 50 44 05 c4 05 7d df 39 55 dd |@....7PD...}.9U.| etc. --- decompressed (解压)---> 00000000 0a - S 1 'stringtable' 00000000 __ d4 2e - length 5972 bytes 00000000 __ __ __ 0a - S 1 00000000 __ __ __ __ 00 length 0 bytes 00000000 __ __ __ __ __ 0a - S 1 00000000 __ __ __ __ __ __ 07 length 7 bytes 00000000 __ __ __ __ __ __ __ 44 65 65 6c 6b 61 72 - "Deelkar" 00000000 __ __ __ __ __ __ __ __ __ __ __ __ __ __ 0a 0a |.......Deelkar..| 00000010 63 72 65 61 74 65 64 5f 62 79 0a 04 4a 4f 53 4d |created_by..JOSM| 00000020 0a 0b 45 74 72 69 63 43 65 6c 69 6e 65 0a 04 4b |..EtricCeline..K| 00000030 6f 77 61 0a 05 55 53 63 68 61 0a 0d 4b 61 72 74 |owa..UScha..Kart| 00000040 6f 47 72 61 70 48 69 74 69 0a 05 4d 75 65 63 6b |oGrapHiti..Mueck| etc. --- decompressed part from offset 5975(从偏移量5975开始的解压部分) ---> 00000000 12 - S 2 'primitivegroup' 00000000 __ ad d5 07 - length 125613 bytes 00000000 __ __ __ __ 12 - S 2 -- Tag #2 in a 'PrimitiveGroup', containing a serialized DenseNodes(包含一个序列号的密集节点). 00000000 __ __ __ __ __ a9 d5 07 - of length 125609 bytes 00000000 __ __ __ __ __ __ __ __ 0a - S 1 - Tag #1 in a DenseNodes, which is an array of packed varints(一组包装好的变体). 00000000 __ __ __ __ __ __ __ __ __ df 42 - of length 8543 bytes 00000000 __ __ __ __ __ __ __ __ __ __ __ ce ad 0f 02 02 |..........B.....| 00000010 02 02 04 02 02 02 02 02 02 02 02 02 02 02 02 02 |................| 00000020 02 02 02 c6 8b ef 13 02 02 02 02 02 02 02 02 f0 |................| 00000030 ea 01 02 02 02 02 02 02 02 02 02 02 02 02 02 02 |................| 每个变量都是连续存储的。 我们一直处理,直到读取了8543字节的值,然后继续解析密集节点。 变量是增量编码的节点id号。 00000040 02 02 02 04 02 04 02 02 04 02 02 02 02 02 02 02 |................| 00000050 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 |................| 00000060 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 04 |................| 00000070 02 02 06 44 02 02 02 02 02 02 02 02 02 02 02 02 |...D............| 00000080 68 02 02 02 02 02 02 02 02 02 02 02 04 02 02 06 |h...............| 00000090 02 02 0c 02 02 02 0a 02 02 02 02 06 0c 06 02 04 |................| 000000a0 02 06 02 02 02 02 02 02 02 02 02 02 02 02 02 02 |................| 000000b0 02 02 02 02 02 02 02 02 04 04 02 06 04 04 10 02 |................| 000000c0 04 02 04 18 0a 02 02 02 02 02 02 02 02 02 02 02 |................| 000000d0 04 06 02 02 04 02 02 02 02 04 02 02 02 02 08 02 |................| 000000e0 02 02 02 02 02 02 02 02 02 02 02 cc 06 02 02 02 |................| 000000f0 02 02 02 02 02 02 02 02 04 02 02 02 02 02 02 02 |................| 00000100 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 |................| 00000110 02 02 02 02 02 02 02 02 36 02 02 04 04 04 02 02 |........6.......| 00000120 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 |................| etc. <--- decompressed(解压) ---
协议缓存库会处理所有这些底层编码细节。
代码
T代码库被分成两部分,github上存在独立于应用程序的通用代码,包括协议缓存定义,以及在文件块级别操作的Java代码,http://github.com/scrosby/OSM-binary。 不过,osmosis、mkgmap和splitter都使用不同的OSM实体的内部表示。这意味着我必须为每个程序重新实现实体序列化和解析代码,实现之间的常见代码比我希望的要少,osmosis的序列化器和反序列化器在master分支中。 一个旧版本的mkgmap拆分器的反序列化器(2010-05)可以在github上找到,http://github.com/scrosby/OSM-splitter。
其他资料
下载
完整的(当前状态,无历史记录)PBF格式的osm数据可以在这里找到:https://planet.openstreetmap.org/pbf/ 其他可以下载OSM PBF格式文件的地方见,https://wiki.openstreetmap.org/wiki/Planet.osm。
另请参阅
- PBF Perl解析器:https://wiki.openstreetmap.org/wiki/PBF_Perl_Parser
- 维基百科的协议缓存定义:https://en.wikipedia.org/wiki/Protocol_Buffers
- Osmium,c++库,用于处理与Python和JavaScript绑定的OSM文件:https://wiki.openstreetmap.org/wiki/Osmium
- Imposm,Python库,用XML和PBF格式解析开放地图数据:https://imposm.org/docs/imposm.parser/latest/
- Libosmpbfreader,一个简单的c++库,用于读取OpenStreetMap二进制文件:https://github.com/CanalTP/libosmpbfreader
- osm-read,node.js库,用于以XML和PBF格式解析OpenStreetMap数据:https://www.npmjs.com/package/osm-read
- pbf_parse,Ruby库,用于轻松解析PBF文件:https://github.com/planas/pbf_parser
- osm4scala,高性能scala库,用于遍历pbf文件中的osm元素:https://github.com/simplexspatial/osm4scala
- Osm4j,用于处理OSM文件的Java框架,https://wiki.openstreetmap.org/wiki/Osm4j
- Parallelpbf,Java多线程PBF格式阅读器,https://github.com/woltapp/parallelpbf
- tiny-osmpbf,Javascript库,针对较小的代码占用进行了优化,https://github.com/tyrasd/tiny-osmpbf
- Node Locations on Ways,PBF格式的扩展“LocationsOnWays”,使ways上可以包括lat/lon对,被libosmium所使用:https://blog.jochentopf.com/2016-04-20-node-locations-on-ways.html
- Mapbox PBF,mapbox的JavaScript库,用于解码和编码PBF文件:https://github.com/mapbox/pbf