1 """ Steps for setting up a test database for osm2pgsql import.
 
   3     Note that osm2pgsql features need a database and therefore need
 
   7 from nose.tools import *
 
  16 logger = logging.getLogger(__name__)
 
  19 def osm2pgsql_setup_test(scenario):
 
  22 @step(u'the osm nodes:')
 
  23 def osm2pgsql_import_nodes(step):
 
  24     """ Define a list of OSM nodes to be imported, given as a table.
 
  25         Each line describes one node with all its attributes.
 
  26         'id' is mendatory, all other fields are filled with random values
 
  27         when not given. If 'tags' is missing an empty tag list is assumed.
 
  28         For updates, a mandatory 'action' column needs to contain 'A' (add),
 
  29         'M' (modify), 'D' (delete).
 
  31     for line in step.hashes:
 
  32         node = { 'type' : 'N', 'version' : '1', 'timestamp': "2012-05-01T15:06:20Z",  
 
  33                  'changeset' : "11470653", 'uid' : "122294", 'user' : "foo"
 
  36         node['id'] = int(node['id'])
 
  37         if 'geometry' in node:
 
  38             lat, lon = node['geometry'].split(' ')
 
  39             node['lat'] = float(lat)
 
  40             node['lon'] = float(lon)
 
  42             node['lon'] = random.random()*360 - 180
 
  43             node['lat'] = random.random()*180 - 90
 
  45             node['tags'] = world.make_hash(line['tags'])
 
  49         world.osm2pgsql.append(node)
 
  52 @step(u'the osm ways:')
 
  53 def osm2pgsql_import_ways(step):
 
  54     """ Define a list of OSM ways to be imported.
 
  56     for line in step.hashes:
 
  57         way = { 'type' : 'W', 'version' : '1', 'timestamp': "2012-05-01T15:06:20Z",  
 
  58                  'changeset' : "11470653", 'uid' : "122294", 'user' : "foo"
 
  62         way['id'] = int(way['id'])
 
  64             way['tags'] = world.make_hash(line['tags'])
 
  67         way['nodes'] = way['nodes'].strip().split()
 
  69         world.osm2pgsql.append(way)
 
  71 membertype = { 'N' : 'node', 'W' : 'way', 'R' : 'relation' }
 
  73 @step(u'the osm relations:')
 
  74 def osm2pgsql_import_rels(step):
 
  75     """ Define a list of OSM relation to be imported.
 
  77     for line in step.hashes:
 
  78         rel = { 'type' : 'R', 'version' : '1', 'timestamp': "2012-05-01T15:06:20Z",  
 
  79                  'changeset' : "11470653", 'uid' : "122294", 'user' : "foo"
 
  83         rel['id'] = int(rel['id'])
 
  85             rel['tags'] = world.make_hash(line['tags'])
 
  89         if rel['members'].strip():
 
  90             for mem in line['members'].split(','):
 
  91                 memparts = mem.strip().split(':', 2)
 
  92                 memid = memparts[0].upper()
 
  93                 members.append((membertype[memid[0]], 
 
  95                                 memparts[1] if len(memparts) == 2 else ''
 
  97         rel['members'] = members
 
  99         world.osm2pgsql.append(rel)
 
 103 def _sort_xml_entries(x, y):
 
 104     if x['type'] == y['type']:
 
 105         return cmp(x['id'], y['id'])
 
 107         return cmp('NWR'.find(x['type']), 'NWR'.find(y['type']))
 
 109 def write_osm_obj(fd, obj):
 
 110     if obj['type'] == 'N':
 
 111         fd.write('<node id="%(id)d" lat="%(lat).8f" lon="%(lon).8f" version="%(version)s" timestamp="%(timestamp)s" changeset="%(changeset)s" uid="%(uid)s" user="%(user)s"'% obj)
 
 112         if obj['tags'] is None:
 
 116             for k,v in obj['tags'].iteritems():
 
 117                 fd.write('  <tag k="%s" v="%s"/>\n' % (k, v))
 
 118             fd.write('</node>\n')
 
 119     elif obj['type'] == 'W':
 
 120         fd.write('<way id="%(id)d" version="%(version)s" changeset="%(changeset)s" timestamp="%(timestamp)s" user="%(user)s" uid="%(uid)s">\n' % obj)
 
 121         for nd in obj['nodes']:
 
 122             fd.write('<nd ref="%s" />\n' % (nd,))
 
 123         for k,v in obj['tags'].iteritems():
 
 124             fd.write('  <tag k="%s" v="%s"/>\n' % (k, v))
 
 126     elif obj['type'] == 'R':
 
 127         fd.write('<relation id="%(id)d" version="%(version)s" changeset="%(changeset)s" timestamp="%(timestamp)s" user="%(user)s" uid="%(uid)s">\n' % obj)
 
 128         for mem in obj['members']:
 
 129             fd.write('  <member type="%s" ref="%s" role="%s"/>\n' % mem)
 
 130         for k,v in obj['tags'].iteritems():
 
 131             fd.write('  <tag k="%s" v="%s"/>\n' % (k, v))
 
 132         fd.write('</relation>\n')
 
 134 @step(u'loading osm data')
 
 135 def osm2pgsql_load_place(step):
 
 136     """Imports the previously defined OSM data into a fresh copy of a
 
 137        Nominatim test database.
 
 140     world.osm2pgsql.sort(cmp=_sort_xml_entries)
 
 142     # create a OSM file in /tmp
 
 143     with tempfile.NamedTemporaryFile(dir='/tmp', suffix='.osm', delete=False) as fd:
 
 145         fd.write("<?xml version='1.0' encoding='UTF-8'?>\n")
 
 146         fd.write('<osm version="0.6" generator="test-nominatim" timestamp="2014-08-26T20:22:02Z">\n')
 
 147         fd.write('\t<bounds minlat="43.72335" minlon="7.409205" maxlat="43.75169" maxlon="7.448637"/>\n')
 
 149         for obj in world.osm2pgsql:
 
 150             write_osm_obj(fd, obj)
 
 154     logger.debug( "Filename: %s" % fname)
 
 156     cmd = [os.path.join(world.config.source_dir, 'utils', 'setup.php')]
 
 157     cmd.extend(['--osm-file', fname, '--import-data','--osm2pgsql-cache', '300'])
 
 158     proc = subprocess.Popen(cmd, cwd=world.config.source_dir,
 
 159                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 
 160     (outp, outerr) = proc.communicate()
 
 161     assert (proc.returncode == 0), "OSM data import failed:\n%s\n%s\n" % (outp, outerr)
 
 163     ### reintroduce the triggers/indexes we've lost by having osm2pgsql set up place again
 
 164     cur = world.conn.cursor()
 
 165     cur.execute("""CREATE TRIGGER place_before_delete BEFORE DELETE ON place
 
 166                     FOR EACH ROW EXECUTE PROCEDURE place_delete()""")
 
 167     cur.execute("""CREATE TRIGGER place_before_insert BEFORE INSERT ON place
 
 168                    FOR EACH ROW EXECUTE PROCEDURE place_insert()""")
 
 169     cur.execute("""CREATE UNIQUE INDEX idx_place_osm_unique on place using btree(osm_id,osm_type,class,type)""")
 
 176 actiontypes = { 'C' : 'create', 'M' : 'modify', 'D' : 'delete' }
 
 178 @step(u'updating osm data')
 
 179 def osm2pgsql_update_place(step):
 
 180     """Creates an osc file from the previously defined data and imports it
 
 183     world.run_nominatim_script('setup', 'create-functions', 'create-partition-functions')
 
 184     cur = world.conn.cursor()
 
 185     cur.execute("""insert into placex (osm_type, osm_id, class, type, name, admin_level,
 
 186                                housenumber, street, addr_place, isin, postcode, country_code, extratags,
 
 187                                geometry) select * from place""")
 
 189     world.run_nominatim_script('setup', 'index', 'index-noanalyse')
 
 190     world.run_nominatim_script('setup', 'create-functions', 'create-partition-functions', 'enable-diff-updates')
 
 192     with tempfile.NamedTemporaryFile(dir='/tmp', suffix='.osc', delete=False) as fd:
 
 194         fd.write("<?xml version='1.0' encoding='UTF-8'?>\n")
 
 195         fd.write('<osmChange version="0.6" generator="Osmosis 0.43.1">\n')
 
 197         for obj in world.osm2pgsql:
 
 198             fd.write('<%s>\n' % (actiontypes[obj['action']], ))
 
 199             write_osm_obj(fd, obj)
 
 200             fd.write('</%s>\n' % (actiontypes[obj['action']], ))
 
 202         fd.write('</osmChange>\n')
 
 204     logger.debug( "Filename: %s" % fname)
 
 206     cmd = [os.path.join(world.config.source_dir, 'utils', 'update.php')]
 
 207     cmd.extend(['--import-diff', fname])
 
 208     proc = subprocess.Popen(cmd, cwd=world.config.source_dir,
 
 209                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 
 210     (outp, outerr) = proc.communicate()
 
 211     assert (proc.returncode == 0), "OSM data update failed:\n%s\n%s\n" % (outp, outerr)