https://wiki.openstreetmap.org/wiki/O5m
目录
- 1. Motivation
- 1.1 Conventional formats
- 1.2 Why a new Format?
- 2. Format description
- 2.1 Basics
- 2.2 File
- 2.3 Node
- 2.4 Way
- 2.5 Relation
- 2.6 File Timestamp
- 2.7 Bounding Box
- 2.8 Reset
- 2.9 Sync
- 2.10 Jump
- 3. File Size Comparison
- 4.Further Information
- 4.1 Why that strange Name?
- 4.2 .o5c as OSM Change Format
- 4.3 Future Extensions
- 4.4 Is there an Example Implementation?
- 5. Software supporting .o5m
- 5.1 Programs already support .o5m
- 5.2 Toolchains using .o5m
1.Motivation
已有两种主流格式:.osm以及.pbf。为什么还要另外一种呢?让我们仔细看看这两种最常见的格式,并尝试确定它们的优缺点。
1.1 Conventional formats 传统格式
1.1.1 .osm
这种数据格式是人类可读的,因为它是用XML编写的。通常,文件中的对象按类型(节点、方式、关系)依次排序,然后按id排序。XML格式有一些优点和缺点。
Advantages
- 人类可读
- 随机访问
- 可以用普通文本编辑器轻松编辑(当不太大时)
- 可以使用任何压缩程序进行压缩(见下面)
- 扁平层次结构,可以作为数据流处理。
- 两个或多个文件可以很容易合并。
Disadvantages
- 巨大的文件大小
- 处理的速度慢
- 写入速度慢(因为文件大小太大)
1.1.2 .osm.bz2
Advantages
- 更小的文件大小
- 一些随机访问
- 扁平层次结构,可以作为数据流处理
- 两个或多个文件可以很容易合并。
Disadvantages
- 不是人类可读的
- 用普通的文本编辑器不容易编辑
- 处理的速度慢
- 写入速度慢(因为压缩)
1.1.3 .pbf
这种优化的格式被引入来消除一些.osm格式的缺点。它具有一定的灵活性,例如允许地理区域集群,因此您可以从较大的文件中选择区域的数据。(这将意味着打破常规的id顺序。)
通常的可以从不同位置下载的.pbf文件,其对象的顺序与传统的.osm文件相同。
Advantages
- 小文件大小
- 可以比.osm处理的更快
- 内置压缩消除了外部压缩的需要
Disadvantages
- 不是人类可读的
- 不容易编辑
- 目前没有显着的随机访问
- 应用程序需要zlib packer函数库。
1.2 Why a new Format?
新格式试图将两种格式的优点结合起来,主要目标是:
- 小文件大小
- 快速的处理速度
- 扁平的层次结构,可作为数据流处理
- 两个或多个文件可以很容易合并
- 用户可以选择压缩方法并压缩文件
不幸的是,由于加速数据处理,某些目标无法达到。我们将不得不忍受一些缺点:
- 不是人类可读的
- 不能用公共文本编辑器进行编辑
此外,目前还没有在o5m文件中随机访问的机制,不过如果需要的话,可以很容易地添加这个机制。
2. Format description
新的.o5m格式的结构类似于传统的.osm格式:对象使用严格的顺序存储。首先是所有节点,然后是所有的路线,最后是所有的关系。在这三组中,对象按其id顺序升序排列。
每个数字都是使用Varint格式打包的,您可能从.pbf文件中知道这种格式。字符串包含在零字节中,或者重复时引用。
2.1 Basics
要理解格式,最好知道如何存储数字和字符串。
2.1.1 Numbers
为了存储不同长度的数字,我们放弃了每个字节的位7(最重要的位),并使用这个位作为长度指示符。这个指示符——当设置为1时——告诉我们下一个字节属于相同的数字。这样一个长数字的第一个字节包含最不重要的7位,最后一个字节是最重要的7位。例如:
- 0x05 = 5
- 0x7f = 127
- 0xc3 0x02 = 323
( ==11000011== 00000010
指示符 | 64 | 32 | 16 | 8 | 4 | 2 | 1 | 14次方 | …… | …… | …… | 1024 | 512 | 256 | 128 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
因此256+64+2+1 = 323
)
- 0x80 0x80 0x01 = 16384
如果一个数字存储为“有符号型”,我们将需要1位符号位。为此,将最小有效字节的最小值作为符号位。0表示正,1表示负。当然,我们不需要数字-0,所以我们可以把负数的范围改为1。例如:
- 0x08 = +4
- 0x80 0x01 = +64
( ==11000011== 00000010
指示符 | 32 | 16 | 8 | 4 | 2 | 1 | 符号位 | 13次方 | …… | …… | …… | 512 | 256 | 128 | 64 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
因此 1*64 = + 64
)
- 0x03 = -2
- 0x05 = -3
- 0x81 0x01 = -65
要正确地解释一个存储的数字,我们必须知道它是否存储为一个有符号或者无符号的数字。出于这个原因,我们希望用于存储OSM信息的格式必须非常准确地定义。
如你所见,小数字比大数字需要的空间更少。我们的数据格式会尽量避免大数字。为了达到这个目的,我们使用了.pbf格式中的一个技巧:即delta编码。
特别是当数字之间的差别很小的时候,我们只存储这些数字之间的差异。例如,假设我们要存储几个节点的id,比如123000,123050,123055。这些被存储为三个有符号整数值:+123000,+50,+5。
所描述的数字格式只支持整数。为了存储小数,我们使用定点表示。纬度和经度被存储为以100纳米为单位的数值,即小数点右移7位。注意,在增量编码精度值时,创建或读取.o5m文件的应用程序使用32位有符号整数值而不是64位。出于这个原因,即使在你期望的情况下,你也不会得到任何超出32位有符号整数范围的delta值 -
例如,如果delta数为358度(179减去 -179)。这些358度的存储值为正值(714.967.296或0x2a9d8900),因为在32位算术中,1.790.000.000 + 714.967.296会溢出,结果是-1.790.000.000,这正是我们想要的值。在64位算法中,我们将存储一个负值(-3.580.000.000或0xffffffff2a9d8900)。很明显,我们只需要这个值的最后32位,这正是存储在32位算术中的值。
2.1.2 Strings
字符串没有打包,它们按原样存储(编码为UTF-8)。一般来说,字符串是成对存储的。为了标记开始、结束和字符串对的两个元素之间的位置,我们使用零字节。如果没有一对,但只有一个字符串,第二个元素将被省略。用户名是用他们的用户ID打包成一个字符串。例如:
-
"oneway"/"yes"
被编码为0x00 0x6f 0x6e 0x65 0x77 0x61 0x79 0x00 0x79 0x65 0x73 0x00
-
"1inner"
(inner前是数字1,编码为0x31,在关系中,数字“1”表示引用的对象的类型,“0”表示节点,“1”表示路线,“2”表示关系)被编码为0x00 0x31 0x69 0x6e 0x6e 0x65 0x72 0x00
-
uid=1020,“John”
被编码为0x00 0xfc 0x07 0x00 0x4a 0x6f 0x68 0x4e 0x00
。(用户id被编码为unsigned Varint,并打包到该字符串对的第一个字符传中。将id编码为Varint有助于节省几个字节的空间。
Varint编码 :0xfc 0x07 = 1111 1100 0000 0111 = 4+8+ 16+32+64 +128+256+512 = 1020
UTF-8编码:0x4a 0x6f 0x68 0x4e ->John
)
要做到这一点,每个字符串都需要花费大量的空间。幸运的是,OSM中的大多数字符串都成对出现,并不断重复。以"highway"/"residential"
或"building"/"yes"
为例。这允许我们在任何时候重用以前遇到的一对字符串时使用引用。
To refer to a string pair which has already been defined in the way shown above, we count the string pair definitions from the present location in the file back to that definition which matches our string pair.
为了引用已经在上面所示的方式定义的字符串对,我们将字符串对定义从文件当前位置返回到与我们的字符串对匹配的定义。使用相同的方法将该计数存储为无符号的数字,该方法在前面的章节中已经描述过。
为了限制解码器程序必须分配的最大内存数量,我们可以引用的不同的字符串对的数量是有限制的,并且它们的长度是有限制的:
- 字符串参考数量的最大值:15000(选择15,000的限制是因为从16384和以上,此时将需要三字节而不是两字节来存储引用值。)。
- 在参考表中存储的字符串的最大长度:总共250个字符。(之所以选择250个字符,是因为很少有字符串超过这个最大值。在内部,字符串表需要有252个字符的行大小,因为终端也必须存储。当通过索引访问表时,建议定义256个字符的行大小。)
当读取一个.o5m编码的文件时,我们使用一个有15000行,250+2个字符的引用表(出于性能考虑:使用256个字符)。我们遇到的每个字符串对都被复制到表中,只有一个例外:字符串对的长度超过250个字符,但不会被复制到表中。
如果表中没有空间容纳新的字符串对,那么最老的字符串对将被覆盖。该表作为FIRO(先进,随机输出)。地址以1开头,表示“最新存储字符串”。地址2表示“第二个最新存储字符串”,等等。
注意,字符串通常存储为字符串对,即使定义为单个字符串。
下面是一个如何存储几个字符串的示例:
"oneway"/"yes"
"atm"/"no"
"oneway"/"yes"
uid=1020,"John"
"atm"/"no"
"oneway"/"yes"
uid=1020,"John"
它们的编码如下:
0x00 0x6f 0x6e 0x65 0x77 0x61 0x79 0x00 0x79 0x65 0x73 0x00
0x00 0x61 0x74 0x6d 0x00 0x6e 0x6f 0x00
-
0x02
(倒回2个的引用) -
0x00 0xfc 0x07 0x00 0x4a 0x6f 0x68 0x4e 0x00
(Varint + UTF8) -
0x02
(倒回2个的引用,Varint+UTF8的不算) 0x03
-
0x01
(倒回1个Varint+UTF8的引用)
2.2 File
每一个.o5m文件以字节0xff
开始,以字节0xfe
结束。每个数据集从一个特定的字节开始:
-
0x10
:node -
0x11
:way -
0x12
:relation -
0xdb
:bounding box -
0xdc
:file timestamp -
0xe0
:头,应该紧跟文件的第一个0xff;内容为:0x04 0x6f 0x35 0x6d 0x32
("o5m2"),或者对于.o5m变更文件是0x04 0x6f 0x35 0x63 0x32
("o5c2") -
0xee
:Sync -
0xef
:Jump -
oxff
:(不是数据集)在文件内容的某处:重置。
每个数据集的第二个字节,如果需要,因为值大于127,则下面的字节包含长度信息,编码为无符号数(见上文)。如果解码程序不理解一个特定的数据集,它将跳过它的内容。长度信息不包括长度字节本身,也不包括数据集的开始字节。
注意,在从0xf0
到0xff
的范围内没有长度信息,因此程序必须跳过一个字节。
2.3 Node
每个节点数据集从0x10
开始,然后是数据集长度
和数据
。数据字段:
ox10 | 数据集长度 | 数据 |
---|
id (有符号,增量编码)
-
0x00
或版本信息:版本 (无符号)
时间戳(自1970年以来的秒,有符号,增量编码)
-
作者信息 - 仅当时间戳不为0时:
- 变更集(有符号,增量编码)
- uid,user (字符串对)
经度(有符号,增量编码)
纬度(有符号,增量编码)
-
标签:
- key, val (字符串对)
- key, val (字符串对)
- ...
例如:
0x10
- 节点0x21
- 此节点的以下数据的长度:33字节0xce 0xad 0x0f
- id: 0+125799 = 1257990x05
- version:50xe4 0x8e 0xa7 0xca 0x09
- 时间戳: 2010-09-30T19:23:30Z0x94 0xfe 0xd2 0x05
- 变更集:0+5922698 = 5922698-
0x00
- 字符串对:-
0x85 0xe3 0x02 0x00
- uid: 45445 -
0x55 0x53 0x63 0x68 0x61 0x00
- user:"UScha"
-
0x86 0x87 0xe6 0x53
- 经度: 0+8.7867843 = 8.78678430xcc 0xe2 0x94 0xfa 0x03
- 纬度: 0+53.0749606 = 53.07496060x10
– 节点0x0d
- 此节点的以下数据的长度:13字节0x02
– id: 125799+1 = 1258000x0a
– 版本号: 100xd2 0x1f
– 时间戳(增量编码): 2010-09-30T19:23:30Z+2025s=2010-09-30T19:57:15Z0xe2 0x04
– 变更集(增量编码): 5922698+305 = 5923003-
0x01
– 字符串对: 引用 前1- uid: 45445
- user: "UScha"
0x89 0xae 0x03
– 经度(增量编码): 8.7867843-27525 = 8.78403180xe5 0xd8 0x03
- 纬度(增量编码):53.0749606-0.0030259 = 53.0719347
这个例子的OSM XML格式文件如下:
注意,可以通过减少数据集的长度(数据集的第二字节)来缩短数据集。解码程序必须处理这样的剪切数据集,并接受它们不包含键/val对、纬度/经度,甚至不包含作者信息。如果数据集以这样的方式被剪切,只剩下它的正文(id和可能的版本和作者信息),程序应该执行一个delete操作,并使用这个id删除对象(参见下面,section .o5c)。
2.4 Way
数据集从
0x11
开始,然后是数据集长度
和数据
。数据字段:
id (有符号,增量编码)
-
0x00
或版本信息:版本 (无符号)
时间戳(自1970年以来的秒,有符号,增量编码)
-
作者信息 - 仅当时间戳不为0时:
- 变更集(有符号,增量编码)
- uid,user(字符串对)
引用部分的长度(无符号)
-
节点引用部分(如果长度>0)
- 引用节点的id(有符号,delta编码)
- 引用节点的id(有符号,delta编码)
- ...
-
标签:
- key, val (字符串对)
- key, val (字符串对)
- ...
例如:
-
0x11
- 路线 -
0x20
- 此路线的以下数据长度:32字节 -
0xec 0x9b 0xe8 0x03
– id: 0+3999478 = 3999478 -
0x00
-没有版本和作者信息 -
0x07
-节点引用区域的长度: 7字节-
0xce 0xb9 0xfe 0x13
- 引用节点:0+20958823 = 20958823 -
0xce 0xeb 0x01
– 引用节点(增量编码): 20958823+15079=20973902
-
-
0x00
- 字符串对-
0x68 0x69 0x67 0x68 0x77 0x61 0x79 0x00
- key:"highway" -
0x73 0x65 0x63 0x6f 0x6e 0x64 0x61 0x72 0x79 0x00
- val: "secondary"
-
OSM XML格式的例子:
2.5 Relation
每个关系数据集从
0x12
开始,然后是数据集长度
和数据
。数据字段:
id (有符号,增量编码)
-
0x00
或版本信息:版本 (无符号)
时间戳(自1970年以来的秒,有符号,增量编码)
-
作者信息 - 仅当时间戳不为0时:
- 变更集(有符号,增量编码)
- uid,user(字符串对)
引用部分的长度(无符号)
-
引用部分(如果长度>0)
- 引用信息:
- 被引用对象的id(有符号,delta编码)
- 对象类型(“0”..“2”)与角色连接(单个字符串)
- 引用信息:
- 被引用对象的id(有符号,delta编码)
- 对象类型(“0”..“2”)与角色连接(单个字符串)
- ...
- 引用信息:
-
标签:
- key, val (字符串对)
- key, val (字符串对)
- ...
例如:
- 0x12 - 关系
- 0x28 - 该关系的以下数据长度:40个字节。
- 0x90 0x2e - id: 0+2952 = 2952
- 0x00 - 无版本号和作者信息
- 0x11 - 引用部分的长度:17字节
- 引用信息:
- 0xf4 0x98 0x83 0x0b - id: 0+11560506 = 11560506
- 0x00 - string pair:
- 0x31 - 类型:路线
- 0x69 0x6e 0x6e 0x65 0x72 0x00 - 角色:"inner"
- 引用信息:
- 0xca 0x93 0xd3 0x0d - id(增量编码): 11560506+14312677=25873183
- 0x01 - string pair:引用 前1
- 类型:路线
- 角色:"inner"
- 引用信息:
- 0x00 - 字符串对:
- 0x74 0x79 0x70 0x65 0x00 - key:"type"
-
0x6d 0x75 0x6c 0x74 0x69 0x70 0x6f 0x6c 0x79 0x67 0x6f 0x6e 0x00
- val:"multipolygon"
OSM XML格式的例子:
2.6 File Timestamp
这是一个可选的数据集。它以
0xdc
开始,然后是文件的时间戳
。该单位从1970年1月1日开始的秒数:
- 文件时间戳 (有符号型)
如果该数据集用于文件,那么它必须存储在任何OSM对象之前,即在任何节点、路线或关系之前。
2.7 Bounding Box
这个(可选的)数据集从
0xdb
开始,然后是数据集长度
和边界框坐标
。
- 西南角
- x1(经度,有符号)
- y1(纬度,有符号)
- 东北角
- x1(经度,有符号)
- y1(纬度,有符号)
类似于文件时间戳(见上面),必须在任何OSM对象之前(即在任何节点、路线或关系之前)存储该文件。
2.8 Reset
重置字节
0xff
告诉编码器重新设置计数器。此时,每个增量编码将从0开始,没有使用字符串引用,这将在重置字节之前引用文件内容。
这个机制是它允许.o5m文件通过不同的线程并行处理。通常,至少开始的路线和关系部分将由重置字节开始。
注意,0xff
不启动数据集;因此,没有长度信息会跟随0xff
。
2.9 Sync
如果您需要将OSM数据作为流处理,您将需要一些您可以同步的位置。这些同步点在解析32位零的数据流时可以找到它们。每个同步数据集必须有一个重置字节。否则,如果使用增量编码,您将无法解码随后存储的OSM数据集。语法如下:
0xee 0x07 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xff
如果开发一个不使用这个同步机制的解析器程序,您不需要关心同步数据集,因为这些数据集将被识别为“未知数据集”,因此被忽略。
2.10 Jump
要获得随机访问,而不仅仅是访问其中一个节的开头(节点的开始,路线的开始,关系的开始),你可以定义额外的跳转点作为跳转数据集。这些跳转点允许您快速地在文件中向前和向后移动。
跳转数据集以0xef
开始,然后是数据集长度
和数据
。数据字段:
- 到下一个跳转数据集的距离(从
0xef
到0xef
字节,无符号) - 到上一个跳转数据集的距离(从
0xef
到0xef
字节,无符号) - 未使用的内存空间的一些字节(如果稍后输入距离,并且在写入Jump数据集时这些值的空间未知)必须设置为
0xff
。
每个跳转数据集必须接着一个重置字节。
否则,如果使用增量编码,您将无法解码随后存储的OSM数据集。例如:
-
0xef 0x07 0x80 0x04 0x40 0xff 0xff 0xff 0xff 0xff
– 512 向前, 64 向后
如果开发一个不使用这个跳转机制的解析器程序,您不需要关心跳转数据集,因为这些数据集将被识别为“未知数据集”,因此被忽略。
3.File Size Comparison
OSM数据文件的大小取决于所使用的格式。来自geofabrik.de的2011- 5 -12德国文件被用作这种比较的基础。压缩算法.gz和.7z是用默认参数(中等压缩强度)完成的。pbf使用内部的zlib压缩。
OSM File Format and Compression Type | Size in Bytes | Relative Size | Reading Time (slow computer) |
---|---|---|---|
germany.osm | 15,519,707,799 | 100.0 % | 604 s |
germany.osm.bz2 | 1,442,403,577 | 9.3 % | |
germany.o5m | 1,469,972,938 | 9.5 % | 36 s |
germany.o5m.gz | 949,544,868 | 6.1 % | |
germany.o5m.7z | 851,845,615 | 5.5 % | |
germany.pbf | 948,980,117 | 6.1 % | 90 s |
如果您丢弃元数据(时间戳、用户名等),文件大小将减少:
OSM File Format and Compression Type | Size in Bytes | Relative Size | Reading Time (slow computer) |
---|---|---|---|
germany.osm (excl. meta data) | 7,937,414,195 | 51.1 % | |
germany.o5m (excl. meta data) | 1,046,676,637 | 6.7 % | |
germany.o5m.gz (excl. meta data) | 730,187,675 | 4.7 % | |
germany.o5m.7z (excl. meta data) | 647,320,555 | 4.2 % | |
germany.pbf (excl. meta data) | 697,041,975 | 4.5 % |
4.Further Information
4.1 Why that strange Name?
之所以选择o5m,是因为它看起来有点像osm。数字5代表“比。osm小5倍”。同时,我们知道因子是10,而不是5。但是,o10m的声音会有多傻呢?
4.2 .5c as OSM Change Format
你很容易使用.o5m格式来存储OSM更改文件。文件的数据结构没有区别,只有一个例外:文件头包含“o5c2”而不是“o5m2”。对于文件名,建议使用扩展名.o5c代替.o5m。
我们如何处理
4.3 Future Extensions
如果由于新的OSM api,数据格式需要额外的需求,我们会怎么做?这不应该是个问题。有239个o5m数据集id(范围0x01到0xdf);目前,只有3个在使用:0x10用于节点,0x11用于路线,0x12用于关系。Varint数格式和字符串格式也可以用于任何目的。
4.4 Is there an Example Implementation?
文件格式已被完全描述,因此您可以使用任何您喜欢的编程语言从零开始实施.o5m读写器。 不过,看一下.o5m阅读器示例代码(用C语言编写)或osmconvert,osmfilter等的源代码可能是有用的。
5. Software supporting .o5m
数据格式非常新,所以这个列表非常短。
5.1 Programs already support .o5m
- osmconvert - .osm,.osc,.osh,.pbf, .05m, .o5c格式之间的转换,应用更改文件和边界多边形,创建diffs。
- osmfilter - 筛选OSM对象和标记,考虑层次依赖性。
- osmupdate - 通过互联网下载更新OSM数据文件。
- navit map tool - 将OSM数据导入到navit。
- osm2po - OSM数据导入osm2po路由引擎。
- osm2pgsql - 将OSM数据导入到PostgreSQL数据库中。
- o5mreader - 以O5M格式解析OpenStreetMap数据的C库。
- mkgmap - 为Garmin设备绘制来自OpenStreetMap的地图
- splitter – 瓷砖为mkgmap分配器
- o5m4j - 由Java写的o5m数据阅读器
- OSMemory - Java库验证器
- phyghtmap - 生成等高线.osm,.pbf和.o5m来自NASA SRTM和类似的数据。
5.2 Toolchains using .o5m
- Daily update an OSM XML file
- openptmap (公共交通地图)
- OpenLinkMap
- OpenFireMap
- navit 数据更新
- MAPS.ME