0

全部,

我一直在尝试建立一个网站(在 Django 中),该网站将成为世界上所有 MTB 路线的索引。我是 Pythonian,所以无论我在哪里都可以尝试使用 Python。

我已经成功地从 OSM API 中提取了数据(在传单中显示关系(轨迹)),但发现对所有 MTB 轨迹(标签:路由 = mtb)执行此操作的数据太多(处理需要很长时间)。因此,我尝试通过下载整个 OpenStreetMap 数据集(来自最新的每周星球 XML 文件)的种子并使用 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 DB 之前进行一些进一步的过滤和转换。我尝试使用 Python Pandas 将文件加载为常规 XML 文件,但这使 Jupyter notebook 内核崩溃。估计数据太大了。

我的第二种方法是这个解决方案:如何在 Python 中从 OSM 文件中提取和可视化数据。它对我有用,至少,我可以获得一些信息,例如文件中关系的标签(以及其他指定的详细信息)。我缺少的是关系成员(方式),然后是方式成员(节点)及其纬度/经度。我需要这些来实现我在这里所做的:绘制 OpenStreetMap 关系不会生成连续线

我对许多解决方案持开放态度,例如,可以使用基于 osmium 的脚本将文件分解为许多不同的文件,其中包含 1 个关系及其每个文件的成员。也许那时我可以继续使用 pandas.read_xml()。这对于填充数据库的批处理非常有用。将整个 OSM XML 文件加载到 pd.DataFrame 会很好,但我想这确实是很多数据。也许这也可以在与 pyosmium 的每个关系的基础上完成?

任何帮助表示赞赏。

4

1 回答 1

1

好的,我想出了如何获得我想要的东西(以可访问的方式存储的“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 并完成它。我也可以一次性将数据加载到数据库中,但我对数据库还不是很好。

于 2021-11-14T14:03:59.300 回答