Advertisement
gomyhr

elveg2osm.py

Oct 22nd, 2014
196
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 13.29 KB | None | 0 0
  1. #! /usr/bin/env python2
  2. import sys
  3. import osmapis
  4. import csv
  5. import numpy as np
  6. from pyproj import Geod
  7. import geographiclib.geodesic as gg
  8.  
  9. g = Geod(ellps='WGS84')
  10.  
  11. # Add useful (for our purpose) methods to the osmapis.OSM class
  12. class ElvegOSM(osmapis.OSM):
  13.  
  14.     def __init__(self, items=()):
  15.         # First call the parent's __init__
  16.         super(ElvegOSM, self).__init__(items)
  17.  
  18.         # Generate dict with TRANSID as key and is as value
  19.         self.wayid_dict = {}
  20.         for wayid,way in self.ways.iteritems():
  21.             transid = way.tags['TRANSID']
  22.             self.wayid_dict[transid] = wayid
  23.  
  24.     def way_nodes_from_transid(self, transid):
  25.         wayid = self.wayid_dict[transid]
  26.         way = self.ways[wayid]
  27.         node_ids = way.nds
  28.         nodes = [osmobj.nodes[nid] for nid in node_ids]
  29.         return nodes
  30.  
  31.     def distances_from_transid(self, transid):
  32.         global g
  33.         nodes = self.way_nodes_from_transid(transid)
  34.         node_distances = []
  35.         distance_so_far = 0.
  36.         prev_lon = nodes[0].lon
  37.         prev_lat = nodes[0].lat
  38.        
  39.         for i,nd in enumerate(nodes):
  40.             #az1,az2,d_from_previous = g.inv(prev_lon, prev_lat, nd.lon, nd.lat)
  41.             ggresults = gg.Geodesic.WGS84.Inverse(prev_lat, prev_lon, nd.lat, nd.lon)
  42.             d_from_previous = ggresults['s12']
  43.             if i != 0 and d_from_previous < 0.5:
  44.                 # Report if very short distance
  45.                 sys.stderr.write('Short distance ({2}) for transid {0} to node No. {1}\n'.format(transid, i,d_from_previous))
  46.             distance_so_far += d_from_previous
  47.             node_distances.append(distance_so_far)
  48.             # Prepare previous coordinates for next round
  49.             prev_lon = nd.lon
  50.             prev_lat = nd.lat
  51.  
  52.         return node_distances
  53.  
  54. class ElvegNode(osmapis.Node):
  55.  
  56.     def __init__(self, attribs={}, tags={}):
  57.         osmapis.Node.__init__(self, attribs, tags)
  58.         # Make sure the class counter is as low as the lowest existing ID
  59.         # This should probably have been done in osmapis.Node
  60.         if self.id is not None:
  61.             self.__class__._counter = min(self.__class__._counter, self.id)
  62.            
  63. class ElvegWay(osmapis.Way):
  64.  
  65.     def __init__(self, attribs={}, tags={}, nds=()):
  66.         osmapis.Way.__init__(self, attribs, tags, nds)
  67.         # Make sure the class counter is as low as the lowest existing ID
  68.         # This should probably have been done in osmapis.Way
  69.         if self.id is not None:
  70.             self.__class__._counter = min(self.__class__._counter, self.id)
  71.  
  72.  
  73. # Override default classes in osmapis.py
  74. osmapis.wrappers["osm"]  = ElvegOSM
  75. osmapis.wrappers["node"] = ElvegNode
  76. osmapis.wrappers["way"]  = ElvegWay
  77.  
  78. def create_osmtags(elveg_tags):
  79.     '''Create tags based on standard tags in ????Elveg_default.osm'''
  80.  
  81.     roadOBJTYPEs = set([u'VegSenterlinje',
  82.                         u'Svingekonnekteringslenke',
  83.                         u'Kj\xf8refelt',
  84.                         u'Kj\xf8rebane',
  85.                         u'Bilferjestrekning'])
  86.  
  87.     osmtags = dict()
  88.  
  89.     # Add the nvdb:id tag from the TRANSID tag
  90.     # All ways should have a TRANSID
  91.     osmtags['nvdb:id'] = elveg_tags['TRANSID']
  92.  
  93.     if elveg_tags['OBJTYPE'] in roadOBJTYPEs:
  94.  
  95.         # Split VNR tag
  96.         # The "vegnummer" tag is optional, but let's assume it is always present for now
  97.         # (i.e. fix it if it causes problems)
  98.         vegkategori,vegstatus,vegnummer = [s.strip() for s in elveg_tags['VNR'].split(':')]
  99.  
  100.         # Set the road category
  101.         # Let it be
  102.         if vegkategori == 'E':
  103.             # Europaveg
  104.             osmtags['highway'] = 'trunk'
  105.             osmtags['ref'] = 'E ' + vegnummer
  106.         elif vegkategori == 'R':
  107.             # Riksveg
  108.             osmtags['highway'] = 'trunk'
  109.             osmtags['ref'] = vegnummer
  110.         elif vegkategori == 'F':
  111.             # Fylkesveg
  112.             # May be primary or secondary. If one is not already on the map, it is probably a secondary.
  113.             osmtags['highway'] = 'secondary'
  114.             osmtags['ref'] = vegnummer
  115.         elif vegkategori == 'K':
  116.             # Kommunal veg
  117.             # Don't try to classify. May be tertiary, residential, unclassified, or service.
  118.             osmtags['highway'] = 'road'
  119.         elif vegkategori == 'P':
  120.             # Privat veg
  121.             # Don't try to classify. May be tertiary, residential, unclassified, or service.
  122.             osmtags['highway'] = 'road'
  123.         elif vegkategori == 'S':
  124.             # Skogsbilveg
  125.             # There may be more information in the LBVKLASSE tag,
  126.             # but I have only found it in 19 Troms county
  127.             # (not present in counties 01, 02, 05, 08)
  128.             osmtags['highway'] = 'track'
  129.  
  130.     # TODO: Add more tag handling here
  131.  
  132.     return osmtags
  133.  
  134. # FIXME: Return also the original way
  135. #        (and verify that the right number of ways ids are returned)
  136. def split_way(osmobj, way_id, split_points):
  137.     '''Split way at split points.
  138.  
  139.    Return list of new ways. If the input list split_points is empty,
  140.    the original way is returned in a one-entry list.
  141.  
  142.    Warning: The current splitting may not work if there are no nodes
  143.             between the split points.
  144.  
  145.    '''
  146.     # Do not go through the hassle, if the way needs no splitting
  147.     if len(split_points) == 0:
  148.         return [way_id]
  149.  
  150.     # Initialize a list of new way id's (to be returned)
  151.     newway_id_list = []
  152.  
  153.     # Get the way that is to be split
  154.     way = osmobj.ways[way_id]
  155.     transid = way.elveg_tags['TRANSID']
  156.  
  157.     # Compute distances from start to each node of way
  158.     node_distances = osmobj.distances_from_transid(transid)
  159.     geo_length = node_distances[-1]
  160.  
  161.     # Compute VPA length and normalize split_points to geographic length
  162.     if way.elveg_tags.has_key("VPA"):
  163.         vpa = [int(n) for n in way.elveg_tags["VPA"].split(':')]
  164.     else:
  165.         # These roads are probably not split, so 1.0 is fine, but raise Exception for now
  166.         #corrction_factor = 1.0
  167.         raise KeyError("VPA Elveg tag not present")
  168.     vpa_length = vpa[2] - vpa[1]
  169.     normalization_factor = geo_length / float(vpa_length)
  170.     split_points_normalized = [normalization_factor * sp for sp in split_points]
  171.  
  172.     # Make sure the normalized split points are sorted
  173.     # (so that we can split off ways from the end of the list)
  174.     split_points_normalized.sort()
  175.  
  176.     # Loop over the split points, splitting off the last way each time
  177.     while len(split_points_normalized) > 0:
  178.         current_split_point = split_points_normalized.pop()
  179.         upper_split_index = np.searchsorted(node_distances, current_split_point)
  180.  
  181.         # Find the coordinates for the new split node
  182.         from_node_id = way.nds[upper_split_index - 1]
  183.         to_node_id = way.nds[upper_split_index]
  184.         from_node = osmobj.nodes[from_node_id]
  185.         to_node = osmobj.nodes[to_node_id]
  186.         ggresults = gg.Geodesic.WGS84.Inverse(from_node.lat, from_node.lon, to_node.lat, to_node.lon)
  187.         distance = ggresults['s12']
  188.         azi1 = ggresults['azi1']
  189.         dist_from_last_node = current_split_point - node_distances[upper_split_index - 1]
  190.         ggresults = gg.Geodesic.WGS84.Direct(from_node.lat, from_node.lon, azi1, dist_from_last_node)
  191.         newlon = ggresults['lon2']
  192.         newlat = ggresults['lat2']
  193.  
  194.         # Create the new node
  195.         split_node = ElvegNode(attribs={"lon": newlon, "lat": newlat})
  196.         if osmobj.nodes.has_key(split_node.id):
  197.             # This should not happen if ElvegNode.__init__() does the right thing
  198.             raise Exception('Almost overwrote node {0}\n'.format(split_node.id))
  199.         osmobj.nodes[split_node.id] = split_node
  200.  
  201.         # TEMPORARY:
  202.         osmobj.nodes[split_node.id].tags['newnode'] = 'yes'
  203.         osmobj.nodes[split_node.id].tags['transid'] = transid
  204.  
  205.         # Create a new way from the split_point to the end of the way
  206.         newway_nodes = [split_node.id] + way.nds[upper_split_index:]
  207.         newway = ElvegWay(tags=way.tags, nds=newway_nodes)
  208.         newway_id_list.append(newway.id)
  209.         osmobj.ways[newway.id] = newway
  210.  
  211.         # Remove nodes for the new way from the old way
  212.         way.nds = way.nds[:upper_split_index] + [split_node.id]
  213.  
  214.         # TEMPORARY: Add a tag to identify split ways
  215.         newway.tags['splitindex'] = str(i)
  216.  
  217.     return newway_id_list
  218.  
  219.        
  220. # Read input arguments
  221. osm_input = sys.argv[1]
  222. elveg_fart = sys.argv[2]
  223. elveg_hoyde = sys.argv[3]
  224. osm_output = sys.argv[4]
  225.  
  226. # Loop over speed limits and tags where the whole
  227. # way where possible. Other places, add to split list
  228. roaddata = {}
  229. with open(elveg_fart, 'rb') as ef:
  230.     # Read first four header lines
  231.     ef_header = ef.next()
  232.     ef_export_line = ef.next()
  233.     ef_some_number = ef.next()
  234.     ef_empty_line = ef.next()
  235.    
  236.     # Then use csv module for reading data
  237.     reader = csv.DictReader(ef, delimiter=';')
  238.     for row in reader:
  239.         transid = row[' TransID']
  240.  
  241.         fart_start = int(row['Fra'])
  242.         fart_stop =  int(row['   Til'])
  243.         fart_length = fart_stop - fart_start
  244.         fart_limit = row[' Fart']
  245.  
  246.         if not roaddata.has_key(transid):
  247.             roaddata[transid] = {}
  248.         if not roaddata[transid].has_key('maxspeed'):
  249.             roaddata[transid]['maxspeed'] = []
  250.         roaddata[transid]['maxspeed'].append({'maxspeed': fart_limit,
  251.                                               'start': fart_start,
  252.                                               'stop': fart_stop})
  253. # Add height limits to roaddata
  254. with open(elveg_hoyde, 'rb') as eh:
  255.     # Read first four header lines
  256.     eh_header = eh.next()
  257.     eh_export_line = eh.next()
  258.     eh_empty_line1 = eh.next()
  259.     eh_empty_line2 = eh.next()
  260.  
  261.     # Then use csv module for reading data
  262.     reader = csv.DictReader(eh, delimiter=';')
  263.     for row in reader:
  264.         transid = row[' TransID']
  265.  
  266.         height_start = int(row['Fra'])
  267.         height_stop =  int(row['   Til'])
  268.         height_length = height_stop - height_start
  269.         height_limit = row['H\xf8yde']
  270.  
  271.         if not roaddata.has_key(transid):
  272.             roaddata[transid] = {}
  273.         if not roaddata[transid].has_key('maxheight'):
  274.             roaddata[transid]['maxheight'] = []
  275.         roaddata[transid]['maxheight'].append({'maxheight': height_limit,
  276.                                                'start': height_start,
  277.                                                'stop': height_stop})
  278.  
  279. # Read OSM file
  280. osmobj = ElvegOSM.load(osm_input)
  281.  
  282. # Loop through all ways in osmobj and
  283. # - swap original tags with OSM tags.
  284. # - extract the way length from the Elveg VPA tag and
  285. #   store in roaddata structure
  286. # Important to use items() instead of iteritems() here as we are adding
  287. # items to the obmobj.ways dictionary during the loop.
  288. for wid,w in osmobj.ways.items():
  289.     # Add new tags (using the create_osmtags function)
  290.     w.elveg_tags = w.tags
  291.     osm_tags = create_osmtags(w.elveg_tags)
  292.     w.tags = osm_tags
  293.     w.tags['test2'] = 'test2'
  294.  
  295.     # Add way length as given by VPA to the roadddata structure
  296.     transid = w.elveg_tags['TRANSID']
  297.     vpa = [int(n) for n in w.elveg_tags["VPA"].split(':')]
  298.     # We do not care about those ways where we have no data to add,
  299.     # so move to next if this is the case.
  300.     if not roaddata.has_key(transid):
  301.         continue
  302.     roaddata[transid]['length'] = vpa[2] - vpa[1]
  303.    
  304.     # make a sorted list of meter values, including end
  305.     # points, where some roaddata may change
  306.     end_points = [0, roaddata[transid]['length']]
  307.     for restriction_type in ['maxspeed', 'maxheight']: # Add any new restrictions here
  308.         for endpoint_type in ['start', 'stop']:
  309.             end_points.extend([d[endpoint_type] for d in roaddata[transid].get(restriction_type, [])])
  310.     end_points = list(set(end_points))
  311.     end_points.sort()
  312.  
  313.     # Make a list of intervals, representing the new ways after a split
  314.     # For most ways, there will be only one interval, but whenever
  315.     # the speed limit changes on a way or a height restriction
  316.     # does not apply to the whole way, there will be more than one interval
  317.     interval_list = zip(end_points[:-1],end_points[1:])
  318.  
  319.     # Make a list of tags (maxheight=*, maxspeed=*)
  320.     # with one list entry per new way interval
  321.     newway_tags = [{} for i in interval_list] # I.e. a list of empty dicts
  322.     for i,interval in enumerate(interval_list):
  323.         for restriction_type in ['maxspeed', 'maxheight']: # Add any new restrictions here
  324.             for j,restr in enumerate(roaddata[transid].get(restriction_type, [])):
  325.                 if restr['start'] <= interval[0] and interval[1] <= restr['stop']:
  326.                     newway_tags[i][restriction_type] = restr[restriction_type]
  327.  
  328.     # DEBUG: Remove later
  329.     #print newway_tags
  330.  
  331.     # Split the way in osmobj into the right number of segments
  332.     split_points = end_points[1:-1]
  333.     segment_ids = split_way(osmobj, w.id, split_points)
  334.  
  335.     # Add nvdb:id:part subkey to each way segment
  336.     for i,segment_id in enumerate(segment_ids):
  337.         osmobj.ways[segment_id].tags['nvdb:id:part'] = str(i)
  338.    
  339.     # Add maxheight and maxspeed restrictions
  340.     for i,segment_in in enumerate(segment_ids):
  341.         osmobj.ways[segment_id].tags.update(newway_tags[i])
  342.  
  343. osmobj.save(osm_output)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement