]> git.openstreetmap.org Git - nominatim.git/blob - tests/steps/osm2pgsql_setup.py
Merge remote-tracking branch 'upstream/master'
[nominatim.git] / tests / steps / osm2pgsql_setup.py
1 """ Steps for setting up a test database for osm2pgsql import.
2
3     Note that osm2pgsql features need a database and therefore need
4     to be tagged with @DB.
5 """
6
7 from nose.tools import *
8 from lettuce import *
9
10 import logging
11 import random
12 import tempfile
13 import os
14 import subprocess
15
16 logger = logging.getLogger(__name__)
17
18 @before.each_scenario
19 def osm2pgsql_setup_test(scenario):
20     world.osm2pgsql = []
21
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).
30     """
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"
34                }
35         node.update(line)
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)
41         else:
42             node['lon'] = random.random()*360 - 180
43             node['lat'] = random.random()*180 - 90
44         if 'tags' in node:
45             node['tags'] = world.make_hash(line['tags'])
46         else:
47             node['tags'] = {}
48
49         world.osm2pgsql.append(node)
50
51
52 @step(u'the osm ways:')
53 def osm2pgsql_import_ways(step):
54     """ Define a list of OSM ways to be imported.
55     """
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"
59                }
60         way.update(line)
61
62         way['id'] = int(way['id'])
63         if 'tags' in way:
64             way['tags'] = world.make_hash(line['tags'])
65         else:
66             way['tags'] = None
67         way['nodes'] = way['nodes'].strip().split()
68
69         world.osm2pgsql.append(way)
70
71 membertype = { 'N' : 'node', 'W' : 'way', 'R' : 'relation' }
72
73 @step(u'the osm relations:')
74 def osm2pgsql_import_rels(step):
75     """ Define a list of OSM relation to be imported.
76     """
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"
80                }
81         rel.update(line)
82
83         rel['id'] = int(rel['id'])
84         if 'tags' in rel:
85             rel['tags'] = world.make_hash(line['tags'])
86         else:
87             rel['tags'] = {}
88         members = []
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]], 
94                                 memid[1:], 
95                                 memparts[1] if len(memparts) == 2 else ''
96                               ))
97         rel['members'] = members
98
99         world.osm2pgsql.append(rel)
100
101
102
103 def _sort_xml_entries(x, y):
104     if x['type'] == y['type']:
105         return cmp(x['id'], y['id'])
106     else:
107         return cmp('NWR'.find(x['type']), 'NWR'.find(y['type']))
108
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:
113             fd.write('/>\n')
114         else:
115             fd.write('>\n')
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))
125         fd.write('</way>\n')
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')
133
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.
138     """
139
140     world.osm2pgsql.sort(cmp=_sort_xml_entries)
141
142     # create a OSM file in /tmp
143     with tempfile.NamedTemporaryFile(dir='/tmp', suffix='.osm', delete=False) as fd:
144         fname = fd.name
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')
148         
149         for obj in world.osm2pgsql:
150             write_osm_obj(fd, obj)
151
152         fd.write('</osm>\n')
153
154     logger.debug( "Filename: %s" % fname)
155
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, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
159     (outp, outerr) = proc.communicate()
160     assert (proc.returncode == 0), "OSM data import failed:\n%s\n%s\n" % (outp, outerr)
161
162     ### reintroduce the triggers/indexes we've lost by having osm2pgsql set up place again
163     cur = world.conn.cursor()
164     cur.execute("""CREATE TRIGGER place_before_delete BEFORE DELETE ON place
165                     FOR EACH ROW EXECUTE PROCEDURE place_delete()""")
166     cur.execute("""CREATE TRIGGER place_before_insert BEFORE INSERT ON place
167                    FOR EACH ROW EXECUTE PROCEDURE place_insert()""")
168     cur.execute("""CREATE UNIQUE INDEX idx_place_osm_unique on place using btree(osm_id,osm_type,class,type)""")
169     world.conn.commit()
170
171         
172     os.remove(fname)
173     world.osm2pgsql = []
174
175 actiontypes = { 'C' : 'create', 'M' : 'modify', 'D' : 'delete' }
176
177 @step(u'updating osm data')
178 def osm2pgsql_update_place(step):
179     """Creates an osc file from the previously defined data and imports it
180        into the database.
181     """
182     world.run_nominatim_script('setup', 'create-functions', 'create-partition-functions')
183     cur = world.conn.cursor()
184     cur.execute("""insert into placex (osm_type, osm_id, class, type, name, admin_level,
185                                housenumber, street, addr_place, isin, postcode, country_code, extratags,
186                                geometry) select * from place""")
187     world.conn.commit()
188     world.run_nominatim_script('setup', 'index', 'index-noanalyse')
189     world.run_nominatim_script('setup', 'create-functions', 'create-partition-functions', 'enable-diff-updates')
190
191     with tempfile.NamedTemporaryFile(dir='/tmp', suffix='.osc', delete=False) as fd:
192         fname = fd.name
193         fd.write("<?xml version='1.0' encoding='UTF-8'?>\n")
194         fd.write('<osmChange version="0.6" generator="Osmosis 0.43.1">\n')
195
196         for obj in world.osm2pgsql:
197             fd.write('<%s>\n' % (actiontypes[obj['action']], ))
198             write_osm_obj(fd, obj)
199             fd.write('</%s>\n' % (actiontypes[obj['action']], ))
200
201         fd.write('</osmChange>\n')
202
203     logger.debug( "Filename: %s" % fname)
204
205     cmd = [os.path.join(world.config.source_dir, 'utils', 'update.php')]
206     cmd.extend(['--import-diff', fname])
207     proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
208     (outp, outerr) = proc.communicate()
209     assert (proc.returncode == 0), "OSM data update failed:\n%s\n%s\n" % (outp, outerr)
210
211     os.remove(fname)
212     world.osm2pgsql = []