Advertisement
Guest User

Untitled

a guest
Sep 26th, 2017
62
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.92 KB | None | 0 0
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8
  3.  
  4. """
  5. After auditing is complete the next step is to prepare the data to be inserted into a SQL database.
  6. To do so you will parse the elements in the OSM XML file, transforming them from document format to
  7. tabular format, thus making it possible to write to .csv files.  These csv files can then easily be
  8. imported to a SQL database as tables.
  9.  
  10. The process for this transformation is as follows:
  11. - Use iterparse to iteratively step through each top level element in the XML
  12. - Shape each element into several data structures using a custom function
  13. - Utilize a schema and validation library to ensure the transformed data is in the correct format
  14. - Write each data structure to the appropriate .csv files
  15.  
  16. We've already provided the code needed to load the data, perform iterative parsing and write the
  17. output to csv files. Your task is to complete the shape_element function that will transform each
  18. element into the correct format. To make this process easier we've already defined a schema (see
  19. the schema.py file in the last code tab) for the .csv files and the eventual tables. Using the
  20. cerberus library we can validate the output against this schema to ensure it is correct.
  21.  
  22. ## Shape Element Function
  23. The function should take as input an iterparse Element object and return a dictionary.
  24.  
  25. ### If the element top level tag is "node":
  26. The dictionary returned should have the format {"node": .., "node_tags": ...}
  27.  
  28. The "node" field should hold a dictionary of the following top level node attributes:
  29. - id
  30. - user
  31. - uid
  32. - version
  33. - lat
  34. - lon
  35. - timestamp
  36. - changeset
  37. All other attributes can be ignored
  38.  
  39. The "node_tags" field should hold a list of dictionaries, one per secondary tag. Secondary tags are
  40. child tags of node which have the tag name/type: "tag". Each dictionary should have the following
  41. fields from the secondary tag attributes:
  42. - id: the top level node id attribute value
  43. - key: the full tag "k" attribute value if no colon is present or the characters after the colon if one is.
  44. - value: the tag "v" attribute value
  45. - type: either the characters before the colon in the tag "k" value or "regular" if a colon
  46.        is not present.
  47.  
  48. Additionally,
  49.  
  50. - if the tag "k" value contains problematic characters, the tag should be ignored
  51. - if the tag "k" value contains a ":" the characters before the ":" should be set as the tag type
  52.  and characters after the ":" should be set as the tag key
  53. - if there are additional ":" in the "k" value they and they should be ignored and kept as part of
  54.  the tag key. For example:
  55.  
  56.  <tag k="addr:street:name" v="Lincoln"/>
  57.  should be turned into
  58.  {'id': 12345, 'key': 'street:name', 'value': 'Lincoln', 'type': 'addr'}
  59.  
  60. - If a node has no secondary tags then the "node_tags" field should just contain an empty list.
  61.  
  62. The final return value for a "node" element should look something like:
  63.  
  64. {'node': {'id': 757860928,
  65.          'user': 'uboot',
  66.          'uid': 26299,
  67.       'version': '2',
  68.          'lat': 41.9747374,
  69.          'lon': -87.6920102,
  70.          'timestamp': '2010-07-22T16:16:51Z',
  71.      'changeset': 5288876},
  72. 'node_tags': [{'id': 757860928,
  73.                'key': 'amenity',
  74.                'value': 'fast_food',
  75.                'type': 'regular'},
  76.               {'id': 757860928,
  77.                'key': 'cuisine',
  78.                'value': 'sausage',
  79.                'type': 'regular'},
  80.               {'id': 757860928,
  81.                'key': 'name',
  82.                'value': "Shelly's Tasty Freeze",
  83.                'type': 'regular'}]}
  84.  
  85. ### If the element top level tag is "way":
  86. The dictionary should have the format {"way": ..., "way_tags": ..., "way_nodes": ...}
  87.  
  88. The "way" field should hold a dictionary of the following top level way attributes:
  89. - id
  90. -  user
  91. - uid
  92. - version
  93. - timestamp
  94. - changeset
  95.  
  96. All other attributes can be ignored
  97.  
  98. The "way_tags" field should again hold a list of dictionaries, following the exact same rules as
  99. for "node_tags".
  100.  
  101. Additionally, the dictionary should have a field "way_nodes". "way_nodes" should hold a list of
  102. dictionaries, one for each nd child tag.  Each dictionary should have the fields:
  103. - id: the top level element (way) id
  104. - node_id: the ref attribute value of the nd tag
  105. - position: the index starting at 0 of the nd tag i.e. what order the nd tag appears within
  106.            the way element
  107.  
  108. The final return value for a "way" element should look something like:
  109.  
  110. {'way': {'id': 209809850,
  111.         'user': 'chicago-buildings',
  112.         'uid': 674454,
  113.         'version': '1',
  114.         'timestamp': '2013-03-13T15:58:04Z',
  115.         'changeset': 15353317},
  116. 'way_nodes': [{'id': 209809850, 'node_id': 2199822281, 'position': 0},
  117.               {'id': 209809850, 'node_id': 2199822390, 'position': 1},
  118.               {'id': 209809850, 'node_id': 2199822392, 'position': 2},
  119.               {'id': 209809850, 'node_id': 2199822369, 'position': 3},
  120.               {'id': 209809850, 'node_id': 2199822370, 'position': 4},
  121.               {'id': 209809850, 'node_id': 2199822284, 'position': 5},
  122.               {'id': 209809850, 'node_id': 2199822281, 'position': 6}],
  123. 'way_tags': [{'id': 209809850,
  124.               'key': 'housenumber',
  125.               'type': 'addr',
  126.               'value': '1412'},
  127.              {'id': 209809850,
  128.               'key': 'street',
  129.               'type': 'addr',
  130.               'value': 'West Lexington St.'},
  131.              {'id': 209809850,
  132.               'key': 'street:name',
  133.               'type': 'addr',
  134.               'value': 'Lexington'},
  135.              {'id': '209809850',
  136.               'key': 'street:prefix',
  137.               'type': 'addr',
  138.               'value': 'West'},
  139.              {'id': 209809850,
  140.               'key': 'street:type',
  141.               'type': 'addr',
  142.               'value': 'Street'},
  143.              {'id': 209809850,
  144.               'key': 'building',
  145.               'type': 'regular',
  146.               'value': 'yes'},
  147.              {'id': 209809850,
  148.               'key': 'levels',
  149.               'type': 'building',
  150.               'value': '1'},
  151.              {'id': 209809850,
  152.               'key': 'building_id',
  153.               'type': 'chicago',
  154.               'value': '366409'}]}
  155. """
  156.  
  157. import csv
  158. import codecs
  159. import pprint
  160. import re
  161. import xml.etree.cElementTree as ET
  162.  
  163. import cerberus
  164.  
  165. import schema
  166.  
  167. OSM_PATH = "example.osm"
  168.  
  169. NODES_PATH = "nodes.csv"
  170. NODE_TAGS_PATH = "nodes_tags.csv"
  171. WAYS_PATH = "ways.csv"
  172. WAY_NODES_PATH = "ways_nodes.csv"
  173. WAY_TAGS_PATH = "ways_tags.csv"
  174.  
  175. LOWER_COLON = re.compile(r'^([a-z]|_)+:([a-z]|_)+')
  176. PROBLEMCHARS = re.compile(r'[=\+/&<>;\'"\?%#$@\,\. \t\r\n]')
  177.  
  178. SCHEMA = schema.schema
  179.  
  180. # Make sure the fields order in the csvs matches the column order in the sql table schema
  181. NODE_FIELDS = ['id', 'lat', 'lon', 'user', 'uid', 'version', 'changeset', 'timestamp']
  182. NODE_TAGS_FIELDS = ['id', 'key', 'value', 'type']
  183. WAY_FIELDS = ['id', 'user', 'uid', 'version', 'changeset', 'timestamp']
  184. WAY_TAGS_FIELDS = ['id', 'key', 'value', 'type']
  185. WAY_NODES_FIELDS = ['id', 'node_id', 'position']
  186.  
  187.  
  188. def shape_element(element, node_attr_fields=NODE_FIELDS, way_attr_fields=WAY_FIELDS,
  189.                   problem_chars=PROBLEMCHARS, default_tag_type='regular'):
  190.     """Clean and shape node or way XML element to Python dict"""
  191.  
  192.     node_attribs = {}
  193.     tag_dict = {}
  194.     way_attribs = {}
  195.     way_nodes_dict = {}
  196.    
  197.     way_nodes = []
  198.     tags = []  # Handle secondary tags the same way for both node and way elements
  199.     way_tag_dict = {}
  200.    
  201.  
  202.     # YOUR CODE HERE
  203.    
  204.    
  205.     if element.tag == 'node':
  206.         for node in NODE_FIELDS:
  207.             node_attribs[node] = element.attrib[node]
  208.         for child in element:
  209.             if child.tag == 'tag':
  210.                 tag_dict['id'] = element.attrib['id'] # set the id = parent id
  211.                 # you access child.attrib['k']
  212.                 tag_dict['key'] = child.attrib['k']
  213.                 # and child.attrib['v'] here
  214.                 tag_dict['value'] = child.attrib['v']
  215.                 # and so on, as described in the problem
  216.    
  217.                 # Access type here
  218.                 tag_dict['type'] = 'reg'
  219.                
  220.                 #print tag_dict works fine here
  221.                 # append the result of dictionaries to tags list
  222.                 # Note it is not appending as it supposed to
  223.                 tags.append(tag_dict)
  224.         print tags
  225.            
  226.  
  227.         return {'node': node_attribs, 'node_tags': tags}
  228.    
  229.     elif element.tag == 'way':
  230.         # implement way_attribs here
  231.         for way in WAY_FIELDS:
  232.             way_attribs[way] = element.attrib[way]
  233.            
  234.            
  235.         # implement childern in elements here (way_nodes_dict)
  236.         counter = 0
  237.         for child in element:
  238.             if child.tag == 'tag':
  239.                 tag = {'id': way_attribs['id']}
  240.                 k = child.get('k')
  241.                 if not PROBLEMCHARS.search(k):
  242.                     k = k.split(':', 1)
  243.                     tag['key'] = k[-1]
  244.                     tag['value'] = child.get('v')
  245.                     if len(k) == 1:
  246.                         tag['type'] = 'regular'    
  247.                     elif len(k) == 2:
  248.                         tag['type'] = k[0]
  249.                 tags.append(tag)
  250.                
  251.             if child.tag == 'nd':
  252.                 nd = {'id' : way_attribs['id']}
  253.                 nd['node_id'] = child.get('ref')
  254.                 nd['position'] = counter
  255.                 way_nodes.append(nd)
  256.             counter += 1
  257.                
  258.                
  259.        
  260.            
  261.         #Note: error, all the values appended are the same in csv files
  262.        
  263.            
  264.                
  265.        
  266.                
  267.         return {'way': way_attribs, 'way_nodes': way_nodes, 'way_tags': tags}
  268.  
  269.  
  270. # ================================================== #
  271. #               Helper Functions                     #
  272. # ================================================== #
  273. def get_element(osm_file, tags=('node', 'way', 'relation')):
  274.     """Yield element if it is the right type of tag"""
  275.  
  276.     context = ET.iterparse(osm_file, events=('start', 'end'))
  277.     _, root = next(context)
  278.     for event, elem in context:
  279.         if event == 'end' and elem.tag in tags:
  280.             yield elem
  281.             root.clear()
  282.  
  283.  
  284. def validate_element(element, validator, schema=SCHEMA):
  285.     """Raise ValidationError if element does not match schema"""
  286.     if validator.validate(element, schema) is not True:
  287.         field, errors = next(validator.errors.iteritems())
  288.         message_string = "\nElement of type '{0}' has the following errors:\n{1}"
  289.         error_string = pprint.pformat(errors)
  290.        
  291.         raise Exception(message_string.format(field, error_string))
  292.  
  293.  
  294. class UnicodeDictWriter(csv.DictWriter, object):
  295.     """Extend csv.DictWriter to handle Unicode input"""
  296.  
  297.     def writerow(self, row):
  298.         super(UnicodeDictWriter, self).writerow({
  299.             k: (v.encode('utf-8') if isinstance(v, unicode) else v) for k, v in row.iteritems()
  300.         })
  301.  
  302.     def writerows(self, rows):
  303.         for row in rows:
  304.             self.writerow(row)
  305.  
  306.  
  307. # ================================================== #
  308. #               Main Function                        #
  309. # ================================================== #
  310. def process_map(file_in, validate):
  311.     """Iteratively process each XML element and write to csv(s)"""
  312.  
  313.     with codecs.open(NODES_PATH, 'w') as nodes_file, \
  314.          codecs.open(NODE_TAGS_PATH, 'w') as nodes_tags_file, \
  315.          codecs.open(WAYS_PATH, 'w') as ways_file, \
  316.          codecs.open(WAY_NODES_PATH, 'w') as way_nodes_file, \
  317.          codecs.open(WAY_TAGS_PATH, 'w') as way_tags_file:
  318.  
  319.         nodes_writer = UnicodeDictWriter(nodes_file, NODE_FIELDS)
  320.         node_tags_writer = UnicodeDictWriter(nodes_tags_file, NODE_TAGS_FIELDS)
  321.         ways_writer = UnicodeDictWriter(ways_file, WAY_FIELDS)
  322.         way_nodes_writer = UnicodeDictWriter(way_nodes_file, WAY_NODES_FIELDS)
  323.         way_tags_writer = UnicodeDictWriter(way_tags_file, WAY_TAGS_FIELDS)
  324.  
  325.         nodes_writer.writeheader()
  326.         node_tags_writer.writeheader()
  327.         ways_writer.writeheader()
  328.         way_nodes_writer.writeheader()
  329.         way_tags_writer.writeheader()
  330.  
  331.         validator = cerberus.Validator()
  332.  
  333.         for element in get_element(file_in, tags=('node', 'way')):
  334.             el = shape_element(element)
  335.             if el:
  336.                 if validate is True:
  337.                     validate_element(el, validator)
  338.  
  339.                 if element.tag == 'node':
  340.                     nodes_writer.writerow(el['node'])
  341.                     node_tags_writer.writerows(el['node_tags'])
  342.                 elif element.tag == 'way':
  343.                     ways_writer.writerow(el['way'])
  344.                     way_nodes_writer.writerows(el['way_nodes'])
  345.                     way_tags_writer.writerows(el['way_tags'])
  346.  
  347.  
  348.  
  349. process_map(OSM_PATH, validate=True)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement