diff options
| -rw-r--r-- | chimere/migrations/0010_auto__add_field_route_import_version__add_field_marker_import_version.py | 195 | ||||
| -rw-r--r-- | chimere/models.py | 2 | ||||
| -rw-r--r-- | chimere/utils.py | 7 | ||||
| -rw-r--r-- | example_project/settings.py.example | 3 | ||||
| -rw-r--r-- | utils/OsmApi.py | 747 | ||||
| -rw-r--r-- | utils/__init__.py | 0 | 
6 files changed, 953 insertions, 1 deletions
diff --git a/chimere/migrations/0010_auto__add_field_route_import_version__add_field_marker_import_version.py b/chimere/migrations/0010_auto__add_field_route_import_version__add_field_marker_import_version.py new file mode 100644 index 0000000..c94710f --- /dev/null +++ b/chimere/migrations/0010_auto__add_field_route_import_version__add_field_marker_import_version.py @@ -0,0 +1,195 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + +    def forwards(self, orm): +         +        # Adding field 'Route.import_version' +        db.add_column('chimere_route', 'import_version', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True), keep_default=False) + +        # Adding field 'Marker.import_version' +        db.add_column('chimere_marker', 'import_version', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True), keep_default=False) + + +    def backwards(self, orm): +         +        # Deleting field 'Route.import_version' +        db.delete_column('chimere_route', 'import_version') + +        # Deleting field 'Marker.import_version' +        db.delete_column('chimere_marker', 'import_version') + + +    models = { +        'chimere.area': { +            'Meta': {'ordering': "('order', 'name')", 'object_name': 'Area'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'lower_right_corner': ('django.contrib.gis.db.models.fields.PointField', [], {'default': "'POINT(0 0)'"}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), +            'order': ('django.db.models.fields.IntegerField', [], {}), +            'upper_left_corner': ('django.contrib.gis.db.models.fields.PointField', [], {'default': "'POINT(0 0)'"}), +            'urn': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'unique': 'True', 'max_length': '50', 'blank': 'True'}) +        }, +        'chimere.category': { +            'Meta': {'ordering': "['order']", 'object_name': 'Category'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), +            'order': ('django.db.models.fields.IntegerField', [], {}) +        }, +        'chimere.color': { +            'Meta': {'ordering': "['order']", 'object_name': 'Color'}, +            'code': ('django.db.models.fields.CharField', [], {'max_length': '6'}), +            'color_theme': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.ColorTheme']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'order': ('django.db.models.fields.IntegerField', [], {}) +        }, +        'chimere.colortheme': { +            'Meta': {'object_name': 'ColorTheme'}, +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}) +        }, +        'chimere.icon': { +            'Meta': {'object_name': 'Icon'}, +            'height': ('django.db.models.fields.IntegerField', [], {}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), +            'width': ('django.db.models.fields.IntegerField', [], {}) +        }, +        'chimere.importer': { +            'Meta': {'object_name': 'Importer'}, +            'categories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False'}), +            'filtr': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'importer_type': ('django.db.models.fields.CharField', [], {'max_length': '4'}), +            'source_url': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'state': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}) +        }, +        'chimere.marker': { +            'Meta': {'ordering': "('status', 'name')", 'object_name': 'Marker'}, +            'available_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), +            'categories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False'}), +            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'import_key': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'import_source': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'import_version': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), +            'multimedia_files': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'marker'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['chimere.MultimediaFile']"}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), +            'pictures': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'marker'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['chimere.PictureFile']"}), +            'point': ('chimere.widgets.PointField', [], {}), +            'ref_item': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'submited_marker'", 'null': 'True', 'to': "orm['chimere.Marker']"}), +            'route': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'associated_marker'", 'null': 'True', 'to': "orm['chimere.Route']"}), +            'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), +            'status': ('django.db.models.fields.CharField', [], {'max_length': '1'}), +            'submiter_comment': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'submiter_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), +            'submiter_session_key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}) +        }, +        'chimere.multimediafile': { +            'Meta': {'object_name': 'MultimediaFile'}, +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'multimedia_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.MultimediaType']"}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150', 'null': 'True', 'blank': 'True'}), +            'order': ('django.db.models.fields.IntegerField', [], {'default': '1'}), +            'url': ('django.db.models.fields.CharField', [], {'max_length': '200'}) +        }, +        'chimere.multimediatype': { +            'Meta': {'object_name': 'MultimediaType'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'iframe': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'media_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), +            'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}) +        }, +        'chimere.news': { +            'Meta': {'object_name': 'News'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'content': ('django.db.models.fields.TextField', [], {}), +            'date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'title': ('django.db.models.fields.CharField', [], {'max_length': '150'}) +        }, +        'chimere.picturefile': { +            'Meta': {'object_name': 'PictureFile'}, +            'height': ('django.db.models.fields.IntegerField', [], {}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'miniature': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150', 'null': 'True', 'blank': 'True'}), +            'order': ('django.db.models.fields.IntegerField', [], {'default': '1'}), +            'picture': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), +            'width': ('django.db.models.fields.IntegerField', [], {}) +        }, +        'chimere.property': { +            'Meta': {'object_name': 'Property'}, +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'marker': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Marker']"}), +            'propertymodel': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.PropertyModel']"}), +            'value': ('django.db.models.fields.TextField', [], {}) +        }, +        'chimere.propertymodel': { +            'Meta': {'ordering': "('order',)", 'object_name': 'PropertyModel'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), +            'order': ('django.db.models.fields.IntegerField', [], {}), +            'type': ('django.db.models.fields.CharField', [], {'max_length': '1'}) +        }, +        'chimere.route': { +            'Meta': {'ordering': "('status', 'name')", 'object_name': 'Route'}, +            'associated_file': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.RouteFile']", 'null': 'True', 'blank': 'True'}), +            'categories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False'}), +            'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), +            'height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'import_key': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'import_source': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'import_version': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), +            'picture': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), +            'ref_item': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'submited_route'", 'null': 'True', 'to': "orm['chimere.Route']"}), +            'route': ('chimere.widgets.RouteField', [], {}), +            'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), +            'status': ('django.db.models.fields.CharField', [], {'max_length': '1'}), +            'submiter_comment': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'submiter_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), +            'submiter_session_key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), +            'width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) +        }, +        'chimere.routefile': { +            'Meta': {'ordering': "('name',)", 'object_name': 'RouteFile'}, +            'file_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), +            'raw_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), +            'simplified_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}) +        }, +        'chimere.subcategory': { +            'Meta': {'ordering': "['category', 'order']", 'object_name': 'SubCategory'}, +            'areas': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'related_name': "'areas'", 'blank': 'True', 'to': "orm['chimere.Area']"}), +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Category']"}), +            'color_theme': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.ColorTheme']", 'null': 'True', 'blank': 'True'}), +            'icon': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Icon']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'item_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), +            'order': ('django.db.models.fields.IntegerField', [], {}) +        }, +        'chimere.tinyurl': { +            'Meta': {'object_name': 'TinyUrl'}, +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'parameters': ('django.db.models.fields.CharField', [], {'max_length': '500'}) +        } +    } + +    complete_apps = ['chimere'] diff --git a/chimere/models.py b/chimere/models.py index 4122712..c93d271 100644 --- a/chimere/models.py +++ b/chimere/models.py @@ -239,6 +239,8 @@ class GeographicItem(models.Model):      status = models.CharField(_(u"Status"), max_length=1, choices=STATUS)      import_key = models.CharField(_(u"Import key"), max_length=200,                                    blank=True, null=True) +    import_version = models.IntegerField(_(u"Import version"), +                                         blank=True, null=True)      import_source = models.CharField(_(u"Source"), max_length=200,                                       blank=True, null=True)      if settings.CHIMERE_DAYS_BEFORE_EVENT: diff --git a/chimere/utils.py b/chimere/utils.py index e243c34..62d3786 100644 --- a/chimere/utils.py +++ b/chimere/utils.py @@ -149,6 +149,7 @@ class OSMManager(ImportManager):          for node in tree.xpath('//node'):              name, point, linestring = None, None, None              node_id = node.attrib.get('id') +            version = node.attrib.get('version')              for item in node:                  k = item.attrib.get('k')                  if k == 'name': @@ -157,7 +158,8 @@ class OSMManager(ImportManager):                                                  node.get('lat'))              if point:                  dct = {'point':point, -                       'name':name,} +                       'name':name, +                       'import_version':version}                  m = None                  if node_id:                      dct_import = { @@ -165,6 +167,9 @@ class OSMManager(ImportManager):                          'import_source':self.importer_instance.source_url}                      try:                          m = Marker.objects.get(**dct_import) +                        if version and m.import_version == int(version): +                            # no update since the last import +                            continue                          for k in dct:                              setattr(m, k, dct[k])                          m.save() diff --git a/example_project/settings.py.example b/example_project/settings.py.example index a42a1be..dc49768 100644 --- a/example_project/settings.py.example +++ b/example_project/settings.py.example @@ -69,6 +69,9 @@ CHIMERE_DEFAULT_CATEGORIES = [1]  CHIMERE_MAP_LAYER = "new OpenLayers.Layer.OSM.Mapnik('Mapnik')" # OSM mapnik map  CHIMERE_XAPI_URL = 'http://open.mapquestapi.com/xapi/api/0.6/' +CHIMERE_OSM_API_URL = 'api06.dev.openstreetmap.org' # test URL +CHIMERE_OSM_USER = 'test' +CHIMERE_OSM_PASSWORD = 'test'  DEBUG = True  TEMPLATE_DEBUG = DEBUG diff --git a/utils/OsmApi.py b/utils/OsmApi.py new file mode 100644 index 0000000..3b1b9b1 --- /dev/null +++ b/utils/OsmApi.py @@ -0,0 +1,747 @@ +#-*- coding: utf-8 -*- + +########################################################################### +##                                                                       ## +## Copyrights Etienne Chové <chove@crans.org> 2009-2010                  ## +##                                                                       ## +## 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 3 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, see <http://www.gnu.org/licenses/>. ## +##                                                                       ## +########################################################################### + +## HomePage : http://wiki.openstreetmap.org/wiki/PythonOsmApi + +########################################################################### +## History                                                               ## +########################################################################### +## 0.2.19  2010-05-24 Add debug message on ApiError                      ## +## 0.2.18  2010-04-20 Fix ChangesetClose and _http_request               ## +## 0.2.17  2010-01-02 Capabilities implementation                        ## +## 0.2.16  2010-01-02 ChangesetsGet by Alexander Rampp                   ## +## 0.2.15  2009-12-16 xml encoding error for < and >                     ## +## 0.2.14  2009-11-20 changesetautomulti parameter                       ## +## 0.2.13  2009-11-16 modify instead update for osc                      ## +## 0.2.12  2009-11-14 raise ApiError on 4xx errors -- Xoff               ## +## 0.2.11  2009-10-14 unicode error on ChangesetUpload                   ## +## 0.2.10  2009-10-14 RelationFullRecur definition                       ## +## 0.2.9   2009-10-13 automatic changeset management                     ## +##                    ChangesetUpload implementation                     ## +## 0.2.8   2009-10-13 *(Create|Update|Delete) use not unique _do method  ## +## 0.2.7   2009-10-09 implement all missing fonctions except             ## +##                    ChangesetsGet and GetCapabilities                  ## +## 0.2.6   2009-10-09 encoding clean-up                                  ## +## 0.2.5   2009-10-09 implements NodesGet, WaysGet, RelationsGet         ## +##                               ParseOsm, ParseOsc                      ## +## 0.2.4   2009-10-06 clean-up                                           ## +## 0.2.3   2009-09-09 keep http connection alive for multiple request    ## +##                    (Node|Way|Relation)Get return None when object     ## +##                    have been deleted (raising error before)           ## +## 0.2.2   2009-07-13 can identify applications built on top of the lib  ## +## 0.2.1   2009-05-05 some changes in constructor -- chove@crans.org     ## +## 0.2     2009-05-01 initial import                                     ## +########################################################################### + +__version__ = '0.2.19' + +import httplib, base64, xml.dom.minidom, time, sys, urllib + +class ApiError(Exception): +    	 +    def __init__(self, status, reason, payload): +        self.status  = status +        self.reason  = reason +        self.payload = payload +     +    def __str__(self): +        return "Request failed: " + str(self.status) + " - " + self.reason + " - " + self.payload + +########################################################################### +## Main class                                                            ## + +class OsmApi: +         +    def __init__(self, +        username = None, +        password = None, +        passwordfile = None, +        appid = "", +        created_by = "PythonOsmApi/"+__version__, +        api = "www.openstreetmap.org", +        changesetauto = False, +        changesetautotags = {}, +        changesetautosize = 500, +        changesetautomulti = 1, +        debug = False +        ): +     +        # debug +        self._debug = debug +         +        # Get username +        if username: +            self._username = username +        elif passwordfile: +            self._username =  open(passwordfile).readline().split(":")[0].strip()             +     +        # Get password +        if password: +            self._password   = password +        elif passwordfile: +            for l in open(passwordfile).readlines(): +                l = l.strip().split(":") +                if l[0] == self._username: +                    self._password = l[1] + +        # Changest informations +        self._changesetauto      = changesetauto      # auto create and close changesets +        self._changesetautotags  = changesetautotags  # tags for automatic created changesets +        self._changesetautosize  = changesetautosize  # change count for auto changeset +        self._changesetautosize  = changesetautosize  # change count for auto changeset +        self._changesetautomulti = changesetautomulti # close a changeset every # upload +        self._changesetautocpt   = 0 +        self._changesetautodata  = []                 # data to upload for auto group +         +        # Get API +        self._api = api + +        # Get created_by +        if not appid: +            self._created_by = created_by +        else: +            self._created_by = appid + " (" + created_by + ")" + +        # Initialisation      +        self._CurrentChangesetId = 0 +         +        # Http connection +        self._conn = httplib.HTTPConnection(self._api, 80) + +    def __del__(self): +        if self._changesetauto: +            self._changesetautoflush(True) +        return None + +    ####################################################################### +    # Capabilities                                                        # +    ####################################################################### +     +    def Capabilities(self): +        """ Returns ApiCapabilities. """ +        uri = "/api/capabilities" +        data = self._get(uri) +        data = xml.dom.minidom.parseString(data) +        print data.getElementsByTagName("osm") +        data = data.getElementsByTagName("osm")[0].getElementsByTagName("api")[0] +        result = {} +        for elem in data.childNodes: +            if elem.nodeType <> elem.ELEMENT_NODE: +                continue +            result[elem.nodeName] = {} +            print elem.nodeName +            for k, v in elem.attributes.items(): +                try: +                    result[elem.nodeName][k] = float(v) +                except: +                    result[elem.nodeName][k] = v +        return result + +    ####################################################################### +    # Node                                                                # +    ####################################################################### + +    def NodeGet(self, NodeId, NodeVersion = -1): +        """ Returns NodeData for node #NodeId. """ +        uri = "/api/0.6/node/"+str(NodeId) +        if NodeVersion <> -1: uri += "/"+str(NodeVersion) +        data = self._get(uri) +        if not data: return data +        data = xml.dom.minidom.parseString(data) +        data = data.getElementsByTagName("osm")[0].getElementsByTagName("node")[0] +        return self._DomParseNode(data) + +    def NodeCreate(self, NodeData): +        """ Creates a node. Returns updated NodeData (without timestamp). """ +        return self._do("create", "node", NodeData) +             +    def NodeUpdate(self, NodeData): +        """ Updates node with NodeData. Returns updated NodeData (without timestamp). """ +        return self._do("modify", "node", NodeData) + +    def NodeDelete(self, NodeData): +        """ Delete node with NodeData. Returns updated NodeData (without timestamp). """ +        return self._do("delete", "node", NodeData) + +    def NodeHistory(self, NodeId): +        """ Returns dict(NodeVerrsion: NodeData). """ +        uri = "/api/0.6/node/"+str(NodeId)+"/history" +        data = self._get(uri) +        data = xml.dom.minidom.parseString(data) +        result = {} +        for data in data.getElementsByTagName("osm")[0].getElementsByTagName("node"): +            data = self._DomParseNode(data) +            result[data[u"version"]] = data +        return result + +    def NodeWays(self, NodeId): +        """ Returns [WayData, ... ] containing node #NodeId. """ +        uri = "/api/0.6/node/%d/ways"%NodeId +        data = self._get(uri) +        data = xml.dom.minidom.parseString(data) +        result = [] +        for data in data.getElementsByTagName("osm")[0].getElementsByTagName("way"): +            data = self._DomParseRelation(data) +            result.append(data) +        return result +     +    def NodeRelations(self, NodeId): +        """ Returns [RelationData, ... ] containing node #NodeId. """ +        uri = "/api/0.6/node/%d/relations"%NodeId +        data = self._get(uri) +        data = xml.dom.minidom.parseString(data) +        result = [] +        for data in data.getElementsByTagName("osm")[0].getElementsByTagName("relation"): +            data = self._DomParseRelation(data) +            result.append(data) +        return result + +    def NodesGet(self, NodeIdList): +        """ Returns dict(NodeId: NodeData) for each node in NodeIdList """ +        uri  = "/api/0.6/nodes?nodes=" + ",".join([str(x) for x in NodeIdList]) +        data = self._get(uri) +        data = xml.dom.minidom.parseString(data) +        result = {} +        for data in data.getElementsByTagName("osm")[0].getElementsByTagName("node"): +            data = self._DomParseNode(data) +            result[data[u"id"]] = data +        return result + +    ####################################################################### +    # Way                                                                 # +    ####################################################################### + +    def WayGet(self, WayId, WayVersion = -1): +        """ Returns WayData for way #WayId. """ +        uri = "/api/0.6/way/"+str(WayId) +        if WayVersion <> -1: uri += "/"+str(WayVersion) +        data = self._get(uri) +        if not data: return data +        data = xml.dom.minidom.parseString(data) +        data = data.getElementsByTagName("osm")[0].getElementsByTagName("way")[0] +        return self._DomParseWay(data) +     +    def WayCreate(self, WayData): +        """ Creates a way. Returns updated WayData (without timestamp). """ +        return self._do("create", "way", WayData) + +    def WayUpdate(self, WayData): +        """ Updates way with WayData. Returns updated WayData (without timestamp). """ +        return self._do("modify", "way", WayData) + +    def WayDelete(self, WayData): +        """ Delete way with WayData. Returns updated WayData (without timestamp). """ +        return self._do("delete", "way", WayData) + +    def WayHistory(self, WayId): +        """ Returns dict(WayVerrsion: WayData). """ +        uri = "/api/0.6/way/"+str(WayId)+"/history" +        data = self._get(uri) +        data = xml.dom.minidom.parseString(data) +        result = {} +        for data in data.getElementsByTagName("osm")[0].getElementsByTagName("way"): +            data = self._DomParseWay(data) +            result[data[u"version"]] = data +        return result +     +    def WayRelations(self, WayId): +        """ Returns [RelationData, ...] containing way #WayId. """ +        uri = "/api/0.6/way/%d/relations"%WayId +        data = self._get(uri) +        data = xml.dom.minidom.parseString(data) +        result = [] +        for data in data.getElementsByTagName("osm")[0].getElementsByTagName("relation"): +            data = self._DomParseRelation(data) +            result.append(data) +        return result + +    def WayFull(self, WayId): +        """ Return full data for way WayId as list of {type: node|way|relation, data: {}}. """ +        uri = "/api/0.6/way/"+str(WayId)+"/full" +        data = self._get(uri) +        return self.ParseOsm(data) + +    def WaysGet(self, WayIdList): +        """ Returns dict(WayId: WayData) for each way in WayIdList """ +        uri = "/api/0.6/ways?ways=" + ",".join([str(x) for x in WayIdList]) +        data = self._get(uri) +        data = xml.dom.minidom.parseString(data) +        result = {} +        for data in data.getElementsByTagName("osm")[0].getElementsByTagName("way"): +            data = self._DomParseWay(data) +            result[data[u"id"]] = data +        return result + +    ####################################################################### +    # Relation                                                            # +    ####################################################################### + +    def RelationGet(self, RelationId, RelationVersion = -1): +        """ Returns RelationData for relation #RelationId. """ +        uri = "/api/0.6/relation/"+str(RelationId) +        if RelationVersion <> -1: uri += "/"+str(RelationVersion) +        data = self._get(uri) +        if not data: return data +        data = xml.dom.minidom.parseString(data) +        data = data.getElementsByTagName("osm")[0].getElementsByTagName("relation")[0] +        return self._DomParseRelation(data) + +    def RelationCreate(self, RelationData): +        """ Creates a relation. Returns updated RelationData (without timestamp). """ +        return self._do("create", "relation", RelationData) +     +    def RelationUpdate(self, RelationData): +        """ Updates relation with RelationData. Returns updated RelationData (without timestamp). """ +        return self._do("modify", "relation", RelationData) + +    def RelationDelete(self, RelationData): +        """ Delete relation with RelationData. Returns updated RelationData (without timestamp). """ +        return self._do("delete", "relation", RelationData) + +    def RelationHistory(self, RelationId): +        """ Returns dict(RelationVerrsion: RelationData). """ +        uri = "/api/0.6/relation/"+str(RelationId)+"/history" +        data = self._get(uri) +        data = xml.dom.minidom.parseString(data) +        result = {} +        for data in data.getElementsByTagName("osm")[0].getElementsByTagName("relation"): +            data = self._DomParseRelation(data) +            result[data[u"version"]] = data +        return result +     +    def RelationRelations(self, RelationId): +        """ Returns list of RelationData containing relation #RelationId. """ +        uri = "/api/0.6/relation/%d/relations"%RelationId +        data = self._get(uri) +        data = xml.dom.minidom.parseString(data) +        result = [] +        for data in data.getElementsByTagName("osm")[0].getElementsByTagName("relation"): +            data = self._DomParseRelation(data) +            result.append(data) +        return result + +    def RelationFullRecur(self, RelationId): +        """ Return full data for relation RelationId. Recurisve version relation of relations. """ +        data = [] +        todo = [RelationId] +        done = [] +        while todo: +            rid = todo.pop(0) +            done.append(rid) +            temp = self.RelationFull(rid) +            for item in temp: +                if item["type"] <> "relation": +                    continue +                if item["data"]["id"] in done: +                    continue +                todo.append(item["data"]["id"])             +            data += temp +        return data +     +    def RelationFull(self, RelationId): +        """ Return full data for relation RelationId as list of {type: node|way|relation, data: {}}. """ +        uri = "/api/0.6/relation/"+str(RelationId)+"/full" +        data = self._get(uri) +        return self.ParseOsm(data) + +    def RelationsGet(self, RelationIdList): +        """ Returns dict(RelationId: RelationData) for each relation in RelationIdList """ +        uri = "/api/0.6/relations?relations=" + ",".join([str(x) for x in RelationIdList]) +        data = self._get(uri) +        data = xml.dom.minidom.parseString(data) +        result = {} +        for data in data.getElementsByTagName("osm")[0].getElementsByTagName("relation"): +            data = self._DomParseRelation(data) +            result[data[u"id"]] = data             +        return result + +    ####################################################################### +    # Changeset                                                           # +    ####################################################################### + +    def ChangesetGet(self, ChangesetId): +        """ Returns ChangesetData for changeset #ChangesetId. """ +        data = self._get("/api/0.6/changeset/"+str(ChangesetId)) +        data = xml.dom.minidom.parseString(data) +        data = data.getElementsByTagName("osm")[0].getElementsByTagName("changeset")[0] +        return self._DomParseChangeset(data) +     +    def ChangesetUpdate(self, ChangesetTags = {}): +        """ Updates current changeset with ChangesetTags. """ +        if self._CurrentChangesetId == -1: +            raise Exception, "No changeset currently opened" +        if u"created_by" not in ChangesetTags: +            ChangesetTags[u"created_by"] = self._created_by +        result = self._put("/api/0.6/changeset/"+str(self._CurrentChangesetId), self._XmlBuild("changeset", {u"tag": ChangesetTags})) +        return self._CurrentChangesetId + +    def ChangesetCreate(self, ChangesetTags = {}): +        """ Opens a changeset. Returns #ChangesetId. """ +        if self._CurrentChangesetId: +            raise Exception, "Changeset alreadey opened" +        if u"created_by" not in ChangesetTags: +            ChangesetTags[u"created_by"] = self._created_by +        result = self._put("/api/0.6/changeset/create", self._XmlBuild("changeset", {u"tag": ChangesetTags})) +        self._CurrentChangesetId = int(result) +        return self._CurrentChangesetId +     +    def ChangesetClose(self): +        """ Closes current changeset. Returns #ChangesetId. """ +        if not self._CurrentChangesetId: +            raise Exception, "No changeset currently opened" +        result = self._put("/api/0.6/changeset/"+str(self._CurrentChangesetId)+"/close", u"") +        CurrentChangesetId = self._CurrentChangesetId +        self._CurrentChangesetId = 0 +        return CurrentChangesetId + +    def ChangesetUpload(self, ChangesData): +        """ Upload data. ChangesData is a list of dict {type: node|way|relation, action: create|delete|modify, data: {}}. Returns list with updated ids. """ +        data = "" +        data += u"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +        data += u"<osmChange version=\"0.6\" generator=\"" + self._created_by + "\">\n" +        for change in ChangesData: +            data += u"<"+change["action"]+">\n" +            change["data"]["changeset"] = self._CurrentChangesetId +            data += self._XmlBuild(change["type"], change["data"], False).decode("utf-8") +            data += u"</"+change["action"]+">\n" +        data += u"</osmChange>" +        data = self._http("POST", "/api/0.6/changeset/"+str(self._CurrentChangesetId)+"/upload", True, data.encode("utf-8")) +        data = xml.dom.minidom.parseString(data) +        data = data.getElementsByTagName("diffResult")[0] +        data = [x for x in data.childNodes if x.nodeType == x.ELEMENT_NODE] +        for i in range(len(ChangesData)): +            if ChangesData[i]["action"] == "delete": +                ChangesData[i]["data"].pop("version") +            else: +                ChangesData[i]["data"]["version"] = int(data[i].getAttribute("new_id")) +        return ChangesData +         +    def ChangesetDownload(self, ChangesetId): +        """ Download data from a changeset. Returns list of dict {type: node|way|relation, action: create|delete|modify, data: {}}. """ +        uri = "/api/0.6/changeset/"+str(ChangesetId)+"/download" +        data = self._get(uri) +        return self.ParseOsc(data) +     +    def ChangesetsGet(self, min_lon=None, min_lat=None, max_lon=None, max_lat=None, +                      userid=None, username=None, +                      closed_after=None, created_before=None, +                      only_open=False, only_closed=False): +        """ Returns dict(ChangsetId: ChangesetData) matching all criteria. """ +         +        uri = "/api/0.6/changesets" +        params = {} +        if min_lon or min_lat or max_lon or max_lat: +            params["bbox"] = ",".join([str(min_lon),str(min_lat),str(max_lon),str(max_lat)]) +        if userid: +            params["user"] = userid +        if username: +            params["display_name"] = username +        if closed_after and not created_before: +            params["time"] = closed_after +        if created_before: +            if not closed_after: +                closed_after = "1970-01-01T00:00:00Z" +            params["time"] = closed_after + "," + created_before +        if only_open: +            params["open"] = 1 +        if only_closed: +            params["closed"] = 1 +             +        if params: +            uri += "?" + urllib.urlencode(params) +                 +        data = self._get(uri) +        data = xml.dom.minidom.parseString(data) +        data = data.getElementsByTagName("osm")[0].getElementsByTagName("changeset") +        result = {} +        for curChangeset in data: +            tmpCS = self._DomParseChangeset(curChangeset) +            result[tmpCS["id"]] = tmpCS +        return result +     +    ####################################################################### +    # Other                                                               # +    ####################################################################### + +    def Map(self, min_lon, min_lat, max_lon, max_lat): +        """ Download data in bounding box. Returns list of dict {type: node|way|relation, data: {}}. """ +        uri = "/api/0.6/map?bbox=%f,%f,%f,%f"%(min_lon, min_lat, max_lon, max_lat) +        data = self._get(uri) +        return self.ParseOsm(data) + +    ####################################################################### +    # Data parser                                                         # +    ####################################################################### +     +    def ParseOsm(self, data): +        """ Parse osm data. Returns list of dict {type: node|way|relation, data: {}}. """ +        data = xml.dom.minidom.parseString(data) +        data = data.getElementsByTagName("osm")[0] +        result = [] +        for elem in data.childNodes: +            if elem.nodeName == u"node": +                result.append({u"type": elem.nodeName, u"data": self._DomParseNode(elem)}) +            elif elem.nodeName == u"way": +                result.append({u"type": elem.nodeName, u"data": self._DomParseWay(elem)})                         +            elif elem.nodeName == u"relation": +                result.append({u"type": elem.nodeName, u"data": self._DomParseRelation(elem)}) +        return result     + +    def ParseOsc(self, data): +        """ Parse osc data. Returns list of dict {type: node|way|relation, action: create|delete|modify, data: {}}. """ +        data = xml.dom.minidom.parseString(data) +        data = data.getElementsByTagName("osmChange")[0] +        result = [] +        for action in data.childNodes: +            if action.nodeName == u"#text": continue +            for elem in action.childNodes: +                if elem.nodeName == u"node": +                    result.append({u"action":action.nodeName, u"type": elem.nodeName, u"data": self._DomParseNode(elem)}) +                elif elem.nodeName == u"way": +                    result.append({u"action":action.nodeName, u"type": elem.nodeName, u"data": self._DomParseWay(elem)})                         +                elif elem.nodeName == u"relation": +                    result.append({u"action":action.nodeName, u"type": elem.nodeName, u"data": self._DomParseRelation(elem)}) +        return result + +    ####################################################################### +    # Internal http function                                              # +    ####################################################################### + +    def _do(self, action, OsmType, OsmData): +        if self._changesetauto: +            self._changesetautodata.append({"action":action, "type":OsmType, "data":OsmData}) +            self._changesetautoflush() +            return None +        else: +            return self._do_manu(action, OsmType, OsmData) +             +    def _do_manu(self, action, OsmType, OsmData):         +        if not self._CurrentChangesetId: +            raise Exception, "You need to open a changeset before uploading data" +        if u"timestamp" in OsmData: +            OsmData.pop(u"timestamp") +        OsmData[u"changeset"] = self._CurrentChangesetId +        if action == "create": +            if OsmData.get(u"id", -1) > 0: +                raise Exception, "This "+OsmType+" already exists" +            result = self._put("/api/0.6/"+OsmType+"/create", self._XmlBuild(OsmType, OsmData)) +            OsmData[u"id"] = int(result.strip()) +            OsmData[u"version"] = 1 +            return OsmData +        elif action == "modify": +            result = self._put("/api/0.6/"+OsmType+"/"+str(OsmData[u"id"]), self._XmlBuild(OsmType, OsmData)) +            OsmData[u"version"] = int(result.strip()) +            return OsmData +        elif action =="delete": +            result = self._delete("/api/0.6/"+OsmType+"/"+str(OsmData[u"id"]), self._XmlBuild(OsmType, OsmData)) +            OsmData[u"version"] = int(result.strip()) +            OsmData[u"visible"] = False +            return OsmData +     +    def flush(self): +        return self._changesetautoflush(True) +         +    def _changesetautoflush(self, force = False): +        while (len(self._changesetautodata) >= self._changesetautosize) or (force and self._changesetautodata): +            if self._changesetautocpt == 0: +                self.ChangesetCreate(self._changesetautotags) +            self.ChangesetUpload(self._changesetautodata[:self._changesetautosize]) +            self._changesetautodata = self._changesetautodata[self._changesetautosize:] +            self._changesetautocpt += 1 +            if self._changesetautocpt == self._changesetautomulti: +                self.ChangesetClose() +                self._changesetautocpt = 0 +        if self._changesetautocpt and force: +            self.ChangesetClose() +            self._changesetautocpt = 0 +        return None +         +    def _http_request(self, cmd, path, auth, send): +        if self._debug: +            path2 = path +            if len(path2) > 50: +                path2 = path2[:50]+"[...]" +            print >>sys.stderr, "%s %s %s"%(time.strftime("%Y-%m-%d %H:%M:%S"),cmd,path2) +        self._conn.putrequest(cmd, path) +        self._conn.putheader('User-Agent', self._created_by) +        if auth: +            self._conn.putheader('Authorization', 'Basic ' + base64.encodestring(self._username + ':' + self._password).strip()) +        if send <> None: +            self._conn.putheader('Content-Length', len(send)) +        self._conn.endheaders() +        if send: +            self._conn.send(send) +        response = self._conn.getresponse() +        if response.status <> 200: +            payload = response.read().strip() +            if response.status == 410: +                return None +            raise ApiError(response.status, response.reason, payload) +        if self._debug: +            print >>sys.stderr, "%s %s %s done"%(time.strftime("%Y-%m-%d %H:%M:%S"),cmd,path2) +        return response.read() +     +    def _http(self, cmd, path, auth, send): +        i = 0 +        while True: +            i += 1 +            try: +                return self._http_request(cmd, path, auth, send) +            except ApiError, e: +                if e.status >= 500: +                    if i == 5: raise +                    if i <> 1: time.sleep(5) +                    self._conn = httplib.HTTPConnection(self._api, 80) +                else: raise +            except Exception: +                if i == 5: raise +                if i <> 1: time.sleep(5) +                self._conn = httplib.HTTPConnection(self._api, 80) +     +    def _get(self, path): +        return self._http('GET', path, False, None) + +    def _put(self, path, data): +        return self._http('PUT', path, True, data) +     +    def _delete(self, path, data): +        return self._http('DELETE', path, True, data) +     +    ####################################################################### +    # Internal dom function                                               # +    ####################################################################### +     +    def _DomGetAttributes(self, DomElement): +        """ Returns a formated dictionnary of attributes of a DomElement. """ +        result = {} +        for k, v in DomElement.attributes.items(): +            if k == u"uid"         : v = int(v) +            elif k == u"changeset" : v = int(v) +            elif k == u"version"   : v = int(v) +            elif k == u"id"        : v = int(v) +            elif k == u"lat"       : v = float(v) +            elif k == u"lon"       : v = float(v) +            elif k == u"open"      : v = v=="true" +            elif k == u"visible"   : v = v=="true" +            elif k == u"ref"       : v = int(v) +            result[k] = v +        return result             +         +    def _DomGetTag(self, DomElement): +        """ Returns the dictionnary of tags of a DomElement. """ +        result = {} +        for t in DomElement.getElementsByTagName("tag"): +            k = t.attributes["k"].value +            v = t.attributes["v"].value +            result[k] = v +        return result + +    def _DomGetNd(self, DomElement): +        """ Returns the list of nodes of a DomElement. """ +        result = [] +        for t in DomElement.getElementsByTagName("nd"): +            result.append(int(int(t.attributes["ref"].value))) +        return result             + +    def _DomGetMember(self, DomElement): +        """ Returns a list of relation members. """ +        result = [] +        for m in DomElement.getElementsByTagName("member"): +            result.append(self._DomGetAttributes(m)) +        return result + +    def _DomParseNode(self, DomElement): +        """ Returns NodeData for the node. """ +        result = self._DomGetAttributes(DomElement) +        result[u"tag"] = self._DomGetTag(DomElement) +        return result + +    def _DomParseWay(self, DomElement): +        """ Returns WayData for the way. """ +        result = self._DomGetAttributes(DomElement) +        result[u"tag"] = self._DomGetTag(DomElement) +        result[u"nd"]  = self._DomGetNd(DomElement)         +        return result +     +    def _DomParseRelation(self, DomElement): +        """ Returns RelationData for the relation. """ +        result = self._DomGetAttributes(DomElement) +        result[u"tag"]    = self._DomGetTag(DomElement) +        result[u"member"] = self._DomGetMember(DomElement) +        return result + +    def _DomParseChangeset(self, DomElement): +        """ Returns ChangesetData for the changeset. """ +        result = self._DomGetAttributes(DomElement) +        result[u"tag"] = self._DomGetTag(DomElement) +        return result + +    ####################################################################### +    # Internal xml builder                                                # +    ####################################################################### + +    def _XmlBuild(self, ElementType, ElementData, WithHeaders = True): + +        xml  = u"" +        if WithHeaders: +            xml += u"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +            xml += u"<osm version=\"0.6\" generator=\"" + self._created_by + "\">\n" + +        # <element attr="val"> +        xml += u"  <" + ElementType +        if u"id" in ElementData: +            xml += u" id=\"" + str(ElementData[u"id"]) + u"\""         +        if u"lat" in ElementData: +            xml += u" lat=\"" + str(ElementData[u"lat"]) + u"\""         +        if u"lon" in ElementData: +            xml += u" lon=\"" + str(ElementData[u"lon"]) + u"\"" +        if u"version" in ElementData: +            xml += u" version=\"" + str(ElementData[u"version"]) + u"\"" +        xml += u" visible=\"" + str(ElementData.get(u"visible", True)).lower() + u"\"" +        if ElementType in [u"node", u"way", u"relation"]: +            xml += u" changeset=\"" + str(self._CurrentChangesetId) + u"\"" +        xml += u">\n" + +        # <tag... /> +        for k, v in ElementData.get(u"tag", {}).items(): +            xml += u"    <tag k=\""+self._XmlEncode(k)+u"\" v=\""+self._XmlEncode(v)+u"\"/>\n" + +        # <member... /> +        for member in ElementData.get(u"member", []): +            xml += u"    <member type=\""+member[u"type"]+"\" ref=\""+str(member[u"ref"])+u"\" role=\""+self._XmlEncode(member[u"role"])+"\"/>\n" + +        # <nd... /> +        for ref in ElementData.get(u"nd", []): +            xml += u"    <nd ref=\""+str(ref)+u"\"/>\n" + +        # </element> +        xml += u"  </" + ElementType + u">\n" +         +        if WithHeaders: +            xml += u"</osm>\n" + +        return xml.encode("utf8") + +    def _XmlEncode(self, text): +        return text.replace("&", "&").replace("\"", """).replace("<","<").replace(">",">") + +## End of main class                                                     ## +########################################################################### diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/utils/__init__.py  | 
