]> git.openstreetmap.org Git - nominatim.git/blob - tests/steps/osm2pgsql_setup.py
add osm2pgsql update tests
[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, 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)
162
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)""")
170     world.conn.commit()
171
172         
173     os.remove(fname)
174     world.osm2pgsql = []
175
176 actiontypes = { 'C' : 'create', 'M' : 'modify', 'D' : 'delete' }
177
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
181        into the database.
182     """
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""")
188     world.conn.commit()
189     world.run_nominatim_script('setup', 'index', 'index-noanalyse')
190     world.run_nominatim_script('setup', 'create-functions', 'create-partition-functions', 'enable-diff-updates')
191
192     with tempfile.NamedTemporaryFile(dir='/tmp', suffix='.osc', delete=False) as fd:
193         fname = fd.name
194         fd.write("<?xml version='1.0' encoding='UTF-8'?>\n")
195         fd.write('<osmChange version="0.6" generator="Osmosis 0.43.1">\n')
196
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']], ))
201
202         fd.write('</osmChange>\n')
203
204     logger.debug( "Filename: %s" % fname)
205
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)
212
213     os.remove(fname)
214     world.osm2pgsql = []