remove usage of behave

This commit is contained in:
Sarah Hoffmann
2025-04-09 14:57:39 +02:00
parent b34991d85f
commit d95e9737da
40 changed files with 3 additions and 8086 deletions

View File

@@ -94,7 +94,7 @@ jobs:
if: matrix.dependencies == 'pip' if: matrix.dependencies == 'pip'
- name: Install test prerequisites - name: Install test prerequisites
run: ./venv/bin/pip install behave==1.2.6 pytest-bdd run: ./venv/bin/pip install pytest-bdd
- name: Install latest flake8 - name: Install latest flake8
run: ./venv/bin/pip install -U flake8 run: ./venv/bin/pip install -U flake8
@@ -118,9 +118,7 @@ jobs:
- name: BDD tests - name: BDD tests
run: | run: |
../venv/bin/python -m pytest test/bdd ../venv/bin/python -m pytest test/bdd --nominatim-purge
cd test/bdd
../../../venv/bin/python -m behave -DREMOVE_TEMPLATE=1 --format=progress3 db osm2pgsql
working-directory: Nominatim working-directory: Nominatim
install: install:

View File

@@ -27,8 +27,7 @@ lint:
flake8 src test/python test/bdd flake8 src test/python test/bdd
bdd: bdd:
pytest test/bdd pytest test/bdd --nominatim-purge
cd test/bdd; behave -DREMOVE_TEMPLATE=1 db osm2pgsql
# Documentation # Documentation

View File

@@ -1,10 +0,0 @@
all: bdd python
bdd:
cd bdd && behave -DREMOVE_TEMPLATE=1
python:
pytest python
.PHONY: bdd python

View File

@@ -1,3 +0,0 @@
[behave]
show_skipped=False
default_tags=~@Fail

View File

@@ -1,565 +0,0 @@
@DB
Feature: Address computation
Tests for filling of place_addressline
Scenario: place nodes are added to the address when they are close enough
Given the 0.002 grid
| 2 | | | | | | 1 | | 3 |
And the places
| osm | class | type | name | geometry |
| N1 | place | square | Square | 1 |
| N2 | place | hamlet | West Farm | 2 |
| N3 | place | hamlet | East Farm | 3 |
When importing
Then place_addressline contains
| object | address | fromarea |
| N1 | N3 | False |
Then place_addressline doesn't contain
| object | address |
| N1 | N2 |
When sending search query "Square"
Then results contain
| osm | display_name |
| N1 | Square, East Farm |
Scenario: given two place nodes, the closer one wins for the address
Given the grid
| 2 | | | 1 | | 3 |
And the named places
| osm | class | type | geometry |
| N1 | place | square | 1 |
| N2 | place | hamlet | 2 |
| N3 | place | hamlet | 3 |
When importing
Then place_addressline contains
| object | address | fromarea | isaddress |
| N1 | N3 | False | True |
| N1 | N2 | False | False |
Scenario: boundaries around the place are added to the address
Given the grid
| 1 | | 4 | | 7 | 10 |
| 2 | | 5 | | 8 | 11 |
| | | | | | |
| | | | | | |
| | | 6 | | 9 | |
| | 99 | | | | |
| 3 | | | | | 12 |
And the named places
| osm | class | type | admin | geometry |
| R1 | boundary | administrative | 3 | (1,2,3,12,11,10,7,8,9,6,5,4,1) |
| R2 | boundary | administrative | 4 | (2,3,12,11,8,9,6,5,2) |
| N1 | place | square | 15 | 99 |
When importing
Then place_addressline contains
| object | address | isaddress |
| N1 | R1 | True |
| N1 | R2 | True |
Scenario: with boundaries of same rank the one with the closer centroid is preferred
Given the grid
| 1 | | | 3 | | 5 |
| | 9 | | | | |
| 2 | | | 4 | | 6 |
And the named places
| osm | class | type | admin | geometry |
| R1 | boundary | administrative | 8 | (1,2,4,3,1) |
| R2 | boundary | administrative | 8 | (1,2,6,5,1) |
| N1 | place | square | 15 | 9 |
When importing
Then place_addressline contains
| object | address | isaddress |
| N1 | R1 | True |
| N1 | R2 | False |
Scenario: boundary areas are preferred over place nodes in the address
Given the grid
| 1 | | | | 10 | | 3 |
| | 5 | | | | | |
| | 6 | | | | | |
| 2 | | | | 11 | | 4 |
And the named places
| osm | class | type | admin | geometry |
| N1 | place | square | 15 | 5 |
| N2 | place | city | 15 | 6 |
| R1 | place | city | 8 | (1,2,4,3,1) |
| R2 | boundary | administrative | 9 | (1,10,11,2,1) |
When importing
Then place_addressline contains
| object | address | isaddress | cached_rank_address |
| N1 | R1 | True | 16 |
| N1 | R2 | True | 18 |
| N1 | N2 | False | 18 |
Scenario: place nodes outside a smaller ranked area are ignored
Given the grid
| 1 | | 2 | |
| | 7 | | 9 |
| 4 | | 3 | |
And the named places
| osm | class | type | admin | geometry |
| N1 | place | square | 15 | 7 |
| N2 | place | city | 15 | 9 |
| R1 | place | city | 8 | (1,2,3,4,1) |
When importing
Then place_addressline contains
| object | address | isaddress | cached_rank_address |
| N1 | R1 | True | 16 |
And place_addressline doesn't contain
| object | address |
| N1 | N2 |
Scenario: place nodes close enough to smaller ranked place nodes are included
Given the 0.002 grid
| 2 | | 3 | 1 |
And the named places
| osm | class | type | geometry |
| N1 | place | square | 1 |
| N2 | place | hamlet | 2 |
| N3 | place | quarter | 3 |
When importing
Then place_addressline contains
| object | address | fromarea | isaddress |
| N1 | N2 | False | True |
| N1 | N3 | False | True |
Scenario: place nodes too far away from a smaller ranked place nodes are marked non-address
Given the 0.002 grid
| 2 | | | 1 | | 3 |
And the named places
| osm | class | type | geometry |
| N1 | place | square | 1 |
| N2 | place | hamlet | 2 |
| N3 | place | quarter | 3 |
When importing
Then place_addressline contains
| object | address | fromarea | isaddress |
| N1 | N2 | False | True |
| N1 | N3 | False | False |
# github #121
Scenario: Roads crossing boundaries should contain both states
Given the grid
| 1 | | | 2 | | 3 |
| | 7 | | | 8 | |
| 4 | | | 5 | | 6 |
And the named places
| osm | class | type | geometry |
| W1 | highway | road | 7, 8 |
And the named places
| osm | class | type | admin | geometry |
| W10 | boundary | administrative | 5 | (1, 2, 5, 4, 1) |
| W11 | boundary | administrative | 5 | (2, 3, 6, 5, 2) |
When importing
Then place_addressline contains
| object | address | cached_rank_address |
| W1 | W10 | 10 |
| W1 | W11 | 10 |
Scenario: Roads following a boundary should contain both states
Given the grid
| 1 | | | 2 | | 3 |
| | | 8 | 7 | | |
| 4 | | | 5 | | 6 |
And the named places
| osm | class | type | geometry |
| W1 | highway | road | 2, 7, 8 |
And the named places
| osm | class | type | admin | geometry |
| W10 | boundary | administrative | 5 | (1, 2, 5, 4, 1) |
| W11 | boundary | administrative | 5 | (2, 3, 6, 5, 2) |
When importing
Then place_addressline contains
| object | address | cached_rank_address |
| W1 | W10 | 10 |
| W1 | W11 | 10 |
Scenario: Roads should not contain boundaries they touch in a end point
Given the grid
| 1 | | | 2 | | 3 |
| | 7 | | 8 | | |
| 4 | | | 5 | | 6 |
And the named places
| osm | class | type | geometry |
| W1 | highway | road | 7, 8 |
And the named places
| osm | class | type | admin | geometry |
| W10 | boundary | administrative | 5 | (1, 2, 8, 5, 4, 1) |
| W11 | boundary | administrative | 5 | (2, 3, 6, 5, 8, 2) |
When importing
Then place_addressline contains
| object | address | cached_rank_address |
| W1 | W10 | 10 |
Then place_addressline doesn't contain
| object | address |
| W1 | W11 |
Scenario: Roads should not contain boundaries they touch in a middle point
Given the grid
| 1 | | | 2 | | 3 |
| | 7 | | 8 | | |
| 4 | | 9 | 5 | | 6 |
And the named places
| osm | class | type | geometry |
| W1 | highway | road | 7, 8, 9 |
And the named places
| osm | class | type | admin | geometry |
| W10 | boundary | administrative | 5 | (1, 2, 8, 5, 4, 1) |
| W11 | boundary | administrative | 5 | (2, 3, 6, 5, 8, 2) |
When importing
Then place_addressline contains
| object | address | cached_rank_address |
| W1 | W10 | 10 |
Then place_addressline doesn't contain
| object | address |
| W1 | W11 |
Scenario: Locality points should contain all boundaries they touch
Given the 0.001 grid
| 1 | | | 2 | | 3 |
| | | | 8 | | |
| 4 | | | 5 | | 6 |
And the named places
| osm | class | type | geometry |
| N1 | place | locality | 8 |
And the named places
| osm | class | type | admin | geometry |
| W10 | boundary | administrative | 5 | (1, 2, 8, 5, 4, 1) |
| W11 | boundary | administrative | 5 | (2, 3, 6, 5, 8, 2) |
When importing
Then place_addressline contains
| object | address | cached_rank_address |
| N1 | W10 | 10 |
| N1 | W11 | 10 |
Scenario: Areas should not contain boundaries they touch
Given the grid
| 1 | | | 2 | | 3 |
| | | | | | |
| 4 | | | 5 | | 6 |
And the named places
| osm | class | type | geometry |
| W1 | landuse | industrial | (1, 2, 5, 4, 1) |
And the named places
| osm | class | type | admin | geometry |
| W10 | boundary | administrative | 5 | (2, 3, 6, 5, 2) |
When importing
Then place_addressline doesn't contain
| object | address |
| W1 | W10 |
Scenario: buildings with only addr:postcodes do not appear in the address of a way
Given the grid with origin DE
| 1 | | | | | 8 | | 6 | | 2 |
| |10 |11 | | | | | | | |
| |13 |12 | | | | | | | |
| 20| | | 21| | | | | | |
| | | | | | | | | | |
| | | | | | 9 | | | | |
| 4 | | | | | | | 7 | | 3 |
And the named places
| osm | class | type | admin | addr+postcode | geometry |
| R1 | boundary | administrative | 6 | 10000 | (1,2,3,4,1)|
| R34 | boundary | administrative | 8 | 11200 | (1,6,7,4,1)|
| R4 | boundary | administrative | 10 | 11230 | (1,8,9,4,1)|
And the named places
| osm | class | type | geometry |
| W93 | highway | residential | 20,21 |
And the places
| osm | class | type | addr+postcode | geometry |
| W22 | place | postcode | 11234 | (10,11,12,13,10) |
When importing
Then place_addressline doesn't contain
| object | address |
| W93 | W22 |
Scenario: postcode boundaries do appear in the address of a way
Given the grid with origin DE
| 1 | | | | | 8 | | 6 | | 2 |
| |10 |11 | | | | | | | |
| |13 |12 | | | | | | | |
| 20| | | 21| | | | | | |
| | | | | | | | | | |
| | | | | | 9 | | | | |
| 4 | | | | | | | 7 | | 3 |
And the named places
| osm | class | type | admin | addr+postcode | geometry |
| R1 | boundary | administrative | 6 | 10000 | (1,2,3,4,1) |
| R34 | boundary | administrative | 8 | 11000 | (1,6,7,4,1) |
And the places
| osm | class | type | addr+postcode | geometry |
| R4 | boundary | postal_code | 11200 | (1,8,9,4,1) |
And the named places
| osm | class | type | geometry |
| W93 | highway | residential | 20,21 |
And the places
| osm | class | type | addr+postcode | geometry |
| W22 | place | postcode | 11234 | (10,11,12,13,10) |
When importing
Then place_addressline contains
| object | address |
| W93 | R4 |
Scenario: squares do not appear in the address of a street
Given the grid
| | 1 | | 2 | |
| 8 | | | | 9 |
| | 4 | | 3 | |
And the named places
| osm | class | type | geometry |
| W1 | highway | residential | 8, 9 |
| W2 | place | square | (1, 2, 3 ,4, 1) |
When importing
Then place_addressline doesn't contain
| object | address |
| W1 | W2 |
Scenario: addr:* tags are honored even when a street is far away from the place
Given the grid
| 1 | | 2 | | | 5 |
| | | | 8 | 9 | |
| 4 | | 3 | | | 6 |
And the places
| osm | class | type | admin | name | geometry |
| R1 | boundary | administrative | 8 | Left | (1,2,3,4,1) |
| R2 | boundary | administrative | 8 | Right | (2,3,6,5,2) |
And the places
| osm | class | type | addr+city | geometry |
| W1 | highway | primary | Left | 8,9 |
| W2 | highway | primary | Right | 8,9 |
When importing
Then place_addressline contains
| object | address | isaddress |
| W1 | R1 | True |
| W1 | R2 | False |
| W2 | R2 | True |
And place_addressline doesn't contain
| object | address |
| W2 | R1 |
Scenario: addr:* tags are honored even when a POI is far away from the place
Given the grid
| 1 | | 2 | | | 5 |
| | | | 8 | 9 | |
| 4 | | 3 | | | 6 |
And the places
| osm | class | type | admin | name | geometry |
| R1 | boundary | administrative | 8 | Left | (1,2,3,4,1) |
| R2 | boundary | administrative | 8 | Right | (2,3,6,5,2) |
And the places
| osm | class | type | name | addr+city | geometry |
| W1 | highway | primary | Wonderway | Right | 8,9 |
| N1 | amenity | cafe | Bolder | Left | 9 |
When importing
Then place_addressline contains
| object | address | isaddress |
| W1 | R2 | True |
| N1 | R1 | True |
And place_addressline doesn't contain
| object | address |
| W1 | R1 |
When sending search query "Bolder"
Then results contain
| osm | display_name |
| N1 | Bolder, Wonderway, Left |
Scenario: addr:* tags do not produce addresslines when the parent has the address part
Given the grid
| 1 | | | 5 |
| | 8 | 9 | |
| 4 | | | 6 |
And the places
| osm | class | type | admin | name | geometry |
| R1 | boundary | administrative | 8 | Outer | (1,5,6,4,1) |
And the places
| osm | class | type | name | addr+city | geometry |
| W1 | highway | primary | Wonderway | Outer | 8,9 |
| N1 | amenity | cafe | Bolder | Outer | 9 |
When importing
Then place_addressline contains
| object | address | isaddress |
| W1 | R1 | True |
And place_addressline doesn't contain
| object | address |
| N1 | R1 |
When sending search query "Bolder"
Then results contain
| osm | display_name |
| N1 | Bolder, Wonderway, Outer |
Scenario: addr:* tags on outside do not produce addresslines when the parent has the address part
Given the grid
| 1 | | 2 | | | 5 |
| | | | 8 | 9 | |
| 4 | | 3 | | | 6 |
And the places
| osm | class | type | admin | name | geometry |
| R1 | boundary | administrative | 8 | Left | (1,2,3,4,1) |
| R2 | boundary | administrative | 8 | Right | (2,3,6,5,2) |
And the places
| osm | class | type | name | addr+city | geometry |
| W1 | highway | primary | Wonderway | Left | 8,9 |
| N1 | amenity | cafe | Bolder | Left | 9 |
When importing
Then place_addressline contains
| object | address | isaddress |
| W1 | R1 | True |
| W1 | R2 | False |
And place_addressline doesn't contain
| object | address |
| N1 | R1 |
When sending search query "Bolder"
Then results contain
| osm | display_name |
| N1 | Bolder, Wonderway, Left |
Scenario: POIs can correct address parts on the fly
Given the grid
| 1 | | | | 2 | | 5 |
| | | | 9 | | 8 | |
| 4 | | | | 3 | | 6 |
And the places
| osm | class | type | admin | name | geometry |
| R1 | boundary | administrative | 8 | Left | (1,2,3,4,1) |
| R2 | boundary | administrative | 8 | Right | (2,3,6,5,2) |
And the places
| osm | class | type | name | geometry |
| W1 | highway | primary | Wonderway | 2,3 |
| N1 | amenity | cafe | Bolder | 9 |
| N2 | amenity | cafe | Leftside | 8 |
When importing
Then place_addressline contains
| object | address | isaddress |
| W1 | R1 | False |
| W1 | R2 | True |
And place_addressline doesn't contain
| object | address |
| N1 | R1 |
| N2 | R2 |
When sending search query "Bolder"
Then results contain
| osm | display_name |
| N1 | Bolder, Wonderway, Left |
When sending search query "Leftside"
Then results contain
| osm | display_name |
| N2 | Leftside, Wonderway, Right |
Scenario: POIs can correct address parts on the fly (with partial unmatching address)
Given the grid
| 1 | | | | 2 | | 5 |
| | | | 9 | | 8 | |
| | 10| 11| | | 12| |
| 4 | | | | 3 | | 6 |
And the places
| osm | class | type | admin | name | geometry |
| R1 | boundary | administrative | 8 | Left | (1,2,3,4,1) |
| R2 | boundary | administrative | 8 | Right | (2,3,6,5,2) |
And the places
| osm | class | type | name | geometry |
| W1 | highway | primary | Wonderway | 10,11,12 |
And the places
| osm | class | type | name | addr+suburb | geometry |
| N1 | amenity | cafe | Bolder | Boring | 9 |
| N2 | amenity | cafe | Leftside | Boring | 8 |
When importing
Then place_addressline contains
| object | address | isaddress |
| W1 | R1 | True |
| W1 | R2 | False |
And place_addressline doesn't contain
| object | address |
| N1 | R1 |
| N2 | R2 |
When sending search query "Bolder"
Then results contain
| osm | display_name |
| N1 | Bolder, Wonderway, Left |
When sending search query "Leftside"
Then results contain
| osm | display_name |
| N2 | Leftside, Wonderway, Right |
Scenario: POIs can correct address parts on the fly (with partial matching address)
Given the grid
| 1 | | | | 2 | | 5 |
| | | | 9 | | 8 | |
| | 10| 11| | | 12| |
| 4 | | | | 3 | | 6 |
And the places
| osm | class | type | admin | name | geometry |
| R1 | boundary | administrative | 8 | Left | (1,2,3,4,1) |
| R2 | boundary | administrative | 8 | Right | (2,3,6,5,2) |
And the places
| osm | class | type | name | geometry |
| W1 | highway | primary | Wonderway | 10,11,12 |
And the places
| osm | class | type | name | addr+state | geometry |
| N1 | amenity | cafe | Bolder | Left | 9 |
| N2 | amenity | cafe | Leftside | Left | 8 |
When importing
Then place_addressline contains
| object | address | isaddress |
| W1 | R1 | True |
| W1 | R2 | False |
And place_addressline doesn't contain
| object | address |
| N1 | R1 |
| N2 | R2 |
When sending search query "Bolder"
Then results contain
| osm | display_name |
| N1 | Bolder, Wonderway, Left |
When sending search query "Leftside"
Then results contain
| osm | display_name |
| N2 | Leftside, Wonderway, Left |
Scenario: addr:* tags always match the closer area
Given the grid
| 1 | | | | 2 | | 5 |
| | | | | | | |
| | 10| 11| | | | |
| 4 | | | | 3 | | 6 |
And the places
| osm | class | type | admin | name | geometry |
| R1 | boundary | administrative | 8 | Left | (1,2,3,4,1) |
| R2 | boundary | administrative | 8 | Left | (2,3,6,5,2) |
And the places
| osm | class | type | name | addr+city | geometry |
| W1 | highway | primary | Wonderway | Left | 10,11 |
When importing
Then place_addressline doesn't contain
| object | address |
| W1 | R2 |
Scenario: Full name is prefered for unlisted addr:place tags
Given the grid
| | 1 | 2 | |
| 8 | | | 9 |
And the places
| osm | class | type | name | geometry |
| W10 | place | city | Away | (8,1,2,9,8) |
And the places
| osm | class | type | name | addr+city | geometry |
| W1 | highway | residential | Royal Terrace | Gardens | 8,9 |
And the places
| osm | class | type | housenr | addr+place | geometry | extra+foo |
| N1 | place | house | 1 | Royal Terrace Gardens | 1 | bar |
And the places
| osm | class | type | housenr | addr+street | geometry |
| N2 | place | house | 2 | Royal Terrace | 2 |
When importing
When sending search query "1, Royal Terrace Gardens"
Then results contain
| ID | osm |
| 0 | N1 |

View File

@@ -1,92 +0,0 @@
@DB
Feature: Country handling
Tests for import and use of country information
Scenario: Country names from OSM country relations are added
Given the places
| osm | class | type | admin | name+name:xy | country | geometry |
| R1 | boundary | administrative | 2 | Loudou | de | (9 52, 9 53, 10 52, 9 52) |
Given the places
| osm | class | type | name | geometry |
| N1 | place | town | Wenig | country:de |
When importing
When sending search query "Wenig, Loudou"
Then results contain
| osm | display_name |
| N1 | Wenig, Deutschland |
When sending search query "Wenig"
| accept-language |
| xy,en |
Then results contain
| osm | display_name |
| N1 | Wenig, Loudou |
Scenario: OSM country relations outside expected boundaries are ignored for naming
Given the grid
| 1 | | 2 |
| 4 | | 3 |
Given the places
| osm | class | type | admin | name+name:xy | country | geometry |
| R1 | boundary | administrative | 2 | Loudou | de | (1,2,3,4,1) |
Given the places
| osm | class | type | name | geometry |
| N1 | place | town | Wenig | country:de |
When importing
When sending search query "Wenig"
| accept-language |
| xy,en |
Then results contain
| osm | display_name |
| N1 | Wenig, Germany |
Scenario: Pre-defined country names are used
Given the grid with origin CH
| 1 |
Given the places
| osm | class | type | name | geometry |
| N1 | place | town | Ingb | 1 |
When importing
And sending search query "Ingb"
| accept-language |
| en,de |
Then results contain
| osm | display_name |
| N1 | Ingb, Switzerland |
Scenario: For overlapping countries, pre-defined countries are tie-breakers
Given the grid with origin US
| 1 | | 2 | | 5 |
| | 9 | | 8 | |
| 4 | | 3 | | 6 |
Given the named places
| osm | class | type | admin | country | geometry |
| R1 | boundary | administrative | 2 | de | (1,5,6,4,1) |
| R2 | boundary | administrative | 2 | us | (1,2,3,4,1) |
And the named places
| osm | class | type | geometry |
| N1 | place | town | 9 |
| N2 | place | town | 8 |
When importing
Then placex contains
| object | country_code |
| N1 | us |
| N2 | de |
Scenario: For overlapping countries outside pre-define countries prefer smaller partition
Given the grid with origin US
| 1 | | 2 | | 5 |
| | 9 | | 8 | |
| 4 | | 3 | | 6 |
Given the named places
| osm | class | type | admin | country | geometry |
| R1 | boundary | administrative | 2 | ch | (1,5,6,4,1) |
| R2 | boundary | administrative | 2 | de | (1,2,3,4,1) |
And the named places
| osm | class | type | geometry |
| N1 | place | town | 9 |
| N2 | place | town | 8 |
When importing
Then placex contains
| object | country_code |
| N1 | de |
| N2 | ch |

View File

@@ -1,617 +0,0 @@
@DB
Feature: Import of address interpolations
Tests that interpolated addresses are added correctly
Scenario: Simple even interpolation line with two points and no street nearby
Given the grid with origin 1,1
| 1 | | 9 | | 2 |
Given the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 6 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 1,2 |
And the ways
| id | nodes |
| 1 | 1,2 |
When importing
Then W1 expands to no interpolation
Scenario: Simple even interpolation line with two points
Given the grid with origin 1,1
| 1 | | 9 | | 2 |
| 4 | | | | 5 |
Given the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 6 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 1,2 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 4,5 |
And the ways
| id | nodes |
| 1 | 1,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 4 | 4 | 9 |
Scenario: Backwards even two point interpolation line
Given the grid with origin 1,1
| 1 | 8 | 9 | 2 |
| 4 | | | 5 |
Given the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 8 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 2,1 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 4,5 |
And the ways
| id | nodes |
| 1 | 2,1 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 4 | 6 | 9,8 |
Scenario: Simple odd two point interpolation
Given the grid with origin 1,1
| 1 | 8 | | | 9 | 2 |
| 4 | | | | 5 | |
Given the places
| osm | class | type | housenr |
| N1 | place | house | 1 |
| N2 | place | house | 11 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | odd | 1,2 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 4,5 |
And the ways
| id | nodes |
| 1 | 1,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 3 | 9 | 8,9 |
Scenario: Simple all two point interpolation
Given the grid with origin 1,1
| 1 | 8 | 9 | 2 |
| 4 | | | 5 |
Given the places
| osm | class | type | housenr |
| N1 | place | house | 1 |
| N2 | place | house | 4 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | all | 1,2 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 4,5 |
And the ways
| id | nodes |
| 1 | 1,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 2 | 3 | 8,9 |
Scenario: Even two point interpolation line with intermediate empty node
Given the grid
| 1 | 8 | | 3 | 9 | 2 |
| 4 | | | | 5 | |
Given the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 12 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 1,3,2 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 4,5 |
And the ways
| id | nodes |
| 1 | 1,3,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 4 | 10 | 8,3,9 |
Scenario: Even two point interpolation line with intermediate duplicated empty node
Given the grid
| 4 | | | | 5 |
| 1 | 8 | 3 | 9 | 2 |
Given the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 10 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 1,3,2 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 4,5 |
And the ways
| id | nodes |
| 1 | 1,3,3,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 4 | 8 | 8,3,9 |
Scenario: Simple even three point interpolation line
Given the grid
| 4 | | | | | | 5 |
| 1 | 8 | | 9 | 3 | 7 | 2 |
Given the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 14 |
| N3 | place | house | 10 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 1,3,2 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 4,5 |
And the ways
| id | nodes |
| 1 | 1,3,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 4 | 8 | 8,9 |
| 12 | 12 | 7 |
Scenario: Simple even four point interpolation line
Given the grid
| 1 | 10 | | 11 | 3 |
| | | | | 12|
| | | 4 | 13 | 2 |
Given the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 14 |
| N3 | place | house | 10 |
| N4 | place | house | 18 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 1,3,2,4 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 1,3,2,4 |
And the ways
| id | nodes |
| 1 | 1,3,2,4 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 4 | 8 | 10,11 |
| 12 | 12 | 12 |
| 16 | 16 | 13 |
Scenario: Reverse simple even three point interpolation line
Given the grid
| 1 | 8 | | 9 | 3 | 7 | 2 |
| 4 | | | | | | 5 |
Given the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 14 |
| N3 | place | house | 10 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 2,3,1 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 4,5 |
And the ways
| id | nodes |
| 1 | 2,3,1 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 4 | 8 | 8,9 |
| 12 | 12 | 7 |
Scenario: Even three point interpolation line with odd center point
Given the grid
| 1 | | 10 | | | 11 | 3 | 2 |
| 4 | | | | | | | 5 |
Given the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 8 |
| N3 | place | house | 7 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 1,3,2 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 4,5 |
And the ways
| id | nodes |
| 1 | 1,3,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 4 | 6 | 10,11 |
Scenario: Interpolation line with self-intersecting way
Given the grid
| 1 | 9 | 2 |
| | | 8 |
| | | 3 |
Given the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 6 |
| N3 | place | house | 10 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 1,2,3,2 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 1,2,3 |
And the ways
| id | nodes |
| 1 | 1,2,3,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 4 | 4 | 9 |
| 8 | 8 | 8 |
| 8 | 8 | 8 |
Scenario: Interpolation line with self-intersecting way II
Given the grid
| 1 | 9 | 2 |
| | | 3 |
Given the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 6 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 1,2,3,2 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 1,2,3 |
And the ways
| id | nodes |
| 1 | 1,2,3,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 4 | 4 | 9 |
Scenario: addr:street on interpolation way
Given the grid
| | 1 | | 2 | |
| 10 | | | | 11 |
| 20 | | | | 21 |
And the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | 1 |
| N2 | place | house | 6 | 2 |
| N3 | place | house | 12 | 1 |
| N4 | place | house | 16 | 2 |
And the places
| osm | class | type | addr+interpolation | street | geometry |
| W10 | place | houses | even | | 1,2 |
| W11 | place | houses | even | Cloud Street | 1,2 |
And the places
| osm | class | type | name | geometry |
| W2 | highway | tertiary | Sun Way | 10,11 |
| W3 | highway | tertiary | Cloud Street | 20,21 |
And the ways
| id | nodes |
| 10 | 1,2 |
| 11 | 3,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 | 4 | 4 |
Then W11 expands to interpolation
| parent_place_id | start | end |
| W3 | 14 | 14 |
When sending search query "16 Cloud Street"
Then results contain
| ID | osm |
| 0 | N4 |
When sending search query "14 Cloud Street"
Then results contain
| ID | osm |
| 0 | W11 |
Scenario: addr:street on housenumber way
Given the grid
| | 1 | | 2 | |
| 10 | | | | 11 |
| 20 | | | | 21 |
And the places
| osm | class | type | housenr | street | geometry |
| N1 | place | house | 2 | | 1 |
| N2 | place | house | 6 | | 2 |
| N3 | place | house | 12 | Cloud Street | 1 |
| N4 | place | house | 16 | Cloud Street | 2 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W10 | place | houses | even | 1,2 |
| W11 | place | houses | even | 1,2 |
And the places
| osm | class | type | name | geometry |
| W2 | highway | tertiary | Sun Way | 10,11 |
| W3 | highway | tertiary | Cloud Street | 20,21 |
And the ways
| id | nodes |
| 10 | 1,2 |
| 11 | 3,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 | 4 | 4 |
Then W11 expands to interpolation
| parent_place_id | start | end |
| W3 | 14 | 14 |
When sending search query "16 Cloud Street"
Then results contain
| ID | osm |
| 0 | N4 |
When sending search query "14 Cloud Street"
Then results contain
| ID | osm |
| 0 | W11 |
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 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 144.9632341 -37.76163,144.9630541 -37.7628172,144.9629794 -37.7630755 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 144.9632341 -37.76163,144.9629794 -37.7630755 |
And the ways
| id | nodes |
| 1 | 1,2,3 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 4 | 4 | 144.963016 -37.762946 |
| 8 | 8 | 144.96314407 -37.762223692 |
Scenario: Place with missing address information
Given the grid
| 1 | | 2 | | | 3 |
| 4 | | | | | 5 |
And the places
| osm | class | type | housenr |
| N1 | place | house | 23 |
| N2 | amenity | school | |
| N3 | place | house | 29 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | odd | 1,2,3 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 4,5 |
And the ways
| id | nodes |
| 1 | 1,2,3 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 25 | 27 | 0.000016 0,0.00002 0,0.000033 0 |
Scenario: Ways without node entries are ignored
Given the places
| osm | class | type | housenr | geometry |
| W1 | place | houses | even | 1 1, 1 1.001 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 1 1, 1 1.001 |
When importing
Then W1 expands to no interpolation
Scenario: Ways with nodes without housenumbers are ignored
Given the grid
| 1 | | 2 |
| 4 | | 5 |
Given the places
| osm | class | type |
| N1 | place | house |
| N2 | place | house |
Given the places
| osm | class | type | housenr | geometry |
| W1 | place | houses | even | 1,2 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 4,5 |
When importing
Then W1 expands to no interpolation
Scenario: Two point interpolation starting at 0
Given the grid with origin 1,1
| 1 | 10 | | | 11 | 2 |
| 4 | | | | | 5 |
Given the places
| osm | class | type | housenr |
| N1 | place | house | 0 |
| N2 | place | house | 10 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 1,2 |
And the places
| osm | class | type | name | geometry |
| W10 | highway | residential | London Road |4,5 |
And the ways
| id | nodes |
| 1 | 1,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 2 | 8 | 10,11 |
When sending v1/reverse at 1,1
Then results contain
| ID | osm | type | display_name |
| 0 | N1 | house | 0, London Road |
Scenario: Parenting of interpolation with additional tags
Given the grid
| 1 | | | | | |
| | | | | | |
| | 8 | | | 9 | |
| | | | | | |
| 2 | | | | | 3 |
Given the places
| osm | class | type | housenr | addr+street |
| N8 | place | house | 10 | Horiz St |
| N9 | place | house | 16 | Horiz St |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | Vert St | 1,2 |
| W2 | highway | residential | Horiz St | 2,3 |
And the places
| osm | class | type | addr+interpolation | addr+inclusion | geometry |
| W10 | place | houses | even | actual | 8,9 |
And the ways
| id | nodes |
| 10 | 8,9 |
When importing
Then placex contains
| object | parent_place_id |
| N8 | W2 |
| N9 | W2 |
And W10 expands to interpolation
| start | end | parent_place_id |
| 12 | 14 | W2 |
Scenario Outline: Bad interpolation values are ignored
Given the grid with origin 1,1
| 1 | | 9 | | 2 |
| 4 | | | | 5 |
Given the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 6 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | <value> | 1,2 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 4,5 |
And the ways
| id | nodes |
| 1 | 1,2 |
When importing
Then W1 expands to no interpolation
Examples:
| value |
| foo |
| x |
| 12-2 |
Scenario: Interpolation line where points have been moved (Github #3022)
Given the 0.00001 grid
| 1 | | | | | | | | 2 | 3 | 9 | | | | | | | | 4 |
Given the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | 1 |
| N2 | place | house | 18 | 3 |
| N3 | place | house | 24 | 9 |
| N4 | place | house | 42 | 4 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 1,2,3,4 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 1,4 |
And the ways
| id | nodes |
| 1 | 1,2,3,4 |
When importing
Then W1 expands to interpolation
| start | end |
| 4 | 16 |
| 20 | 22 |
| 26 | 40 |
Scenario: Interpolation line with duplicated points
Given the grid
| 7 | 10 | 8 | 11 | 9 |
| 4 | | | | 5 |
Given the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | 7 |
| N2 | place | house | 6 | 8 |
| N3 | place | house | 10 | 8 |
| N4 | place | house | 14 | 9 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 7,8,8,9 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 4,5 |
And the ways
| id | nodes |
| 1 | 1,2,3,4 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 4 | 4 | 10 |
| 12 | 12 | 11 |
Scenario: Interpolaton line with broken way geometry (Github #2986)
Given the grid
| 1 | 8 | 10 | 11 | 9 | 2 | 3 | 4 |
Given the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 8 |
| N3 | place | house | 12 |
| N4 | place | house | 14 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 8,9 |
And the named places
| osm | class | type | geometry |
| W10 | highway | residential | 1,4 |
And the ways
| id | nodes |
| 1 | 1,8,9,2,3,4 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 4 | 6 | 10,11 |

View File

@@ -1,331 +0,0 @@
@DB
Feature: Linking of places
Tests for correctly determining linked places
Scenario: Only address-describing places can be linked
Given the grid
| 1 | | | | 2 |
| | | 9 | | |
| 4 | | | | 3 |
And the places
| osm | class | type | name | geometry |
| R13 | landuse | forest | Garbo | (1,2,3,4,1) |
| N256 | natural | peak | Garbo | 9 |
When importing
Then placex contains
| object | linked_place_id |
| R13 | - |
| N256 | - |
Scenario: Postcode areas cannot be linked
Given the grid with origin US
| 1 | | 2 |
| | 9 | |
| 4 | | 3 |
And the named places
| osm | class | type | addr+postcode | extra+wikidata | geometry |
| R13 | boundary | postal_code | 12345 | Q87493 | (1,2,3,4,1) |
| N25 | place | suburb | 12345 | Q87493 | 9 |
When importing
Then placex contains
| object | linked_place_id |
| R13 | - |
| N25 | - |
Scenario: Waterways are linked when in waterway relations
Given the grid
| 1 | | | | 3 | 4 | | | | 6 |
| | | 2 | | | 10 | | 5 | | |
| | | | | | 11 | | | | |
And the places
| osm | class | type | name | geometry |
| W1 | waterway | river | Rhein | 1,2,3 |
| W2 | waterway | river | Rhein | 3,4,5 |
| R13 | waterway | river | Rhein | 1,2,3,4,5,6 |
| R23 | waterway | river | Limmat| 4,10,11 |
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 sending search query "rhein"
Then results contain
| osm |
| R13 |
Scenario: Relations are not linked when in waterway relations
Given the grid
| 1 | | | | 3 | 4 | | | | 6 |
| | | 2 | | | 10 | | 5 | | |
| | | | | | 11 | | | | |
And the places
| osm | class | type | name | geometry |
| W1 | waterway | stream | Rhein | 1,2,3,4 |
| W2 | waterway | river | Rhein | 4,5,6 |
| R1 | waterway | river | Rhein | 1,2,3,4 |
| R2 | waterway | river | Limmat| 4,10,11 |
And the relations
| id | members | tags+type |
| 1 | R2 | waterway |
When importing
Then placex contains
| object | linked_place_id |
| W1 | - |
| W2 | - |
| R1 | - |
| R2 | - |
When sending search query "rhein"
Then results contain
| ID | osm |
| 0 | R1 |
| 1 | W2 |
Scenario: Empty waterway relations are handled correctly
Given the grid
| 1 | | | | 3 |
And the places
| osm | class | type | name | geometry |
| R1 | waterway | river | Rhein | 1,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 the way type is not a river feature
Given the grid
| 1 | | 2 |
| | | |
| 3 | | 4 |
And the places
| osm | class | type | name | geometry |
| W1 | waterway | lock | Rhein | 3,4 |
| R1 | landuse | meadow | Rhein | (3,1,2,4,3) |
And the relations
| id | members | tags+type |
| 1 | W1,W2 | multipolygon |
When importing
Then placex contains
| object | linked_place_id |
| W1 | - |
| R1 | - |
Scenario: Side streams are linked only when they have the same name
Given the grid
| | | | | 8 | | | |
| 1 | | 2 | 3 | | 4 | 5 | 6|
| | | | | | 9 | | |
And the places
| osm | class | type | name | geometry |
| W1 | waterway | river | Rhein2 | 2,8,4 |
| W2 | waterway | river | Rhein | 3,9,5 |
| R1 | waterway | river | Rhein | 1,2,3,4,5,6 |
And the relations
| id | members | tags+type |
| 1 | W1:side_stream,W2:side_stream,W3 | waterway |
When importing
Then placex contains
| object | linked_place_id |
| W1 | - |
| W2 | R1 |
When sending search query "rhein2"
Then results contain
| osm |
| W1 |
# github #573
Scenario: Boundaries should only be linked to places
Given the 0.05 grid
| 1 | | 2 |
| | 9 | |
| 4 | | 3 |
Given the named places
| osm | class | type | extra+wikidata | admin | geometry |
| R1 | boundary | administrative | 34 | 8 | (1,2,3,4,1) |
And the named places
| osm | class | type |
| N9 | natural | island |
| N9 | place | city |
And the relations
| id | members |
| 1 | N9:label |
When importing
Then placex contains
| object | linked_place_id |
| N9:natural | - |
| N9:place | R1 |
Scenario: Nodes with 'role' label are always linked
Given the 0.05 grid
| 1 | | 2 |
| | 9 | |
| 4 | | 3 |
Given the places
| osm | class | type | admin | name | geometry |
| R13 | boundary | administrative | 6 | Garbo | (1,2,3,4,1) |
| N2 | place | hamlet | 15 | Vario | 9 |
And the relations
| id | members | tags+type |
| 13 | N2:label | boundary |
When importing
Then placex contains
| object | linked_place_id |
| N2 | R13 |
And placex contains
| object | centroid | name+name | extratags+linked_place |
| R13 | 9 | Garbo | hamlet |
Scenario: Boundaries with place tags are linked against places with same type
Given the 0.01 grid
| 1 | | 2 |
| | 9 | |
| 4 | | 3 |
Given the places
| osm | class | type | admin | name | extra+place | geometry |
| R13 | boundary | administrative | 4 | Berlin | city | (1,2,3,4,1) |
And the places
| osm | class | type | name | geometry |
| N2 | place | city | Berlin | 9 |
When importing
Then placex contains
| object | linked_place_id |
| N2 | R13 |
And placex contains
| object | rank_address |
| R13 | 16 |
When sending search query ""
| city |
| Berlin |
Then results contain
| ID | osm |
| 0 | R13 |
When sending search query ""
| state |
| Berlin |
Then results contain
| ID | osm |
| 0 | R13 |
Scenario: Boundaries without place tags only link against same admin level
Given the 0.05 grid
| 1 | | 2 |
| | 9 | |
| 4 | | 3 |
Given the places
| osm | class | type | admin | name | geometry |
| R13 | boundary | administrative | 4 | Berlin | (1,2,3,4,1) |
And the places
| osm | class | type | name | geometry |
| N2 | place | city | Berlin | 9 |
When importing
Then placex contains
| object | linked_place_id |
| N2 | - |
And placex contains
| object | rank_address |
| R13 | 8 |
When sending search query ""
| state |
| Berlin |
Then results contain
| ID | osm |
| 0 | R13 |
When sending search query ""
| city |
| Berlin |
Then results contain
| ID | osm |
| 0 | N2 |
# github #1352
Scenario: Do not use linked centroid when it is outside the area
Given the 0.05 grid
| 1 | | 2 | |
| | | | 9 |
| 4 | | 3 | |
Given the named places
| osm | class | type | admin | geometry |
| R13 | boundary | administrative | 4 | (1,2,3,4,1) |
And the named places
| osm | class | type | geometry |
| N2 | place | city | 9 |
And the relations
| id | members | tags+type |
| 13 | N2:label | boundary |
When importing
Then placex contains
| object | linked_place_id |
| N2 | R13 |
And placex contains
| object | centroid |
| R13 | in geometry |
Scenario: Place nodes can only be linked once
Given the 0.02 grid
| 1 | | 2 | | 5 |
| | 9 | | | |
| 4 | | 3 | | 6 |
Given the named places
| osm | class | type | extra+wikidata | geometry |
| N2 | place | city | Q1234 | 9 |
And the named places
| osm | class | type | extra+wikidata | admin | geometry |
| R1 | boundary | administrative | Q1234 | 8 | (1,2,5,6,3,4,1) |
| R2 | boundary | administrative | Q1234 | 9 | (1,2,3,4,1) |
When importing
Then placex contains
| object | linked_place_id |
| N2 | R1 |
And placex contains
| object | extratags |
| R1 | 'linked_place' : 'city', 'wikidata': 'Q1234' |
| R2 | 'wikidata': 'Q1234' |
Scenario: Boundaries without names inherit names from linked places
Given the 0.05 grid
| 1 | | 2 |
| | 9 | |
| 4 | | 3 |
Given the places
| osm | class | type | extra+wikidata | admin | geometry |
| R1 | boundary | administrative | 34 | 8 | (1,2,3,4,1) |
And the places
| osm | class | type | name+name |
| N9 | place | city | LabelPlace |
And the relations
| id | members |
| 1 | N9:label |
When importing
Then placex contains
| object | name+_place_name |
| R1 | LabelPlace |
@Fail
Scenario: Linked places expand default language names
Given the grid
| 1 | | 2 |
| | 9 | |
| 4 | | 3 |
Given the places
| osm | class | type | name+name | geometry |
| N9 | place | city | Popayán | 9 |
| R1 | boundary | administrative | Perímetro Urbano Popayán | (1,2,3,4,1) |
And the relations
| id | members |
| 1 | N9:label |
When importing
Then placex contains
| object | name+_place_name | name+_place_name:es |
| R1 | Popayán | Popayán |

View File

@@ -1,105 +0,0 @@
@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 | 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 | 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 | 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 | country_code | name | name+name:fi | name+name:de |
| N1 | de | german | finnish | local |
Scenario Outline: Names in any script can be found
Given the places
| osm | class | type | name |
| N1 | place | hamlet | <name> |
When importing
And sending search query "<name>"
Then results contain
| osm |
| N1 |
Examples:
| name |
| Berlin |
| |
| Вологда |
| Αθήνα |
| القاهرة |
| |
| |
| |
Scenario: German umlauts can be found when expanded
Given the places
| osm | class | type | name+name:de |
| N1 | place | city | Münster |
| N2 | place | city | Köln |
| N3 | place | city | Gräfenroda |
When importing
When sending search query "münster"
Then results contain
| osm |
| N1 |
When sending search query "muenster"
Then results contain
| osm |
| N1 |
When sending search query "munster"
Then results contain
| osm |
| N1 |
When sending search query "Köln"
Then results contain
| osm |
| N2 |
When sending search query "Koeln"
Then results contain
| osm |
| N2 |
When sending search query "Koln"
Then results contain
| osm |
| N2 |
When sending search query "gräfenroda"
Then results contain
| osm |
| N3 |
When sending search query "graefenroda"
Then results contain
| osm |
| N3 |
When sending search query "grafenroda"
Then results contain
| osm |
| N3 |

View File

@@ -1,652 +0,0 @@
@DB
Feature: Parenting of objects
Tests that the correct parent is chosen
Scenario: Address inherits postcode from its street unless it has a postcode
Given the grid with origin DE
| 10 | | | | | 11 |
| | | | | | |
| | 1 | | 2 | | |
And the places
| osm | class | type | housenr |
| N1 | place | house | 4 |
And the places
| osm | class | type | housenr | postcode |
| N2 | place | house | 5 | 99999 |
And the places
| osm | class | type | name | postcode | geometry |
| W1 | highway | residential | galoo | 12345 | 10,11 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W1 |
| N2 | W1 |
When sending search query "4 galoo"
Then results contain
| ID | osm | display_name |
| 0 | N1 | 4, galoo, 12345, Deutschland |
When sending search query "5 galoo"
Then results contain
| ID | osm | display_name |
| 0 | N2 | 5, galoo, 99999, Deutschland |
Scenario: Address without tags, closest street
Given the grid
| 10 | | | | | 11 |
| | 1 | 2 | | | |
| | | | 3 | 4 | |
| 20 | | | | | 21 |
And the places
| osm | class | type |
| N1 | place | house |
| N2 | place | house |
| N3 | place | house |
| N4 | place | house |
And the named places
| osm | class | type | geometry |
| W1 | highway | residential | 10,11 |
| W2 | highway | residential | 20,21 |
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 grid
| 10 | | | | | 11 |
| | 1 | 2 | | | |
| | | | 3 | 4 | |
| 20 | | | | | 21 |
And the places
| osm | class | type |
| N1 | place | house |
| N2 | place | house |
| N3 | place | house |
| N4 | place | house |
And the places
| osm | class | type | geometry |
| W1 | highway | residential | 10,11 |
And the named places
| osm | class | type | geometry |
| W2 | highway | residential | 20,21 |
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 grid
| 10 | | | | | 11 |
| | 1 | 2 | | | |
| | | | 3 | 4 | |
| 20 | | | | | 21 |
And the places
| osm | class | type | street|
| N1 | place | house | south |
| N2 | place | house | north |
| N3 | place | house | south |
| N4 | place | house | north |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | north | 10,11 |
| W2 | highway | residential | south | 20,21 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W2 |
| N2 | W1 |
| N3 | W2 |
| N4 | W1 |
Scenario: addr:street tag parents to appropriately named street, locale names
Given the grid
| 10 | | | | | 11 |
| | 1 | 2 | | | |
| | | | 3 | 4 | |
| 20 | | | | | 21 |
And the places
| osm | class | type | street| addr+street:de |
| N1 | place | house | south | Süd |
| N2 | place | house | north | Nord |
| N3 | place | house | south | Süd |
| N4 | place | house | north | Nord |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | Nord | 10,11 |
| W2 | highway | residential | Süd | 20,21 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W2 |
| N2 | W1 |
| N3 | W2 |
| N4 | W1 |
Scenario: addr:street tag parents to appropriately named street with abbreviation
Given the grid
| 10 | | | | | 11 |
| | 1 | 2 | | | |
| | | | 3 | 4 | |
| 20 | | | | | 21 |
And the places
| osm | class | type | street |
| N1 | place | house | south st |
| N2 | place | house | north st |
| N3 | place | house | south st |
| N4 | place | house | north st |
And the places
| osm | class | type | name+name:en | geometry |
| W1 | highway | residential | north street | 10,11 |
| W2 | highway | residential | south street | 20,21 |
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 grid
| 10 | | | | | 11 |
| | 1 | 2 | | | |
| | | | 3 | 4 | |
| 20 | | | | | 21 |
And the places
| osm | class | type | street |
| N1 | place | house | abcdef |
| N2 | place | house | abcdef |
| N3 | place | house | abcdef |
| N4 | place | house | abcdef |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | abcdef | 10,11 |
| W2 | highway | residential | abcdef | 20,21 |
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 grid
| 10 | | | | | 11 |
| | 1 | | | | |
| | | | 3 | | |
| 20 | | | | | 21 |
And the places
| osm | class | type | street |
| N1 | place | house | abcdef |
| N3 | place | house | abcdef |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | abcde | 10,11 |
| W2 | highway | residential | abcde | 20,21 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W1 |
| N3 | W2 |
Scenario: addr:place address
Given the grid
| 10 | | | |
| | 1 | | 2 |
| 11 | | | |
And the places
| osm | class | type | addr_place |
| N1 | place | house | myhamlet |
And the places
| osm | class | type | name | geometry |
| N2 | place | hamlet | myhamlet | 2 |
| W1 | highway | residential | myhamlet | 10,11 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | N2 |
Scenario: addr:street is preferred over addr:place
Given the grid
| 10 | | | |
| | | 1 | 2 |
| 11 | | | |
And the places
| osm | class | type | addr_place | street |
| N1 | place | house | myhamlet | mystreet|
And the places
| osm | class | type | name | geometry |
| N2 | place | hamlet | myhamlet | 2 |
| W1 | highway | residential | mystreet | 10,11 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W1 |
Scenario: Untagged address in simple associated street relation
Given the grid
| 10 | | | | | 11 |
| | 2 | | 3 | | |
| | | | | | |
| 12 | 1 | | | | |
And the places
| osm | class | type |
| N1 | place | house |
| N2 | place | house |
| N3 | place | house |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | foo | 10,11 |
| W2 | highway | service | bar | 10,12 |
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 grid
| 10 | | | | | 11 |
| | 2 | | 3 | | |
| | | | | | |
| 12 | 1 | | | | |
And the places
| osm | class | type |
| N1 | place | house |
| N2 | place | house |
| N3 | place | house |
And the places
| osm | class | type | geometry |
| W2 | highway | residential | 10,12 |
And the named places
| osm | class | type | geometry |
| W1 | highway | residential | 10,11 |
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 grid
| 10 | | | | 11 |
| | | | | |
| | | 1 | | |
| | 20 | | 21 | |
And the places
| osm | class | type | street |
| N1 | place | house | bar |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | foo | 10,11 |
| W2 | highway | residential | bar | 20,21 |
And the relations
| id | members | tags+type |
| 1 | W1:street,N1 | associatedStreet |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W1 |
Scenario: Building without tags, closest street from center point
Given the grid
| 10 | | | | 11 |
| | | 1 | 2 | |
| 12 | | 4 | 3 | |
And the named places
| osm | class | type | geometry |
| W1 | building | yes | (1,2,3,4,1) |
| W2 | highway | primary | 10,11 |
| W3 | highway | residential | 10,12 |
When importing
Then placex contains
| object | parent_place_id |
| W1 | W2 |
Scenario: Building with addr:street tags
Given the grid
| 10 | | | | 11 |
| | | 1 | 2 | |
| 12 | | 4 | 3 | |
And the named places
| osm | class | type | street | geometry |
| W1 | building | yes | foo | (1,2,3,4,1) |
And the places
| osm | class | type | name | geometry |
| W2 | highway | primary | bar | 10,11 |
| W3 | highway | residential | foo | 10,12 |
When importing
Then placex contains
| object | parent_place_id |
| W1 | W3 |
Scenario: Building with addr:place tags
Given the grid
| 10 | | | | |
| | 1 | 2 | | 9 |
| 11 | 4 | 3 | | |
And the places
| osm | class | type | name | geometry |
| N9 | place | village | bar | 9 |
| W2 | highway | primary | bar | 10,11 |
And the named places
| osm | class | type | addr_place | geometry |
| W1 | building | yes | bar | (1,2,3,4,1) |
When importing
Then placex contains
| object | parent_place_id |
| W1 | N9 |
Scenario: Building in associated street relation
Given the grid
| 10 | | | | 11 |
| | | 1 | 2 | |
| 12 | | 4 | 3 | |
And the named places
| osm | class | type | geometry |
| W1 | building | yes | (1,2,3,4,1) |
And the places
| osm | class | type | name | geometry |
| W2 | highway | primary | bar | 10,11 |
| W3 | highway | residential | foo | 10,12 |
And the relations
| id | members | tags+type |
| 1 | W1:house,W3:street | associatedStreet |
When importing
Then placex contains
| object | parent_place_id |
| W1 | W3 |
Scenario: Building in associated street relation overrides addr:street
Given the grid
| 10 | | | | 11 |
| | | 1 | 2 | |
| 12 | | 4 | 3 | |
And the named places
| osm | class | type | street | geometry |
| W1 | building | yes | foo | (1,2,3,4,1) |
And the places
| osm | class | type | name | geometry |
| W2 | highway | primary | bar | 10,11 |
| W3 | highway | residential | foo | 10,12 |
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 grid
| 10 | | | | | | | 11 |
| | 1 | | 3 | 4 | | | |
| | | | 6 | 5 | | | |
And the named places
| osm | class | type | geometry |
| N1 | place | house | 11 |
And the named places
| osm | class | type | street | geometry |
| W1 | building | yes | foo | (3,4,5,6,3) |
And the places
| osm | class | type | name | geometry |
| W3 | highway | residential | foo | 10,11 |
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: street member in associatedStreet relation can be a relation
Given the grid
| 1 | | | 2 |
| 3 | | | 4 |
| | | | |
| | 9 | | |
| 5 | | | 6 |
And the places
| osm | class | type | housenr | geometry |
| N9 | place | house | 34 | 9 |
And the named places
| osm | class | type | name | geometry |
| R14 | highway | pedestrian | Right St | (1,2,4,3,1) |
| W14 | highway | pedestrian | Left St | 5,6 |
And the relations
| id | members | tags+type |
| 1 | N9:house,R14:street | associatedStreet |
When importing
Then placex contains
| object | parent_place_id |
| N9 | R14 |
Scenario: Choose closest street in associatedStreet relation
Given the grid
| 1 | | | | 3 |
| 10 | | 11 | | 12 |
And the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 1 | 1 |
| N3 | place | house | 3 | 3 |
And the named places
| osm | class | type | geometry |
| W100 | highway | residential | 10,11 |
| W101 | highway | residential | 11,12 |
And the relations
| id | members | tags+type |
| 1 | N1:house,N3:house,W100:street,W101:street | associatedStreet |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W100 |
| N3 | W101 |
Scenario: POIs in building inherit address
Given the grid
| 10 | | | | | | 11 |
| | | 5 | 2 | 6 | | |
| | | 3 | 1 | | | |
| 12 | | 8 | | 7 | | |
And the named places
| osm | class | type |
| N1 | amenity | bank |
| N2 | shop | bakery |
| N3 | shop | supermarket|
And the places
| osm | class | type | street | housenr | geometry |
| W1 | building | yes | foo | 3 | (5,6,7,8,5) |
And the places
| osm | class | type | name | geometry |
| W2 | highway | primary | bar | 10,11 |
| W3 | highway | residential | foo | 10,12 |
When importing
Then placex contains
| object | parent_place_id | housenumber |
| W1 | W3 | 3 |
| N1 | W3 | 3 |
| N2 | W3 | 3 |
| N3 | W3 | 3 |
When sending geocodejson search query "3, foo" with address
Then results contain
| housenumber |
| 3 |
Scenario: POIs don't inherit from streets
Given the grid
| 10 | | | | 11 |
| | 5 | 1 | 6 | |
| | 8 | | 7 | |
And the named places
| osm | class | type |
| N1 | amenity | bank |
And the places
| osm | class | type | name | street | housenr | geometry |
| W1 | highway | path | bar | foo | 3 | (5,6,7,8,5) |
And the places
| osm | class | type | name | geometry |
| W3 | highway | residential | foo | 10,11 |
When importing
Then placex contains
| object | parent_place_id | housenumber |
| N1 | W1 | None |
Scenario: POIs with own address do not inherit building address
Given the grid
| 10 | | | | | | 11 |
| | | 6 | 2 | 7 | | |
| | | 3 | 1 | | 5 | 4 |
| 12 | | 9 | | 8 | | |
And the named places
| osm | class | type | street |
| N1 | amenity | bank | bar |
And the named places
| osm | class | type | housenr |
| N2 | shop | bakery | 4 |
And the named places
| osm | class | type | addr_place |
| N3 | shop | supermarket| nowhere |
And the places
| osm | class | type | name |
| N4 | place | isolated_dwelling | theplace |
| N5 | place | isolated_dwelling | nowhere |
And the places
| osm | class | type | addr_place | housenr | geometry |
| W1 | building | yes | theplace | 3 | (6,7,8,9,6) |
And the places
| osm | class | type | name | geometry |
| W2 | highway | primary | bar | 10,11 |
| W3 | highway | residential | foo | 10,12 |
When importing
Then placex contains
| object | parent_place_id | housenumber |
| W1 | N4 | 3 |
| N1 | W2 | None |
| N2 | W2 | 4 |
| N3 | N5 | None |
Scenario: POIs parent a road if they are attached to it
Given the grid
| | 10 | |
| 20 | 1 | 21 |
| | 11 | |
And the named places
| osm | class | type |
| N1 | highway | bus_stop |
And the places
| osm | class | type | name | geometry |
| W1 | highway | secondary | North St | 10,11 |
| W2 | highway | unclassified | South St | 20,1,21 |
And the ways
| id | nodes |
| 1 | 10,11 |
| 2 | 20,1,21 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W2 |
Scenario: POIs do not parent non-roads they are attached to
Given the grid
| 10 | | 1 | | 11 | | 30 |
| 14 | | | | 15 | | |
| 13 | | 2 | | 12 | | 31 |
And the named places
| osm | class | type | street |
| N1 | highway | bus_stop | North St |
| N2 | highway | bus_stop | South St |
And the places
| osm | class | type | name | geometry |
| W1 | landuse | residential | North St | (14,15,12,2,13,14) |
| W2 | waterway| river | South St | 10,1,11 |
| W3 | highway | residential | foo | 30,31 |
And the ways
| id | nodes |
| 1 | 10,11,12,2,13,10 |
| 2 | 10,1,11 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W3 |
| N2 | W3 |
Scenario: POIs on building outlines inherit associated street relation
Given the grid
| 10 | | | | 11 |
| | 5 | 1 | 6 | |
| 12 | 8 | | 7 | |
And the named places
| osm | class | type | geometry |
| N1 | place | house | 1 |
| W1 | building | yes | (5,1,6,7,8,5)|
And the places
| osm | class | type | name | geometry |
| W2 | highway | primary | bar | 10,11 |
| W3 | highway | residential | foo | 10,12 |
And the relations
| id | members | tags+type |
| 1 | W1:house,W3:street | associatedStreet |
And the ways
| id | nodes |
| 1 | 5,1,6,7,8,5 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W3 |
# github #1056
Scenario: Full names should be preferably matched for nearest road
Given the grid
| 1 | | 2 | 5 |
| | | | |
| 3 | | | 4 |
| | 10| | |
And the places
| osm | class | type | name+name | geometry |
| W1 | highway | residential | Via Cavassico superiore | 1, 2 |
| W3 | highway | residential | Via Cavassico superiore | 2, 5 |
| W2 | highway | primary | Via Frazione Cavassico | 3, 4 |
And the named places
| osm | class | type | addr+street |
| N10 | shop | yes | Via Cavassico superiore |
When importing
Then placex contains
| object | parent_place_id |
| N10 | W1 |
Scenario: place=square may be parented via addr:place
Given the grid
| | | 9 | | |
| | 5 | | 6 | |
| | 8 | | 7 | |
And the places
| osm | class | type | name+name | geometry |
| W2 | place | square | Foo pl | (5, 6, 7, 8, 5) |
And the places
| osm | class | type | name+name | housenr | addr_place | geometry |
| N10 | shop | grocery | le shop | 5 | Foo pl | 9 |
When importing
Then placex contains
| object | rank_address |
| W2 | 25 |
Then placex contains
| object | parent_place_id |
| N10 | W2 |

View File

@@ -1,194 +0,0 @@
@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 | addr+country | country_code |
| N1 | - | 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 | addr+country | 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 | rank_search| addr+country | country_code |
| R1 | 4 | 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 | addr+country | 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: 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 0.01 grid
| 1 | 2 |
| 3 | |
Given the places
| osm | class | type | name+ref | geometry |
| R1 | boundary | postal_code | 554476 | (1,2,3,1) |
When importing
Then placex has no entry for R1
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) |
And the named places
| osm | class | type | geometry |
| R40 | place | country | (1 1, 2 2, 1 2, 1 1) |
| R41 | place | state | (3 3, 4 4, 3 4, 3 3) |
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 | 25 | 0 |
| R22 | 25 | 0 |
| R23 | 25 | 0 |
| R40 | 4 | 0 |
| R41 | 8 | 0 |
Scenario: search and address ranks for highways correctly assigned
Given the grid
| 10 | 1 | 11 | | 12 | | 13 | | 14 | | 15 | | 16 |
And the places
| osm | class | type |
| N1 | highway | bus_stop |
And the places
| osm | class | type | geometry |
| W1 | highway | primary | 10,11 |
| W2 | highway | secondary | 11,12 |
| W3 | highway | tertiary | 12,13 |
| W4 | highway | residential | 13,14 |
| W5 | highway | unclassified | 14,15 |
| W6 | highway | something | 15,16 |
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 | 30 | 30 |
Scenario: rank and inclusion of landuses
Given the 0.4 grid
| 1 | 2 | | | | | | 5 |
| 4 | 3 | | | | | | 6 |
Given the named places
| osm | class | type |
| N2 | landuse | residential |
And the named places
| osm | class | type | geometry |
| W2 | landuse | residential | 1,2,5 |
| W4 | landuse | residential | (1,4,3,1) |
| R2 | landuse | residential | (1,2,3,4,1) |
| R3 | landuse | forrest | (1,5,6,4,1) |
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 0.4 grid
| 1 | 2 | | | | | | 5 |
| 4 | 3 | | | | | | 6 |
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 | 1,2,5 |
| W3 | natural | foobar | 2,3 |
| R3 | natural | volcano | (1,2,4,1) |
| R4 | natural | foobar | (1,2,3,4,1) |
| R5 | natural | sea | (1,2,5,6,3,4,1) |
| R6 | natural | sea | (2,3,4,2) |
When importing
Then placex contains
| object | rank_search | rank_address |
| N2 | 18 | 0 |
| N4 | 18 | 0 |
| N5 | 22 | 0 |
| W2 | 18 | 0 |
| R3 | 18 | 0 |
| R4 | 22 | 0 |
| R5 | 4 | 0 |
| R6 | 4 | 0 |
| W3 | 22 | 0 |
Scenario: boundary ways for countries and states are ignored
Given the 0.3 grid
| 1 | 2 |
| 4 | 3 |
Given the named places
| osm | class | type | admin | geometry |
| W4 | boundary | administrative | 2 | (1,2,3,4,1) |
| R4 | boundary | administrative | 2 | (1,2,3,4,1) |
| W5 | boundary | administrative | 3 | (1,2,3,4,1) |
| R5 | boundary | administrative | 3 | (1,2,3,4,1) |
| W6 | boundary | administrative | 4 | (1,2,3,4,1) |
| R6 | boundary | administrative | 4 | (1,2,3,4,1) |
| W7 | boundary | administrative | 5 | (1,2,3,4,1) |
| R7 | boundary | administrative | 5 | (1,2,3,4,1) |
When importing
Then placex contains exactly
| object |
| R4 |
| R5 |
| R6 |
| W7 |
| R7 |

View File

@@ -1,210 +0,0 @@
@DB
Feature: Import of postcodes
Tests for postcode estimation
Scenario: Postcodes on the object are preferred over those on the address
Given the grid with origin FR
| 1 | | | | 4 | | 6 | | 8 |
| | 10 | | 11 | | | | | |
| | | 22 | | | | | | |
| 2 | | | | 3 | | 5 | | 7 |
And the named places
| osm | class | type | admin | addr+postcode | geometry |
| R1 | boundary | administrative | 6 | 10000 | (1,8,7,2,1) |
| R34 | boundary | administrative | 8 | 11000 | (1,6,5,2,1) |
| R4 | boundary | administrative | 10 | 11200 | (1,4,3,2,1) |
And the named places
| osm | class | type | addr+postcode | geometry |
| W93 | highway | residential | 11250 | 10,11 |
| N22 | building | yes | 11254 | 22 |
When importing
Then placex contains
| object | postcode |
| N22 | 11254 |
| W93 | 11250 |
| R4 | 11200 |
| R34 | 11000 |
| R1 | 10000 |
Scenario: Postcodes from a road are inherited by an attached building
Given the grid with origin DE
| 10 | | | | 11 |
| | 1 | 2 | | |
| | 4 | 3 | | |
And the named places
| osm | class | type | addr+postcode | geometry |
| W93 | highway | residential | 86034 | 10,11 |
And the named places
| osm | class | type | geometry |
| W22 | building | yes | (1,2,3,4,1) |
When importing
Then placex contains
| object | postcode | parent_place_id |
| W22 | 86034 | W93 |
Scenario: Postcodes from the lowest admin area are inherited by ways
Given the grid with origin FR
| 1 | | | | 4 | | 6 | | 8 |
| | 10 | | 11 | | | | | |
| 2 | | | | 3 | | 5 | | 7 |
And the named places
| osm | class | type | admin | addr+postcode | geometry |
| R1 | boundary | administrative | 6 | 10000 | (1,8,7,2,1) |
| R34 | boundary | administrative | 8 | 11000 | (1,6,5,2,1) |
| R4 | boundary | administrative | 10 | 11200 | (1,4,3,2,1) |
And the named places
| osm | class | type | geometry |
| W93 | highway | residential | 10,11 |
When importing
Then placex contains
| object | postcode |
| W93 | 11200 |
Scenario: Postcodes from the lowest admin area with postcode are inherited by ways
Given the grid with origin FR
| 1 | | | | 4 | | 6 | | 8 |
| | 10 | | 11 | | | | | |
| 2 | | | | 3 | | 5 | | 7 |
And the named places
| osm | class | type | admin | addr+postcode | geometry |
| R1 | boundary | administrative | 6 | 10000 | (1,8,7,2,1) |
| R34 | boundary | administrative | 8 | 11000 | (1,6,5,2,1) |
And the named places
| osm | class | type | admin | geometry |
| R4 | boundary | administrative | 10 | (1,4,3,2,1) |
And the named places
| osm | class | type | geometry |
| W93 | highway | residential | 10,11 |
When importing
Then placex contains
| object | postcode | parent_place_id |
| W93 | 11000 | R4 |
Scenario: Postcodes from the lowest admin area are inherited by buildings
Given the grid with origin FR
| 1 | | | | 4 | | 6 | | 8 |
| | 10 | | 11 | | | | | |
| | 13 | | 12 | | | | | |
| 2 | | | | 3 | | 5 | | 7 |
And the named places
| osm | class | type | admin | addr+postcode | geometry |
| R1 | boundary | administrative | 6 | 10000 | (1,8,7,2,1) |
| R34 | boundary | administrative | 8 | 11000 | (1,6,5,2,1) |
| R4 | boundary | administrative | 10 | 11200 | (1,4,3,2,1) |
And the named places
| osm | class | type | geometry |
| W22 | building | yes | (10,11,12,13,10) |
When importing
Then placex contains
| object | postcode |
| W22 | 11200 |
Scenario: Roads get postcodes from nearby named buildings without other info
Given the grid with origin US
| 10 | | | | 11 |
| | 1 | 2 | | |
| | 4 | 3 | | |
And the named places
| osm | class | type | geometry |
| W93 | highway | residential | 10,11 |
And the named places
| osm | class | type | addr+postcode | geometry |
| W22 | building | yes | 45023 | (1,2,3,4,1) |
When importing
Then placex contains
| object | postcode |
| W93 | 45023 |
Scenario: Road areas get postcodes from nearby named buildings without other info
Given the grid with origin US
| 10 | | | | 11 |
| 13 | | | | 12 |
| | 1 | 2 | | |
| | 4 | 3 | | |
And the named places
| osm | class | type | geometry |
| W93 | highway | pedestriant | (10,11,12,13,10) |
And the named places
| osm | class | type | addr+postcode | geometry |
| W22 | building | yes | 45023 | (1,2,3,4,1) |
When importing
Then placex contains
| object | postcode |
| W93 | 45023 |
Scenario: Roads get postcodes from nearby unnamed buildings without other info
Given the grid with origin US
| 10 | | | | 11 |
| | 1 | 2 | | |
| | 4 | 3 | | |
And the named places
| osm | class | type | geometry |
| W93 | highway | residential | 10,11 |
And the places
| osm | class | type | addr+postcode | geometry |
| W22 | place | postcode | 45023 | (1,2,3,4,1) |
When importing
Then placex contains
| object | postcode |
| W93 | 45023 |
Scenario: Postcodes from admin boundaries are preferred over estimated postcodes
Given the grid with origin FR
| 1 | | | | 4 | | 6 | | 8 |
| | 10 | | 11 | | | | | |
| | | 22 | | | | | | |
| 2 | | | | 3 | | 5 | | 7 |
And the named places
| osm | class | type | admin | addr+postcode | geometry |
| R1 | boundary | administrative | 6 | 10000 | (1,8,7,2,1) |
| R34 | boundary | administrative | 8 | 11000 | (1,6,5,2,1) |
| R4 | boundary | administrative | 10 | 11200 | (1,4,3,2,1) |
And the named places
| osm | class | type | geometry |
| W93 | highway | residential | 10,1 |
And the named places
| osm | class | type | addr+postcode |
| N22 | building | yes | 45023 |
When importing
Then placex contains
| object | postcode |
| W93 | 11200 |
Scenario: Postcodes are added to the postcode
Given the places
| osm | class | type | addr+postcode | addr+housenumber | geometry |
| N34 | place | house | 01982 | 111 |country:de |
When importing
Then location_postcode contains exactly
| country | postcode | geometry |
| de | 01982 | country:de |
@Fail
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 location_postcode contains exactly
| postcode | country | rank_search | rank_address |
| E45 2CD | gb | 25 | 5 |
| E45 2 | gb | 23 | 5 |
| Y45 | gb | 21 | 5 |
Scenario: Postcodes outside all countries are not added to the postcode table
Given the places
| osm | class | type | addr+postcode | addr+housenumber | addr+place | geometry |
| N34 | place | house | 01982 | 111 | Null Island | 0 0.00001 |
And the places
| osm | class | type | name | geometry |
| N1 | place | hamlet | Null Island | 0 0 |
When importing
Then location_postcode contains exactly
| country | postcode | geometry |
When sending search query "111, 01982 Null Island"
Then results contain
| osm | display_name |
| N34 | 111, Null Island, 01982 |

View File

@@ -1,300 +0,0 @@
@DB
Feature: Rank assignment
Tests for assignment of search and address ranks.
Scenario: Ranks for place nodes are assigned according to their type
Given the named places
| osm | class | type | geometry |
| N1 | foo | bar | 0 0 |
| N11 | place | Continent | 0 0 |
| N12 | place | continent | 0 0 |
| N13 | place | sea | 0 0 |
| N14 | place | country | 0 0 |
| N15 | place | state | 0 0 |
| N16 | place | region | 0 0 |
| N17 | place | county | 0 0 |
| N18 | place | city | 0 0 |
| N19 | place | island | 0 0 |
| N36 | place | house | 0 0 |
And the named places
| osm | class | type | extra+capital | geometry |
| N101 | place | city | yes | 0 0 |
When importing
Then placex contains
| object | rank_search | rank_address |
| N1 | 30 | 30 |
| N11 | 22 | 0 |
| N12 | 2 | 0 |
| N13 | 2 | 0 |
| N14 | 4 | 0 |
| N15 | 8 | 0 |
| N16 | 18 | 0 |
| N17 | 12 | 12 |
| N18 | 16 | 16 |
| N19 | 17 | 0 |
| N101 | 15 | 16 |
| N36 | 30 | 30 |
Scenario: Ranks for boundaries are assigned according to admin level
Given 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 | administrative | 6 | (0 0, 1 0, 0 1, 0 0) |
| R23 | boundary | administrative | 10 | (0 0, 1 1, 1 0, 0 0) |
When importing
Then placex contains
| object | rank_search | rank_address |
| R20 | 4 | 4 |
| R21 | 25 | 0 |
| R22 | 12 | 12 |
| R23 | 20 | 20 |
Scenario: Ranks for addressable boundaries with place assignment go with place address ranks if available
Given the named places
| osm | class | type | admin | extra+place | geometry |
| R20 | boundary | administrative | 3 | state | (1 1, 2 2, 1 2, 1 1) |
| R21 | boundary | administrative | 32 | suburb | (3 3, 4 4, 3 4, 3 3) |
| R22 | boundary | administrative | 6 | town | (0 0, 1 0, 0 1, 0 0) |
| R23 | boundary | administrative | 10 | village | (0 0, 1 1, 1 0, 0 0) |
When importing
Then placex contains
| object | rank_search | rank_address |
| R20 | 6 | 6 |
| R21 | 25 | 0 |
| R22 | 12 | 16 |
| R23 | 20 | 16 |
Scenario: Place address ranks cannot overtake a parent address rank
Given the named places
| osm | class | type | admin | extra+place | geometry |
| R20 | boundary | administrative | 8 | town | (0 0, 0 2, 2 2, 2 0, 0 0) |
| R21 | boundary | administrative | 9 | municipality | (0 0, 0 1, 1 1, 1 0, 0 0) |
| R22 | boundary | administrative | 9 | suburb | (0 0, 0 1, 1 1, 1 0, 0 0) |
When importing
Then placex contains
| object | rank_search | rank_address |
| R20 | 16 | 16 |
| R21 | 18 | 18 |
| R22 | 18 | 20 |
Then place_addressline contains
| object | address | cached_rank_address |
| R21 | R20 | 16 |
| R22 | R20 | 16 |
Scenario: Admin levels cannot overtake each other due to place address ranks
Given the named places
| osm | class | type | admin | extra+place | geometry |
| R20 | boundary | administrative | 6 | town | (0 0, 0 2, 2 2, 2 0, 0 0) |
| R21 | boundary | administrative | 8 | | (0 0, 0 1, 1 1, 1 0, 0 0) |
| R22 | boundary | administrative | 8 | suburb | (0 0, 0 1, 1 1, 1 0, 0 0) |
When importing
Then placex contains
| object | rank_search | rank_address |
| R20 | 12 | 16 |
| R21 | 16 | 18 |
| R22 | 16 | 20 |
Then place_addressline contains
| object | address | cached_rank_address |
| R21 | R20 | 16 |
| R22 | R20 | 16 |
Scenario: Admin levels cannot overtake each other due to place address ranks even when slightly misaligned
Given the named places
| osm | class | type | admin | extra+place | geometry |
| R20 | boundary | administrative | 6 | town | (0 0, 0 2, 2 2, 2 0, 0 0) |
| R21 | boundary | administrative | 8 | | (0 0, -0.0001 1, 1 1, 1 0, 0 0) |
When importing
Then placex contains
| object | rank_search | rank_address |
| R20 | 12 | 16 |
| R21 | 16 | 18 |
Then place_addressline contains
| object | address | cached_rank_address |
| R21 | R20 | 16 |
Scenario: Admin levels must not be larger than 25
Given the named places
| osm | class | type | admin | extra+place | geometry |
| R20 | boundary | administrative | 6 | neighbourhood | (0 0, 0 2, 2 2, 2 0, 0 0) |
| R21 | boundary | administrative | 7 | | (0 0, 0 1, 1 1, 1 0, 0 0) |
| R22 | boundary | administrative | 8 | | (0 0, 0 0.5, 0.5 0.5, 0.5 0, 0 0) |
When importing
Then placex contains
| object | rank_search | rank_address |
| R20 | 12 | 22 |
| R21 | 14 | 24 |
| R22 | 16 | 25 |
Scenario: admin levels contained in a place area must not overtake address ranks
Given the named places
| osm | class | type | admin | geometry |
| R10 | place | city | 15 | (0 0, 0 2, 2 0, 0 0) |
| R20 | boundary | administrative | 6 | (0 0, 0 1, 1 0, 0 0) |
When importing
Then placex contains
| object | rank_search | rank_address |
| R10 | 16 | 16 |
| R20 | 12 | 18 |
Scenario: admin levels overlapping a place area are not demoted
Given the named places
| osm | class | type | admin | geometry |
| R10 | place | city | 15 | (0 0, 0 2, 2 0, 0 0) |
| R20 | boundary | administrative | 6 | (-1 0, 0 1, 1 0, -1 0) |
When importing
Then placex contains
| object | rank_search | rank_address |
| R10 | 16 | 16 |
| R20 | 12 | 12 |
Scenario: admin levels with equal area as a place area are not demoted
Given the named places
| osm | class | type | admin | geometry |
| R10 | place | city | 15 | (0 0, 0 2, 2 0, 0 0) |
| R20 | boundary | administrative | 6 | (0 0, 0 2, 2 0, 0 0) |
When importing
Then placex contains
| object | rank_search | rank_address |
| R10 | 16 | 16 |
| R20 | 12 | 12 |
Scenario: adjacent admin_levels are considered the same object when they have the same wikidata
Given the named places
| osm | class | type | admin | extra+wikidata | geometry |
| N20 | place | square | 15 | Q123 | 0.1 0.1 |
| R23 | boundary | administrative | 10 | Q444 | (0 0, 0 1, 1 1, 1 0, 0 0) |
| R21 | boundary | administrative | 9 | Q444 | (0 0, 0 1, 1 1, 1 0, 0 0) |
| R22 | boundary | administrative | 8 | Q444 | (0 0, 0 1, 1 1, 1 0, 0 0) |
When importing
Then placex contains
| object | rank_search | rank_address |
| R23 | 20 | 0 |
| R21 | 18 | 0 |
| R22 | 16 | 16 |
Then place_addressline contains
| object | address | cached_rank_address |
| N20 | R22 | 16 |
Then place_addressline doesn't contain
| object | address |
| N20 | R21 |
| N20 | R23 |
Scenario: adjacent admin_levels are considered different objects when they have different wikidata
Given the named places
| osm | class | type | admin | extra+wikidata | geometry |
| N20 | place | square | 15 | Q123 | 0.1 0.1 |
| R21 | boundary | administrative | 9 | Q4441 | (0 0, 0 1, 1 1, 1 0, 0 0) |
| R22 | boundary | administrative | 8 | Q444 | (0 0, 0 1, 1 1, 1 0, 0 0) |
When importing
Then placex contains
| object | rank_search | rank_address |
| R21 | 18 | 18 |
| R22 | 16 | 16 |
Then place_addressline contains
| object | address | cached_rank_address |
| N20 | R22 | 16 |
| N20 | R21 | 18 |
Scenario: Mixes of admin boundaries and place areas I
Given the grid
| 1 | | 10 | | | 2 |
| | 9 | | | | |
| 20| | 21 | | | |
| 4 | | 11 | | | 3 |
And the places
| osm | class | type | admin | name | geometry |
| R1 | boundary | administrative | 5 | Greater London | (1,2,3,4,1) |
| R2 | boundary | administrative | 8 | Kensington | (1,10,11,4,1) |
And the places
| osm | class | type | name | geometry |
| R10 | place | city | London | (1,2,3,4,1) |
| N9 | place | town | Fulham | 9 |
| W1 | highway | residential | Lots Grove | 20,21 |
When importing
Then placex contains
| object | rank_search | rank_address |
| R1 | 10 | 10 |
| R10 | 16 | 16 |
| R2 | 16 | 18 |
| N9 | 18 | 18 |
And place_addressline contains
| object | address | isaddress | cached_rank_address |
| W1 | R1 | True | 10 |
| W1 | R10 | True | 16 |
| W1 | R2 | True | 18 |
| W1 | N9 | False | 18 |
Scenario: Mixes of admin boundaries and place areas II
Given the grid
| 1 | | 10 | | 5 | 2 |
| | 9 | | | | |
| 20| | 21 | | | |
| 4 | | 11 | | 6 | 3 |
And the places
| osm | class | type | admin | name | geometry |
| R1 | boundary | administrative | 5 | Greater London | (1,2,3,4,1) |
| R2 | boundary | administrative | 8 | London | (1,5,6,4,1) |
And the places
| osm | class | type | name | geometry |
| R10 | place | city | Westminster | (1,10,11,4,1) |
| N9 | place | town | Fulham | 9 |
| W1 | highway | residential | Lots Grove | 20,21 |
When importing
Then placex contains
| object | rank_search | rank_address |
| R1 | 10 | 10 |
| R2 | 16 | 16 |
| R10 | 16 | 18 |
| N9 | 18 | 18 |
And place_addressline contains
| object | address | isaddress | cached_rank_address |
| W1 | R1 | True | 10 |
| W1 | R10 | True | 18 |
| W1 | R2 | True | 16 |
| W1 | N9 | False | 18 |
Scenario: POI nodes with place tags
Given the places
| osm | class | type | name | extratags |
| N23 | amenity | playground | AB | "place": "city" |
| N23 | place | city | AB | "amenity": "playground" |
When importing
Then placex contains exactly
| object | rank_search | rank_address |
| N23:amenity | 30 | 30 |
| N23:place | 16 | 16 |
Scenario: Address rank 25 is only used for addr:place
Given the grid
| 10 | 33 | 34 | 11 |
Given the places
| osm | class | type | name |
| N10 | place | village | vil |
| N11 | place | farm | farm |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | RD | 33,11 |
And the places
| osm | class | type | name | addr+farm | geometry |
| W2 | highway | residential | RD2 | farm | 34,11 |
And the places
| osm | class | type | housenr |
| N33 | place | house | 23 |
And the places
| osm | class | type | housenr | addr+place |
| N34 | place | house | 23 | farm |
When importing
Then placex contains
| object | parent_place_id |
| N11 | N10 |
| N33 | W1 |
| N34 | N11 |
And place_addressline contains
| object | address |
| W1 | N10 |
| W2 | N10 |
| W2 | N11 |

View File

@@ -1,428 +0,0 @@
@DB
Feature: Creation of search terms
Tests that search_name table is filled correctly
Scenario: Semicolon-separated names appear as separate full names
Given the places
| osm | class | type | name+alt_name |
| N1 | place | city | New York; Big Apple |
When importing
Then search_name contains
| object | name_vector |
| N1 | #New York, #Big Apple |
Scenario: Comma-separated names appear as a single full name
Given the places
| osm | class | type | name+alt_name |
| N1 | place | city | New York, Big Apple |
When importing
Then search_name contains
| object | name_vector |
| N1 | #New York Big Apple |
Scenario: Name parts before brackets appear as full names
Given the places
| osm | class | type | name+name |
| N1 | place | city | Halle (Saale) |
When importing
Then search_name contains
| object | name_vector |
| N1 | #Halle Saale, #Halle |
Scenario: Unnamed POIs have no search entry
Given the grid
| | 1 | | |
| 10 | | | 11 |
And the places
| osm | class | type |
| N1 | place | house |
And the named places
| osm | class | type | geometry |
| W1 | highway | residential | 10,11 |
When importing
Then search_name has no entry for N1
Scenario: Unnamed POI has a search entry when it has unknown addr: tags
Given the grid
| | 1 | | |
| 10 | | | 11 |
And the places
| osm | class | type | housenr | addr+city |
| N1 | place | house | 23 | Walltown |
And the places
| osm | class | type | name+name | geometry |
| W1 | highway | residential | Rose Street | 10,11 |
When importing
Then search_name contains
| object | nameaddress_vector |
| N1 | #Rose Street, Walltown |
When sending search query "23 Rose Street, Walltown"
Then results contain
| osm | display_name |
| N1 | 23, Rose Street |
When sending search query "Walltown, Rose Street 23"
Then results contain
| osm | display_name |
| N1 | 23, Rose Street |
When sending search query "Rose Street 23, Walltown"
Then results contain
| osm | display_name |
| N1 | 23, Rose Street |
Scenario: Searching for unknown addr: tags also works for multiple words
Given the grid
| | 1 | | |
| 10 | | | 11 |
And the places
| osm | class | type | housenr | addr+city |
| N1 | place | house | 23 | Little Big Town |
And the places
| osm | class | type | name+name | geometry |
| W1 | highway | residential | Rose Street | 10,11 |
When importing
Then search_name contains
| object | nameaddress_vector |
| N1 | #Rose Street, rose, Little, Big, Town |
When sending search query "23 Rose Street, Little Big Town"
Then results contain
| osm | display_name |
| N1 | 23, Rose Street |
When sending search query "Rose Street 23, Little Big Town"
Then results contain
| osm | display_name |
| N1 | 23, Rose Street |
When sending search query "Little big Town, Rose Street 23"
Then results contain
| osm | display_name |
| N1 | 23, Rose Street |
Scenario: Unnamed POI has no search entry when it has known addr: tags
Given the grid
| | 1 | | |
| 10 | | | 11 |
And the places
| osm | class | type | housenr | addr+city |
| N1 | place | house | 23 | Walltown |
And the places
| osm | class | type | name+name | addr+city | geometry |
| W1 | highway | residential | Rose Street | Walltown | 10,11 |
When importing
Then search_name has no entry for N1
When sending search query "23 Rose Street, Walltown"
Then results contain
| osm | display_name |
| N1 | 23, Rose Street |
Scenario: Unnamed POI must have a house number to get a search entry
Given the grid
| | 1 | | |
| 10 | | | 11 |
And the places
| osm | class | type | addr+city |
| N1 | place | house | Walltown |
And the places
| osm | class | type | name+name | geometry |
| W1 | highway | residential | Rose Street | 10,11 |
When importing
Then search_name has no entry for N1
Scenario: Unnamed POIs inherit parent name when unknown addr:place is present
Given the grid
| 100 | | | | | 101 |
| | | 1 | | | |
| 103 | 10 | | | 11 | 102 |
And the places
| osm | class | type | housenr | addr+place |
| N1 | place | house | 23 | Walltown |
And the places
| osm | class | type | name+name | geometry |
| W1 | highway | residential | Rose Street | 10,11 |
| R1 | place | city | Strange Town | (100,101,102,103,100) |
When importing
Then placex contains
| object | parent_place_id |
| N1 | R1 |
When sending search query "23 Rose Street"
Then exactly 1 results are returned
And results contain
| osm | display_name |
| W1 | Rose Street, Strange Town |
When sending search query "23 Walltown, Strange Town"
Then results contain
| osm | display_name |
| N1 | 23, Walltown, Strange Town |
When sending search query "Walltown 23, Strange Town"
Then results contain
| osm | display_name |
| N1 | 23, Walltown, Strange Town |
When sending search query "Strange Town, Walltown 23"
Then results contain
| osm | display_name |
| N1 | 23, Walltown, Strange Town |
Scenario: Named POIs can be searched by housenumber when unknown addr:place is present
Given the grid
| 100 | | | | | 101 |
| | | 1 | | | |
| 103 | 10 | | | 11 | 102 |
And the places
| osm | class | type | name | housenr | addr+place |
| N1 | place | house | Blue house | 23 | Walltown |
And the places
| osm | class | type | name+name | geometry |
| W1 | highway | residential | Rose Street | 10,11 |
| R1 | place | city | Strange Town | (100,101,102,103,100) |
When importing
When sending search query "23 Walltown, Strange Town"
Then results contain
| osm | display_name |
| N1 | Blue house, 23, Walltown, Strange Town |
When sending search query "Walltown 23, Strange Town"
Then results contain
| osm | display_name |
| N1 | Blue house, 23, Walltown, Strange Town |
When sending search query "Strange Town, Walltown 23"
Then results contain
| osm | display_name |
| N1 | Blue house, 23, Walltown, Strange Town |
When sending search query "Strange Town, Walltown 23, Blue house"
Then results contain
| osm | display_name |
| N1 | Blue house, 23, Walltown, Strange Town |
When sending search query "Strange Town, Walltown, Blue house"
Then results contain
| osm | display_name |
| N1 | Blue house, 23, Walltown, Strange Town |
Scenario: Named POIs can be found when unknown multi-word addr:place is present
Given the grid
| 100 | | | | | 101 |
| | | 1 | | | |
| 103 | 10 | | | 11 | 102 |
And the places
| osm | class | type | name | housenr | addr+place |
| N1 | place | house | Blue house | 23 | Moon sun |
And the places
| osm | class | type | name+name | geometry |
| W1 | highway | residential | Rose Street | 10,11 |
| R1 | place | city | Strange Town | (100,101,102,103,100) |
When importing
When sending search query "23 Moon Sun, Strange Town"
Then results contain
| osm | display_name |
| N1 | Blue house, 23, Moon sun, Strange Town |
When sending search query "Blue house, Moon Sun, Strange Town"
Then results contain
| osm | display_name |
| N1 | Blue house, 23, Moon sun, Strange Town |
Scenario: Unnamed POIs doesn't inherit parent name when addr:place is present only in parent address
Given the grid
| 100 | | | | | 101 |
| | | 1 | | | |
| 103 | 10 | | | 11 | 102 |
And the places
| osm | class | type | housenr | addr+place |
| N1 | place | house | 23 | Walltown |
And the places
| osm | class | type | name+name | addr+city | geometry |
| W1 | highway | residential | Rose Street | Walltown | 10,11 |
| R1 | place | suburb | Strange Town | Walltown | (100,101,102,103,100) |
When importing
When sending search query "23 Rose Street, Walltown"
Then exactly 1 result is returned
And results contain
| osm | display_name |
| W1 | Rose Street, Strange Town |
When sending search query "23 Walltown"
Then exactly 1 result is returned
And results contain
| osm | display_name |
| N1 | 23, Walltown, Strange Town |
Scenario: Unnamed POIs does inherit parent name when unknown addr:place and addr:street is present
Given the grid
| | 1 | | |
| 10 | | | 11 |
And the places
| osm | class | type | housenr | addr+place | addr+street |
| N1 | place | house | 23 | Walltown | Lily Street |
And the places
| osm | class | type | name+name | geometry |
| W1 | highway | residential | Rose Street | 10,11 |
When importing
Then search_name has no entry for N1
When sending search query "23 Rose Street"
Then results contain
| osm | display_name |
| N1 | 23, Rose Street |
When sending search query "23 Lily Street"
Then exactly 0 results are returned
Scenario: An unknown addr:street is ignored
Given the grid
| | 1 | | |
| 10 | | | 11 |
And the places
| osm | class | type | housenr | addr+street |
| N1 | place | house | 23 | Lily Street |
And the places
| osm | class | type | name+name | geometry |
| W1 | highway | residential | Rose Street | 10,11 |
When importing
Then search_name has no entry for N1
When sending search query "23 Rose Street"
Then results contain
| osm | display_name |
| N1 | 23, Rose Street |
When sending search query "23 Lily Street"
Then exactly 0 results are returned
Scenario: Named POIs get unknown address tags added in the search_name table
Given the grid
| | 1 | | |
| 10 | | | 11 |
And the places
| osm | class | type | name+name | housenr | addr+city |
| N1 | place | house | Green Moss | 26 | Walltown |
And the places
| osm | class | type | name+name | geometry |
| W1 | highway | residential | Rose Street | 10,11 |
When importing
Then search_name contains
| object | name_vector | nameaddress_vector |
| N1 | #Green Moss | #Rose Street, Walltown |
When sending search query "Green Moss, Rose Street, Walltown"
Then results contain
| osm | display_name |
| N1 | Green Moss, 26, Rose Street |
When sending search query "Green Moss, 26, Rose Street, Walltown"
Then results contain
| osm | display_name |
| N1 | Green Moss, 26, Rose Street |
When sending search query "26, Rose Street, Walltown"
Then results contain
| osm | display_name |
| N1 | Green Moss, 26, Rose Street |
When sending search query "Rose Street 26, Walltown"
Then results contain
| osm | display_name |
| N1 | Green Moss, 26, Rose Street |
When sending search query "Walltown, Rose Street 26"
Then results contain
| osm | display_name |
| N1 | Green Moss, 26, Rose Street |
Scenario: Named POI doesn't inherit parent name when addr:place is present only in parent address
Given the grid
| 100 | | | | | 101 |
| | | 1 | | | |
| 103 | 10 | | | 11 | 102 |
And the places
| osm | class | type | name+name | addr+place |
| N1 | place | house | Green Moss | Walltown |
And the places
| osm | class | type | name+name | geometry |
| W1 | highway | residential | Rose Street | 10,11 |
| R1 | place | suburb | Strange Town | (100,101,102,103,100) |
When importing
When sending search query "Green Moss, Rose Street, Walltown"
Then exactly 0 result is returned
When sending search query "Green Moss, Walltown"
Then results contain
| osm | display_name |
| N1 | Green Moss, Walltown, Strange Town |
Scenario: Named POIs inherit address from parent
Given the grid
| | 1 | | |
| 10 | | | 11 |
And the places
| osm | class | type | name | geometry |
| N1 | place | house | foo | 1 |
| W1 | highway | residential | the road | 10,11 |
When importing
Then search_name contains
| object | name_vector | nameaddress_vector |
| N1 | foo | #the road |
Scenario: Some addr: tags are added to address
Given the grid
| | 2 | 3 | |
| 10 | | | 11 |
And the places
| osm | class | type | name |
| N2 | place | city | bonn |
| N3 | place | suburb | smalltown|
And the named places
| osm | class | type | addr+city | addr+municipality | addr+suburb | geometry |
| W1 | highway | service | bonn | New York | Smalltown | 10,11 |
When importing
Then search_name contains
| object | nameaddress_vector |
| W1 | bonn, new, york, smalltown |
Scenario: A known addr:* tag is added even if the name is unknown
Given the grid
| 10 | | | | 11 |
And the places
| osm | class | type | name | addr+city | geometry |
| W1 | highway | residential | Road | Nandu | 10,11 |
When importing
Then search_name contains
| object | nameaddress_vector |
| W1 | nandu |
Scenario: addr:postcode is not added to the address terms
Given the grid with origin DE
| | 1 | | |
| 10 | | | 11 |
And the places
| osm | class | type | name+ref |
| N1 | place | state | 12345 |
And the named places
| osm | class | type | addr+postcode | geometry |
| W1 | highway | residential | 12345 | 10,11 |
When importing
Then search_name contains not
| object | nameaddress_vector |
| W1 | 12345 |
Scenario: a linked place does not show up in search name
Given the 0.01 grid
| 10 | | 11 |
| | 2 | |
| 13 | | 12 |
Given the named places
| osm | class | type | admin | geometry |
| R13 | boundary | administrative | 9 | (10,11,12,13,10) |
And the named places
| osm | class | type |
| N2 | place | city |
And the relations
| id | members | tags+type |
| 13 | N2:label | boundary |
When importing
Then placex contains
| object | linked_place_id |
| N2 | R13 |
And search_name has no entry for N2
Scenario: a linked waterway does not show up in search name
Given the grid
| 1 | | 2 | | 3 |
And the places
| osm | class | type | name | geometry |
| W1 | waterway | river | Rhein | 1,2 |
| W2 | waterway | river | Rhein | 2,3 |
| R13 | waterway | river | Rhein | 1,2,3 |
And the relations
| id | members | tags+type |
| 13 | W1,W2:main_stream | waterway |
When importing
Then placex contains
| object | linked_place_id |
| W1 | R13 |
| W2 | R13 |
And search_name has no entry for W1
And search_name has no entry for W2

View File

@@ -1,321 +0,0 @@
@DB
Feature: Searching of house numbers
Test for specialised treeatment of housenumbers
Background:
Given the grid
| 1 | | 2 | | 3 |
| | 9 | | | |
| | | | | 4 |
Scenario: A simple ascii digit housenumber is found
Given the places
| osm | class | type | housenr | geometry |
| N1 | building | yes | 45 | 9 |
And the places
| osm | class | type | name | geometry |
| W10 | highway | path | North Road | 1,2,3 |
When importing
And sending search query "45, North Road"
Then results contain
| osm |
| N1 |
When sending search query "North Road 45"
Then results contain
| osm |
| N1 |
Scenario Outline: Numeral housenumbers in any script are found
Given the places
| osm | class | type | housenr | geometry |
| N1 | building | yes | <number> | 9 |
And the places
| osm | class | type | name | geometry |
| W10 | highway | path | North Road | 1,2,3 |
When importing
And sending search query "45, North Road"
Then results contain
| osm |
| N1 |
When sending search query "North Road "
Then results contain
| osm |
| N1 |
When sending search query "North Road 𑁪𑁫"
Then results contain
| osm |
| N1 |
Examples:
| number |
| 45 |
| |
| 𑁪𑁫 |
Scenario Outline: Each housenumber in a list is found
Given the places
| osm | class | type | housenr | geometry |
| N1 | building | yes | <hnrs> | 9 |
And the places
| osm | class | type | name | geometry |
| W10 | highway | path | Multistr | 1,2,3 |
When importing
When sending search query "2 Multistr"
Then results contain
| osm |
| N1 |
When sending search query "4 Multistr"
Then results contain
| osm |
| N1 |
When sending search query "12 Multistr"
Then results contain
| osm |
| N1 |
Examples:
| hnrs |
| 2;4;12 |
| 2,4,12 |
| 2, 4, 12 |
Scenario Outline: Housenumber - letter combinations are found
Given the places
| osm | class | type | housenr | geometry |
| N1 | building | yes | <hnr> | 9 |
And the places
| osm | class | type | name | geometry |
| W10 | highway | path | Multistr | 1,2,3 |
When importing
When sending search query "2A Multistr"
Then results contain
| osm |
| N1 |
When sending search query "2 a Multistr"
Then results contain
| osm |
| N1 |
When sending search query "2-A Multistr"
Then results contain
| osm |
| N1 |
When sending search query "Multistr 2 A"
Then results contain
| osm |
| N1 |
Examples:
| hnr |
| 2a |
| 2 A |
| 2-a |
| 2/A |
Scenario Outline: Number - Number combinations as a housenumber are found
Given the places
| osm | class | type | housenr | geometry |
| N1 | building | yes | <hnr> | 9 |
And the places
| osm | class | type | name | geometry |
| W10 | highway | path | Chester St | 1,2,3 |
When importing
When sending search query "34-10 Chester St"
Then results contain
| osm |
| N1 |
When sending search query "34/10 Chester St"
Then results contain
| osm |
| N1 |
When sending search query "34 10 Chester St"
Then results contain
| osm |
| N1 |
When sending search query "3410 Chester St"
Then results contain
| osm |
| W10 |
Examples:
| hnr |
| 34-10 |
| 34 10 |
| 34/10 |
Scenario Outline: a bis housenumber is found
Given the places
| osm | class | type | housenr | geometry |
| N1 | building | yes | <hnr> | 9 |
And the places
| osm | class | type | name | geometry |
| W10 | highway | path | Rue Paris | 1,2,3 |
When importing
When sending search query "Rue Paris 45bis"
Then results contain
| osm |
| N1 |
When sending search query "Rue Paris 45 BIS"
Then results contain
| osm |
| N1 |
When sending search query "Rue Paris 45BIS"
Then results contain
| osm |
| N1 |
When sending search query "Rue Paris 45 bis"
Then results contain
| osm |
| N1 |
Examples:
| hnr |
| 45bis |
| 45BIS |
| 45 BIS |
| 45 bis |
Scenario Outline: a ter housenumber is found
Given the places
| osm | class | type | housenr | geometry |
| N1 | building | yes | <hnr> | 9 |
And the places
| osm | class | type | name | geometry |
| W10 | highway | path | Rue du Berger | 1,2,3 |
When importing
When sending search query "Rue du Berger 45ter"
Then results contain
| osm |
| N1 |
When sending search query "Rue du Berger 45 TER"
Then results contain
| osm |
| N1 |
When sending search query "Rue du Berger 45TER"
Then results contain
| osm |
| N1 |
When sending search query "Rue du Berger 45 ter"
Then results contain
| osm |
| N1 |
Examples:
| hnr |
| 45ter |
| 45TER |
| 45 ter |
| 45 TER |
Scenario Outline: a number - letter - number combination housenumber is found
Given the places
| osm | class | type | housenr | geometry |
| N1 | building | yes | <hnr> | 9 |
And the places
| osm | class | type | name | geometry |
| W10 | highway | path | Herengracht | 1,2,3 |
When importing
When sending search query "501-H 1 Herengracht"
Then results contain
| osm |
| N1 |
When sending search query "501H-1 Herengracht"
Then results contain
| osm |
| N1 |
When sending search query "501H1 Herengracht"
Then results contain
| osm |
| N1 |
When sending search query "501-H1 Herengracht"
Then results contain
| osm |
| N1 |
Examples:
| hnr |
| 501 H1 |
| 501H 1 |
| 501/H/1 |
| 501h1 |
Scenario Outline: Russian housenumbers are found
Given the places
| osm | class | type | housenr | geometry |
| N1 | building | yes | <hnr> | 9 |
And the places
| osm | class | type | name | geometry |
| W10 | highway | path | Голубинская улица | 1,2,3 |
When importing
When sending search query "Голубинская улица 55к3"
Then results contain
| osm |
| N1 |
When sending search query "Голубинская улица 55 k3"
Then results contain
| osm |
| N1 |
When sending search query "Голубинская улица 55 к-3"
Then results contain
| osm |
| N1 |
Examples:
| hnr |
| 55к3 |
| 55 к3 |
Scenario: A name mapped as a housenumber is found
Given the places
| osm | class | type | housenr | geometry |
| N1 | building | yes | Warring | 9 |
And the places
| osm | class | type | name | geometry |
| W10 | highway | path | Chester St | 1,2,3 |
When importing
When sending search query "Chester St Warring"
Then results contain
| osm |
| N1 |
Scenario: Interpolations are found according to their type
Given the grid
| 10 | | 11 |
| 100 | | 101 |
| 20 | | 21 |
And the places
| osm | class | type | name | geometry |
| W100 | highway | residential | Ringstr | 100, 101 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W10 | place | houses | even | 10, 11 |
| W20 | place | houses | odd | 20, 21 |
And the places
| osm | class | type | housenr | geometry |
| N10 | place | house | 10 | 10 |
| N11 | place | house | 20 | 11 |
| N20 | place | house | 11 | 20 |
| N21 | place | house | 21 | 21 |
And the ways
| id | nodes |
| 10 | 10, 11 |
| 20 | 20, 21 |
When importing
When sending search query "Ringstr 12"
Then results contain
| osm |
| W10 |
When sending search query "Ringstr 13"
Then results contain
| osm |
| W20 |

View File

@@ -1,58 +0,0 @@
@DB
Feature: Query of address interpolations
Tests that interpolated addresses can be queried correctly
Background:
Given the grid
| 1 | | 2 | | 3 |
| 10 | | 12 | | 13 |
| 7 | | 8 | | 9 |
Scenario: Find interpolations with single number
Given the places
| osm | class | type | name | geometry |
| W10 | highway | primary | Nickway | 10,12,13 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | odd | 1,3 |
And the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 1 | 1 |
| N3 | place | house | 5 | 3 |
And the ways
| id | nodes |
| 1 | 1,3 |
When importing
When sending v1/reverse N2
Then results contain
| ID | display_name |
| 0 | 3, Nickway |
When sending search query "Nickway 3"
Then results contain
| osm | display_name |
| W1 | 3, Nickway |
Scenario: Find interpolations with multiple numbers
Given the places
| osm | class | type | name | geometry |
| W10 | highway | primary | Nickway | 10,12,13 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 1,3 |
And the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 2 | 1 |
| N3 | place | house | 18 | 3 |
And the ways
| id | nodes |
| 1 | 1,3 |
When importing
When sending v1/reverse N2
Then results contain
| ID | display_name | centroid |
| 0 | 10, Nickway | 2 |
When sending search query "Nickway 10"
Then results contain
| osm | display_name | centroid |
| W1 | 10, Nickway | 2 |

View File

@@ -1,29 +0,0 @@
@DB
Feature: Searches in Japan
Test specifically for searches of Japanese addresses and in Japanese language.
Scenario: A block house-number is parented to the neighbourhood
Given the grid with origin JP
| 1 | | | | 2 |
| | 3 | | | |
| | | 9 | | |
| | | | 6 | |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | | 1,2 |
And the places
| osm | class | type | housenr | addr+block_number | addr+neighbourhood | geometry |
| N3 | amenity | restaurant | 2 | 6 | 2 | 3 |
And the places
| osm | class | type | name | geometry |
| N9 | place | neighbourhood | 2 | 9 |
And the places
| osm | class | type | name | geometry |
| N6 | place | quarter | | 6 |
When importing
Then placex contains
| object | parent_place_id |
| N3 | N9 |
When sending search query "2 6-2"
Then results contain
| osm |
| N3 |

View File

@@ -1,64 +0,0 @@
@DB
Feature: Searching linked places
Tests that information from linked places can be searched correctly
Scenario: Additional names from linked places are searchable
Given the 0.1 grid
| 10 | | 11 |
| | 2 | |
| 13 | | 12 |
Given the places
| osm | class | type | admin | name | geometry |
| R13 | boundary | administrative | 6 | Garbo | (10,11,12,13,10) |
Given the places
| osm | class | type | admin | name+name:it |
| N2 | place | hamlet | 15 | Vario |
And the relations
| id | members | tags+type |
| 13 | N2:label | boundary |
When importing
Then placex contains
| object | linked_place_id |
| N2 | R13 |
When sending search query "Vario"
| namedetails |
| 1 |
Then results contain
| osm | display_name | namedetails |
| R13 | Garbo | "name": "Garbo", "name:it": "Vario" |
When sending search query "Vario"
| accept-language |
| it |
Then results contain
| osm | display_name |
| R13 | Vario |
Scenario: Differing names from linked places are searchable
Given the 0.1 grid
| 10 | | 11 |
| | 2 | |
| 13 | | 12 |
Given the places
| osm | class | type | admin | name | geometry |
| R13 | boundary | administrative | 6 | Garbo | (10,11,12,13,10) |
Given the places
| osm | class | type | admin | name |
| N2 | place | hamlet | 15 | Vario |
And the relations
| id | members | tags+type |
| 13 | N2:label | boundary |
When importing
Then placex contains
| object | linked_place_id |
| N2 | R13 |
When sending search query "Vario"
| namedetails |
| 1 |
Then results contain
| osm | display_name | namedetails |
| R13 | Garbo | "name": "Garbo", "_place_name": "Vario" |
When sending search query "Garbo"
Then results contain
| osm | display_name |
| R13 | Garbo |

View File

@@ -1,226 +0,0 @@
@DB
Feature: Import and search of names
Tests all naming related issues: normalisation,
abbreviations, internationalisation, etc.
Scenario: non-latin scripts can be found
Given the places
| osm | class | type | name |
| N1 | place | locality | Речицкий район |
| N2 | place | locality | Refugio de montaña |
| N3 | place | locality | |
| N4 | place | locality | الدوحة |
When importing
When sending search query "Речицкий район"
Then results contain
| ID | osm |
| 0 | N1 |
When sending search query "Refugio de montaña"
Then results contain
| ID | osm |
| 0 | N2 |
When sending search query ""
Then results contain
| ID | osm |
| 0 | N3 |
When sending search query "الدوحة"
Then results contain
| ID | osm |
| 0 | N4 |
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 sending search query "FooBar"
Then results contain
| ID | osm |
| 0 | N1 |
When sending search query "foobar"
Then results contain
| ID | osm |
| 0 | N1 |
When sending search query "fOObar"
Then results contain
| ID | osm |
| 0 | N1 |
When sending search query "FOOBAR"
Then results contain
| ID | osm |
| 0 | N1 |
Scenario: Multiple spaces in name
Given the places
| osm | class | type | name |
| N1 | place | locality | one two three |
When importing
When sending search query "one two three"
Then results contain
| ID | osm |
| 0 | N1 |
When sending search query "one two three"
Then results contain
| ID | osm |
| 0 | N1 |
When sending search query "one two three"
Then results contain
| ID | osm |
| 0 | N1 |
When sending search query " one two three"
Then results contain
| ID | osm |
| 0 | N1 |
Scenario: Special characters in name
Given the places
| osm | class | type | name+name:de |
| N1 | place | locality | Jim-Knopf-Straße |
| N2 | place | locality | Smith/Weston |
| N3 | place | locality | space mountain |
| N4 | place | locality | space |
| N5 | place | locality | mountain |
When importing
When sending search query "Jim-Knopf-Str"
Then results contain
| ID | osm |
| 0 | N1 |
When sending search query "Jim Knopf-Str"
Then results contain
| ID | osm |
| 0 | N1 |
When sending search query "Jim Knopf Str"
Then results contain
| ID | osm |
| 0 | N1 |
When sending search query "Jim/Knopf-Str"
Then results contain
| ID | osm |
| 0 | N1 |
When sending search query "Jim-Knopfstr"
Then results contain
| ID | osm |
| 0 | N1 |
When sending search query "Smith/Weston"
Then results contain
| ID | osm |
| 0 | N2 |
When sending search query "Smith Weston"
Then results contain
| ID | osm |
| 0 | N2 |
When sending search query "Smith-Weston"
Then results contain
| ID | osm |
| 0 | N2 |
When sending search query "space mountain"
Then results contain
| ID | osm |
| 0 | N3 |
When sending search query "space-mountain"
Then results contain
| ID | osm |
| 0 | N3 |
When sending search query "space/mountain"
Then results contain
| ID | osm |
| 0 | N3 |
When sending search query "space\mountain"
Then results contain
| ID | osm |
| 0 | N3 |
When sending search query "space(mountain)"
Then results contain
| ID | osm |
| 0 | N3 |
Scenario: Landuse with name are found
Given the grid
| 1 | 2 |
| 3 | |
Given the places
| osm | class | type | name | geometry |
| R1 | natural | meadow | landuse1 | (1,2,3,1) |
| R2 | landuse | industrial | landuse2 | (2,3,1,2) |
When importing
When sending search query "landuse1"
Then results contain
| ID | osm |
| 0 | R1 |
When sending search query "landuse2"
Then results contain
| ID | osm |
| 0 | R2 |
Scenario: Postcode boundaries without ref
Given the grid with origin FR
| | 2 | |
| 1 | | 3 |
Given the places
| osm | class | type | postcode | geometry |
| R1 | boundary | postal_code | 123-45 | (1,2,3,1) |
When importing
When sending search query "123-45"
Then results contain
| ID | osm |
| 0 | R1 |
Scenario Outline: Housenumbers with special characters are found
Given the grid
| 1 | | | | 2 |
| | | 9 | | |
And the places
| osm | class | type | name | geometry |
| W1 | highway | primary | Main St | 1,2 |
And the places
| osm | class | type | housenr | geometry |
| N1 | building | yes | <nr> | 9 |
When importing
And sending search query "Main St <nr>"
Then results contain
| osm | display_name |
| N1 | <nr>, Main St |
Examples:
| nr |
| 1 |
| 3456 |
| 1 a |
| 56b |
| 1 A |
| 2 |
| 1Б |
| 1 к1 |
| 23-123 |
Scenario Outline: Housenumbers in lists are found
Given the grid
| 1 | | | | 2 |
| | | 9 | | |
And the places
| osm | class | type | name | geometry |
| W1 | highway | primary | Main St | 1,2 |
And the places
| osm | class | type | housenr | geometry |
| N1 | building | yes | <nr-list> | 9 |
When importing
And sending search query "Main St <nr>"
Then results contain
| ID | osm | display_name |
| 0 | N1 | <nr-list>, Main St |
Examples:
| nr-list | nr |
| 1,2,3 | 1 |
| 1,2,3 | 2 |
| 1, 2, 3 | 3 |
| 45 ;67;3 | 45 |
| 45 ;67;3 | 67 |
| 1a;1k | 1a |
| 1a;1k | 1k |
| 34/678 | 34 |
| 34/678 | 678 |
| 34/678 | 34/678 |

View File

@@ -1,110 +0,0 @@
@DB
Feature: Querying fo postcode variants
Scenario: Postcodes in Singapore (6-digit postcode)
Given the grid with origin SG
| 10 | | | | 11 |
And the places
| osm | class | type | name | addr+postcode | geometry |
| W1 | highway | path | Lorang | 399174 | 10,11 |
When importing
When sending search query "399174"
Then results contain
| ID | type | display_name |
| 0 | postcode | 399174, Singapore |
Scenario Outline: Postcodes in the Netherlands (mixed postcode with spaces)
Given the grid with origin NL
| 10 | | | | 11 |
And the places
| osm | class | type | name | addr+postcode | geometry |
| W1 | highway | path | De Weide | 3993 DX | 10,11 |
When importing
When sending search query "3993 DX"
Then results contain
| ID | type | display_name |
| 0 | postcode | 3993 DX, Nederland |
When sending search query "3993dx"
Then results contain
| ID | type | display_name |
| 0 | postcode | 3993 DX, Nederland |
Examples:
| postcode |
| 3993 DX |
| 3993DX |
| 3993 dx |
Scenario: Postcodes in Singapore (6-digit postcode)
Given the grid with origin SG
| 10 | | | | 11 |
And the places
| osm | class | type | name | addr+postcode | geometry |
| W1 | highway | path | Lorang | 399174 | 10,11 |
When importing
When sending search query "399174"
Then results contain
| ID | type | display_name |
| 0 | postcode | 399174, Singapore |
Scenario Outline: Postcodes in Andorra (with country code)
Given the grid with origin AD
| 10 | | | | 11 |
And the places
| osm | class | type | name | addr+postcode | geometry |
| W1 | highway | path | Lorang | <postcode> | 10,11 |
When importing
When sending search query "675"
Then results contain
| ID | type | display_name |
| 0 | postcode | AD675, Andorra |
When sending search query "AD675"
Then results contain
| ID | type | display_name |
| 0 | postcode | AD675, Andorra |
Examples:
| postcode |
| 675 |
| AD 675 |
| AD675 |
Scenario: Different postcodes with the same normalization can both be found
Given the places
| osm | class | type | addr+postcode | addr+housenumber | geometry |
| N34 | place | house | EH4 7EA | 111 | country:gb |
| N35 | place | house | E4 7EA | 111 | country:gb |
When importing
Then location_postcode contains exactly
| country | postcode | geometry |
| gb | EH4 7EA | country:gb |
| gb | E4 7EA | country:gb |
When sending search query "EH4 7EA"
Then results contain
| type | display_name |
| postcode | EH4 7EA, United Kingdom |
When sending search query "E4 7EA"
Then results contain
| type | display_name |
| postcode | E4 7EA, United Kingdom |
Scenario: Postcode areas are preferred over postcode points
Given the grid with origin DE
| 1 | 2 |
| 4 | 3 |
Given the places
| osm | class | type | postcode | geometry |
| R23 | boundary | postal_code | 12345 | (1,2,3,4,1) |
When importing
Then location_postcode contains exactly
| country | postcode |
| de | 12345 |
When sending search query "12345, de"
Then results contain
| osm |
| R23 |

View File

@@ -1,22 +0,0 @@
@DB
Feature: Reverse searches
Test results of reverse queries
Scenario: POI in POI area
Given the 0.0001 grid with origin 1,1
| 1 | | | | | | | | 2 |
| | 9 | | | | | | | |
| 4 | | | | | | | | 3 |
And the places
| osm | class | type | geometry |
| W1 | aeroway | terminal | (1,2,3,4,1) |
| N1 | amenity | restaurant | 9 |
When importing
And sending v1/reverse at 1.0001,1.0001
Then results contain
| osm |
| N1 |
When sending v1/reverse at 1.0003,1.0001
Then results contain
| osm |
| W1 |

View File

@@ -1,108 +0,0 @@
@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 sending search query "Foo"
Then results contain
| ID | osm | category | type | centroid |
| 0 | N1 | place | village | 10 -10 |
Scenario: Updating postcode in postcode boundaries without ref
Given the grid
| 1 | 2 |
| 4 | 3 |
Given the places
| osm | class | type | postcode | geometry |
| R1 | boundary | postal_code | 12345 | (1,2,3,4,1) |
When importing
And sending search query "12345"
Then results contain
| ID | osm |
| 0 | R1 |
When updating places
| osm | class | type | postcode | geometry |
| R1 | boundary | postal_code | 54321 | (1,2,3,4,1) |
And sending search query "12345"
Then exactly 0 results are returned
When sending search query "54321"
Then results contain
| ID | osm |
| 0 | R1 |
# github #1763
Scenario: Correct translation of highways under construction
Given the grid
| 1 | | | | 2 |
| | | 9 | | |
And the places
| osm | class | type | name | geometry |
| W1 | highway | construction | The build | 1,2 |
| N1 | amenity | cafe | Bean | 9 |
When importing
And sending json search query "Bean" with address
Then result addresses contain
| amenity | road |
| Bean | The build |
Scenario: when missing housenumbers in search don't return a POI
Given the places
| osm | class | type | name |
| N3 | amenity | restaurant | Wood Street |
And the places
| osm | class | type | name | housenr |
| N20 | amenity | restaurant | Red Way | 34 |
When importing
And sending search query "Wood Street 45"
Then exactly 0 results are returned
When sending search query "Red Way 34"
Then results contain
| osm |
| N20 |
Scenario: when the housenumber is missing the street is still returned
Given the grid
| 1 | | 2 |
Given the places
| osm | class | type | name | geometry |
| W1 | highway | residential | Wood Street | 1, 2 |
When importing
And sending search query "Wood Street"
Then results contain
| osm |
| W1 |
Scenario Outline: Special cased american states will be found
Given the grid
| 1 | | 2 |
| | 10 | |
| 4 | | 3 |
Given the places
| osm | class | type | admin | name | name+ref | geometry |
| R1 | boundary | administrative | 4 | <state> | <ref> | (1,2,3,4,1) |
Given the places
| osm | class | type | name | geometry |
| N2 | place | town | <city> | 10 |
| N3 | place | city | <city> | country:ca |
When importing
And sending search query "<city>, <state>"
Then results contain
| osm |
| N2 |
When sending search query "<city>, <ref>"
| accept-language |
| en |
Then results contain
| osm |
| N2 |
Examples:
| city | state | ref |
| Chicago | Illinois | IL |
| Auburn | Alabama | AL |
| New Orleans | Louisiana | LA |

View File

@@ -1,109 +0,0 @@
@DB
Feature: Country handling
Tests for update of country information
Background:
Given the 1.0 grid with origin DE
| 1 | | 2 |
| | 10 | |
| 4 | | 3 |
Scenario: When country names are changed old ones are no longer searchable
Given the places
| osm | class | type | admin | name+name:xy | country | geometry |
| R1 | boundary | administrative | 2 | Loudou | de | (1,2,3,4,1) |
Given the places
| osm | class | type | name |
| N10 | place | town | Wenig |
When importing
When sending search query "Wenig, Loudou"
Then results contain
| osm |
| N10 |
When updating places
| osm | class | type | admin | name+name:xy | country | geometry |
| R1 | boundary | administrative | 2 | Germany | de | (1,2,3,4,1) |
When sending search query "Wenig, Loudou"
Then exactly 0 results are returned
Scenario: When country names are deleted they are no longer searchable
Given the places
| osm | class | type | admin | name+name:xy | country | geometry |
| R1 | boundary | administrative | 2 | Loudou | de | (1,2,3,4,1) |
Given the places
| osm | class | type | name |
| N10 | place | town | Wenig |
When importing
When sending search query "Wenig, Loudou"
Then results contain
| osm |
| N10 |
When updating places
| osm | class | type | admin | name+name:en | country | geometry |
| R1 | boundary | administrative | 2 | Germany | de | (1,2,3,4,1) |
When sending search query "Wenig, Loudou"
Then exactly 0 results are returned
When sending search query "Wenig"
| accept-language |
| xy,en |
Then results contain
| osm | display_name |
| N10 | Wenig, Germany |
Scenario: Default country names are always searchable
Given the places
| osm | class | type | name |
| N10 | place | town | Wenig |
When importing
When sending search query "Wenig, Germany"
Then results contain
| osm |
| N10 |
When sending search query "Wenig, de"
Then results contain
| osm |
| N10 |
When updating places
| osm | class | type | admin | name+name:en | country | geometry |
| R1 | boundary | administrative | 2 | Lilly | de | (1,2,3,4,1) |
When sending search query "Wenig, Germany"
| accept-language |
| en,de |
Then results contain
| osm | display_name |
| N10 | Wenig, Lilly |
When sending search query "Wenig, de"
| accept-language |
| en,de |
Then results contain
| osm | display_name |
| N10 | Wenig, Lilly |
Scenario: When a localised name is deleted, the standard name takes over
Given the places
| osm | class | type | admin | name+name:de | country | geometry |
| R1 | boundary | administrative | 2 | Loudou | de | (1,2,3,4,1) |
Given the places
| osm | class | type | name |
| N10 | place | town | Wenig |
When importing
When sending search query "Wenig, Loudou"
| accept-language |
| de,en |
Then results contain
| osm | display_name |
| N10 | Wenig, Loudou |
When updating places
| osm | class | type | admin | name+name:en | country | geometry |
| R1 | boundary | administrative | 2 | Germany | de | (1,2,3,4,1) |
When sending search query "Wenig, Loudou"
Then exactly 0 results are returned
When sending search query "Wenig"
| accept-language |
| de,en |
Then results contain
| osm | display_name |
| N10 | Wenig, Deutschland |

View File

@@ -1,419 +0,0 @@
@DB
Feature: Update of address interpolations
Test the interpolated address are updated correctly
Scenario: new interpolation added to existing street
Given the grid
| 10 | | | | 11 |
| | 1 | 99 | 2 | |
| | | | | |
| 20 | | | | 21 |
And the places
| osm | class | type | name | geometry |
| W2 | highway | unclassified | Sun Way | 10,11 |
| W3 | highway | unclassified | Cloud Street | 20,21 |
And the ways
| id | nodes |
| 10 | 1,2 |
When importing
Then W10 expands to no interpolation
When updating places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 6 |
And updating places
| osm | class | type | addr+interpolation | geometry |
| W10 | place | houses | even | 1,2 |
Then placex contains
| object | parent_place_id |
| N1 | W2 |
| N2 | W2 |
And W10 expands to interpolation
| parent_place_id | start | end | geometry |
| W2 | 4 | 4 | 99 |
Scenario: addr:street added to interpolation
Given the grid
| 10 | | | | 11 |
| | 1 | | 2 | |
| | | | | |
| 20 | | | | 21 |
And the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 6 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W10 | place | houses | even | 1,2 |
And the places
| osm | class | type | name | geometry |
| W2 | highway | unclassified | Sun Way | 10,11 |
| W3 | highway | unclassified | Cloud Street | 20,21 |
And the ways
| id | nodes |
| 10 | 1,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 | 4 | 4 |
When updating places
| osm | class | type | addr+interpolation | street | geometry |
| W10 | place | houses | even | Cloud Street | 1,2 |
Then placex contains
| object | parent_place_id |
| N1 | W3 |
| N2 | W3 |
And W10 expands to interpolation
| parent_place_id | start | end |
| W3 | 4 | 4 |
Scenario: addr:street added to housenumbers
Given the grid
| 10 | | | | 11 |
| | 1 | | 2 | |
| | | | | |
| 20 | | | | 21 |
And the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 6 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W10 | place | houses | even | 1,2 |
And the places
| osm | class | type | name | geometry |
| W2 | highway | unclassified | Sun Way | 10,11 |
| W3 | highway | unclassified | Cloud Street | 20,21 |
And the ways
| id | nodes |
| 10 | 1,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 | 4 | 4 |
When updating places
| osm | class | type | street | housenr |
| N1 | place | house | Cloud Street| 2 |
| N2 | place | house | Cloud Street| 6 |
Then placex contains
| object | parent_place_id |
| N1 | W3 |
| N2 | W3 |
And W10 expands to interpolation
| parent_place_id | start | end |
| W3 | 4 | 4 |
Scenario: interpolation tag removed
Given the grid
| 10 | | | | 11 |
| | 1 | | 2 | |
| | | | | |
| 20 | | | | 21 |
And the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 6 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W10 | place | houses | even | 1,2 |
And the places
| osm | class | type | name | geometry |
| W2 | highway | unclassified | Sun Way | 10,11 |
| W3 | highway | unclassified | Cloud Street | 20,21 |
And the ways
| id | nodes |
| 10 | 1,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 | 4 | 4 |
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 grid
| 10 | | | | 11 |
| | 1 | | 2 | |
| | | | | |
| 20 | | | | 21 |
And the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 6 |
And the places
| osm | class | type | addr+interpolation | street | geometry |
| W10 | place | houses | even | Cloud Street| 1,2 |
And the places
| osm | class | type | name | geometry |
| W2 | highway | unclassified | Sun Way | 10,11 |
And the ways
| id | nodes |
| 10 | 1,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 | 4 | 4 |
When updating places
| osm | class | type | name | geometry |
| W3 | highway | unclassified | Cloud Street | 20,21 |
Then placex contains
| object | parent_place_id |
| N1 | W3 |
| N2 | W3 |
And W10 expands to interpolation
| parent_place_id | start | end |
| W3 | 4 | 4 |
Scenario: referenced road deleted
Given the grid
| 10 | | | | 11 |
| | 1 | | 2 | |
| | | | | |
| 20 | | | | 21 |
And the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 6 |
And the places
| osm | class | type | addr+interpolation | street | geometry |
| W10 | place | houses | even | Cloud Street| 1,2 |
And the places
| osm | class | type | name | geometry |
| W2 | highway | unclassified | Sun Way | 10,11 |
| W3 | highway | unclassified | Cloud Street | 20,21 |
And the ways
| id | nodes |
| 10 | 1,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 | 4 | 4 |
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 | 4 | 4 |
Scenario: building becomes interpolation
Given the grid
| 10 | | | | 11 |
| | 1 | | 2 | |
| | 4 | | 3 | |
And the places
| osm | class | type | housenr | geometry |
| W1 | place | house | 3 | (1,2,3,4,1) |
And the places
| osm | class | type | name | geometry |
| W2 | highway | unclassified | Cloud Street | 10,11 |
When importing
Then placex contains
| object | parent_place_id |
| W1 | W2 |
Given the ways
| id | nodes |
| 1 | 1,2 |
When updating places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 6 |
And updating places
| osm | class | type | addr+interpolation | street | geometry |
| W1 | place | houses | even | Cloud Street| 1,2 |
Then placex has no entry for W1
And W1 expands to interpolation
| parent_place_id | start | end |
| W2 | 4 | 4 |
Scenario: interpolation becomes building
Given the grid
| 10 | | | | 11 |
| | 1 | | 2 | |
| | 4 | | 3 | |
And the places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 6 |
And the places
| osm | class | type | name | geometry |
| W2 | highway | unclassified | Cloud Street | 10,11 |
And the ways
| id | nodes |
| 1 | 1,2 |
And the places
| osm | class | type | addr+interpolation | street | geometry |
| W1 | place | houses | even | Cloud Street| 1,2 |
When importing
Then placex has no entry for W1
And W1 expands to interpolation
| parent_place_id | start | end |
| W2 | 4 | 4 |
When updating places
| osm | class | type | housenr | geometry |
| W1 | place | house | 3 | (1,2,3,4,1) |
Then placex contains
| object | parent_place_id |
| W1 | W2 |
And W1 expands to no interpolation
Scenario: housenumbers added to interpolation
Given the grid
| 10 | | | | 11 |
| | 1 | | 2 | |
And the places
| osm | class | type | name | geometry |
| W2 | highway | unclassified | Cloud Street | 10,11 |
And the ways
| id | nodes |
| 1 | 1,2 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 1,2 |
When importing
Then W1 expands to no interpolation
When updating places
| osm | class | type | housenr |
| N1 | place | house | 2 |
| N2 | place | house | 6 |
Then W1 expands to interpolation
| parent_place_id | start | end |
| W2 | 4 | 4 |
Scenario: housenumber added in middle of interpolation
Given the grid
| 1 | | | | | 2 |
| 3 | | | 4 | | 5 |
And the places
| osm | class | type | name | geometry |
| W1 | highway | unclassified | Cloud Street | 1, 2 |
And the ways
| id | nodes |
| 2 | 3,4,5 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W2 | place | houses | even | 3,4,5 |
And the places
| osm | class | type | housenr |
| N3 | place | house | 2 |
| N5 | place | house | 10 |
When importing
Then W2 expands to interpolation
| parent_place_id | start | end |
| W1 | 4 | 8 |
When updating places
| osm | class | type | housenr |
| N4 | place | house | 6 |
Then W2 expands to interpolation
| parent_place_id | start | end |
| W1 | 4 | 4 |
| W1 | 8 | 8 |
@Fail
Scenario: housenumber removed in middle of interpolation
Given the grid
| 1 | | | | | 2 |
| 3 | | | 4 | | 5 |
And the places
| osm | class | type | name | geometry |
| W1 | highway | unclassified | Cloud Street | 1, 2 |
And the ways
| id | nodes |
| 2 | 3,4,5 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W2 | place | houses | even | 3,4,5 |
And the places
| osm | class | type | housenr |
| N3 | place | house | 2 |
| N4 | place | house | 6 |
| N5 | place | house | 10 |
When importing
Then W2 expands to interpolation
| parent_place_id | start | end |
| W1 | 4 | 4 |
| W1 | 8 | 8 |
When marking for delete N4
Then W2 expands to interpolation
| parent_place_id | start | end |
| W1 | 4 | 8 |
Scenario: Change the start housenumber
Given the grid
| 1 | | 2 |
| 3 | | 4 |
And the places
| osm | class | type | name | geometry |
| W1 | highway | unclassified | Cloud Street | 1, 2 |
And the ways
| id | nodes |
| 2 | 3,4 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W2 | place | houses | even | 3,4 |
And the places
| osm | class | type | housenr |
| N3 | place | house | 2 |
| N4 | place | house | 6 |
When importing
Then W2 expands to interpolation
| parent_place_id | start | end |
| W1 | 4 | 4 |
When updating places
| osm | class | type | housenr |
| N4 | place | house | 8 |
Then W2 expands to interpolation
| parent_place_id | start | end |
| W1 | 4 | 6 |
Scenario: Legal interpolation type changed to illegal one
Given the grid
| 1 | | 2 |
| 3 | | 4 |
And the places
| osm | class | type | name | geometry |
| W1 | highway | unclassified | Cloud Street | 1, 2 |
And the ways
| id | nodes |
| 2 | 3,4 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W2 | place | houses | even | 3,4 |
And the places
| osm | class | type | housenr |
| N3 | place | house | 2 |
| N4 | place | house | 6 |
When importing
Then W2 expands to interpolation
| parent_place_id | start | end |
| W1 | 4 | 4 |
When updating places
| osm | class | type | addr+interpolation | geometry |
| W2 | place | houses | 12-2 | 3,4 |
Then W2 expands to no interpolation

View File

@@ -1,341 +0,0 @@
@DB
Feature: Updates of linked places
Tests that linked places are correctly added and deleted.
Scenario: Linking is kept when boundary is updated
Given the 0.1 grid
| 10 | | 11 |
| | 1 | |
| 13 | | 12 |
Given the places
| osm | class | type | name |
| N1 | place | city | foo |
And the places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | foo | 8 | (10,11,12,13,10) |
When importing
Then placex contains
| object | linked_place_id |
| N1 | R1 |
When updating places
| osm | class | type | name | name+name:de | admin | geometry |
| R1 | boundary | administrative | foo | Dingens | 8 | (10,11,12,13,10) |
Then placex contains
| object | linked_place_id |
| N1 | R1 |
Scenario: Add linked place when linking relation is renamed
Given the 0.1 grid
| 10 | | 11 |
| | 1 | |
| 13 | | 12 |
Given the places
| osm | class | type | name |
| N1 | place | city | foo |
And the places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | foo | 8 | (10,11,12,13,10) |
When importing
Then placex contains
| object | linked_place_id |
| N1 | R1 |
When sending search query "foo"
| dups |
| 1 |
Then results contain
| osm |
| R1 |
When updating places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | foobar | 8 | (10,11,12,13,10) |
Then placex contains
| object | linked_place_id |
| N1 | - |
When sending search query "foo"
| dups |
| 1 |
Then results contain
| osm |
| N1 |
Scenario: Add linked place when linking relation is removed
Given the 0.1 grid
| 10 | | 11 |
| | 1 | |
| 13 | | 12 |
Given the places
| osm | class | type | name |
| N1 | place | city | foo |
And the places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | foo | 8 | (10,11,12,13,10) |
When importing
And sending search query "foo"
| dups |
| 1 |
Then results contain
| osm |
| R1 |
When marking for delete R1
Then placex contains
| object | linked_place_id |
| N1 | - |
When sending search query "foo"
| dups |
| 1 |
Then results contain
| osm |
| N1 |
Scenario: Remove linked place when linking relation is added
Given the 0.1 grid
| 10 | | 11 |
| | 1 | |
| 13 | | 12 |
Given the places
| osm | class | type | name |
| N1 | place | city | foo |
When importing
And sending search query "foo"
| dups |
| 1 |
Then results contain
| osm |
| N1 |
When updating places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | foo | 8 | (10,11,12,13,10) |
Then placex contains
| object | linked_place_id |
| N1 | R1 |
When sending search query "foo"
| dups |
| 1 |
Then results contain
| osm |
| R1 |
Scenario: Remove linked place when linking relation is renamed
Given the 0.1 grid
| 10 | | 11 |
| | 1 | |
| 13 | | 12 |
Given the places
| osm | class | type | name |
| N1 | place | city | foo |
And the places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | foobar | 8 | (10,11,12,13,10) |
When importing
And sending search query "foo"
| dups |
| 1 |
Then results contain
| osm |
| N1 |
When updating places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | foo | 8 | (10,11,12,13,10) |
Then placex contains
| object | linked_place_id |
| N1 | R1 |
When sending search query "foo"
| dups |
| 1 |
Then results contain
| osm |
| R1 |
Scenario: Update linking relation when linkee name is updated
Given the 0.1 grid
| 10 | | 11 |
| | 3 | |
| 13 | | 12 |
Given the places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | rel | 8 | (10,11,12,13,10) |
And the places
| osm | class | type | name+name:de |
| N3 | place | city | greeny |
And the relations
| id | members |
| 1 | N3:label |
When importing
Then placex contains
| object | linked_place_id | name+_place_name:de |
| R1 | - | greeny |
And placex contains
| object | linked_place_id | name+name:de |
| N3 | R1 | greeny |
When updating places
| osm | class | type | name+name:de |
| N3 | place | city | newname |
Then placex contains
| object | linked_place_id | name+name:de |
| N3 | R1 | newname |
And placex contains
| object | linked_place_id | name+_place_name:de |
| R1 | - | newname |
Scenario: Update linking relation when linkee name is deleted
Given the 0.1 grid
| 10 | | 11 |
| | 3 | |
| 13 | | 12 |
Given the places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | rel | 8 | (10,11,12,13,10) |
And the places
| osm | class | type | name |
| N3 | place | city | greeny |
And the relations
| id | members |
| 1 | N3:label |
When importing
Then placex contains
| object | linked_place_id | name+_place_name | name+name |
| R1 | - | greeny | rel |
And placex contains
| object | linked_place_id | name+name |
| N3 | R1 | greeny |
When sending search query "greeny"
Then results contain
| osm |
| R1 |
When updating places
| osm | class | type | name+name:de |
| N3 | place | city | depnt |
Then placex contains
| object | linked_place_id | name+name:de |
| N3 | R1 | depnt |
And placex contains
| object | linked_place_id | name+_place_name:de | name+name |
| R1 | - | depnt | rel |
When sending search query "greeny"
Then exactly 0 results are returned
Scenario: Updating linkee extratags keeps linker's extratags
Given the 0.1 grid
| 10 | | 11 |
| | 3 | |
| 13 | | 12 |
Given the named places
| osm | class | type | extra+wikidata | admin | geometry |
| R1 | boundary | administrative | 34 | 8 | (10,11,12,13,10) |
And the named places
| osm | class | type |
| N3 | place | city |
And the relations
| id | members |
| 1 | N3:label |
When importing
Then placex contains
| object | extratags |
| R1 | 'wikidata' : '34', 'linked_place' : 'city' |
When updating places
| osm | class | type | name | extra+oneway |
| N3 | place | city | newname | yes |
Then placex contains
| object | extratags |
| R1 | 'wikidata' : '34', 'oneway' : 'yes', 'linked_place' : 'city' |
Scenario: Remove linked_place info when linkee is removed
Given the 0.1 grid
| 10 | | 11 |
| | 1 | |
| 13 | | 12 |
Given the places
| osm | class | type | name |
| N1 | place | city | foo |
And the places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | foo | 8 | (10,11,12,13,10) |
When importing
Then placex contains
| object | extratags |
| R1 | 'linked_place' : 'city' |
When marking for delete N1
Then placex contains
| object | extratags |
| R1 | - |
Scenario: Update linked_place info when linkee type changes
Given the 0.1 grid
| 10 | | 11 |
| | 1 | |
| 13 | | 12 |
Given the places
| osm | class | type | name |
| N1 | place | city | foo |
And the places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | foo | 8 | (10,11,12,13,10) |
When importing
Then placex contains
| object | extratags |
| R1 | 'linked_place' : 'city' |
When updating places
| osm | class | type | name |
| N1 | place | town | foo |
Then placex contains
| object | extratags |
| R1 | 'linked_place' : 'town' |
Scenario: Keep linking and ranks when place type changes
Given the grid
| 1 | | | 2 |
| | | 9 | |
| 4 | | | 3 |
And the places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | foo | 8 | (1,2,3,4,1) |
And the places
| osm | class | type | name | geometry |
| N1 | place | city | foo | 9 |
When importing
Then placex contains
| object | linked_place_id | rank_address |
| N1 | R1 | 16 |
| R1 | - | 16 |
When updating places
| osm | class | type | name | geometry |
| N1 | place | town | foo | 9 |
Then placex contains
| object | linked_place_id | rank_address |
| N1 | R1 | 16 |
| R1 | - | 16 |
Scenario: Invalidate surrounding place nodes when place type changes
Given the grid
| 1 | | | 2 |
| | 8 | 9 | |
| 4 | | | 3 |
And the places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | foo | 8 | (1,2,3,4,1) |
And the places
| osm | class | type | name | geometry |
| N1 | place | town | foo | 9 |
| N2 | place | city | bar | 8 |
And the relations
| id | members |
| 1 | N1:label |
When importing
Then placex contains
| object | linked_place_id | rank_address |
| N1 | R1 | 16 |
| R1 | - | 16 |
| N2 | - | 18 |
When updating places
| osm | class | type | name | geometry |
| N1 | place | suburb | foo | 9 |
Then placex contains
| object | linked_place_id | rank_address |
| N1 | R1 | 20 |
| R1 | - | 20 |
| N2 | - | 16 |

View File

@@ -1,21 +0,0 @@
@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 grid with origin DE
| 1 | 2 |
| 4 | 3 |
Given the places
| osm | class | type | postcode | geometry |
| R1 | boundary | postal_code | 123-45 | (1,2,3,4,1) |
When importing
And sending search query "123-45"
Then results contain
| ID | osm |
| 0 | R1 |
When updating places
| osm | class | type | geometry |
| R1 | boundary | postal_code | (1,2,3,4,1) |
Then placex has no entry for R1

View File

@@ -1,164 +0,0 @@
@DB
Feature: Update parenting of objects
Scenario: POI inside building inherits addr:street change
Given the grid
| 10 | | | | | | | 11 |
| | | 5 | | | 6 | | |
| | | | | | | | |
| | | | | 1 | | | |
| 12 | | 8 | | | 7 | | |
And the named places
| osm | class | type |
| N1 | amenity | bank |
And the places
| osm | class | type | street | housenr | geometry |
| W1 | building | yes | nowhere | 3 | (5,6,7,8,5) |
And the places
| osm | class | type | name | geometry |
| W2 | highway | primary | bar | 10,11 |
| W3 | highway | residential | foo | 10,12 |
When importing
Then placex contains
| object | parent_place_id | housenumber |
| W1 | W2 | 3 |
| N1 | W2 | 3 |
When updating places
| osm | class | type | street | addr_place | housenr | geometry |
| W1 | building | yes | foo | nowhere | 3 | (5,6,7,8,5) |
And updating places
| osm | class | type | name |
| N1 | amenity | bank | well |
Then placex contains
| object | parent_place_id | housenumber |
| W1 | W3 | 3 |
| N1 | W3 | 3 |
Scenario: Housenumber is reparented when street gets name matching addr:street
Given the grid
| 1 | | | 2 |
| | 10 | | |
| | | | |
| 3 | | | 4 |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | A street | 1,2 |
| W2 | highway | residential | B street | 3,4 |
And the places
| osm | class | type | housenr | street | geometry |
| N1 | building | yes | 3 | X street | 10 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W1 |
When updating places
| osm | class | type | name | geometry |
| W2 | highway | residential | X street | 3,4 |
Then placex contains
| object | parent_place_id |
| N1 | W2 |
Scenario: Housenumber is reparented when street looses name matching addr:street
Given the grid
| 1 | | | 2 |
| | 10 | | |
| | | | |
| 3 | | | 4 |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | A street | 1,2 |
| W2 | highway | residential | X street | 3,4 |
And the places
| osm | class | type | housenr | street | geometry |
| N1 | building | yes | 3 | X street | 10 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W2 |
When updating places
| osm | class | type | name | geometry |
| W2 | highway | residential | B street | 3,4 |
Then placex contains
| object | parent_place_id |
| N1 | W1 |
Scenario: Housenumber is reparented when street gets name matching addr:street
Given the grid
| 1 | | | 2 |
| | 10 | | |
| | | | |
| 3 | | | 4 |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | A street | 1,2 |
| W2 | highway | residential | B street | 3,4 |
And the places
| osm | class | type | housenr | street | geometry |
| N1 | building | yes | 3 | X street | 10 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | W1 |
When updating places
| osm | class | type | name | geometry |
| W2 | highway | residential | X street | 3,4 |
Then placex contains
| object | parent_place_id |
| N1 | W2 |
# Invalidation of geometries currently disabled for addr:place matches.
@Fail
Scenario: Housenumber is reparented when place is renamed to matching addr:place
Given the grid
| 1 | | | 2 |
| | 10 | 4 | |
| | | | |
| | | 5 | |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | A street | 1,2 |
| N5 | place | village | Bdorf | 5 |
| N4 | place | village | Other | 4 |
And the places
| osm | class | type | housenr | addr_place | geometry |
| N1 | building | yes | 3 | Cdorf | 10 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | N4 |
When updating places
| osm | class | type | name | geometry |
| N5 | place | village | Cdorf | 5 |
Then placex contains
| object | parent_place_id |
| N1 | N5 |
Scenario: Housenumber is reparented when it looses a matching addr:place
Given the grid
| 1 | | | 2 |
| | 10 | 4 | |
| | | | |
| | | 5 | |
And the places
| osm | class | type | name | geometry |
| W1 | highway | residential | A street | 1,2 |
| N5 | place | village | Bdorf | 5 |
| N4 | place | village | Other | 4 |
And the places
| osm | class | type | housenr | addr_place | geometry |
| N1 | building | yes | 3 | Bdorf | 10 |
When importing
Then placex contains
| object | parent_place_id |
| N1 | N5 |
When updating places
| osm | class | type | name | geometry |
| N5 | place | village | Cdorf | 5 |
Then placex contains
| object | parent_place_id |
| N1 | N4 |

View File

@@ -1,119 +0,0 @@
@DB
Feature: Update of postcode
Tests for updating of data related to postcodes
Scenario: A new postcode appears in the postcode table
Given the places
| osm | class | type | addr+postcode | addr+housenumber | geometry |
| N34 | place | house | 01982 | 111 |country:de |
When importing
Then location_postcode contains exactly
| country | postcode | geometry |
| de | 01982 | country:de |
When updating places
| osm | class | type | addr+postcode | addr+housenumber | geometry |
| N35 | place | house | 4567 | 5 |country:ch |
And updating postcodes
Then location_postcode contains exactly
| country | postcode | geometry |
| de | 01982 | country:de |
| ch | 4567 | country:ch |
Scenario: When the last postcode is deleted, it is deleted from postcode
Given the places
| osm | class | type | addr+postcode | addr+housenumber | geometry |
| N34 | place | house | 01982 | 111 |country:de |
| N35 | place | house | 4567 | 5 |country:ch |
When importing
And marking for delete N34
And updating postcodes
Then location_postcode contains exactly
| country | postcode | geometry |
| ch | 4567 | country:ch |
Scenario: A postcode is not deleted from postcode when it exist in another country
Given the places
| osm | class | type | addr+postcode | addr+housenumber | geometry |
| N34 | place | house | 01982 | 111 |country:de |
| N35 | place | house | 01982 | 5 |country:fr |
When importing
And marking for delete N34
And updating postcodes
Then location_postcode contains exactly
| country | postcode | geometry |
| fr | 01982 | country:fr |
Scenario: Updating a postcode is reflected in postcode table
Given the places
| osm | class | type | addr+postcode | geometry |
| N34 | place | postcode | 01982 | country:de |
When importing
And updating places
| osm | class | type | addr+postcode | geometry |
| N34 | place | postcode | 20453 | country:de |
And updating postcodes
Then location_postcode contains exactly
| country | postcode | geometry |
| de | 20453 | country:de |
Scenario: When changing from a postcode type, the entry appears in placex
When importing
And updating places
| osm | class | type | addr+postcode | geometry |
| N34 | place | postcode | 01982 | country:de |
Then placex has no entry for N34
When updating places
| osm | class | type | addr+postcode | housenr | geometry |
| N34 | place | house | 20453 | 1 | country:de |
Then placex contains
| object | addr+housenumber | geometry |
| N34 | 1 | country:de|
And place contains exactly
| object | class | type |
| N34 | place | house |
When updating postcodes
Then location_postcode contains exactly
| country | postcode | geometry |
| de | 20453 | country:de |
Scenario: When changing to a postcode type, the entry disappears from placex
When importing
And updating places
| osm | class | type | addr+postcode | housenr | geometry |
| N34 | place | house | 20453 | 1 | country:de |
Then placex contains
| object | addr+housenumber | geometry |
| N34 | 1 | country:de|
When updating places
| osm | class | type | addr+postcode | geometry |
| N34 | place | postcode | 01982 | country:de |
Then placex has no entry for N34
And place contains exactly
| object | class | type |
| N34 | place | postcode |
When updating postcodes
Then location_postcode contains exactly
| country | postcode | geometry |
| de | 01982 | country:de |
Scenario: When a parent is deleted, the postcode gets a new parent
Given the grid with origin DE
| 1 | | 3 | 4 |
| | 9 | | |
| 2 | | 5 | 6 |
Given the places
| osm | class | type | name | admin | geometry |
| R1 | boundary | administrative | Big | 6 | (1,4,6,2,1) |
| R2 | boundary | administrative | Small | 6 | (1,3,5,2,1) |
Given the named places
| osm | class | type | addr+postcode | geometry |
| N9 | place | postcode | 12345 | 9 |
When importing
And updating postcodes
Then location_postcode contains exactly
| country | postcode | geometry | parent_place_id |
| de | 12345 | 9 | R2 |
When marking for delete R2
Then location_postcode contains exactly
| country | postcode | geometry | parent_place_id |
| de | 12345 | 9 | R1 |

View File

@@ -1,116 +0,0 @@
@DB
Feature: Update of simple objects
Testing simple updating functionality
Scenario: Do delete small boundary features
Given the 1.0 grid
| 1 | 2 |
| 4 | 3 |
Given the places
| osm | class | type | admin | geometry |
| R1 | boundary | administrative | 3 | (1,2,3,4,1) |
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 2.0 grid
| 1 | 2 |
| 4 | 3 |
Given the places
| osm | class | type | admin | geometry |
| R1 | boundary | administrative | 3 | (1,2,3,4,1) |
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 2.0 grid
| 1 | 2 |
| 4 | 3 |
Given the named places
| osm | class | type | geometry |
| W1 | place | house | (1,2,3,4,1) |
| R1 | natural | wood | (1,2,3,4,1) |
| R2 | highway | residential | (1,2,3,4,1) |
When importing
Then placex contains
| object | rank_address |
| R1 | 0 |
| R2 | 26 |
| W1 | 30 |
When marking for delete R1,R2,W1
Then placex has no entry for W1
Then placex has no entry for R1
Then placex has no entry for R2
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 | country:de |
When importing
Then placex has no entry for N3
When updating places
| osm | class | type | postcode | housenr | geometry |
| N3 | place | house | 12345 | 13 | country:de |
Then placex contains
| object | class | type |
| N3 | place | house |
Scenario: remove boundary when changing from polygon to way
Given the grid
| 1 | 2 |
| 3 | 4 |
And the places
| osm | class | type | name | admin | geometry |
| W1 | boundary | administrative | Haha | 5 | (1, 2, 4, 3, 1) |
When importing
Then placex contains
| object |
| W1 |
When updating places
| osm | class | type | name | admin | geometry |
| W1 | boundary | administrative | Haha | 5 | 1, 2, 4, 3 |
Then placex has no entry for W1
#895
Scenario: update rank when boundary is downgraded from admin to historic
Given the grid
| 1 | 2 |
| 3 | 4 |
And the places
| osm | class | type | name | admin | geometry |
| W1 | boundary | administrative | Haha | 5 | (1, 2, 4, 3, 1) |
When importing
Then placex contains
| object | rank_address |
| W1 | 10 |
When updating places
| osm | class | type | name | admin | geometry |
| W1 | boundary | historic | Haha | 5 | (1, 2, 4, 3, 1) |
Then placex contains
| object | rank_address |
| W1 | 0 |

View File

@@ -1,64 +0,0 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
from pathlib import Path
import sys
from behave import * # noqa
sys.path.insert(1, str(Path(__file__, '..', '..', '..', 'src').resolve()))
from steps.geometry_factory import GeometryFactory # noqa: E402
from steps.nominatim_environment import NominatimEnvironment # noqa: E402
TEST_BASE_DIR = Path(__file__, '..', '..').resolve()
userconfig = {
'REMOVE_TEMPLATE': False,
'KEEP_TEST_DB': False,
'DB_HOST': None,
'DB_PORT': None,
'DB_USER': None,
'DB_PASS': None,
'TEMPLATE_DB': 'test_template_nominatim',
'TEST_DB': 'test_nominatim',
'API_TEST_DB': 'test_api_nominatim',
'API_TEST_FILE': TEST_BASE_DIR / 'testdb' / 'apidb-test-data.pbf',
'TOKENIZER': None, # Test with a custom tokenizer
'STYLE': 'extratags',
'API_ENGINE': 'falcon'
}
use_step_matcher("re") # noqa: F405
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)
# Nominatim test setup
context.nominatim = NominatimEnvironment(context.config.userdata)
context.osm = GeometryFactory()
def before_scenario(context, scenario):
if 'SQLITE' not in context.tags \
and context.config.userdata['API_TEST_DB'].startswith('sqlite:'):
context.scenario.skip("Not usable with Sqlite database.")
elif 'DB' in context.tags:
context.nominatim.setup_db(context)
elif 'APIDB' in context.tags:
context.nominatim.setup_api_db()
elif 'UNKNOWNDB' in context.tags:
context.nominatim.setup_unknown_db()
def after_scenario(context, scenario):
if 'DB' in context.tags:
context.nominatim.teardown_db(context)

View File

@@ -1,99 +0,0 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Collection of assertion functions used for the steps.
"""
import json
import math
import re
OSM_TYPE = {'N': 'node', 'W': 'way', 'R': 'relation',
'n': 'node', 'w': 'way', 'r': 'relation',
'node': 'n', 'way': 'w', 'relation': 'r'}
class OsmType:
""" Compares an OSM type, accepting both N/R/W and node/way/relation.
"""
def __init__(self, value):
self.value = value
def __eq__(self, other):
return other == self.value or other == OSM_TYPE[self.value]
def __str__(self):
return f"{self.value} or {OSM_TYPE[self.value]}"
class Field:
""" Generic comparator for fields, which looks at the type of the
value compared.
"""
def __init__(self, value, **extra_args):
self.value = value
self.extra_args = extra_args
def __eq__(self, other):
if isinstance(self.value, float):
return math.isclose(self.value, float(other), **self.extra_args)
if self.value.startswith('^'):
return re.fullmatch(self.value, str(other))
if isinstance(other, dict):
return other == eval('{' + self.value + '}')
return str(self.value) == str(other)
def __str__(self):
return str(self.value)
class Bbox:
""" Comparator for bounding boxes.
"""
def __init__(self, bbox_string):
self.coord = [float(x) for x in bbox_string.split(',')]
def __contains__(self, item):
if isinstance(item, str):
item = item.split(',')
item = list(map(float, item))
if len(item) == 2:
return self.coord[0] <= item[0] <= self.coord[2] \
and self.coord[1] <= item[1] <= self.coord[3]
if len(item) == 4:
return item[0] >= self.coord[0] and item[1] <= self.coord[1] \
and item[2] >= self.coord[2] and item[3] <= self.coord[3]
raise ValueError("Not a coordinate or bbox.")
def __str__(self):
return str(self.coord)
def check_for_attributes(obj, attrs, presence='present'):
""" Check that the object has the given attributes. 'attrs' is a
string with a comma-separated list of attributes. If 'presence'
is set to 'absent' then the function checks that the attributes do
not exist for the object
"""
def _dump_json():
return json.dumps(obj, sort_keys=True, indent=2, ensure_ascii=False)
for attr in attrs.split(','):
attr = attr.strip()
if presence == 'absent':
assert attr not in obj, \
f"Unexpected attribute {attr}. Full response:\n{_dump_json()}"
else:
assert attr in obj, \
f"No attribute '{attr}'. Full response:\n{_dump_json()}"

View File

@@ -1,262 +0,0 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Collection of aliases for various world coordinates.
"""
ALIASES = {
# Country aliases
'AD': (1.58972, 42.54241),
'AE': (54.61589, 24.82431),
'AF': (65.90264, 34.84708),
'AG': (-61.72430, 17.069),
'AI': (-63.10571, 18.25461),
'AL': (19.84941, 40.21232),
'AM': (44.64229, 40.37821),
'AO': (16.21924, -12.77014),
'AQ': (44.99999, -75.65695),
'AR': (-61.10759, -34.37615),
'AS': (-170.68470, -14.29307),
'AT': (14.25747, 47.36542),
'AU': (138.23155, -23.72068),
'AW': (-69.98255, 12.555),
'AX': (19.91839, 59.81682),
'AZ': (48.38555, 40.61639),
'BA': (17.18514, 44.25582),
'BB': (-59.53342, 13.19),
'BD': (89.75989, 24.34205),
'BE': (4.90078, 50.34682),
'BF': (-0.56743, 11.90471),
'BG': (24.80616, 43.09859),
'BH': (50.52032, 25.94685),
'BI': (29.54561, -2.99057),
'BJ': (2.70062, 10.02792),
'BL': (-62.79349, 17.907),
'BM': (-64.77406, 32.30199),
'BN': (114.52196, 4.28638),
'BO': (-62.02473, -17.77723),
'BQ': (-63.14322, 17.566),
'BR': (-45.77065, -9.58685),
'BS': (-77.60916, 23.8745),
'BT': (90.01350, 27.28137),
'BV': (3.35744, -54.4215),
'BW': (23.51505, -23.48391),
'BY': (26.77259, 53.15885),
'BZ': (-88.63489, 16.33951),
'CA': (-107.74817, 67.12612),
'CC': (96.84420, -12.01734),
'CD': (24.09544, -1.67713),
'CF': (22.58701, 5.98438),
'CG': (15.78875, 0.40388),
'CH': (7.65705, 46.57446),
'CI': (-6.31190, 6.62783),
'CK': (-159.77835, -21.23349),
'CL': (-70.41790, -53.77189),
'CM': (13.26022, 5.94519),
'CN': (96.44285, 38.04260),
'CO': (-72.52951, 2.45174),
'CR': (-83.83314, 9.93514),
'CU': (-80.81673, 21.88852),
'CV': (-24.50810, 14.929),
'CW': (-68.96409, 12.1845),
'CX': (105.62411, -10.48417),
'CY': (32.95922, 35.37010),
'CZ': (16.32098, 49.50692),
'DE': (9.30716, 50.21289),
'DJ': (42.96904, 11.41542),
'DK': (9.18490, 55.98916),
'DM': (-61.00358, 15.65470),
'DO': (-69.62855, 18.58841),
'DZ': (4.24749, 25.79721),
'EC': (-77.45831, -0.98284),
'EE': (23.94288, 58.43952),
'EG': (28.95293, 28.17718),
'EH': (-13.69031, 25.01241),
'ER': (39.01223, 14.96033),
'ES': (-2.59110, 38.79354),
'ET': (38.61697, 7.71399),
'FI': (26.89798, 63.56194),
'FJ': (177.91853, -17.74237),
'FK': (-58.99044, -51.34509),
'FM': (151.95358, 8.5045),
'FO': (-6.60483, 62.10000),
'FR': (0.28410, 47.51045),
'GA': (10.81070, -0.07429),
'GB': (-0.92823, 52.01618),
'GD': (-61.64524, 12.191),
'GE': (44.16664, 42.00385),
'GF': (-53.46524, 3.56188),
'GG': (-2.50580, 49.58543),
'GH': (-0.46348, 7.16051),
'GI': (-5.32053, 36.11066),
'GL': (-33.85511, 74.66355),
'GM': (-16.40960, 13.25),
'GN': (-13.83940, 10.96291),
'GP': (-61.68712, 16.23049),
'GQ': (10.23973, 1.43119),
'GR': (23.17850, 39.06206),
'GS': (-36.49430, -54.43067),
'GT': (-90.74368, 15.20428),
'GU': (144.73362, 13.44413),
'GW': (-14.83525, 11.92486),
'GY': (-58.45167, 5.73698),
'HK': (114.18577, 22.34923),
'HM': (73.68230, -53.22105),
'HN': (-86.95414, 15.23820),
'HR': (17.49966, 45.52689),
'HT': (-73.51925, 18.32492),
'HU': (20.35362, 47.51721),
'ID': (123.34505, -0.83791),
'IE': (-9.00520, 52.87725),
'IL': (35.46314, 32.86165),
'IM': (-4.86740, 54.023),
'IN': (88.67620, 27.86155),
'IO': (71.42743, -6.14349),
'IQ': (42.58109, 34.26103),
'IR': (56.09355, 30.46751),
'IS': (-17.51785, 64.71687),
'IT': (10.42639, 44.87904),
'JE': (-2.19261, 49.12458),
'JM': (-76.84020, 18.3935),
'JO': (36.55552, 30.75741),
'JP': (138.72531, 35.92099),
'KE': (36.90602, 1.08512),
'KG': (76.15571, 41.66497),
'KH': (104.31901, 12.95555),
'KI': (173.63353, 0.139),
'KM': (44.31474, -12.241),
'KN': (-62.69379, 17.2555),
'KP': (126.65575, 39.64575),
'KR': (127.27740, 36.41388),
'KW': (47.30684, 29.69180),
'KY': (-81.07455, 19.29949),
'KZ': (72.00811, 49.88855),
'LA': (102.44391, 19.81609),
'LB': (35.48464, 33.41766),
'LC': (-60.97894, 13.891),
'LI': (9.54693, 47.15934),
'LK': (80.38520, 8.41649),
'LR': (-11.16960, 4.04122),
'LS': (28.66984, -29.94538),
'LT': (24.51735, 55.49293),
'LU': (6.08649, 49.81533),
'LV': (23.51033, 56.67144),
'LY': (15.36841, 28.12177),
'MA': (-4.03061, 33.21696),
'MC': (7.47743, 43.62917),
'MD': (29.61725, 46.66517),
'ME': (19.72291, 43.02441),
'MF': (-63.06666, 18.08102),
'MG': (45.86378, -20.50245),
'MH': (171.94982, 5.983),
'MK': (21.42108, 41.08980),
'ML': (-1.93310, 16.46993),
'MM': (95.54624, 21.09620),
'MN': (99.81138, 48.18615),
'MO': (113.56441, 22.16209),
'MP': (145.21345, 14.14902),
'MQ': (-60.81128, 14.43706),
'MR': (-9.42324, 22.59251),
'MS': (-62.19455, 16.745),
'MT': (14.38363, 35.94467),
'MU': (57.55121, -20.41),
'MV': (73.39292, 4.19375),
'MW': (33.95722, -12.28218),
'MX': (-105.89221, 25.86826),
'MY': (112.71154, 2.10098),
'MZ': (37.58689, -13.72682),
'NA': (16.68569, -21.46572),
'NC': (164.95322, -20.38889),
'NE': (10.06041, 19.08273),
'NF': (167.95718, -29.0645),
'NG': (10.17781, 10.17804),
'NI': (-85.87974, 13.21715),
'NL': (-68.57062, 12.041),
'NO': (23.11556, 70.09934),
'NP': (83.36259, 28.13107),
'NR': (166.93479, -0.5275),
'NU': (-169.84873, -19.05305),
'NZ': (167.97209, -45.13056),
'OM': (56.86055, 20.47413),
'PA': (-79.40160, 8.80656),
'PE': (-78.66540, -7.54711),
'PF': (-145.05719, -16.70862),
'PG': (146.64600, -7.37427),
'PH': (121.48359, 15.09965),
'PK': (72.11347, 31.14629),
'PL': (17.88136, 52.77182),
'PM': (-56.19515, 46.78324),
'PN': (-130.10642, -25.06955),
'PR': (-65.88755, 18.37169),
'PS': (35.39801, 32.24773),
'PT': (-8.45743, 40.11154),
'PW': (134.49645, 7.3245),
'PY': (-59.51787, -22.41281),
'QA': (51.49903, 24.99816),
'RE': (55.77345, -21.36388),
'RO': (26.37632, 45.36120),
'RS': (20.40371, 44.56413),
'RU': (116.44060, 59.06780),
'RW': (29.57882, -1.62404),
'SA': (47.73169, 22.43790),
'SB': (164.63894, -10.23606),
'SC': (46.36566, -9.454),
'SD': (28.14720, 14.56423),
'SE': (15.68667, 60.35568),
'SG': (103.84187, 1.304),
'SH': (-12.28155, -37.11546),
'SI': (14.04738, 46.39085),
'SJ': (15.27552, 79.23365),
'SK': (20.41603, 48.86970),
'SL': (-11.47773, 8.78156),
'SM': (12.46062, 43.94279),
'SN': (-15.37111, 14.99477),
'SO': (46.93383, 9.34094),
'SR': (-55.42864, 4.56985),
'SS': (28.13573, 8.50933),
'ST': (6.61025, 0.2215),
'SV': (-89.36665, 13.43072),
'SX': (-63.15393, 17.9345),
'SY': (38.15513, 35.34221),
'SZ': (31.78263, -26.14244),
'TC': (-71.32554, 21.35),
'TD': (17.42092, 13.46223),
'TF': (137.5, -67.5),
'TG': (1.06983, 7.87677),
'TH': (102.00877, 16.42310),
'TJ': (71.91349, 39.01527),
'TK': (-171.82603, -9.20990),
'TL': (126.22520, -8.72636),
'TM': (57.71603, 39.92534),
'TN': (9.04958, 34.84199),
'TO': (-176.99320, -23.11104),
'TR': (32.82002, 39.86350),
'TT': (-60.70793, 11.1385),
'TV': (178.77499, -9.41685),
'TW': (120.30074, 23.17002),
'TZ': (33.53892, -5.01840),
'UA': (33.44335, 49.30619),
'UG': (32.96523, 2.08584),
'UM': (-169.50993, 16.74605),
'US': (-116.39535, 40.71379),
'UY': (-56.46505, -33.62658),
'UZ': (61.35529, 42.96107),
'VA': (12.33197, 42.04931),
'VC': (-61.09905, 13.316),
'VE': (-64.88323, 7.69849),
'VG': (-64.62479, 18.419),
'VI': (-64.88950, 18.32263),
'VN': (104.20179, 10.27644),
'VU': (167.31919, -15.88687),
'WF': (-176.20781, -13.28535),
'WS': (-172.10966, -13.85093),
'YE': (45.94562, 16.16338),
'YT': (44.93774, -12.60882),
'ZA': (23.19488, -30.43276),
'ZM': (26.38618, -14.39966),
'ZW': (30.12419, -19.86907)
}

View File

@@ -1,88 +0,0 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
from steps.geometry_alias import ALIASES
class GeometryFactory:
""" Provides functions to create geometries from coordinates and data grids.
"""
def __init__(self):
self.grid = {}
def parse_geometry(self, geom):
""" Create a WKT SQL term for the given geometry.
The function understands the following formats:
country:<country code>
Point geometry guaranteed to be in the given country
<P>
Point geometry
<P>,...,<P>
Line geometry
(<P>,...,<P>)
Polygon geometry
<P> may either be a coordinate of the form '<x> <y>' or a single
number. In the latter case it must refer to a point in
a previously defined grid.
"""
if geom.startswith('country:'):
ccode = geom[8:].upper()
assert ccode in ALIASES, "Geometry error: unknown country " + ccode
return "ST_SetSRID('POINT({} {})'::geometry, 4326)".format(*ALIASES[ccode])
if geom.find(',') < 0:
out = "POINT({})".format(self.mk_wkt_point(geom))
elif geom.find('(') < 0:
out = "LINESTRING({})".format(self.mk_wkt_points(geom))
else:
out = "POLYGON(({}))".format(self.mk_wkt_points(geom.strip('() ')))
return "ST_SetSRID('{}'::geometry, 4326)".format(out)
def mk_wkt_point(self, point):
""" Parse a point description.
The point may either consist of 'x y' coordinates or a number
that refers to a grid setup.
"""
geom = point.strip()
if geom.find(' ') >= 0:
return geom
try:
pt = self.grid_node(int(geom))
except ValueError:
assert False, "Scenario error: Point '{}' is not a number".format(geom)
assert pt is not None, "Scenario error: Point '{}' not found in grid".format(geom)
return "{} {}".format(*pt)
def mk_wkt_points(self, geom):
""" Parse a list of points.
The list must be a comma-separated list of points. Points
in coordinate and grid format may be mixed.
"""
return ','.join([self.mk_wkt_point(x) for x in geom.split(',')])
def set_grid(self, lines, grid_step, origin=(0.0, 0.0)):
""" Replace the grid with one from the given lines.
"""
self.grid = {}
y = origin[1]
for line in lines:
x = origin[0]
for pt_id in line:
if pt_id.isdigit():
self.grid[int(pt_id)] = (x, y)
x += grid_step
y += grid_step
def grid_node(self, nodeid):
""" Get the coordinates for the given grid node.
"""
return self.grid.get(nodeid)

View File

@@ -1,253 +0,0 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Classes wrapping HTTP responses from the Nominatim API.
"""
import re
import json
import xml.etree.ElementTree as ET
from check_functions import OsmType, Field, check_for_attributes
class GenericResponse:
""" Common base class for all API responses.
"""
def __init__(self, page, fmt, errorcode=200):
fmt = fmt.strip()
if fmt == 'jsonv2':
fmt = 'json'
self.page = page
self.format = fmt
self.errorcode = errorcode
self.result = []
self.header = dict()
if errorcode == 200 and fmt != 'debug':
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().decode(code)
if isinstance(self.result, dict):
if 'error' in self.result:
self.result = []
else:
self.result = [self.result]
def _parse_geojson(self):
self._parse_json()
if self.result:
geojson = self.result[0]
# check for valid geojson
check_for_attributes(geojson, 'type,features')
assert geojson['type'] == 'FeatureCollection'
assert isinstance(geojson['features'], list)
self.result = []
for result in geojson['features']:
check_for_attributes(result, 'type,properties,geometry')
assert result['type'] == 'Feature'
new = result['properties']
check_for_attributes(new, 'geojson', 'absent')
new['geojson'] = result['geometry']
if 'bbox' in result:
check_for_attributes(new, 'boundingbox', 'absent')
# bbox is minlon, minlat, maxlon, maxlat
# boundingbox is minlat, maxlat, minlon, maxlon
new['boundingbox'] = [result['bbox'][1],
result['bbox'][3],
result['bbox'][0],
result['bbox'][2]]
for k, v in geojson.items():
if k not in ('type', 'features'):
check_for_attributes(new, '__' + k, 'absent')
new['__' + k] = v
self.result.append(new)
def _parse_geocodejson(self):
self._parse_geojson()
if self.result:
for r in self.result:
assert set(r.keys()) == {'geocoding', 'geojson', '__geocoding'}, \
f"Unexpected keys in result: {r.keys()}"
check_for_attributes(r['geocoding'], 'geojson', 'absent')
inner = r.pop('geocoding')
r.update(inner)
def assert_address_field(self, idx, field, value):
""" Check that result rows`idx` has a field `field` with value `value`
in its address. If idx is None, then all results are checked.
"""
if idx is None:
todo = range(len(self.result))
else:
todo = [int(idx)]
for idx in todo:
self.check_row(idx, 'address' in self.result[idx], "No field 'address'")
address = self.result[idx]['address']
self.check_row_field(idx, field, value, base=address)
def match_row(self, row, context=None, field=None):
""" Match the result fields against the given behave table row.
"""
if 'ID' in row.headings:
todo = [int(row['ID'])]
else:
todo = range(len(self.result))
for i in todo:
subdict = self.result[i]
if field is not None:
for key in field.split('.'):
self.check_row(i, key in subdict, f"Missing subfield {key}")
subdict = subdict[key]
self.check_row(i, isinstance(subdict, dict),
f"Subfield {key} not a dict")
for name, value in zip(row.headings, row.cells):
if name == 'ID':
pass
elif name == 'osm':
self.check_row_field(i, 'osm_type', OsmType(value[0]), base=subdict)
self.check_row_field(i, 'osm_id', Field(value[1:]), base=subdict)
elif name == 'centroid':
if ' ' in value:
lon, lat = value.split(' ')
elif context is not None:
lon, lat = context.osm.grid_node(int(value))
else:
raise RuntimeError("Context needed when using grid coordinates")
self.check_row_field(i, 'lat', Field(float(lat), abs_tol=1e-07), base=subdict)
self.check_row_field(i, 'lon', Field(float(lon), abs_tol=1e-07), base=subdict)
else:
self.check_row_field(i, name, Field(value), base=subdict)
def check_row(self, idx, check, msg):
""" Assert for the condition 'check' and print 'msg' on fail together
with the contents of the failing result.
"""
class _RowError:
def __init__(self, row):
self.row = row
def __str__(self):
return f"{msg}. Full row {idx}:\n" \
+ json.dumps(self.row, indent=4, ensure_ascii=False)
assert check, _RowError(self.result[idx])
def check_row_field(self, idx, field, expected, base=None):
""" Check field 'field' of result 'idx' for the expected value
and print a meaningful error if the condition fails.
When 'base' is set to a dictionary, then the field is checked
in that base. The error message will still report the contents
of the full result.
"""
if base is None:
base = self.result[idx]
self.check_row(idx, field in base, f"No field '{field}'")
value = base[field]
self.check_row(idx, expected == value,
f"\nBad value for field '{field}'. Expected: {expected}, got: {value}")
class SearchResponse(GenericResponse):
""" Specialised class for search and lookup responses.
Transforms the xml response in a format similar to json.
"""
def _parse_xml(self):
xml_tree = ET.fromstring(self.page)
self.header = dict(xml_tree.attrib)
for child in xml_tree:
assert 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 == 'geokml':
self.result[-1][sub.tag] = True
else:
address[sub.tag] = sub.text
if address:
self.result[-1]['address'] = address
class ReverseResponse(GenericResponse):
""" Specialised class for reverse responses.
Transforms the xml response in a format similar to json.
"""
def _parse_xml(self):
xml_tree = ET.fromstring(self.page)
self.header = dict(xml_tree.attrib)
self.result = []
for child in xml_tree:
if child.tag == 'result':
assert not self.result, "More than one result in reverse result"
self.result.append(dict(child.attrib))
check_for_attributes(self.result[0], 'display_name', 'absent')
self.result[0]['display_name'] = child.text
elif child.tag == 'addressparts':
assert 'address' not in self.result[0], "More than one address in result"
address = {}
for sub in child:
assert len(sub) == 0, f"Address element '{sub.tag}' has subelements"
address[sub.tag] = sub.text
self.result[0]['address'] = address
elif child.tag == 'extratags':
assert 'extratags' not in self.result[0], "More than one extratags in result"
self.result[0]['extratags'] = {}
for tag in child:
assert len(tag) == 0, f"Extratags element '{tag.attrib['key']}' has subelements"
self.result[0]['extratags'][tag.attrib['key']] = tag.attrib['value']
elif child.tag == 'namedetails':
assert 'namedetails' not in self.result[0], "More than one namedetails in result"
self.result[0]['namedetails'] = {}
for tag in child:
assert len(tag) == 0, \
f"Namedetails element '{tag.attrib['desc']}' has subelements"
self.result[0]['namedetails'][tag.attrib['desc']] = tag.text
elif child.tag == 'geokml':
assert 'geokml' not in self.result[0], "More than one geokml in result"
self.result[0]['geokml'] = ET.tostring(child, encoding='unicode')
else:
assert child.tag == 'error', \
f"Unknown XML tag {child.tag} on page: {self.page}"
class StatusResponse(GenericResponse):
""" Specialised class for status responses.
Can also parse text responses.
"""
def _parse_text(self):
pass

View File

@@ -1,315 +0,0 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
from pathlib import Path
import tempfile
import psycopg
from psycopg import sql as pysql
from nominatim_db import cli
from nominatim_db.config import Configuration
from nominatim_db.db.connection import register_hstore, execute_scalar
from nominatim_db.tokenizer import factory as tokenizer_factory
class NominatimEnvironment:
""" Collects all functions for the execution of Nominatim functions.
"""
def __init__(self, config):
self.src_dir = (Path(__file__) / '..' / '..' / '..' / '..').resolve()
self.db_host = config['DB_HOST']
self.db_port = config['DB_PORT']
self.db_user = config['DB_USER']
self.db_pass = config['DB_PASS']
self.template_db = config['TEMPLATE_DB']
self.test_db = config['TEST_DB']
self.api_test_db = config['API_TEST_DB']
self.api_test_file = config['API_TEST_FILE']
self.tokenizer = config['TOKENIZER']
self.import_style = config['STYLE']
self.reuse_template = not config['REMOVE_TEMPLATE']
self.keep_scenario_db = config['KEEP_TEST_DB']
self.default_config = Configuration(None).get_os_env()
self.test_env = None
self.template_db_done = False
self.api_db_done = False
self.website_dir = None
if not hasattr(self, f"create_api_request_func_{config['API_ENGINE']}"):
raise RuntimeError(f"Unknown API engine '{config['API_ENGINE']}'")
self.api_engine = getattr(self, f"create_api_request_func_{config['API_ENGINE']}")()
def connect_database(self, dbname):
""" Return a connection to the database with the given name.
Uses configured host, user and port.
"""
dbargs = {'dbname': dbname, 'row_factory': psycopg.rows.dict_row}
if self.db_host:
dbargs['host'] = self.db_host
if self.db_port:
dbargs['port'] = self.db_port
if self.db_user:
dbargs['user'] = self.db_user
if self.db_pass:
dbargs['password'] = self.db_pass
return psycopg.connect(**dbargs)
def write_nominatim_config(self, dbname):
""" Set up a custom test configuration that connects to the given
database. This sets up the environment variables so that they can
be picked up by dotenv and creates a project directory with the
appropriate website scripts.
"""
if dbname.startswith('sqlite:'):
dsn = 'sqlite:dbname={}'.format(dbname[7:])
else:
dsn = 'pgsql:dbname={}'.format(dbname)
if self.db_host:
dsn += ';host=' + self.db_host
if self.db_port:
dsn += ';port=' + self.db_port
if self.db_user:
dsn += ';user=' + self.db_user
if self.db_pass:
dsn += ';password=' + self.db_pass
self.test_env = dict(self.default_config)
self.test_env['NOMINATIM_DATABASE_DSN'] = dsn
self.test_env['NOMINATIM_LANGUAGES'] = 'en,de,fr,ja'
self.test_env['NOMINATIM_FLATNODE_FILE'] = ''
self.test_env['NOMINATIM_IMPORT_STYLE'] = 'full'
self.test_env['NOMINATIM_USE_US_TIGER_DATA'] = 'yes'
self.test_env['NOMINATIM_DATADIR'] = str((self.src_dir / 'data').resolve())
self.test_env['NOMINATIM_SQLDIR'] = str((self.src_dir / 'lib-sql').resolve())
self.test_env['NOMINATIM_CONFIGDIR'] = str((self.src_dir / 'settings').resolve())
if self.tokenizer is not None:
self.test_env['NOMINATIM_TOKENIZER'] = self.tokenizer
if self.import_style is not None:
self.test_env['NOMINATIM_IMPORT_STYLE'] = self.import_style
if self.website_dir is not None:
self.website_dir.cleanup()
self.website_dir = tempfile.TemporaryDirectory()
def get_test_config(self):
cfg = Configuration(Path(self.website_dir.name), environ=self.test_env)
return cfg
def get_libpq_dsn(self):
dsn = self.test_env['NOMINATIM_DATABASE_DSN']
def quote_param(param):
key, val = param.split('=')
val = val.replace('\\', '\\\\').replace("'", "\\'")
if ' ' in val:
val = "'" + val + "'"
return key + '=' + val
if dsn.startswith('pgsql:'):
# Old PHP DSN format. Convert before returning.
return ' '.join([quote_param(p) for p in dsn[6:].split(';')])
return dsn
def db_drop_database(self, name):
""" Drop the database with the given name.
"""
with self.connect_database('postgres') as conn:
conn.autocommit = True
conn.execute(pysql.SQL('DROP DATABASE IF EXISTS')
+ pysql.Identifier(name))
def setup_template_db(self):
""" Setup a template database that already contains common test data.
Having a template database speeds up tests considerably but at
the price that the tests sometimes run with stale data.
"""
if self.template_db_done:
return
self.template_db_done = True
self.write_nominatim_config(self.template_db)
if not self._reuse_or_drop_db(self.template_db):
try:
# execute nominatim import on an empty file to get the right tables
with tempfile.NamedTemporaryFile(dir='/tmp', suffix='.xml') as fd:
fd.write(b'<osm version="0.6"></osm>')
fd.flush()
self.run_nominatim('import', '--osm-file', fd.name,
'--osm2pgsql-cache', '1',
'--ignore-errors',
'--offline', '--index-noanalyse')
except: # noqa: E722
self.db_drop_database(self.template_db)
raise
self.run_nominatim('refresh', '--functions')
def setup_api_db(self):
""" Setup a test against the API test database.
"""
self.write_nominatim_config(self.api_test_db)
if self.api_test_db.startswith('sqlite:'):
return
if not self.api_db_done:
self.api_db_done = True
if not self._reuse_or_drop_db(self.api_test_db):
testdata = (Path(__file__) / '..' / '..' / '..' / 'testdb').resolve()
self.test_env['NOMINATIM_WIKIPEDIA_DATA_PATH'] = str(testdata)
simp_file = Path(self.website_dir.name) / 'secondary_importance.sql.gz'
simp_file.symlink_to(testdata / 'secondary_importance.sql.gz')
try:
self.run_nominatim('import', '--osm-file', str(self.api_test_file))
self.run_nominatim('add-data', '--tiger-data', str(testdata / 'tiger'))
self.run_nominatim('freeze')
csv_path = str(testdata / 'full_en_phrases_test.csv')
self.run_nominatim('special-phrases', '--import-from-csv', csv_path)
except: # noqa: E722
self.db_drop_database(self.api_test_db)
raise
tokenizer_factory.get_tokenizer_for_db(self.get_test_config())
def setup_unknown_db(self):
""" Setup a test against a non-existing database.
"""
# The tokenizer needs an existing database to function.
# So start with the usual database
class _Context:
db = None
context = _Context()
self.setup_db(context)
tokenizer_factory.create_tokenizer(self.get_test_config(), init_db=False)
# Then drop the DB again
self.teardown_db(context, force_drop=True)
def setup_db(self, context):
""" Setup a test against a fresh, empty test database.
"""
self.setup_template_db()
with self.connect_database(self.template_db) as conn:
conn.autocommit = True
conn.execute(pysql.SQL('DROP DATABASE IF EXISTS')
+ pysql.Identifier(self.test_db))
conn.execute(pysql.SQL('CREATE DATABASE {} TEMPLATE = {}').format(
pysql.Identifier(self.test_db),
pysql.Identifier(self.template_db)))
self.write_nominatim_config(self.test_db)
context.db = self.connect_database(self.test_db)
context.db.autocommit = True
register_hstore(context.db)
def teardown_db(self, context, force_drop=False):
""" Remove the test database, if it exists.
"""
if hasattr(context, 'db'):
context.db.close()
if force_drop or not self.keep_scenario_db:
self.db_drop_database(self.test_db)
def _reuse_or_drop_db(self, name):
""" Check for the existence of the given DB. If reuse is enabled,
then the function checks for existnce and returns True if the
database is already there. Otherwise an existing database is
dropped and always false returned.
"""
if self.reuse_template:
with self.connect_database('postgres') as conn:
num = execute_scalar(conn,
'select count(*) from pg_database where datname = %s',
(name,))
if num == 1:
return True
else:
self.db_drop_database(name)
return False
def reindex_placex(self, db):
""" Run the indexing step until all data in the placex has
been processed. Indexing during updates can produce more data
to index under some circumstances. That is why indexing may have
to be run multiple times.
"""
self.run_nominatim('index')
def run_nominatim(self, *cmdline):
""" Run the nominatim command-line tool via the library.
"""
if self.website_dir is not None:
cmdline = list(cmdline) + ['--project-dir', self.website_dir.name]
cli.nominatim(cli_args=cmdline,
environ=self.test_env)
def copy_from_place(self, db):
""" Copy data from place to the placex and location_property_osmline
tables invoking the appropriate triggers.
"""
self.run_nominatim('refresh', '--functions', '--no-diff-updates')
with db.cursor() as cur:
cur.execute("""INSERT INTO placex (osm_type, osm_id, class, type,
name, admin_level, address,
extratags, geometry)
SELECT osm_type, osm_id, class, type,
name, admin_level, address,
extratags, geometry
FROM place
WHERE not (class='place' and type='houses' and osm_type='W')""")
cur.execute("""INSERT INTO location_property_osmline (osm_id, address, linegeo)
SELECT osm_id, address, geometry
FROM place
WHERE class='place' and type='houses'
and osm_type='W'
and ST_GeometryType(geometry) = 'ST_LineString'""")
def create_api_request_func_starlette(self):
import nominatim_api.server.starlette.server
from asgi_lifespan import LifespanManager
import httpx
async def _request(endpoint, params, project_dir, environ, http_headers):
app = nominatim_api.server.starlette.server.get_application(project_dir, environ)
async with LifespanManager(app):
async with httpx.AsyncClient(app=app, base_url="http://nominatim.test") as client:
response = await client.get(f"/{endpoint}", params=params,
headers=http_headers)
return response.text, response.status_code
return _request
def create_api_request_func_falcon(self):
import nominatim_api.server.falcon.server
import falcon.testing
async def _request(endpoint, params, project_dir, environ, http_headers):
app = nominatim_api.server.falcon.server.get_application(project_dir, environ)
async with falcon.testing.ASGIConductor(app) as conductor:
response = await conductor.get(f"/{endpoint}", params=params,
headers=http_headers)
return response.text, response.status_code
return _request

View File

@@ -1,120 +0,0 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Helper classes for filling the place table.
"""
import random
import string
class PlaceColumn:
""" Helper class to collect contents from a behave table row and
insert it into the place table.
"""
def __init__(self, context):
self.columns = {'admin_level': 15}
self.context = context
self.geometry = None
def add_row(self, row, force_name):
""" Parse the content from the given behave row as place column data.
"""
for name, value in zip(row.headings, row.cells):
self._add(name, value)
assert 'osm_type' in self.columns, "osm column missing"
if force_name and 'name' not in self.columns:
self._add_hstore(
'name',
'name',
''.join(random.choices(string.printable, k=random.randrange(30))),
)
return self
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)
elif key.startswith('addr+'):
self._add_hstore('address', key[5:], value)
elif key in ('name', 'address', 'extratags'):
self.columns[key] = eval('{' + value + '}')
else:
assert key in ('class', 'type'), "Unknown column '{}'.".format(key)
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 value[0] in 'NRW' and value[1:].isdigit(), \
"OSM id needs to be of format <NRW><id>."
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):
if value:
self._add_hstore('address', 'housenumber', value)
def _set_key_postcode(self, value):
if value:
self._add_hstore('address', 'postcode', value)
def _set_key_street(self, value):
if value:
self._add_hstore('address', 'street', value)
def _set_key_addr_place(self, value):
if value:
self._add_hstore('address', 'place', value)
def _set_key_country(self, value):
if value:
self._add_hstore('address', 'country', value)
def _set_key_geometry(self, value):
self.geometry = self.context.osm.parse_geometry(value)
assert self.geometry is not None, "Bad geometry: {}".format(value)
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_delete(self, cursor):
""" Issue a delete for the given OSM object.
"""
cursor.execute('DELETE FROM place WHERE osm_type = %s and osm_id = %s',
(self.columns['osm_type'], self.columns['osm_id']))
def db_insert(self, cursor):
""" Insert the collected data into the database.
"""
if self.columns['osm_type'] == 'N' and self.geometry is None:
pt = self.context.osm.grid_node(self.columns['osm_id'])
if pt is None:
pt = (random.uniform(-180, 180), random.uniform(-90, 90))
self.geometry = "ST_SetSRID(ST_Point(%f, %f), 4326)" % pt
else:
assert self.geometry is not None, "Geometry missing"
query = 'INSERT INTO place ({}, geometry) values({}, {})'.format(
','.join(self.columns.keys()),
','.join(['%s' for x in range(len(self.columns))]),
self.geometry)
cursor.execute(query, list(self.columns.values()))

View File

@@ -1,307 +0,0 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
""" Steps that run queries against the API.
"""
from pathlib import Path
import re
import logging
import asyncio
import xml.etree.ElementTree as ET
from http_responses import GenericResponse, SearchResponse, ReverseResponse, StatusResponse
from check_functions import Bbox, check_for_attributes
from table_compare import NominatimID
LOG = logging.getLogger(__name__)
def make_todo_list(context, result_id):
if result_id is None:
context.execute_steps("then at least 1 result is returned")
return range(len(context.response.result))
context.execute_steps(f"then more than {result_id}results are returned")
return (int(result_id.strip()), )
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 ValueError(f"Unknown operator '{operator}'")
def send_api_query(endpoint, params, fmt, context):
if fmt is not None:
if fmt.strip() == 'debug':
params['debug'] = '1'
else:
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]
return asyncio.run(context.nominatim.api_engine(endpoint, params,
Path(context.nominatim.website_dir.name),
context.nominatim.test_env,
getattr(context, 'http_headers', {})))
@given('the HTTP header')
def add_http_header(context):
if not hasattr(context, 'http_headers'):
context.http_headers = {}
for h in context.table.headings:
context.http_headers[h] = context.table[0][h]
@when(r'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)
context.response = SearchResponse(outp, fmt or 'json', status)
@when(r'sending v1/reverse at (?P<lat>[\d.-]*),(?P<lon>[\d.-]*)(?: with format (?P<fmt>.+))?')
def api_endpoint_v1_reverse(context, lat, lon, fmt):
params = {}
if lat is not None:
params['lat'] = lat
if lon is not None:
params['lon'] = lon
if fmt is None:
fmt = 'jsonv2'
elif fmt == "''":
fmt = None
outp, status = send_api_query('reverse', params, fmt, context)
context.response = ReverseResponse(outp, fmt or 'xml', status)
@when(r'sending v1/reverse N(?P<nodeid>\d+)(?: with format (?P<fmt>.+))?')
def api_endpoint_v1_reverse_from_node(context, nodeid, fmt):
params = {}
params['lon'], params['lat'] = (f'{c:f}' for c in context.osm.grid_node(int(nodeid)))
outp, status = send_api_query('reverse', params, fmt, context)
context.response = ReverseResponse(outp, fmt or 'xml', status)
@when(r'sending (?P<fmt>\S+ )?details query for (?P<query>.*)')
def website_details_request(context, fmt, query):
params = {}
if query[0] in 'NWR':
nid = NominatimID(query)
params['osmtype'] = nid.typ
params['osmid'] = nid.oid
if nid.cls:
params['class'] = nid.cls
else:
params['place_id'] = query
outp, status = send_api_query('details', params, fmt, context)
context.response = GenericResponse(outp, fmt or 'json', status)
@when(r'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)
context.response = SearchResponse(outp, fmt or 'xml', status)
@when(r'sending (?P<fmt>\S+ )?status query')
def website_status_request(context, fmt):
params = {}
outp, status = send_api_query('status', params, fmt, context)
context.response = StatusResponse(outp, fmt or 'text', status)
@step(r'(?P<operator>less than|more than|exactly|at least|at most) '
r'(?P<number>\d+) results? (?:is|are) returned')
def validate_result_number(context, operator, number):
context.execute_steps("Then a HTTP 200 is returned")
numres = len(context.response.result)
assert compare(operator, numres, int(number)), \
f"Bad number of results: expected {operator} {number}, got {numres}."
@then(r'a HTTP (?P<status>\d+) is returned')
def check_http_return_status(context, status):
assert context.response.errorcode == int(status), \
f"Return HTTP status is {context.response.errorcode}."\
f" Full response:\n{context.response.page}"
@then(r'the page contents equals "(?P<text>.+)"')
def check_page_content_equals(context, text):
assert context.response.page == text
@then(r'the result is valid (?P<fmt>\w+)')
def step_impl(context, fmt):
context.execute_steps("Then a HTTP 200 is returned")
if fmt.strip() == 'html':
try:
tree = ET.fromstring(context.response.page)
except Exception as ex:
assert False, f"Could not parse page: {ex}\n{context.response.page}"
assert tree.tag == 'html'
body = tree.find('./body')
assert body is not None
assert body.find('.//script') is None
else:
assert context.response.format == fmt
@then(r'a (?P<fmt>\w+) user error is returned')
def check_page_error(context, fmt):
context.execute_steps("Then a HTTP 400 is returned")
assert context.response.format == fmt
if fmt == 'xml':
assert re.search(r'<error>.+</error>', context.response.page, re.DOTALL) is not None
else:
assert re.search(r'({"error":)', context.response.page, re.DOTALL) is not None
@then('result header contains')
def check_header_attr(context):
context.execute_steps("Then a HTTP 200 is returned")
for line in context.table:
assert line['attr'] in context.response.header, \
f"Field '{line['attr']}' missing in header. " \
f"Full header:\n{context.response.header}"
value = context.response.header[line['attr']]
assert re.fullmatch(line['value'], value) is not None, \
f"Attribute '{line['attr']}': expected: '{line['value']}', got '{value}'"
@then('result header has (?P<neg>not )?attributes (?P<attrs>.*)')
def check_header_no_attr(context, neg, attrs):
check_for_attributes(context.response.header, attrs,
'absent' if neg else 'present')
@then(r'results contain(?: in field (?P<field>.*))?')
def results_contain_in_field(context, field):
context.execute_steps("then at least 1 result is returned")
for line in context.table:
context.response.match_row(line, context=context, field=field)
@then(r'result (?P<lid>\d+ )?has (?P<neg>not )?attributes (?P<attrs>.*)')
def validate_attributes(context, lid, neg, attrs):
for i in make_todo_list(context, lid):
check_for_attributes(context.response.result[i], attrs,
'absent' if neg else 'present')
@then(u'result addresses contain')
def result_addresses_contain(context):
context.execute_steps("then at least 1 result is returned")
for line in context.table:
idx = int(line['ID']) if 'ID' in line.headings else None
for name, value in zip(line.headings, line.cells):
if name != 'ID':
context.response.assert_address_field(idx, name, value)
@then(r'address of result (?P<lid>\d+) has(?P<neg> no)? types (?P<attrs>.*)')
def check_address_has_types(context, lid, neg, attrs):
context.execute_steps(f"then more than {lid} results are returned")
addr_parts = context.response.result[int(lid)]['address']
for attr in attrs.split(','):
if neg:
assert attr not in addr_parts
else:
assert attr in addr_parts
@then(r'address of result (?P<lid>\d+) (?P<complete>is|contains)')
def check_address(context, lid, complete):
context.execute_steps(f"then more than {lid} results are returned")
lid = int(lid)
addr_parts = dict(context.response.result[lid]['address'])
for line in context.table:
context.response.assert_address_field(lid, line['type'], line['value'])
del addr_parts[line['type']]
if complete == 'is':
assert len(addr_parts) == 0, f"Additional address parts found: {addr_parts!s}"
@then(r'result (?P<lid>\d+ )?has bounding box in (?P<coords>[\d,.-]+)')
def check_bounding_box_in_area(context, lid, coords):
expected = Bbox(coords)
for idx in make_todo_list(context, lid):
res = context.response.result[idx]
check_for_attributes(res, 'boundingbox')
context.response.check_row(idx, res['boundingbox'] in expected,
f"Bbox is not contained in {expected}")
@then(r'result (?P<lid>\d+ )?has centroid in (?P<coords>[\d,.-]+)')
def check_centroid_in_area(context, lid, coords):
expected = Bbox(coords)
for idx in make_todo_list(context, lid):
res = context.response.result[idx]
check_for_attributes(res, 'lat,lon')
context.response.check_row(idx, (res['lon'], res['lat']) in expected,
f"Centroid is not inside {expected}")
@then('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, f"Found duplicate for {dup}"
else:
assert has_dupe, "No duplicates found"

View File

@@ -1,464 +0,0 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
from itertools import chain
import psycopg
from psycopg import sql as pysql
from place_inserter import PlaceColumn
from table_compare import NominatimID, DBRow
from nominatim_db.tokenizer import factory as tokenizer_factory
def check_database_integrity(context):
""" Check some generic constraints on the tables.
"""
with context.db.cursor(row_factory=psycopg.rows.tuple_row) as cur:
# place_addressline should not have duplicate (place_id, address_place_id)
cur.execute("""SELECT count(*) FROM
(SELECT place_id, address_place_id, count(*) as c
FROM place_addressline GROUP BY place_id, address_place_id) x
WHERE c > 1""")
assert cur.fetchone()[0] == 0, "Duplicates found in place_addressline"
# word table must not have empty word_tokens
cur.execute("SELECT count(*) FROM word WHERE word_token = ''")
assert cur.fetchone()[0] == 0, "Empty word tokens found in word table"
# GIVEN ##################################
@given("the (?P<named>named )?places")
def add_data_to_place_table(context, named):
""" Add entries into the place table. 'named places' makes sure that
the entries get a random name when none is explicitly given.
"""
with context.db.cursor() as cur:
cur.execute('ALTER TABLE place DISABLE TRIGGER place_before_insert')
for row in context.table:
PlaceColumn(context).add_row(row, named is not None).db_insert(cur)
cur.execute('ALTER TABLE place ENABLE TRIGGER place_before_insert')
@given("the relations")
def add_data_to_planet_relations(context):
""" Add entries into the osm2pgsql relation middle table. This is needed
for tests on data that looks up members.
"""
with context.db.cursor() as cur:
cur.execute("SELECT value FROM osm2pgsql_properties WHERE property = 'db_format'")
row = cur.fetchone()
if row is None or row['value'] == '1':
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 = chain.from_iterable([(h[5:], r[h]) for h in r.headings
if h.startswith("tags+")])
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, list(tags)))
else:
for r in context.table:
if r['members']:
members = []
for m in r['members'].split(','):
mid = NominatimID(m)
members.append({'ref': mid.oid, 'role': mid.cls or '', 'type': mid.typ})
else:
members = []
tags = {h[5:]: r[h] for h in r.headings if h.startswith("tags+")}
cur.execute("""INSERT INTO planet_osm_rels (id, tags, members)
VALUES (%s, %s, %s)""",
(r['id'], psycopg.types.json.Json(tags),
psycopg.types.json.Json(members)))
@given("the ways")
def add_data_to_planet_ways(context):
""" Add entries into the osm2pgsql way middle table. This is necessary for
tests on that that looks up node ids in this table.
"""
with context.db.cursor() as cur:
cur.execute("SELECT value FROM osm2pgsql_properties WHERE property = 'db_format'")
row = cur.fetchone()
json_tags = row is not None and row['value'] != '1'
for r in context.table:
if json_tags:
tags = psycopg.types.json.Json({h[5:]: r[h] for h in r.headings
if h.startswith("tags+")})
else:
tags = list(chain.from_iterable([(h[5:], r[h])
for h in r.headings if h.startswith("tags+")]))
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))
# WHEN ##################################
@when("importing")
def import_and_index_data_from_place_table(context):
""" Import data previously set up in the place table.
"""
context.nominatim.run_nominatim('import', '--continue', 'load-data',
'--index-noanalyse', '-q',
'--offline')
check_database_integrity(context)
# Remove the output of the input, when all was right. Otherwise it will be
# output when there are errors that had nothing to do with the import
# itself.
context.log_capture.buffer.clear()
@when("updating places")
def update_place_table(context):
""" Update the place table with the given data. Also runs all triggers
related to updates and reindexes the new data.
"""
context.nominatim.run_nominatim('refresh', '--functions')
with context.db.cursor() as cur:
for row in context.table:
col = PlaceColumn(context).add_row(row, False)
col.db_delete(cur)
col.db_insert(cur)
cur.execute('SELECT flush_deleted_places()')
context.nominatim.reindex_placex(context.db)
check_database_integrity(context)
# Remove the output of the input, when all was right. Otherwise it will be
# output when there are errors that had nothing to do with the import
# itself.
context.log_capture.buffer.clear()
@when("updating postcodes")
def update_postcodes(context):
""" Rerun the calculation of postcodes.
"""
context.nominatim.run_nominatim('refresh', '--postcodes')
@when("marking for delete (?P<oids>.*)")
def delete_places(context, oids):
""" Remove entries from the place table. Multiple ids may be given
separated by commas. Also runs all triggers
related to updates and reindexes the new data.
"""
context.nominatim.run_nominatim('refresh', '--functions')
with context.db.cursor() as cur:
cur.execute('TRUNCATE place_to_be_deleted')
for oid in oids.split(','):
NominatimID(oid).query_osm_id(cur, 'DELETE FROM place WHERE {}')
cur.execute('SELECT flush_deleted_places()')
context.nominatim.reindex_placex(context.db)
# Remove the output of the input, when all was right. Otherwise it will be
# output when there are errors that had nothing to do with the import
# itself.
context.log_capture.buffer.clear()
# THEN ##################################
@then("(?P<table>placex|place) contains(?P<exact> exactly)?")
def check_place_contents(context, table, exact):
""" Check contents of place/placex tables. Each row represents a table row
and all data must match. Data not present in the expected table, may
be arbitrary. The rows are identified via the 'object' column which must
have an identifier of the form '<NRW><osm id>[:<class>]'. When multiple
rows match (for example because 'class' was left out and there are
multiple entries for the given OSM object) then all must match. All
expected rows are expected to be present with at least one database row.
When 'exactly' is given, there must not be additional rows in the database.
"""
with context.db.cursor() as cur:
expected_content = set()
for row in context.table:
nid = NominatimID(row['object'])
query = """SELECT *, ST_AsText(geometry) as geomtxt,
ST_GeometryType(geometry) as geometrytype """
if table == 'placex':
query += ' ,ST_X(centroid) as cx, ST_Y(centroid) as cy'
query += " FROM %s WHERE {}" % (table, )
nid.query_osm_id(cur, query)
assert cur.rowcount > 0, "No rows found for " + row['object']
for res in cur:
if exact:
expected_content.add((res['osm_type'], res['osm_id'], res['class']))
DBRow(nid, res, context).assert_row(row, ['object'])
if exact:
cur.execute(pysql.SQL('SELECT osm_type, osm_id, class from')
+ pysql.Identifier(table))
actual = set([(r['osm_type'], r['osm_id'], r['class']) for r in cur])
assert expected_content == actual, \
f"Missing entries: {expected_content - actual}\n" \
f"Not expected in table: {actual - expected_content}"
@then("(?P<table>placex|place) has no entry for (?P<oid>.*)")
def check_place_has_entry(context, table, oid):
""" Ensure that no database row for the given object exists. The ID
must be of the form '<NRW><osm id>[:<class>]'.
"""
with context.db.cursor() as cur:
NominatimID(oid).query_osm_id(cur, "SELECT * FROM %s where {}" % table)
assert cur.rowcount == 0, \
"Found {} entries for ID {}".format(cur.rowcount, oid)
@then("search_name contains(?P<exclude> not)?")
def check_search_name_contents(context, exclude):
""" Check contents of place/placex tables. Each row represents a table row
and all data must match. Data not present in the expected table, may
be arbitrary. The rows are identified via the 'object' column which must
have an identifier of the form '<NRW><osm id>[:<class>]'. All
expected rows are expected to be present with at least one database row.
"""
tokenizer = tokenizer_factory.get_tokenizer_for_db(context.nominatim.get_test_config())
with tokenizer.name_analyzer() as analyzer:
with context.db.cursor() as cur:
for row in context.table:
nid = NominatimID(row['object'])
nid.row_by_place_id(cur, 'search_name',
['ST_X(centroid) as cx', 'ST_Y(centroid) as cy'])
assert cur.rowcount > 0, "No rows found for " + row['object']
for res in cur:
db_row = DBRow(nid, res, context)
for name, value in zip(row.headings, row.cells):
if name in ('name_vector', 'nameaddress_vector'):
items = [x.strip() for x in value.split(',')]
tokens = analyzer.get_word_token_info(items)
if not exclude:
assert len(tokens) >= len(items), \
f"No word entry found for {value}. Entries found: {len(tokens)}"
for word, token, wid in tokens:
if exclude:
assert wid not in res[name], \
"Found term for {}/{}: {}".format(nid, name, wid)
else:
assert wid in res[name], \
"Missing term for {}/{}: {}".format(nid, name, wid)
elif name != 'object':
assert db_row.contains(name, value), db_row.assert_msg(name, value)
@then("search_name has no entry for (?P<oid>.*)")
def check_search_name_has_entry(context, oid):
""" Check that there is noentry in the search_name table for the given
objects. IDs are in format '<NRW><osm id>[:<class>]'.
"""
with context.db.cursor() as cur:
NominatimID(oid).row_by_place_id(cur, 'search_name')
assert cur.rowcount == 0, \
"Found {} entries for ID {}".format(cur.rowcount, oid)
@then("location_postcode contains exactly")
def check_location_postcode(context):
""" Check full contents for location_postcode table. Each row represents a table row
and all data must match. Data not present in the expected table, may
be arbitrary. The rows are identified via 'country' and 'postcode' columns.
All rows must be present as excepted and there must not be additional
rows.
"""
with context.db.cursor() as cur:
cur.execute("SELECT *, ST_AsText(geometry) as geomtxt FROM location_postcode")
assert cur.rowcount == len(list(context.table)), \
"Postcode table has {cur.rowcount} rows, expected {len(list(context.table))}."
results = {}
for row in cur:
key = (row['country_code'], row['postcode'])
assert key not in results, "Postcode table has duplicate entry: {}".format(row)
results[key] = DBRow((row['country_code'], row['postcode']), row, context)
for row in context.table:
db_row = results.get((row['country'], row['postcode']))
assert db_row is not None, \
f"Missing row for country '{row['country']}' postcode '{row['postcode']}'."
db_row.assert_row(row, ('country', 'postcode'))
@then("there are(?P<exclude> no)? word tokens for postcodes (?P<postcodes>.*)")
def check_word_table_for_postcodes(context, exclude, postcodes):
""" Check that the tokenizer produces postcode tokens for the given
postcodes. The postcodes are a comma-separated list of postcodes.
Whitespace matters.
"""
nctx = context.nominatim
tokenizer = tokenizer_factory.get_tokenizer_for_db(nctx.get_test_config())
with tokenizer.name_analyzer() as ana:
plist = [ana.normalize_postcode(p) for p in postcodes.split(',')]
plist.sort()
with context.db.cursor() as cur:
cur.execute("SELECT word FROM word WHERE type = 'P' and word = any(%s)",
(plist,))
found = [row['word'] for row in cur]
assert len(found) == len(set(found)), f"Duplicate rows for postcodes: {found}"
if exclude:
assert len(found) == 0, f"Unexpected postcodes: {found}"
else:
assert set(found) == set(plist), \
f"Missing postcodes {set(plist) - set(found)}. Found: {found}"
@then("place_addressline contains")
def check_place_addressline(context):
""" Check the contents of the place_addressline table. Each row represents
a table row and all data must match. Data not present in the expected
table, may be arbitrary. The rows are identified via the 'object' column,
representing the addressee and the 'address' column, representing the
address item.
"""
with context.db.cursor() as cur:
for row in context.table:
nid = NominatimID(row['object'])
pid = nid.get_place_id(cur)
apid = NominatimID(row['address']).get_place_id(cur)
cur.execute(""" SELECT * FROM place_addressline
WHERE place_id = %s AND address_place_id = %s""",
(pid, apid))
assert cur.rowcount > 0, \
f"No rows found for place {row['object']} and address {row['address']}."
for res in cur:
DBRow(nid, res, context).assert_row(row, ('address', 'object'))
@then("place_addressline doesn't contain")
def check_place_addressline_exclude(context):
""" Check that the place_addressline doesn't contain any entries for the
given addressee/address item pairs.
"""
with context.db.cursor() as cur:
for row in context.table:
pid = NominatimID(row['object']).get_place_id(cur)
apid = NominatimID(row['address']).get_place_id(cur, allow_empty=True)
if apid is not None:
cur.execute(""" SELECT * FROM place_addressline
WHERE place_id = %s AND address_place_id = %s""",
(pid, apid))
assert cur.rowcount == 0, \
f"Row found for place {row['object']} and address {row['address']}."
@then(r"W(?P<oid>\d+) expands to(?P<neg> no)? interpolation")
def check_location_property_osmline(context, oid, neg):
""" Check that the given way is present in the interpolation table.
"""
with context.db.cursor() as cur:
cur.execute("""SELECT *, ST_AsText(linegeo) as geomtxt
FROM location_property_osmline
WHERE osm_id = %s AND startnumber IS NOT NULL""",
(oid, ))
if neg:
assert cur.rowcount == 0, "Interpolation found for way {}.".format(oid)
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 " + str(res)
DBRow(oid, res, context).assert_row(row, ('start', 'end'))
assert not todo, f"Unmatched lines in table: {list(context.table[i] for i in todo)}"
@then("location_property_osmline contains(?P<exact> exactly)?")
def check_osmline_contents(context, exact):
""" Check contents of the interpolation table. Each row represents a table row
and all data must match. Data not present in the expected table, may
be arbitrary. The rows are identified via the 'object' column which must
have an identifier of the form '<osm id>[:<startnumber>]'. When multiple
rows match (for example because 'startnumber' was left out and there are
multiple entries for the given OSM object) then all must match. All
expected rows are expected to be present with at least one database row.
When 'exactly' is given, there must not be additional rows in the database.
"""
with context.db.cursor() as cur:
expected_content = set()
for row in context.table:
if ':' in row['object']:
nid, start = row['object'].split(':', 2)
start = int(start)
else:
nid, start = row['object'], None
query = """SELECT *, ST_AsText(linegeo) as geomtxt,
ST_GeometryType(linegeo) as geometrytype
FROM location_property_osmline WHERE osm_id=%s"""
if ':' in row['object']:
query += ' and startnumber = %s'
params = [int(val) for val in row['object'].split(':', 2)]
else:
params = (int(row['object']), )
cur.execute(query, params)
assert cur.rowcount > 0, "No rows found for " + row['object']
for res in cur:
if exact:
expected_content.add((res['osm_id'], res['startnumber']))
DBRow(nid, res, context).assert_row(row, ['object'])
if exact:
cur.execute('SELECT osm_id, startnumber from location_property_osmline')
actual = set([(r['osm_id'], r['startnumber']) for r in cur])
assert expected_content == actual, \
f"Missing entries: {expected_content - actual}\n" \
f"Not expected in table: {actual - expected_content}"

View File

@@ -1,144 +0,0 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2024 by the Nominatim developer community.
# For a full list of authors see the git log.
import tempfile
import random
import os
from pathlib import Path
from nominatim_db.tools.exec_utils import run_osm2pgsql
from nominatim_db.tools.replication import run_osm2pgsql_updates
from geometry_alias import ALIASES
def get_osm2pgsql_options(nominatim_env, fname, append):
return dict(import_file=fname,
osm2pgsql='osm2pgsql',
osm2pgsql_cache=50,
osm2pgsql_style=str(nominatim_env.get_test_config().get_import_style_file()),
osm2pgsql_style_path=nominatim_env.get_test_config().lib_dir.lua,
threads=1,
dsn=nominatim_env.get_libpq_dsn(),
flatnode_file='',
tablespaces=dict(slim_data='', slim_index='',
main_data='', main_index=''),
append=append)
def write_opl_file(opl, grid):
""" Create a temporary OSM file from OPL and return the file name. It is
the responsibility of the caller to delete the file again.
Node with missing coordinates, can retrieve their coordinates from
a supplied grid. Failing that a random coordinate is assigned.
"""
with tempfile.NamedTemporaryFile(suffix='.opl', delete=False) as fd:
for line in opl.splitlines():
if line.startswith('n') and line.find(' x') < 0:
coord = grid.grid_node(int(line[1:].split(' ')[0]))
if coord is None:
coord = (random.uniform(-180, 180), random.uniform(-90, 90))
line += " x%f y%f" % coord
fd.write(line.encode('utf-8'))
fd.write(b'\n')
return fd.name
@given('the lua style file')
def lua_style_file(context):
""" Define a custom style file to use for the import.
"""
style = Path(context.nominatim.website_dir.name) / 'custom.lua'
style.write_text(context.text)
context.nominatim.test_env['NOMINATIM_IMPORT_STYLE'] = str(style)
@given(u'the ([0-9.]+ )?grid(?: with origin (?P<origin>.*))?')
def define_node_grid(context, grid_step, origin):
"""
Define a grid of node positions.
Use a table to define the grid. The nodes must be integer ids. Optionally
you can give the grid distance. The default is 0.00001 degrees.
"""
if grid_step is not None:
grid_step = float(grid_step.strip())
else:
grid_step = 0.00001
if origin:
if ',' in origin:
# TODO coordinate
coords = origin.split(',')
if len(coords) != 2:
raise RuntimeError('Grid origin expects origin with x,y coordinates.')
origin = (float(coords[0]), float(coords[1]))
elif origin in ALIASES:
origin = ALIASES[origin]
else:
raise RuntimeError('Grid origin must be either coordinate or alias.')
else:
origin = (0.0, 0.0)
context.osm.set_grid([context.table.headings] + [list(h) for h in context.table],
grid_step, origin)
@when(u'loading osm data')
def load_osm_file(context):
"""
Load the given data into a freshly created test database using osm2pgsql.
No further indexing is done.
The data is expected as attached text in OPL format.
"""
# create an OSM file and import it
fname = write_opl_file(context.text, context.osm)
try:
run_osm2pgsql(get_osm2pgsql_options(context.nominatim, fname, append=False))
finally:
os.remove(fname)
# 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()
@when(u'updating osm data')
def update_from_osm_file(context):
"""
Update a database previously populated with 'loading osm data'.
Needs to run indexing on the existing data first to yield the correct result.
The data is expected as attached text in OPL format.
"""
context.nominatim.copy_from_place(context.db)
context.nominatim.run_nominatim('index')
context.nominatim.run_nominatim('refresh', '--functions')
# create an OSM file and import it
fname = write_opl_file(context.text, context.osm)
try:
run_osm2pgsql_updates(context.db,
get_osm2pgsql_options(context.nominatim, fname, append=True))
finally:
os.remove(fname)
@when('indexing')
def index_database(context):
"""
Run the Nominatim indexing step. This will process data previously
loaded with 'updating osm data'
"""
context.nominatim.run_nominatim('index')

View File

@@ -1,230 +0,0 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Functions to facilitate accessing and comparing the content of DB tables.
"""
import math
import re
import json
import psycopg
from psycopg import sql as pysql
ID_REGEX = re.compile(r"(?P<typ>[NRW])(?P<oid>\d+)(:(?P<cls>\w+))?")
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>].
"""
def __init__(self, oid):
self.typ = self.oid = self.cls = None
if oid is not None:
m = ID_REGEX.fullmatch(oid)
assert m is not None, \
"ID '{}' not of form <osmtype><osmid>[:<class>]".format(oid)
self.typ = m.group('typ')
self.oid = m.group('oid')
self.cls = m.group('cls')
def __str__(self):
if self.cls is None:
return self.typ + self.oid
return '{self.typ}{self.oid}:{self.cls}'.format(self=self)
def query_osm_id(self, cur, query):
""" Run a query on cursor `cur` using osm ID, type and class. The
`query` string must contain exactly one placeholder '{}' where
the 'where' query should go.
"""
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)
cur.execute(query.format(where), params)
def row_by_place_id(self, cur, table, extra_columns=None):
""" Get a row by place_id from the given table using cursor `cur`.
extra_columns may contain a list additional elements for the select
part of the query.
"""
pid = self.get_place_id(cur)
query = "SELECT {} FROM {} WHERE place_id = %s".format(
','.join(['*'] + (extra_columns or [])), table)
cur.execute(query, (pid, ))
def get_place_id(self, cur, allow_empty=False):
""" Look up the place id for the ID. Throws an assertion if the ID
is not unique.
"""
self.query_osm_id(cur, "SELECT place_id FROM placex WHERE {}")
if cur.rowcount == 0 and allow_empty:
return None
assert cur.rowcount == 1, \
"Place ID {!s} not unique. Found {} entries.".format(self, cur.rowcount)
return cur.fetchone()['place_id']
class DBRow:
""" Represents a row from a database and offers comparison functions.
"""
def __init__(self, nid, db_row, context):
self.nid = nid
self.db_row = db_row
self.context = context
def assert_row(self, row, exclude_columns):
""" Check that all columns of the given behave row are contained
in the database row. Exclude behave rows with the names given
in the `exclude_columns` list.
"""
for name, value in zip(row.headings, row.cells):
if name not in exclude_columns:
assert self.contains(name, value), self.assert_msg(name, value)
def contains(self, name, expected):
""" Check that the DB row contains a column `name` with the given value.
"""
if '+' in name:
column, field = name.split('+', 1)
return self._contains_hstore_value(column, field, expected)
if name == 'geometry':
return self._has_geometry(expected)
if name not in self.db_row:
return False
actual = self.db_row[name]
if expected == '-':
return actual is None
if name == 'name' and ':' not in expected:
return self._compare_column(actual[name], expected)
if 'place_id' in name:
return self._compare_place_id(actual, expected)
if name == 'centroid':
return self._has_centroid(expected)
return self._compare_column(actual, expected)
def _contains_hstore_value(self, column, field, expected):
if column == 'addr':
column = 'address'
if column not in self.db_row:
return False
if expected == '-':
return self.db_row[column] is None or field not in self.db_row[column]
if self.db_row[column] is None:
return False
return self._compare_column(self.db_row[column].get(field), expected)
def _compare_column(self, actual, expected):
if isinstance(actual, dict):
return actual == eval('{' + expected + '}')
return str(actual) == expected
def _compare_place_id(self, actual, expected):
if expected == '0':
return actual == 0
with self.context.db.cursor() as cur:
return NominatimID(expected).get_place_id(cur) == actual
def _has_centroid(self, expected):
if expected == 'in geometry':
with self.context.db.cursor(row_factory=psycopg.rows.tuple_row) as cur:
cur.execute("""SELECT ST_Within(ST_SetSRID(ST_Point(%(cx)s, %(cy)s), 4326),
ST_SetSRID(%(geomtxt)s::geometry, 4326))""",
(self.db_row))
return cur.fetchone()[0]
if ' ' in expected:
x, y = expected.split(' ')
else:
x, y = self.context.osm.grid_node(int(expected))
return math.isclose(float(x), self.db_row['cx']) \
and math.isclose(float(y), self.db_row['cy'])
def _has_geometry(self, expected):
geom = self.context.osm.parse_geometry(expected)
with self.context.db.cursor(row_factory=psycopg.rows.tuple_row) as cur:
cur.execute(pysql.SQL("""
SELECT ST_Equals(ST_SnapToGrid({}, 0.00001, 0.00001),
ST_SnapToGrid(ST_SetSRID({}::geometry, 4326), 0.00001, 0.00001))""")
.format(pysql.SQL(geom),
pysql.Literal(self.db_row['geomtxt'])))
return cur.fetchone()[0]
def assert_msg(self, name, value):
""" Return a string with an informative message for a failed compare.
"""
msg = "\nBad column '{}' in row '{!s}'.".format(name, self.nid)
actual = self._get_actual(name)
if actual is not None:
msg += " Expected: {}, got: {}.".format(value, actual)
else:
msg += " No such column."
return msg + "\nFull DB row: {}".format(json.dumps(dict(self.db_row),
indent=4, default=str))
def _get_actual(self, name):
if '+' in name:
column, field = name.split('+', 1)
if column == 'addr':
column = 'address'
return (self.db_row.get(column) or {}).get(field)
if name == 'geometry':
return self.db_row['geomtxt']
if name not in self.db_row:
return None
if name == 'centroid':
return "POINT({cx} {cy})".format(**self.db_row)
actual = self.db_row[name]
if 'place_id' in name:
if actual is None:
return '<null>'
if actual == 0:
return "place ID 0"
with self.context.db.cursor(row_factory=psycopg.rows.tuple_row) as cur:
cur.execute("""SELECT osm_type, osm_id, class
FROM placex WHERE place_id = %s""",
(actual, ))
if cur.rowcount == 1:
return "{0[0]}{0[1]}:{0[2]}".format(cur.fetchone())
return "[place ID {} not found]".format(actual)
return actual