首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何从.osm xml文件中提取关系成员

如何从.osm xml文件中提取关系成员
EN

Stack Overflow用户
提问于 2021-08-02 13:10:40
回答 1查看 190关注 0票数 0

全,

我一直在尝试建立一个网站(在Django),这是一个索引,在世界上所有的MTB路线。我是Python人,所以只要有可能,我都会尝试使用Python。

我已经成功地从OSM (Display relation (trail) in leaflet)中提取了数据,但发现对所有MTB跟踪(tag: route=mtb)执行此操作的数据量太大(处理时间非常长)。因此,我尝试在本地完成所有操作,方法是(从Latest Weekly Planet XML File)下载整个OpenStreetMap数据集的洪流,并使用osmfilter (Ubuntu20.04中的osmctools的一部分)过滤tag: route=mtb,如下所示:

代码语言:javascript
复制
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笔记本内核崩溃。我猜数据太大了。

我的第二种方法是这个解决方案:How to extract and visualize data from OSM file in Python。这对我来说很有效,至少,我可以获得一些信息,比如文件中关系的标签(以及其他指定的细节)。我遗漏的是关系成员(方式),然后是方式成员(节点)和它们的纬度/经度。我需要这些来实现我在这里所做的事情:Plotting OpenStreetMap relations does not generate continuous lines

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

任何帮助都是非常感谢的。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-11-14 14:03:59

好吧,我想出了如何获取我想要的东西(以一种可访问的方式存储每个"route=mtb“类型的关系的所有信息),这是一个多步骤的过程,我将在这里描述它。

首先,我下载了world文件(转到wiki.openstreetmap.org/wiki/Planet.osmopened the xml of the pbf file,并将world文件下载为.pbf ( Linux上的所有内容,下面将该文件称为$osm_planet_file )。

我使用osmconvert将此文件转换为o5m (在Ubuntu20.04中通过执行apt install osmctools,在Linux:

代码语言:javascript
复制
osmconvert --verbose --drop-version $osm_planet_file -o=$osm_planet_dir/planet.o5m

下一步是从这个文件中过滤出所有感兴趣的关系(在我的例子中,我想要所有MTB路由:route=mtb),并将它们存储在一个新文件中,如下所示:

代码语言:javascript
复制
osmfilter $osm_planet_dir/planet.o5m --keep="route=mtb" -o=$osm_planet_dir/world_mtb_routes.o5m

这将创建一个小得多的文件,其中包含作为MTB布线的关系的所有信息。

从那时起,我转而使用Jupyter笔记本,并使用Python3进一步将文件划分为有用的、可管理的块。我首先使用conda安装了osmium (在我首先创建的env中,可以跳过):

代码语言:javascript
复制
conda install -c conda-forge osmium

然后我创建了一个推荐的osm.SimpleHandle类,这个类遍历大的o5m文件,在执行此操作时它可以执行操作。这是处理这些文件的方法,因为它们的内存太大了。我选择遍历该文件并将所需的所有内容存储到单独的json文件中。这确实会生成超过12.000个json文件,但它可以在我的笔记本电脑上用8 GB的内存完成。这是一个类:

代码语言:javascript
复制
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")

像这样运行这个类:

代码语言:javascript
复制
osmhandler = OSMHandler()
osmhandler.apply_file("../data/world_mtb_routes.o5m")

现在我们有了json文件,文件名是关系号,包含所有的元数据,还有一个方法列表。但是我们需要一个路线列表,然后是每条路线的所有节点,这样我们就可以绘制完整的关系( MTB路线)。为此,我们再次解析o5m文件(使用在osm.SimpleHandler类上构建的类),这一次我们提取所有的成员(节点),并创建一个字典:

代码语言:javascript
复制
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")

执行类:

代码语言:javascript
复制
osmhandler = OSMHandler()
osmhandler.apply_file("../data/world_mtb_routes.o5m")
ways = osmhandler.osm_data

这给出了所有方法的关键字和节点ID(!意思是我们需要更多步骤!)作为值。

代码语言:javascript
复制
len(ways.keys())
>>> 337597

在下一步(也是几乎最后一步)中,我们将所有方法的节点In添加到我们的关系json中,以便它们成为文件的一部分:

代码语言:javascript
复制
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的类:

代码语言:javascript
复制
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]

执行类:

代码语言:javascript
复制
h = CounterHandler()
h.apply_file("../data/world_mtb_routes.o5m")
nodes = h.osm_data

我们可以在我们的json文件上使用它来填充坐标(现在仍然只有节点ID ),我在一个新的目录中创建了这些最终的json文件(在我的例子中是data/ with _coords),因为如果出现错误,我的原始(输入) json文件不会受到影响,我可以重试:

代码语言:javascript
复制
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数据库中,但这超出了这个问题的范围。

顺便说一句,有一些关系给出了错误,我怀疑对于一些关系方式被标记为节点,但我不确定。如果我发现了,我会在这里更新。我也必须定期进行这个过程(当world文件更新时,或者每隔一段时间),所以我可能会在以后写一些更简洁的东西,但现在这是有效的,步骤是可以理解的,至少对我来说是经过很多思考的。

所有的复杂性都来自于数据不足以容纳内存的事实,否则我会在第一步中创建一个pandas.DataFrame并使用它。也许我也可以一次性将数据加载到数据库中,但我对数据库还不是很在行。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68622198

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档