2 from nose.tools import *
8 from haversine import haversine
9 from shapely.wkt import loads as wkt_load
10 from shapely.ops import linemerge
12 logger = logging.getLogger(__name__)
14 class NominatimConfig:
18 loglevel = getattr(logging, os.environ.get('LOGLEVEL','info').upper())
19 if 'LOGFILE' in os.environ:
20 logging.basicConfig(filename=os.environ.get('LOGFILE','run.log'),
23 logging.basicConfig(level=loglevel)
24 # Nominatim test setup
25 self.base_url = os.environ.get('NOMINATIM_SERVER', 'http://localhost/nominatim')
26 self.source_dir = os.path.abspath(os.environ.get('NOMINATIM_DIR', '../Nominatim'))
27 self.template_db = os.environ.get('TEMPLATE_DB', 'test_template_nominatim')
28 self.test_db = os.environ.get('TEST_DB', 'test_nominatim')
29 self.local_settings_file = os.environ.get('NOMINATIM_SETTINGS', '/tmp/nominatim_settings.php')
30 self.reuse_template = 'NOMINATIM_REUSE_TEMPLATE' in os.environ
31 self.keep_scenario_db = 'NOMINATIM_KEEP_SCENARIO_DB' in os.environ
32 os.environ['NOMINATIM_SETTINGS'] = '/tmp/nominatim_settings.php'
34 scriptpath = os.path.dirname(os.path.abspath(__file__))
35 self.scene_path = os.environ.get('SCENE_PATH',
36 os.path.join(scriptpath, '..', 'scenes', 'data'))
40 return 'Server URL: %s\nSource dir: %s\n' % (self.base_url, self.source_dir)
42 world.config = NominatimConfig()
45 def write_nominatim_config(dbname):
46 f = open(world.config.local_settings_file, 'w')
47 f.write("<?php\n @define('CONST_Database_DSN', 'pgsql://@/%s');\n" % dbname)
52 def run_nominatim_script(script, *args):
53 cmd = [os.path.join(world.config.source_dir, 'utils', '%s.php' % script)]
54 cmd.extend(['--%s' % x for x in args])
55 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
56 (outp, outerr) = proc.communicate()
57 assert (proc.returncode == 0), "Script '%s' failed:\n%s\n%s\n" % (script, outp, outerr)
61 return eval('{' + inp + '}')
65 """ Splits a unique identifier for places into its components.
66 As place_ids cannot be used for testing, we use a unique
67 identifier instead that is of the form <osmtype><osmid>[:class].
71 return None, None, None
73 assert_in(osmtype, ('R','N','W'))
75 osmid, cls = oid[1:].split(':')
76 return (osmtype, int(osmid), cls)
78 return (osmtype, int(oid[1:]), None)
82 """ Tries to retrive the place_id for a unique identifier. """
86 osmtype, osmid, cls = world.split_id(oid)
89 cur = world.conn.cursor()
91 q = 'SELECT place_id FROM placex where osm_type = %s and osm_id = %s'
92 params = (osmtype, osmid)
94 q = 'SELECT place_id FROM placex where osm_type = %s and osm_id = %s and class = %s'
95 params = (osmtype, osmid, cls)
96 cur.execute(q, params)
97 assert_equals (cur.rowcount, 1)
98 return cur.fetchone()[0]
102 def match_geometry(coord, matchstring):
103 m = re.match(r'([-0-9.]+),\s*([-0-9.]+)\s*(?:\+-([0-9.]+)([a-z]+)?)?', matchstring)
104 assert_is_not_none(m, "Invalid match string")
106 logger.debug("Distmatch: %s/%s %s %s" % (m.group(1), m.group(2), m.group(3), m.group(4) ))
107 dist = haversine(coord, (float(m.group(1)), float(m.group(2))))
109 if m.group(3) is not None:
110 expdist = float(m.group(3))
111 if m.group(4) is not None:
112 if m.group(4) == 'm':
113 expdist = expdist/1000
114 elif m.group(4) == 'km':
117 raise Exception("Unknown unit '%s' in geometry match" % (m.group(4), ))
121 logger.debug("Distances expected: %f, got: %f" % (expdist, dist))
122 assert dist <= expdist, "Geometry too far away, expected: %f, got: %f" % (expdist, dist)
127 def db_dump_table(table):
128 cur = world.conn.cursor()
129 cur.execute('SELECT * FROM %s' % table)
130 print '<<<<<<< BEGIN OF TABLE DUMP %s' % table
133 print '<<<<<<< END OF TABLE DUMP %s' % table
136 def db_drop_database(name):
137 conn = psycopg2.connect(database='postgres')
138 conn.set_isolation_level(0)
140 cur.execute('DROP DATABASE IF EXISTS %s' % (name, ))
144 world.is_template_set_up = False
147 def db_template_setup():
148 """ Set up a template database, containing all tables
149 but not yet any functions.
151 if world.is_template_set_up:
154 world.is_template_set_up = True
155 world.write_nominatim_config(world.config.template_db)
156 if world.config.reuse_template:
157 # check that the template is there
158 conn = psycopg2.connect(database='postgres')
160 cur.execute('select count(*) from pg_database where datname = %s',
161 (world.config.template_db,))
162 if cur.fetchone()[0] == 1:
165 # just in case... make sure a previous table has been dropped
166 world.db_drop_database(world.config.template_db)
167 # call the first part of database setup
168 world.run_nominatim_script('setup', 'create-db', 'setup-db')
169 # remove external data to speed up indexing for tests
170 conn = psycopg2.connect(database=world.config.template_db)
171 psycopg2.extras.register_hstore(conn, globally=False, unicode=True)
173 for table in ('gb_postcode', 'us_postcode', 'us_state', 'us_statecounty'):
174 cur.execute('TRUNCATE TABLE %s' % (table,))
177 # execute osm2pgsql on an empty file to get the right tables
178 osm2pgsql = os.path.join(world.config.source_dir, 'osm2pgsql', 'osm2pgsql')
179 proc = subprocess.Popen([osm2pgsql, '-lsc', '-O', 'gazetteer', '-d', world.config.template_db, '-'],
180 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
181 [outstr, errstr] = proc.communicate(input='<osm version="0.6"></osm>')
182 world.run_nominatim_script('setup', 'create-functions', 'create-tables', 'create-partition-tables', 'create-partition-functions', 'load-data', 'create-search-indices')
185 # Leave the table around so it can be reused again after a non-reuse test round.
187 def db_template_teardown(total):
188 """ Set up a template database, containing all tables
189 but not yet any functions.
191 if world.is_template_set_up:
193 if not world.config.reuse_template:
194 world.db_drop_database(world.config.template_db)
196 os.remove(world.config.local_settings_file)
198 pass # ignore missing file
201 ##########################################################################
203 # Data scene handling
207 world.current_scene = None
210 def load_scene(name):
211 if name in world.scenes:
212 world.current_scene = world.scenes[name]
214 with open(os.path.join(world.config.scene_path, "%s.wkt" % name), 'r') as fd:
218 obj, wkt = line.split('|', 2)
220 scene[obj.strip()] = wkt_load(wkt)
221 world.scenes[name] = scene
222 world.current_scene = scene
225 def get_scene_geometry(name):
227 # Not a scene description
231 for obj in name.split('+'):
233 if oname.startswith(':'):
234 geoms.append(world.current_scene[oname[1:]])
236 scene, obj = oname.split(':', 2)
237 oldscene = world.current_scene
238 world.load_scene(scene)
239 wkt = world.current_scene[obj]
240 world.current_scene = oldscene
246 return linemerge(geoms)