好的,我想出了如何获得我想要的东西(以可访问的方式存储的“route = mtb”类型的每个关系的所有信息),这是一个多步骤的过程,我将在这里进行描述。
首先,我下载了世界文件(去wiki.openstreetmap.org/wiki/Planet.osm,打开pbf文件的xml并将世界文件下载为.pbf(Linux上的一切,这个文件被称为$ osm_planet_file 下)。
我使用 osmconvert 将此文件转换为 o5m (在 Ubuntu 20.04 中可用apt install osmctools
,在 Linux cli 上执行:
osmconvert --verbose --drop-version $osm_planet_file -o=$osm_planet_dir/planet.o5m
下一步是从该文件中过滤出所有感兴趣的关系(在我的情况下,我想要所有 MTB 路线:)route=mtb
并将它们存储在一个新文件中,如下所示:
osmfilter $osm_planet_dir/planet.o5m --keep="route=mtb" -o=$osm_planet_dir/world_mtb_routes.o5m
这将创建一个小得多的文件,其中包含有关 MTB 路线关系的所有信息。
从那以后,我切换到 Jupyter 笔记本并使用 Python3 将文件进一步划分为有用的、可管理的块。我首先使用 conda 安装了 osmium(在我首先创建的环境中,但可以跳过):
conda install -c conda-forge osmium
然后我做了一个推荐的 osm.SimpleHandle 类,这个类遍历大的 o5m 文件,同时它可以做一些动作。这是处理这些文件的方法,因为它们对内存来说太大了。我选择遍历文件并将所需的所有内容存储到单独的 json 文件中。这确实会生成超过 12.000 个 json 文件,但可以在我的 8 GB 内存的笔记本电脑上完成。这是课程:
import osmium as osm
import json
import os
data_dump_dir = '../data'
class OSMHandler(osm.SimpleHandler):
def __init__(self):
osm.SimpleHandler.__init__(self)
self.osm_data = []
def tag_inventory(self, elem, elem_type):
for tag in elem.tags:
data = dict()
data['version'] = elem.version,
data['members'] = [int(member.ref) for member in elem.members if member.type == 'w'], # filter nodes from waylist => could be a mistake
data['visible'] = elem.visible,
data['timestamp'] = str(elem.timestamp),
data['uid'] = elem.uid,
data['user'] = elem.user,
data['changeset'] = elem.changeset,
data['num_tags'] = len(elem.tags),
data['key'] = tag.k,
data['value'] = tag.v,
data['deleted'] = elem.deleted
with open(os.path.join(data_dump_dir, str(elem.id)+'.json'), 'w') as f:
json.dump(data, f)
def relation(self, r):
self.tag_inventory(r, "relation")
像这样运行类:
osmhandler = OSMHandler()
osmhandler.apply_file("../data/world_mtb_routes.o5m")
现在我们有了 json 文件,其中关系号作为文件名,所有元数据,以及方法列表。但是我们想要一个路径列表,然后是每个路径的所有节点,所以我们可以绘制完整的关系(MTB 路线)。为此,我们再次解析 o5m 文件(使用构建在 osm.SimpleHandler 类上的类),这次我们提取所有路径成员(节点),并创建一个字典:
class OSMHandler(osm.SimpleHandler):
def __init__(self):
osm.SimpleHandler.__init__(self)
self.osm_data = dict()
def tag_inventory(self, elem, elem_type):
for tag in elem.tags:
self.osm_data[int(elem.id)] = dict()
# self.osm_data[int(elem.id)]['is_closed'] = str(elem.is_closed)
self.osm_data[int(elem.id)]['nodes'] = [str(n) for n in elem.nodes]
def way(self, w):
self.tag_inventory(w, "way")
执行类:
osmhandler = OSMHandler()
osmhandler.apply_file("../data/world_mtb_routes.o5m")
ways = osmhandler.osm_data
这给出了所有方式的dict(称为方式)作为键和节点ID(!意味着我们需要更多步骤!)作为值。
len(ways.keys())
>>> 337597
在下一步(几乎是最后一步)中,我们将所有方式的节点 ID 添加到我们的关系 json 中,因此它们成为文件的一部分:
all_data = dict()
for relation_file in [
os.path.join(data_dump_dir,file) for file in os.listdir(data_dump_dir) if file.endswith('.json')
]:
with open(relation_file, 'r') as f:
data = json.load(f)
if 'members' in data: # Make sure these steps are never performed twice
try:
data['ways'] = dict()
for way in data['members'][0]:
data['ways'][way] = ways[way]['nodes']
del data['members']
with open(relation_file, 'w') as f:
json.dump(data, f)
except KeyError as err:
print(err, relation_file) # Not sure why some relations give errors?
所以现在我们有了所有方式的关系 json,所有方式都有所有节点 ID,最后要做的就是用它们的值(纬度和经度)替换节点 ID。我也分两步完成了这项工作,首先我构建了一个 nodeID:lat/lon 字典,再次使用基于 osmium.SimpleHandler 的类:
import osmium
class CounterHandler(osmium.SimpleHandler):
def __init__(self):
osmium.SimpleHandler.__init__(self)
self.osm_data = dict()
def node(self, n):
self.osm_data[int(n.id)] = [n.location.lat, n.location.lon]
执行类:
h = CounterHandler()
h.apply_file("../data/world_mtb_routes.o5m")
nodes = h.osm_data
这为我们提供了每个节点 ID 的纬度/经度对的 dict。我们可以在我们的 json 文件上使用它来用坐标填充路径(现在仍然只有节点 ID),我在一个新目录(在我的例子中是 data/with_coords)中创建这些最终的 json 文件,因为如果出现错误,我的原始(输入)json 文件不受影响,我可以再试一次:
import os
relation_files = [file for file in os.listdir('../data/') if file.endswith('.json')]
for relation in relation_files:
relation_file = os.path.join('../data/',relation)
relation_file_with_coords = os.path.join('../data/with_coords',relation)
with open(relation_file, 'r') as f:
data = json.load(f)
try:
for way in data['ways']:
node_coords_per_way = []
for node in data['ways'][way]:
node_coords_per_way.append(nodes[int(node)])
data['ways'][way] = node_coords_per_way
with open(relation_file_with_coords, 'w') as f:
json.dump(data, f)
except KeyError:
print(relation)
现在我有了我需要的东西,我可以开始将信息添加到我的 Django 数据库中,但这超出了这个问题的范围。
顺便说一句,有些关系会出错,我怀疑对于某些关系方式被标记为节点,但我不确定。如果我发现了,我会在这里更新。我还必须定期执行此过程(当世界文件更新时,或不时更新)所以我可能会在以后写一些更简洁的东西,但现在这个工作和步骤对我来说是可以理解的,经过很多至少在思考。
所有的复杂性都来自于数据不足以容纳内存的事实,否则我会在第一步中创建一个 pandas.DataFrame 并完成它。我也可以一次性将数据加载到数据库中,但我对数据库还不是很好。