zoukankan      html  css  js  c++  java
  • Python环境下使用OpenStreetMap下载的.osm数据

    引言

    最近在项目中需要使用地理空间信息来辅助进行聚类工作,除了常规的经纬度信息之外,还需要更重要的地理层级信息,如对于“都江堰”来进行查询,期望获得“都江堰,成都,中国”这样一个完整的地理层级关系。因此,在这两天笔者便研究了一下如何获得这样的信息。

    使用geopy包来实现

    工程中用的是Python2,而在python中也确实有现有的包可以实现这样的功能,比如一个常用的包是geopy

    其使用方法如下:

    # -*- coding: utf-8 -*-
    from geopy.geocoders import Nominatim
    
    geolocator = Nominatim()
    location = geolocator.geocode("dujiangyan")
    
    print (location.address)
    

    程序输出:

    都江堰市, 都江堰市 / Dujiangyan, 成都市 / Chengdu, 四川省, 中国
    

    可以看到,确实输出了一串地理层级信息,而且其中也确实包含了我们想要的正确的结果,但是结构非常的不标准,这样的结构在之后的操作中,想要处理成方便程序使用的形式是有些困难的,因为有很多地名会输出各种意想不到的结果的形式。

    还有一点问题是,这个包是通过在线查询来返回结果的,而每次返回所需的时间大约是数秒级别的,如果是单次的查询,那么这个时间是完全可以接受的,而若大批量的查询,特别是需要实时效果的话,这种方法就难以获得理想的效果了。

    而在尝试过几个包之后,发现这个包的效果其实已经相对较好了。在以前曾经用过谷歌的地理信息查询服务,但是谷歌提供的接口目前开始收费了,所以笔者又把目光放向了开源的OpenStreetMap。

    下载OpenStreetMap的地图包

    OpenStreetMap是一款开源的,由网络大众共同打造的地图服务,而且是知名度最高、应用最为广泛的开源地图之一,在前一部分所介绍的geopy包里的一部分返回数据就是通过这个开源地图得到的。而且,OpenStreetMap由于是开源地图,是提供地图的下载的。

    要下载OSM上的地图,我们可以在这个网址直接下载:
    http://download.geofabrik.de/index.html

    进入页面之后,可以看到按大洲下载地图的链接,也可以从左边某个大洲点进去,下载某个国家的地图,如我们进入“Asia”,然后下载中国的地图。
    按大洲下载的地图
    下载中国地图
    可以看到,在下载中,有三个可选项,分别是.osm.pbf、.shp.zip、.osm.bz3,在这里,我们需要的是.osm文件中的信息,而第一项和第三项都是.osm文件的压缩形式,其中,.bz2是可以直接解压缩的,但是大小是.pbf的1.5到2倍左右。需要下载哪一项,大家可以自己斟酌。

    如果下载的是.pbf文件,是需要一个专门的工具来将.pbf文件转换成.osm文件的,这个工具可以在这里下载:https://wiki.openstreetmap.org/wiki/Osmconvert

    pbf文件转换

    工具本身非常小,下载下来之后,放入存储下载数据的文件夹(即存储.pbf文件的文件夹,推荐所在分区留出较多空间,因为可能占用较多空间),然后打开之后是一个命令行操作的界面。这时,可以用如下的命令直接进行转换:

    osmconvert syria-latest.osm.pbf --out-osm -o=syria-latest.osm_01.osm
    

    当然,工具本身也比较方便,无需记忆命令,先按照提示键入a,然后程序会询问要处理哪个文件,这时键入文件的全名,之后程序会询问需要对该文件进行什么操作,这时键入1,选择要对文件格式进行转换,最后在选择输出格式时,选择1,选择按照.osm格式输出,便可以得到我们需要的.osm格式的文件了。

    .osm文件中的数据

    .osm文件是OpenStreetMap专门用来封装自家数据的一种格式,里面可以按照XML格式的文件来进行读取。

    关于osm内部的结构,我参考了这篇文章:https://blog.csdn.net/scy411082514/article/details/7484497/

    OpenStreetMap的元素主要包括三种:点(Nodes)、路(Ways)和关系(Relations),这三种原始构成了整个地图画面。其中,Nodes定义了空间中点的位置;Ways定义了线或区域;Relations(可选的)定义了元素间的关系。

    而我们所需要的地理层级信息便在node字段中,其中也可获取到经纬度等信息,如“都江堰”字段内如下:

    {u'k': u'gns:ADM1', u'v': u'32'}
    {u'k': u'gns:DSG', u'v': u'ADM3'}
    {u'k': u'gns:UFI', u'v': u'-1907309'}
    {u'k': u'gns:UNI', u'v': u'10071801'}
    {u'k': u'is_in', u'v': u'Chengdu, Sichuan, China'}
    {u'k': u'is_in:continent', u'v': u'Asia'}
    {u'k': u'is_in:country', u'v': u'China'}
    {u'k': u'is_in:country_code', u'v': u'CN'}
    {u'k': u'name', u'v': u'u90fdu6c5fu5830u5e02'}
    {u'k': u'name:de', u'v': u'Dujiangyan'}
    {u'k': u'name:en', u'v': u'Dujiangyan'}
    {u'k': u'name:fr', u'v': u'Du016bjiu0101ngyxe0n'}
    {u'k': u'name:ja', u'v': u'u90fdu6c5fu5830u5e02'}
    {u'k': u'name:ru', u'v': u'u0414u0443u0446u0437u044fu043du044au044fu043du044c'}
    {u'k': u'name:vi', u'v': u'u0110xf4 Giang Yu1ec3n'}
    {u'k': u'name:zh', u'v': u'u90fdu6c5fu5830u5e02'}
    {u'k': u'name:zh_pinyin', u'v': u'Du016bjiu0101ngyxe0n Shi'}
    {u'k': u'place', u'v': u'city'}
    {u'k': u'wikidata', u'v': u'Q1023900'}
    {u'k': u'wikipedia', u'v': u'en:Dujiangyan City'}
    

    而其中的is_in字段,便是我们需要的地理层级信息了,如这一条中的{u'k': u'is_in', u'v': u'Chengdu, Sichuan, China'},便说明都江堰属于“中国,四川,成都,都江堰”,将其解析出来便可直接使用。

    将.osm文件中的数据转存到json中

    由于.osm中的数据是按照xml的形式存储的,若每次都从中读取数据的话,对于单个就达几个G甚至数十上百G的文件,若按照树的方式来进行解析,不光时间上难以接受,首先面临的就是内存不足的问题。

    对于.osm文件已经有专门的数据库可以来存储其中的信息,而在我们的工程中使用的是MongoDb数据库,为了便于以后的使用,和往我们的数据库里导入数据,这里我准备先将.osm文件转换到.json文件中。

    需要注意的是,在转换的过程中,我们是不能将整个文件完整地解析出来的,因为会占用极大的内存,在这里,我们可以采用递归的方法来进行处理。

    代码如下:

    # -*- coding: utf-8 -*-
    import json
    from lxml import etree
    import xmltodict
    
    def iter_element(file_parsed, file_length, file_write):
        current_line = 0
        try:
            for event, element in file_parsed:
                current_line += 1
                print current_line/float(file_length)
                elem_data = etree.tostring(element)
                elem_dict = xmltodict.parse(elem_data, attr_prefix="", cdata_key="")
                if (element.tag == "node"):
                    elem_jsonStr = json.dumps(elem_dict["node"])
                    file_write.write(elem_jsonStr + "
    ")
                # 每次读取之后进行一次清空
                element.clear()
                while element.getprevious() is not None:
                    del element.getparent()[0]
        except:
            pass
    
    if __name__ == '__main__':
        osmfile = r'D:datachina-latest.osm'
    
        file_length = -1
        for file_length, line in enumerate(open(osmfile, 'rU')):
            pass
        file_length += 1
        print "length of the file:	" + str(file_length)
    
        file_node = open(osmfile+"_node.json","w+")
        file_parsed = etree.iterparse(osmfile, tag=["node"])
        iter_element(file_parsed, file_length, file_node)
        file_node.close()
    
    

    这样,就可以将其中的node里的信息保存下来了,之后,我们可以将其中包含地理层级信息的部分筛选出来,经过观察,可以发现里面有大量只有一到两行的数据,其中数据是后面不会使用的,这里,我们可以将其筛出,代码如下:

    import json
    
    count = 0
    
    file_write = open(r'D:data	est.txt', mode = 'wb')
    with open(r'D:datachina-latest.osm_node.json', mode='rb') as file_read:
        for line in file_read:
            line = line.replace('
    ', '')
            info = json.loads(line)
            if 'tag' in info and type(info['tag']) is list and len(info['tag']) > 2:
                for item in info['tag']:
                    print item
                    file_write.write(str(item) + '
    ')
                print "-" * 60
                file_write.write("-" * 100 + '
    ')
            count += 1
    

    在上面这段代码中,仅仅是读取之前所得到的.json文件并将大于两行的数据打印出来并保存到一个txt文件中,后面可以改为其它操作。

  • 相关阅读:
    多线程与高并发常见面试题(1)
    LoadRunner 多用户并发 登录,上传数据,登出的脚本教程
    windows cmd 链接远程mysql服务器
    Ubuntu 16.04添加阿里云源
    sqlite 数据库与mysql 数据库使用区别记录
    jdk源码之 hashmap 与hashtable 的区别
    通过构造器启动线程的实现方式及其缺点记录。
    eclipse 中过滤空包,目录树中不显示。
    javascript中正则实现读取当前url中指定参数值方法。
    Reactjs+Webpack+es2015 入门HelloWord(一)
  • 原文地址:https://www.cnblogs.com/liulaolaiu/p/11744422.html
Copyright © 2011-2022 走看看