如何从 .osm xml 文件中提取关系成员
How to extract relation members from .osm xml files
全部,
我一直在尝试建立一个网站(在 Django 中),该网站将成为世界上所有 MTB 路线的索引。我是一个 Python 人,所以无论我在哪里,我都会尝试使用 Python.
我已成功从 OSM API () but found that doing this for all MTB trails (tag: route=mtb) is too much data (processing takes very long). So I tried to do everything locally by downloading a torrent of the entire OpenStreetMap dataset (from Latest Weekly Planet XML File) 中提取数据并使用 osmfilter(Ubuntu 20.04 中 osmctools 的一部分)过滤标签:route=mtb,例如这个:
osmfilter $unzipped_osm_planet_file --keep="route=mtb" -o=$osm_planet_dir/world_mtb_routes.osm
这会生成一个大约 1.2 GB 的文件,仔细检查似乎包含了我需要的所有数据。我的目标是将文件转换为 pandas.DataFrame() 以便我可以在将相关方面推送到我的 Django 数据库之前进行一些进一步的过滤和转换。我尝试使用 Python Pandas 将文件作为常规 XML 文件加载,但这会使 Jupyter 笔记本内核崩溃。估计是数据太大了。
我的第二种方法是这个解决方案:How to extract and visualize data from OSM file in Python. It worked for me, at least, I can get some of the information, like the tags of the relations in the file (and the other specified details). What I'm missing is the relation members (the ways) and then the way members (the nodes) and their latitude/longitudes. I need these to achieve what I did here: Plotting OpenStreetMap relations does not generate continuous lines
我对许多解决方案持开放态度,例如,可以使用基于 osmium 的脚本将文件分成许多包含 1 个关系的不同文件,每个文件都有成员。也许那时我可以继续使用 pandas.read_xml()。这对于填充数据库的批处理非常有用。将整个 OSM XML 文件加载到 pd.DataFrame 中会很好,但我想这确实是很多数据。也许这也可以在每个关系的基础上与pyosmium一起完成?
感谢任何帮助。
好的,我想出了如何获得我想要的东西(所有信息都以“route=mtb”类型的关系存储在一个可访问的方式中),这是一个多步骤的过程,我将在这里描述它。
首先,我下载了世界文件(去 wiki.openstreetmap.org/wiki/Planet.osm, opened the xml of the pbf file 并将世界文件下载为 .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 class,这个class遍历了大的o5m文件,同时它可以做一些动作。这是处理这些文件的方法,因为它们对内存来说太大了。我选择遍历文件并将我需要的所有内容存储到单独的 json 文件中。这确实会生成超过 12.000 json 个文件,但它可以在我具有 8 GB 内存的笔记本电脑上完成。这是 class:
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")
运行 class 像这样:
osmhandler = OSMHandler()
osmhandler.apply_file("../data/world_mtb_routes.o5m")
现在我们有 json 个文件,文件名是关系号,所有元数据和路径列表。但是我们想要一个路径列表,然后是每条路径的所有节点,这样我们就可以绘制完整的关系(山地车路线)。为此,我们再次解析 o5m 文件(使用 osm.SimpleHandler class 上的 class 构建),这次我们提取所有方式成员(节点),并创建一个字典:
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")
执行 class:
osmhandler = OSMHandler()
osmhandler.apply_file("../data/world_mtb_routes.o5m")
ways = osmhandler.osm_data
这给出了所有方式的字典(称为方式)作为键和节点 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?
所以现在我们有关系 jsons 与所有方式,所有方式都有所有节点 ID,最后要做的是用它们的值(纬度和经度)替换节点 ID。我也分两步做了这个,首先我建立了一个 nodeID:lat/lon 字典,再次使用基于 osmium.SimpleHandler 的 class :
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]
执行 class:
h = CounterHandler()
h.apply_file("../data/world_mtb_routes.o5m")
nodes = h.osm_data
这为我们提供了包含每个节点 ID 的 latitude/longitude 对的字典。我们可以在我们的 json 文件上使用它来用坐标填充路径(现在仍然只有节点 ID),我在新目录中创建这些最终的 json 文件(data/with_coords我的情况)因为如果出现错误,我的原始(输入)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 并完成它。也许我也可以一次性将数据加载到数据库中,但我对数据库还不是很好。
全部,
我一直在尝试建立一个网站(在 Django 中),该网站将成为世界上所有 MTB 路线的索引。我是一个 Python 人,所以无论我在哪里,我都会尝试使用 Python.
我已成功从 OSM API (
osmfilter $unzipped_osm_planet_file --keep="route=mtb" -o=$osm_planet_dir/world_mtb_routes.osm
这会生成一个大约 1.2 GB 的文件,仔细检查似乎包含了我需要的所有数据。我的目标是将文件转换为 pandas.DataFrame() 以便我可以在将相关方面推送到我的 Django 数据库之前进行一些进一步的过滤和转换。我尝试使用 Python Pandas 将文件作为常规 XML 文件加载,但这会使 Jupyter 笔记本内核崩溃。估计是数据太大了。
我的第二种方法是这个解决方案:How to extract and visualize data from OSM file in Python. It worked for me, at least, I can get some of the information, like the tags of the relations in the file (and the other specified details). What I'm missing is the relation members (the ways) and then the way members (the nodes) and their latitude/longitudes. I need these to achieve what I did here: Plotting OpenStreetMap relations does not generate continuous lines
我对许多解决方案持开放态度,例如,可以使用基于 osmium 的脚本将文件分成许多包含 1 个关系的不同文件,每个文件都有成员。也许那时我可以继续使用 pandas.read_xml()。这对于填充数据库的批处理非常有用。将整个 OSM XML 文件加载到 pd.DataFrame 中会很好,但我想这确实是很多数据。也许这也可以在每个关系的基础上与pyosmium一起完成?
感谢任何帮助。
好的,我想出了如何获得我想要的东西(所有信息都以“route=mtb”类型的关系存储在一个可访问的方式中),这是一个多步骤的过程,我将在这里描述它。
首先,我下载了世界文件(去 wiki.openstreetmap.org/wiki/Planet.osm, opened the xml of the pbf file 并将世界文件下载为 .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 class,这个class遍历了大的o5m文件,同时它可以做一些动作。这是处理这些文件的方法,因为它们对内存来说太大了。我选择遍历文件并将我需要的所有内容存储到单独的 json 文件中。这确实会生成超过 12.000 json 个文件,但它可以在我具有 8 GB 内存的笔记本电脑上完成。这是 class:
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")
运行 class 像这样:
osmhandler = OSMHandler()
osmhandler.apply_file("../data/world_mtb_routes.o5m")
现在我们有 json 个文件,文件名是关系号,所有元数据和路径列表。但是我们想要一个路径列表,然后是每条路径的所有节点,这样我们就可以绘制完整的关系(山地车路线)。为此,我们再次解析 o5m 文件(使用 osm.SimpleHandler class 上的 class 构建),这次我们提取所有方式成员(节点),并创建一个字典:
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")
执行 class:
osmhandler = OSMHandler()
osmhandler.apply_file("../data/world_mtb_routes.o5m")
ways = osmhandler.osm_data
这给出了所有方式的字典(称为方式)作为键和节点 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?
所以现在我们有关系 jsons 与所有方式,所有方式都有所有节点 ID,最后要做的是用它们的值(纬度和经度)替换节点 ID。我也分两步做了这个,首先我建立了一个 nodeID:lat/lon 字典,再次使用基于 osmium.SimpleHandler 的 class :
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]
执行 class:
h = CounterHandler()
h.apply_file("../data/world_mtb_routes.o5m")
nodes = h.osm_data
这为我们提供了包含每个节点 ID 的 latitude/longitude 对的字典。我们可以在我们的 json 文件上使用它来用坐标填充路径(现在仍然只有节点 ID),我在新目录中创建这些最终的 json 文件(data/with_coords我的情况)因为如果出现错误,我的原始(输入)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 并完成它。也许我也可以一次性将数据加载到数据库中,但我对数据库还不是很好。