Merge pull request #600 from lonvia/behave-tests-rebased

Rework tests
This commit is contained in:
Sarah Hoffmann 2017-01-01 10:57:23 +01:00 committed by GitHub
commit 4a5a997e18
96 changed files with 5314 additions and 5743 deletions

View File

@ -8,13 +8,13 @@
processIsolation="false"
stopOnFailure="false"
syntaxCheck="true"
bootstrap="tests-php/bootstrap.php"
bootstrap="test/php/bootstrap.php"
>
<php>
</php>
<testsuites>
<testsuite name="Nominatim PHP Test Suite">
<directory>./tests-php/Nominatim</directory>
<directory>./test/php/Nominatim</directory>
</testsuite>
</testsuites>
<filter>

134
test/README.md Normal file
View File

@ -0,0 +1,134 @@
This directory contains functional and unit tests for the Nominatim API.
Prerequisites
=============
* Python 3 (https://www.python.org/)
* behave test framework >= 1.2.5 (https://github.com/behave/behave)
* nose (https://nose.readthedocs.org)
* pytidylib (http://countergram.com/open-source/pytidylib)
* psycopg2 (http://initd.org/psycopg/)
To get the prerequisites on a a fresh Ubuntu LTS 16.04 run:
[sudo] apt-get install python3-dev python3-pip python3-psycopg2 python3-tidylib phpunit
pip3 install --user behave nose
Overall structure
=================
There are two kind of tests in this test suite. There are functional tests
which test the API interface using a BDD test framework and there are unit
tests for specific PHP functions.
This test directory is sturctured as follows:
-+- bdd Functional API tests
| \
| +- steps Step implementations for test descriptions
| +- osm2pgsql Tests for data import via osm2pgsql
| +- db Tests for internal data processing on import and update
| +- api Tests for API endpoints (search, reverse, etc.)
|
+- php PHP unit tests
+- scenes Geometry test data
+- testdb Base data for generating API test database
PHP Unit Tests
==============
Unit tests can be found in the php/ directory and tests selected php functions.
Very low coverage.
To execute the test suite run
cd test/php
phpunit ../
It will read phpunit.xml which points to the library, test path, bootstrap
strip and set other parameters.
BDD Functional Tests
====================
Functional tests are written as BDD instructions. For more information on
the philosophy of BDD testing, see http://pythonhosted.org/behave/philosophy.html
Usage
-----
To run the functional tests, do
cd test/bdd
behave
The tests can be configured with a set of environment variables:
* `BUILD_DIR` - build directory of Nominatim installation to test
* `TEMPLATE_DB` - name of template database used as a skeleton for
the test databases (db tests)
* `TEST_DB` - name of test database (db tests)
* `ABI_TEST_DB` - name of the database containing the API test data (api tests)
* `TEST_SETTINGS_TEMPLATE` - file to write temporary Nominatim settings to
* `REMOVE_TEMPLATE` - if true, the template database will not be reused during
the next run. Reusing the base templates speeds up tests
considerably but might lead to outdated errors for some
changes in the database layout.
* `KEEP_TEST_DB` - if true, the test database will not be dropped after a test
is finished. Should only be used if one single scenario is
run, otherwise the result is undefined.
Logging can be defined through command line parameters of behave itself. Check
out `behave --help` for details. Also keep an eye out for the 'work-in-progress'
feature of behave which comes in handy when writing new tests.
Writing Tests
-------------
The following explanation assume that the reader is familiar with the BDD
notations of features, scenarios and steps.
All possible steps can be found in the `steps` directory and should ideally
be documented.
### API Tests (`test/bdd/api`)
These tests are meant to test the different API endpoints and their parameters.
They require a preimported test database, which consists of the import of a
planet extract. The polygons defining the extract can be found in the test/testdb
directory. There is also a reduced set of wikipedia data for this extract,
which you need to import as well. For Tiger tests the data of South Dakota
is required. Get the Tiger files `46*`.
The official test dataset is derived from the 160725 planet. Newer
planets are likely to work as well but you may see isolated test
failures where the data has changed. To recreate the input data
for the test database run:
wget http://free.nchc.org.tw/osm.planet/pbf/planet-160725.osm.pbf
osmconvert planet-160725.osm.pbf -B=test/testdb/testdb.polys -o=testdb.pbf
Before importing make sure to add the following to your local settings:
@define('CONST_Database_DSN', 'pgsql://@/test_api_nominatim');
@define('CONST_Wikipedia_Data_Path', CONST_BasePath.'/test/testdb');
### Indexing Tests (`test/bdd/db`)
These tests check the import and update of the Nominatim database. They do not
test the correctness of osm2pgsql. Each test will write some data into the `place`
table (and optionally `the planet_osm_*` tables if required) and then run
Nominatim's processing functions on that.
These tests need to create their own test databases. By default they will be
called `test_template_nominatim` and `test_nominatim`. Names can be changed with
the environment variables `TEMPLATE_DB` and `TEST_DB`. The user running the tests
needs superuser rights for postgres.
### Import Tests (`test/bdd/osm2pgsql`)
These tests check that data is imported correctly into the place table. They
use the same template database as the Indexing tests, so the same remarks apply.

View File

@ -0,0 +1,14 @@
@APIDB
Feature: Object details
Check details page for correctness
Scenario Outline: Details via OSM id
When sending details query for <object>
Then the result is valid html
Examples:
| object |
| 492887 |
| N4267356889 |
| W230804120 |
| R123924 |

View File

@ -0,0 +1,17 @@
@APIDB
Feature: Places by osm_type and osm_id Tests
Simple tests for internal server errors and response format.
Scenario Outline: address lookup for existing node, way, relation
When sending <format> lookup query for N3284625766,W6065798,,R123924,X99,N0
Then the result is valid <format>
And exactly 3 results are returned
Examples:
| format |
| xml |
| json |
Scenario: address lookup for non-existing or invalid node, way, relation
When sending xml lookup query for X99,,N0,nN158845944,ABC,,W9
Then exactly 0 results are returned

View File

@ -0,0 +1,36 @@
@APIDB
Feature: Localization of reverse search results
Scenario: default language
When sending json reverse coordinates 18.1147,-15.95
Then result addresses contain
| ID | country |
| 0 | Mauritanie موريتانيا |
Scenario: accept-language parameter
When sending json reverse coordinates 18.1147,-15.95
| accept-language |
| en,fr |
Then result addresses contain
| ID | country |
| 0 | Mauritania |
Scenario: HTTP accept language header
Given the HTTP header
| accept-language |
| fr-ca,fr;q=0.8,en-ca;q=0.5,en;q=0.3 |
When sending json reverse coordinates 18.1147,-15.95
Then result addresses contain
| ID | country |
| 0 | Mauritanie |
Scenario: accept-language parameter and HTTP header
Given the HTTP header
| accept-language |
| fr-ca,fr;q=0.8,en-ca;q=0.5,en;q=0.3 |
When sending json reverse coordinates 18.1147,-15.95
| accept-language |
| en |
Then result addresses contain
| ID | country |
| 0 | Mauritania |

View File

@ -0,0 +1,102 @@
@APIDB
Feature: Parameters for Reverse API
Testing diferent parameter options for reverse API.
Scenario Outline: Reverse-geocoding without address
When sending <format> reverse coordinates 53.603,10.041
| addressdetails |
| 0 |
Then exactly 1 result is returned
And result has not attributes address
Examples:
| format |
| json |
| jsonv2 |
| xml |
Scenario Outline: Reverse Geocoding with extratags
When sending <format> reverse coordinates 10.776234290950017,106.70425325632095
| extratags |
| 1 |
Then result 0 has attributes extratags
Examples:
| format |
| xml |
| json |
| jsonv2 |
Scenario Outline: Reverse Geocoding with namedetails
When sending <format> reverse coordinates 10.776455623137625,106.70175343751907
| namedetails |
| 1 |
Then result 0 has attributes namedetails
Examples:
| format |
| xml |
| json |
| jsonv2 |
Scenario Outline: Reverse Geocoding contains TEXT geometry
When sending <format> reverse coordinates 47.165989816710066,9.515774846076965
| polygon_text |
| 1 |
Then result 0 has attributes <response_attribute>
Examples:
| format | response_attribute |
| xml | geotext |
| json | geotext |
| jsonv2 | geotext |
Scenario Outline: Reverse Geocoding contains polygon-as-points geometry
When sending <format> reverse coordinates 47.165989816710066,9.515774846076965
| polygon |
| 1 |
Then result 0 has not attributes <response_attribute>
Examples:
| format | response_attribute |
| xml | polygonpoints |
| json | polygonpoints |
| jsonv2 | polygonpoints |
Scenario Outline: Reverse Geocoding contains SVG geometry
When sending <format> reverse coordinates 47.165989816710066,9.515774846076965
| polygon_svg |
| 1 |
Then result 0 has attributes <response_attribute>
Examples:
| format | response_attribute |
| xml | geosvg |
| json | svg |
| jsonv2 | svg |
Scenario Outline: Reverse Geocoding contains KML geometry
When sending <format> reverse coordinates 47.165989816710066,9.515774846076965
| polygon_kml |
| 1 |
Then result 0 has attributes <response_attribute>
Examples:
| format | response_attribute |
| xml | geokml |
| json | geokml |
| jsonv2 | geokml |
Scenario Outline: Reverse Geocoding contains GEOJSON geometry
When sending <format> reverse coordinates 47.165989816710066,9.515774846076965
| polygon_geojson |
| 1 |
Then result 0 has attributes <response_attribute>
Examples:
| format | response_attribute |
| xml | geojson |
| json | geojson |
| jsonv2 | geojson |

View File

@ -0,0 +1,25 @@
@APIDB
Feature: Reverse geocoding
Testing the reverse function
@Tiger
Scenario: TIGER house number
When sending jsonv2 reverse coordinates 45.3345,-97.5214
Then results contain
| osm_type | category | type |
| way | place | house |
And result addresses contain
| house_number | road | postcode | country_code |
| 906 | West 1st Street | 57274 | us |
@Tiger
Scenario: No TIGER house number for zoom < 18
When sending jsonv2 reverse coordinates 45.3345,-97.5214
| zoom |
| 17 |
Then results contain
| osm_type | category |
| way | highway |
And result addresses contain
| road | postcode | country_code |
| West 1st Street | 57274 | us |

View File

@ -0,0 +1,130 @@
@APIDB
Feature: Simple Reverse Tests
Simple tests for internal server errors and response format.
Scenario Outline: Simple reverse-geocoding
When sending reverse coordinates <lat>,<lon>
Then the result is valid xml
When sending xml reverse coordinates <lat>,<lon>
Then the result is valid xml
When sending json reverse coordinates <lat>,<lon>
Then the result is valid json
When sending jsonv2 reverse coordinates <lat>,<lon>
Then the result is valid json
When sending html reverse coordinates <lat>,<lon>
Then the result is valid html
Examples:
| lat | lon |
| 0.0 | 0.0 |
| -34.830 | -56.105 |
| 45.174 | -103.072 |
| 21.156 | -12.2744 |
Scenario Outline: Testing different parameters
When sending reverse coordinates 53.603,10.041
| param | value |
| <parameter> | <value> |
Then the result is valid xml
When sending html reverse coordinates 53.603,10.041
| param | value |
| <parameter> | <value> |
Then the result is valid html
When sending xml reverse coordinates 53.603,10.041
| param | value |
| <parameter> | <value> |
Then the result is valid xml
When sending json reverse coordinates 53.603,10.041
| param | value |
| <parameter> | <value> |
Then the result is valid json
When sending jsonv2 reverse coordinates 53.603,10.041
| param | value |
| <parameter> | <value> |
Then the result is valid json
Examples:
| parameter | value |
| polygon | 1 |
| polygon | 0 |
| polygon_text | 1 |
| polygon_text | 0 |
| polygon_kml | 1 |
| polygon_kml | 0 |
| polygon_geojson | 1 |
| polygon_geojson | 0 |
| polygon_svg | 1 |
| polygon_svg | 0 |
Scenario Outline: Wrapping of legal jsonp requests
When sending <format> reverse coordinates 67.3245,0.456
| json_callback |
| foo |
Then the result is valid json
Examples:
| format |
| json |
| jsonv2 |
@wip
Scenario Outline: Boundingbox is returned
When sending <format> reverse coordinates 14.62,108.1
| zoom |
| 4 |
Then result has bounding box in 9,20,102,113
Examples:
| format |
| json |
| jsonv2 |
| xml |
Scenario Outline: Reverse-geocoding with zoom
When sending <format> reverse coordinates 53.603,10.041
| zoom |
| 10 |
Then exactly 1 result is returned
Examples:
| format |
| json |
| jsonv2 |
| html |
| xml |
Scenario: Missing lon parameter
When sending reverse coordinates 52.52,
Then a HTTP 400 is returned
Scenario: Missing lat parameter
When sending reverse coordinates ,52.52
Then a HTTP 400 is returned
Scenario: Missing osm_id parameter
When sending reverse coordinates ,
| osm_type |
| N |
Then a HTTP 400 is returned
Scenario: Missing osm_type parameter
When sending reverse coordinates ,
| osm_id |
| 3498564 |
Then a HTTP 400 is returned
Scenario Outline: Bad format for lat or lon
When sending reverse coordinates ,
| lat | lon |
| <lat> | <lon> |
Then a HTTP 400 is returned
Examples:
| lat | lon |
| 48.9660 | 8,4482 |
| 48,9660 | 8.4482 |
| 48,9660 | 8,4482 |
| 48.966.0 | 8.4482 |
| 48.966 | 8.448.2 |
| Nan | 8.448 |
| 48.966 | Nan |

View File

@ -0,0 +1,62 @@
@APIDB
Feature: Localization of search results
Scenario: default language
When sending json search query "Vietnam"
Then results contain
| ID | display_name |
| 0 | Vit Nam |
Scenario: accept-language first
When sending json search query "Mauretanien"
| accept-language |
| en,de |
Then results contain
| ID | display_name |
| 0 | Mauritania |
Scenario: accept-language missing
When sending json search query "Mauretanien"
| accept-language |
| xx,fr,en,de |
Then results contain
| ID | display_name |
| 0 | Mauritanie |
Scenario: http accept language header first
Given the HTTP header
| accept-language |
| fr-ca,fr;q=0.8,en-ca;q=0.5,en;q=0.3 |
When sending json search query "Mauretanien"
Then results contain
| ID | display_name |
| 0 | Mauritanie |
Scenario: http accept language header and accept-language
Given the HTTP header
| accept-language |
| fr-ca,fr;q=0.8,en-ca;q=0.5,en;q=0.3 |
When sending json search query "Mauretanien"
| accept-language |
| de,en |
Then results contain
| ID | display_name |
| 0 | Mauretanien |
Scenario: http accept language header fallback
Given the HTTP header
| accept-language |
| fr-ca,en-ca;q=0.5 |
When sending json search query "Mauretanien"
Then results contain
| ID | display_name |
| 0 | Mauritanie |
Scenario: http accept language header fallback (upper case)
Given the HTTP header
| accept-language |
| fr-FR;q=0.8,en-ca;q=0.5 |
When sending json search query "Mauretanie"
Then results contain
| ID | display_name |
| 0 | Mauritanie |

View File

@ -0,0 +1,300 @@
@APIDB
Feature: Search queries
Testing different queries and parameters
Scenario: Simple XML search
When sending xml search query "Schaan"
Then result 0 has attributes place_id,osm_type,osm_id
And result 0 has attributes place_rank,boundingbox
And result 0 has attributes lat,lon,display_name
And result 0 has attributes class,type,importance,icon
And result 0 has not attributes address
And result 0 has bounding box in 46.5,47.5,9,10
Scenario: Simple JSON search
When sending json search query "Vaduz"
Then result 0 has attributes place_id,licence,icon,class,type
And result 0 has attributes osm_type,osm_id,boundingbox
And result 0 has attributes lat,lon,display_name,importance
And result 0 has not attributes address
And result 0 has bounding box in 46.5,47.5,9,10
Scenario: JSON search with addressdetails
When sending json search query "Montevideo" with address
Then address of result 0 is
| type | value |
| city | Montevideo |
| state | Montevideo |
| country | Uruguay |
| country_code | uy |
Scenario: XML search with addressdetails
When sending xml search query "Aleg" with address
| accept-language |
| en |
Then address of result 0 is
| type | value |
| city | Aleg |
| state | Brakna |
| country | Mauritania |
| country_code | mr |
Scenario: coordinate search with addressdetails
When sending json search query "14.271104294939,107.69828796387"
| accept-language |
| en |
Then results contain
| display_name |
| Plei Ya Rê, Kon Tum province, Vietnam |
Scenario: Address details with unknown class types
When sending json search query "Hundeauslauf, Hamburg" with address
Then results contain
| ID | class | type |
| 0 | leisure | dog_park |
And result addresses contain
| ID | address29 |
| 0 | Hundeauslauf |
And address of result 0 has no types leisure,dog_park
Scenario: Disabling deduplication
When sending json search query "Sievekingsallee, Hamburg"
Then there are no duplicates
When sending json search query "Sievekingsallee, Hamburg"
| dedupe |
| 0 |
Then there are duplicates
Scenario: Search with bounded viewbox in right area
When sending json search query "restaurant" with address
| bounded | viewbox |
| 1 | 9.93027,53.61634,10.10073,53.54500 |
Then result addresses contain
| state |
| Hamburg |
Scenario: Search with bounded viewboxlbrt in right area
When sending json search query "restaurant" with address
| bounded | viewboxlbrt |
| 1 | 9.93027,53.54500,10.10073,53.61634 |
Then result addresses contain
| state |
| Hamburg |
Scenario: No POI search with unbounded viewbox
When sending json search query "restaurant"
| viewbox |
| 9.93027,53.61634,10.10073,53.54500 |
Then results contain
| display_name |
| ^[^,]*[Rr]estaurant.* |
Scenario: bounded search remains within viewbox, even with no results
When sending json search query "restaurant"
| bounded | viewbox |
| 1 | 43.5403125,-5.6563282,43.54285,-5.662003 |
Then less than 1 result is returned
Scenario: bounded search remains within viewbox with results
When sending json search query "restaurant"
| bounded | viewbox |
| 1 | 9.93027,53.61634,10.10073,53.54500 |
Then result has bounding box in 53.54500,53.61634,9.93027,10.10073
Scenario: Prefer results within viewbox
When sending json search query "25 de Mayo" with address
| accept-language |
| en |
Then result addresses contain
| ID | state |
| 0 | Salto |
When sending json search query "25 de Mayo" with address
| accept-language | viewbox |
| en | -56.35879,-34.18330,-56.31618,-34.20815 |
Then result addresses contain
| ID | state |
| 0 | Florida |
Scenario: Overly large limit number for search results
When sending json search query "restaurant"
| limit |
| 1000 |
Then at most 50 results are returned
Scenario: Limit number of search results
When sending json search query "restaurant"
| limit |
| 4 |
Then exactly 4 results are returned
Scenario: Restrict to feature type country
When sending xml search query "Uruguay"
Then results contain
| ID | place_rank |
| 1 | 16 |
When sending xml search query "Uruguay"
| featureType |
| country |
Then results contain
| place_rank |
| 4 |
Scenario: Restrict to feature type state
When sending xml search query "Dakota"
Then results contain
| place_rank |
| 12 |
When sending xml search query "Dakota"
| featureType |
| state |
Then results contain
| place_rank |
| 8 |
Scenario: Restrict to feature type city
When sending xml search query "vaduz"
Then results contain
| ID | place_rank |
| 1 | 30 |
When sending xml search query "vaduz"
| featureType |
| city |
Then results contain
| place_rank |
| 16 |
Scenario: Restrict to feature type settlement
When sending json search query "Burg"
Then results contain
| ID | class |
| 1 | amenity |
When sending json search query "Burg"
| featureType |
| settlement |
Then results contain
| class | type |
| boundary | administrative |
Scenario Outline: Search with polygon threshold (json)
When sending json search query "switzerland"
| polygon_geojson | polygon_threshold |
| 1 | <th> |
Then at least 1 result is returned
And result 0 has attributes geojson
Examples:
| th |
| -1 |
| 0.0 |
| 0.5 |
| 999 |
Scenario Outline: Search with polygon threshold (xml)
When sending xml search query "switzerland"
| polygon_geojson | polygon_threshold |
| 1 | <th> |
Then at least 1 result is returned
And result 0 has attributes geojson
Examples:
| th |
| -1 |
| 0.0 |
| 0.5 |
| 999 |
Scenario Outline: Search with invalid polygon threshold (xml)
When sending xml search query "switzerland"
| polygon_geojson | polygon_threshold |
| 1 | <th> |
Then a HTTP 400 is returned
Examples:
| th |
| x |
| ;; |
| 1m |
Scenario Outline: Search with extratags
When sending <format> search query "Hauptstr"
| extratags |
| 1 |
Then result has attributes extratags
Examples:
| format |
| xml |
| json |
| jsonv2 |
Scenario Outline: Search with namedetails
When sending <format> search query "Hauptstr"
| namedetails |
| 1 |
Then result has attributes namedetails
Examples:
| format |
| xml |
| json |
| jsonv2 |
Scenario Outline: Search result with contains TEXT geometry
When sending <format> search query "Highmore"
| polygon_text |
| 1 |
Then result has attributes <response_attribute>
Examples:
| format | response_attribute |
| xml | geotext |
| json | geotext |
| jsonv2 | geotext |
Scenario Outline: Search result contains polygon-as-points geometry
When sending <format> search query "Highmore"
| polygon |
| 1 |
Then result has attributes <response_attribute>
Examples:
| format | response_attribute |
| xml | polygonpoints |
| json | polygonpoints |
| jsonv2 | polygonpoints |
Scenario Outline: Search result contains SVG geometry
When sending <format> search query "Highmore"
| polygon_svg |
| 1 |
Then result has attributes <response_attribute>
Examples:
| format | response_attribute |
| xml | geosvg |
| json | svg |
| jsonv2 | svg |
Scenario Outline: Search result contains KML geometry
When sending <format> search query "Highmore"
| polygon_kml |
| 1 |
Then result has attributes <response_attribute>
Examples:
| format | response_attribute |
| xml | geokml |
| json | geokml |
| jsonv2 | geokml |
Scenario Outline: Search result contains GEOJSON geometry
When sending <format> search query "Highmore"
| polygon_geojson |
| 1 |
Then result has attributes <response_attribute>
Examples:
| format | response_attribute |
| xml | geojson |
| json | geojson |
| jsonv2 | geojson |

View File

@ -0,0 +1,67 @@
@APIDB
Feature: Search queries
Generic search result correctness
Scenario: House number search for non-street address
When sending json search query "2 Steinwald, Austria" with address
| accept-language |
| en |
Then address of result 0 is
| type | value |
| house_number | 2 |
| hamlet | Steinwald |
| postcode | 6811 |
| country | Austria |
| country_code | at |
Scenario: House number interpolation even
When sending json search query "Schellingstr 86, Hamburg" with address
| accept-language |
| de |
Then address of result 0 is
| type | value |
| house_number | 86 |
| road | Schellingstraße |
| suburb | Eilbek |
| postcode | 22089 |
| city_district | Wandsbek |
| state | Hamburg |
| country | Deutschland |
| country_code | de |
Scenario: House number interpolation odd
When sending json search query "Schellingstr 73, Hamburg" with address
| accept-language |
| de |
Then address of result 0 is
| type | value |
| house_number | 73 |
| road | Schellingstraße |
| suburb | Eilbek |
| postcode | 22089 |
| city_district | Wandsbek |
| state | Hamburg |
| country | Deutschland |
| country_code | de |
@Tiger
Scenario: TIGER house number
When sending json search query "323 22nd Street Southwest, Huron"
Then results contain
| osm_type |
| way |
Scenario: Search with class-type feature
When sending jsonv2 search query "Hotel California"
Then results contain
| place_rank |
| 30 |
# https://trac.openstreetmap.org/ticket/5094
Scenario: housenumbers are ordered by complete match first
When sending json search query "6395 geminis, montevideo" with address
Then result addresses contain
| ID | house_number |
| 0 | 6395 |
| 1 | 6395 BIS |

View File

@ -0,0 +1,221 @@
@APIDB
Feature: Simple Tests
Simple tests for internal server errors and response format.
Scenario Outline: Testing different parameters
When sending search query "Hamburg"
| param | value |
| <parameter> | <value> |
Then at least 1 result is returned
When sending html search query "Hamburg"
| param | value |
| <parameter> | <value> |
Then at least 1 result is returned
When sending xml search query "Hamburg"
| param | value |
| <parameter> | <value> |
Then at least 1 result is returned
When sending json search query "Hamburg"
| param | value |
| <parameter> | <value> |
Then at least 1 result is returned
When sending jsonv2 search query "Hamburg"
| param | value |
| <parameter> | <value> |
Then at least 1 result is returned
Examples:
| parameter | value |
| addressdetails | 1 |
| addressdetails | 0 |
| polygon | 1 |
| polygon | 0 |
| polygon_text | 1 |
| polygon_text | 0 |
| polygon_kml | 1 |
| polygon_kml | 0 |
| polygon_geojson | 1 |
| polygon_geojson | 0 |
| polygon_svg | 1 |
| polygon_svg | 0 |
| accept-language | de,en |
| countrycodes | de |
| bounded | 1 |
| bounded | 0 |
| exclude_place_ids| 385252,1234515 |
| limit | 1000 |
| dedupe | 1 |
| dedupe | 0 |
| extratags | 1 |
| extratags | 0 |
| namedetails | 1 |
| namedetails | 0 |
Scenario: Search with invalid output format
When sending search query "Berlin"
| format |
| fd$# |
Then a HTTP 400 is returned
Scenario Outline: Simple Searches
When sending search query "<query>"
Then the result is valid html
When sending html search query "<query>"
Then the result is valid html
When sending xml search query "<query>"
Then the result is valid xml
When sending json search query "<query>"
Then the result is valid json
When sending jsonv2 search query "<query>"
Then the result is valid json
Examples:
| query |
| New York, New York |
| France |
| 12, Main Street, Houston |
| München |
| |
| hotels in nantes |
| xywxkrf |
| gh; foo() |
| %#$@*&l;der#$! |
| 234 |
| 47.4,8.3 |
Scenario: Empty XML search
When sending xml search query "xnznxvcx"
Then result header contains
| attr | value |
| querystring | xnznxvcx |
| polygon | false |
| more_url | .*format=xml.*q=xnznxvcx.* |
Scenario: Empty XML search with special XML characters
When sending xml search query "xfdghn&zxn"xvbyx<vxx>cssdex"
Then result header contains
| attr | value |
| querystring | xfdghn&zxn"xvbyx<vxx>cssdex |
| polygon | false |
| more_url | .*format=xml.*q=xfdghn%26zxn%22xvbyx%3Cvxx%3Ecssdex.* |
Scenario: Empty XML search with viewbox
When sending xml search query "xnznxvcx"
| viewbox |
| 12,45.13,77,33 |
Then result header contains
| attr | value |
| querystring | xnznxvcx |
| polygon | false |
| viewbox | 12,45.13,77,33 |
Scenario: Empty XML search with viewboxlbrt
When sending xml search query "xnznxvcx"
| viewboxlbrt |
| 12,34.13,77,45 |
Then result header contains
| attr | value |
| querystring | xnznxvcx |
| polygon | false |
| viewbox | 12,45,77,34.13 |
Scenario: Empty XML search with viewboxlbrt and viewbox
When sending xml search query "pub"
| viewbox | viewboxblrt |
| 12,45.13,77,33 | 1,2,3,4 |
Then result header contains
| attr | value |
| querystring | pub |
| polygon | false |
| viewbox | 12,45.13,77,33 |
Scenario Outline: Empty XML search with polygon values
When sending xml search query "xnznxvcx"
| param | value |
| polygon | <polyval> |
Then result header contains
| attr | value |
| polygon | <result> |
Examples:
| result | polyval |
| false | 0 |
| true | 1 |
| true | True |
| true | true |
| true | false |
| true | FALSE |
| true | yes |
| true | no |
| true | '; delete from foobar; select ' |
Scenario: Empty XML search with exluded place ids
When sending xml search query "jghrleoxsbwjer"
| exclude_place_ids |
| 123,76,342565 |
Then result header contains
| attr | value |
| exclude_place_ids | 123,76,342565 |
Scenario: Empty XML search with bad exluded place ids
When sending xml search query "jghrleoxsbwjer"
| exclude_place_ids |
| , |
Then result header has not attributes exclude_place_ids
Scenario Outline: Wrapping of legal jsonp search requests
When sending json search query "Tokyo"
| param | value |
|json_callback | <data> |
Then result header contains
| attr | value |
| json_func | <result> |
Examples:
| data | result |
| foo | foo |
| FOO | FOO |
| __world | __world |
| $me | \$me |
| m1[4] | m1\[4\] |
| d_r[$d] | d_r\[\$d\] |
Scenario Outline: Wrapping of illegal jsonp search requests
When sending json search query "Tokyo"
| param | value |
|json_callback | <data> |
Then a HTTP 400 is returned
Examples:
| data |
| 1asd |
| bar(foo) |
| XXX['bad'] |
| foo; evil |
Scenario: Ignore jsonp parameter for anything but json
When sending json search query "Malibu"
| json_callback |
| 234 |
Then a HTTP 400 is returned
When sending xml search query "Malibu"
| json_callback |
| 234 |
Then the result is valid xml
When sending html search query "Malibu"
| json_callback |
| 234 |
Then the result is valid html
Scenario: Empty JSON search
When sending json search query "YHlERzzx"
Then exactly 0 results are returned
Scenario: Empty JSONv2 search
When sending jsonv2 search query "Flubb XdfESSaZx"
Then exactly 0 results are returned
Scenario: Search for non-existing coordinates
When sending json search query "-21.0,-33.0"
Then exactly 0 results are returned

View File

@ -0,0 +1,38 @@
@APIDB
Feature: Structured search queries
Testing correctness of results with
structured queries
Scenario: Country only
When sending json search query "" with address
| country |
| Liechtenstein |
Then address of result 0 is
| type | value |
| country | Liechtenstein |
| country_code | li |
Scenario: Postcode only
When sending json search query "" with address
| postalcode |
| 22547 |
Then results contain
| type |
| postcode |
And result addresses contain
| postcode |
| 22547 |
Scenario: Street, postcode and country
When sending xml search query "" with address
| street | postalcode | country |
| Old Palace Road | GU2 7UP | United Kingdom |
Then result header contains
| attr | value |
| querystring | Old Palace Road, GU2 7UP, United Kingdom |
Scenario: gihub #176
When sending json search query "" with address
| city |
| Mercedes |
Then at least 1 result is returned

View File

@ -0,0 +1,303 @@
@DB
Feature: Import of address interpolations
Tests that interpolated addresses are added correctly
Scenario: Simple even interpolation line with two points
Given the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | 1 1 |
| N2 | place | house | 6 | 1 1.001 |
| W1 | place | houses | even | 1 1, 1 1.001 |
And the ways
| id | nodes |
| 1 | 1,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 2 | 6 | 1 1, 1 1.001 |
Scenario: Backwards even two point interpolation line
Given the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | 1 1 |
| N2 | place | house | 6 | 1 1.001 |
| W1 | place | houses | even | 1 1.001, 1 1 |
And the ways
| id | nodes |
| 1 | 2,1 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 2 | 6 | 1 1, 1 1.001 |
Scenario: Simple odd two point interpolation
Given the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 1 | 1 1 |
| N2 | place | house | 11 | 1 1.001 |
| W1 | place | houses | odd | 1 1, 1 1.001 |
And the ways
| id | nodes |
| 1 | 1,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 1 | 11 | 1 1, 1 1.001 |
Scenario: Simple all two point interpolation
Given the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 1 | 1 1 |
| N2 | place | house | 3 | 1 1.001 |
| W1 | place | houses | all | 1 1, 1 1.001 |
And the ways
| id | nodes |
| 1 | 1,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 1 | 3 | 1 1, 1 1.001 |
Scenario: Even two point interpolation line with intermediate empty node
Given the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | 1 1 |
| N2 | place | house | 10 | 1.001 1.001 |
| W1 | place | houses | even | 1 1, 1 1.001, 1.001 1.001 |
And the ways
| id | nodes |
| 1 | 1,3,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 2 | 10 | 1 1, 1 1.001, 1.001 1.001 |
Scenario: Even two point interpolation line with intermediate duplicated empty node
Given the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | 1 1 |
| N2 | place | house | 10 | 1.001 1.001 |
| W1 | place | houses | even | 1 1, 1 1.001, 1.001 1.001 |
And the ways
| id | nodes |
| 1 | 1,3,3,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 2 | 10 | 1 1, 1 1.001, 1.001 1.001 |
Scenario: Simple even three point interpolation line
Given the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | 1 1 |
| N2 | place | house | 14 | 1.001 1.001 |
| N3 | place | house | 10 | 1 1.001 |
| W1 | place | houses | even | 1 1, 1 1.001, 1.001 1.001 |
And the ways
| id | nodes |
| 1 | 1,3,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 2 | 10 | 1 1, 1 1.001 |
| 10 | 14 | 1 1.001, 1.001 1.001 |
Scenario: Simple even four point interpolation line
Given the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | 1 1 |
| N2 | place | house | 14 | 1.001 1.001 |
| N3 | place | house | 10 | 1 1.001 |
| N4 | place | house | 18 | 1.001 1.002 |
| W1 | place | houses | even | 1 1, 1 1.001, 1.001 1.001, 1.001 1.002 |
And the ways
| id | nodes |
| 1 | 1,3,2,4 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 2 | 10 | 1 1, 1 1.001 |
| 10 | 14 | 1 1.001, 1.001 1.001 |
| 14 | 18 | 1.001 1.001, 1.001 1.002 |
Scenario: Reverse simple even three point interpolation line
Given the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | 1 1 |
| N2 | place | house | 14 | 1.001 1.001 |
| N3 | place | house | 10 | 1 1.001 |
| W1 | place | houses | even | 1.001 1.001, 1 1.001, 1 1 |
And the ways
| id | nodes |
| 1 | 2,3,1 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 2 | 10 | 1 1, 1 1.001 |
| 10 | 14 | 1 1.001, 1.001 1.001 |
Scenario: Even three point interpolation line with odd center point
Given the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | 1 1 |
| N2 | place | house | 8 | 1.001 1.001 |
| N3 | place | house | 7 | 1 1.001 |
| W1 | place | houses | even | 1 1, 1 1.001, 1.001 1.001 |
And the ways
| id | nodes |
| 1 | 1,3,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 2 | 7 | 1 1, 1 1.001 |
| 7 | 8 | 1 1.001, 1.001 1.001 |
Scenario: Interpolation line with self-intersecting way
Given the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | 0 0 |
| N2 | place | house | 6 | 0 0.001 |
| N3 | place | house | 10 | 0 0.002 |
| W1 | place | houses | even | 0 0, 0 0.001, 0 0.002, 0 0.001 |
And the ways
| id | nodes |
| 1 | 1,2,3,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 2 | 6 | 0 0, 0 0.001 |
| 6 | 10 | 0 0.001, 0 0.002 |
| 6 | 10 | 0 0.001, 0 0.002 |
Scenario: Interpolation line with self-intersecting way II
Given the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | 0 0 |
| N2 | place | house | 6 | 0 0.001 |
| W1 | place | houses | even | 0 0, 0 0.001, 0 0.002, 0 0.001 |
And the ways
| id | nodes |
| 1 | 1,2,3,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 2 | 6 | 0 0, 0 0.001 |
Scenario: addr:street on interpolation way
Given the scene parallel-road
And the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | :n-middle-w |
| N2 | place | house | 6 | :n-middle-e |
| N3 | place | house | 12 | :n-middle-w |
| N4 | place | house | 16 | :n-middle-e |
And the places
| osm | class | type | housenr | street | geometry |
| W10 | place | houses | even | | :w-middle |
| W11 | place | houses | even | Cloud Street | :w-middle |
And the places
| osm | class | type | name | geometry |
| W2 | highway | tertiary | Sun Way | :w-north |
| W3 | highway | tertiary | Cloud Street | :w-south |
And the ways
| id | nodes |
| 10 | 1,100,101,102,2 |
| 11 | 3,200,201,202,4 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W2 |
| N2 | W2 |
| N3 | W3 |
| N4 | W3 |
Then W10 expands to interpolation
| parent_place_id | start | end |
| W2 | 2 | 6 |
Then W11 expands to interpolation
| parent_place_id | start | end |
| W3 | 12 | 16 |
When searching for "16 Cloud Street"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 4 |
When searching for "14 Cloud Street"
Then results contain
| ID | osm_type | osm_id |
| 0 | W | 11 |
When searching for "18 Cloud Street"
Then results contain
| ID | osm_type | osm_id |
| 0 | W | 3 |
Scenario: addr:street on housenumber way
Given the scene parallel-road
And the places
| osm | class | type | housenr | street | geometry |
| N1 | place | house | 2 | | :n-middle-w |
| N2 | place | house | 6 | | :n-middle-e |
| N3 | place | house | 12 | Cloud Street | :n-middle-w |
| N4 | place | house | 16 | Cloud Street | :n-middle-e |
And the places
| osm | class | type | housenr | geometry |
| W10 | place | houses | even | :w-middle |
| W11 | place | houses | even | :w-middle |
And the places
| osm | class | type | name | geometry |
| W2 | highway | tertiary | Sun Way | :w-north |
| W3 | highway | tertiary | Cloud Street | :w-south |
And the ways
| id | nodes |
| 10 | 1,100,101,102,2 |
| 11 | 3,200,201,202,4 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W2 |
| N2 | W2 |
| N3 | W3 |
| N4 | W3 |
Then W10 expands to interpolation
| parent_place_id | start | end |
| W2 | 2 | 6 |
Then W11 expands to interpolation
| parent_place_id | start | end |
| W3 | 12 | 16 |
When searching for "16 Cloud Street"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 4 |
When searching for "14 Cloud Street"
Then results contain
| ID | osm_type | osm_id |
| 0 | W | 11 |
Scenario: Geometry of points and way don't match (github #253)
Given the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 10 | 144.9632341 -37.76163 |
| N2 | place | house | 6 | 144.9630541 -37.7628174 |
| N3 | shop | supermarket | 2 | 144.9629794 -37.7630755 |
| W1 | place | houses | even | 144.9632341 -37.76163,144.9630541 -37.7628172,144.9629794 -37.7630755 |
And the ways
| id | nodes |
| 1 | 1,2,3 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 2 | 6 | 144.9629794 -37.7630755, 144.9630541 -37.7628174 |
| 6 | 10 | 144.9630541 -37.7628174, 144.9632341 -37.76163 |
Scenario: Place with missing address information
Given the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 23 | 0.0001 0.0001 |
| N2 | amenity | school | | 0.0001 0.0002 |
| N3 | place | house | 29 | 0.0001 0.0004 |
| W1 | place | houses | odd | 0.0001 0.0001,0.0001 0.0002,0.0001 0.0004 |
And the ways
| id | nodes |
| 1 | 1,2,3 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 23 | 29 | 0.0001 0.0001, 0.0001 0.0002, 0.0001 0.0004 |

View File

@ -0,0 +1,110 @@
@DB
Feature: Linking of places
Tests for correctly determining linked places
Scenario: Only address-describing places can be linked
Given the scene way-area-with-center
And the places
| osm | class | type | name | geometry |
| R13 | landuse | forest | Garbo | :area |
| N256 | natural | peak | Garbo | :inner-C |
When importing
Then placex contains
| object | linked_place_id |
| R13 | - |
| N256 | - |
Scenario: Waterways are linked when in waterway relations
Given the scene split-road
And the places
| osm | class | type | name | geometry |
| W1 | waterway | river | Rhein | :w-2 |
| W2 | waterway | river | Rhein | :w-3 |
| R13 | waterway | river | Rhein | :w-1 + :w-2 + :w-3 |
| R23 | waterway | river | Limmat| :w-4a |
And the relations
| id | members | tags+type |
| 13 | R23:tributary,W1,W2:main_stream | waterway |
When importing
Then placex contains
| object | linked_place_id |
| W1 | R13 |
| W2 | R13 |
| R13 | - |
| R23 | - |
When searching for "rhein"
Then results contain
| osm_type |
| R |
Scenario: Relations are not linked when in waterway relations
Given the scene split-road
And the places
| osm | class | type | name | geometry |
| W1 | waterway | river | Rhein | :w-2 |
| W2 | waterway | river | Rhein | :w-3 |
| R1 | waterway | river | Rhein | :w-1 + :w-2 + :w-3 |
| R2 | waterway | river | Limmat| :w-4a |
And the relations
| id | members | tags+type |
| 1 | R2 | waterway |
When importing
Then placex contains
| object | linked_place_id |
| W1 | - |
| W2 | - |
| R1 | - |
| R2 | - |
Scenario: Empty waterway relations are handled correctly
Given the scene split-road
And the places
| osm | class | type | name | geometry |
| R1 | waterway | river | Rhein | :w-1 + :w-2 + :w-3 |
And the relations
| id | members | tags+type |
| 1 | | waterway |
When importing
Then placex contains
| object | linked_place_id |
| R1 | - |
Scenario: Waterways are not linked when waterway types don't match
Given the scene split-road
And the places
| osm | class | type | name | geometry |
| W1 | waterway | drain | Rhein | :w-2 |
| R1 | waterway | river | Rhein | :w-1 + :w-2 + :w-3 |
And the relations
| id | members | tags+type |
| 1 | N23,N34,W1,R45 | multipolygon |
When importing
Then placex contains
| object | linked_place_id |
| W1 | - |
| R1 | - |
When searching for "rhein"
Then results contain
| ID | osm_type |
| 0 | R |
| 1 | W |
Scenario: Side streams are linked only when they have the same name
Given the scene split-road
And the places
| osm | class | type | name | geometry |
| W1 | waterway | river | Rhein2 | :w-2 |
| W2 | waterway | river | Rhein | :w-3 |
| R1 | waterway | river | Rhein | :w-1 + :w-2 + :w-3 |
And the relations
| id | members | tags+type |
| 1 | W1:side_stream,W2:side_stream | waterway |
When importing
Then placex contains
| object | linked_place_id |
| W1 | - |
| W2 | R1 |
When searching for "rhein2"
Then results contain
| osm_type |
| W |

View File

@ -0,0 +1,39 @@
@DB
Feature: Import and search of names
Tests all naming related import issues
Scenario: No copying name tag if only one name
Given the places
| osm | class | type | name | geometry |
| N1 | place | locality | german | country:de |
When importing
Then placex contains
| object | calculated_country_code | name+name |
| N1 | de | german |
Scenario: Copying name tag to default language if it does not exist
Given the places
| osm | class | type | name | name+name:fi | geometry |
| N1 | place | locality | german | finnish | country:de |
When importing
Then placex contains
| object | calculated_country_code | name | name+name:fi | name+name:de |
| N1 | de | german | finnish | german |
Scenario: Copying default language name tag to name if it does not exist
Given the places
| osm | class | type | name+name:de | name+name:fi | geometry |
| N1 | place | locality | german | finnish | country:de |
When importing
Then placex contains
| object | calculated_country_code | name | name+name:fi | name+name:de |
| N1 | de | german | finnish | german |
Scenario: Do not overwrite default language with name tag
Given the places
| osm | class | type | name | name+name:fi | name+name:de | geometry |
| N1 | place | locality | german | finnish | local | country:de |
When importing
Then placex contains
| object | calculated_country_code | name | name+name:fi | name+name:de |
| N1 | de | german | finnish | local |

View File

@ -0,0 +1,443 @@
@DB
Feature: Parenting of objects
Tests that the correct parent is choosen
Scenario: Address inherits postcode from its street unless it has a postcode
Given the scene roads-with-pois
And the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 4 | :p-N1 |
And the places
| osm | class | type | housenr | postcode | geometry |
| N2 | place | house | 5 | 99999 | :p-N1 |
And the places
| osm | class | type | name | postcode | geometry |
| W1 | highway | residential | galoo | 12345 | :w-north |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W1 |
| N2 | W1 |
When searching for "4 galoo"
Then results contain
| ID | osm_type | osm_id | langaddress
| 0 | N | 1 | 4, galoo, 12345
When searching for "5 galoo"
Then results contain
| ID | osm_type | osm_id | langaddress
| 0 | N | 2 | 5, galoo, 99999
Scenario: Address without tags, closest street
Given the scene roads-with-pois
And the places
| osm | class | type | geometry |
| N1 | place | house | :p-N1 |
| N2 | place | house | :p-N2 |
| N3 | place | house | :p-S1 |
| N4 | place | house | :p-S2 |
And the named places
| osm | class | type | geometry |
| W1 | highway | residential | :w-north |
| W2 | highway | residential | :w-south |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W1 |
| N2 | W1 |
| N3 | W2 |
| N4 | W2 |
Scenario: Address without tags avoids unnamed streets
Given the scene roads-with-pois
And the places
| osm | class | type | geometry |
| N1 | place | house | :p-N1 |
| N2 | place | house | :p-N2 |
| N3 | place | house | :p-S1 |
| N4 | place | house | :p-S2 |
| W1 | highway | residential | :w-north |
And the named places
| osm | class | type | geometry |
| W2 | highway | residential | :w-south |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W2 |
| N2 | W2 |
| N3 | W2 |
| N4 | W2 |
Scenario: addr:street tag parents to appropriately named street
Given the scene roads-with-pois
And the places
| osm | class | type | street| geometry |
| N1 | place | house | south | :p-N1 |
| N2 | place | house | north | :p-N2 |
| N3 | place | house | south | :p-S1 |
| N4 | place | house | north | :p-S2 |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | north | :w-north |
| W2 | highway | residential | south | :w-south |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W2 |
| N2 | W1 |
| N3 | W2 |
| N4 | W1 |
Scenario: addr:street tag parents to next named street
Given the scene roads-with-pois
And the places
| osm | class | type | street | geometry |
| N1 | place | house | abcdef | :p-N1 |
| N2 | place | house | abcdef | :p-N2 |
| N3 | place | house | abcdef | :p-S1 |
| N4 | place | house | abcdef | :p-S2 |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | abcdef | :w-north |
| W2 | highway | residential | abcdef | :w-south |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W1 |
| N2 | W1 |
| N3 | W2 |
| N4 | W2 |
Scenario: addr:street tag without appropriately named street
Given the scene roads-with-pois
And the places
| osm | class | type | street | geometry |
| N1 | place | house | abcdef | :p-N1 |
| N2 | place | house | abcdef | :p-N2 |
| N3 | place | house | abcdef | :p-S1 |
| N4 | place | house | abcdef | :p-S2 |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | abcde | :w-north |
| W2 | highway | residential | abcde | :w-south |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W1 |
| N2 | W1 |
| N3 | W2 |
| N4 | W2 |
Scenario: addr:place address
Given the scene road-with-alley
And the places
| osm | class | type | addr_place | geometry |
| N1 | place | house | myhamlet | :n-alley |
And the places
| osm | class | type | name | geometry |
| N2 | place | hamlet | myhamlet | :n-main-west |
| W1 | highway | residential | myhamlet | :w-main |
When importing
Then placex contains
| object | parent_place_id |
| N1 | N2 |
Scenario: addr:street is preferred over addr:place
Given the scene road-with-alley
And the places
| osm | class | type | addr_place | street | geometry |
| N1 | place | house | myhamlet | mystreet| :n-alley |
And the places
| osm | class | type | name | geometry |
| N2 | place | hamlet | myhamlet | :n-main-west |
| W1 | highway | residential | mystreet | :w-main |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W1 |
Scenario: Untagged address in simple associated street relation
Given the scene road-with-alley
And the places
| osm | class | type | geometry |
| N1 | place | house | :n-alley |
| N2 | place | house | :n-corner |
| N3 | place | house | :n-main-west |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | foo | :w-main |
| W2 | highway | service | bar | :w-alley |
And the relations
| id | members | tags+type |
| 1 | W1:street,N1,N2,N3 | associatedStreet |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W1 |
| N2 | W1 |
| N3 | W1 |
Scenario: Avoid unnamed streets in simple associated street relation
Given the scene road-with-alley
And the places
| osm | class | type | geometry |
| N1 | place | house | :n-alley |
| N2 | place | house | :n-corner |
| N3 | place | house | :n-main-west |
| W2 | highway | residential | :w-alley |
And the named places
| osm | class | type | geometry |
| W1 | highway | residential | :w-main |
And the relations
| id | members | tags+type |
| 1 | N1,N2,N3,W2:street,W1:street | associatedStreet |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W1 |
| N2 | W1 |
| N3 | W1 |
Scenario: Associated street relation overrides addr:street
Given the scene road-with-alley
And the places
| osm | class | type | street | geometry |
| N1 | place | house | bar | :n-alley |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | foo | :w-main |
| W2 | highway | residential | bar | :w-alley |
And the relations
| id | members | tags+type |
| 1 | W1:street,N1,N2,N3 | associatedStreet |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W1 |
Scenario: Building without tags, closest street from center point
Given the scene building-on-street-corner
And the named places
| osm | class | type | geometry |
| W1 | building | yes | :w-building |
| W2 | highway | primary | :w-WE |
| W3 | highway | residential | :w-NS |
When importing
Then placex contains
| object | parent_place_id |
| W1 | W3 |
Scenario: Building with addr:street tags
Given the scene building-on-street-corner
And the named places
| osm | class | type | street | geometry |
| W1 | building | yes | bar | :w-building |
And the places
| osm | class | type | name | geometry |
| W2 | highway | primary | bar | :w-WE |
| W3 | highway | residential | foo | :w-NS |
When importing
Then placex contains
| object | parent_place_id |
| W1 | W2 |
Scenario: Building with addr:place tags
Given the scene building-on-street-corner
And the places
| osm | class | type | name | geometry |
| N1 | place | village | bar | :n-outer |
| W2 | highway | primary | bar | :w-WE |
| W3 | highway | residential | foo | :w-NS |
And the named places
| osm | class | type | addr_place | geometry |
| W1 | building | yes | bar | :w-building |
When importing
Then placex contains
| object | parent_place_id |
| W1 | N1 |
Scenario: Building in associated street relation
Given the scene building-on-street-corner
And the named places
| osm | class | type | geometry |
| W1 | building | yes | :w-building |
And the places
| osm | class | type | name | geometry |
| W2 | highway | primary | bar | :w-WE |
| W3 | highway | residential | foo | :w-NS |
And the relations
| id | members | tags+type |
| 1 | W1:house,W2:street | associatedStreet |
When importing
Then placex contains
| object | parent_place_id |
| W1 | W2 |
Scenario: Building in associated street relation overrides addr:street
Given the scene building-on-street-corner
And the named places
| osm | class | type | street | geometry |
| W1 | building | yes | foo | :w-building |
And the places
| osm | class | type | name | geometry |
| W2 | highway | primary | bar | :w-WE |
| W3 | highway | residential | foo | :w-NS |
And the relations
| id | members | tags+type |
| 1 | W1:house,W2:street | associatedStreet |
When importing
Then placex contains
| object | parent_place_id |
| W1 | W2 |
Scenario: Wrong member in associated street relation is ignored
Given the scene building-on-street-corner
And the named places
| osm | class | type | geometry |
| N1 | place | house | :n-outer |
And the named places
| osm | class | type | street | geometry |
| W1 | building | yes | foo | :w-building |
And the places
| osm | class | type | name | geometry |
| W2 | highway | primary | bar | :w-WE |
| W3 | highway | residential | foo | :w-NS |
And the relations
| id | members | tags+type |
| 1 | N1:house,W1:street,W3:street | associatedStreet |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W3 |
Scenario: POIs in building inherit address
Given the scene building-on-street-corner
And the named places
| osm | class | type | geometry |
| N1 | amenity | bank | :n-inner |
| N2 | shop | bakery | :n-edge-NS |
| N3 | shop | supermarket| :n-edge-WE |
And the places
| osm | class | type | street | addr_place | housenr | geometry |
| W1 | building | yes | foo | nowhere | 3 | :w-building |
And the places
| osm | class | type | name | geometry |
| W2 | highway | primary | bar | :w-WE |
| W3 | highway | residential | foo | :w-NS |
When importing
Then placex contains
| object | parent_place_id | street | addr_place | housenumber |
| W1 | W3 | foo | nowhere | 3 |
| N1 | W3 | foo | nowhere | 3 |
| N2 | W3 | foo | nowhere | 3 |
| N3 | W3 | foo | nowhere | 3 |
Scenario: POIs don't inherit from streets
Given the scene building-on-street-corner
And the named places
| osm | class | type | geometry |
| N1 | amenity | bank | :n-inner |
And the places
| osm | class | type | street | addr_place | housenr | geometry |
| W1 | highway | path | foo | nowhere | 3 | :w-building |
And the places
| osm | class | type | name | geometry |
| W3 | highway | residential | foo | :w-NS |
When importing
Then placex contains
| object | parent_place_id | street | addr_place | housenumber |
| N1 | W3 | None | None | None |
Scenario: POIs with own address do not inherit building address
Given the scene building-on-street-corner
And the named places
| osm | class | type | street | geometry |
| N1 | amenity | bank | bar | :n-inner |
And the named places
| osm | class | type | housenr | geometry |
| N2 | shop | bakery | 4 | :n-edge-NS |
And the named places
| osm | class | type | addr_place | geometry |
| N3 | shop | supermarket| nowhere | :n-edge-WE |
And the places
| osm | class | type | name | geometry |
| N4 | place | isolated_dwelling | theplace | :n-outer |
And the places
| osm | class | type | addr_place | housenr | geometry |
| W1 | building | yes | theplace | 3 | :w-building |
And the places
| osm | class | type | name | geometry |
| W2 | highway | primary | bar | :w-WE |
| W3 | highway | residential | foo | :w-NS |
When importing
Then placex contains
| object | parent_place_id | street | addr_place | housenumber |
| W1 | N4 | None | theplace | 3 |
| N1 | W2 | bar | None | None |
| N2 | W3 | None | None | 4 |
| N3 | W2 | None | nowhere | None |
Scenario: POIs parent a road if they are attached to it
Given the scene points-on-roads
And the named places
| osm | class | type | street | geometry |
| N1 | highway | bus_stop | North St | :n-SE |
| N2 | highway | bus_stop | South St | :n-NW |
| N3 | highway | bus_stop | North St | :n-S-unglued |
| N4 | highway | bus_stop | South St | :n-N-unglued |
And the places
| osm | class | type | name | geometry |
| W1 | highway | secondary | North St | :w-north |
| W2 | highway | unclassified | South St | :w-south |
And the ways
| id | nodes |
| 1 | 100,101,2,103,104 |
| 2 | 200,201,1,202,203 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W1 |
| N2 | W2 |
| N3 | W1 |
| N4 | W2 |
Scenario: POIs do not parent non-roads they are attached to
Given the scene points-on-roads
And the named places
| osm | class | type | street | geometry |
| N1 | highway | bus_stop | North St | :n-SE |
| N2 | highway | bus_stop | South St | :n-NW |
And the places
| osm | class | type | name | geometry |
| W1 | landuse | residential | North St | :w-north |
| W2 | waterway| river | South St | :w-south |
And the ways
| id | nodes |
| 1 | 100,101,2,103,104 |
| 2 | 200,201,1,202,203 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | 0 |
| N2 | 0 |
Scenario: POIs on building outlines inherit associated street relation
Given the scene building-on-street-corner
And the named places
| osm | class | type | geometry |
| N1 | place | house | :n-edge-NS |
| W1 | building | yes | :w-building |
And the places
| osm | class | type | name | geometry |
| W2 | highway | primary | bar | :w-WE |
| W3 | highway | residential | foo | :w-NS |
And the relations
| id | members | tags+type |
| 1 | W1:house,W2:street | associatedStreet |
And the ways
| id | nodes |
| 1 | 100,1,101,102,100 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W2 |

View File

@ -0,0 +1,313 @@
@DB
Feature: Import into placex
Tests that data in placex is completed correctly.
Scenario: No country code tag is available
Given the named places
| osm | class | type | geometry |
| N1 | highway | primary | country:us |
When importing
Then placex contains
| object | country_code | calculated_country_code |
| N1 | None | us |
Scenario: Location overwrites country code tag
Given the named places
| osm | class | type | country | geometry |
| N1 | highway | primary | de | country:us |
When importing
Then placex contains
| object | country_code | calculated_country_code |
| N1 | de | us |
Scenario: Country code tag overwrites location for countries
Given the named places
| osm | class | type | admin | country | geometry |
| R1 | boundary | administrative | 2 | de | (-100 40, -101 40, -101 41, -100 41, -100 40) |
When importing
Then placex contains
| object | country_code | calculated_country_code |
| R1 | de | de |
Scenario: Illegal country code tag for countries is ignored
Given the named places
| osm | class | type | admin | country | geometry |
| R1 | boundary | administrative | 2 | xx | (-100 40, -101 40, -101 41, -100 41, -100 40) |
When importing
Then placex contains
| object | country_code | calculated_country_code |
| R1 | xx | us |
Scenario: admin level is copied over
Given the named places
| osm | class | type | admin |
| N1 | place | state | 3 |
When importing
Then placex contains
| object | admin_level |
| N1 | 3 |
Scenario: admin level is default 15
Given the named places
| osm | class | type |
| N1 | amenity | prison |
When importing
Then placex contains
| object | admin_level |
| N1 | 15 |
Scenario: admin level is never larger than 15
Given the named places
| osm | class | type | admin |
| N1 | amenity | prison | 16 |
When importing
Then placex contains
| object | admin_level |
| N1 | 15 |
Scenario: postcode node without postcode is dropped
Given the places
| osm | class | type | name+ref |
| N1 | place | postcode | 12334 |
When importing
Then placex has no entry for N1
Scenario: postcode boundary without postcode is dropped
Given the places
| osm | class | type | name+ref | geometry |
| R1 | boundary | postal_code | 554476 | poly-area:0.1 |
When importing
Then placex has no entry for R1
Scenario: search and address ranks for GB post codes correctly assigned
Given the places
| osm | class | type | postcode | geometry |
| N1 | place | postcode | E45 2CD | country:gb |
| N2 | place | postcode | E45 2 | country:gb |
| N3 | place | postcode | Y45 | country:gb |
When importing
Then placex contains
| object | postcode | calculated_country_code | rank_search | rank_address |
| N1 | E45 2CD | gb | 25 | 5 |
| N2 | E45 2 | gb | 23 | 5 |
| N3 | Y45 | gb | 21 | 5 |
Scenario: wrongly formatted GB postcodes are down-ranked
Given the places
| osm | class | type | postcode | geometry |
| N1 | place | postcode | EA452CD | country:gb |
| N2 | place | postcode | E45 23 | country:gb |
| N3 | place | postcode | y45 | country:gb |
When importing
Then placex contains
| object | calculated_country_code | rank_search | rank_address |
| N1 | gb | 30 | 30 |
| N2 | gb | 30 | 30 |
| N3 | gb | 30 | 30 |
Scenario: search and address rank for DE postcodes correctly assigned
Given the places
| osm | class | type | postcode | geometry |
| N1 | place | postcode | 56427 | country:de |
| N2 | place | postcode | 5642 | country:de |
| N3 | place | postcode | 5642A | country:de |
| N4 | place | postcode | 564276 | country:de |
When importing
Then placex contains
| object | calculated_country_code | rank_search | rank_address |
| N1 | de | 21 | 11 |
| N2 | de | 30 | 30 |
| N3 | de | 30 | 30 |
| N4 | de | 30 | 30 |
Scenario: search and address rank for other postcodes are correctly assigned
Given the places
| osm | class | type | postcode | geometry |
| N1 | place | postcode | 1 | country:ca |
| N2 | place | postcode | X3 | country:ca |
| N3 | place | postcode | 543 | country:ca |
| N4 | place | postcode | 54dc | country:ca |
| N5 | place | postcode | 12345 | country:ca |
| N6 | place | postcode | 55TT667 | country:ca |
| N7 | place | postcode | 123-65 | country:ca |
| N8 | place | postcode | 12 445 4 | country:ca |
| N9 | place | postcode | A1:bc10 | country:ca |
When importing
Then placex contains
| object | calculated_country_code | rank_search | rank_address |
| N1 | ca | 21 | 11 |
| N2 | ca | 21 | 11 |
| N3 | ca | 21 | 11 |
| N4 | ca | 21 | 11 |
| N5 | ca | 21 | 11 |
| N6 | ca | 21 | 11 |
| N7 | ca | 25 | 11 |
| N8 | ca | 25 | 11 |
| N9 | ca | 25 | 11 |
Scenario: search and address ranks for places are correctly assigned
Given the named places
| osm | class | type |
| N1 | foo | bar |
| N11 | place | Continent |
| N12 | place | continent |
| N13 | place | sea |
| N14 | place | country |
| N15 | place | state |
| N16 | place | region |
| N17 | place | county |
| N18 | place | city |
| N19 | place | island |
| N20 | place | town |
| N21 | place | village |
| N22 | place | hamlet |
| N23 | place | municipality |
| N24 | place | district |
| N25 | place | unincorporated_area |
| N26 | place | borough |
| N27 | place | suburb |
| N28 | place | croft |
| N29 | place | subdivision |
| N30 | place | isolated_dwelling |
| N31 | place | farm |
| N32 | place | locality |
| N33 | place | islet |
| N34 | place | mountain_pass |
| N35 | place | neighbourhood |
| N36 | place | house |
| N37 | place | building |
| N38 | place | houses |
And the named places
| osm | class | type | extra+locality |
| N100 | place | locality | townland |
And the named places
| osm | class | type | extra+capital |
| N101 | place | city | yes |
When importing
Then placex contains
| object | rank_search | rank_address |
| N1 | 30 | 30 |
| N11 | 30 | 30 |
| N12 | 2 | 2 |
| N13 | 2 | 0 |
| N14 | 4 | 4 |
| N15 | 8 | 8 |
| N16 | 18 | 0 |
| N17 | 12 | 12 |
| N18 | 16 | 16 |
| N19 | 17 | 0 |
| N20 | 18 | 16 |
| N21 | 19 | 16 |
| N22 | 19 | 16 |
| N23 | 19 | 16 |
| N24 | 19 | 16 |
| N25 | 19 | 16 |
| N26 | 19 | 16 |
| N27 | 20 | 20 |
| N28 | 20 | 20 |
| N29 | 20 | 20 |
| N30 | 20 | 20 |
| N31 | 20 | 0 |
| N32 | 20 | 0 |
| N33 | 20 | 0 |
| N34 | 20 | 0 |
| N100 | 20 | 20 |
| N101 | 15 | 16 |
| N35 | 22 | 22 |
| N36 | 30 | 30 |
| N37 | 30 | 30 |
| N38 | 28 | 0 |
Scenario: search and address ranks for boundaries are correctly assigned
Given the named places
| osm | class | type |
| N1 | boundary | administrative |
And the named places
| osm | class | type | geometry |
| W10 | boundary | administrative | 10 10, 11 11 |
And the named places
| osm | class | type | admin | geometry |
| R20 | boundary | administrative | 2 | (1 1, 2 2, 1 2, 1 1) |
| R21 | boundary | administrative | 32 | (3 3, 4 4, 3 4, 3 3) |
| R22 | boundary | nature_park | 6 | (0 0, 1 0, 0 1, 0 0) |
| R23 | boundary | natural_reserve| 10 | (0 0, 1 1, 1 0, 0 0) |
When importing
Then placex has no entry for N1
And placex has no entry for W10
And placex contains
| object | rank_search | rank_address |
| R20 | 4 | 4 |
| R21 | 30 | 30 |
| R22 | 12 | 0 |
| R23 | 20 | 0 |
Scenario: search and address ranks for highways correctly assigned
Given the scene roads-with-pois
And the places
| osm | class | type |
| N1 | highway | bus_stop |
And the places
| osm | class | type | geometry |
| W1 | highway | primary | :w-south |
| W2 | highway | secondary | :w-south |
| W3 | highway | tertiary | :w-south |
| W4 | highway | residential | :w-north |
| W5 | highway | unclassified | :w-north |
| W6 | highway | something | :w-north |
When importing
Then placex contains
| object | rank_search | rank_address |
| N1 | 30 | 30 |
| W1 | 26 | 26 |
| W2 | 26 | 26 |
| W3 | 26 | 26 |
| W4 | 26 | 26 |
| W5 | 26 | 26 |
| W6 | 26 | 26 |
Scenario: rank and inclusion of landuses
Given the named places
| osm | class | type |
| N2 | landuse | residential |
And the named places
| osm | class | type | geometry |
| W2 | landuse | residential | 1 1, 1 1.1 |
| W4 | landuse | residential | poly-area:0.1 |
| R2 | landuse | residential | poly-area:0.05 |
| R3 | landuse | forrest | poly-area:0.5 |
When importing
Then placex contains
| object | rank_search | rank_address |
| N2 | 30 | 30 |
| W2 | 30 | 30 |
| W4 | 22 | 22 |
| R2 | 22 | 22 |
| R3 | 22 | 0 |
Scenario: rank and inclusion of naturals
Given the named places
| osm | class | type |
| N2 | natural | peak |
| N4 | natural | volcano |
| N5 | natural | foobar |
And the named places
| osm | class | type | geometry |
| W2 | natural | mountain_range | 12 12,11 11 |
| W3 | natural | foobar | 13 13,13.1 13 |
| R3 | natural | volcano | poly-area:0.1 |
| R4 | natural | foobar | poly-area:0.5 |
| R5 | natural | sea | poly-area:5.0 |
| R6 | natural | sea | poly-area:0.01 |
When importing
Then placex contains
| object | rank_search | rank_address |
| N2 | 18 | 0 |
| N4 | 18 | 0 |
| N5 | 30 | 30 |
| W2 | 18 | 0 |
| R3 | 18 | 0 |
| R4 | 22 | 0 |
| R5 | 4 | 4 |
| R6 | 4 | 4 |
| W3 | 30 | 30 |

View File

@ -0,0 +1,39 @@
@DB
Feature: Creation of search terms
Tests that search_name table is filled correctly
Scenario: POIs without a name have no search entry
Given the scene roads-with-pois
And the places
| osm | class | type | geometry |
| N1 | place | house | :p-N1 |
And the named places
| osm | class | type | geometry |
| W1 | highway | residential | :w-north |
When importing
Then search_name has no entry for N1
Scenario: Named POIs inherit address from parent
Given the scene roads-with-pois
And the places
| osm | class | type | name | geometry |
| N1 | place | house | foo | :p-N1 |
| W1 | highway | residential | the road | :w-north |
When importing
Then search_name contains
| object | name_vector | nameaddress_vector |
| N1 | foo | the road |
Scenario: Roads take over the postcode from attached houses
Given the scene roads-with-pois
And the places
| osm | class | type | housenr | postcode | street | geometry |
| N1 | place | house | 1 | 12345 | North St | :p-S1 |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | North St | :w-north |
When importing
Then search_name contains
| object | nameaddress_vector |
| W1 | 12345 |

View File

@ -0,0 +1,139 @@
@DB
Feature: Import and search of names
Tests all naming related issues: normalisation,
abbreviations, internationalisation, etc.
Scenario: Case-insensitivity of search
Given the places
| osm | class | type | name |
| N1 | place | locality | FooBar |
When importing
Then placex contains
| object | class | type | name+name |
| N1 | place | locality | FooBar |
When searching for "FooBar"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 1 |
When searching for "foobar"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 1 |
When searching for "fOObar"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 1 |
When searching for "FOOBAR"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 1 |
Scenario: Multiple spaces in name
Given the places
| osm | class | type | name |
| N1 | place | locality | one two three |
When importing
When searching for "one two three"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 1 |
When searching for "one two three"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 1 |
When searching for "one two three"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 1 |
When searching for " one two three"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 1 |
Scenario: Special characters in name
Given the places
| osm | class | type | name |
| N1 | place | locality | Jim-Knopf-Str |
| N2 | place | locality | Smith/Weston |
| N3 | place | locality | space mountain |
| N4 | place | locality | space |
| N5 | place | locality | mountain |
When importing
When searching for "Jim-Knopf-Str"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 1 |
When searching for "Jim Knopf-Str"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 1 |
When searching for "Jim Knopf Str"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 1 |
When searching for "Jim/Knopf-Str"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 1 |
When searching for "Jim-Knopfstr"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 1 |
When searching for "Smith/Weston"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 2 |
When searching for "Smith Weston"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 2 |
When searching for "Smith-Weston"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 2 |
When searching for "space mountain"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 3 |
When searching for "space-mountain"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 3 |
When searching for "space/mountain"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 3 |
When searching for "space\mountain"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 3 |
When searching for "space(mountain)"
Then results contain
| ID | osm_type | osm_id |
| 0 | N | 3 |
Scenario: Landuse with name are found
Given the places
| osm | class | type | name | geometry |
| R1 | natural | meadow | landuse1 | (0 0, 1 0, 1 1, 0 1, 0 0) |
| R2 | landuse | industrial | landuse2 | (0 0, -1 0, -1 -1, 0 -1, 0 0) |
When importing
When searching for "landuse1"
Then results contain
| ID | osm_type | osm_id |
| 0 | R | 1 |
When searching for "landuse2"
Then results contain
| ID | osm_type | osm_id |
| 0 | R | 2 |
@wip
Scenario: Postcode boundaries without ref
Given the places
| osm | class | type | postcode | geometry |
| R1 | boundary | postal_code | 12345 | (0 0, 1 0, 1 1, 0 1, 0 0) |
When importing
When searching for "12345"
Then results contain
| ID | osm_type | osm_id |
| 0 | R | 1 |

View File

@ -0,0 +1,32 @@
@DB
Feature: Searching of simple objects
Testing simple stuff
Scenario: Search for place node
Given the places
| osm | class | type | name+name | geometry |
| N1 | place | village | Foo | 10.0 -10.0 |
When importing
And searching for "Foo"
Then results contain
| ID | osm | class | type | centroid |
| 0 | N1 | place | village | 10 -10 |
Scenario: Updating postcode in postcode boundaries without ref
Given the places
| osm | class | type | postcode | geometry |
| R1 | boundary | postal_code | 12345 | poly-area:1.0 |
When importing
And searching for "12345"
Then results contain
| ID | osm_type | osm_id |
| 0 | R | 1 |
When updating places
| osm | class | type | postcode | geometry |
| R1 | boundary | postal_code | 54321 | poly-area:1.0 |
And searching for "12345"
Then exactly 0 results are returned
When searching for "54321"
Then results contain
| ID | osm_type | osm_id |
| 0 | R | 1 |

View File

@ -0,0 +1,269 @@
@DB
Feature: Update of address interpolations
Test the interpolated address are updated correctly
@wip
Scenario: new interpolation added to existing street
Given the scene parallel-road
And the places
| osm | class | type | name | geometry |
| W2 | highway | unclassified | Sun Way | :w-north |
| W3 | highway | unclassified | Cloud Street | :w-south |
And the ways
| id | nodes |
| 10 | 1,100,101,102,2 |
When importing
Then W10 expands to no interpolation
When updating places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | :n-middle-w |
| N2 | place | house | 6 | :n-middle-e |
| W10 | place | houses | even | :w-middle |
Then placex contains
| object | parent_place_id |
| N1 | W2 |
| N2 | W2 |
And W10 expands to interpolation
| parent_place_id | start | end |
| W2 | 2 | 6 |
Scenario: addr:street added to interpolation
Given the scene parallel-road
And the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | :n-middle-w |
| N2 | place | house | 6 | :n-middle-e |
| W10 | place | houses | even | :w-middle |
And the places
| osm | class | type | name | geometry |
| W2 | highway | unclassified | Sun Way | :w-north |
| W3 | highway | unclassified | Cloud Street | :w-south |
And the ways
| id | nodes |
| 10 | 1,100,101,102,2 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W2 |
| N2 | W2 |
And W10 expands to interpolation
| parent_place_id | start | end |
| W2 | 2 | 6 |
When updating places
| osm | class | type | housenr | street | geometry |
| W10 | place | houses | even | Cloud Street | :w-middle |
Then placex contains
| object | parent_place_id |
| N1 | W3 |
| N2 | W3 |
And W10 expands to interpolation
| parent_place_id | start | end |
| W3 | 2 | 6 |
Scenario: addr:street added to housenumbers
Given the scene parallel-road
And the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | :n-middle-w |
| N2 | place | house | 6 | :n-middle-e |
| W10 | place | houses | even | :w-middle |
And the places
| osm | class | type | name | geometry |
| W2 | highway | unclassified | Sun Way | :w-north |
| W3 | highway | unclassified | Cloud Street | :w-south |
And the ways
| id | nodes |
| 10 | 1,100,101,102,2 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W2 |
| N2 | W2 |
And W10 expands to interpolation
| parent_place_id | start | end |
| W2 | 2 | 6 |
When updating places
| osm | class | type | street | housenr | geometry |
| N1 | place | house | Cloud Street| 2 | :n-middle-w |
| N2 | place | house | Cloud Street| 6 | :n-middle-e |
Then placex contains
| object | parent_place_id |
| N1 | W3 |
| N2 | W3 |
And W10 expands to interpolation
| parent_place_id | start | end |
| W3 | 2 | 6 |
Scenario: interpolation tag removed
Given the scene parallel-road
And the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | :n-middle-w |
| N2 | place | house | 6 | :n-middle-e |
| W10 | place | houses | even | :w-middle |
And the places
| osm | class | type | name | geometry |
| W2 | highway | unclassified | Sun Way | :w-north |
| W3 | highway | unclassified | Cloud Street | :w-south |
And the ways
| id | nodes |
| 10 | 1,100,101,102,2 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W2 |
| N2 | W2 |
And W10 expands to interpolation
| parent_place_id | start | end |
| W2 | 2 | 6 |
When marking for delete W10
Then W10 expands to no interpolation
And placex contains
| object | parent_place_id |
| N1 | W2 |
| N2 | W2 |
Scenario: referenced road added
Given the scene parallel-road
And the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | :n-middle-w |
| N2 | place | house | 6 | :n-middle-e |
And the places
| osm | class | type | housenr | street | geometry |
| W10 | place | houses | even | Cloud Street| :w-middle |
And the places
| osm | class | type | name | geometry |
| W2 | highway | unclassified | Sun Way | :w-north |
And the ways
| id | nodes |
| 10 | 1,100,101,102,2 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W2 |
| N2 | W2 |
And W10 expands to interpolation
| parent_place_id | start | end |
| W2 | 2 | 6 |
When updating places
| osm | class | type | name | geometry |
| W3 | highway | unclassified | Cloud Street | :w-south |
Then placex contains
| object | parent_place_id |
| N1 | W3 |
| N2 | W3 |
And W10 expands to interpolation
| parent_place_id | start | end |
| W3 | 2 | 6 |
Scenario: referenced road deleted
Given the scene parallel-road
And the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | :n-middle-w |
| N2 | place | house | 6 | :n-middle-e |
And the places
| osm | class | type | housenr | street | geometry |
| W10 | place | houses | even | Cloud Street| :w-middle |
And the places
| osm | class | type | name | geometry |
| W2 | highway | unclassified | Sun Way | :w-north |
| W3 | highway | unclassified | Cloud Street | :w-south |
And the ways
| id | nodes |
| 10 | 1,100,101,102,2 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W3 |
| N2 | W3 |
And W10 expands to interpolation
| parent_place_id | start | end |
| W3 | 2 | 6 |
When marking for delete W3
Then placex contains
| object | parent_place_id |
| N1 | W2 |
| N2 | W2 |
And W10 expands to interpolation
| parent_place_id | start | end |
| W2 | 2 | 6 |
Scenario: building becomes interpolation
Given the scene building-with-parallel-streets
And the places
| osm | class | type | housenr | geometry |
| W1 | place | house | 3 | :w-building |
And the places
| osm | class | type | name | geometry |
| W2 | highway | unclassified | Cloud Street | :w-south |
When importing
Then placex contains
| object | parent_place_id |
| W1 | W2 |
Given the ways
| id | nodes |
| 1 | 1,100,101,102,2 |
When updating places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | :n-north-w |
| N2 | place | house | 6 | :n-north-e |
And updating places
| osm | class | type | housenr | street | geometry |
| W1 | place | houses | even | Cloud Street| :w-north |
Then placex has no entry for W1
And W1 expands to interpolation
| parent_place_id | start | end |
| W2 | 2 | 6 |
Scenario: interpolation becomes building
Given the scene building-with-parallel-streets
And the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | :n-north-w |
| N2 | place | house | 6 | :n-north-e |
And the places
| osm | class | type | name | geometry |
| W2 | highway | unclassified | Cloud Street | :w-south |
And the ways
| id | nodes |
| 1 | 1,100,101,102,2 |
And the places
| osm | class | type | housenr | street | geometry |
| W1 | place | houses | even | Cloud Street| :w-north |
When importing
Then placex has no entry for W1
And W1 expands to interpolation
| parent_place_id | start | end |
| W2 | 2 | 6 |
When updating places
| osm | class | type | housenr | geometry |
| W1 | place | house | 3 | :w-building |
Then placex contains
| object | parent_place_id |
| W1 | W2 |
Scenario: housenumbers added to interpolation
Given the scene building-with-parallel-streets
And the places
| osm | class | type | name | geometry |
| W2 | highway | unclassified | Cloud Street | :w-south |
And the ways
| id | nodes |
| 1 | 1,100,101,102,2 |
And the places
| osm | class | type | housenr | geometry |
| W1 | place | houses | even | :w-north |
When importing
Then W1 expands to no interpolation
When updating places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | :n-north-w |
| N2 | place | house | 6 | :n-north-e |
And updating places
| osm | class | type | housenr | street | geometry |
| W1 | place | houses | even | Cloud Street| :w-north |
Then W1 expands to interpolation
| parent_place_id | start | end |
| W2 | 2 | 6 |

View File

@ -0,0 +1,91 @@
@DB
Feature: Updates of linked places
Tests that linked places are correctly added and deleted.
Scenario: Add linked place when linking relation is renamed
Given the places
| osm | class | type | name | geometry |
| N1 | place | city | foo | 0 0 |
And the places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | foo | 8 | poly-area:0.1 |
When importing
And searching for "foo" with dups
Then results contain
| osm_type |
| R |
When updating places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | foobar | 8 | poly-area:0.1 |
Then placex contains
| object | linked_place_id |
| N1 | - |
When searching for "foo" with dups
Then results contain
| osm_type |
| N |
Scenario: Add linked place when linking relation is removed
Given the places
| osm | class | type | name | geometry |
| N1 | place | city | foo | 0 0 |
And the places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | foo | 8 | poly-area:0.1 |
When importing
And searching for "foo" with dups
Then results contain
| osm_type |
| R |
When marking for delete R1
Then placex contains
| object | linked_place_id |
| N1 | - |
When searching for "foo" with dups
Then results contain
| osm_type |
| N |
Scenario: Remove linked place when linking relation is added
Given the places
| osm | class | type | name | geometry |
| N1 | place | city | foo | 0 0 |
When importing
And searching for "foo" with dups
Then results contain
| osm_type |
| N |
When updating places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | foo | 8 | poly-area:0.1 |
Then placex contains
| object | linked_place_id |
| N1 | R1 |
When searching for "foo" with dups
Then results contain
| osm_type |
| R |
Scenario: Remove linked place when linking relation is renamed
Given the places
| osm | class | type | name | geometry |
| N1 | place | city | foo | 0 0 |
And the places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | foobar | 8 | poly-area:0.1 |
When importing
And searching for "foo" with dups
Then results contain
| osm_type |
| N |
When updating places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | foo | 8 | poly-area:0.1 |
Then placex contains
| object | linked_place_id |
| N1 | R1 |
When searching for "foo" with dups
Then results contain
| osm_type |
| R |

View File

@ -0,0 +1,18 @@
@DB
Feature: Update of names in place objects
Test all naming related issues in updates
Scenario: Delete postcode from postcode boundaries without ref
Given the places
| osm | class | type | postcode | geometry |
| R1 | boundary | postal_code | 12345 | poly-area:0.5 |
When importing
And searching for "12345"
Then results contain
| ID | osm_type | osm_id |
| 0 | R | 1 |
When updating places
| osm | class | type | geometry |
| R1 | boundary | postal_code | poly-area:0.5 |
Then placex has no entry for R1

View File

@ -0,0 +1,57 @@
@DB
Feature: Update of POI-inherited poscode
Test updates of postcodes on street which was inherited from a related POI
Background: Street and house with postcode
Given the scene roads-with-pois
And the places
| osm | class | type | housenr | postcode | street | geometry |
| N1 | place | house | 1 | 12345 | North St |:p-S1 |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | North St | :w-north |
When importing
Then search_name contains
| object | nameaddress_vector |
| W1 | 12345 |
Scenario: POI-inherited postcode remains when way type is changed
When updating places
| osm | class | type | name | geometry |
| W1 | highway | unclassified | North St | :w-north |
Then search_name contains
| object | nameaddress_vector |
| W1 | 12345 |
Scenario: POI-inherited postcode remains when way name is changed
When updating places
| osm | class | type | name | geometry |
| W1 | highway | unclassified | South St | :w-north |
Then search_name contains
| object | nameaddress_vector |
| W1 | 12345 |
Scenario: POI-inherited postcode remains when way geometry is changed
When updating places
| osm | class | type | name | geometry |
| W1 | highway | unclassified | South St | :w-south |
Then search_name contains
| object | nameaddress_vector |
| W1 | 12345 |
Scenario: POI-inherited postcode is added when POI postcode changes
When updating places
| osm | class | type | housenr | postcode | street | geometry |
| N1 | place | house | 1 | 54321 | North St |:p-S1 |
Then search_name contains
| object | nameaddress_vector |
| W1 | 54321 |
Scenario: POI-inherited postcode remains when POI geometry changes
When updating places
| osm | class | type | housenr | postcode | street | geometry |
| N1 | place | house | 1 | 12345 | North St |:p-S2 |
Then search_name contains
| object | nameaddress_vector |
| W1 | 12345 |

View File

@ -0,0 +1,21 @@
@DB
Feature: Update of search terms
Tests that search_name table is updated correctly
Scenario: POI-inherited postcode remains when another POI is deleted
Given the scene roads-with-pois
And the places
| osm | class | type | housenr | postcode | street | geometry |
| N1 | place | house | 1 | 12345 | North St |:p-S1 |
| N2 | place | house | 2 | | North St |:p-S2 |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | North St | :w-north |
When importing
Then search_name contains
| object | nameaddress_vector |
| W1 | 12345 |
When marking for delete N2
Then search_name contains
| object | nameaddress_vector |
| W1 | 12345 |

View File

@ -0,0 +1,71 @@
@DB
Feature: Update of simple objects
Testing simple updating functionality
Scenario: Do delete small boundary features
Given the places
| osm | class | type | admin | geometry |
| R1 | boundary | administrative | 3 | poly-area:1.0 |
When importing
Then placex contains
| object | rank_search |
| R1 | 6 |
When marking for delete R1
Then placex has no entry for R1
Scenario: Do not delete large boundary features
Given the places
| osm | class | type | admin | geometry |
| R1 | boundary | administrative | 3 | poly-area:5.0 |
When importing
Then placex contains
| object | rank_search |
| R1 | 6 |
When marking for delete R1
Then placex contains
| object | rank_search |
| R1 | 6 |
Scenario: Do delete large features of low rank
Given the named places
| osm | class | type | geometry |
| W1 | place | house | poly-area:5.0 |
| R1 | boundary | national_park | poly-area:5.0 |
When importing
Then placex contains
| object | rank_address |
| R1 | 0 |
| W1 | 30 |
When marking for delete R1,W1
Then placex has no entry for W1
Then placex has no entry for R1
Scenario: type mutation
Given the places
| osm | class | type | geometry |
| N3 | shop | toys | 1 -1 |
When importing
Then placex contains
| object | class | type | centroid |
| N3 | shop | toys | 1 -1 |
When updating places
| osm | class | type | geometry |
| N3 | shop | grocery | 1 -1 |
Then placex contains
| object | class | type | centroid |
| N3 | shop | grocery | 1 -1 |
Scenario: remove postcode place when house number is added
Given the places
| osm | class | type | postcode | geometry |
| N3 | place | postcode | 12345 | 1 -1 |
When importing
Then placex contains
| object | class | type |
| N3 | place | postcode |
When updating places
| osm | class | type | postcode | housenr | geometry |
| N3 | place | house | 12345 | 13 | 1 -1 |
Then placex contains
| object | class | type |
| N3 | place | house |

225
test/bdd/environment.py Normal file
View File

@ -0,0 +1,225 @@
from behave import *
import logging
import os
import psycopg2
import psycopg2.extras
import subprocess
from nose.tools import * # for assert functions
from sys import version_info as python_version
logger = logging.getLogger(__name__)
userconfig = {
'BUILDDIR' : os.path.join(os.path.split(__file__)[0], "../../build"),
'REMOVE_TEMPLATE' : False,
'KEEP_TEST_DB' : False,
'TEMPLATE_DB' : 'test_template_nominatim',
'TEST_DB' : 'test_nominatim',
'API_TEST_DB' : 'test_api_nominatim',
'TEST_SETTINGS_FILE' : '/tmp/nominatim_settings.php'
}
use_step_matcher("re")
class NominatimEnvironment(object):
""" Collects all functions for the execution of Nominatim functions.
"""
def __init__(self, config):
self.build_dir = os.path.abspath(config['BUILDDIR'])
self.template_db = config['TEMPLATE_DB']
self.test_db = config['TEST_DB']
self.api_test_db = config['API_TEST_DB']
self.local_settings_file = config['TEST_SETTINGS_FILE']
self.reuse_template = not config['REMOVE_TEMPLATE']
self.keep_scenario_db = config['KEEP_TEST_DB']
os.environ['NOMINATIM_SETTINGS'] = self.local_settings_file
self.template_db_done = False
def write_nominatim_config(self, dbname):
f = open(self.local_settings_file, 'w')
f.write("<?php\n @define('CONST_Database_DSN', 'pgsql://@/%s');\n" % dbname)
f.close()
def cleanup(self):
try:
os.remove(self.local_settings_file)
except OSError:
pass # ignore missing file
def db_drop_database(self, name):
conn = psycopg2.connect(database='postgres')
conn.set_isolation_level(0)
cur = conn.cursor()
cur.execute('DROP DATABASE IF EXISTS %s' % (name, ))
conn.close()
def setup_template_db(self):
if self.template_db_done:
return
self.template_db_done = True
if self.reuse_template:
# check that the template is there
conn = psycopg2.connect(database='postgres')
cur = conn.cursor()
cur.execute('select count(*) from pg_database where datname = %s',
(self.template_db,))
if cur.fetchone()[0] == 1:
return
conn.close()
else:
# just in case... make sure a previous table has been dropped
self.db_drop_database(self.template_db)
# call the first part of database setup
self.write_nominatim_config(self.template_db)
self.run_setup_script('create-db', 'setup-db')
# remove external data to speed up indexing for tests
conn = psycopg2.connect(database=self.template_db)
cur = conn.cursor()
cur.execute("""select tablename from pg_tables
where tablename in ('gb_postcode', 'us_postcode')""")
for t in cur:
conn.cursor().execute('TRUNCATE TABLE %s' % (t[0],))
conn.commit()
conn.close()
# execute osm2pgsql on an empty file to get the right tables
osm2pgsql = os.path.join(self.build_dir, 'osm2pgsql', 'osm2pgsql')
proc = subprocess.Popen([osm2pgsql, '-lsc', '-r', 'xml',
'-O', 'gazetteer', '-d', self.template_db, '-'],
cwd=self.build_dir, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
[outstr, errstr] = proc.communicate(input=b'<osm version="0.6"></osm>')
logger.debug("running osm2pgsql for template: %s\n%s\n%s" % (osm2pgsql, outstr, errstr))
self.run_setup_script('create-functions', 'create-tables',
'create-partition-tables', 'create-partition-functions',
'load-data', 'create-search-indices')
def setup_api_db(self, context):
self.write_nominatim_config(self.api_test_db)
def setup_db(self, context):
self.setup_template_db()
self.write_nominatim_config(self.test_db)
conn = psycopg2.connect(database=self.template_db)
conn.set_isolation_level(0)
cur = conn.cursor()
cur.execute('DROP DATABASE IF EXISTS %s' % (self.test_db, ))
cur.execute('CREATE DATABASE %s TEMPLATE = %s' % (self.test_db, self.template_db))
conn.close()
context.db = psycopg2.connect(database=self.test_db)
if python_version[0] < 3:
psycopg2.extras.register_hstore(context.db, globally=False, unicode=True)
else:
psycopg2.extras.register_hstore(context.db, globally=False)
def teardown_db(self, context):
if 'db' in context:
context.db.close()
if not self.keep_scenario_db:
self.db_drop_database(self.test_db)
def run_setup_script(self, *args, **kwargs):
self.run_nominatim_script('setup', *args, **kwargs)
def run_update_script(self, *args, **kwargs):
self.run_nominatim_script('update', *args, **kwargs)
def run_nominatim_script(self, script, *args, **kwargs):
cmd = [os.path.join(self.build_dir, 'utils', '%s.php' % script)]
cmd.extend(['--%s' % x for x in args])
for k, v in kwargs.items():
cmd.extend(('--' + k.replace('_', '-'), str(v)))
proc = subprocess.Popen(cmd, cwd=self.build_dir,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(outp, outerr) = proc.communicate()
logger.debug("run_nominatim_script: %s\n%s\n%s" % (cmd, outp, outerr))
assert (proc.returncode == 0), "Script '%s' failed:\n%s\n%s\n" % (script, outp, outerr)
class OSMDataFactory(object):
def __init__(self):
scriptpath = os.path.dirname(os.path.abspath(__file__))
self.scene_path = os.environ.get('SCENE_PATH',
os.path.join(scriptpath, '..', 'scenes', 'data'))
self.scene_cache = {}
def parse_geometry(self, geom, scene):
if geom.find(':') >= 0:
out = self.get_scene_geometry(scene, geom)
elif geom.find(',') < 0:
out = "'POINT(%s)'::geometry" % geom
elif geom.find('(') < 0:
out = "'LINESTRING(%s)'::geometry" % geom
else:
out = "'POLYGON(%s)'::geometry" % geom
return "ST_SetSRID(%s, 4326)" % out
def get_scene_geometry(self, default_scene, name):
geoms = []
for obj in name.split('+'):
oname = obj.strip()
if oname.startswith(':'):
assert_is_not_none(default_scene, "You need to set a scene")
defscene = self.load_scene(default_scene)
wkt = defscene[oname[1:]]
else:
scene, obj = oname.split(':', 2)
scene_geoms = self.load_scene(scene)
wkt = scene_geoms[obj]
geoms.append("'%s'::geometry" % wkt)
if len(geoms) == 1:
return geoms[0]
else:
return 'ST_LineMerge(ST_Collect(ARRAY[%s]))' % ','.join(geoms)
def load_scene(self, name):
if name in self.scene_cache:
return self.scene_cache[name]
scene = {}
with open(os.path.join(self.scene_path, "%s.wkt" % name), 'r') as fd:
for line in fd:
if line.strip():
obj, wkt = line.split('|', 2)
scene[obj.strip()] = wkt.strip()
self.scene_cache[name] = scene
return scene
def before_all(context):
# logging setup
context.config.setup_logging()
# set up -D options
for k,v in userconfig.items():
context.config.userdata.setdefault(k, v)
logging.debug('User config: %s' %(str(context.config.userdata)))
# Nominatim test setup
context.nominatim = NominatimEnvironment(context.config.userdata)
context.osm = OSMDataFactory()
def after_all(context):
context.nominatim.cleanup()
def before_scenario(context, scenario):
if 'DB' in context.tags:
context.nominatim.setup_db(context)
elif 'APIDB' in context.tags:
context.nominatim.setup_api_db(context)
context.scene = None
def after_scenario(context, scenario):
if 'DB' in context.tags:
context.nominatim.teardown_db(context)

View File

@ -0,0 +1,33 @@
@DB
Feature: Import of objects with broken geometries by osm2pgsql
Scenario: Import way with double nodes
When loading osm data
"""
n100 x0 y0
n101 x0 y0.1
n102 x0.1 y0.2
w1 Thighway=primary Nn100,n101,n101,n102
"""
Then place contains
| object | class | type | geometry |
| W1 | highway | primary | 0 0, 0 0.1, 0.1 0.2 |
@wip
Scenario: Import of ballon areas
When loading osm data
"""
n1 x0 y0
n2 x0 y0.0001
n3 x0.00001 y0.0001
n4 x0.00001 y0
n5 x-0.00001 y0
w1 Thighway=unclassified Nn1,n2,n3,n4,n1,n5
w2 Thighway=unclassified Nn1,n2,n3,n4,n1
w3 Thighway=unclassified Nn1,n2,n3,n4,n3
"""
Then place contains
| object | geometrytype |
| W1 | ST_LineString |
| W2 | ST_Polygon |
| W3 | ST_LineString |

View File

@ -0,0 +1,11 @@
@DB
Feature: Import of relations by osm2pgsql
Testing specific relation problems related to members.
Scenario: Don't import empty waterways
When loading osm data
"""
n1 Tamenity=prison,name=foo
r1 Ttype=waterway,waterway=river,name=XZ Mn1@
"""
Then place has no entry for R1

View File

@ -0,0 +1,59 @@
@DB
Feature: Import of simple objects by osm2pgsql
Testing basic tagging in osm2pgsql imports.
Scenario: Import simple objects
When loading osm data
"""
n1 Tamenity=prison,name=foo x34.3 y-23
n100 x0 y0
n101 x0 y0.1
n102 x0.1 y0.2
n200 x0 y0
n201 x0 y1
n202 x1 y1
n203 x1 y0
w1 Tshop=toys,name=tata Nn100,n101,n102
w2 Tref=45 Nn200,n201,n202,n203,n200
r1 Ttype=multipolygon,tourism=hotel,name=XZ Mn1@,w2@
"""
Then place contains exactly
| object | class | type | name | geometry |
| N1 | amenity | prison | 'name' : 'foo' | 34.3 -23 |
| W1 | shop | toys | 'name' : 'tata' | 0 0, 0 0.1, 0.1 0.2 |
| R1 | tourism | hotel | 'name' : 'XZ' | (0 0, 0 1, 1 1, 1 0, 0 0) |
Scenario: Import object with two main tags
When loading osm data
"""
n1 Ttourism=hotel,amenity=restaurant,name=foo
"""
Then place contains
| object | type | name |
| N1:tourism | hotel | 'name' : 'foo' |
| N1:amenity | restaurant | 'name' : 'foo' |
Scenario: Import stand-alone house number with postcode
When loading osm data
"""
n1 Taddr:housenumber=4,addr:postcode=3345
"""
Then place contains
| object | class | type |
| N1 | place | house |
Scenario: Landuses are only imported when named
When loading osm data
"""
n100 x0 y0
n101 x0 y0.1
n102 x0.1 y0.1
n200 x0 y0
n202 x1 y1
n203 x1 y0
w1 Tlanduse=residential,name=rainbow Nn100,n101,n102,n100
w2 Tlanduse=residential Nn200,n202,n203,n200
"""
Then place contains exactly
| object | class | type |
| W1 | landuse | residential |

View File

@ -0,0 +1,554 @@
@DB
Feature: Tag evaluation
Tests if tags are correctly imported into the place table
Scenario Outline: Name tags
When loading osm data
"""
n1 Thighway=yes,<nametag>=Foo
"""
Then place contains
| object | name |
| N1 | '<nametag>' : 'Foo' |
Examples:
| nametag |
| ref |
| int_ref |
| nat_ref |
| reg_ref |
| loc_ref |
| old_ref |
| iata |
| icao |
| pcode:1 |
| pcode:2 |
| pcode:3 |
| name |
| name:de |
| name:bt-BR |
| int_name |
| int_name:xxx |
| nat_name |
| nat_name:fr |
| reg_name |
| reg_name:1 |
| loc_name |
| loc_name:DE |
| old_name |
| old_name:v1 |
| alt_name |
| alt_name:dfe |
| alt_name_1 |
| official_name |
| short_name |
| short_name:CH |
| addr:housename |
| brand |
Scenario: operator only for shops and amenities
When loading osm data
"""
n1 Thighway=yes,operator=Foo,name=null
n2 Tshop=grocery,operator=Foo
n3 Tamenity=hospital,operator=Foo
n4 Ttourism=hotel,operator=Foo
"""
Then place contains
| object | name |
| N1 | 'name' : 'null' |
| N2 | 'operator' : 'Foo' |
| N3 | 'operator' : 'Foo' |
| N4 | 'operator' : 'Foo' |
Scenario Outline: Ignored name tags
When loading osm data
"""
n1 Thighway=yes,<nametag>=Foo,name=real
"""
Then place contains
| object | name |
| N1 | 'name' : 'real' |
Examples:
| nametag |
| name_de |
| Name |
| ref:de |
| ref_de |
| my:ref |
| br:name |
| name:prefix |
| name:source |
Scenario: Special character in name tag
When loading osm data
"""
n1 Thighway=yes,name:%20%de=Foo,name=real1
n2 Thighway=yes,name:%a%de=Foo,name=real2
n3 Thighway=yes,name:%9%de=Foo,name:\\=real3
n4 Thighway=yes,name:%9%de=Foo,name=rea\l3
"""
Then place contains
| object | name |
| N1 | 'name: de' : 'Foo', 'name' : 'real1' |
| N2 | 'name: de' : 'Foo', 'name' : 'real2' |
| N3 | 'name: de' : 'Foo', 'name:\\\\' : 'real3' |
| N4 | 'name: de' : 'Foo', 'name' : 'rea\\l3' |
Scenario Outline: Included places
When loading osm data
"""
n1 T<key>=<value>,name=real
"""
Then place contains
| object | name |
| N1 | 'name' : 'real' |
Examples:
| key | value |
| emergency | phone |
| tourism | information |
| historic | castle |
| military | barracks |
| natural | water |
| highway | residential |
| aerialway | station |
| aeroway | way |
| boundary | administrative |
| craft | butcher |
| leisure | playground |
| office | bookmaker |
| railway | rail |
| shop | bookshop |
| waterway | stream |
| landuse | cemetry |
| man_made | tower |
| mountain_pass | yes |
Scenario Outline: Bridges and Tunnels take special name tags
When loading osm data
"""
n1 Thighway=road,<key>=yes,name=Rd,<key>:name=My
n2 Thighway=road,<key>=yes,name=Rd
"""
Then place contains
| object | type | name |
| N1:highway | road | 'name' : 'Rd' |
| N1:<key> | yes | 'name' : 'My' |
| N2:highway | road | 'name' : 'Rd' |
And place has no entry for N2:<key>
Examples:
| key |
| bridge |
| tunnel |
Scenario Outline: Excluded places
When loading osm data
"""
n1 T<key>=<value>,name=real
n2 Thighway=motorway,name=To%20%Hell
"""
Then place has no entry for N1
Examples:
| key | value |
| emergency | yes |
| emergency | no |
| tourism | yes |
| tourism | no |
| historic | yes |
| historic | no |
| military | yes |
| military | no |
| natural | yes |
| natural | no |
| highway | no |
| highway | turning_circle |
| highway | mini_roundabout |
| highway | noexit |
| highway | crossing |
| aerialway | no |
| aerialway | pylon |
| man_made | survey_point |
| man_made | cutline |
| aeroway | no |
| amenity | no |
| bridge | no |
| craft | no |
| leisure | no |
| office | no |
| railway | no |
| railway | level_crossing |
| shop | no |
| tunnel | no |
| waterway | riverbank |
Scenario Outline: Some tags only are included when named
When loading osm data
"""
n1 T<key>=<value>
n2 T<key>=<value>,name=To%20%Hell
n3 T<key>=<value>,ref=123
"""
Then place contains exactly
| object | class | type |
| N2 | <key> | <value> |
Examples:
| key | value |
| landuse | residential |
| natural | meadow |
| highway | traffic_signals |
| highway | service |
| highway | cycleway |
| highway | path |
| highway | footway |
| highway | steps |
| highway | bridleway |
| highway | track |
| highway | byway |
| highway | motorway_link |
| highway | primary_link |
| highway | trunk_link |
| highway | secondary_link |
| highway | tertiary_link |
| railway | rail |
| boundary | administrative |
| waterway | stream |
Scenario: Footways are not included if they are sidewalks
When loading osm data
"""
n2 Thighway=footway,name=To%20%Hell,footway=sidewalk
n23 Thighway=footway,name=x
"""
Then place has no entry for N2
Scenario: named junctions are included if there is no other tag
When loading osm data
"""
n1 Tjunction=yes
n2 Thighway=secondary,junction=roundabout,name=To-Hell
n3 Tjunction=yes,name=Le%20%Croix
"""
Then place has no entry for N1
And place has no entry for N2:junction
And place contains
| object | class | type |
| N3 | junction | yes |
Scenario: Boundary with place tag
When loading osm data
"""
n200 x0 y0
n201 x0 y1
n202 x1 y1
n203 x1 y0
w2 Tboundary=administrative,place=city,name=Foo Nn200,n201,n202,n203,n200
w4 Tboundary=administrative,place=island,name=Foo Nn200,n201,n202,n203,n200
w20 Tplace=city,name=ngng Nn200,n201,n202,n203,n200
w40 Tplace=city,boundary=statistical,name=BB Nn200,n201,n202,n203,n200
"""
Then place contains
| object | class | extratags | type |
| W2 | boundary | 'place' : 'city' | administrative |
| W4:boundary | boundary | - | administrative |
| W4:place | place | - | island |
| W20 | place | - | city |
| W40:boundary | boundary | - | statistical |
| W40:place | place | - | city |
And place has no entry for W2:place
Scenario Outline: Tags that describe a house
When loading osm data
"""
n100 T<key>=<value>
n999 Tamenity=prison,<key>=<value>
"""
Then place contains exactly
| object | class | type |
| N100 | place | house |
| N999 | amenity | prison |
Examples:
| key | value |
| addr:housename | My%20%Mansion |
| addr:housenumber | 456 |
| addr:conscriptionnumber | 4 |
| addr:streetnumber | 4568765 |
Scenario: Only named with no other interesting tag
When loading osm data
"""
n1 Tlanduse=meadow
n2 Tlanduse=residential,name=important
n3 Tlanduse=residential,name=important,place=hamlet
"""
Then place contains
| object | class | type |
| N2 | landuse | residential |
| N3 | place | hamlet |
And place has no entry for N1
And place has no entry for N3:landuse
Scenario Outline: Import of postal codes
When loading osm data
"""
n10 Thighway=secondary,<key>=<value>
n11 T<key>=<value>
"""
Then place contains
| object | class | type | postcode |
| N10 | highway | secondary | <value> |
| N11 | place | postcode | <value> |
And place has no entry for N10:place
Examples:
| key | value |
| postal_code | 45736 |
| postcode | xxx |
| addr:postcode | 564 |
| tiger:zip_left | 00011 |
| tiger:zip_right | 09123 |
Scenario: Import of street and place
When loading osm data
"""
n10 Tamenity=hospital,addr:street=Foo%20%St
n20 Tamenity=hospital,addr:place=Foo%20%Town
"""
Then place contains
| object | class | type | street | addr_place |
| N10 | amenity | hospital | Foo St | None |
| N20 | amenity | hospital | - | Foo Town |
Scenario Outline: Import of country
When loading osm data
"""
n10 Tplace=village,<key>=<value>
"""
Then place contains
| object | class | type | country_code |
| N10 | place | village | <value> |
Examples:
| key | value |
| country_code | us |
| ISO3166-1 | XX |
| is_in:country_code | __ |
| addr:country | .. |
| addr:country_code | cv |
Scenario Outline: Ignore country codes with wrong length
When loading osm data
"""
n10 Tplace=village,country_code=<value>
"""
Then place contains
| object | class | type | country_code |
| N10 | place | village | - |
Examples:
| value |
| X |
| x |
| ger |
| dkeufr |
| d%20%e |
Scenario: Import of house numbers
When loading osm data
"""
n10 Tbuilding=yes,addr:housenumber=4b
n11 Tbuilding=yes,addr:conscriptionnumber=003
n12 Tbuilding=yes,addr:streetnumber=2345
n13 Tbuilding=yes,addr:conscriptionnumber=3,addr:streetnumber=111
"""
Then place contains
| object | class | type | housenumber |
| N10 | building | yes | 4b |
| N11 | building | yes | 003 |
| N12 | building | yes | 2345 |
| N13 | building | yes | 3/111 |
Scenario: Import of address interpolations
When loading osm data
"""
n10 Taddr:interpolation=odd
n11 Taddr:housenumber=10,addr:interpolation=odd
n12 Taddr:interpolation=odd,addr:housenumber=23
"""
Then place contains
| object | class | type | housenumber |
| N10 | place | houses | odd |
| N11 | place | houses | odd |
| N12 | place | houses | odd |
Scenario: Shorten tiger:county tags
When loading osm data
"""
n10 Tplace=village,tiger:county=Feebourgh%2c%%20%AL
n11 Tplace=village,addr:state=Alabama,tiger:county=Feebourgh%2c%%20%AL
n12 Tplace=village,tiger:county=Feebourgh
"""
Then place contains
| object | class | type | isin |
| N10 | place | village | Feebourgh county |
| N11 | place | village | Alabama,Feebourgh county |
| N12 | place | village | Feebourgh county |
Scenario Outline: Import of address tags
When loading osm data
"""
n10 Tplace=village,<key>=<value>
"""
Then place contains
| object | class | type | isin |
| N10 | place | village | <value> |
Examples:
| key | value |
| is_in:country | Xanadu |
| addr:suburb | hinein |
| addr:city | Sydney |
| addr:state | Jura |
Scenario: Import of isin tags with space
When loading osm data
"""
n10 Tplace=village,is_in=Stockholm%2c%%20%Sweden
n11 Tplace=village,addr:county=le%20%havre
"""
Then place contains
| object | class | type | isin |
| N10 | place | village | Stockholm, Sweden |
| N11 | place | village | le havre |
Scenario: Import of admin level
When loading osm data
"""
n10 Tamenity=hospital,admin_level=3
n11 Tamenity=hospital,admin_level=b
n12 Tamenity=hospital
n13 Tamenity=hospital,admin_level=3.0
"""
Then place contains
| object | class | type | admin_level |
| N10 | amenity | hospital | 3 |
| N11 | amenity | hospital | 100 |
| N12 | amenity | hospital | 100 |
| N13 | amenity | hospital | 3 |
Scenario Outline: Import of extra tags
When loading osm data
"""
n10 Ttourism=hotel,<key>=foo
"""
Then place contains
| object | class | type | extratags |
| N10 | tourism | hotel | '<key>' : 'foo' |
Examples:
| key |
| tracktype |
| traffic_calming |
| service |
| cuisine |
| capital |
| dispensing |
| religion |
| denomination |
| sport |
| internet_access |
| lanes |
| surface |
| smoothness |
| width |
| est_width |
| incline |
| opening_hours |
| collection_times |
| service_times |
| disused |
| wheelchair |
| sac_scale |
| trail_visibility |
| mtb:scale |
| mtb:description |
| wood |
| drive_in |
| access |
| vehicle |
| bicyle |
| foot |
| goods |
| hgv |
| motor_vehicle |
| motor_car |
| access:foot |
| contact:phone |
| drink:mate |
| oneway |
| date_on |
| date_off |
| day_on |
| day_off |
| hour_on |
| hour_off |
| maxweight |
| maxheight |
| maxspeed |
| disused |
| toll |
| charge |
| population |
| description |
| image |
| attribution |
| fax |
| email |
| url |
| website |
| phone |
| real_ale |
| smoking |
| food |
| camera |
| brewery |
| locality |
| wikipedia |
| wikipedia:de |
| wikidata |
| name:prefix |
| name:botanical |
| name:etymology:wikidata |
Scenario: buildings
When loading osm data
"""
n10 Ttourism=hotel,building=yes
n11 Tbuilding=house
n12 Tbuilding=shed,addr:housenumber=1
n13 Tbuilding=yes,name=Das-Haus
n14 Tbuilding=yes,addr:postcode=12345
"""
Then place contains
| object | class | type |
| N10 | tourism | hotel |
| N12 | building| yes |
| N13 | building| yes |
| N14 | building| yes |
And place has no entry for N10:building
And place has no entry for N11
Scenario: complete node entry
When loading osm data
"""
n290393920 Taddr:city=Perpignan,addr:country=FR,addr:housenumber=43\,addr:postcode=66000,addr:street=Rue%20%Pierre%20%Constant%20%d`Ivry,source=cadastre-dgi-fr%20%source%20%:%20%Direction%20%Générale%20%des%20%Impôts%20%-%20%Cadastre%20%;%20%mise%20%à%20%jour%20%:2008
"""
Then place contains
| object | class | type | housenumber |
| N290393920 | place | house| 43\ |

View File

@ -0,0 +1,126 @@
@DB
Feature: Update of relations by osm2pgsql
Testing relation update by osm2pgsql.
Scenario: Remove all members of a relation
When loading osm data
"""
n1 Tamenity=prison,name=foo
n200 x0 y0
n201 x0 y0.0001
n202 x0.0001 y0.0001
n203 x0.0001 y0
w2 Tref=45' Nn200,n201,n202,n203,n200
r1 Ttype=multipolygon,tourism=hotel,name=XZ' Mw2@
"""
Then place contains
| object | class | type | name
| R1 | tourism | hotel | 'name' : 'XZ'
When updating osm data
"""
r1 Ttype=multipolygon,tourism=hotel,name=XZ Mn1@
"""
Then place has no entry for R1
Scenario: Change type of a relation
When loading osm data
"""
n200 x0 y0
n201 x0 y0.0001
n202 x0.0001 y0.0001
n203 x0.0001 y0
w2 Tref=45 Nn200,n201,n202,n203,n200
r1 Ttype=multipolygon,tourism=hotel,name=XZ Mw2@
"""
Then place contains
| object | class | type | name
| R1 | tourism | hotel | 'name' : 'XZ'
When updating osm data
"""
r1 Ttype=multipolygon,amenity=prison,name=XZ Mw2@
"""
Then place has no entry for R1:tourism
And place contains
| object | class | type | name
| R1 | amenity | prison | 'name' : 'XZ'
Scenario: Change name of a relation
When loading osm data
"""
n200 x0 y0
n201 x0 y0.0001
n202 x0.0001 y0.0001
n203 x0.0001 y0
w2 Tref=45 Nn200,n201,n202,n203,n200
r1 Ttype=multipolygon,tourism=hotel,name=AB Mw2@
"""
Then place contains
| object | class | type | name
| R1 | tourism | hotel | 'name' : 'AB'
When updating osm data
"""
r1 Ttype=multipolygon,tourism=hotel,name=XY Mw2@
"""
Then place contains
| object | class | type | name
| R1 | tourism | hotel | 'name' : 'XZ'
Scenario: Change type of a relation into something unknown
When loading osm data
"""
n200 x0 y0
n201 x0 y0.0001
n202 x0.0001 y0.0001
n203 x0.0001 y0
w2 Tref=45 Nn200,n201,n202,n203,n200
r1 Ttype=multipolygon,tourism=hotel,name=XY Mw2@
"""
Then place contains
| object | class | type | name
| R1 | tourism | hotel | 'name' : 'XZ'
When updating osm data
"""
r1 Ttype=multipolygon,amenities=prison,name=XY Mw2@
"""
Then place has no entry for R1
Scenario: Type tag is removed
When loading osm data
"""
n200 x0 y0
n201 x0 y0.0001
n202 x0.0001 y0.0001
n203 x0.0001 y0
w2 Tref=45 Nn200,n201,n202,n203,n200
r1 Ttype=multipolygon,tourism=hotel,name=XY Mw2@
"""
Then place contains
| object | class | type | name
| R1 | tourism | hotel | 'name' : 'XZ'
When updating osm data
"""
r1 Ttourism=hotel,name=XY Mw2@
"""
Then place has no entry for R1
@wip
Scenario: Type tag is renamed to something unknown
When loading osm data
"""
n200 x0 y0
n201 x0 y0.0001
n202 x0.0001 y0.0001
n203 x0.0001 y0
w2 Tref=45 Nn200,n201,n202,n203,n200
r1 Ttype=multipolygon,tourism=hotel,name=XY Mw2@
"""
Then place contains
| object | class | type | name
| R1 | tourism | hotel | 'name' : 'XZ'
When updating osm data
"""
r1 Ttype=multipolygonn,tourism=hotel,name=XY Mw2@
"""
Then place has no entry for R1

View File

@ -0,0 +1,26 @@
@DB
Feature: Update of simple objects by osm2pgsql
Testing basic update functions of osm2pgsql.
Scenario: Import object with two main tags
When loading osm data
"""
n1 Ttourism=hotel,amenity=restaurant,name=foo
n2 Tplace=locality,name=spotty
"""
Then place contains
| object | type | name
| N1:tourism | hotel | 'name' : 'foo'
| N1:amenity | restaurant | 'name' : 'foo'
| N2:place | locality | 'name' : 'spotty'
When updating osm data
"""
n1 dV Ttourism=hotel,name=foo
n2 dD
"""
Then place has no entry for N1:amenity
And place has no entry for N2
And place contains
| object | class | type | name
| N1:tourism | tourism | hotel | 'name' : 'foo'

426
test/bdd/steps/db_ops.py Normal file
View File

@ -0,0 +1,426 @@
import base64
import random
import string
import re
from nose.tools import * # for assert functions
import psycopg2.extras
class PlaceColumn:
def __init__(self, context, force_name):
self.columns = { 'admin_level' : 100}
self.force_name = force_name
self.context = context
self.geometry = None
def add(self, key, value):
if hasattr(self, 'set_key_' + key):
getattr(self, 'set_key_' + key)(value)
elif key.startswith('name+'):
self.add_hstore('name', key[5:], value)
elif key.startswith('extra+'):
self.add_hstore('extratags', key[6:], value)
else:
assert_in(key, ('class', 'type', 'street', 'addr_place',
'isin', 'postcode'))
self.columns[key] = None if value == '' else value
def set_key_name(self, value):
self.add_hstore('name', 'name', value)
def set_key_osm(self, value):
assert_in(value[0], 'NRW')
ok_(value[1:].isdigit())
self.columns['osm_type'] = value[0]
self.columns['osm_id'] = int(value[1:])
def set_key_admin(self, value):
self.columns['admin_level'] = int(value)
def set_key_housenr(self, value):
self.columns['housenumber'] = None if value == '' else value
def set_key_country(self, value):
self.columns['country_code'] = None if value == '' else value
def set_key_geometry(self, value):
self.geometry = self.context.osm.parse_geometry(value, self.context.scene)
assert_is_not_none(self.geometry)
def add_hstore(self, column, key, value):
if column in self.columns:
self.columns[column][key] = value
else:
self.columns[column] = { key : value }
def db_insert(self, cursor):
assert_in('osm_type', self.columns)
if self.force_name and 'name' not in self.columns:
self.add_hstore('name', 'name', ''.join(random.choice(string.printable)
for _ in range(int(random.random()*30))))
if self.columns['osm_type'] == 'N' and self.geometry is None:
self.geometry = "ST_SetSRID(ST_Point(%f, %f), 4326)" % (
random.random()*360 - 180, random.random()*180 - 90)
else:
assert_is_not_none(self.geometry, "Geometry missing")
query = 'INSERT INTO place (%s, geometry) values(%s, %s)' % (
','.join(self.columns.keys()),
','.join(['%s' for x in range(len(self.columns))]),
self.geometry)
cursor.execute(query, list(self.columns.values()))
class NominatimID:
""" Splits a unique identifier for places into its components.
As place_ids cannot be used for testing, we use a unique
identifier instead that is of the form <osmtype><osmid>[:<class>].
"""
id_regex = re.compile(r"(?P<tp>[NRW])(?P<id>\d+)(:(?P<cls>\w+))?")
def __init__(self, oid):
self.typ = self.oid = self.cls = None
if oid is not None:
m = self.id_regex.fullmatch(oid)
assert_is_not_none(m, "ID '%s' not of form <osmtype><osmid>[:<class>]" % oid)
self.typ = m.group('tp')
self.oid = m.group('id')
self.cls = m.group('cls')
def table_select(self):
""" Return where clause and parameter list to select the object
from a Nominatim table.
"""
where = 'osm_type = %s and osm_id = %s'
params = [self.typ, self. oid]
if self.cls is not None:
where += ' and class = %s'
params.append(self.cls)
return where, params
def get_place_id(self, cur):
where, params = self.table_select()
cur.execute("SELECT place_id FROM placex WHERE %s" % where, params)
eq_(1, cur.rowcount, "Expected exactly 1 entry in placex found %s" % cur.rowcount)
return cur.fetchone()[0]
def assert_db_column(row, column, value, context):
if column == 'object':
return
if column.startswith('centroid'):
fac = float(column[9:]) if column.startswith('centroid*') else 1.0
x, y = value.split(' ')
assert_almost_equal(float(x) * fac, row['cx'], "Bad x coordinate")
assert_almost_equal(float(y) * fac, row['cy'], "Bad y coordinate")
elif column == 'geometry':
geom = context.osm.parse_geometry(value, context.scene)
cur = context.db.cursor()
query = "SELECT ST_Equals(ST_SnapToGrid(%s, 0.00001, 0.00001), ST_SnapToGrid(ST_SetSRID('%s'::geometry, 4326), 0.00001, 0.00001))" % (
geom, row['geomtxt'],)
cur.execute(query)
eq_(cur.fetchone()[0], True, "(Row %s failed: %s)" % (column, query))
elif value == '-':
assert_is_none(row[column], "Row %s" % column)
else:
eq_(value, str(row[column]),
"Row '%s': expected: %s, got: %s"
% (column, value, str(row[column])))
################################ STEPS ##################################
@given(u'the scene (?P<scene>.+)')
def set_default_scene(context, scene):
context.scene = scene
@given("the (?P<named>named )?places")
def add_data_to_place_table(context, named):
cur = context.db.cursor()
cur.execute('ALTER TABLE place DISABLE TRIGGER place_before_insert')
for r in context.table:
col = PlaceColumn(context, named is not None)
for h in r.headings:
col.add(h, r[h])
col.db_insert(cur)
cur.execute('ALTER TABLE place ENABLE TRIGGER place_before_insert')
cur.close()
context.db.commit()
@given("the relations")
def add_data_to_planet_relations(context):
cur = context.db.cursor()
for r in context.table:
last_node = 0
last_way = 0
parts = []
if r['members']:
members = []
for m in r['members'].split(','):
mid = NominatimID(m)
if mid.typ == 'N':
parts.insert(last_node, int(mid.oid))
last_node += 1
last_way += 1
elif mid.typ == 'W':
parts.insert(last_way, int(mid.oid))
last_way += 1
else:
parts.append(int(mid.oid))
members.extend((mid.typ.lower() + mid.oid, mid.cls or ''))
else:
members = None
tags = []
for h in r.headings:
if h.startswith("tags+"):
tags.extend((h[5:], r[h]))
cur.execute("""INSERT INTO planet_osm_rels (id, way_off, rel_off, parts, members, tags)
VALUES (%s, %s, %s, %s, %s, %s)""",
(r['id'], last_node, last_way, parts, members, tags))
context.db.commit()
@given("the ways")
def add_data_to_planet_ways(context):
cur = context.db.cursor()
for r in context.table:
tags = []
for h in r.headings:
if h.startswith("tags+"):
tags.extend((h[5:], r[h]))
nodes = [ int(x.strip()) for x in r['nodes'].split(',') ]
cur.execute("INSERT INTO planet_osm_ways (id, nodes, tags) VALUES (%s, %s, %s)",
(r['id'], nodes, tags))
context.db.commit()
@when("importing")
def import_and_index_data_from_place_table(context):
context.nominatim.run_setup_script('create-functions', 'create-partition-functions')
cur = context.db.cursor()
cur.execute(
"""insert into placex (osm_type, osm_id, class, type, name, admin_level,
housenumber, street, addr_place, isin, postcode, country_code, extratags,
geometry)
select * from place where not (class='place' and type='houses' and osm_type='W')""")
cur.execute(
"""select insert_osmline (osm_id, housenumber, street, addr_place,
postcode, country_code, geometry)
from place where class='place' and type='houses' and osm_type='W'""")
context.db.commit()
context.nominatim.run_setup_script('index', 'index-noanalyse')
@when("updating places")
def update_place_table(context):
context.nominatim.run_setup_script(
'create-functions', 'create-partition-functions', 'enable-diff-updates')
cur = context.db.cursor()
for r in context.table:
col = PlaceColumn(context, False)
for h in r.headings:
col.add(h, r[h])
col.db_insert(cur)
context.db.commit()
context.nominatim.run_update_script('index')
@when("marking for delete (?P<oids>.*)")
def delete_places(context, oids):
context.nominatim.run_setup_script(
'create-functions', 'create-partition-functions', 'enable-diff-updates')
cur = context.db.cursor()
for oid in oids.split(','):
where, params = NominatimID(oid).table_select()
cur.execute("DELETE FROM place WHERE " + where, params)
context.db.commit()
context.nominatim.run_update_script('index')
@then("placex contains(?P<exact> exactly)?")
def check_placex_contents(context, exact):
cur = context.db.cursor(cursor_factory=psycopg2.extras.DictCursor)
expected_content = set()
for row in context.table:
nid = NominatimID(row['object'])
where, params = nid.table_select()
cur.execute("""SELECT *, ST_AsText(geometry) as geomtxt,
ST_X(centroid) as cx, ST_Y(centroid) as cy
FROM placex where %s""" % where,
params)
assert_less(0, cur.rowcount, "No rows found for " + row['object'])
for res in cur:
if exact:
expected_content.add((res['osm_type'], res['osm_id'], res['class']))
for h in row.headings:
if h.startswith('name'):
name = h[5:] if h.startswith('name+') else 'name'
assert_in(name, res['name'])
eq_(res['name'][name], row[h])
elif h.startswith('extratags+'):
eq_(res['extratags'][h[10:]], row[h])
elif h in ('linked_place_id', 'parent_place_id'):
if row[h] == '0':
eq_(0, res[h])
elif row[h] == '-':
assert_is_none(res[h])
else:
eq_(NominatimID(row[h]).get_place_id(context.db.cursor()),
res[h])
else:
assert_db_column(res, h, row[h], context)
if exact:
cur.execute('SELECT osm_type, osm_id, class from placex')
eq_(expected_content, set([(r[0], r[1], r[2]) for r in cur]))
context.db.commit()
@then("place contains(?P<exact> exactly)?")
def check_placex_contents(context, exact):
cur = context.db.cursor(cursor_factory=psycopg2.extras.DictCursor)
expected_content = set()
for row in context.table:
nid = NominatimID(row['object'])
where, params = nid.table_select()
cur.execute("""SELECT *, ST_AsText(geometry) as geomtxt,
ST_GeometryType(geometry) as geometrytype
FROM place where %s""" % where,
params)
assert_less(0, cur.rowcount, "No rows found for " + row['object'])
for res in cur:
if exact:
expected_content.add((res['osm_type'], res['osm_id'], res['class']))
for h in row.headings:
msg = "%s: %s" % (row['object'], h)
if h in ('name', 'extratags'):
if row[h] == '-':
assert_is_none(res[h], msg)
else:
vdict = eval('{' + row[h] + '}')
assert_equals(vdict, res[h], msg)
elif h.startswith('name+'):
assert_equals(res['name'][h[5:]], row[h], msg)
elif h.startswith('extratags+'):
assert_equals(res['extratags'][h[10:]], row[h], msg)
elif h in ('linked_place_id', 'parent_place_id'):
if row[h] == '0':
assert_equals(0, res[h], msg)
elif row[h] == '-':
assert_is_none(res[h], msg)
else:
assert_equals(NominatimID(row[h]).get_place_id(context.db.cursor()),
res[h], msg)
else:
assert_db_column(res, h, row[h], context)
if exact:
cur.execute('SELECT osm_type, osm_id, class from place')
eq_(expected_content, set([(r[0], r[1], r[2]) for r in cur]))
context.db.commit()
@then("search_name contains")
def check_search_name_contents(context):
cur = context.db.cursor(cursor_factory=psycopg2.extras.DictCursor)
for row in context.table:
pid = NominatimID(row['object']).get_place_id(cur)
cur.execute("""SELECT *, ST_X(centroid) as cx, ST_Y(centroid) as cy
FROM search_name WHERE place_id = %s""", (pid, ))
assert_less(0, cur.rowcount, "No rows found for " + row['object'])
for res in cur:
for h in row.headings:
if h in ('name_vector', 'nameaddress_vector'):
terms = [x.strip().replace('#', ' ') for x in row[h].split(',')]
subcur = context.db.cursor()
subcur.execute("""SELECT word_id, word_token
FROM word, (SELECT unnest(%s) as term) t
WHERE word_token = make_standard_name(t.term)""",
(terms,))
ok_(subcur.rowcount >= len(terms))
for wid in subcur:
assert_in(wid[0], res[h],
"Missing term for %s/%s: %s" % (pid, h, wid[1]))
else:
assert_db_column(res, h, row[h], context)
context.db.commit()
@then("(?P<oid>\w+) expands to(?P<neg> no)? interpolation")
def check_location_property_osmline(context, oid, neg):
cur = context.db.cursor(cursor_factory=psycopg2.extras.DictCursor)
nid = NominatimID(oid)
eq_('W', nid.typ, "interpolation must be a way")
cur.execute("""SELECT *, ST_AsText(linegeo) as geomtxt
FROM location_property_osmline WHERE osm_id = %s""",
(nid.oid, ))
if neg:
eq_(0, cur.rowcount)
return
todo = list(range(len(list(context.table))))
for res in cur:
for i in todo:
row = context.table[i]
if (int(row['start']) == res['startnumber']
and int(row['end']) == res['endnumber']):
todo.remove(i)
break
else:
assert False, "Unexpected row %s" % (str(res))
for h in row.headings:
if h in ('start', 'end'):
continue
elif h == 'parent_place_id':
if row[h] == '0':
eq_(0, res[h])
elif row[h] == '-':
assert_is_none(res[h])
else:
eq_(NominatimID(row[h]).get_place_id(context.db.cursor()),
res[h])
else:
assert_db_column(res, h, row[h], context)
eq_(todo, [])
@then("(?P<table>placex|place) has no entry for (?P<oid>.*)")
def check_placex_has_entry(context, table, oid):
cur = context.db.cursor(cursor_factory=psycopg2.extras.DictCursor)
nid = NominatimID(oid)
where, params = nid.table_select()
cur.execute("SELECT * FROM %s where %s" % (table, where), params)
eq_(0, cur.rowcount)
context.db.commit()
@then("search_name has no entry for (?P<oid>.*)")
def check_search_name_has_entry(context, oid):
cur = context.db.cursor(cursor_factory=psycopg2.extras.DictCursor)
pid = NominatimID(oid).get_place_id(cur)
cur.execute("SELECT * FROM search_name WHERE place_id = %s", (pid, ))
eq_(0, cur.rowcount)
context.db.commit()

View File

@ -0,0 +1,58 @@
import subprocess
import tempfile
import random
import os
from nose.tools import * # for assert functions
@when(u'loading osm data')
def load_osm_file(context):
# create a OSM file in /tmp and import it
with tempfile.NamedTemporaryFile(dir='/tmp', suffix='.opl', delete=False) as fd:
fname = fd.name
for line in context.text.splitlines():
if line.startswith('n') and line.find(' x') < 0:
line += " x%d y%d" % (random.random() * 360 - 180,
random.random() * 180 - 90)
fd.write(line.encode('utf-8'))
fd.write(b'\n')
context.nominatim.run_setup_script('import-data', osm_file=fname,
osm2pgsql_cache=300)
### reintroduce the triggers/indexes we've lost by having osm2pgsql set up place again
cur = context.db.cursor()
cur.execute("""CREATE TRIGGER place_before_delete BEFORE DELETE ON place
FOR EACH ROW EXECUTE PROCEDURE place_delete()""")
cur.execute("""CREATE TRIGGER place_before_insert BEFORE INSERT ON place
FOR EACH ROW EXECUTE PROCEDURE place_insert()""")
cur.execute("""CREATE UNIQUE INDEX idx_place_osm_unique on place using btree(osm_id,osm_type,class,type)""")
context.db.commit()
os.remove(fname)
@when(u'updating osm data')
def update_from_osm_file(context):
context.nominatim.run_setup_script('create-functions', 'create-partition-functions')
cur = context.db.cursor()
cur.execute("""insert into placex (osm_type, osm_id, class, type, name,
admin_level, housenumber, street, addr_place, isin, postcode,
country_code, extratags, geometry) select * from place""")
context.db.commit()
context.nominatim.run_setup_script('index', 'index-noanalyse')
context.nominatim.run_setup_script('create-functions', 'create-partition-functions',
'enable-diff-updates')
# create a OSM file in /tmp and import it
with tempfile.NamedTemporaryFile(dir='/tmp', suffix='.opl', delete=False) as fd:
fname = fd.name
for line in context.text.splitlines():
if line.startswith('n') and line.find(' x') < 0:
line += " x%d y%d" % (random.random() * 360 - 180,
random.random() * 180 - 90)
fd.write(line.encode('utf-8'))
fd.write(b'\n')
context.nominatim.run_update_script(import_diff=fname)
os.remove(fname)

514
test/bdd/steps/queries.py Normal file
View File

@ -0,0 +1,514 @@
""" Steps that run search queries.
Queries may either be run directly via PHP using the query script
or via the HTTP interface.
"""
import json
import os
import io
import re
from tidylib import tidy_document
import xml.etree.ElementTree as ET
import subprocess
from urllib.parse import urlencode
from collections import OrderedDict
from nose.tools import * # for assert functions
BASE_SERVER_ENV = {
'HTTP_HOST' : 'localhost',
'HTTP_USER_AGENT' : 'Mozilla/5.0 (X11; Linux x86_64; rv:51.0) Gecko/20100101 Firefox/51.0',
'HTTP_ACCEPT' : 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'HTTP_ACCEPT_ENCODING' : 'gzip, deflate',
'HTTP_CONNECTION' : 'keep-alive',
'SERVER_SIGNATURE' : '<address>Nominatim BDD Tests</address>',
'SERVER_SOFTWARE' : 'Nominatim test',
'SERVER_NAME' : 'localhost',
'SERVER_ADDR' : '127.0.1.1',
'SERVER_PORT' : '80',
'REMOTE_ADDR' : '127.0.0.1',
'DOCUMENT_ROOT' : '/var/www',
'REQUEST_SCHEME' : 'http',
'CONTEXT_PREFIX' : '/',
'SERVER_ADMIN' : 'webmaster@localhost',
'REMOTE_PORT' : '49319',
'GATEWAY_INTERFACE' : 'CGI/1.1',
'SERVER_PROTOCOL' : 'HTTP/1.1',
'REQUEST_METHOD' : 'GET',
'REDIRECT_STATUS' : 'CGI'
}
def compare(operator, op1, op2):
if operator == 'less than':
return op1 < op2
elif operator == 'more than':
return op1 > op2
elif operator == 'exactly':
return op1 == op2
elif operator == 'at least':
return op1 >= op2
elif operator == 'at most':
return op1 <= op2
else:
raise Exception("unknown operator '%s'" % operator)
class GenericResponse(object):
def match_row(self, row):
if 'ID' in row.headings:
todo = [int(row['ID'])]
else:
todo = range(len(self.result))
for i in todo:
res = self.result[i]
for h in row.headings:
if h == 'ID':
pass
elif h == 'osm':
assert_equal(res['osm_type'], row[h][0])
assert_equal(res['osm_id'], row[h][1:])
elif h == 'centroid':
x, y = row[h].split(' ')
assert_almost_equal(float(y), float(res['lat']))
assert_almost_equal(float(x), float(res['lon']))
elif row[h].startswith("^"):
assert_in(h, res)
assert_is_not_none(re.fullmatch(row[h], res[h]),
"attribute '%s': expected: '%s', got '%s'"
% (h, row[h], res[h]))
else:
assert_in(h, res)
assert_equal(str(res[h]), str(row[h]))
def property_list(self, prop):
return [ x[prop] for x in self.result ]
class SearchResponse(GenericResponse):
def __init__(self, page, fmt='json', errorcode=200):
self.page = page
self.format = fmt
self.errorcode = errorcode
self.result = []
self.header = dict()
if errorcode == 200:
getattr(self, 'parse_' + fmt)()
def parse_json(self):
m = re.fullmatch(r'([\w$][^(]*)\((.*)\)', self.page)
if m is None:
code = self.page
else:
code = m.group(2)
self.header['json_func'] = m.group(1)
self.result = json.JSONDecoder(object_pairs_hook=OrderedDict).decode(code)
def parse_html(self):
content, errors = tidy_document(self.page,
options={'char-encoding' : 'utf8'})
#eq_(len(errors), 0 , "Errors found in HTML document:\n%s" % errors)
b = content.find('nominatim_results =')
e = content.find('</script>')
content = content[b:e]
b = content.find('[')
e = content.rfind(']')
self.result = json.JSONDecoder(object_pairs_hook=OrderedDict).decode(content[b:e+1])
def parse_xml(self):
et = ET.fromstring(self.page)
self.header = dict(et.attrib)
for child in et:
assert_equal(child.tag, "place")
self.result.append(dict(child.attrib))
address = {}
for sub in child:
if sub.tag == 'extratags':
self.result[-1]['extratags'] = {}
for tag in sub:
self.result[-1]['extratags'][tag.attrib['key']] = tag.attrib['value']
elif sub.tag == 'namedetails':
self.result[-1]['namedetails'] = {}
for tag in sub:
self.result[-1]['namedetails'][tag.attrib['desc']] = tag.text
elif sub.tag in ('geokml'):
self.result[-1][sub.tag] = True
else:
address[sub.tag] = sub.text
if len(address) > 0:
self.result[-1]['address'] = address
class ReverseResponse(GenericResponse):
def __init__(self, page, fmt='json', errorcode=200):
self.page = page
self.format = fmt
self.errorcode = errorcode
self.result = []
self.header = dict()
if errorcode == 200:
getattr(self, 'parse_' + fmt)()
def parse_html(self):
content, errors = tidy_document(self.page,
options={'char-encoding' : 'utf8'})
#eq_(len(errors), 0 , "Errors found in HTML document:\n%s" % errors)
b = content.find('nominatim_results =')
e = content.find('</script>')
content = content[b:e]
b = content.find('[')
e = content.rfind(']')
self.result = json.JSONDecoder(object_pairs_hook=OrderedDict).decode(content[b:e+1])
def parse_json(self):
m = re.fullmatch(r'([\w$][^(]*)\((.*)\)', self.page)
if m is None:
code = self.page
else:
code = m.group(2)
self.header['json_func'] = m.group(1)
self.result = [json.JSONDecoder(object_pairs_hook=OrderedDict).decode(code)]
def parse_xml(self):
et = ET.fromstring(self.page)
self.header = dict(et.attrib)
self.result = []
for child in et:
if child.tag == 'result':
eq_(0, len(self.result), "More than one result in reverse result")
self.result.append(dict(child.attrib))
elif child.tag == 'addressparts':
address = {}
for sub in child:
address[sub.tag] = sub.text
self.result[0]['address'] = address
elif child.tag == 'extratags':
self.result[0]['extratags'] = {}
for tag in child:
self.result[0]['extratags'][tag.attrib['key']] = tag.attrib['value']
elif child.tag == 'namedetails':
self.result[0]['namedetails'] = {}
for tag in child:
self.result[0]['namedetails'][tag.attrib['desc']] = tag.text
elif child.tag in ('geokml'):
self.result[0][child.tag] = True
else:
assert child.tag == 'error', \
"Unknown XML tag %s on page: %s" % (child.tag, self.page)
class DetailsResponse(GenericResponse):
def __init__(self, page, fmt='json', errorcode=200):
self.page = page
self.format = fmt
self.errorcode = errorcode
self.result = []
self.header = dict()
if errorcode == 200:
getattr(self, 'parse_' + fmt)()
def parse_html(self):
content, errors = tidy_document(self.page,
options={'char-encoding' : 'utf8'})
self.result = {}
@when(u'searching for "(?P<query>.*)"(?P<dups> with dups)?')
def query_cmd(context, query, dups):
""" Query directly via PHP script.
"""
cmd = [os.path.join(context.nominatim.build_dir, 'utils', 'query.php'),
'--search', query]
# add more parameters in table form
if context.table:
for h in context.table.headings:
value = context.table[0][h].strip()
if value:
cmd.extend(('--' + h, value))
if dups:
cmd.extend(('--dedupe', '0'))
proc = subprocess.Popen(cmd, cwd=context.nominatim.build_dir,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(outp, err) = proc.communicate()
assert_equals (0, proc.returncode, "query.php failed with message: %s\noutput: %s" % (err, outp))
context.response = SearchResponse(outp.decode('utf-8'), 'json')
def send_api_query(endpoint, params, fmt, context):
if fmt is not None:
params['format'] = fmt.strip()
if context.table:
if context.table.headings[0] == 'param':
for line in context.table:
params[line['param']] = line['value']
else:
for h in context.table.headings:
params[h] = context.table[0][h]
env = dict(BASE_SERVER_ENV)
env['QUERY_STRING'] = urlencode(params)
env['SCRIPT_NAME'] = '/%s.php' % endpoint
env['REQUEST_URI'] = '%s?%s' % (env['SCRIPT_NAME'], env['QUERY_STRING'])
env['CONTEXT_DOCUMENT_ROOT'] = os.path.join(context.nominatim.build_dir, 'website')
env['SCRIPT_FILENAME'] = os.path.join(env['CONTEXT_DOCUMENT_ROOT'],
'%s.php' % endpoint)
env['NOMINATIM_SETTINGS'] = context.nominatim.local_settings_file
if hasattr(context, 'http_headers'):
env.update(context.http_headers)
cmd = ['/usr/bin/php-cgi', env['SCRIPT_FILENAME']]
for k,v in params.items():
cmd.append("%s=%s" % (k, v))
proc = subprocess.Popen(cmd, cwd=context.nominatim.build_dir, env=env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(outp, err) = proc.communicate()
assert_equals(0, proc.returncode,
"query.php failed with message: %s\noutput: %s" % (err, outp))
assert_equals(0, len(err), "Unexpected PHP error: %s" % (err))
outp = outp.decode('utf-8')
if outp.startswith('Status: '):
status = int(outp[8:11])
else:
status = 200
content_start = outp.find('\r\n\r\n')
return outp[content_start + 4:], status
@given(u'the HTTP header')
def add_http_header(context):
if not hasattr(context, 'http_headers'):
context.http_headers = {}
for h in context.table.headings:
envvar = 'HTTP_' + h.upper().replace('-', '_')
context.http_headers[envvar] = context.table[0][h]
@when(u'sending (?P<fmt>\S+ )?search query "(?P<query>.*)"(?P<addr> with address)?')
def website_search_request(context, fmt, query, addr):
params = {}
if query:
params['q'] = query
if addr is not None:
params['addressdetails'] = '1'
outp, status = send_api_query('search', params, fmt, context)
if fmt is None:
outfmt = 'html'
elif fmt == 'jsonv2 ':
outfmt = 'json'
else:
outfmt = fmt.strip()
context.response = SearchResponse(outp, outfmt, status)
@when(u'sending (?P<fmt>\S+ )?reverse coordinates (?P<lat>[0-9.-]+)?,(?P<lon>[0-9.-]+)?')
def website_reverse_request(context, fmt, lat, lon):
params = {}
if lat is not None:
params['lat'] = lat
if lon is not None:
params['lon'] = lon
outp, status = send_api_query('reverse', params, fmt, context)
if fmt is None:
outfmt = 'xml'
elif fmt == 'jsonv2 ':
outfmt = 'json'
else:
outfmt = fmt.strip()
context.response = ReverseResponse(outp, outfmt, status)
@when(u'sending (?P<fmt>\S+ )?details query for (?P<query>.*)')
def website_details_request(context, fmt, query):
params = {}
if query[0] in 'NWR':
params['osmtype'] = query[0]
params['osmid'] = query[1:]
else:
params['place_id'] = query
outp, status = send_api_query('details', params, fmt, context)
context.response = DetailsResponse(outp, 'html', status)
@when(u'sending (?P<fmt>\S+ )?lookup query for (?P<query>.*)')
def website_lookup_request(context, fmt, query):
params = { 'osm_ids' : query }
outp, status = send_api_query('lookup', params, fmt, context)
if fmt == 'json ':
outfmt = 'json'
else:
outfmt = 'xml'
context.response = SearchResponse(outp, outfmt, status)
@step(u'(?P<operator>less than|more than|exactly|at least|at most) (?P<number>\d+) results? (?:is|are) returned')
def validate_result_number(context, operator, number):
eq_(context.response.errorcode, 200)
numres = len(context.response.result)
ok_(compare(operator, numres, int(number)),
"Bad number of results: expected %s %s, got %d." % (operator, number, numres))
@then(u'a HTTP (?P<status>\d+) is returned')
def check_http_return_status(context, status):
eq_(context.response.errorcode, int(status))
@then(u'the result is valid (?P<fmt>\w+)')
def step_impl(context, fmt):
context.execute_steps("Then a HTTP 200 is returned")
eq_(context.response.format, fmt)
@then(u'result header contains')
def check_header_attr(context):
for line in context.table:
assert_is_not_none(re.fullmatch(line['value'], context.response.header[line['attr']]),
"attribute '%s': expected: '%s', got '%s'"
% (line['attr'], line['value'],
context.response.header[line['attr']]))
@then(u'result header has (?P<neg>not )?attributes (?P<attrs>.*)')
def check_header_no_attr(context, neg, attrs):
for attr in attrs.split(','):
if neg:
assert_not_in(attr, context.response.header)
else:
assert_in(attr, context.response.header)
@then(u'results contain')
def step_impl(context):
context.execute_steps("then at least 1 result is returned")
for line in context.table:
context.response.match_row(line)
@then(u'result (?P<lid>\d+ )?has (?P<neg>not )?attributes (?P<attrs>.*)')
def validate_attributes(context, lid, neg, attrs):
if lid is None:
idx = range(len(context.response.result))
context.execute_steps("then at least 1 result is returned")
else:
idx = [int(lid.strip())]
context.execute_steps("then more than %sresults are returned" % lid)
for i in idx:
for attr in attrs.split(','):
if neg:
assert_not_in(attr, context.response.result[i])
else:
assert_in(attr, context.response.result[i])
@then(u'result addresses contain')
def step_impl(context):
context.execute_steps("then at least 1 result is returned")
if 'ID' not in context.table.headings:
addr_parts = context.response.property_list('address')
for line in context.table:
if 'ID' in context.table.headings:
addr_parts = [dict(context.response.result[int(line['ID'])]['address'])]
for h in context.table.headings:
if h != 'ID':
for p in addr_parts:
assert_in(h, p)
assert_equal(p[h], line[h], "Bad address value for %s" % h)
@then(u'address of result (?P<lid>\d+) has(?P<neg> no)? types (?P<attrs>.*)')
def check_address(context, lid, neg, attrs):
context.execute_steps("then more than %s results are returned" % lid)
addr_parts = context.response.result[int(lid)]['address']
for attr in attrs.split(','):
if neg:
assert_not_in(attr, addr_parts)
else:
assert_in(attr, addr_parts)
@then(u'address of result (?P<lid>\d+) is')
def check_address(context, lid):
context.execute_steps("then more than %s results are returned" % lid)
addr_parts = dict(context.response.result[int(lid)]['address'])
for line in context.table:
assert_in(line['type'], addr_parts)
assert_equal(addr_parts[line['type']], line['value'],
"Bad address value for %s" % line['type'])
del addr_parts[line['type']]
eq_(0, len(addr_parts), "Additional address parts found: %s" % str(addr_parts))
@then(u'result (?P<lid>\d+ )?has bounding box in (?P<coords>[\d,.-]+)')
def step_impl(context, lid, coords):
if lid is None:
context.execute_steps("then at least 1 result is returned")
bboxes = context.response.property_list('boundingbox')
else:
context.execute_steps("then more than %sresults are returned" % lid)
bboxes = [ context.response.result[int(lid)]['boundingbox']]
coord = [ float(x) for x in coords.split(',') ]
for bbox in bboxes:
if isinstance(bbox, str):
bbox = bbox.split(',')
bbox = [ float(x) for x in bbox ]
assert_greater_equal(bbox[0], coord[0])
assert_less_equal(bbox[1], coord[1])
assert_greater_equal(bbox[2], coord[2])
assert_less_equal(bbox[3], coord[3])
@then(u'there are(?P<neg> no)? duplicates')
def check_for_duplicates(context, neg):
context.execute_steps("then at least 1 result is returned")
resarr = set()
has_dupe = False
for res in context.response.result:
dup = (res['osm_type'], res['class'], res['type'], res['display_name'])
if dup in resarr:
has_dupe = True
break
resarr.add(dup)
if neg:
assert not has_dupe, "Found duplicate for %s" % (dup, )
else:
assert has_dupe, "No duplicates found"

View File

@ -2,7 +2,7 @@
namespace Nominatim;
require '../lib/lib.php';
require '../../lib/lib.php';
class NominatimTest extends \PHPUnit_Framework_TestCase
{

188
test/testdb/testdb.polys Normal file
View File

@ -0,0 +1,188 @@
hamburg
1
9.5842804224817 53.5792118965693
10.2155260812517 53.8246176085747
10.475796519837 53.4477065812749
9.86815657040402 53.3278566584492
9.5842804224817 53.5792118965693
END
END
liechtenstein
1
9.20853378041844 47.0559465458986
9.29384606832709 47.3507444175206
9.49848868129809 47.4492015884201
9.89867967626406 47.130228397937
9.58252408463202 46.8691824262863
9.20853378041844 47.0559465458986
END
END
mauretania
1
-17.1644809253606 20.8842205115601
-16.9724694177095 21.4269590060279
-13.1021317602129 21.4296172232924
-13.1921945122782 22.933479308252
-12.6268357994672 23.3657053906301
-12.097953263953 23.5240373343518
-12.0829087151283 26.0504750040867
-8.76667736314239 26.0902806494621
-8.70696146546581 27.398150020094
-4.74082309576697 25.0335832288387
-4.81791817472112 24.9110447612753
-6.46188681458007 24.9097418021523
-5.52019664485951 16.5528424294381
-5.23944558032214 16.3345963721271
-5.46430959536222 15.4167603246987
-10.5098309475637 15.3543804758815
-10.8990110578091 15.0165664449548
-11.4524241105327 15.5284484048548
-11.6506044402694 15.4177129737211
-11.7217380574693 14.8443740855381
-12.0680385872662 14.631162290351
-12.9159650211166 15.1353675285383
-13.4682080737434 15.990978880421
-13.9034306609561 16.0335336430301
-14.4204259536969 16.5367120778098
-16.2084993845136 16.4236729998869
-16.4302891135388 15.983629954068
-16.7862990158802 16.0015581695182
-16.3398604501701 18.2277294784962
-16.8886326298423 19.403294102207
-16.585950103956 20.1812628878206
-17.0813413461875 20.5018960498623
-17.1644809253606 20.8842205115601
END
END
southdakota
1
-104.297439187793 45.5046231666747
-104.299553299189 45.9775616107873
-104.096382287788 46.1214554410027
-96.4569073278818 46.0989703969216
-96.3044767039811 45.9115675974812
-96.5247176872565 45.5862877816162
-96.1968327207445 45.3001425265336
-96.1895592635092 43.1140553398185
-96.3329810429579 42.753709571028
-96.2088431240585 42.4315154785281
-96.5131423452368 42.3018249286814
-97.3698668669773 42.6757095208851
-98.0672608450503 42.5864899816312
-98.6364176029847 42.8190548434256
-104.146030684593 42.8349704964079
-104.298744415682 43.0007971343175
-104.297439187793 45.5046231666747
END
END
uruguay
1
-58.7094503498495 -33.5893528935793
-58.5930297220504 -33.0935229194446
-58.329380679061 -32.9223715673938
-58.4160616358367 -31.8529190894557
-58.2162152055053 -31.6271374679322
-58.2872417410783 -31.4234579824293
-58.0141967102111 -30.7805399817192
-58.0801823804181 -30.438369563871
-57.7465673402929 -30.0366166581386
-57.3919054971047 -30.0920714480735
-57.0841245854315 -29.904689506793
-56.5203007925187 -30.0577138349604
-55.8148965965951 -30.7486942236281
-55.4992686810269 -30.6637735134172
-55.0823825399047 -31.0951827436534
-54.4609533378373 -31.3096231186724
-52.8647106347639 -32.7122837473293
-53.3056885052038 -33.2040687582016
-53.3095867684494 -33.547639206286
-52.9652990494926 -33.8719452856167
-53.3564833683333 -34.6077542513996
-55.6741509399751 -35.9609110600942
-58.0955146798429 -34.8078487405856
-58.1517292851949 -34.5120322638008
-58.490557396808 -34.2574246976253
-58.7094503498495 -33.5893528935793
END
END
vietnam
1
111.737359200422 8.65966389848196
112.121385535871 9.03974154821598
112.431435613709 8.95854407291052
112.38091038991 8.72869141993135
112.09887919106 8.68752900955559
111.97764097397 8.47027379584868
111.737359200422 8.65966389848196))
((101.952539778161 22.3744843276505
102.42115782215 22.9512287654112
103.011765362757 22.7267436381062
103.309708309047 22.9942421815192
103.88404879754 22.8346923138744
104.62599830253 23.0355978361405
104.77147320329 23.3199292571035
105.357960240763 23.571602116825
105.914016228782 23.1523756402279
106.944000642084 22.9589139939517
106.898848084023 22.1610135134167
107.607567716302 21.7959107906373
107.974793702983 21.8006347108588
108.387669146529 21.2765369215022
108.350851164743 20.968001228975
107.807714207219 20.5205405897832
108.126799006308 20.25069143158
108.024397106408 19.8655870027683
107.557799016272 19.7778131848293
107.347203511678 19.9746481898397
107.334187929597 20.2503746970115
107.017188364275 20.2484351047036
106.88168214676 19.9452753668929
106.293211714996 19.5544614034104
106.229050701227 19.084494263666
106.339912028886 18.6544696395528
107.02787120458 18.0908270105482
106.992048357062 17.6610336194013
107.61356106657 17.4455208732243
109.497501740771 15.5635441996556
109.851305717105 12.5942782065681
109.388462041025 9.73419261849773
106.766311197844 8.27477170182912
104.780565299066 8.00956568214471
103.236641212428 8.93273491992321
102.869224286517 9.37238182036115
102.909736286172 9.54505245725422
103.717955146996 10.1271353150665
103.686354089781 10.5210100495922
104.015476398095 10.662190116791
104.247688572369 10.5092223585766
104.775438554966 10.7014491439673
104.980441972174 11.1056591699101
105.7024629092 11.2045497780933
105.626753980109 11.6300119740348
105.748149258633 11.8111016132902
107.378741221451 12.5115515071093
107.300732476862 12.958173736809
107.435951728865 13.4799057185978
107.150187488306 14.1107044150892
107.411164618718 15.2667546971578
107.034817435829 15.6854570535274
107.112895173759 15.9819504632758
106.538255811019 16.3296204491326
106.173473133351 17.0787384431658
105.048356842632 18.1992017641427
104.927464569104 18.5590394564598
103.69790542148 19.2392576346486
103.937765477152 19.8144348780526
104.649272403028 19.9254452865302
104.202109979964 20.3691301778448
104.304114148338 20.6519242141597
103.979630359435 20.7152945884418
103.65735416319 20.4809125958884
103.000528825335 20.7510959123255
102.627912181829 21.1973495084135
102.683177661723 21.4733395191357
101.952539778161 22.3744843276505
END
END

Binary file not shown.

Binary file not shown.

View File

@ -1,14 +0,0 @@
Basic unit tests of PHP code. Very low coverage. Doesn't cover interaction
with the webserver/HTTP or database (yet).
You need to have
https://phpunit.de/manual/4.2/en/
installed.
To execute the test suite run
$ cd tests-php
$ phpunit ./
It will read phpunit.xml which points to the library, test path, bootstrap
strip and set other parameters.

View File

@ -1,102 +0,0 @@
This directory contains functional tests for the Nominatim API,
for the import/update from osm files and for indexing.
The tests use the lettuce framework (http://lettuce.it/) and
nose (https://nose.readthedocs.org). API tests are meant to be run
against a Nominatim installation with a complete planet-wide
setup based on a fairly recent planet. If you only have an
excerpt, some of the API tests may fail. Database tests can be
run without having a database installed.
Prerequisites
=============
* lettuce framework (http://lettuce.it/)
* nose (https://nose.readthedocs.org)
* pytidylib (http://countergram.com/open-source/pytidylib)
* haversine (https://github.com/mapado/haversine)
* shapely (https://github.com/Toblerity/Shapely)
Usage
=====
* get prerequisites
# on a fresh Ubuntu LTS 14.04 you'll also need these system-wide packages
[sudo] apt-get install python-dev python-pip python-Levenshtein tidy
[sudo] pip install lettuce nose pytidylib haversine psycopg2 shapely
* run the tests
NOMINATIM_SERVER=http://your.nominatim.instance/ lettuce features
The tests can be configured with a set of environment variables:
* `NOMINATIM_SERVER` - URL of the nominatim instance (API tests)
* `NOMINATIM_DIR` - source directory of Nominatim (import tests)
* `TEMPLATE_DB` - name of template database used as a skeleton for
the test databases (db tests)
* `TEST_DB` - name of test database (db tests)
* `NOMINATIM_SETTINGS` - file to write temporary Nominatim settings to (db tests)
* `NOMINATIM_REUSE_TEMPLATE` - if defined, the template database will not be
deleted after the test runs and reused during
the next run. This speeds up tests considerably
but might lead to outdated errors for some
changes in the database layout.
* `NOMINATIM_KEEP_SCENARIO_DB` - if defined, the test database will not be
dropped after a test is finished. Should
only be used if one single scenario is run,
otherwise the result is undefined.
* `LOGLEVEL` - set to 'debug' to get more verbose output (only works properly
when output to a logfile is configured)
* `LOGFILE` - sends debug output to the given file
Writing Tests
=============
The following explanation assume that the reader is familiar with the lettuce
notations of features, scenarios and steps.
All possible steps can be found in the `steps` directory and should ideally
be documented.
API Tests (`features/api`)
--------------------------
These tests are meant to test the different API calls and their parameters.
There are two kind of steps defined for these tests:
request setup steps (see `steps/api_setup.py`)
and steps for checking results (see `steps/api_result.py`).
Each scenario follows this simple sequence of steps:
1. One or more steps to define parameters and HTTP headers of the request.
These are cumulative, so you can use multiple steps.
2. A single step to call the API. This sends a HTTP request to the configured
server and collects the answer. The cached parameters will be deleted,
to ensure that the setup works properly with scenario outlines.
3. As many result checks as necessary. The result remains cached, so that
multiple tests can be added here.
Indexing Tests (`features/db`)
---------------------------------------------------
These tests check the import and update of the Nominatim database. They do not
test the correctness of osm2pgsql. Each test will write some data into the `place`
table (and optionally `the planet_osm_*` tables if required) and then run
Nominatim's processing functions on that.
These tests need to create their own test databases. By default they will be
called `test_template_nominatim` and `test_nominatim`. Names can be changed with
the environment variables `TEMPLATE_DB` and `TEST_DB`. The user running the tests
needs superuser rights for postgres.
Import Tests (`features/osm2pgsql`)
-----------------------------------
These tests check that data is imported correctly into the place table. They
use the same template database as the Indexing tests, so the same remarks apply.

View File

@ -1,13 +0,0 @@
Feature: Object details
Check details page for correctness
Scenario Outline: Details via OSM id
When looking up details for <object>
Then the result is valid
Examples:
| object
| 1758375
| N158845944
| W72493656
| R62422

View File

@ -1,100 +0,0 @@
Feature: Localization of search results
Scenario: Search - default language
When sending json search query "Germany"
Then results contain
| ID | display_name
| 0 | Deutschland.*
Scenario: Search - accept-language first
Given the request parameters
| accept-language
| en,de
When sending json search query "Deutschland"
Then results contain
| ID | display_name
| 0 | Germany.*
Scenario: Search - accept-language missing
Given the request parameters
| accept-language
| xx,fr,en,de
When sending json search query "Deutschland"
Then results contain
| ID | display_name
| 0 | Allemagne.*
Scenario: Search - http accept language header first
Given the HTTP header
| accept-language
| fr-ca,fr;q=0.8,en-ca;q=0.5,en;q=0.3
When sending json search query "Deutschland"
Then results contain
| ID | display_name
| 0 | Allemagne.*
Scenario: Search - http accept language header and accept-language
Given the request parameters
| accept-language
| de,en
Given the HTTP header
| accept-language
| fr-ca,fr;q=0.8,en-ca;q=0.5,en;q=0.3
When sending json search query "Deutschland"
Then results contain
| ID | display_name
| 0 | Deutschland.*
Scenario: Search - http accept language header fallback
Given the HTTP header
| accept-language
| fr-ca,en-ca;q=0.5
When sending json search query "Deutschland"
Then results contain
| ID | display_name
| 0 | Allemagne.*
Scenario: Search - http accept language header fallback (upper case)
Given the HTTP header
| accept-language
| fr-FR;q=0.8,en-ca;q=0.5
When sending json search query "Deutschland"
Then results contain
| ID | display_name
| 0 | Allemagne.*
Scenario: Reverse - default language
When looking up coordinates 48.13921,11.57328
Then result addresses contain
| ID | city
| 0 | München
Scenario: Reverse - accept-language parameter
Given the request parameters
| accept-language
| en,fr
When looking up coordinates 48.13921,11.57328
Then result addresses contain
| ID | city
| 0 | Munich
Scenario: Reverse - HTTP accept language header
Given the HTTP header
| accept-language
| fr-ca,fr;q=0.8,en-ca;q=0.5,en;q=0.3
When looking up coordinates 48.13921,11.57328
Then result addresses contain
| ID | city
| 0 | Munich
Scenario: Reverse - accept-language parameter and HTTP header
Given the request parameters
| accept-language
| it
Given the HTTP header
| accept-language
| fr-ca,fr;q=0.8,en-ca;q=0.5,en;q=0.3
When looking up coordinates 48.13921,11.57328
Then result addresses contain
| ID | city
| 0 | Monaco di Baviera

View File

@ -1,15 +0,0 @@
Feature: Places by osm_type and osm_id Tests
Simple tests for internal server errors and response format.
Scenario: address lookup for existing node, way, relation
When looking up xml places N158845944,W72493656,,R62422,X99,N0
Then the result is valid xml
exactly 3 results are returned
When looking up json places N158845944,W72493656,,R62422,X99,N0
Then the result is valid json
exactly 3 results are returned
Scenario: address lookup for non-existing or invalid node, way, relation
When looking up xml places X99,,N0,nN158845944,ABC,,W9
Then the result is valid xml
exactly 0 results are returned

View File

@ -1,217 +0,0 @@
Feature: API regression tests
Tests error cases reported in tickets.
Scenario: trac #2430
When sending json search query "89 River Avenue, Hoddesdon, Hertfordshire, EN11 0JT"
Then at least 1 result is returned
Scenario: trac #2440
When sending json search query "East Harvard Avenue, Denver"
Then more than 2 results are returned
Scenario: trac #2456
When sending xml search query "Borlänge Kommun"
Then results contain
| ID | place_rank
| 0 | 19
Scenario: trac #2530
When sending json search query "Lange Straße, Bamberg" with address
Then result addresses contain
| ID | town
| 0 | Bamberg
Scenario: trac #2541
When sending json search query "pad, germany"
Then results contain
| ID | class | display_name
| 0 | aeroway | Paderborn/Lippstadt,.*
Scenario: trac #2579
When sending json search query "Johnsons Close, hackbridge" with address
Then result addresses contain
| ID | postcode
| 0 | SM5 2LU
@Fail
Scenario Outline: trac #2586
When sending json search query "<query>" with address
Then result addresses contain
| ID | country_code
| 0 | uk
Examples:
| query
| DL7 0SN
| DL70SN
Scenario: trac #2628 (1)
When sending json search query "Adam Kraft Str" with address
Then result addresses contain
| ID | road
| 0 | Adam-Kraft-Straße
Scenario: trac #2628 (2)
When sending json search query "Maxfeldstr. 5, Nürnberg" with address
Then result addresses contain
| ID | house_number | road | city
| 0 | 5 | Maxfeldstraße | Nürnberg
Scenario: trac #2638
When sending json search query "Nöthnitzer Str. 40, 01187 Dresden" with address
Then result addresses contain
| ID | house_number | road | city
| 0 | 40 | Nöthnitzer Straße | Dresden
Scenario Outline: trac #2667
When sending json search query "<query>" with address
Then result addresses contain
| ID | house_number
| 0 | <number>
Examples:
| number | query
| 16 | 16 Woodpecker Way, Cambourne
| 14906 | 14906, 114 Street Northwest, Edmonton, Alberta, Canada
| 14904 | 14904, 114 Street Northwest, Edmonton, Alberta, Canada
| 15022 | 15022, 114 Street Northwest, Edmonton, Alberta, Canada
| 15024 | 15024, 114 Street Northwest, Edmonton, Alberta, Canada
Scenario: trac #2681
When sending json search query "kirchstraße troisdorf Germany"
Then results contain
| ID | display_name
| 0 | .*, Troisdorf, .*
Scenario: trac #2758
When sending json search query "6а, полуботка, чернигов" with address
Then result addresses contain
| ID | house_number
| 0 | 6а
Scenario: trac #2790
When looking up coordinates 49.0942079697809,8.27565898861822
Then result addresses contain
| ID | road | village | country
| 0 | Daimlerstraße | Jockgrim | Deutschland
Scenario: trac #2794
When sending json search query "4008"
Then results contain
| ID | class | type
| 0 | place | postcode
Scenario: trac #2797
When sending json search query "Philippstr.4, 52349 Düren" with address
Then result addresses contain
| ID | road | town
| 0 | Philippstraße | Düren
Scenario: trac #2830
When sending json search query "207, Boardman Street, S0J 1L0, CA" with address
Then result addresses contain
| ID | house_number | road | postcode | country
| 0 | 207 | Boardman Street | S0J 1L0 | Canada
Scenario: trac #2830
When sending json search query "S0J 1L0,CA"
Then results contain
| ID | class | type | display_name
| 0 | place | postcode | .*, Canada
Scenario: trac #2845
When sending json search query "Leliestraat 31, Zwolle" with address
Then result addresses contain
| ID | city
| 0 | Zwolle
Scenario: trac #2852
When sending json search query "berlinerstrasse, leipzig" with address
Then result addresses contain
| ID | road
| 0 | Berliner Straße
Scenario: trac #2871
When looking up coordinates -33.906895553,150.99609375
Then result addresses contain
| ID | city | country
| 0 | [^0-9]*$ | Australia
Scenario: trac #2981
When sending json search query "Ohmstraße 7, Berlin" with address
Then at least 2 results are returned
And result addresses contain
| house_number | road | state
| 7 | Ohmstraße | Berlin
Scenario: trac #3049
When sending json search query "Soccer City"
Then results contain
| ID | class | type | latlon
| 0 | leisure | stadium | -26.2347261,27.982645 +-50m
Scenario: trac #3130
When sending json search query "Old Way, Frinton"
Then results contain
| ID | class | latlon
| 0 | highway | 51.8324206,1.2447352 +-100m
Scenario Outline: trac #5025
When sending json search query "Kriegsstr <house_nr>, Karlsruhe" with address
Then result addresses contain
| house_number | road
| <house_nr> | Kriegsstraße
Examples:
| house_nr
| 5c
| 25
| 78
| 80
| 99
| 130
| 153
| 196
| 256
| 294
Scenario: trac #5238
Given the request parameters
| bounded | viewbox
| 1 | -1,0,0,-1
When sending json search query "sy"
Then exactly 0 results are returned
Scenario: trac #5274
When sending json search query "Goedestraat 41-BS, Utrecht" with address
Then result addresses contain
| house_number | road | city
| 41-BS | Goedestraat | Utrecht
@poldi-only
Scenario Outline: github #36
When sending json search query "<query>" with address
Then result addresses contain
| ID | road | city
| 0 | Seegasse | .*Wieselburg-Land
Examples:
| query
| Seegasse, Gemeinde Wieselburg-Land
| Seegasse, Wieselburg-Land
| Seegasse, Wieselburg
Scenario: github #190
When looking up place N257363453
Then the results contain
| osm_type | osm_id | latlon
| node | 257363453 | 35.8404121,128.5586643 +-100m
Scenario: trac #5427
Given the request parameters
| countrycodes |
| DE |
When sending json search query "12345" with address
Then result addresses contain
| country_code |
| de |

View File

@ -1,148 +0,0 @@
Feature: Reverse geocoding
Testing the reverse function
# Make sure country is not overwritten by the postcode
Scenario: Country is returned
Given the request parameters
| accept-language
| de
When looking up coordinates 53.9788769,13.0830313
Then result addresses contain
| ID | country
| 0 | Deutschland
Scenario: Boundingbox is returned
Given the request parameters
| format | zoom
| xml | 4
When looking up coordinates 53.9788769,13.0830313
And results contain valid boundingboxes
Scenario: Reverse geocoding for odd interpolated housenumber
Scenario: Reverse geocoding for even interpolated housenumber
@Tiger
Scenario: TIGER house number
Given the request parameters
| addressdetails
| 1
When looking up jsonv2 coordinates 40.6863624710666,-112.060005720023
And exactly 1 result is returned
And result addresses contain
| ID | house_number | road | postcode | country_code
| 0 | 709. | Kings Estate Drive | 84128 | us
And results contain
| osm_type | category | type
| way | place | house
@Tiger
Scenario: No TIGER house number for zoom < 18
Given the request parameters
| addressdetails | zoom
| 1 | 17
When looking up coordinates 40.6863624710666,-112.060005720023
And exactly 1 result is returned
And result addresses contain
| ID | road | postcode | country_code
| 0 | Kings Estate Drive | 84128 | us
And result 0 has attributes osm_id,osm_type
Scenario Outline: Reverse Geocoding with extratags
Given the request parameters
| extratags
| 1
When looking up <format> coordinates 48.86093,2.2978
Then result 0 has attributes extratags
Examples:
| format
| xml
| json
| jsonv2
Scenario Outline: Reverse Geocoding with namedetails
Given the request parameters
| namedetails
| 1
When looking up <format> coordinates 48.86093,2.2978
Then result 0 has attributes namedetails
Examples:
| format
| xml
| json
| jsonv2
Scenario Outline: Reverse Geocoding contains TEXT geometry
Given the request parameters
| polygon_text
| 1
When looking up <format> coordinates 48.86093,2.2978
Then result 0 has attributes <response_attribute>
Examples:
| format | response_attribute
| xml | geotext
| json | geotext
| jsonv2 | geotext
Scenario Outline: Reverse Geocoding contains polygon-as-points geometry
Given the request parameters
| polygon
| 1
When looking up <format> coordinates 48.86093,2.2978
Then result 0 has not attributes <response_attribute>
Examples:
| format | response_attribute
| xml | polygonpoints
| json | polygonpoints
| jsonv2 | polygonpoints
Scenario Outline: Reverse Geocoding contains SVG geometry
Given the request parameters
| polygon_svg
| 1
When looking up <format> coordinates 48.86093,2.2978
Then result 0 has attributes <response_attribute>
Examples:
| format | response_attribute
| xml | geosvg
| json | svg
| jsonv2 | svg
Scenario Outline: Reverse Geocoding contains KML geometry
Given the request parameters
| polygon_kml
| 1
When looking up <format> coordinates 48.86093,2.2978
Then result 0 has attributes <response_attribute>
Examples:
| format | response_attribute
| xml | geokml
| json | geokml
| jsonv2 | geokml
Scenario Outline: Reverse Geocoding contains GEOJSON geometry
Given the request parameters
| polygon_geojson
| 1
When looking up <format> coordinates 48.86093,2.2978
Then result 0 has attributes <response_attribute>
Examples:
| format | response_attribute
| xml | geojson
| json | geojson
| jsonv2 | geojson

View File

@ -1,13 +0,0 @@
Feature: Reverse lookup by ID
Testing reverse geocoding via OSM ID
# see github issue #269
Scenario: Get address of linked places
Given the request parameters
| osm_type | osm_id
| N | 151421301
When sending an API call reverse
Then exactly 1 result is returned
And result addresses contain
| county | state
| Pratt County | Kansas

View File

@ -1,140 +0,0 @@
Feature: Simple Reverse Tests
Simple tests for internal server errors and response format.
These tests should pass on any Nominatim installation.
Scenario Outline: Simple reverse-geocoding
When looking up xml coordinates <lat>,<lon>
Then the result is valid xml
When looking up json coordinates <lat>,<lon>
Then the result is valid json
When looking up jsonv2 coordinates <lat>,<lon>
Then the result is valid json
Examples:
| lat | lon
| 0.0 | 0.0
| 45.3 | 3.5
| -79.34 | 23.5
| 0.23 | -178.555
Scenario Outline: Testing different parameters
Given the request parameters
| <parameter>
| <value>
When sending search query "Manchester"
Then the result is valid html
Given the request parameters
| <parameter>
| <value>
When sending html search query "Manchester"
Then the result is valid html
Given the request parameters
| <parameter>
| <value>
When sending xml search query "Manchester"
Then the result is valid xml
Given the request parameters
| <parameter>
| <value>
When sending json search query "Manchester"
Then the result is valid json
Given the request parameters
| <parameter>
| <value>
When sending jsonv2 search query "Manchester"
Then the result is valid json
Examples:
| parameter | value
| polygon | 1
| polygon | 0
| polygon_text | 1
| polygon_text | 0
| polygon_kml | 1
| polygon_kml | 0
| polygon_geojson | 1
| polygon_geojson | 0
| polygon_svg | 1
| polygon_svg | 0
Scenario Outline: Wrapping of legal jsonp requests
Given the request parameters
| json_callback
| foo
When looking up <format> coordinates 67.3245,0.456
Then the result is valid json
Examples:
| format
| json
| jsonv2
Scenario: Reverse-geocoding without address
Given the request parameters
| addressdetails
| 0
When looking up xml coordinates 36.791966,127.171726
Then the result is valid xml
When looking up json coordinates 36.791966,127.171726
Then the result is valid json
When looking up jsonv2 coordinates 36.791966,127.171726
Then the result is valid json
Scenario: Reverse-geocoding with zoom
Given the request parameters
| zoom
| 10
When looking up xml coordinates 36.791966,127.171726
Then the result is valid xml
When looking up json coordinates 36.791966,127.171726
Then the result is valid json
When looking up jsonv2 coordinates 36.791966,127.171726
Then the result is valid json
Scenario: Missing lon parameter
Given the request parameters
| lat
| 51.51
When sending an API call reverse
Then a HTTP 400 is returned
Scenario: Missing lat parameter
Given the request parameters
| lon
| -79.39114
When sending an API call reverse
Then a HTTP 400 is returned
Scenario: Missing osm_id parameter
Given the request parameters
| osm_type
| N
When sending an API call reverse
Then a HTTP 400 is returned
Scenario: Missing osm_type parameter
Given the request parameters
| osm_id
| 3498564
When sending an API call reverse
Then a HTTP 400 is returned
Scenario Outline: Bad format for lat or lon
Given the request parameters
| lat | lon |
| <lat> | <lon> |
When sending an API call reverse
Then a HTTP 400 is returned
Examples:
| lat | lon
| 48.9660 | 8,4482
| 48,9660 | 8.4482
| 48,9660 | 8,4482
| 48.966.0 | 8.4482
| 48.966 | 8.448.2
| Nan | 8.448
| 48.966 | Nan

View File

@ -1,86 +0,0 @@
Feature: Search queries
Testing correctness of results
Scenario: UK House number search
When sending json search query "27 Thoresby Road, Broxtowe" with address
Then address of result 0 contains
| type | value
| house_number | 27
| road | Thoresby Road
| city | Broxtowe
| state | England
| country | U.*K.*
| country_code | gb
Scenario: House number search for non-street address
Given the request parameters
| accept-language
| en
When sending json search query "4 Pomocnia, Pokrzywnica, Poland" with address
Then address of result 0 contains
| type | value
| house_number | 4
| county | gmina Pokrzywnica
| state | Masovian Voivodeship
| postcode | 06-121
| country | Poland
| country_code | pl
Then address of result 0 does not contain road
Scenario: House number interpolation even
Given the request parameters
| accept-language
| en
When sending json search query "140 rue Don Bosco, Saguenay" with address
Then address of result 0 contains
| type | value
| house_number | 140
| road | [Rr]ue Don Bosco
| city | .*Saguenay
| state | Quebec
| country | Canada
| country_code | ca
Scenario: House number interpolation odd
Given the request parameters
| accept-language
| en
When sending json search query "141 rue Don Bosco, Saguenay" with address
Then address of result 0 contains
| type | value
| house_number | 141
| road | [rR]ue Don Bosco
| city | .*Saguenay
| state | Quebec
| country | Canada
| country_code | ca
@Tiger
Scenario: TIGER house number
When sending json search query "3 West Victory Way, Craig"
Then results contain
| osm_type
| way
@Tiger
Scenario: TIGER house number (road fallback)
When sending json search query "3030 West Victory Way, Craig"
Then results contain
| osm_type
| way
Scenario: Expansion of Illinois
Given the request parameters
| accept-language
| en
When sending json search query "il, us"
Then results contain
| ID | display_name
| 0 | Illinois.*
Scenario: Search with class-type feature
When sending jsonv2 search query "Hotel California"
Then results contain
| place_rank
| 30

View File

@ -1,36 +0,0 @@
Feature: Result order for Geocoding
Testing that importance ordering returns sensible results
Scenario Outline: city order in street search
Given the request parameters
| limit
| 100
When sending json search query "<street>, <city>" with address
Then address of result 0 contains
| type | value
| <type> | <city>
Examples:
| type | city | street
| city | Zürich | Rigistr
| city | Karlsruhe | Sophienstr
| city | München | Karlstr
| city | Praha | Dlouhá
Scenario Outline: use more important city in street search
When sending json search query "<street>, <city>" with address
Then result addresses contain
| ID | country_code
| 0 | <country>
Examples:
| country | city | street
| gb | London | Main St
| gb | Manchester | Central Street
# https://trac.openstreetmap.org/ticket/5094
Scenario: housenumbers are ordered by complete match first
When sending json search query "4 Докукина Москва" with address
Then result addresses contain
| ID | house_number
| 0 | 4

View File

@ -1,315 +0,0 @@
Feature: Search queries
Testing different queries and parameters
Scenario: Simple XML search
When sending xml search query "Schaan"
Then result 0 has attributes place_id,osm_type,osm_id
And result 0 has attributes place_rank,boundingbox
And result 0 has attributes lat,lon,display_name
And result 0 has attributes class,type,importance,icon
And result 0 has not attributes address
And results contain valid boundingboxes
Scenario: Simple JSON search
When sending json search query "Vaduz"
And result 0 has attributes place_id,licence,icon,class,type
And result 0 has attributes osm_type,osm_id,boundingbox
And result 0 has attributes lat,lon,display_name,importance
And result 0 has not attributes address
And results contain valid boundingboxes
Scenario: JSON search with addressdetails
When sending json search query "Montevideo" with address
Then address of result 0 is
| type | value
| city | Montevideo
| state | Montevideo
| country | Uruguay
| country_code | uy
Scenario: XML search with addressdetails
When sending xml search query "Inuvik" with address
Then address of result 0 contains
| type | value
| state | Northwest Territories
| country | Canada
| country_code | ca
Scenario: coordinate search with addressdetails
When sending json search query "51.193058013916,15.5245780944824" with address
Then result addresses contain
| village | country | country_code
| Kraszowice | Polska | pl
Scenario: Address details with unknown class types
When sending json search query "foobar, Essen" with address
Then results contain
| ID | class | type
| 0 | leisure | hackerspace
And result addresses contain
| ID | address29
| 0 | Chaospott
And address of result 0 does not contain leisure,hackerspace
Scenario: Disabling deduplication
When sending json search query "Oxford Street, London"
Then there are no duplicates
Given the request parameters
| dedupe
| 0
When sending json search query "Oxford Street, London"
Then there are duplicates
Scenario: Search with bounded viewbox in right area
Given the request parameters
| bounded | viewbox
| 1 | -87.7,41.9,-87.57,41.85
When sending json search query "restaurant" with address
Then result addresses contain
| ID | city
| 0 | Chicago
Scenario: Search with bounded viewboxlbrt in right area
Given the request parameters
| bounded | viewboxlbrt
| 1 | -87.7,41.85,-87.57,41.9
When sending json search query "restaurant" with address
Then result addresses contain
| ID | city
| 0 | Chicago
Scenario: No POI search with unbounded viewbox
Given the request parameters
| viewbox
| -87.7,41.9,-87.57,41.85
When sending json search query "restaurant"
Then results contain
| display_name
| [^,]*(?i)restaurant.*
Scenario: bounded search remains within viewbox, even with no results
Given the request parameters
| bounded | viewbox
| 1 | 43.5403125,-5.6563282,43.54285,-5.662003
When sending json search query "restaurant"
Then less than 1 result is returned
Scenario: bounded search remains within viewbox with results
Given the request parameters
| bounded | viewbox
| 1 | -5.662003,43.55,-5.6563282,43.5403125
When sending json search query "restaurant"
| lon | lat
| >= -5.662003 | >= 43.5403125
| <= -5.6563282| <= 43.55
Scenario: Prefer results within viewbox
Given the request parameters
| accept-language
| en
When sending json search query "royan" with address
Then result addresses contain
| ID | country
| 0 | France
Given the request parameters
| accept-language | viewbox
| en | 51.94,36.59,51.99,36.56
When sending json search query "royan" with address
Then result addresses contain
| ID | country
| 0 | Iran
Scenario: Overly large limit number for search results
Given the request parameters
| limit
| 1000
When sending json search query "Neustadt"
Then at most 50 results are returned
Scenario: Limit number of search results
Given the request parameters
| limit
| 4
When sending json search query "Neustadt"
Then exactly 4 results are returned
Scenario: Restrict to feature type country
Given the request parameters
| featureType
| country
When sending xml search query "Monaco"
Then results contain
| place_rank
| 4
Scenario: Restrict to feature type state
When sending xml search query "Berlin"
Then results contain
| ID | place_rank
| 0 | 1[56]
Given the request parameters
| featureType
| state
When sending xml search query "Berlin"
Then results contain
| place_rank
| [78]
Scenario: Restrict to feature type city
Given the request parameters
| featureType
| city
When sending xml search query "Monaco"
Then results contain
| place_rank
| 1[56789]
Scenario: Restrict to feature type settlement
When sending json search query "Everest"
Then results contain
| ID | display_name
| 0 | Mount Everest.*
Given the request parameters
| featureType
| settlement
When sending json search query "Everest"
Then results contain
| ID | display_name
| 0 | Everest.*
Scenario Outline: Search with polygon threshold (json)
Given the request parameters
| polygon_geojson | polygon_threshold
| 1 | <th>
When sending json search query "switzerland"
Then at least 1 result is returned
And result 0 has attributes geojson
Examples:
| th
| -1
| 0.0
| 0.5
| 999
Scenario Outline: Search with polygon threshold (xml)
Given the request parameters
| polygon_geojson | polygon_threshold
| 1 | <th>
When sending xml search query "switzerland"
Then at least 1 result is returned
And result 0 has attributes geojson
Examples:
| th
| -1
| 0.0
| 0.5
| 999
Scenario Outline: Search with invalid polygon threshold (xml)
Given the request parameters
| polygon_geojson | polygon_threshold
| 1 | <th>
When sending xml search query "switzerland"
Then a HTTP 400 is returned
Scenario Outline: Search with extratags
Given the request parameters
| extratags
| 1
When sending <format> search query "Hauptstr"
Then result 0 has attributes extratags
And result 1 has attributes extratags
Examples:
| format
| xml
| json
| jsonv2
Scenario Outline: Search with namedetails
Given the request parameters
| namedetails
| 1
When sending <format> search query "Hauptstr"
Then result 0 has attributes namedetails
And result 1 has attributes namedetails
Examples:
| format
| xml
| json
| jsonv2
Scenario Outline: Search result with contains TEXT geometry
Given the request parameters
| polygon_text
| 1
When sending <format> search query "switzerland"
Then result 0 has attributes <response_attribute>
Examples:
| format | response_attribute
| xml | geotext
| json | geotext
| jsonv2 | geotext
Scenario Outline: Search result contains polygon-as-points geometry
Given the request parameters
| polygon
| 1
When sending <format> search query "switzerland"
Then result 0 has attributes <response_attribute>
Examples:
| format | response_attribute
| xml | polygonpoints
| json | polygonpoints
| jsonv2 | polygonpoints
Scenario Outline: Search result contains SVG geometry
Given the request parameters
| polygon_svg
| 1
When sending <format> search query "switzerland"
Then result 0 has attributes <response_attribute>
Examples:
| format | response_attribute
| xml | geosvg
| json | svg
| jsonv2 | svg
Scenario Outline: Search result contains KML geometry
Given the request parameters
| polygon_kml
| 1
When sending <format> search query "switzerland"
Then result 0 has attributes <response_attribute>
Examples:
| format | response_attribute
| xml | geokml
| json | geokml
| jsonv2 | geokml
Scenario Outline: Search result contains GEOJSON geometry
Given the request parameters
| polygon_geojson
| 1
When sending <format> search query "switzerland"
Then result 0 has attributes <response_attribute>
Examples:
| format | response_attribute
| xml | geojson
| json | geojson
| jsonv2 | geojson

View File

@ -1,238 +0,0 @@
Feature: Simple Tests
Simple tests for internal server errors and response format.
These tests should pass on any Nominatim installation.
Scenario Outline: Testing different parameters
Given the request parameters
| <parameter>
| <value>
When sending search query "Manchester"
Then the result is valid html
Given the request parameters
| <parameter>
| <value>
When sending html search query "Manchester"
Then the result is valid html
Given the request parameters
| <parameter>
| <value>
When sending xml search query "Manchester"
Then the result is valid xml
Given the request parameters
| <parameter>
| <value>
When sending json search query "Manchester"
Then the result is valid json
Given the request parameters
| <parameter>
| <value>
When sending jsonv2 search query "Manchester"
Then the result is valid json
Examples:
| parameter | value
| addressdetails | 1
| addressdetails | 0
| polygon | 1
| polygon | 0
| polygon_text | 1
| polygon_text | 0
| polygon_kml | 1
| polygon_kml | 0
| polygon_geojson | 1
| polygon_geojson | 0
| polygon_svg | 1
| polygon_svg | 0
| accept-language | de,en
| countrycodes | uk,ir
| bounded | 1
| bounded | 0
| exclude_place_ids| 385252,1234515
| limit | 1000
| dedupe | 1
| dedupe | 0
| extratags | 1
| extratags | 0
| namedetails | 1
| namedetails | 0
Scenario: Search with invalid output format
Given the request parameters
| format
| fd$#
When sending search query "Berlin"
Then a HTTP 400 is returned
Scenario Outline: Simple Searches
When sending search query "<query>"
Then the result is valid html
When sending html search query "<query>"
Then the result is valid html
When sending xml search query "<query>"
Then the result is valid xml
When sending json search query "<query>"
Then the result is valid json
When sending jsonv2 search query "<query>"
Then the result is valid json
Examples:
| query
| New York, New York
| France
| 12, Main Street, Houston
| München
|
| hotels in nantes
| xywxkrf
| gh; foo()
| %#$@*&l;der#$!
| 234
| 47.4,8.3
Scenario: Empty XML search
When sending xml search query "xnznxvcx"
Then result header contains
| attr | value
| querystring | xnznxvcx
| polygon | false
| more_url | .*format=xml.*q=xnznxvcx.*
Scenario: Empty XML search with special XML characters
When sending xml search query "xfdghn&zxn"xvbyx<vxx>cssdex"
Then result header contains
| attr | value
| querystring | xfdghn&zxn"xvbyx<vxx>cssdex
| polygon | false
| more_url | .*format=xml.*q=xfdghn&zxn"xvbyx<vxx>cssdex.*
Scenario: Empty XML search with viewbox
Given the request parameters
| viewbox
| 12,45.13,77,33
When sending xml search query "xnznxvcx"
Then result header contains
| attr | value
| querystring | xnznxvcx
| polygon | false
| viewbox | 12,45.13,77,33
Scenario: Empty XML search with viewboxlbrt
Given the request parameters
| viewboxlbrt
| 12,34.13,77,45
When sending xml search query "xnznxvcx"
Then result header contains
| attr | value
| querystring | xnznxvcx
| polygon | false
| viewbox | 12,45.13,77,33
Scenario: Empty XML search with viewboxlbrt and viewbox
Given the request parameters
| viewbox | viewboxblrt
| 12,45.13,77,33 | 1,2,3,4
When sending xml search query "pub"
Then result header contains
| attr | value
| querystring | pub
| polygon | false
| viewbox | 12,45.13,77,33
Scenario Outline: Empty XML search with polygon values
Given the request parameters
| polygon
| <polyval>
When sending xml search query "xnznxvcx"
Then result header contains
| attr | value
| polygon | <result>
Examples:
| result | polyval
| false | 0
| true | 1
| true | True
| true | true
| true | false
| true | FALSE
| true | yes
| true | no
| true | '; delete from foobar; select '
Scenario: Empty XML search with exluded place ids
Given the request parameters
| exclude_place_ids
| 123,76,342565
When sending xml search query "jghrleoxsbwjer"
Then result header contains
| attr | value
| exclude_place_ids | 123,76,342565
Scenario: Empty XML search with bad exluded place ids
Given the request parameters
| exclude_place_ids
| ,
When sending xml search query "jghrleoxsbwjer"
Then result header has no attribute exclude_place_ids
Scenario Outline: Wrapping of legal jsonp search requests
Given the request parameters
| json_callback
| <data>
When sending json search query "Tokyo"
Then there is a json wrapper "<data>"
Examples:
| data
| foo
| FOO
| __world
| $me
| m1[4]
| d_r[$d]
Scenario Outline: Wrapping of illegal jsonp search requests
Given the request parameters
| json_callback
| <data>
When sending json search query "Tokyo"
Then a HTTP 400 is returned
Examples:
| data
| 1asd
| bar(foo)
| XXX['bad']
| foo; evil
Scenario Outline: Ignore jsonp parameter for anything but json
Given the request parameters
| json_callback
| 234
When sending json search query "Malibu"
Then a HTTP 400 is returned
Given the request parameters
| json_callback
| 234
When sending xml search query "Malibu"
Then the result is valid xml
Given the request parameters
| json_callback
| 234
When sending html search query "Malibu"
Then the result is valid html
Scenario: Empty JSON search
When sending json search query "YHlERzzx"
Then exactly 0 results are returned
Scenario: Empty JSONv2 search
When sending jsonv2 search query "Flubb XdfESSaZx"
Then exactly 0 results are returned
Scenario: Search for non-existing coordinates
When sending json search query "-21.0,-33.0"
Then exactly 0 results are returned

View File

@ -1,41 +0,0 @@
Feature: Structured search queries
Testing correctness of results with
structured queries
Scenario: Country only
When sending json structured query with address
| country
| Canada
Then address of result 0 is
| type | value
| country | Canada
| country_code | ca
Scenario: Postcode only
When sending json structured query with address
| postalcode
| 22547
Then at least 1 result is returned
And results contain
| type
| post(al_)?code
And result addresses contain
| postcode
| 22547
Scenario: Street, postcode and country
When sending xml structured query with address
| street | postalcode | country
| Old Palace Road | GU2 7UP | United Kingdom
Then at least 1 result is returned
Then result header contains
| attr | value
| querystring | Old Palace Road, GU2 7UP, United Kingdom
Scenario: gihub #176
When sending json structured query with address
| city
| Washington
Then at least 1 result is returned

View File

@ -1,327 +0,0 @@
@DB
Feature: Import of address interpolations
Tests that interpolated addresses are added correctly
Scenario: Simple even interpolation line with two points
Given the place nodes
| osm_id | osm_type | class | type | housenumber | geometry
| 1 | N | place | house | 2 | 1 1
| 2 | N | place | house | 6 | 1 1.001
And the place ways
| osm_id | osm_type | class | type | housenumber | geometry
| 1 | W | place | houses | even | 1 1, 1 1.001
And the ways
| id | nodes
| 1 | 1,2
When importing
Then way 1 expands to lines
| startnumber | endnumber | geometry
| 2 | 6 | 1 1, 1 1.001
Scenario: Backwards even two point interpolation line
Given the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 2 | 1 1
| 2 | place | house | 6 | 1 1.001
And the place ways
| osm_id | class | type | housenumber | geometry
| 1 | place | houses | even | 1 1.001, 1 1
And the ways
| id | nodes
| 1 | 2,1
When importing
Then way 1 expands to lines
| startnumber | endnumber | geometry
| 2 | 6 | 1 1, 1 1.001
Scenario: Simple odd two point interpolation
Given the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 1 | 1 1
| 2 | place | house | 11 | 1 1.001
And the place ways
| osm_id | class | type | housenumber | geometry
| 1 | place | houses | odd | 1 1, 1 1.001
And the ways
| id | nodes
| 1 | 1,2
When importing
Then way 1 expands to lines
| startnumber | endnumber | geometry
| 1 | 11 | 1 1, 1 1.001
Scenario: Simple all two point interpolation
Given the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 1 | 1 1
| 2 | place | house | 3 | 1 1.001
And the place ways
| osm_id | class | type | housenumber | geometry
| 1 | place | houses | all | 1 1, 1 1.001
And the ways
| id | nodes
| 1 | 1,2
When importing
Then way 1 expands to lines
| startnumber | endnumber | geometry
| 1 | 3 | 1 1, 1 1.001
Scenario: Even two point interpolation line with intermediate empty node
Given the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 2 | 1 1
| 2 | place | house | 10 | 1.001 1.001
And the place ways
| osm_id | class | type | housenumber | geometry
| 1 | place | houses | even | 1 1, 1 1.001, 1.001 1.001
And the ways
| id | nodes
| 1 | 1,3,2
When importing
Then way 1 expands to lines
| startnumber | endnumber | geometry
| 2 | 10 | 1 1, 1 1.001, 1.001 1.001
Scenario: Even two point interpolation line with intermediate duplicated empty node
Given the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 2 | 1 1
| 2 | place | house | 10 | 1.001 1.001
And the place ways
| osm_id | class | type | housenumber | geometry
| 1 | place | houses | even | 1 1, 1 1.001, 1.001 1.001
And the ways
| id | nodes
| 1 | 1,3,3,2
When importing
Then way 1 expands to lines
| startnumber | endnumber | geometry
| 2 | 10 | 1 1, 1 1.001, 1.001 1.001
Scenario: Simple even three point interpolation line
Given the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 2 | 1 1
| 2 | place | house | 14 | 1.001 1.001
| 3 | place | house | 10 | 1 1.001
And the place ways
| osm_id | class | type | housenumber | geometry
| 1 | place | houses | even | 1 1, 1 1.001, 1.001 1.001
And the ways
| id | nodes
| 1 | 1,3,2
When importing
Then way 1 expands to lines
| startnumber | endnumber | geometry
| 2 | 10 | 1 1, 1 1.001
| 10 | 14 | 1 1.001, 1.001 1.001
Scenario: Simple even four point interpolation line
Given the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 2 | 1 1
| 2 | place | house | 14 | 1.001 1.001
| 3 | place | house | 10 | 1 1.001
| 4 | place | house | 18 | 1.001 1.002
And the place ways
| osm_id | class | type | housenumber | geometry
| 1 | place | houses | even | 1 1, 1 1.001, 1.001 1.001, 1.001 1.002
And the ways
| id | nodes
| 1 | 1,3,2,4
When importing
Then way 1 expands to lines
| startnumber | endnumber | geometry
| 2 | 10 | 1 1, 1 1.001
| 10 | 14 | 1 1.001, 1.001 1.001
| 14 | 18 | 1.001 1.001, 1.001 1.002
Scenario: Reverse simple even three point interpolation line
Given the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 2 | 1 1
| 2 | place | house | 14 | 1.001 1.001
| 3 | place | house | 10 | 1 1.001
And the place ways
| osm_id | class | type | housenumber | geometry
| 1 | place | houses | even | 1.001 1.001, 1 1.001, 1 1
And the ways
| id | nodes
| 1 | 2,3,1
When importing
Then way 1 expands to lines
| startnumber | endnumber | geometry
| 2 | 10 | 1 1, 1 1.001
| 10 | 14 | 1 1.001, 1.001 1.001
Scenario: Even three point interpolation line with odd center point
Given the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 2 | 1 1
| 2 | place | house | 8 | 1.001 1.001
| 3 | place | house | 7 | 1 1.001
And the place ways
| osm_id | class | type | housenumber | geometry
| 1 | place | houses | even | 1 1, 1 1.001, 1.001 1.001
And the ways
| id | nodes
| 1 | 1,3,2
When importing
Then way 1 expands to lines
| startnumber | endnumber | geometry
| 2 | 7 | 1 1, 1 1.001
| 7 | 8 | 1 1.001, 1.001 1.001
Scenario: Interpolation line with self-intersecting way
Given the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 2 | 0 0
| 2 | place | house | 6 | 0 0.001
| 3 | place | house | 10 | 0 0.002
And the place ways
| osm_id | class | type | housenumber | geometry
| 1 | place | houses | even | 0 0, 0 0.001, 0 0.002, 0 0.001
And the ways
| id | nodes
| 1 | 1,2,3,2
When importing
Then way 1 expands to lines
| startnumber | endnumber | geometry
| 2 | 6 | 0 0, 0 0.001
| 6 | 10 | 0 0.001, 0 0.002
| 6 | 10 | 0 0.001, 0 0.002
Scenario: Interpolation line with self-intersecting way II
Given the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 2 | 0 0
| 2 | place | house | 6 | 0 0.001
And the place ways
| osm_id | class | type | housenumber | geometry
| 1 | place | houses | even | 0 0, 0 0.001, 0 0.002, 0 0.001
And the ways
| id | nodes
| 1 | 1,2,3,2
When importing
Then way 1 expands to lines
| startnumber | endnumber | geometry
| 2 | 6 | 0 0, 0 0.001
Scenario: addr:street on interpolation way
Given the scene parallel-road
And the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 2 | :n-middle-w
| 2 | place | house | 6 | :n-middle-e
| 3 | place | house | 12 | :n-middle-w
| 4 | place | house | 16 | :n-middle-e
And the place ways
| osm_id | class | type | housenumber | street | geometry
| 10 | place | houses | even | | :w-middle
| 11 | place | houses | even | Cloud Street | :w-middle
And the place ways
| osm_id | class | type | name | geometry
| 2 | highway | tertiary | 'name' : 'Sun Way' | :w-north
| 3 | highway | tertiary | 'name' : 'Cloud Street' | :w-south
And the ways
| id | nodes
| 10 | 1,100,101,102,2
| 11 | 3,200,201,202,4
When importing
Then table placex contains
| object | parent_place_id
| N1 | W2
| N2 | W2
| N3 | W3
| N4 | W3
Then table location_property_osmline contains
| object | parent_place_id | startnumber | endnumber
| W10 | W2 | 2 | 6
| W11 | W3 | 12 | 16
When sending query "16 Cloud Street"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 4
When sending query "14 Cloud Street"
Then results contain
| ID | osm_type | osm_id
| 0 | W | 11
When sending query "18 Cloud Street"
Then results contain
| ID | osm_type | osm_id
| 0 | W | 3
Scenario: addr:street on housenumber way
Given the scene parallel-road
And the place nodes
| osm_id | class | type | housenumber | street | geometry
| 1 | place | house | 2 | | :n-middle-w
| 2 | place | house | 6 | | :n-middle-e
| 3 | place | house | 12 | Cloud Street | :n-middle-w
| 4 | place | house | 16 | Cloud Street | :n-middle-e
And the place ways
| osm_id | class | type | housenumber | geometry
| 10 | place | houses | even | :w-middle
| 11 | place | houses | even | :w-middle
And the place ways
| osm_id | class | type | name | geometry
| 2 | highway | tertiary | 'name' : 'Sun Way' | :w-north
| 3 | highway | tertiary | 'name' : 'Cloud Street' | :w-south
And the ways
| id | nodes
| 10 | 1,100,101,102,2
| 11 | 3,200,201,202,4
When importing
Then table placex contains
| object | parent_place_id
| N1 | W2
| N2 | W2
| N3 | W3
| N4 | W3
And table location_property_osmline contains
| object | parent_place_id | startnumber | endnumber
| W10 | W2 | 2 | 6
| W11 | W3 | 12 | 16
When sending query "16 Cloud Street"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 4
When sending query "14 Cloud Street"
Then results contain
| ID | osm_type | osm_id
| 0 | W | 11
Scenario: Geometry of points and way don't match (github #253)
Given the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 10 | 144.9632341 -37.76163
| 2 | place | house | 6 | 144.9630541 -37.7628174
| 3 | shop | supermarket | 2 | 144.9629794 -37.7630755
And the place ways
| osm_id | class | type | housenumber | geometry
| 1 | place | houses | even | 144.9632341 -37.76163,144.9630541 -37.7628172,144.9629794 -37.7630755
And the ways
| id | nodes
| 1 | 1,2,3
When importing
Then way 1 expands to lines
| startnumber | endnumber | geometry
| 2 | 6 | 144.9629794 -37.7630755, 144.9630541 -37.7628174
| 6 | 10 | 144.9630541 -37.7628174, 144.9632341 -37.76163
Scenario: Place with missing address information
Given the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 23 | 0.0001 0.0001
| 2 | amenity | school | | 0.0001 0.0002
| 3 | place | house | 29 | 0.0001 0.0004
And the place ways
| osm_id | class | type | housenumber | geometry
| 1 | place | houses | odd | 0.0001 0.0001,0.0001 0.0002,0.0001 0.0004
And the ways
| id | nodes
| 1 | 1,2,3
When importing
Then way 1 expands to lines
| startnumber | endnumber | geometry
| 23 | 29 | 0.0001 0.0001, 0.0001 0.0002, 0.0001 0.0004

View File

@ -1,112 +0,0 @@
@DB
Feature: Linking of places
Tests for correctly determining linked places
Scenario: Only address-describing places can be linked
Given the scene way-area-with-center
And the place areas
| osm_type | osm_id | class | type | name | geometry
| R | 13 | landuse | forest | Garbo | :area
And the place nodes
| osm_id | class | type | name | geometry
| 256 | natural | peak | Garbo | :inner-C
When importing
Then table placex contains
| object | linked_place_id
| R13 | None
| N256 | None
Scenario: Waterways are linked when in waterway relations
Given the scene split-road
And the place ways
| osm_type | osm_id | class | type | name | geometry
| W | 1 | waterway | river | Rhein | :w-2
| W | 2 | waterway | river | Rhein | :w-3
| R | 13 | waterway | river | Rhein | :w-1 + :w-2 + :w-3
| R | 23 | waterway | river | Limmat| :w-4a
And the relations
| id | members | tags
| 13 | R23:tributary,W1,W2:main_stream | 'type' : 'waterway'
When importing
Then table placex contains
| object | linked_place_id
| W1 | R13
| W2 | R13
| R13 | None
| R23 | None
When sending query "rhein"
Then results contain
| osm_type
| R
Scenario: Relations are not linked when in waterway relations
Given the scene split-road
And the place ways
| osm_type | osm_id | class | type | name | geometry
| W | 1 | waterway | river | Rhein | :w-2
| W | 2 | waterway | river | Rhein | :w-3
| R | 1 | waterway | river | Rhein | :w-1 + :w-2 + :w-3
| R | 2 | waterway | river | Limmat| :w-4a
And the relations
| id | members | tags
| 1 | R2 | 'type' : 'waterway'
When importing
Then table placex contains
| object | linked_place_id
| W1 | None
| W2 | None
| R1 | None
| R2 | None
Scenario: Empty waterway relations are handled correctly
Given the scene split-road
And the place ways
| osm_type | osm_id | class | type | name | geometry
| R | 1 | waterway | river | Rhein | :w-1 + :w-2 + :w-3
And the relations
| id | members | tags
| 1 | | 'type' : 'waterway'
When importing
Then table placex contains
| object | linked_place_id
| R1 | None
Scenario: Waterways are not linked when waterway types don't match
Given the scene split-road
And the place ways
| osm_type | osm_id | class | type | name | geometry
| W | 1 | waterway | drain | Rhein | :w-2
| R | 1 | waterway | river | Rhein | :w-1 + :w-2 + :w-3
And the relations
| id | members | tags
| 1 | N23,N34,W1,R45 | 'type' : 'multipolygon'
When importing
Then table placex contains
| object | linked_place_id
| W1 | None
| R1 | None
When sending query "rhein"
Then results contain
| ID | osm_type
| 0 | R
| 1 | W
Scenario: Side streams are linked only when they have the same name
Given the scene split-road
And the place ways
| osm_type | osm_id | class | type | name | geometry
| W | 1 | waterway | river | Rhein2 | :w-2
| W | 2 | waterway | river | Rhein | :w-3
| R | 1 | waterway | river | Rhein | :w-1 + :w-2 + :w-3
And the relations
| id | members | tags
| 1 | W1:side_stream,W2:side_stream | 'type' : 'waterway'
When importing
Then table placex contains
| object | linked_place_id
| W1 | None
| W2 | R1
When sending query "rhein2"
Then results contain
| osm_type
| W

View File

@ -1,193 +0,0 @@
@DB
Feature: Import and search of names
Tests all naming related issues: normalisation,
abbreviations, internationalisation, etc.
Scenario: Case-insensitivity of search
Given the place nodes
| osm_id | class | type | name
| 1 | place | locality | 'name' : 'FooBar'
When importing
Then table placex contains
| object | class | type | name
| N1 | place | locality | 'name' : 'FooBar'
When sending query "FooBar"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 1
When sending query "foobar"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 1
When sending query "fOObar"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 1
When sending query "FOOBAR"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 1
Scenario: Multiple spaces in name
Given the place nodes
| osm_id | class | type | name
| 1 | place | locality | 'name' : 'one two three'
When importing
When sending query "one two three"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 1
When sending query "one two three"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 1
When sending query "one two three"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 1
When sending query " one two three"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 1
Scenario: Special characters in name
Given the place nodes
| osm_id | class | type | name
| 1 | place | locality | 'name' : 'Jim-Knopf-Str'
| 2 | place | locality | 'name' : 'Smith/Weston'
| 3 | place | locality | 'name' : 'space mountain'
| 4 | place | locality | 'name' : 'space'
| 5 | place | locality | 'name' : 'mountain'
When importing
When sending query "Jim-Knopf-Str"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 1
When sending query "Jim Knopf-Str"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 1
When sending query "Jim Knopf Str"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 1
When sending query "Jim/Knopf-Str"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 1
When sending query "Jim-Knopfstr"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 1
When sending query "Smith/Weston"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 2
When sending query "Smith Weston"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 2
When sending query "Smith-Weston"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 2
When sending query "space mountain"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 3
When sending query "space-mountain"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 3
When sending query "space/mountain"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 3
When sending query "space\mountain"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 3
When sending query "space(mountain)"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 3
Scenario: No copying name tag if only one name
Given the place nodes
| osm_id | class | type | name | geometry
| 1 | place | locality | 'name' : 'german' | country:de
When importing
Then table placex contains
| object | calculated_country_code |
| N1 | de
And table placex contains as names for N1
| object | k | v
| N1 | name | german
Scenario: Copying name tag to default language if it does not exist
Given the place nodes
| osm_id | class | type | name | geometry
| 1 | place | locality | 'name' : 'german', 'name:fi' : 'finnish' | country:de
When importing
Then table placex contains
| object | calculated_country_code |
| N1 | de
And table placex contains as names for N1
| k | v
| name | german
| name:fi | finnish
| name:de | german
Scenario: Copying default language name tag to name if it does not exist
Given the place nodes
| osm_id | class | type | name | geometry
| 1 | place | locality | 'name:de' : 'german', 'name:fi' : 'finnish' | country:de
When importing
Then table placex contains
| object | calculated_country_code |
| N1 | de
And table placex contains as names for N1
| k | v
| name | german
| name:fi | finnish
| name:de | german
Scenario: Do not overwrite default language with name tag
Given the place nodes
| osm_id | class | type | name | geometry
| 1 | place | locality | 'name' : 'german', 'name:fi' : 'finnish', 'name:de' : 'local' | country:de
When importing
Then table placex contains
| object | calculated_country_code |
| N1 | de
And table placex contains as names for N1
| k | v
| name | german
| name:fi | finnish
| name:de | local
Scenario: Landuse with name are found
Given the place areas
| osm_type | osm_id | class | type | name | geometry
| R | 1 | natural | meadow | 'name' : 'landuse1' | (0 0, 1 0, 1 1, 0 1, 0 0)
| R | 2 | landuse | industrial | 'name' : 'landuse2' | (0 0, -1 0, -1 -1, 0 -1, 0 0)
When importing
When sending query "landuse1"
Then results contain
| ID | osm_type | osm_id
| 0 | R | 1
When sending query "landuse2"
Then results contain
| ID | osm_type | osm_id
| 0 | R | 2
Scenario: Postcode boundaries without ref
Given the place areas
| osm_type | osm_id | class | type | postcode | geometry
| R | 1 | boundary | postal_code | 12345 | (0 0, 1 0, 1 1, 0 1, 0 0)
When importing
When sending query "12345"
Then results contain
| ID | osm_type | osm_id
| 0 | R | 1

View File

@ -1,458 +0,0 @@
@DB
Feature: Parenting of objects
Tests that the correct parent is choosen
Scenario: Address inherits postcode from its street unless it has a postcode
Given the scene roads-with-pois
And the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 4 | :p-N1
And the place nodes
| osm_id | class | type | housenumber | postcode | geometry
| 2 | place | house | 5 | 99999 | :p-N1
And the place ways
| osm_id | class | type | name | postcode | geometry
| 1 | highway | residential | galoo | 12345 | :w-north
When importing
Then table placex contains
| object | parent_place_id
| N1 | W1
| N2 | W1
When sending query "4 galoo"
Then results contain
| ID | osm_type | osm_id | langaddress
| 0 | N | 1 | 4, galoo, 12345
When sending query "5 galoo"
Then results contain
| ID | osm_type | osm_id | langaddress
| 0 | N | 2 | 5, galoo, 99999
Scenario: Address without tags, closest street
Given the scene roads-with-pois
And the place nodes
| osm_id | class | type | geometry
| 1 | place | house | :p-N1
| 2 | place | house | :p-N2
| 3 | place | house | :p-S1
| 4 | place | house | :p-S2
And the named place ways
| osm_id | class | type | geometry
| 1 | highway | residential | :w-north
| 2 | highway | residential | :w-south
When importing
Then table placex contains
| object | parent_place_id
| N1 | W1
| N2 | W1
| N3 | W2
| N4 | W2
Scenario: Address without tags avoids unnamed streets
Given the scene roads-with-pois
And the place nodes
| osm_id | class | type | geometry
| 1 | place | house | :p-N1
| 2 | place | house | :p-N2
| 3 | place | house | :p-S1
| 4 | place | house | :p-S2
And the place ways
| osm_id | class | type | geometry
| 1 | highway | residential | :w-north
And the named place ways
| osm_id | class | type | geometry
| 2 | highway | residential | :w-south
When importing
Then table placex contains
| object | parent_place_id
| N1 | W2
| N2 | W2
| N3 | W2
| N4 | W2
Scenario: addr:street tag parents to appropriately named street
Given the scene roads-with-pois
And the place nodes
| osm_id | class | type | street| geometry
| 1 | place | house | south | :p-N1
| 2 | place | house | north | :p-N2
| 3 | place | house | south | :p-S1
| 4 | place | house | north | :p-S2
And the place ways
| osm_id | class | type | name | geometry
| 1 | highway | residential | north | :w-north
| 2 | highway | residential | south | :w-south
When importing
Then table placex contains
| object | parent_place_id
| N1 | W2
| N2 | W1
| N3 | W2
| N4 | W1
Scenario: addr:street tag parents to next named street
Given the scene roads-with-pois
And the place nodes
| osm_id | class | type | street | geometry
| 1 | place | house | abcdef | :p-N1
| 2 | place | house | abcdef | :p-N2
| 3 | place | house | abcdef | :p-S1
| 4 | place | house | abcdef | :p-S2
And the place ways
| osm_id | class | type | name | geometry
| 1 | highway | residential | abcdef | :w-north
| 2 | highway | residential | abcdef | :w-south
When importing
Then table placex contains
| object | parent_place_id
| N1 | W1
| N2 | W1
| N3 | W2
| N4 | W2
Scenario: addr:street tag without appropriately named street
Given the scene roads-with-pois
And the place nodes
| osm_id | class | type | street | geometry
| 1 | place | house | abcdef | :p-N1
| 2 | place | house | abcdef | :p-N2
| 3 | place | house | abcdef | :p-S1
| 4 | place | house | abcdef | :p-S2
And the place ways
| osm_id | class | type | name | geometry
| 1 | highway | residential | abcde | :w-north
| 2 | highway | residential | abcde | :w-south
When importing
Then table placex contains
| object | parent_place_id
| N1 | W1
| N2 | W1
| N3 | W2
| N4 | W2
Scenario: addr:place address
Given the scene road-with-alley
And the place nodes
| osm_id | class | type | addr_place | geometry
| 1 | place | house | myhamlet | :n-alley
And the place nodes
| osm_id | class | type | name | geometry
| 2 | place | hamlet | myhamlet | :n-main-west
And the place ways
| osm_id | class | type | name | geometry
| 1 | highway | residential | myhamlet | :w-main
When importing
Then table placex contains
| object | parent_place_id
| N1 | N2
Scenario: addr:street is preferred over addr:place
Given the scene road-with-alley
And the place nodes
| osm_id | class | type | addr_place | street | geometry
| 1 | place | house | myhamlet | mystreet| :n-alley
And the place nodes
| osm_id | class | type | name | geometry
| 2 | place | hamlet | myhamlet | :n-main-west
And the place ways
| osm_id | class | type | name | geometry
| 1 | highway | residential | mystreet | :w-main
When importing
Then table placex contains
| object | parent_place_id
| N1 | W1
Scenario: Untagged address in simple associated street relation
Given the scene road-with-alley
And the place nodes
| osm_id | class | type | geometry
| 1 | place | house | :n-alley
| 2 | place | house | :n-corner
| 3 | place | house | :n-main-west
And the place ways
| osm_id | class | type | name | geometry
| 1 | highway | residential | foo | :w-main
| 2 | highway | service | bar | :w-alley
And the relations
| id | members | tags
| 1 | W1:street,N1,N2,N3 | 'type' : 'associatedStreet'
When importing
Then table placex contains
| object | parent_place_id
| N1 | W1
| N2 | W1
| N3 | W1
Scenario: Avoid unnamed streets in simple associated street relation
Given the scene road-with-alley
And the place nodes
| osm_id | class | type | geometry
| 1 | place | house | :n-alley
| 2 | place | house | :n-corner
| 3 | place | house | :n-main-west
And the named place ways
| osm_id | class | type | geometry
| 1 | highway | residential | :w-main
And the place ways
| osm_id | class | type | geometry
| 2 | highway | residential | :w-alley
And the relations
| id | members | tags
| 1 | N1,N2,N3,W2:street,W1:street | 'type' : 'associatedStreet'
When importing
Then table placex contains
| object | parent_place_id
| N1 | W1
| N2 | W1
| N3 | W1
### Scenario 10
Scenario: Associated street relation overrides addr:street
Given the scene road-with-alley
And the place nodes
| osm_id | class | type | street | geometry
| 1 | place | house | bar | :n-alley
And the place ways
| osm_id | class | type | name | geometry
| 1 | highway | residential | foo | :w-main
| 2 | highway | residential | bar | :w-alley
And the relations
| id | members | tags
| 1 | W1:street,N1,N2,N3 | 'type' : 'associatedStreet'
When importing
Then table placex contains
| object | parent_place_id
| N1 | W1
Scenario: Building without tags, closest street from center point
Given the scene building-on-street-corner
And the named place ways
| osm_id | class | type | geometry
| 1 | building | yes | :w-building
| 2 | highway | primary | :w-WE
| 3 | highway | residential | :w-NS
When importing
Then table placex contains
| object | parent_place_id
| W1 | W3
Scenario: Building with addr:street tags
Given the scene building-on-street-corner
And the named place ways
| osm_id | class | type | street | geometry
| 1 | building | yes | bar | :w-building
And the place ways
| osm_id | class | type | name | geometry
| 2 | highway | primary | bar | :w-WE
| 3 | highway | residential | foo | :w-NS
When importing
Then table placex contains
| object | parent_place_id
| W1 | W2
Scenario: Building with addr:place tags
Given the scene building-on-street-corner
And the place nodes
| osm_id | class | type | name | geometry
| 1 | place | village | bar | :n-outer
And the named place ways
| osm_id | class | type | addr_place | geometry
| 1 | building | yes | bar | :w-building
And the place ways
| osm_id | class | type | name | geometry
| 2 | highway | primary | bar | :w-WE
| 3 | highway | residential | foo | :w-NS
When importing
Then table placex contains
| object | parent_place_id
| W1 | N1
Scenario: Building in associated street relation
Given the scene building-on-street-corner
And the named place ways
| osm_id | class | type | geometry
| 1 | building | yes | :w-building
And the place ways
| osm_id | class | type | name | geometry
| 2 | highway | primary | bar | :w-WE
| 3 | highway | residential | foo | :w-NS
And the relations
| id | members | tags
| 1 | W1:house,W2:street | 'type' : 'associatedStreet'
When importing
Then table placex contains
| object | parent_place_id
| W1 | W2
Scenario: Building in associated street relation overrides addr:street
Given the scene building-on-street-corner
And the named place ways
| osm_id | class | type | street | geometry
| 1 | building | yes | foo | :w-building
And the place ways
| osm_id | class | type | name | geometry
| 2 | highway | primary | bar | :w-WE
| 3 | highway | residential | foo | :w-NS
And the relations
| id | members | tags
| 1 | W1:house,W2:street | 'type' : 'associatedStreet'
When importing
Then table placex contains
| object | parent_place_id
| W1 | W2
Scenario: Wrong member in associated street relation is ignored
Given the scene building-on-street-corner
And the named place nodes
| osm_id | class | type | geometry
| 1 | place | house | :n-outer
And the named place ways
| osm_id | class | type | street | geometry
| 1 | building | yes | foo | :w-building
And the place ways
| osm_id | class | type | name | geometry
| 2 | highway | primary | bar | :w-WE
| 3 | highway | residential | foo | :w-NS
And the relations
| id | members | tags
| 1 | N1:house,W1:street,W3:street | 'type' : 'associatedStreet'
When importing
Then table placex contains
| object | parent_place_id
| N1 | W3
Scenario: POIs in building inherit address
Given the scene building-on-street-corner
And the named place nodes
| osm_id | class | type | geometry
| 1 | amenity | bank | :n-inner
| 2 | shop | bakery | :n-edge-NS
| 3 | shop | supermarket| :n-edge-WE
And the place ways
| osm_id | class | type | street | addr_place | housenumber | geometry
| 1 | building | yes | foo | nowhere | 3 | :w-building
And the place ways
| osm_id | class | type | name | geometry
| 2 | highway | primary | bar | :w-WE
| 3 | highway | residential | foo | :w-NS
When importing
Then table placex contains
| object | parent_place_id | street | addr_place | housenumber
| W1 | W3 | foo | nowhere | 3
| N1 | W3 | foo | nowhere | 3
| N2 | W3 | foo | nowhere | 3
| N3 | W3 | foo | nowhere | 3
Scenario: POIs don't inherit from streets
Given the scene building-on-street-corner
And the named place nodes
| osm_id | class | type | geometry
| 1 | amenity | bank | :n-inner
And the place ways
| osm_id | class | type | street | addr_place | housenumber | geometry
| 1 | highway | path | foo | nowhere | 3 | :w-building
And the place ways
| osm_id | class | type | name | geometry
| 3 | highway | residential | foo | :w-NS
When importing
Then table placex contains
| object | parent_place_id | street | addr_place | housenumber
| N1 | W3 | None | None | None
Scenario: POIs with own address do not inherit building address
Given the scene building-on-street-corner
And the named place nodes
| osm_id | class | type | street | geometry
| 1 | amenity | bank | bar | :n-inner
And the named place nodes
| osm_id | class | type | housenumber | geometry
| 2 | shop | bakery | 4 | :n-edge-NS
And the named place nodes
| osm_id | class | type | addr_place | geometry
| 3 | shop | supermarket| nowhere | :n-edge-WE
And the place nodes
| osm_id | class | type | name | geometry
| 4 | place | isolated_dwelling | theplace | :n-outer
And the place ways
| osm_id | class | type | addr_place | housenumber | geometry
| 1 | building | yes | theplace | 3 | :w-building
And the place ways
| osm_id | class | type | name | geometry
| 2 | highway | primary | bar | :w-WE
| 3 | highway | residential | foo | :w-NS
When importing
Then table placex contains
| object | parent_place_id | street | addr_place | housenumber
| W1 | N4 | None | theplace | 3
| N1 | W2 | bar | None | None
| N2 | W3 | None | None | 4
| N3 | W2 | None | nowhere | None
### Scenario 20
Scenario: POIs parent a road if they are attached to it
Given the scene points-on-roads
And the named place nodes
| osm_id | class | type | street | geometry
| 1 | highway | bus_stop | North St | :n-SE
| 2 | highway | bus_stop | South St | :n-NW
| 3 | highway | bus_stop | North St | :n-S-unglued
| 4 | highway | bus_stop | South St | :n-N-unglued
And the place ways
| osm_id | class | type | name | geometry
| 1 | highway | secondary | North St | :w-north
| 2 | highway | unclassified | South St | :w-south
And the ways
| id | nodes
| 1 | 100,101,2,103,104
| 2 | 200,201,1,202,203
When importing
Then table placex contains
| object | parent_place_id
| N1 | W1
| N2 | W2
| N3 | W1
| N4 | W2
Scenario: POIs do not parent non-roads they are attached to
Given the scene points-on-roads
And the named place nodes
| osm_id | class | type | street | geometry
| 1 | highway | bus_stop | North St | :n-SE
| 2 | highway | bus_stop | South St | :n-NW
And the place ways
| osm_id | class | type | name | geometry
| 1 | landuse | residential | North St | :w-north
| 2 | waterway| river | South St | :w-south
And the ways
| id | nodes
| 1 | 100,101,2,103,104
| 2 | 200,201,1,202,203
When importing
Then table placex contains
| object | parent_place_id
| N1 | 0
| N2 | 0
Scenario: POIs on building outlines inherit associated street relation
Given the scene building-on-street-corner
And the named place nodes
| osm_id | class | type | geometry
| 1 | place | house | :n-edge-NS
And the named place ways
| osm_id | class | type | geometry
| 1 | building | yes | :w-building
And the place ways
| osm_id | class | type | name | geometry
| 2 | highway | primary | bar | :w-WE
| 3 | highway | residential | foo | :w-NS
And the relations
| id | members | tags
| 1 | W1:house,W2:street | 'type' : 'associatedStreet'
And the ways
| id | nodes
| 1 | 100,1,101,102,100
When importing
Then table placex contains
| object | parent_place_id
| N1 | W2

View File

@ -1,318 +0,0 @@
@DB
Feature: Import into placex
Tests that data in placex is completed correctly.
Scenario: No country code tag is available
Given the place nodes
| osm_id | class | type | name | geometry
| 1 | highway | primary | 'name' : 'A1' | country:us
When importing
Then table placex contains
| object | country_code | calculated_country_code |
| N1 | None | us |
Scenario: Location overwrites country code tag
Given the scene country
And the place nodes
| osm_id | class | type | name | country_code | geometry
| 1 | highway | primary | 'name' : 'A1' | de | :us
When importing
Then table placex contains
| object | country_code | calculated_country_code |
| N1 | de | us |
Scenario: Country code tag overwrites location for countries
Given the place areas
| osm_type | osm_id | class | type | admin_level | name | country_code | geometry
| R | 1 | boundary | administrative | 2 | 'name' : 'foo' | de | (-100 40, -101 40, -101 41, -100 41, -100 40)
When importing
Then table placex contains
| object | country_code | calculated_country_code |
| R1 | de | de |
Scenario: Illegal country code tag for countries is ignored
And the place areas
| osm_type | osm_id | class | type | admin_level | name | country_code | geometry
| R | 1 | boundary | administrative | 2 | 'name' : 'foo' | xx | (-100 40, -101 40, -101 41, -100 41, -100 40)
When importing
Then table placex contains
| object | country_code | calculated_country_code |
| R1 | xx | us |
Scenario: admin level is copied over
Given the place nodes
| osm_id | class | type | admin_level | name
| 1 | place | state | 3 | 'name' : 'foo'
When importing
Then table placex contains
| object | admin_level |
| N1 | 3 |
Scenario: admin level is default 15
Given the place nodes
| osm_id | class | type | name
| 1 | amenity | prison | 'name' : 'foo'
When importing
Then table placex contains
| object | admin_level |
| N1 | 15 |
Scenario: admin level is never larger than 15
Given the place nodes
| osm_id | class | type | name | admin_level
| 1 | amenity | prison | 'name' : 'foo' | 16
When importing
Then table placex contains
| object | admin_level |
| N1 | 15 |
Scenario: postcode node without postcode is dropped
Given the place nodes
| osm_id | class | type
| 1 | place | postcode
When importing
Then table placex has no entry for N1
Scenario: postcode boundary without postcode is dropped
Given the place areas
| osm_type | osm_id | class | type | geometry
| R | 1 | boundary | postal_code | poly-area:0.1
When importing
Then table placex has no entry for R1
Scenario: search and address ranks for GB post codes correctly assigned
Given the place nodes
| osm_id | class | type | postcode | geometry
| 1 | place | postcode | E45 2CD | country:gb
| 2 | place | postcode | E45 2 | country:gb
| 3 | place | postcode | Y45 | country:gb
When importing
Then table placex contains
| object | postcode | calculated_country_code | rank_search | rank_address
| N1 | E45 2CD | gb | 25 | 5
| N2 | E45 2 | gb | 23 | 5
| N3 | Y45 | gb | 21 | 5
Scenario: wrongly formatted GB postcodes are down-ranked
Given the place nodes
| osm_id | class | type | postcode | geometry
| 1 | place | postcode | EA452CD | country:gb
| 2 | place | postcode | E45 23 | country:gb
| 3 | place | postcode | y45 | country:gb
When importing
Then table placex contains
| object | calculated_country_code | rank_search | rank_address
| N1 | gb | 30 | 30
| N2 | gb | 30 | 30
| N3 | gb | 30 | 30
Scenario: search and address rank for DE postcodes correctly assigned
Given the place nodes
| osm_id | class | type | postcode | geometry
| 1 | place | postcode | 56427 | country:de
| 2 | place | postcode | 5642 | country:de
| 3 | place | postcode | 5642A | country:de
| 4 | place | postcode | 564276 | country:de
When importing
Then table placex contains
| object | calculated_country_code | rank_search | rank_address
| N1 | de | 21 | 11
| N2 | de | 30 | 30
| N3 | de | 30 | 30
| N4 | de | 30 | 30
Scenario: search and address rank for other postcodes are correctly assigned
Given the place nodes
| osm_id | class | type | postcode | geometry
| 1 | place | postcode | 1 | country:ca
| 2 | place | postcode | X3 | country:ca
| 3 | place | postcode | 543 | country:ca
| 4 | place | postcode | 54dc | country:ca
| 5 | place | postcode | 12345 | country:ca
| 6 | place | postcode | 55TT667 | country:ca
| 7 | place | postcode | 123-65 | country:ca
| 8 | place | postcode | 12 445 4 | country:ca
| 9 | place | postcode | A1:bc10 | country:ca
When importing
Then table placex contains
| object | calculated_country_code | rank_search | rank_address
| N1 | ca | 21 | 11
| N2 | ca | 21 | 11
| N3 | ca | 21 | 11
| N4 | ca | 21 | 11
| N5 | ca | 21 | 11
| N6 | ca | 21 | 11
| N7 | ca | 25 | 11
| N8 | ca | 25 | 11
| N9 | ca | 25 | 11
Scenario: search and address ranks for places are correctly assigned
Given the named place nodes
| osm_id | class | type |
| 1 | foo | bar |
| 11 | place | Continent |
| 12 | place | continent |
| 13 | place | sea |
| 14 | place | country |
| 15 | place | state |
| 16 | place | region |
| 17 | place | county |
| 18 | place | city |
| 19 | place | island |
| 20 | place | town |
| 21 | place | village |
| 22 | place | hamlet |
| 23 | place | municipality |
| 24 | place | district |
| 25 | place | unincorporated_area |
| 26 | place | borough |
| 27 | place | suburb |
| 28 | place | croft |
| 29 | place | subdivision |
| 30 | place | isolated_dwelling |
| 31 | place | farm |
| 32 | place | locality |
| 33 | place | islet |
| 34 | place | mountain_pass |
| 35 | place | neighbourhood |
| 36 | place | house |
| 37 | place | building |
| 38 | place | houses |
And the named place nodes
| osm_id | class | type | extratags
| 100 | place | locality | 'locality' : 'townland'
| 101 | place | city | 'capital' : 'yes'
When importing
Then table placex contains
| object | rank_search | rank_address |
| N1 | 30 | 30 |
| N11 | 30 | 30 |
| N12 | 2 | 2 |
| N13 | 2 | 0 |
| N14 | 4 | 4 |
| N15 | 8 | 8 |
| N16 | 18 | 0 |
| N17 | 12 | 12 |
| N18 | 16 | 16 |
| N19 | 17 | 0 |
| N20 | 18 | 16 |
| N21 | 19 | 16 |
| N22 | 19 | 16 |
| N23 | 19 | 16 |
| N24 | 19 | 16 |
| N25 | 19 | 16 |
| N26 | 19 | 16 |
| N27 | 20 | 20 |
| N28 | 20 | 20 |
| N29 | 20 | 20 |
| N30 | 20 | 20 |
| N31 | 20 | 0 |
| N32 | 20 | 0 |
| N33 | 20 | 0 |
| N34 | 20 | 0 |
| N100 | 20 | 20 |
| N101 | 15 | 16 |
| N35 | 22 | 22 |
| N36 | 30 | 30 |
| N37 | 30 | 30 |
| N38 | 28 | 0 |
Scenario: search and address ranks for boundaries are correctly assigned
Given the named place nodes
| osm_id | class | type
| 1 | boundary | administrative
And the named place ways
| osm_id | class | type | geometry
| 10 | boundary | administrative | 10 10, 11 11
And the named place areas
| osm_type | osm_id | class | type | admin_level | geometry
| R | 20 | boundary | administrative | 2 | (1 1, 2 2, 1 2, 1 1)
| R | 21 | boundary | administrative | 32 | (3 3, 4 4, 3 4, 3 3)
| R | 22 | boundary | nature_park | 6 | (0 0, 1 0, 0 1, 0 0)
| R | 23 | boundary | natural_reserve| 10 | (0 0, 1 1, 1 0, 0 0)
When importing
Then table placex has no entry for N1
And table placex has no entry for W10
And table placex contains
| object | rank_search | rank_address
| R20 | 4 | 4
| R21 | 30 | 30
| R22 | 12 | 0
| R23 | 20 | 0
Scenario: search and address ranks for highways correctly assigned
Given the scene roads-with-pois
And the place nodes
| osm_id | class | type
| 1 | highway | bus_stop
And the place ways
| osm_id | class | type | geometry
| 1 | highway | primary | :w-south
| 2 | highway | secondary | :w-south
| 3 | highway | tertiary | :w-south
| 4 | highway | residential | :w-north
| 5 | highway | unclassified | :w-north
| 6 | highway | something | :w-north
When importing
Then table placex contains
| object | rank_search | rank_address
| N1 | 30 | 30
| W1 | 26 | 26
| W2 | 26 | 26
| W3 | 26 | 26
| W4 | 26 | 26
| W5 | 26 | 26
| W6 | 26 | 26
Scenario: rank and inclusion of landuses
And the named place nodes
| osm_id | class | type
| 2 | landuse | residential
And the named place ways
| osm_id | class | type | geometry
| 2 | landuse | residential | 1 1, 1 1.1
And the named place areas
| osm_type | osm_id | class | type | geometry
| W | 4 | landuse | residential | poly-area:0.1
| R | 2 | landuse | residential | poly-area:0.05
| R | 3 | landuse | forrest | poly-area:0.5
When importing
And table placex contains
| object | rank_search | rank_address
| N2 | 30 | 30
| W2 | 30 | 30
| W4 | 22 | 22
| R2 | 22 | 22
| R3 | 22 | 0
Scenario: rank and inclusion of naturals
And the named place nodes
| osm_id | class | type
| 2 | natural | peak
| 4 | natural | volcano
| 5 | natural | foobar
And the named place ways
| osm_id | class | type | geometry
| 2 | natural | mountain_range | 12 12,11 11
| 3 | natural | foobar | 13 13,13.1 13
And the named place areas
| osm_type | osm_id | class | type | geometry
| R | 3 | natural | volcano | poly-area:0.1
| R | 4 | natural | foobar | poly-area:0.5
| R | 5 | natural | sea | poly-area:5.0
| R | 6 | natural | sea | poly-area:0.01
When importing
And table placex contains
| object | rank_search | rank_address
| N2 | 18 | 0
| N4 | 18 | 0
| N5 | 30 | 30
| W2 | 18 | 0
| R3 | 18 | 0
| R4 | 22 | 0
| R5 | 4 | 4
| R6 | 4 | 4
| W3 | 30 | 30

View File

@ -1,42 +0,0 @@
@DB
Feature: Creation of search terms
Tests that search_name table is filled correctly
Scenario: POIs without a name have no search entry
Given the scene roads-with-pois
And the place nodes
| osm_id | class | type | geometry
| 1 | place | house | :p-N1
And the place ways
| osm_id | class | type | geometry
| 1 | highway | residential | :w-north
When importing
Then table search_name has no entry for N1
Scenario: Named POIs inherit address from parent
Given the scene roads-with-pois
And the place nodes
| osm_id | class | type | name | geometry
| 1 | place | house | foo | :p-N1
And the place ways
| osm_id | class | type | name | geometry
| 1 | highway | residential | the road | :w-north
When importing
Then search_name table contains
| place_id | name_vector | nameaddress_vector
| N1 | foo | the road
Scenario: Roads take over the postcode from attached houses
Given the scene roads-with-pois
And the place nodes
| osm_id | class | type | housenumber | postcode | street | geometry
| 1 | place | house | 1 | 12345 | North St |:p-S1
And the place ways
| osm_id | class | type | name | geometry
| 1 | highway | residential | North St | :w-north
When importing
Then search_name table contains
| place_id | nameaddress_vector
| W1 | 12345

View File

@ -1,17 +0,0 @@
@DB
Feature: Import of simple objects
Testing simple stuff
Scenario: Import place node
Given the place nodes:
| osm_id | class | type | name | geometry
| 1 | place | village | 'name' : 'Foo' | 10.0 -10.0
When importing
Then table placex contains
| object | class | type | name | centroid
| N1 | place | village | 'name' : 'Foo' | 10.0,-10.0 +- 1m
When sending query "Foo"
Then results contain
| ID | osm_type | osm_id
| 0 | N | 1

View File

@ -1,258 +0,0 @@
@DB
Feature: Update of address interpolations
Test the interpolated address are updated correctly
Scenario: addr:street added to interpolation
Given the scene parallel-road
And the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 2 | :n-middle-w
| 2 | place | house | 6 | :n-middle-e
And the place ways
| osm_id | class | type | housenumber | geometry
| 10 | place | houses | even | :w-middle
And the place ways
| osm_id | class | type | name | geometry
| 2 | highway | unclassified | 'name' : 'Sun Way' | :w-north
| 3 | highway | unclassified | 'name' : 'Cloud Street' | :w-south
And the ways
| id | nodes
| 10 | 1,100,101,102,2
When importing
Then table placex contains
| object | parent_place_id
| N1 | W2
| N2 | W2
And table location_property_osmline contains
| object | parent_place_id | startnumber | endnumber
| W10 | W2 | 2 | 6
When updating place ways
| osm_id | class | type | housenumber | street | geometry
| 10 | place | houses | even | Cloud Street | :w-middle
Then table placex contains
| object | parent_place_id
| N1 | W3
| N2 | W3
And table location_property_osmline contains
| object | parent_place_id | startnumber | endnumber
| W10 | W3 | 2 | 6
Scenario: addr:street added to housenumbers
Given the scene parallel-road
And the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 2 | :n-middle-w
| 2 | place | house | 6 | :n-middle-e
And the place ways
| osm_id | class | type | housenumber | geometry
| 10 | place | houses | even | :w-middle
And the place ways
| osm_id | class | type | name | geometry
| 2 | highway | unclassified | 'name' : 'Sun Way' | :w-north
| 3 | highway | unclassified | 'name' : 'Cloud Street' | :w-south
And the ways
| id | nodes
| 10 | 1,100,101,102,2
When importing
Then table placex contains
| object | parent_place_id
| N1 | W2
| N2 | W2
And table location_property_osmline contains
| object | parent_place_id | startnumber | endnumber
| W10 | W2 | 2 | 6
When updating place nodes
| osm_id | class | type | street | housenumber | geometry
| 1 | place | house | Cloud Street| 2 | :n-middle-w
| 2 | place | house | Cloud Street| 6 | :n-middle-e
Then table placex contains
| object | parent_place_id
| N1 | W3
| N2 | W3
And table location_property_osmline contains
| object | parent_place_id | startnumber | endnumber
| W10 | W3 | 2 | 6
Scenario: interpolation tag removed
Given the scene parallel-road
And the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 2 | :n-middle-w
| 2 | place | house | 6 | :n-middle-e
And the place ways
| osm_id | class | type | housenumber | geometry
| 10 | place | houses | even | :w-middle
And the place ways
| osm_id | class | type | name | geometry
| 2 | highway | unclassified | 'name' : 'Sun Way' | :w-north
| 3 | highway | unclassified | 'name' : 'Cloud Street' | :w-south
And the ways
| id | nodes
| 10 | 1,100,101,102,2
When importing
Then table placex contains
| object | parent_place_id
| N1 | W2
| N2 | W2
And table location_property_osmline contains
| object | parent_place_id | startnumber | endnumber
| W10 | W2 | 2 | 6
When marking for delete W10
Then table location_property_osmline has no entry for W10
And table placex contains
| object | parent_place_id
| N1 | W2
| N2 | W2
Scenario: referenced road added
Given the scene parallel-road
And the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 2 | :n-middle-w
| 2 | place | house | 6 | :n-middle-e
And the place ways
| osm_id | class | type | housenumber | street | geometry
| 10 | place | houses | even | Cloud Street| :w-middle
And the place ways
| osm_id | class | type | name | geometry
| 2 | highway | unclassified | 'name' : 'Sun Way' | :w-north
And the ways
| id | nodes
| 10 | 1,100,101,102,2
When importing
Then table placex contains
| object | parent_place_id
| N1 | W2
| N2 | W2
And table location_property_osmline contains
| object | parent_place_id | startnumber | endnumber
| W10 | W2 | 2 | 6
When updating place ways
| osm_id | class | type | name | geometry
| 3 | highway | unclassified | 'name' : 'Cloud Street' | :w-south
Then table placex contains
| object | parent_place_id
| N1 | W3
| N2 | W3
And table location_property_osmline contains
| object | parent_place_id | startnumber | endnumber
| W10 | W3 | 2 | 6
Scenario: referenced road deleted
Given the scene parallel-road
And the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 2 | :n-middle-w
| 2 | place | house | 6 | :n-middle-e
And the place ways
| osm_id | class | type | housenumber | street | geometry
| 10 | place | houses | even | Cloud Street| :w-middle
And the place ways
| osm_id | class | type | name | geometry
| 2 | highway | unclassified | 'name' : 'Sun Way' | :w-north
| 3 | highway | unclassified | 'name' : 'Cloud Street' | :w-south
And the ways
| id | nodes
| 10 | 1,100,101,102,2
When importing
Then table placex contains
| object | parent_place_id
| N1 | W3
| N2 | W3
And table location_property_osmline contains
| object | parent_place_id | startnumber | endnumber
| W10 | W3 | 2 | 6
When marking for delete W3
Then table placex contains
| object | parent_place_id
| N1 | W2
| N2 | W2
And table location_property_osmline contains
| object | parent_place_id | startnumber | endnumber
| W10 | W2 | 2 | 6
Scenario: building becomes interpolation
Given the scene building-with-parallel-streets
And the place ways
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 3 | :w-building
And the place ways
| osm_id | class | type | name | geometry
| 2 | highway | unclassified | 'name' : 'Cloud Street' | :w-south
When importing
Then table placex contains
| object | parent_place_id
| W1 | W2
When updating place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 2 | :n-north-w
| 2 | place | house | 6 | :n-north-e
And the ways
| id | nodes
| 1 | 1,100,101,102,2
And updating place ways
| osm_id | class | type | housenumber | street | geometry
| 1 | place | houses | even | Cloud Street| :w-north
Then table placex has no entry for W1
And table location_property_osmline contains
| object | parent_place_id | startnumber | endnumber
| W1 | W2 | 2 | 6
Scenario: interpolation becomes building
Given the scene building-with-parallel-streets
And the place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 2 | :n-north-w
| 2 | place | house | 6 | :n-north-e
And the place ways
| osm_id | class | type | name | geometry
| 2 | highway | unclassified | 'name' : 'Cloud Street' | :w-south
And the ways
| id | nodes
| 1 | 1,100,101,102,2
And the place ways
| osm_id | class | type | housenumber | street | geometry
| 1 | place | houses | even | Cloud Street| :w-north
When importing
Then table placex has no entry for W1
And table location_property_osmline contains
| object | parent_place_id | startnumber | endnumber
| W1 | W2 | 2 | 6
When updating place ways
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 3 | :w-building
Then table placex contains
| object | parent_place_id
| W1 | W2
Scenario: housenumbers added to interpolation
Given the scene building-with-parallel-streets
And the place ways
| osm_id | class | type | name | geometry
| 2 | highway | unclassified | 'name' : 'Cloud Street' | :w-south
And the ways
| id | nodes
| 1 | 1,100,101,102,2
And the place ways
| osm_id | class | type | housenumber | geometry
| 1 | place | houses | even | :w-north
When importing
Then table location_property_osmline has no entry for W1
When updating place nodes
| osm_id | class | type | housenumber | geometry
| 1 | place | house | 2 | :n-north-w
| 2 | place | house | 6 | :n-north-e
And updating place ways
| osm_id | class | type | housenumber | street | geometry
| 1 | place | houses | even | Cloud Street| :w-north
Then table location_property_osmline contains
| object | parent_place_id | startnumber | endnumber
| W1 | W2 | 2 | 6

View File

@ -1,96 +0,0 @@
@DB
Feature: Updates of linked places
Tests that linked places are correctly added and deleted.
Scenario: Add linked place when linking relation is renamed
Given the place nodes
| osm_id | class | type | name | geometry
| 1 | place | city | foo | 0 0
And the place areas
| osm_type | osm_id | class | type | name | admin_level | geometry
| R | 1 | boundary | administrative | foo | 8 | poly-area:0.1
When importing
And sending query "foo" with dups
Then results contain
| osm_type
| R
When updating place areas
| osm_type | osm_id | class | type | name | admin_level | geometry
| R | 1 | boundary | administrative | foobar | 8 | poly-area:0.1
Then table placex contains
| object | linked_place_id
| N1 | None
When updating place areas
| osm_type | osm_id | class | type | name | admin_level | geometry
When sending query "foo" with dups
Then results contain
| osm_type
| N
Scenario: Add linked place when linking relation is removed
Given the place nodes
| osm_id | class | type | name | geometry
| 1 | place | city | foo | 0 0
And the place areas
| osm_type | osm_id | class | type | name | admin_level | geometry
| R | 1 | boundary | administrative | foo | 8 | poly-area:0.1
When importing
And sending query "foo" with dups
Then results contain
| osm_type
| R
When marking for delete R1
Then table placex contains
| object | linked_place_id
| N1 | None
When updating place areas
| osm_type | osm_id | class | type | name | admin_level | geometry
And sending query "foo" with dups
Then results contain
| osm_type
| N
Scenario: Remove linked place when linking relation is added
Given the place nodes
| osm_id | class | type | name | geometry
| 1 | place | city | foo | 0 0
When importing
And sending query "foo" with dups
Then results contain
| osm_type
| N
When updating place areas
| osm_type | osm_id | class | type | name | admin_level | geometry
| R | 1 | boundary | administrative | foo | 8 | poly-area:0.1
Then table placex contains
| object | linked_place_id
| N1 | R1
When sending query "foo" with dups
Then results contain
| osm_type
| R
Scenario: Remove linked place when linking relation is renamed
Given the place nodes
| osm_id | class | type | name | geometry
| 1 | place | city | foo | 0 0
And the place areas
| osm_type | osm_id | class | type | name | admin_level | geometry
| R | 1 | boundary | administrative | foobar | 8 | poly-area:0.1
When importing
And sending query "foo" with dups
Then results contain
| osm_type
| N
When updating place areas
| osm_type | osm_id | class | type | name | admin_level | geometry
| R | 1 | boundary | administrative | foo | 8 | poly-area:0.1
Then table placex contains
| object | linked_place_id
| N1 | R1
When sending query "foo" with dups
Then results contain
| osm_type
| R

View File

@ -1,39 +0,0 @@
@DB
Feature: Update of names in place objects
Test all naming related issues in updates
Scenario: Updating postcode in postcode boundaries without ref
Given the place areas
| osm_type | osm_id | class | type | postcode | geometry
| R | 1 | boundary | postal_code | 12345 | (0 0, 1 0, 1 1, 0 1, 0 0)
When importing
And sending query "12345"
Then results contain
| ID | osm_type | osm_id
| 0 | R | 1
When updating place areas
| osm_type | osm_id | class | type | postcode | geometry
| R | 1 | boundary | postal_code | 54321 | (0 0, 1 0, 1 1, 0 1, 0 0)
And sending query "12345"
Then exactly 0 results are returned
When sending query "54321"
Then results contain
| ID | osm_type | osm_id
| 0 | R | 1
Scenario: Delete postcode from postcode boundaries without ref
Given the place areas
| osm_type | osm_id | class | type | postcode | geometry
| R | 1 | boundary | postal_code | 12345 | (0 0, 1 0, 1 1, 0 1, 0 0)
When importing
And sending query "12345"
Then results contain
| ID | osm_type | osm_id
| 0 | R | 1
When updating place areas
| osm_type | osm_id | class | type | geometry
| R | 1 | boundary | postal_code | (0 0, 1 0, 1 1, 0 1, 0 0)
Then table placex has no entry for R1

View File

@ -1,117 +0,0 @@
@DB
Feature: Update of search terms
Tests that search_name table is filled correctly
Scenario: POI-inherited postcode remains when way type is changed
Given the scene roads-with-pois
And the place nodes
| osm_id | class | type | housenumber | postcode | street | geometry
| 1 | place | house | 1 | 12345 | North St |:p-S1
And the place ways
| osm_id | class | type | name | geometry
| 1 | highway | residential | North St | :w-north
When importing
Then search_name table contains
| place_id | nameaddress_vector
| W1 | 12345
When updating place ways
| osm_id | class | type | name | geometry
| 1 | highway | unclassified | North St | :w-north
Then search_name table contains
| place_id | nameaddress_vector
| W1 | 12345
Scenario: POI-inherited postcode remains when way name is changed
Given the scene roads-with-pois
And the place nodes
| osm_id | class | type | housenumber | postcode | street | geometry
| 1 | place | house | 1 | 12345 | North St |:p-S1
And the place ways
| osm_id | class | type | name | geometry
| 1 | highway | residential | North St | :w-north
When importing
Then search_name table contains
| place_id | nameaddress_vector
| W1 | 12345
When updating place ways
| osm_id | class | type | name | geometry
| 1 | highway | unclassified | South St | :w-north
Then search_name table contains
| place_id | nameaddress_vector
| W1 | 12345
Scenario: POI-inherited postcode remains when way geometry is changed
Given the scene roads-with-pois
And the place nodes
| osm_id | class | type | housenumber | postcode | street | geometry
| 1 | place | house | 1 | 12345 | North St |:p-S1
And the place ways
| osm_id | class | type | name | geometry
| 1 | highway | residential | North St | :w-north
When importing
Then search_name table contains
| place_id | nameaddress_vector
| W1 | 12345
When updating place ways
| osm_id | class | type | name | geometry
| 1 | highway | unclassified | South St | :w-south
Then search_name table contains
| place_id | nameaddress_vector
| W1 | 12345
Scenario: POI-inherited postcode is added when POI postcode changes
Given the scene roads-with-pois
And the place nodes
| osm_id | class | type | housenumber | postcode | street | geometry
| 1 | place | house | 1 | 12345 | North St |:p-S1
And the place ways
| osm_id | class | type | name | geometry
| 1 | highway | residential | North St | :w-north
When importing
Then search_name table contains
| place_id | nameaddress_vector
| W1 | 12345
When updating place nodes
| osm_id | class | type | housenumber | postcode | street | geometry
| 1 | place | house | 1 | 54321 | North St |:p-S1
Then search_name table contains
| place_id | nameaddress_vector
| W1 | 54321
Scenario: POI-inherited postcode remains when POI geometry changes
Given the scene roads-with-pois
And the place nodes
| osm_id | class | type | housenumber | postcode | street | geometry
| 1 | place | house | 1 | 12345 | North St |:p-S1
And the place ways
| osm_id | class | type | name | geometry
| 1 | highway | residential | North St | :w-north
When importing
Then search_name table contains
| place_id | nameaddress_vector
| W1 | 12345
When updating place nodes
| osm_id | class | type | housenumber | postcode | street | geometry
| 1 | place | house | 1 | 12345 | North St |:p-S2
Then search_name table contains
| place_id | nameaddress_vector
| W1 | 12345
Scenario: POI-inherited postcode remains when another POI is deleted
Given the scene roads-with-pois
And the place nodes
| osm_id | class | type | housenumber | postcode | street | geometry
| 1 | place | house | 1 | 12345 | North St |:p-S1
| 2 | place | house | 2 | | North St |:p-S2
And the place ways
| osm_id | class | type | name | geometry
| 1 | highway | residential | North St | :w-north
When importing
Then search_name table contains
| place_id | nameaddress_vector
| W1 | 12345
When marking for delete N2
Then search_name table contains
| place_id | nameaddress_vector
| W1 | 12345

View File

@ -1,73 +0,0 @@
@DB
Feature: Update of simple objects
Testing simple stuff
Scenario: Do delete small boundary features
Given the place areas
| osm_type | osm_id | class | type | admin_level | geometry
| R | 1 | boundary | administrative | 3 | (0 0, 1 0, 1 1, 0 1, 0 0)
When importing
Then table placex contains
| object | rank_search
| R1 | 6
When marking for delete R1
Then table placex has no entry for R1
Scenario: Do not delete large boundary features
Given the place areas
| osm_type | osm_id | class | type | admin_level | geometry
| R | 1 | boundary | administrative | 3 | (0 0, 2 0, 2 2.1, 0 2, 0 0)
When importing
Then table placex contains
| object | rank_search
| R1 | 6
When marking for delete R1
Then table placex contains
| object | rank_search
| R1 | 6
Scenario: Do delete large features of low rank
Given the named place areas
| osm_type | osm_id | class | type | geometry
| W | 1 | place | house | (0 0, 2 0, 2 2.1, 0 2, 0 0)
| R | 1 | boundary | national_park | (0 0, 2 0, 2 2.1, 0 2, 0 0)
When importing
Then table placex contains
| object | rank_address
| R1 | 0
| W1 | 30
When marking for delete R1,W1
Then table placex has no entry for W1
Then table placex has no entry for R1
Scenario: type mutation
Given the place nodes
| osm_id | class | type | geometry
| 3 | shop | toys | 1 -1
When importing
Then table placex contains
| object | class | type
| N3 | shop | toys
When updating place nodes
| osm_id | class | type | geometry
| 3 | shop | grocery | 1 -1
Then table placex contains
| object | class | type
| N3 | shop | grocery
Scenario: remove postcode place when house number is added
Given the place nodes
| osm_id | class | type | postcode | geometry
| 3 | place | postcode | 12345 | 1 -1
When importing
Then table placex contains
| object | class | type
| N3 | place | postcode
When updating place nodes
| osm_id | class | type | postcode | housenumber | geometry
| 3 | place | house | 12345 | 13 | 1 -1
Then table placex contains
| object | class | type
| N3 | place | house

View File

@ -1,37 +0,0 @@
@DB
Feature: Import of objects with broken geometries by osm2pgsql
@Fail
Scenario: Import way with double nodes
Given the osm nodes:
| id | geometry
| 100 | 0 0
| 101 | 0 0.1
| 102 | 0.1 0.2
And the osm ways:
| id | tags | nodes
| 1 | 'highway' : 'primary' | 100 101 101 102
When loading osm data
Then table place contains
| object | class | type | geometry
| W1 | highway | primary | (0 0, 0 0.1, 0.1 0.2)
Scenario: Import of ballon areas
Given the osm nodes:
| id | geometry
| 1 | 0 0
| 2 | 0 0.0001
| 3 | 0.00001 0.0001
| 4 | 0.00001 0
| 5 | -0.00001 0
And the osm ways:
| id | tags | nodes
| 1 | 'highway' : 'unclassified' | 1 2 3 4 1 5
| 2 | 'highway' : 'unclassified' | 1 2 3 4 1
| 3 | 'highway' : 'unclassified' | 1 2 3 4 3
When loading osm data
Then table place contains
| object | geometrytype
| W1 | ST_LineString
| W2 | ST_Polygon
| W3 | ST_LineString

View File

@ -1,13 +0,0 @@
@DB
Feature: Import of relations by osm2pgsql
Testing specific relation problems related to members.
Scenario: Don't import empty waterways
Given the osm nodes:
| id | tags
| 1 | 'amenity' : 'prison', 'name' : 'foo'
And the osm relations:
| id | tags | members
| 1 | 'type' : 'waterway', 'waterway' : 'river', 'name' : 'XZ' | N1
When loading osm data
Then table place has no entry for R1

View File

@ -1,68 +0,0 @@
@DB
Feature: Import of simple objects by osm2pgsql
Testing basic tagging in osm2pgsql imports.
Scenario: Import simple objects
Given the osm nodes:
| id | tags
| 1 | 'amenity' : 'prison', 'name' : 'foo'
Given the osm nodes:
| id | geometry
| 100 | 0 0
| 101 | 0 0.1
| 102 | 0.1 0.2
| 200 | 0 0
| 201 | 0 1
| 202 | 1 1
| 203 | 1 0
And the osm ways:
| id | tags | nodes
| 1 | 'shop' : 'toys', 'name' : 'tata' | 100 101 102
| 2 | 'ref' : '45' | 200 201 202 203 200
And the osm relations:
| id | tags | members
| 1 | 'type' : 'multipolygon', 'tourism' : 'hotel', 'name' : 'XZ' | N1,W2
When loading osm data
Then table place contains
| object | class | type | name
| N1 | amenity | prison | 'name' : 'foo'
| W1 | shop | toys | 'name' : 'tata'
| R1 | tourism | hotel | 'name' : 'XZ'
Scenario: Import object with two main tags
Given the osm nodes:
| id | tags
| 1 | 'tourism' : 'hotel', 'amenity' : 'restaurant', 'name' : 'foo'
When loading osm data
Then table place contains
| object | class | type | name
| N1:tourism | tourism | hotel | 'name' : 'foo'
| N1:amenity | amenity | restaurant | 'name' : 'foo'
Scenario: Import stand-alone house number with postcode
Given the osm nodes:
| id | tags
| 1 | 'addr:housenumber' : '4', 'addr:postcode' : '3345'
When loading osm data
Then table place contains
| object | class | type
| N1 | place | house
Scenario: Landuses are only imported when named
Given the osm nodes:
| id | geometry
| 100 | 0 0
| 101 | 0 0.1
| 102 | 0.1 0.1
| 200 | 0 0
| 202 | 1 1
| 203 | 1 0
And the osm ways:
| id | tags | nodes
| 1 | 'landuse' : 'residential', 'name' : 'rainbow' | 100 101 102 100
| 2 | 'landuse' : 'residential' | 200 202 203 200
When loading osm data
Then table place contains
| object | class | type
| W1 | landuse | residential
And table place has no entry for W2

View File

@ -1,550 +0,0 @@
@DB
Feature: Tag evaluation
Tests if tags are correctly imported into the place table
Scenario Outline: Name tags
Given the osm nodes:
| id | tags
| 1 | 'highway' : 'yes', '<nametag>' : 'Foo'
When loading osm data
Then table place contains
| object | name
| N1 | '<nametag>' : 'Foo'
Examples:
| nametag
| ref
| int_ref
| nat_ref
| reg_ref
| loc_ref
| old_ref
| iata
| icao
| pcode:1
| pcode:2
| pcode:3
| name
| name:de
| name:bt-BR
| int_name
| int_name:xxx
| nat_name
| nat_name:fr
| reg_name
| reg_name:1
| loc_name
| loc_name:DE
| old_name
| old_name:v1
| alt_name
| alt_name:dfe
| alt_name_1
| official_name
| short_name
| short_name:CH
| addr:housename
| brand
Scenario Outline: operator only for shops and amenities
Given the osm nodes:
| id | tags
| 1 | 'highway' : 'yes', 'operator' : 'Foo', 'name' : 'null'
| 2 | 'shop' : 'grocery', 'operator' : 'Foo'
| 3 | 'amenity' : 'hospital', 'operator' : 'Foo'
| 4 | 'tourism' : 'hotel', 'operator' : 'Foo'
When loading osm data
Then table place contains
| object | name
| N1 | 'name' : 'null'
| N2 | 'operator' : 'Foo'
| N3 | 'operator' : 'Foo'
| N4 | 'operator' : 'Foo'
Scenario Outline: Ignored name tags
Given the osm nodes:
| id | tags
| 1 | 'highway' : 'yes', '<nametag>' : 'Foo', 'name' : 'real'
When loading osm data
Then table place contains
| object | name
| N1 | 'name' : 'real'
Examples:
| nametag
| name_de
| Name
| ref:de
| ref_de
| my:ref
| br:name
| name:prefix
| name:source
Scenario: Special character in name tag
Given the osm nodes:
| id | tags
| 1 | 'highway' : 'yes', 'name: de' : 'Foo', 'name' : 'real1'
| 2 | 'highway' : 'yes', 'name:&#xa;de' : 'Foo', 'name' : 'real2'
| 3 | 'highway' : 'yes', 'name:&#x9;de' : 'Foo', 'name:\\' : 'real3'
When loading osm data
Then table place contains
| object | name
| N1 | 'name: de' : 'Foo', 'name' : 'real1'
| N2 | 'name: de' : 'Foo', 'name' : 'real2'
| N3 | 'name: de' : 'Foo', 'name:\\' : 'real3'
Scenario Outline: Included places
Given the osm nodes:
| id | tags
| 1 | '<key>' : '<value>', 'name' : 'real'
When loading osm data
Then table place contains
| object | name
| N1 | 'name' : 'real'
Examples:
| key | value
| emergency | phone
| tourism | information
| historic | castle
| military | barracks
| natural | water
| highway | residential
| aerialway | station
| aeroway | way
| boundary | administrative
| craft | butcher
| leisure | playground
| office | bookmaker
| railway | rail
| shop | bookshop
| waterway | stream
| landuse | cemetry
| man_made | tower
| mountain_pass | yes
Scenario Outline: Bridges and Tunnels take special name tags
Given the osm nodes:
| id | tags
| 1 | 'highway' : 'road', '<key>' : 'yes', 'name' : 'Rd', '<key>:name' : 'My'
| 2 | 'highway' : 'road', '<key>' : 'yes', 'name' : 'Rd'
When loading osm data
Then table place contains
| object | class | type | name
| N1:highway | highway | road | 'name' : 'Rd'
| N1:<key> | <key> | yes | 'name' : 'My'
| N2:highway | highway | road | 'name' : 'Rd'
And table place has no entry for N2:<key>
Examples:
| key
| bridge
| tunnel
Scenario Outline: Excluded places
Given the osm nodes:
| id | tags
| 1 | '<key>' : '<value>', 'name' : 'real'
| 2 | 'highway' : 'motorway', 'name' : 'To Hell'
When loading osm data
Then table place has no entry for N1
Examples:
| key | value
| emergency | yes
| emergency | no
| tourism | yes
| tourism | no
| historic | yes
| historic | no
| military | yes
| military | no
| natural | yes
| natural | no
| highway | no
| highway | turning_circle
| highway | mini_roundabout
| highway | noexit
| highway | crossing
| aerialway | no
| aerialway | pylon
| man_made | survey_point
| man_made | cutline
| aeroway | no
| amenity | no
| bridge | no
| craft | no
| leisure | no
| office | no
| railway | no
| railway | level_crossing
| shop | no
| tunnel | no
| waterway | riverbank
Scenario: Some tags only are included when named
Given the osm nodes:
| id | tags
| 1 | '<key>' : '<value>'
| 2 | '<key>' : '<value>', 'name' : 'To Hell'
| 3 | '<key>' : '<value>', 'ref' : '123'
When loading osm data
Then table place has no entry for N1
And table place has no entry for N3
And table place contains
| object | class | type
| N2 | <key> | <value>
Examples:
| key | value
| landuse | residential
| natural | meadow
| highway | traffic_signals
| highway | service
| highway | cycleway
| highway | path
| highway | footway
| highway | steps
| highway | bridleway
| highway | track
| highway | byway
| highway | motorway_link
| highway | primary_link
| highway | trunk_link
| highway | secondary_link
| highway | tertiary_link
| railway | rail
| boundary | administrative
| waterway | stream
Scenario: Footways are not included if they are sidewalks
Given the osm nodes:
| id | tags
| 2 | 'highway' : 'footway', 'name' : 'To Hell', 'footway' : 'sidewalk'
| 23 | 'highway' : 'footway', 'name' : 'x'
When loading osm data
Then table place has no entry for N2
Scenario: named junctions are included if there is no other tag
Given the osm nodes:
| id | tags
| 1 | 'junction' : 'yes'
| 2 | 'highway' : 'secondary', 'junction' : 'roundabout', 'name' : 'To Hell'
| 3 | 'junction' : 'yes', 'name' : 'Le Croix'
When loading osm data
Then table place has no entry for N1
And table place has no entry for N2:junction
And table place contains
| object | class | type
| N3 | junction | yes
Scenario: Boundary with place tag
Given the osm nodes:
| id | geometry
| 200 | 0 0
| 201 | 0 1
| 202 | 1 1
| 203 | 1 0
And the osm ways:
| id | tags | nodes
| 2 | 'boundary' : 'administrative', 'place' : 'city', 'name' : 'Foo' | 200 201 202 203 200
| 4 | 'boundary' : 'administrative', 'place' : 'island','name' : 'Foo' | 200 201 202 203 200
| 20 | 'place' : 'city', 'name' : 'ngng' | 200 201 202 203 200
| 40 | 'place' : 'city', 'boundary' : 'statistical', 'name' : 'BB' | 200 201 202 203 200
When loading osm data
Then table place contains
| object | class | extratags | type
| W2 | boundary | 'place' : 'city' | administrative
| W4:boundary | boundary | None | administrative
| W4:place | place | None | island
| W20 | place | None | city
| W40:boundary | boundary | None | statistical
| W40:place | place | None | city
And table place has no entry for W2:place
Scenario Outline: Tags that describe a house
Given the osm nodes:
| id | tags
| 100 | '<key>' : '<value>'
| 999 | 'amenity' : 'prison', '<key>' : '<value>'
When loading osm data
Then table place contains
| object | class | type
| N100 | place | house
| N999 | amenity | prison
And table place has no entry for N100:<key>
And table place has no entry for N999:<key>
And table place has no entry for N999:place
Examples:
| key | value
| addr:housename | My Mansion
| addr:housenumber | 456
| addr:conscriptionnumber | 4
| addr:streetnumber | 4568765
Scenario: Only named with no other interesting tag
Given the osm nodes:
| id | tags
| 1 | 'landuse' : 'meadow'
| 2 | 'landuse' : 'residential', 'name' : 'important'
| 3 | 'landuse' : 'residential', 'name' : 'important', 'place' : 'hamlet'
When loading osm data
Then table place contains
| object | class | type
| N2 | landuse | residential
| N3 | place | hamlet
And table place has no entry for N1
And table place has no entry for N3:landuse
Scenario Outline: Import of postal codes
Given the osm nodes:
| id | tags
| 10 | 'highway' : 'secondary', '<key>' : '<value>'
| 11 | '<key>' : '<value>'
When loading osm data
Then table place contains
| object | class | type | postcode
| N10 | highway | secondary | <value>
| N11 | place | postcode | <value>
And table place has no entry for N10:place
Examples:
| key | value
| postal_code | 45736
| postcode | xxx
| addr:postcode | 564
| tiger:zip_left | 00011
| tiger:zip_right | 09123
Scenario: Import of street and place
Given the osm nodes:
| id | tags
| 10 | 'amenity' : 'hospital', 'addr:street' : 'Foo St'
| 20 | 'amenity' : 'hospital', 'addr:place' : 'Foo Town'
When loading osm data
Then table place contains
| object | class | type | street | addr_place
| N10 | amenity | hospital | Foo St | None
| N20 | amenity | hospital | None | Foo Town
Scenario Outline: Import of country
Given the osm nodes:
| id | tags
| 10 | 'place' : 'village', '<key>' : '<value>'
When loading osm data
Then table place contains
| object | class | type | country_code
| N10 | place | village | <value>
Examples:
| key | value
| country_code | us
| ISO3166-1 | XX
| is_in:country_code | __
| addr:country | ..
| addr:country_code | cv
Scenario Outline: Ignore country codes with wrong length
Given the osm nodes:
| id | tags
| 10 | 'place' : 'village', 'country_code' : '<value>'
When loading osm data
Then table place contains
| object | class | type | country_code
| N10 | place | village | None
Examples:
| value
| X
| x
| ger
| dkeufr
| d e
Scenario: Import of house numbers
Given the osm nodes:
| id | tags
| 10 | 'building' : 'yes', 'addr:housenumber' : '4b'
| 11 | 'building' : 'yes', 'addr:conscriptionnumber' : '003'
| 12 | 'building' : 'yes', 'addr:streetnumber' : '2345'
| 13 | 'building' : 'yes', 'addr:conscriptionnumber' : '3', 'addr:streetnumber' : '111'
When loading osm data
Then table place contains
| object | class | type | housenumber
| N10 | building | yes | 4b
| N11 | building | yes | 003
| N12 | building | yes | 2345
| N13 | building | yes | 3/111
Scenario: Import of address interpolations
Given the osm nodes:
| id | tags
| 10 | 'addr:interpolation' : 'odd'
| 11 | 'addr:housenumber' : '10', 'addr:interpolation' : 'odd'
| 12 | 'addr:interpolation' : 'odd', 'addr:housenumber' : '23'
When loading osm data
Then table place contains
| object | class | type | housenumber
| N10 | place | houses | odd
| N11 | place | houses | odd
| N12 | place | houses | odd
Scenario: Shorten tiger:county tags
Given the osm nodes:
| id | tags
| 10 | 'place' : 'village', 'tiger:county' : 'Feebourgh, AL'
| 11 | 'place' : 'village', 'addr:state' : 'Alabama', 'tiger:county' : 'Feebourgh, AL'
| 12 | 'place' : 'village', 'tiger:county' : 'Feebourgh'
When loading osm data
Then table place contains
| object | class | type | isin
| N10 | place | village | Feebourgh county
| N11 | place | village | Feebourgh county,Alabama
| N12 | place | village | Feebourgh county
Scenario Outline: Import of address tags
Given the osm nodes:
| id | tags
| 10 | 'place' : 'village', '<key>' : '<value>'
When loading osm data
Then table place contains
| object | class | type | isin
| N10 | place | village | <value>
Examples:
| key | value
| is_in | Stockholm, Sweden
| is_in:country | Xanadu
| addr:suburb | hinein
| addr:county | le havre
| addr:city | Sydney
| addr:state | Jura
Scenario: Import of admin level
Given the osm nodes:
| id | tags
| 10 | 'amenity' : 'hospital', 'admin_level' : '3'
| 11 | 'amenity' : 'hospital', 'admin_level' : 'b'
| 12 | 'amenity' : 'hospital'
| 13 | 'amenity' : 'hospital', 'admin_level' : '3.0'
When loading osm data
Then table place contains
| object | class | type | admin_level
| N10 | amenity | hospital | 3
| N11 | amenity | hospital | 100
| N12 | amenity | hospital | 100
| N13 | amenity | hospital | 3
Scenario: Import of extra tags
Given the osm nodes:
| id | tags
| 10 | 'tourism' : 'hotel', '<key>' : 'foo'
When loading osm data
Then table place contains
| object | class | type | extratags
| N10 | tourism | hotel | '<key>' : 'foo'
Examples:
| key
| tracktype
| traffic_calming
| service
| cuisine
| capital
| dispensing
| religion
| denomination
| sport
| internet_access
| lanes
| surface
| smoothness
| width
| est_width
| incline
| opening_hours
| collection_times
| service_times
| disused
| wheelchair
| sac_scale
| trail_visibility
| mtb:scale
| mtb:description
| wood
| drive_in
| access
| vehicle
| bicyle
| foot
| goods
| hgv
| motor_vehicle
| motor_car
| access:foot
| contact:phone
| drink:mate
| oneway
| date_on
| date_off
| day_on
| day_off
| hour_on
| hour_off
| maxweight
| maxheight
| maxspeed
| disused
| toll
| charge
| population
| description
| image
| attribution
| fax
| email
| url
| website
| phone
| real_ale
| smoking
| food
| camera
| brewery
| locality
| wikipedia
| wikipedia:de
| wikidata
| name:prefix
| name:botanical
| name:etymology:wikidata
Scenario: buildings
Given the osm nodes:
| id | tags
| 10 | 'tourism' : 'hotel', 'building' : 'yes'
| 11 | 'building' : 'house'
| 12 | 'building' : 'shed', 'addr:housenumber' : '1'
| 13 | 'building' : 'yes', 'name' : 'Das Haus'
| 14 | 'building' : 'yes', 'addr:postcode' : '12345'
When loading osm data
Then table place contains
| object | class | type
| N10 | tourism | hotel
| N12 | building| yes
| N13 | building| yes
| N14 | building| yes
And table place has no entry for N10:building
And table place has no entry for N11
Scenario: complete node entry
Given the osm nodes:
| id | tags
| 290393920 | 'addr:city':'Perpignan','addr:country':'FR','addr:housenumber':'43\\','addr:postcode':'66000','addr:street':'Rue Pierre Constant d`Ivry','source':'cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre ; mise à jour :2008'
When loading osm data
Then table place contains
| object | class | type | housenumber
| N290393920 | place | house| 43\

View File

@ -1,152 +0,0 @@
@DB
Feature: Update of relations by osm2pgsql
Testing relation update by osm2pgsql.
Scenario: Remove all members of a relation
Given the osm nodes:
| id | tags
| 1 | 'amenity' : 'prison', 'name' : 'foo'
Given the osm nodes:
| id | geometry
| 200 | 0 0
| 201 | 0 0.0001
| 202 | 0.0001 0.0001
| 203 | 0.0001 0
Given the osm ways:
| id | tags | nodes
| 2 | 'ref' : '45' | 200 201 202 203 200
Given the osm relations:
| id | tags | members
| 1 | 'type' : 'multipolygon', 'tourism' : 'hotel', 'name' : 'XZ' | W2
When loading osm data
Then table place contains
| object | class | type | name
| R1 | tourism | hotel | 'name' : 'XZ'
Given the osm relations:
| action | id | tags | members
| M | 1 | 'type' : 'multipolygon', 'tourism' : 'hotel', 'name' : 'XZ' | N1
When updating osm data
Then table place has no entry for R1
Scenario: Change type of a relation
Given the osm nodes:
| id | geometry
| 200 | 0 0
| 201 | 0 0.0001
| 202 | 0.0001 0.0001
| 203 | 0.0001 0
Given the osm ways:
| id | tags | nodes
| 2 | 'ref' : '45' | 200 201 202 203 200
Given the osm relations:
| id | tags | members
| 1 | 'type' : 'multipolygon', 'tourism' : 'hotel', 'name' : 'XZ' | W2
When loading osm data
Then table place contains
| object | class | type | name
| R1 | tourism | hotel | 'name' : 'XZ'
Given the osm relations:
| action | id | tags | members
| M | 1 | 'type' : 'multipolygon', 'amenity' : 'prison', 'name' : 'XZ' | W2
When updating osm data
Then table place has no entry for R1:tourism
And table place contains
| object | class | type | name
| R1 | amenity | prison | 'name' : 'XZ'
Scenario: Change name of a relation
Given the osm nodes:
| id | geometry
| 200 | 0 0
| 201 | 0 0.0001
| 202 | 0.0001 0.0001
| 203 | 0.0001 0
Given the osm ways:
| id | tags | nodes
| 2 | 'ref' : '45' | 200 201 202 203 200
Given the osm relations:
| id | tags | members
| 1 | 'type' : 'multipolygon', 'tourism' : 'hotel', 'name' : 'AB' | W2
When loading osm data
Then table place contains
| object | class | type | name
| R1 | tourism | hotel | 'name' : 'AB'
Given the osm relations:
| action | id | tags | members
| M | 1 | 'type' : 'multipolygon', 'tourism' : 'hotel', 'name' : 'XZ' | W2
When updating osm data
Then table place contains
| object | class | type | name
| R1 | tourism | hotel | 'name' : 'XZ'
Scenario: Change type of a relation into something unknown
Given the osm nodes:
| id | geometry
| 200 | 0 0
| 201 | 0 0.0001
| 202 | 0.0001 0.0001
| 203 | 0.0001 0
Given the osm ways:
| id | tags | nodes
| 2 | 'ref' : '45' | 200 201 202 203 200
Given the osm relations:
| id | tags | members
| 1 | 'type' : 'multipolygon', 'tourism' : 'hotel', 'name' : 'XZ' | W2
When loading osm data
Then table place contains
| object | class | type | name
| R1 | tourism | hotel | 'name' : 'XZ'
Given the osm relations:
| action | id | tags | members
| M | 1 | 'type' : 'multipolygon', 'amenities' : 'prison', 'name' : 'XZ' | W2
When updating osm data
Then table place has no entry for R1
Scenario: Type tag is removed
Given the osm nodes:
| id | geometry
| 200 | 0 0
| 201 | 0 0.0001
| 202 | 0.0001 0.0001
| 203 | 0.0001 0
Given the osm ways:
| id | tags | nodes
| 2 | 'ref' : '45' | 200 201 202 203 200
Given the osm relations:
| id | tags | members
| 1 | 'type' : 'multipolygon', 'tourism' : 'hotel', 'name' : 'XZ' | W2
When loading osm data
Then table place contains
| object | class | type | name
| R1 | tourism | hotel | 'name' : 'XZ'
Given the osm relations:
| action | id | tags | members
| M | 1 | 'tourism' : 'hotel', 'name' : 'XZ' | W2
When updating osm data
Then table place has no entry for R1
Scenario: Type tag is renamed to something unknown
Given the osm nodes:
| id | geometry
| 200 | 0 0
| 201 | 0 0.0001
| 202 | 0.0001 0.0001
| 203 | 0.0001 0
Given the osm ways:
| id | tags | nodes
| 2 | 'ref' : '45' | 200 201 202 203 200
Given the osm relations:
| id | tags | members
| 1 | 'type' : 'multipolygon', 'tourism' : 'hotel', 'name' : 'XZ' | W2
When loading osm data
Then table place contains
| object | class | type | name
| R1 | tourism | hotel | 'name' : 'XZ'
Given the osm relations:
| action | id | tags | members
| M | 1 | 'type' : 'multipolygonn', 'tourism' : 'hotel', 'name' : 'XZ' | W2
When updating osm data
Then table place has no entry for R1

View File

@ -1,22 +0,0 @@
@DB
Feature: Update of simple objects by osm2pgsql
Testing basic update functions of osm2pgsql.
Scenario: Import object with two main tags
Given the osm nodes:
| id | tags
| 1 | 'tourism' : 'hotel', 'amenity' : 'restaurant', 'name' : 'foo'
When loading osm data
Then table place contains
| object | class | type | name
| N1:tourism | tourism | hotel | 'name' : 'foo'
| N1:amenity | amenity | restaurant | 'name' : 'foo'
Given the osm nodes:
| action | id | tags
| M | 1 | 'tourism' : 'hotel', 'name' : 'foo'
When updating osm data
Then table place has no entry for N1:amenity
And table place contains
| object | class | type | name
| N1:tourism | tourism | hotel | 'name' : 'foo'

View File

@ -1,286 +0,0 @@
""" Steps for checking the results of queries.
"""
from nose.tools import *
from lettuce import *
from tidylib import tidy_document
from collections import OrderedDict
import json
import logging
import re
from xml.dom.minidom import parseString
logger = logging.getLogger(__name__)
def _parse_xml():
""" Puts the DOM structure into more convenient python
with a similar structure as the json document, so
that the same the semantics can be used. It does not
check if the content is valid (or at least not more than
necessary to transform it into a dict structure).
"""
page = parseString(world.page).documentElement
# header info
world.result_header = OrderedDict(page.attributes.items())
logger.debug('Result header: %r' % (world.result_header))
world.results = []
# results
if page.nodeName == 'searchresults' or page.nodeName == 'lookupresults':
for node in page.childNodes:
if node.nodeName != "#text":
assert_equals(node.nodeName, 'place', msg="Unexpected element '%s'" % node.nodeName)
newresult = OrderedDict(node.attributes.items())
assert_not_in('address', newresult)
assert_not_in('geokml', newresult)
assert_not_in('extratags', newresult)
assert_not_in('namedetails', newresult)
address = OrderedDict()
for sub in node.childNodes:
if sub.nodeName == 'geokml':
newresult['geokml'] = sub.childNodes[0].toxml()
elif sub.nodeName == 'extratags':
newresult['extratags'] = {}
for tag in sub.childNodes:
assert_equals(tag.nodeName, 'tag')
attrs = dict(tag.attributes.items())
assert_in('key', attrs)
assert_in('value', attrs)
newresult['extratags'][attrs['key']] = attrs['value']
elif sub.nodeName == 'namedetails':
newresult['namedetails'] = {}
for tag in sub.childNodes:
assert_equals(tag.nodeName, 'name')
attrs = dict(tag.attributes.items())
assert_in('desc', attrs)
newresult['namedetails'][attrs['desc']] = tag.firstChild.nodeValue.strip()
elif sub.nodeName == '#text':
pass
else:
address[sub.nodeName] = sub.firstChild.nodeValue.strip()
if address:
newresult['address'] = address
world.results.append(newresult)
elif page.nodeName == 'reversegeocode':
haserror = False
address = {}
for node in page.childNodes:
if node.nodeName == 'result':
assert_equals(len(world.results), 0)
assert (not haserror)
world.results.append(OrderedDict(node.attributes.items()))
assert_not_in('display_name', world.results[0])
assert_not_in('address', world.results[0])
world.results[0]['display_name'] = node.firstChild.nodeValue.strip()
elif node.nodeName == 'error':
assert_equals(len(world.results), 0)
haserror = True
elif node.nodeName == 'addressparts':
assert (not haserror)
address = OrderedDict()
for sub in node.childNodes:
address[sub.nodeName] = sub.firstChild.nodeValue.strip()
world.results[0]['address'] = address
elif node.nodeName == 'extratags':
world.results[0]['extratags'] = {}
for tag in node.childNodes:
assert_equals(tag.nodeName, 'tag')
attrs = dict(tag.attributes.items())
assert_in('key', attrs)
assert_in('value', attrs)
world.results[0]['extratags'][attrs['key']] = attrs['value']
elif node.nodeName == 'namedetails':
world.results[0]['namedetails'] = {}
for tag in node.childNodes:
assert_equals(tag.nodeName, 'name')
attrs = dict(tag.attributes.items())
assert_in('desc', attrs)
world.results[0]['namedetails'][attrs['desc']] = tag.firstChild.nodeValue.strip()
elif node.nodeName == "geokml":
world.results[0]['geokml'] = node
elif node.nodeName == "#text":
pass
else:
assert False, "Unknown content '%s' in XML" % node.nodeName
else:
assert False, "Unknown document node name %s in XML" % page.nodeName
logger.debug("The following was parsed out of XML:")
logger.debug(world.results)
@step(u'a HTTP (\d+) is returned')
def api_result_http_error(step, error):
assert_equals(world.returncode, int(error))
@step(u'the result is valid( \w+)?')
def api_result_is_valid(step, fmt):
assert_equals(world.returncode, 200)
if world.response_format == 'html':
document, errors = tidy_document(world.page,
options={'char-encoding' : 'utf8'})
# assert(len(errors) == 0), "Errors found in HTML document:\n%s" % errors
world.results = document
elif world.response_format == 'xml':
_parse_xml()
elif world.response_format == 'json':
world.results = json.JSONDecoder(object_pairs_hook=OrderedDict).decode(world.page)
if world.request_type == 'reverse':
world.results = (world.results,)
else:
assert False, "Unknown page format: %s" % (world.response_format)
if fmt:
assert_equals (fmt.strip(), world.response_format)
def compare(operator, op1, op2):
if operator == 'less than':
return op1 < op2
elif operator == 'more than':
return op1 > op2
elif operator == 'exactly':
return op1 == op2
elif operator == 'at least':
return op1 >= op2
elif operator == 'at most':
return op1 <= op2
else:
raise Exception("unknown operator '%s'" % operator)
@step(u'(less than|more than|exactly|at least|at most) (\d+) results? (?:is|are) returned')
def validate_result_number(step, operator, number):
step.given('the result is valid')
numres = len(world.results)
assert compare(operator, numres, int(number)), \
"Bad number of results: expected %s %s, got %d." % (operator, number, numres)
@step(u'result (\d+) has( not)? attributes (\S+)')
def search_check_for_result_attribute(step, num, invalid, attrs):
num = int(num)
step.given('at least %d results are returned' % (num + 1))
res = world.results[num]
for attr in attrs.split(','):
if invalid:
assert_not_in(attr.strip(), res)
else:
assert_in(attr.strip(),res)
@step(u'there is a json wrapper "([^"]*)"')
def api_result_check_json_wrapper(step, wrapper):
step.given('the result is valid json')
assert_equals(world.json_callback, wrapper)
@step(u'result header contains')
def api_result_header_contains(step):
step.given('the result is valid')
for line in step.hashes:
assert_in(line['attr'], world.result_header)
m = re.match("%s$" % (line['value'],), world.result_header[line['attr']])
@step(u'result header has no attribute (.*)')
def api_result_header_contains_not(step, attr):
step.given('the result is valid')
assert_not_in(attr, world.result_header)
@step(u'results contain$')
def api_result_contains(step):
step.given('at least 1 result is returned')
for line in step.hashes:
if 'ID' in line:
reslist = (world.results[int(line['ID'])],)
else:
reslist = world.results
for k,v in line.iteritems():
if k == 'latlon':
for curres in reslist:
world.match_geometry((float(curres['lat']), float(curres['lon'])), v)
elif k != 'ID':
for curres in reslist:
assert_in(k, curres)
if v[0] in '<>=':
# mathematical operation
evalexp = '%s %s' % (curres[k], v)
res = eval(evalexp)
logger.debug('Evaluating: %s = %s' % (res, evalexp))
assert_true(res, "Evaluation failed: %s" % (evalexp, ))
else:
# regex match
m = re.match("%s$" % (v,), curres[k])
assert_is_not_none(m, msg="field %s does not match: %s$ != %s." % (k, v, curres[k]))
@step(u'results contain valid boundingboxes$')
def api_result_address_contains(step):
step.given('the result is valid')
for curres in world.results:
bb = curres['boundingbox']
if world.response_format == 'json':
bb = ','.join(bb)
m = re.match('^(-?\d+\.\d+),(-?\d+\.\d+),(-?\d+\.\d+),(-?\d+\.\d+)$', bb)
assert_is_not_none(m, msg="invalid boundingbox: %s." % (curres['boundingbox']))
@step(u'result addresses contain$')
def api_result_address_contains(step):
step.given('the result is valid')
for line in step.hashes:
if 'ID' in line:
reslist = (world.results[int(line['ID'])],)
else:
reslist = world.results
for k,v in line.iteritems():
if k != 'ID':
for res in reslist:
curres = res['address']
assert_in(k, curres)
m = re.match("%s$" % (v,), curres[k])
assert_is_not_none(m, msg="field %s does not match: %s$ != %s." % (k, v, curres[k]))
@step(u'address of result (\d+) contains')
def api_result_address_exact(step, resid):
resid = int(resid)
step.given('at least %d results are returned' % (resid + 1))
addr = world.results[resid]['address']
for line in step.hashes:
assert_in(line['type'], addr)
m = re.match("%s$" % line['value'], addr[line['type']])
assert_is_not_none(m, msg="field %s does not match: %s$ != %s." % (
line['type'], line['value'], addr[line['type']]))
#assert_equals(line['value'], addr[line['type']])
@step(u'address of result (\d+) does not contain (.*)')
def api_result_address_details_missing(step, resid, types):
resid = int(resid)
step.given('at least %d results are returned' % (resid + 1))
addr = world.results[resid]['address']
for t in types.split(','):
assert_not_in(t.strip(), addr)
@step(u'address of result (\d+) is')
def api_result_address_exact(step, resid):
resid = int(resid)
step.given('at least %d results are returned' % (resid + 1))
result = world.results[resid]
linenr = 0
assert_equals(len(step.hashes), len(result['address']))
for k,v in result['address'].iteritems():
assert_equals(step.hashes[linenr]['type'], k)
assert_equals(step.hashes[linenr]['value'], v)
linenr += 1
@step('there are( no)? duplicates')
def api_result_check_for_duplicates(step, nodups=None):
step.given('at least 1 result is returned')
resarr = []
for res in world.results:
resarr.append((res['osm_type'], res['class'],
res['type'], res['display_name']))
if nodups is None:
assert len(resarr) > len(set(resarr))
else:
assert_equal(len(resarr), len(set(resarr)))

View File

@ -1,136 +0,0 @@
""" Steps for setting up and sending API requests.
"""
from nose.tools import *
from lettuce import *
import urllib
import urllib2
import logging
logger = logging.getLogger(__name__)
def api_call(requesttype):
world.request_type = requesttype
world.json_callback = None
data = urllib.urlencode(world.params)
url = "%s/%s?%s" % (world.config.base_url, requesttype, data)
req = urllib2.Request(url=url, headers=world.header)
try:
fd = urllib2.urlopen(req)
world.page = fd.read()
world.returncode = 200
except urllib2.HTTPError, ex:
world.returncode = ex.code
world.page = None
return
pageinfo = fd.info()
assert_equal('utf-8', pageinfo.getparam('charset').lower())
pagetype = pageinfo.gettype()
fmt = world.params.get('format')
if fmt == 'html':
assert_equals('text/html', pagetype)
world.response_format = fmt
elif fmt == 'xml':
assert_equals('text/xml', pagetype)
world.response_format = fmt
elif fmt in ('json', 'jsonv2'):
if 'json_callback' in world.params:
world.json_callback = world.params['json_callback'].encode('utf8')
assert world.page.startswith(world.json_callback + '(')
assert world.page.endswith(')')
world.page = world.page[(len(world.json_callback)+1):-1]
assert_equals('application/javascript', pagetype)
else:
assert_equals('application/json', pagetype)
world.response_format = 'json'
else:
if requesttype == 'reverse':
assert_equals('text/xml', pagetype)
world.response_format = 'xml'
else:
assert_equals('text/html', pagetype)
world.response_format = 'html'
logger.debug("Page received (%s):" % world.response_format)
logger.debug(world.page)
api_setup_prepare_params(None)
@before.each_scenario
def api_setup_prepare_params(scenario):
world.results = []
world.params = {}
world.header = {}
@step(u'the request parameters$')
def api_setup_parameters(step):
"""Define the parameters of the request as a hash.
Resets parameter list.
"""
world.params = step.hashes[0]
@step(u'the HTTP header$')
def api_setup_parameters(step):
"""Define additional HTTP header parameters as a hash.
Resets parameter list.
"""
world.header = step.hashes[0]
@step(u'sending( \w+)? search query "([^"]*)"( with address)?')
def api_setup_search(step, fmt, query, doaddr):
world.params['q'] = query.encode('utf8')
if doaddr:
world.params['addressdetails'] = 1
if fmt:
world.params['format'] = fmt.strip()
api_call('search')
@step(u'sending( \w+)? structured query( with address)?$')
def api_setup_structured_search(step, fmt, doaddr):
world.params.update(step.hashes[0])
if doaddr:
world.params['addressdetails'] = 1
if fmt:
world.params['format'] = fmt.strip()
api_call('search')
@step(u'looking up (\w+ )?coordinates ([-\d.]+),([-\d.]+)')
def api_setup_reverse(step, fmt, lat, lon):
world.params['lat'] = lat
world.params['lon'] = lon
if fmt and fmt.strip():
world.params['format'] = fmt.strip()
api_call('reverse')
@step(u'looking up place ([NRW]?\d+)')
def api_setup_details_reverse(step, obj):
if obj[0] in ('N', 'R', 'W'):
# an osm id
world.params['osm_type'] = obj[0]
world.params['osm_id'] = obj[1:]
else:
world.params['place_id'] = obj
api_call('reverse')
@step(u'looking up details for ([NRW]?\d+)')
def api_setup_details(step, obj):
if obj[0] in ('N', 'R', 'W'):
# an osm id
world.params['osmtype'] = obj[0]
world.params['osmid'] = obj[1:]
else:
world.params['place_id'] = obj
api_call('details')
@step(u'looking up (\w+) places ((?:[a-z]\d+,*)+)')
def api_setup_lookup(step, fmt, ids):
world.params['osm_ids'] = ids
if fmt and fmt.strip():
world.params['format'] = fmt.strip()
api_call('lookup')
@step(u'sending an API call (\w+)')
def api_general_call(step, call):
api_call(call)

View File

@ -1,201 +0,0 @@
""" Steps for checking the DB after import and update tests.
There are two groups of test here. The first group tests
the contents of db tables directly, the second checks
query results by using the command line query tool.
"""
from nose.tools import *
from lettuce import *
import psycopg2
import psycopg2.extensions
import psycopg2.extras
import os
import random
import json
import re
import logging
from collections import OrderedDict
logger = logging.getLogger(__name__)
@step(u'table placex contains as names for (N|R|W)(\d+)')
def check_placex_names(step, osmtyp, osmid):
""" Check for the exact content of the name hstore in placex.
"""
cur = world.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
cur.execute('SELECT name FROM placex where osm_type = %s and osm_id =%s', (osmtyp, int(osmid)))
for line in cur:
names = dict(line['name'])
for name in step.hashes:
assert_in(name['k'], names)
assert_equals(names[name['k']], name['v'])
del names[name['k']]
assert_equals(len(names), 0)
@step(u'table ([a-z_]+) contains$')
def check_placex_content(step, tablename):
""" check that the given lines are in the given table
Entries are searched by osm_type/osm_id and then all
given columns are tested. If there is more than one
line for an OSM object, they must match in these columns.
"""
try:
cur = world.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
for line in step.hashes:
osmtype, osmid, cls = world.split_id(line['object'])
q = 'SELECT *'
if tablename == 'placex':
q = q + ", ST_X(centroid) as clat, ST_Y(centroid) as clon"
if tablename == 'location_property_osmline':
q = q + ' FROM %s where osm_id = %%s' % (tablename,)
else:
q = q + ", ST_GeometryType(geometry) as geometrytype"
q = q + ' FROM %s where osm_type = %%s and osm_id = %%s' % (tablename,)
if cls is None:
if tablename == 'location_property_osmline':
params = (osmid,)
else:
params = (osmtype, osmid)
else:
q = q + ' and class = %s'
if tablename == 'location_property_osmline':
params = (osmid, cls)
else:
params = (osmtype, osmid, cls)
cur.execute(q, params)
assert(cur.rowcount > 0)
for res in cur:
for k,v in line.iteritems():
if not k == 'object':
assert_in(k, res)
if type(res[k]) is dict:
val = world.make_hash(v)
assert_equals(res[k], val)
elif k in ('parent_place_id', 'linked_place_id'):
pid = world.get_placeid(v)
assert_equals(pid, res[k], "Results for '%s'/'%s' differ: '%s' != '%s'" % (line['object'], k, pid, res[k]))
elif k == 'centroid':
world.match_geometry((res['clat'], res['clon']), v)
else:
assert_equals(str(res[k]), v, "Results for '%s'/'%s' differ: '%s' != '%s'" % (line['object'], k, str(res[k]), v))
finally:
cur.close()
world.conn.commit()
@step(u'table (placex?) has no entry for (N|R|W)(\d+)(:\w+)?')
def check_placex_missing(step, tablename, osmtyp, osmid, placeclass):
cur = world.conn.cursor()
try:
q = 'SELECT count(*) FROM %s where osm_type = %%s and osm_id = %%s' % (tablename, )
args = [osmtyp, int(osmid)]
if placeclass is not None:
q = q + ' and class = %s'
args.append(placeclass[1:])
cur.execute(q, args)
numres = cur.fetchone()[0]
assert_equals (numres, 0)
finally:
cur.close()
world.conn.commit()
@step(u'table location_property_osmline has no entry for W(\d+)?')
def check_osmline_missing(step, osmid):
cur = world.conn.cursor()
try:
q = 'SELECT count(*) FROM location_property_osmline where osm_id = %s' % (osmid, )
cur.execute(q)
numres = cur.fetchone()[0]
assert_equals (numres, 0)
finally:
cur.close()
world.conn.commit()
@step(u'search_name table contains$')
def check_search_name_content(step):
cur = world.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
for line in step.hashes:
placeid = world.get_placeid(line['place_id'])
cur.execute('SELECT * FROM search_name WHERE place_id = %s', (placeid,))
assert(cur.rowcount > 0)
for res in cur:
for k,v in line.iteritems():
if k in ('search_rank', 'address_rank'):
assert_equals(int(v), res[k], "Results for '%s'/'%s' differ: '%s' != '%d'" % (line['place_id'], k, v, res[k]))
elif k in ('importance'):
assert_equals(float(v), res[k], "Results for '%s'/'%s' differ: '%s' != '%d'" % (line['place_id'], k, v, res[k]))
elif k in ('name_vector', 'nameaddress_vector'):
terms = [x.strip().replace('#', ' ') for x in v.split(',')]
cur.execute('SELECT word_id, word_token FROM word, (SELECT unnest(%s) as term) t WHERE word_token = make_standard_name(t.term)', (terms,))
assert cur.rowcount >= len(terms)
for wid in cur:
assert_in(wid['word_id'], res[k], "Missing term for %s/%s: %s" % (line['place_id'], k, wid['word_token']))
elif k in ('country_code'):
assert_equals(v, res[k], "Results for '%s'/'%s' differ: '%s' != '%d'" % (line['place_id'], k, v, res[k]))
elif k == 'place_id':
pass
else:
raise Exception("Cannot handle field %s in search_name table" % (k, ))
@step(u'way (\d+) expands to lines')
def check_interpolation_lines(step, wayid):
"""Check that the correct interpolation line has been entered in
location_property_osmline for the given source line/nodes.
Expected are three columns:
startnumber, endnumber and linegeo
"""
lines = []
for line in step.hashes:
lines.append((line["startnumber"], line["endnumber"], line["geometry"]))
cur = world.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
cur.execute("""SELECT startnumber::text, endnumber::text, st_astext(linegeo) as geometry
FROM location_property_osmline WHERE osm_id = %s""",
(int(wayid),))
assert_equals(len(lines), cur.rowcount)
for r in cur:
linegeo = str(str(r["geometry"].split('(')[1]).split(')')[0]).replace(',', ', ')
exp = (r["startnumber"], r["endnumber"], linegeo)
assert_in(exp, lines)
lines.remove(exp)
@step(u'way (\d+) expands exactly to housenumbers ([0-9,]*)')
def check_interpolated_housenumber_list(step, nodeid, numberlist):
""" Checks that the interpolated house numbers corresponds
to the given list.
"""
expected = numberlist.split(',');
cur = world.conn.cursor()
cur.execute("""SELECT housenumber FROM placex
WHERE osm_type = 'W' and osm_id = %s
and class = 'place' and type = 'address'""", (int(nodeid),))
for r in cur:
assert_in(r[0], expected, "Unexpected house number %s for node %s." % (r[0], nodeid))
expected.remove(r[0])
assert_equals(0, len(expected), "Missing house numbers for way %s: %s" % (nodeid, expected))
@step(u'way (\d+) expands to no housenumbers')
def check_no_interpolated_housenumber_list(step, nodeid):
""" Checks that the interpolated house numbers corresponds
to the given list.
"""
cur = world.conn.cursor()
cur.execute("""SELECT housenumber FROM placex
WHERE osm_type = 'W' and osm_id = %s
and class = 'place' and type = 'address'""", (int(nodeid),))
res = [r[0] for r in cur]
assert_equals(0, len(res), "Unexpected house numbers for way %s: %s" % (nodeid, res))
@step(u'table search_name has no entry for (.*)')
def check_placex_missing(step, osmid):
""" Checks if there is an entry in the search index for the
given place object.
"""
cur = world.conn.cursor()
placeid = world.get_placeid(osmid)
cur.execute('SELECT count(*) FROM search_name WHERE place_id =%s', (placeid,))
numres = cur.fetchone()[0]
assert_equals (numres, 0)

View File

@ -1,278 +0,0 @@
""" Steps for setting up a test database with imports and updates.
There are two ways to state geometries for test data: with coordinates
and via scenes.
Coordinates should be given as a wkt without the enclosing type name.
Scenes are prepared geometries which can be found in the scenes/data/
directory. Each scene is saved in a .wkt file with its name, which
contains a list of id/wkt pairs. A scene can be set globally
for a scene by using the step `the scene <scene name>`. Then each
object should be refered to as `:<object id>`. A geometry can also
be referred to without loading the scene by explicitly stating the
scene: `<scene name>:<object id>`.
"""
from nose.tools import *
from lettuce import *
import psycopg2
import psycopg2.extensions
import psycopg2.extras
import os
import subprocess
import random
import base64
import sys
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
@before.each_scenario
def setup_test_database(scenario):
""" Creates a new test database from the template database
that was set up earlier in terrain.py. Will be done only
for scenarios whose feature is tagged with 'DB'.
"""
if scenario.feature.tags is not None and 'DB' in scenario.feature.tags:
world.db_template_setup()
world.write_nominatim_config(world.config.test_db)
conn = psycopg2.connect(database=world.config.template_db)
conn.set_isolation_level(0)
cur = conn.cursor()
cur.execute('DROP DATABASE IF EXISTS %s' % (world.config.test_db, ))
cur.execute('CREATE DATABASE %s TEMPLATE = %s' % (world.config.test_db, world.config.template_db))
conn.close()
world.conn = psycopg2.connect(database=world.config.test_db)
psycopg2.extras.register_hstore(world.conn, globally=False, unicode=True)
@step('a wiped database')
def db_setup_wipe_db(step):
"""Explicit DB scenario setup only needed
to work around a bug where scenario outlines don't call
before_each_scenario correctly.
"""
if hasattr(world, 'conn'):
world.conn.close()
conn = psycopg2.connect(database=world.config.template_db)
conn.set_isolation_level(0)
cur = conn.cursor()
cur.execute('DROP DATABASE IF EXISTS %s' % (world.config.test_db, ))
cur.execute('CREATE DATABASE %s TEMPLATE = %s' % (world.config.test_db, world.config.template_db))
conn.close()
world.conn = psycopg2.connect(database=world.config.test_db)
psycopg2.extras.register_hstore(world.conn, globally=False, unicode=True)
@after.each_scenario
def tear_down_test_database(scenario):
""" Drops any previously created test database.
"""
if hasattr(world, 'conn'):
world.conn.close()
if scenario.feature.tags is not None and 'DB' in scenario.feature.tags and not world.config.keep_scenario_db:
conn = psycopg2.connect(database=world.config.template_db)
conn.set_isolation_level(0)
cur = conn.cursor()
cur.execute('DROP DATABASE %s' % (world.config.test_db,))
conn.close()
def _format_placex_cols(cols, geomtype, force_name):
if 'name' in cols:
if cols['name'].startswith("'"):
cols['name'] = world.make_hash(cols['name'])
else:
cols['name'] = { 'name' : cols['name'] }
elif force_name:
cols['name'] = { 'name' : base64.urlsafe_b64encode(os.urandom(int(random.random()*30))) }
if 'extratags' in cols:
cols['extratags'] = world.make_hash(cols['extratags'])
if 'admin_level' not in cols:
cols['admin_level'] = 100
if 'geometry' in cols:
coords = world.get_scene_geometry(cols['geometry'])
if coords is None:
coords = "'%s(%s)'::geometry" % (geomtype, cols['geometry'])
else:
coords = "'%s'::geometry" % coords.wkt
cols['geometry'] = coords
for k in cols:
if not cols[k]:
cols[k] = None
def _insert_place_table_nodes(places, force_name):
cur = world.conn.cursor()
for line in places:
cols = dict(line)
cols['osm_type'] = 'N'
_format_placex_cols(cols, 'POINT', force_name)
if 'geometry' in cols:
coords = cols.pop('geometry')
else:
coords = "ST_Point(%f, %f)" % (random.random()*360 - 180, random.random()*180 - 90)
query = 'INSERT INTO place (%s,geometry) values(%s, ST_SetSRID(%s, 4326))' % (
','.join(cols.iterkeys()),
','.join(['%s' for x in range(len(cols))]),
coords
)
cur.execute(query, cols.values())
world.conn.commit()
def _insert_place_table_objects(places, geomtype, force_name):
cur = world.conn.cursor()
for line in places:
cols = dict(line)
if 'osm_type' not in cols:
cols['osm_type'] = 'W'
_format_placex_cols(cols, geomtype, force_name)
coords = cols.pop('geometry')
query = 'INSERT INTO place (%s, geometry) values(%s, ST_SetSRID(%s, 4326))' % (
','.join(cols.iterkeys()),
','.join(['%s' for x in range(len(cols))]),
coords
)
cur.execute(query, cols.values())
world.conn.commit()
@step(u'the scene (.*)')
def import_set_scene(step, scene):
world.load_scene(scene)
@step(u'the (named )?place (node|way|area)s')
def import_place_table_nodes(step, named, osmtype):
"""Insert a list of nodes into the place table.
Expects a table where columns are named in the same way as place.
"""
cur = world.conn.cursor()
cur.execute('ALTER TABLE place DISABLE TRIGGER place_before_insert')
if osmtype == 'node':
_insert_place_table_nodes(step.hashes, named is not None)
elif osmtype == 'way' :
_insert_place_table_objects(step.hashes, 'LINESTRING', named is not None)
elif osmtype == 'area' :
_insert_place_table_objects(step.hashes, 'POLYGON', named is not None)
cur.execute('ALTER TABLE place ENABLE TRIGGER place_before_insert')
cur.close()
world.conn.commit()
@step(u'the relations')
def import_fill_planet_osm_rels(step):
"""Adds a raw relation to the osm2pgsql table.
Three columns need to be suplied: id, tags, members.
"""
cur = world.conn.cursor()
for line in step.hashes:
members = []
parts = { 'n' : [], 'w' : [], 'r' : [] }
if line['members'].strip():
for mem in line['members'].split(','):
memparts = mem.strip().split(':', 2)
memid = memparts[0].lower()
parts[memid[0]].append(int(memid[1:]))
members.append(memid)
if len(memparts) == 2:
members.append(memparts[1])
else:
members.append('')
tags = []
for k,v in world.make_hash(line['tags']).iteritems():
tags.extend((k,v))
if not members:
members = None
cur.execute("""INSERT INTO planet_osm_rels
(id, way_off, rel_off, parts, members, tags)
VALUES (%s, %s, %s, %s, %s, %s)""",
(line['id'], len(parts['n']), len(parts['n']) + len(parts['w']),
parts['n'] + parts['w'] + parts['r'], members, tags))
world.conn.commit()
@step(u'the ways')
def import_fill_planet_osm_ways(step):
cur = world.conn.cursor()
for line in step.hashes:
if 'tags' in line:
tags = world.make_hash(line['tags'])
else:
tags = None
nodes = [int(x.strip()) for x in line['nodes'].split(',')]
cur.execute("""INSERT INTO planet_osm_ways (id, nodes, tags)
VALUES (%s, %s, %s)""",
(line['id'], nodes, tags))
world.conn.commit()
############### import and update steps #######################################
@step(u'importing')
def import_database(step):
""" Runs the actual indexing. """
world.run_nominatim_script('setup', 'create-functions', 'create-partition-functions')
cur = world.conn.cursor()
#world.db_dump_table('place')
cur.execute("""insert into placex (osm_type, osm_id, class, type, name, admin_level,
housenumber, street, addr_place, isin, postcode, country_code, extratags,
geometry) select * from place where not (class='place' and type='houses' and osm_type='W')""")
cur.execute("""select insert_osmline (osm_id, housenumber, street, addr_place, postcode, country_code, geometry) from place where class='place' and type='houses' and osm_type='W'""")
world.conn.commit()
world.run_nominatim_script('setup', 'index', 'index-noanalyse')
#world.db_dump_table('placex')
#world.db_dump_table('location_property_osmline')
@step(u'updating place (node|way|area)s')
def update_place_table_nodes(step, osmtype):
""" Replace a geometry in place by reinsertion and reindex database."""
world.run_nominatim_script('setup', 'create-functions', 'create-partition-functions', 'enable-diff-updates')
if osmtype == 'node':
_insert_place_table_nodes(step.hashes, False)
elif osmtype == 'way':
_insert_place_table_objects(step.hashes, 'LINESTRING', False)
elif osmtype == 'area':
_insert_place_table_objects(step.hashes, 'POLYGON', False)
world.run_nominatim_script('update', 'index')
@step(u'marking for delete (.*)')
def update_delete_places(step, places):
""" Remove an entry from place and reindex database.
"""
world.run_nominatim_script('setup', 'create-functions', 'create-partition-functions', 'enable-diff-updates')
cur = world.conn.cursor()
for place in places.split(','):
osmtype, osmid, cls = world.split_id(place)
if cls is None:
q = "delete from place where osm_type = %s and osm_id = %s"
params = (osmtype, osmid)
else:
q = "delete from place where osm_type = %s and osm_id = %s and class = %s"
params = (osmtype, osmid, cls)
cur.execute(q, params)
world.conn.commit()
#world.db_dump_table('placex')
world.run_nominatim_script('update', 'index')
@step(u'sending query "(.*)"( with dups)?$')
def query_cmd(step, query, with_dups):
""" Results in standard query output. The same tests as for API queries
can be used.
"""
cmd = [os.path.join(world.config.source_dir, 'utils', 'query.php'),
'--search', query]
if with_dups is not None:
cmd.append('--nodedupe')
proc = subprocess.Popen(cmd, cwd=world.config.source_dir,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(outp, err) = proc.communicate()
assert (proc.returncode == 0), "query.php failed with message: %s" % err
world.page = outp
world.response_format = 'json'
world.request_type = 'search'
world.returncode = 200

View File

@ -1,214 +0,0 @@
""" Steps for setting up a test database for osm2pgsql import.
Note that osm2pgsql features need a database and therefore need
to be tagged with @DB.
"""
from nose.tools import *
from lettuce import *
import logging
import random
import tempfile
import os
import subprocess
logger = logging.getLogger(__name__)
@before.each_scenario
def osm2pgsql_setup_test(scenario):
world.osm2pgsql = []
@step(u'the osm nodes:')
def osm2pgsql_import_nodes(step):
""" Define a list of OSM nodes to be imported, given as a table.
Each line describes one node with all its attributes.
'id' is mendatory, all other fields are filled with random values
when not given. If 'tags' is missing an empty tag list is assumed.
For updates, a mandatory 'action' column needs to contain 'A' (add),
'M' (modify), 'D' (delete).
"""
for line in step.hashes:
node = { 'type' : 'N', 'version' : '1', 'timestamp': "2012-05-01T15:06:20Z",
'changeset' : "11470653", 'uid' : "122294", 'user' : "foo"
}
node.update(line)
node['id'] = int(node['id'])
if 'geometry' in node:
lat, lon = node['geometry'].split(' ')
node['lat'] = float(lat)
node['lon'] = float(lon)
else:
node['lon'] = random.random()*360 - 180
node['lat'] = random.random()*180 - 90
if 'tags' in node:
node['tags'] = world.make_hash(line['tags'])
else:
node['tags'] = {}
world.osm2pgsql.append(node)
@step(u'the osm ways:')
def osm2pgsql_import_ways(step):
""" Define a list of OSM ways to be imported.
"""
for line in step.hashes:
way = { 'type' : 'W', 'version' : '1', 'timestamp': "2012-05-01T15:06:20Z",
'changeset' : "11470653", 'uid' : "122294", 'user' : "foo"
}
way.update(line)
way['id'] = int(way['id'])
if 'tags' in way:
way['tags'] = world.make_hash(line['tags'])
else:
way['tags'] = None
way['nodes'] = way['nodes'].strip().split()
world.osm2pgsql.append(way)
membertype = { 'N' : 'node', 'W' : 'way', 'R' : 'relation' }
@step(u'the osm relations:')
def osm2pgsql_import_rels(step):
""" Define a list of OSM relation to be imported.
"""
for line in step.hashes:
rel = { 'type' : 'R', 'version' : '1', 'timestamp': "2012-05-01T15:06:20Z",
'changeset' : "11470653", 'uid' : "122294", 'user' : "foo"
}
rel.update(line)
rel['id'] = int(rel['id'])
if 'tags' in rel:
rel['tags'] = world.make_hash(line['tags'])
else:
rel['tags'] = {}
members = []
if rel['members'].strip():
for mem in line['members'].split(','):
memparts = mem.strip().split(':', 2)
memid = memparts[0].upper()
members.append((membertype[memid[0]],
memid[1:],
memparts[1] if len(memparts) == 2 else ''
))
rel['members'] = members
world.osm2pgsql.append(rel)
def _sort_xml_entries(x, y):
if x['type'] == y['type']:
return cmp(x['id'], y['id'])
else:
return cmp('NWR'.find(x['type']), 'NWR'.find(y['type']))
def write_osm_obj(fd, obj):
if obj['type'] == 'N':
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)
if obj['tags'] is None:
fd.write('/>\n')
else:
fd.write('>\n')
for k,v in obj['tags'].iteritems():
fd.write(' <tag k="%s" v="%s"/>\n' % (k, v))
fd.write('</node>\n')
elif obj['type'] == 'W':
fd.write('<way id="%(id)d" version="%(version)s" changeset="%(changeset)s" timestamp="%(timestamp)s" user="%(user)s" uid="%(uid)s">\n' % obj)
for nd in obj['nodes']:
fd.write('<nd ref="%s" />\n' % (nd,))
for k,v in obj['tags'].iteritems():
fd.write(' <tag k="%s" v="%s"/>\n' % (k, v))
fd.write('</way>\n')
elif obj['type'] == 'R':
fd.write('<relation id="%(id)d" version="%(version)s" changeset="%(changeset)s" timestamp="%(timestamp)s" user="%(user)s" uid="%(uid)s">\n' % obj)
for mem in obj['members']:
fd.write(' <member type="%s" ref="%s" role="%s"/>\n' % mem)
for k,v in obj['tags'].iteritems():
fd.write(' <tag k="%s" v="%s"/>\n' % (k, v))
fd.write('</relation>\n')
@step(u'loading osm data')
def osm2pgsql_load_place(step):
"""Imports the previously defined OSM data into a fresh copy of a
Nominatim test database.
"""
world.osm2pgsql.sort(cmp=_sort_xml_entries)
# create a OSM file in /tmp
with tempfile.NamedTemporaryFile(dir='/tmp', suffix='.osm', delete=False) as fd:
fname = fd.name
fd.write("<?xml version='1.0' encoding='UTF-8'?>\n")
fd.write('<osm version="0.6" generator="test-nominatim" timestamp="2014-08-26T20:22:02Z">\n')
fd.write('\t<bounds minlat="43.72335" minlon="7.409205" maxlat="43.75169" maxlon="7.448637"/>\n')
for obj in world.osm2pgsql:
write_osm_obj(fd, obj)
fd.write('</osm>\n')
logger.debug( "Filename: %s" % fname)
cmd = [os.path.join(world.config.source_dir, 'utils', 'setup.php')]
cmd.extend(['--osm-file', fname, '--import-data','--osm2pgsql-cache', '300'])
proc = subprocess.Popen(cmd, cwd=world.config.source_dir,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(outp, outerr) = proc.communicate()
assert (proc.returncode == 0), "OSM data import failed:\n%s\n%s\n" % (outp, outerr)
### reintroduce the triggers/indexes we've lost by having osm2pgsql set up place again
cur = world.conn.cursor()
cur.execute("""CREATE TRIGGER place_before_delete BEFORE DELETE ON place
FOR EACH ROW EXECUTE PROCEDURE place_delete()""")
cur.execute("""CREATE TRIGGER place_before_insert BEFORE INSERT ON place
FOR EACH ROW EXECUTE PROCEDURE place_insert()""")
cur.execute("""CREATE UNIQUE INDEX idx_place_osm_unique on place using btree(osm_id,osm_type,class,type)""")
world.conn.commit()
os.remove(fname)
world.osm2pgsql = []
actiontypes = { 'C' : 'create', 'M' : 'modify', 'D' : 'delete' }
@step(u'updating osm data')
def osm2pgsql_update_place(step):
"""Creates an osc file from the previously defined data and imports it
into the database.
"""
world.run_nominatim_script('setup', 'create-functions', 'create-partition-functions')
cur = world.conn.cursor()
cur.execute("""insert into placex (osm_type, osm_id, class, type, name, admin_level,
housenumber, street, addr_place, isin, postcode, country_code, extratags,
geometry) select * from place""")
world.conn.commit()
world.run_nominatim_script('setup', 'index', 'index-noanalyse')
world.run_nominatim_script('setup', 'create-functions', 'create-partition-functions', 'enable-diff-updates')
with tempfile.NamedTemporaryFile(dir='/tmp', suffix='.osc', delete=False) as fd:
fname = fd.name
fd.write("<?xml version='1.0' encoding='UTF-8'?>\n")
fd.write('<osmChange version="0.6" generator="Osmosis 0.43.1">\n')
for obj in world.osm2pgsql:
fd.write('<%s>\n' % (actiontypes[obj['action']], ))
write_osm_obj(fd, obj)
fd.write('</%s>\n' % (actiontypes[obj['action']], ))
fd.write('</osmChange>\n')
logger.debug( "Filename: %s" % fname)
cmd = [os.path.join(world.config.source_dir, 'utils', 'update.php')]
cmd.extend(['--import-diff', fname])
proc = subprocess.Popen(cmd, cwd=world.config.source_dir,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(outp, outerr) = proc.communicate()
assert (proc.returncode == 0), "OSM data update failed:\n%s\n%s\n" % (outp, outerr)
os.remove(fname)
world.osm2pgsql = []

View File

@ -1,255 +0,0 @@
from lettuce import *
from nose.tools import *
import logging
import os
import subprocess
import psycopg2
import re
from haversine import haversine
from shapely.wkt import loads as wkt_load
from shapely.ops import linemerge
logger = logging.getLogger(__name__)
class NominatimConfig:
def __init__(self):
# logging setup
loglevel = getattr(logging, os.environ.get('LOGLEVEL','info').upper())
if 'LOGFILE' in os.environ:
logging.basicConfig(filename=os.environ.get('LOGFILE','run.log'),
level=loglevel)
else:
logging.basicConfig(level=loglevel)
# Nominatim test setup
self.base_url = os.environ.get('NOMINATIM_SERVER', 'http://localhost/nominatim')
self.source_dir = os.path.abspath(os.environ.get('NOMINATIM_DIR', '../build'))
self.template_db = os.environ.get('TEMPLATE_DB', 'test_template_nominatim')
self.test_db = os.environ.get('TEST_DB', 'test_nominatim')
self.local_settings_file = os.environ.get('NOMINATIM_SETTINGS', '/tmp/nominatim_settings.php')
self.reuse_template = 'NOMINATIM_REMOVE_TEMPLATE' not in os.environ
self.keep_scenario_db = 'NOMINATIM_KEEP_SCENARIO_DB' in os.environ
os.environ['NOMINATIM_SETTINGS'] = '/tmp/nominatim_settings.php'
scriptpath = os.path.dirname(os.path.abspath(__file__))
self.scene_path = os.environ.get('SCENE_PATH',
os.path.join(scriptpath, '..', 'scenes', 'data'))
def __str__(self):
return 'Server URL: %s\nSource dir: %s\n' % (self.base_url, self.source_dir)
world.config = NominatimConfig()
@world.absorb
def write_nominatim_config(dbname):
f = open(world.config.local_settings_file, 'w')
f.write("<?php\n @define('CONST_Database_DSN', 'pgsql://@/%s');\n" % dbname)
f.close()
@world.absorb
def run_nominatim_script(script, *args):
cmd = [os.path.join(world.config.source_dir, 'utils', '%s.php' % script)]
cmd.extend(['--%s' % x for x in args])
proc = subprocess.Popen(cmd, cwd=world.config.source_dir,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(outp, outerr) = proc.communicate()
logger.debug("run_nominatim_script: %s\n%s\n%s" % (cmd, outp, outerr))
assert (proc.returncode == 0), "Script '%s' failed:\n%s\n%s\n" % (script, outp, outerr)
@world.absorb
def make_hash(inp):
return eval('{' + inp + '}')
@world.absorb
def split_id(oid):
""" Splits a unique identifier for places into its components.
As place_ids cannot be used for testing, we use a unique
identifier instead that is of the form <osmtype><osmid>[:class].
"""
oid = oid.strip()
if oid == 'None':
return None, None, None
osmtype = oid[0]
assert_in(osmtype, ('R','N','W'))
if ':' in oid:
osmid, cls = oid[1:].split(':')
return (osmtype, int(osmid), cls)
else:
return (osmtype, int(oid[1:]), None)
@world.absorb
def get_placeid(oid):
""" Tries to retrive the place_id for a unique identifier. """
if oid[0].isdigit():
return int(oid)
osmtype, osmid, cls = world.split_id(oid)
if osmtype is None:
return None
cur = world.conn.cursor()
if cls is None:
q = 'SELECT place_id FROM placex where osm_type = %s and osm_id = %s'
params = (osmtype, osmid)
else:
q = 'SELECT place_id FROM placex where osm_type = %s and osm_id = %s and class = %s'
params = (osmtype, osmid, cls)
cur.execute(q, params)
assert_equals(cur.rowcount, 1, "%d rows found for place %s" % (cur.rowcount, oid))
return cur.fetchone()[0]
@world.absorb
def match_geometry(coord, matchstring):
m = re.match(r'([-0-9.]+),\s*([-0-9.]+)\s*(?:\+-([0-9.]+)([a-z]+)?)?', matchstring)
assert_is_not_none(m, "Invalid match string")
logger.debug("Distmatch: %s/%s %s %s" % (m.group(1), m.group(2), m.group(3), m.group(4) ))
dist = haversine(coord, (float(m.group(1)), float(m.group(2))))
if m.group(3) is not None:
expdist = float(m.group(3))
if m.group(4) is not None:
if m.group(4) == 'm':
expdist = expdist/1000
elif m.group(4) == 'km':
pass
else:
raise Exception("Unknown unit '%s' in geometry match" % (m.group(4), ))
else:
expdist = 0
logger.debug("Distances expected: %f, got: %f" % (expdist, dist))
assert dist <= expdist, "Geometry too far away, expected: %f, got: %f" % (expdist, dist)
@world.absorb
def print_statement(element):
print '\n\n\n'+str(element)+'\n\n\n'
@world.absorb
def db_dump_table(table):
cur = world.conn.cursor()
cur.execute('SELECT * FROM %s' % table)
print '\n\n\n<<<<<<< BEGIN OF TABLE DUMP %s' % table
for res in cur:
print res
print '<<<<<<< END OF TABLE DUMP %s\n\n\n' % table
@world.absorb
def db_drop_database(name):
conn = psycopg2.connect(database='postgres')
conn.set_isolation_level(0)
cur = conn.cursor()
cur.execute('DROP DATABASE IF EXISTS %s' % (name, ))
conn.close()
world.is_template_set_up = False
@world.absorb
def db_template_setup():
""" Set up a template database, containing all tables
but not yet any functions.
"""
if world.is_template_set_up:
return
world.is_template_set_up = True
world.write_nominatim_config(world.config.template_db)
if world.config.reuse_template:
# check that the template is there
conn = psycopg2.connect(database='postgres')
cur = conn.cursor()
cur.execute('select count(*) from pg_database where datname = %s',
(world.config.template_db,))
if cur.fetchone()[0] == 1:
return
else:
# just in case... make sure a previous table has been dropped
world.db_drop_database(world.config.template_db)
# call the first part of database setup
world.run_nominatim_script('setup', 'create-db', 'setup-db')
# remove external data to speed up indexing for tests
conn = psycopg2.connect(database=world.config.template_db)
psycopg2.extras.register_hstore(conn, globally=False, unicode=True)
cur = conn.cursor()
for table in ('gb_postcode', 'us_postcode'):
cur.execute("select * from pg_tables where tablename = '%s'" % (table, ))
if cur.rowcount > 0:
cur.execute('TRUNCATE TABLE %s' % (table,))
conn.commit()
conn.close()
# execute osm2pgsql on an empty file to get the right tables
osm2pgsql = os.path.join(world.config.source_dir, 'osm2pgsql', 'osm2pgsql')
proc = subprocess.Popen([osm2pgsql, '-lsc', '-r', 'xml', '-O', 'gazetteer', '-d', world.config.template_db, '-'],
cwd=world.config.source_dir, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
[outstr, errstr] = proc.communicate(input='<osm version="0.6"></osm>')
logger.debug("running osm2pgsql for template: %s\n%s\n%s" % (osm2pgsql, outstr, errstr))
world.run_nominatim_script('setup', 'create-functions', 'create-tables', 'create-partition-tables', 'create-partition-functions', 'load-data', 'create-search-indices')
# Leave the table around so it can be reused again after a non-reuse test round.
#@after.all
def db_template_teardown(total):
""" Set up a template database, containing all tables
but not yet any functions.
"""
if world.is_template_set_up:
# remove template DB
if not world.config.reuse_template:
world.db_drop_database(world.config.template_db)
try:
os.remove(world.config.local_settings_file)
except OSError:
pass # ignore missing file
##########################################################################
#
# Data scene handling
#
world.scenes = {}
world.current_scene = None
@world.absorb
def load_scene(name):
if name in world.scenes:
world.current_scene = world.scenes[name]
else:
with open(os.path.join(world.config.scene_path, "%s.wkt" % name), 'r') as fd:
scene = {}
for line in fd:
if line.strip():
obj, wkt = line.split('|', 2)
wkt = wkt.strip()
scene[obj.strip()] = wkt_load(wkt)
world.scenes[name] = scene
world.current_scene = scene
@world.absorb
def get_scene_geometry(name):
if not ':' in name:
# Not a scene description
return None
geoms = []
for obj in name.split('+'):
oname = obj.strip()
if oname.startswith(':'):
geoms.append(world.current_scene[oname[1:]])
else:
scene, obj = oname.split(':', 2)
oldscene = world.current_scene
world.load_scene(scene)
wkt = world.current_scene[obj]
world.current_scene = oldscene
geoms.append(wkt)
if len(geoms) == 1:
return geoms[0]
else:
return linemerge(geoms)