查看: 1607|回复: 0

OSM和PBF数据格式说明

[复制链接]
发表于 2022-9-25 11:50:53 | 显示全部楼层 |阅读模式
从openstreetmap上下载的数据都是*.osm后缀的,虽然后缀是osm,但它的格式其实是xml的,而pbf则是一种xml的压缩格式。

OSM和PBF数据格式说明-1523

一、osm格式

复制于:http://wiki.citydatum.com/index.php/OpenStreetMap,这个网页对openstreetmap有一个整体介绍,包括openstreetmap的发展和现状等等,这里只摘抄跟数据格式有关的内容,并补充了点内容。
1.数据格式

OSM使用拓扑数据结构,包括四类核心要素(aka. data primitives):
Node(节点):地理位置点,以经纬度坐标形式存储,通常代表没有尺寸的要素,比如POI(Points Of Interest,兴趣点)或山峰的制高点;
Way(通道):一系列节点的列表,可以是多义线,当形成闭合路径时也可以是面;既可以代表线性要素,如街道、河流等,也可以代表面状要素,如森林、公园、停车场、湖泊等;
Relation(关系):节点、通道、关系的有序列表,合在一起叫做“member(成员)”,member可以有“role(地位)”属性(字符串格式);Relation用来表示已存在的节点和通道的相互关系,比如道路的转弯限制,高速公路跨越多条普通道路(并不相交),以及中间有洞的环形面等;
Tag(标签):关键值,键名、键值均为字符串格式,用于存储地图要素的元数据,比如类型、名称、物理属性等;标签是独立要素,但总是附着到地图要素(节点、通道、关系), 推荐的各类地图要素的标签定义及含义在一个wiki网站上进行维护(https://wiki.openstreetmap.org/wiki/Tags)。
OSM采用WGS 84(EPSG:4326)地理坐标系。
OSM原始数据格式为PBF、XML;PBF压缩率更高,更便于下载。
OSM数据很大程度上依赖于本地参与者的贡献,因而地图数据的质量在不同地区差异很大。
2.数据获取

OpenStreetMap官网:可以导出当前地图浏览范围涉及的数据,导出格式为OSM XML。
OSM星球:提供每周更新的完整OSM数据库副本,包括最新数据、历史数据(包括早期版本及已删除的数据),格式包括OSM XML、OSM PBF等,2017年9月最新XML数据库文件大小约为60G。
Geogfabrik:定期更新的洲、 国家和特定城市数据库,格式包括OSM XML、OSM PBF、ESRI Shape文件等(https://download.geofabrik.de/)。
Mapzen城市摘录:世界主要城市和周边地区的摘录,格式包括OSM XML、OSM PBF、ESRI Shape文件、GeoJSON等。
ArcGIS OSM编辑器:Arcmap的免费开源扩展插件,单次下载的地图范围有限,可通过切块下载解决,同时提供了一些编辑、分析工具。
QGIS:内置了OSM数据下载功能,点击菜单Vector ‣ OpenStreetMap ‣ Download data,输入需要下载的范围坐标即可,下载文件格式为OSM XML。
3.数据清理

图层拆分:OSM原始数据格式,仅区分点、线(面)等基本类型,数据使用前通常需要将不同类型的要素拆分成多个数据文件,如道路与河流,建筑物与水面等。
坐标转换:OSM数据采用地理坐标系,测量距离或与其他数据共同使用时,可能需要投影和转换坐标系。
4.格式转换

可利用多种工具,将OSM数据库转为其他格式的数据库:

OSM转PostgreSQL的工具:Osmosis、Osm2postgresql等;
利用Osmosis转换的基本流程:安装PostgreSQL、PostGIS、Osmosis;创建数据库,并创建Schema(模式);导入OSM数据至PostgreSQL数据库;
OSM转ArcGIS可用格式的工具:GeoConverter(网络应用)、 ArcGIS OSM编辑器等;
QGIS直接能打开*.osm文件,可以在QGIS中加载*.osm文件,右键图层,矢量图层另存为,将osm文件分别存储为shp、csv等。

OSM和PBF数据格式说明-239

5.总结

Osm文件也可以直接用文本文档打开,以读写xml数据的方式读写它,转成json、shp等。

二、PBF格式

PBF的难点在于它有压缩算法,稍微了解一下原理,真要读写PBF,还是用包吧,C++、java、python都有。
复制于:https://wiki.openstreetmap.org/wiki/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专用部分,见https://github.com/openstreetmap/osmosis,以及应用通用部分,见https://github.com/openstreetmap/OSM-binary。应用程序通用部分用于构建osmpbf.jar(在osmosis和其他基于java的PBF阅读器中使用),还包含PBF协议缓存定义的主定义(*.proto文件)。

1、PBF的软件支持

除了原始的XML格式之外,OSM项目中使用的许多软件已经支持PBF,而且还有一些工具可以将PBF转换为OSM XML,反之亦然。
请参阅PBF/软件合规性(https://wiki.openstreetmap.org/wiki/PBF/Software_Compliance),了解各种程序对PBF文件支持的详细信息。

2、设计

2-1.底层编码

GPB格式(Google Protocol Buffers,谷歌协议缓存)用于底层存储。给定一个包含一条或多条消息的规范文件,协议缓存编译器编写底层序列化代码。消息可以包含其他消息,形成层次结构。协议缓存也支持可扩展性,可以将新字段添加到消息中,旧客户端无需重新编译就可以读取这些消息。欲了解更多细节,请参阅https://github.com/protocolbuffers/protobuf/或阅读谷歌开源博客上的相关文章(https://opensource.googleblog.com/2008/07/protocol-buffers-googles-data.html)。谷歌官方支持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包中。
2-2.文件格式

一个文件包含一个文件头,后面跟着一系列文件块。该设计旨在允许将来对文件内容的随机访问,并跳过不理解或不需要的数据。
格式是一个重复的序列:

  • int4:以网络字节顺序表示的BlobHeader消息的长度
  • 序列化BlobHeader消息
  • 序列化的Blob消息(大小在文件头中给出)
一个BlobHeader当前定义为:
message BlobHeader {
   required string type = 1;
   optional bytes indexdata = 2;
   required int32 datasize = 3;
}


  • type包含此块消息中数据的类型。
  • indexdata是一些任意的blob,它可能包含关于以下blob的元数据(例如,对于OSM数据,它可能包含一个边界框)。这是一个存根,用于支持索引*.osm.pbf文件的未来设计。
  • 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说明见https://en.wikipedia.org/wiki/Byte#Multiple-byte_units),并且必须小于64kib。一个Blob的未压缩长度应该小于16mib(16*1024*1024字节,mib说明见https://en.wikipedia.org/wiki/Byte#Multiple-byte_units),并且必须小于32mib。
3. OSM实体编码为文件块

目前有两种OSM数据文件块类型。这些文本类型字符串存储在BlobHeader的type字段中:

  • OSMHeader:这个Blob包含一个序列化的HeaderBlock消息(参见osmformat.proto)。每个文件块在第一个“OSMData”块之前必须有一个这样的块。
  • OSMData:包含一个序列化的PrimitiveBlock消息(参见osmformat.proto),它们包含实体信息。
这种设计允许其他软件扩展该格式,以包含其他类型的文件块以满足自己的目的。解析器应该忽略和跳过它们不能识别的文件块类型。
3-1.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”—文件包含密集节点和密集信息。
  • “HistoricalInformation”—该文件包含OSM历史数据。
此外,一个文件可能有可供解析器利用的可选属性。例如,文件可能是预排序的,在使用之前不需要排序。或者,文件中的方法可能有预先计算好的边界框。如果程序遇到了它不知道的可选特性,它仍然可以安全地读取文件。如果一个程序需要一个可选的特性,而这个特性并不存在,那么它就会出错。下列特征需要被提出:

  • “Has_Metadata”—该文件包含作者和时间戳元数据。
  • “Sort.Type_then_ID”—实体按类型排序,然后按ID排序。
  • “Sort.Geographic”—实体是以某种几何形式存在的,(目前未使用)。
  • “timestamp=2011-10-16T15:45:00Z”—存储文件时间戳的临时解决方案,请使用osmosis_replication_timestamp代替。

3-1-1.复制字段的用途是什么?

osmosis_replication_*字段的目的是允许PBF文件的消费者附加来自osmosis管理的更新服务器的数据,以保持文件的当前状态。Osmosis(https://wiki.openstreetmap.org/wiki/Osmosis)是一种软件,用于产生http://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块一样,除非您后续不想用特定类型的处理方式更新这些文件。

3-2.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。
3-2-1. 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;
}
3-2-2.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];
}
4.格式示例

在下面,我们将查看OSM PBF文件的字节。以bremen.osm.pbf (http://download.geofabrik.de/, 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(解压) ---协议缓存库会处理所有这些底层编码细节。
5.代码

代码库被分成两部分,github上存在独立于应用程序的通用代码,包括协议缓存定义,以及在文件块级别操作的Java代码,http://github.com/scrosby/OSM-binary。
不过,osmosis、mkgmap和splitter都使用不同的OSM实体的内部表示。这意味着我必须为每个程序重新实现实体序列化和解析代码,实现之间的常见代码比我希望的要少,osmosis的序列化器和反序列化器在master分支中。
一个旧版本的mkgmap拆分器的反序列化器(2010-05)可以在github上找到,http://github.com/scrosby/OSM-splitter。
6.其他资料

6-1.下载

完整的(当前状态,无历史记录)PBF格式的osm数据可以在这里找到:https://planet.openstreetmap.org/pbf/。
其他可以下载OSM PBF格式文件的地方见,https://wiki.openstreetmap.org/wiki/Planet.osm。

7.另请参阅


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://osmcode.org/libosmium/)所使用:https://blog.jochentopf.com/2016-04-20-node-locations-on-ways.html
Mapbox PBF,mapbox的JavaScript库,用于解码和编码PBF文件:https://github.com/mapbox/pbf

8.总结

读写PBF,用现成的包吧,PBF格式很适合分层级渲染,因为它的数据补充的形式是添加,而非更新,效率会比较高,也会很流畅。
您需要登录后才可以回帖 登录 | 加入联盟

本版积分规则

快速回复 返回顶部 返回列表