source: subversion/applications/editors/django/osmeditor/lib/osmparser.py

Last change on this file was 14831, checked in by edgemaster, 10 years ago

Begin 0.6ifying osmparser.
In this commit: attributes, attributes and more attributes (version, uid, changeset id on features)
No proper changeset support yet!

File size: 12.4 KB
Line 
1from xml.sax.handler import ContentHandler
2import sys, re, xml.sax
3
4elementtree = True 
5
6try:
7    from xml.etree.cElementTree import Element, SubElement, tostring
8except:
9    try:
10        from cElementTree import Element, SubElement, tostring
11    except:
12        try:
13            sys.path.append("..")
14            from third.ElementTree import Element, SubElement, tostring
15        except ImportError, E:
16            elementtree = False
17
18try:
19    import httplib2
20except ImportError, E:
21    try:
22        from third import httplib2
23    except ImportError, E2:
24        httplib2 = False
25        httplib2_error = "%s/%s" % (E, E2)
26
27def indentElement(elem, level=0):
28    """Used for pretty printing XML."""
29    i = "\n" + level*"  "
30    if len(elem):
31        if not elem.text or not elem.text.strip():
32            elem.text = i + "  "
33        for e in elem:
34            indentElement(e, level+1)
35            if not e.tail or not e.tail.strip():
36                e.tail = i + "  "
37        if not e.tail or not e.tail.strip():
38            e.tail = i
39    else:
40        if level and (not elem.tail or not elem.tail.strip()):
41            elem.tail = i
42
43class LibException(Exception): pass
44class DependancyException(LibException): pass
45class OSMException(LibException): pass
46
47
48class OSMObj:
49    """
50    >>> o = OSMObj(type='node')
51    >>> o.loc = [-5, -5]
52    >>> o.tags['created_by'] = 'osmparser'
53    >>> xml = o.toxml()
54    >>> xml
55    '<osm version="0.6">\\n  <node lat="-5" lon="-5">\\n    <tag k="created_by" v="osmparser" />\\n  </node>\\n</osm>'
56    >>> shortxml = o.toxml(indent=False)
57    >>> len(xml) > len(shortxml)
58    True
59    >>> len(shortxml)
60    92
61   
62    >> o.save(username, password)
63    '339668244'
64    >> o.id
65    339668244
66    >> o.delete(username, password)
67    ' '
68    >> o.id
69    >> o.delete(username, password)
70    Traceback (most recent call last):
71      File "<stdin>", line 1, in <module>
72      File "lib/osmparser.py", line 162, in delete
73        raise Exception("Can't delete object with no id")
74    Exception: Can't delete object with no id
75    """
76    type = None
77    id = None
78    version = None
79    changeset = None
80    uid = None
81    user = None
82    timestamp = None
83    loc = None
84
85    tags = None
86
87    nodes = None
88    members = None
89
90    def __init__(self, id=None, type=None, version=None, site_url="http://openstreetmap.org"):
91        self.id = id
92        self.version = version
93        self.tags = {}
94        if type:
95            self.setType(type)
96        self.site_url = site_url
97
98    def setType(self, type):
99        """
100        >>> o = OSMObj()
101        >>> o.nodes == None
102        True
103        >>> o.members == None
104        True
105        >>> o.loc == None
106        True
107        >>> o.setType("way")
108        >>> o.nodes
109        []
110        >>> o.setType("relation")
111        >>> o.members
112        []
113        >>> o.setType("node")
114        >>> len(o.loc)
115        2
116        """
117        self.type = type
118        if self.type == "way":
119            self.nodes = []
120        elif self.type == "relation":
121            self.members = []
122        elif self.type == "node":
123            self.loc = [-181, -91]
124   
125    def display(self):
126        d = str(self)
127        if 'name' in self.tags:
128            d = "%s (%s)" % (self.tags['name'], d)
129        return d
130
131    def api_url(self):
132        id = self.id or "create"
133        return "%s/api/0.6/%s/%s" % (self.site_url, self.type, id)
134   
135    def local_url(self):
136        return "/%s/%s/" % (self.type, self.id)
137   
138    def user_link(self):
139        if self.user:
140            return "%s/user/%s" % (self.site_url, self.user)
141        else: 
142            return ""   
143   
144    def browse_url(self):
145        return "%s/browse/%s/%s" % (self.site_url, self.type, self.id)
146   
147    def __str__(self):
148        return "%s %s (%s)" % (self.type.title(), self.id, self.version)
149
150    def __repr__(self):
151        start =  "<OSM %s %s (%s)" % (self.type.title(), self.id, self.version) 
152        middle = ""
153        if self.nodes:
154            middle = "%s nodes" % len(self.nodes)
155        elif self.members:
156            middle = "%s members" % (len(self.members))
157        end = ">"
158        return " ".join(filter(None, (start, middle, end)))
159
160    def toxml(self, as_string=True, parent=None, needs_parent=True, indent=True):
161        if not elementtree:
162            raise DependancyException("ElementTree support required for writing to XML.") 
163
164        if self.type == "node":
165            element = Element("node", {
166                'lon': str(self.loc[0]), 
167                'lat': str(self.loc[1])})
168        elif self.type == "way":
169            element = Element('way')
170            for n in self.nodes:
171                id = None
172                if isinstance(n, int):
173                    id = n
174                else:
175                    id = n.id
176                id = str(id)   
177                SubElement(element, "nd", {'ref': id})
178        elif self.type == "relation":
179            element = Element('relation')
180            for m in self.members:
181                obj = None
182                id = None
183                type = ''
184                role = ''
185                if isinstance(m, dict):
186                    obj = m['ref']
187                    type = m['type']
188                    role = m['role']
189                elif isinstance(m, (tuple, list)):
190                    obj = m[0]
191                    role = m[1]
192                elif isinstance(m, OSMObj):
193                    obj = m
194
195                if isinstance(obj, int):
196                    id = obj
197                else:
198                    id = obj.id
199                    type = obj.type
200                   
201                id = str(id)
202                SubElement(element, "member", {
203                    'ref': id,
204                    'type': type,
205                    'role': role
206                })
207       
208        if self.id:
209            element.attrib['id'] = str(self.id)
210           
211        if self.version:
212            element.attrib['version'] = str(self.version)
213
214        keys = self.tags.keys()
215        keys.sort()
216       
217
218        for key in keys:
219            SubElement(element, "tag", {'k': key, 'v': self.tags[key]})
220       
221        if needs_parent:
222            if parent == None:
223                parent = Element("osm", {"version": "0.6"})
224            parent.append(element)
225        else:
226            parent = element
227       
228        if indent:
229            indentElement(parent)
230        if as_string: 
231            return tostring(parent)
232        else:
233            return parent
234
235    def save(self, username, password):
236        if not httplib2:
237            raise DependancyException("Couldn't import httplib2: %s" % httplib2_error)
238        url = self.api_url()
239        h = httplib2.Http()
240        h.add_credentials(username, password)
241        xml = self.toxml()   
242        (resp, content) = h.request(url, "PUT", body=xml)
243        if int(resp.status) != 200:
244            raise OSMException("Status was: %s, Content: %s" % (resp.status, content))
245        if not self.id:
246            self.id = int(content)
247        return content
248   
249    def test_delete(self):
250        if not httplib2:
251            raise DependancyException("Couldn't import httplib2: %s" % httplib2_error)
252        if not self.id:
253            raise OSMException("Can't delete object with no id")
254        res = {
255            'ok': [],
256            'not_ok': []
257        }   
258        h = httplib2.Http()
259        url = "%s/api/0.6/%s/%s/relations" % (self.site_url, self.type, id)
260        resp, content = h.request(url)
261        data = parseString(content)
262        add_to = 'ok'
263        if len(data['relations']):
264            add_to = 'not_ok'
265
266        if self.type == "node":
267            url = "%s/api/0.6/%s/%s/ways" % (self.site_url, self.type, id)
268            resp, content = h.request(url)
269            data = parseString(content)
270            add_to = 'ok'
271            if len(data['ways']) > 0:
272                add_to = 'not_ok'
273           
274            res[add_to].append((self.type, self.id))   
275           
276
277    def delete(self, username, password):
278        if not httplib2:
279            raise DependancyException("Couldn't import httplib2: %s" % httplib2_error)
280        if not self.id:
281            raise OSMException("Can't delete object with no id")
282        url = self.api_url()
283        h = httplib2.Http()
284        h.add_credentials(username, password)
285        xml = self.toxml()   
286        (resp, content) = h.request(url, "DELETE")
287        if int(resp.status) != 200:
288            raise OSMException("Status was: %s, Content: %s" % (resp.status, content))
289        self.id = None
290        return content
291
292def rearrange(output):
293    new_output = {
294        'nodes': {},
295        'ways': {},
296        'relations': {}
297    }
298   
299    for i in output['nodes']:
300        new_output['nodes'][i.id] = i
301   
302    for i in output['ways']:
303        nodes = []
304        for n in i.nodes:
305            nodes.append(new_output['nodes'][n])
306        i.nodes = nodes
307        new_output['ways'][i.id] = i
308   
309    for i in output['relations']:
310        members = []
311        for m in i.members:
312            o = new_output["%ss" % m['type']][m['ref']] 
313            members.append({'type':m['type'], 'ref': o, 'role': m['role']})
314        i.members = members
315        new_output['relations'][i.id] = i
316   
317    return new_output
318
319class ParseObjects(ContentHandler):
320    def __init__ (self, site_url="http://openstreetmap.org"):
321        self.site_url = site_url
322        self.output = {
323           'nodes': [],
324           'ways': [],
325           'relations': []
326        }   
327       
328   
329    def startElement (self, name, attr):
330         """Handle creating the self.current node, and pulling tags/nd refs."""
331         
332         if name in ['node', 'way', 'relation']:
333            self.current = OSMObj(int(attr['id']), name, int(attr['version']), site_url=self.site_url)
334            if attr.has_key("changeset"):
335                self.current.changeset = int(attr['changeset'])
336            if attr.has_key("user"):
337                self.current.user = attr['user']
338            if attr.has_key("uid"):
339                self.current.uid = int(attr['uid'])
340            if attr.has_key("timestamp"):
341                self.current.timestamp = attr['timestamp']
342         
343         elif name =='nd' and self.current:
344             self.current.nodes.append(int(attr["ref"]))
345         
346         elif name == 'tag' and self.current:
347             self.current.tags[attr['k']] = attr['v']
348         
349         elif name == "member":
350            self.current.members.append({
351                'type': attr['type'],
352                'ref': int(attr['ref']),
353                'role': attr['role']
354             })   
355         
356         if name == "node":
357            self.current.loc = (attr['lon'], attr['lat'])
358         
359    def endElement (self, name):
360        """Switch on node type, and serialize to XML for upload or print."""
361        if name in ['way', 'node','relation']:
362            self.output['%ss' % name].append(self.current)
363
364def parse(f, arrange=True, site_url = "http://openstreetmap.org"):
365    """
366    >>> import urllib
367    >>> u = urllib.urlopen("http://openstreetmap.org/api/0.6/way/29787178/full")
368    >>> data = parse(u)
369    >>> way = data['ways'][29787178]
370    >>> node0 = way.nodes[0]
371    >>> node0.id
372    328108960
373    >>> node0.type
374    u'node'
375    >>> 'addr:housenumber' in way.tags.keys()
376    True
377    >>> way.tags['addr:housenumber']
378    u'236'
379    """
380   
381    parser = ParseObjects(site_url=site_url)
382    xml.sax.parse( f, parser )         
383    output = parser.output
384    if arrange:
385        try:
386            output = rearrange(output)
387        except:
388            pass
389    return output
390
391def parseString(data, arrange=True, site_url = "http://openstreetmap.org"):
392    """
393    >>> nodeXML = '<osm version="0.6" generator="OpenStreetMap server"><node id="327260132" lat="42.3621444" lon="-71.0996605" version="1" changeset="736637" user="crschmidt" uid="1034" visible="true" timestamp="2009-01-05T15:35:26Z"/></osm>'
394    >>> data = parseString(nodeXML)
395    >>> len(data['nodes'])
396    1
397    >>> node = data['nodes'][327260132] 
398    >>> map(lambda x: int(float(x)), node.loc)
399    [-71, 42]
400    """
401    parser = ParseObjects(site_url=site_url)
402    xml.sax.parseString( data, parser  )         
403    output = parser.output
404    if arrange:
405        try:
406            output = rearrange(output)
407        except:
408            pass
409    return output
410
411if __name__ == "__main__": 
412    if len(sys.argv) > 1 and sys.argv[1] == "--test":
413        import doctest
414        doctest.testmod()
415        sys.exit()
416    f = sys.stdin
417    parser = ParseObjects()
418    xml.sax.parse( f, parser )           
419    import pprint
420    pprint.pprint(rearrange(parser.output))
Note: See TracBrowser for help on using the repository browser.