Skip to content
Snippets Groups Projects
io_scene_open_street_map.py 11.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • Campbell Barton's avatar
    Campbell Barton committed
    # ***** BEGIN GPL LICENSE BLOCK *****
    
    #
    # This program is free software; you can redistribute it and/or
    # modify it under the terms of the GNU General Public License
    # as published by the Free Software Foundation; either version 2
    # of the License, or (at your option) any later version.
    #
    # This program is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    # GNU General Public License for more details.
    #
    # You should have received a copy of the GNU General Public License
    # along with this program; if not, write to the Free Software Foundation,
    # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    #
    # ***** END GPL LICENCE BLOCK *****
    
    # <pep8 compliant>
    
    bl_info = {
        "name": "Open Street Map (.osm)",
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
        "author": "Michael Anthrax Schlachter, ideasman42, littleneo",
        "version": (0, 2),
    
        "blender": (2, 63, 0),
    
        "location": "File > Import",
        "description": "Load Open Street Map File",
    
        "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
    
        "category": "Import-Export"}
    
    # originally written for blender 2.4x by (manthrax _at_ hotmail.com),
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
    # updated for blender 2.6x by ideasman42
    
    # If you use it for something cool, send me an email and let me know!
    
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
    import bpy
    from mathutils import Vector, Matrix
    import math
    from math import radians, sin, cos, tan, sqrt
    
    
    Campbell Barton's avatar
    Campbell Barton committed
    # add more osm tags here.
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
    # http://wiki.openstreetmap.org/wiki/Map_Features
    osmkeys = [
        'highway',
        'barrier',
        'wall',
        'cycleway',
        'bicycle',
        'waterway',
        'railway',
        'aeroway',
        'aerialway',
        'power',
        'man_made',
        'building',
        'leisure',
        'amenity',
        'office',
        'shop',
        'craft',
        'emergency',
        'tourism',
        'historic',
        'landuse',
        'military',
        'natural',
    ]
    
    # just a try for nodes, for retrieving v tag values.
    # keyname must exists in osmkeys
    
    Campbell Barton's avatar
    Campbell Barton committed
    osmvals = {'highway': ['traffic_signals']}
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
    
    # vertex group name -> vertex group index lookup
    grouplookup = {}
    
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
    def parseBranch(nodes, bm, nmap, obj, scale=100.0, tag=False, UTM=False):
        vgroups = bm.verts.layers.deform.verify()
    
        dlong = clat = clong = minlat = maxlat = minlong = maxlong = 0.0
        dlat = 1.0  # avoid divide by zero
    
        for node in nodes:
            if node.localName == "bounds":
                if node.hasAttributes():
                    for i in range(node.attributes.length):
                        at = node.attributes.item(i)
                        if at.name == "minlat":
                            minlat = float(at.nodeValue)
                        elif at.name == "minlon":
                            minlong = float(at.nodeValue)
                        elif at.name == "maxlat":
                            maxlat = float(at.nodeValue)
                        elif at.name == "maxlon":
                            maxlong = float(at.nodeValue)
                    dlat = maxlat - minlat
                    dlong = maxlong - minlong
                    clat = (maxlat + minlat) * 0.5
                    clong = (maxlong + minlong) * 0.5
    
    Campbell Barton's avatar
    Campbell Barton committed
    
                    if UTM:
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
                        dlong, dlat = geoToUTM(dlong, dlat)
                        clong, clat = geoToUTM(clong, clat)
    
    
                    print(dlat, dlong, clat, clong)
    
            if node.localName == "way":
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
                wayid = node.getAttribute('id')
    
    Campbell Barton's avatar
    Campbell Barton committed
                if tag:
                    group = obj.vertex_groups.new('way_%s' % wayid)
                    gid = len(obj.vertex_groups) - 1
    
                '''
                if node.hasAttributes():
                    for i in range(node.attributes.length):
                        at=node.attributes.item(i)
                        print(at.name)
                '''
    
    
    Campbell Barton's avatar
    Campbell Barton committed
                if tag:
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
                    metagid = []
                    for ch in node.childNodes:
                        if ch.localName == "tag":
                            key = ch.getAttribute('k')
    
    Campbell Barton's avatar
    Campbell Barton committed
                            if key in osmkeys:
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
                                metagid.append(grouplookup[key])
    
    
                for ch in node.childNodes:
                    if ch.localName == "nd":
                        for i in range(ch.attributes.length):
                            at = ch.attributes.item(i)
                            if at.name == "ref":
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
                                vid = int(at.nodeValue)
                                refs.append(vid)
    
    Campbell Barton's avatar
    Campbell Barton committed
                                if tag:
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
                                    vert = nmap[vid]
                                    weigths = vert[vgroups]
                                    weigths[gid] = 1.0
    
    Campbell Barton's avatar
    Campbell Barton committed
                                    for mid in metagid:
                                        weigths[mid] = 1.0
    
                first = True
    
    Campbell Barton's avatar
    Campbell Barton committed
                    if first is False:
    
                        edge = bm.edges.get((nmap[pr], nmap[r]))
                        if edge is None:
                            edge = bm.edges.new((nmap[pr], nmap[r]))
                        del edge  # don't actually use it
                    else:
    
    Campbell Barton's avatar
    Campbell Barton committed
                        first = False
    
                    pr = r
    
            if node.localName == "node":
                if node.hasAttributes():
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
                    nid = node.getAttribute('id')
                    nlong = node.getAttribute('lon')
                    nlat = node.getAttribute('lat')
    
                    # is this test necessary ? maybe for faulty .osm files
    
    Campbell Barton's avatar
    Campbell Barton committed
                    if (nid != '') and (nlat != '') and (nlong != ''):
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
    
    
    Campbell Barton's avatar
    Campbell Barton committed
                        if UTM:
                            nlong, nlat = geoToUTM(float(nlong), float(nlat))
                        else:
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
                            nlat = float(nlat)
                            nlong = float(nlong)
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
                        x = (nlong - clong) * scale / dlat
                        y = (nlat - clat) * scale / dlat
                        vert = bm.verts.new((x, y, 0.0))
                        nmap[int(nid)] = vert
    
    Campbell Barton's avatar
    Campbell Barton committed
                        if tag:
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
                            metagid = []
                            for ch in node.childNodes:
                                if ch.localName == "tag":
                                    key = ch.getAttribute('k')
                                    val = ch.getAttribute('v')
    
    Campbell Barton's avatar
    Campbell Barton committed
                                    if key in osmvals and val in osmvals[key]:
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
                                        metagid.append(grouplookup[key])
    
    Campbell Barton's avatar
    Campbell Barton committed
                                        metagid.append(grouplookup['_V_' + val])
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
                                        weigths = vert[vgroups]
    
    Campbell Barton's avatar
    Campbell Barton committed
                                        group = obj.vertex_groups.new('node_%s' % nid)
                                        gid = len(obj.vertex_groups) - 1
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
                                        weigths[gid] = 1.0
    
    Campbell Barton's avatar
    Campbell Barton committed
                                        for mid in metagid:
                                            weigths[mid] = 1.0
                    else:
                        print('node is missing some elements : %s %s %s' % (nid, nlat, nlong))
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
    
    
    Campbell Barton's avatar
    Campbell Barton committed
            # if tidx > 1000:
            #     break
            tidx += parseBranch(node.childNodes, bm, nmap, obj, scale, tag, UTM)
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    def read(context, filepath, scale=100.0, tag=False, utm=False):
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
        # create mesh
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
        name = bpy.path.display_name_from_filepath(filepath)
    
    Campbell Barton's avatar
    Campbell Barton committed
        me = bpy.data.meshes.new(name)
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
        obj = bpy.data.objects.new(name, me)
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
        # osm tags option
    
    Campbell Barton's avatar
    Campbell Barton committed
        if tag:
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
            tvid = 0
    
    Campbell Barton's avatar
    Campbell Barton committed
            for gid, grname in enumerate(osmkeys):
                obj.vertex_groups.new('_' + grname)
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
                grouplookup[grname] = gid + tvid
    
    Campbell Barton's avatar
    Campbell Barton committed
                if grname in osmvals:
                    for val in osmvals[grname]:
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
                        tvid += 1
    
    Campbell Barton's avatar
    Campbell Barton committed
                        obj.vertex_groups.new('_V_' + val)
                        grouplookup['_V_' + val] = gid + tvid
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
    
        # get xml then feed bmesh
        print("Reading xml...")
        xmldoc = minidom.parse(filepath)
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
        print("Starting parse: %r..." % filepath)
    
    Campbell Barton's avatar
    Campbell Barton committed
        tidx = parseBranch(xmldoc.childNodes, bm, nmap, obj, scale, tag, utm)
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
        # fast approximation of utm for not too big area
    
    Campbell Barton's avatar
    Campbell Barton committed
        if utm is False:
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
            global_matrix = Matrix(((0.65, 0.0, 0.0, 0.0),
                                    (0.0, 1.0, 0.0, 0.0),
                                    (0.0, 0.0, 1.0, 0.0),
                                    (0.0, 0.0, 0.0, 1.0)))
            me.transform(global_matrix)
    
        context.collection.objects.link(obj)
        context.view_layer.objects.active = obj
    
        obj.select_set(True)
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
        # entry points for other addons
        obj['osmfile'] = filepath
        obj['tagged'] = tag
        obj['utm'] = utm
    
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
    # given lat and longitude in degrees, returns x and y in UTM kilometers.
    # accuracy : supposed to be centimeter :)
    # http://fr.wikipedia.org/wiki/Projection_UTM
    # http://fr.wikipedia.org/wiki/WGS_84
    # http://earth-info.nga.mil/GandG/publications/tr8350.2/wgs84fin.pdf
    # http://geodesie.ign.fr/contenu/fichiers/documentation/algorithmes/alg0071.pdf
    
    Campbell Barton's avatar
    Campbell Barton committed
    def geoToUTM(lon, lat):
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
    
        # if abs(lat) > 80 : lat = 80 #wrong coords.
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
        # UTM zone, longitude origin, then lat lon in radians
    
    Campbell Barton's avatar
    Campbell Barton committed
        z = int((lon + 180) / 6) + 1
        lon0 = radians(6 * z - 183)
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
        lat = radians(lat)
        lon = radians(lon)
    
        # CONSTANTS (see refs.)
        # rayon de la terre à l'équateur
        a = 6378.137
        K0 = 0.9996
        # flattening consts
    
    Campbell Barton's avatar
    Campbell Barton committed
        f  = 0.0033528106647474805   # 1 / 298.257223563
        e2 = 0.0066943799901413165   # 2*f - f**2
        e4 = 4.481472345240445e-05   # e2**2
        e6 = 3.0000678794349315e-07  # e2**3
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
    
        # lat0. 10000 for South, 0 for North
        N0 = 10000 if lat < 0 else 0
    
        # wiki is your friend (don't ask me Im just a writing monkey.)
        A = (lon - lon0) * cos(lat)
    
    Campbell Barton's avatar
    Campbell Barton committed
        C = (e2 / (1 - e2)) * cos(lat) ** 2
        T = tan(lat) ** 2.0
        vlat = 1.0 / sqrt(1.0 - e2 * sin(lat) ** 2.0)
        slat = ((1.0 - (e2    / 4.0) - ((3.0 * e4)    / 64)   - ((5.0  * e6) / 256.0))  * lat -
                (((3.0 * e2)  / 8.0) + ((3.0 * e4)    / 32.0) + ((45.0 * e6) / 1024.0)) * sin(lat * 2.0) +
                (((15.0 * e4) / 256.0) + ((45.0 * e6) / 1024.0)) *
                sin(lat * 4.0) - ((35.0 * e6) / 3072.0) * sin(lat * 6.0))
    
        E = (500.0 + (K0 * a * vlat) * (A + (1.0 - T + C) *
             ((A ** 3.0) / 6.0) + (5.0 - 18.0 * T + T**2) * ((A ** 5.0) / 120.0)))
        N = (N0 + (K0 * a) * (slat + vlat * tan(lat) *
             (A ** 2.0 / 2.0 + (5.0 - T + 9.0 * C + 4.0 * C ** 2.0) *
             (A ** 4.0 / 24.0) + (61.0 - 58.0 * T + T ** 2) * A ** 6.0 / 720.0)))
        return E, N
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
    
    
    ## for testing
    #if __name__ == "__main__":
    #    read("/data/downloads/osm_parser/map.osm", bpy.context)
    
    
    # ----------------------------------------------------------------------------
    # blender integration
    
    from bpy.types import Operator
    from bpy_extras.io_utils import ImportHelper
    
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
    from bpy.props import StringProperty, FloatProperty, BoolProperty
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
        #bl_idname = "import.open_street_map"
        bl_idname = "import_mesh.osm"
    
        bl_label = "Import OpenStreetMap (.osm)"
    
        # ExportHelper mixin class uses this
        filename_ext = ".osm"
    
        filter_glob: StringProperty(
    
                default="*.osm",
                options={'HIDDEN'},
                )
        # List of operator properties, the attributes will be assigned
        # to the class instance from the operator settings before calling.
    
        scale: FloatProperty(
    
        utm: BoolProperty(
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
                name="in UTM coordinates",
                default=True,
                )
    
        tag: BoolProperty(
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
                name="retrieve .osm tags as vertex groups",
                default=False,
                )
    
    
    Campbell Barton's avatar
    Campbell Barton committed
        def execute(self, context):
    
    Jerome Mahieux's avatar
    Jerome Mahieux committed
            return read(context, self.filepath, self.scale, self.tag, self.utm)
    
    
    
    # Only needed if you want to add into a dynamic menu
    def menu_func_export(self, context):
        self.layout.operator(ImportOSM.bl_idname)
    
    
    def register():
        bpy.utils.register_class(ImportOSM)
    
        bpy.types.TOPBAR_MT_file_import.append(menu_func_export)
    
    
    
    def unregister():
        bpy.utils.unregister_class(ImportOSM)
    
        bpy.types.TOPBAR_MT_file_import.remove(menu_func_export)