Sarah Hoffmann [Tue, 20 Oct 2026 08:18:35 +0000 (10:18 +0200)]
Add support for addr:substreet and addr:parentstreet (#4226)
This adds support for the [special addressing schema in the
UK](https://wiki.openstreetmap.org/wiki/Addresses_in_the_United_Kingdom)
which may consist of two streets or a place and a street.
The implementation follows what is described in the wiki with support
for `addr:street`+ `addr:parentstreet` and `addr:substreet` +
`addr:parentstreet`. Also possible are combinations of `addr:place` +
`addr:parentstreet` and `addr:housename` + `addr:street` (although here
`addr:unit` support is missing for completeness).
Not supported is the combination of `addr:substreet` + `addr:street`
which is in relatively wide usage. It ends up interpreting the
`addr:substreet` as a suburb. The result is that the combination can
still be found but will be displayed wrongly.
Internally, this is solved as follows:
* addr:substreet is aliased to addr:place in the osm2pgsql import
configuration
* address ranks for results are slightly changed:
* addr:place parents are assigned rank 28 (was 18-25 before)
* addr:street parents are always assigned 27 (was 26-27 before)
* addr:parentstreet is inserted at rank 26, when it exists
Standard output formats should be largely uneffected except roads
showing up at rank 27 instead of 26. However, if you have implemented
custom output formatters, you might want to review your implementation.
Sarah Hoffmann [Tue, 22 Sep 2026 11:59:19 +0000 (13:59 +0200)]
add support for addr:substreet and addr:parentstreet
addr:substreet is aliased to addr:place and output ranking slightly
changed: addr:place is 28, addr:street always 27, while addr:parentstreet
is inserted at 26. This might or might not break some custom
formatter.
Sarah Hoffmann [Sun, 18 Oct 2026 09:12:15 +0000 (11:12 +0200)]
fix joining of words in structured postcode phrases (#4224)
## Summary
Fixes #4223.
Adds the missing parentheses in the `PHRASE_POSTCODE` join in
`PostcodeParser.parse()`, so words after a space or dash are kept. Also
adds a parser test for a postcode phrase with a space, which fails on
master and passes with the fix.
Sarah Hoffmann [Tue, 13 Oct 2026 17:28:48 +0000 (19:28 +0200)]
Tighten down AI usage policy (#4220)
As announced in #4207, I'm going to take a break from reviewing
AI-generated PRs. This is not a judgement on the overall quality of such
contributions. A good part of them is actually quite usable. The problem
is that it takes a huge amount of time to find out if it is a good or
bad PR. They all look the same on the surface. I'm happy to keep working
with AI-generated content as part of contracting work and when submitted
from long-time contributors, but I'm out of spare time to do this stuff
for drive-by contributions.
The PR also removes the requirement to show a prove of testing. That's
just become useless when AI can easily make up a text section that looks
like a proof.
Sarah Hoffmann [Tue, 13 Oct 2026 07:53:10 +0000 (09:53 +0200)]
add categories for all fallback tags, drop artificial place/house category (#4203)
Fixes #4182.
## Summary
fallback tags such as building and landuse now always get a category,
and all of them are retained if there are more than one. before, only
one (whichever `pairs()` returned last) survived, and only if the object
had a name or address. class/type selection is the same as before.
address only objects still have class/type `place`/`house` but no longer
have `osm.place.house` as a category. `categories` is NULL for those
rows - an empty array would make `is_rankable_place()` drop them. the
backfill in `placex_update()` skips place/house so it doesn't return on
re-index.
adjusted the fallback scenarios in `osm2pgsql/import/tags.feature` and
the two address-only node scenarios in `osm2pgsql/update/tags.feature`
to match.
ran lint, the unit tests and the bdd suites locally, all passed (with
flying colors).
on a full import of the test pbf, all 81 place/house rows in placex now
have NULL categories and none carry osm.place.house.
CI is red because of the sqlalchemy 2.1 thing that #4202 mentioned.
## AI usage
Used Ai for the testing purposes and for extending the existing tests
for this case.
## Summary
While debugging https://github.com/osm-search/Nominatim/issues/4205 with
a local database (added the gate as entrance locally) I discovered
"nominatim lookup" with "--entrances" didn't print the entrance data.
For the examples I'm using [an
entrance](https://www.openstreetmap.org/node/3144704341) of [a swimming
center](https://www.openstreetmap.org/way/309147947).
`nominatim search --query "Piscinas do Sport Lisboa e Benfica" --limit 1
--entrances`
- before: `{"name": "Piscinas do Sport Lisboa e Benfica", "entrances":
[{"osm_id": 3144704341, "type": "yes"}] }`
- after: `{"name": "Piscinas do Sport Lisboa e Benfica", "entrances":
[{"osm_id": 3144704341, "type": "yes"}] }`
`nominatim reverse --lat 38.751207 --lon -9.182630 --entrances`
- before: `{"name": "Piscinas do Sport Lisboa e Benfica", "entrances":
null}`
- after: `{"name": "Piscinas do Sport Lisboa e Benfica", "entrances":
[{"osm_id": 3144704341, "type": "yes"}] }`
`nominatim lookup --id W309147947 --entrances`
- before: `{"name": "Piscinas do Sport Lisboa e Benfica", "entrances":
null}`
- after: `{"name": "Piscinas do Sport Lisboa e Benfica", "entrances":
[{"osm_id": 3144704341, "type": "yes"}] }`
`nominatim details --way 309147947 --entrances`
- before: `{"name": "Piscinas do Sport Lisboa e Benfica"}` //
"entrances" key not in response
- after:`{"name": "Piscinas do Sport Lisboa e Benfica", "entrances":
[{"osm_id": 3144704341, "type": "yes"}]}`
## AI usage
Used for both code and test. Tests iterated several times trying to make
it more readable. All commands in this PR tested manually on two
servers.
Sarah Hoffmann [Tue, 13 Oct 2026 07:28:32 +0000 (09:28 +0200)]
Escape error message text (#4210)
## Summary
I got a HTTP 500 error with
https://nominatim.openstreetmap.org/search?q=berlin&format=xml&include=%3C1
but it turns out it was my app. The Nominatim API returns HTTP 400 with
broken XML which couldn't be parsed.
```
XML Parsing Error: not well-formed
Line Number 4, Column 50
```
Best to escape all error message text.
## AI usage
I did the XML format handling, documentation, manual tests, PR. LLM
added JSON and extended the test.
Sarah Hoffmann [Mon, 12 Oct 2026 08:00:36 +0000 (10:00 +0200)]
Documentation: point to helper script cutting down TIGER data (#4212)
## Summary
Nominatim TIGER import processes every input row and looks for nearby
streets even if the region doesn't have OSM data imported. Importing a
small region of OSM data, then TIGER data can take hours. Instead of
adding more logic/parameters/thing-that-need tests I created a helper
script to cut down the CSV files.
## AI usage
Script mostly LLM generated. Years ago I wrote two versions of the TIGER
data extract in the repo so I understand the input and output data.
Tested with extracts.
Sarah Hoffmann [Mon, 12 Oct 2026 07:59:00 +0000 (09:59 +0200)]
Various updates to CI and adaptions for newer dependency versions (#4218)
This finally fixes all the latest CI fails:
* type adaptions for SQLAlchemy 2.1 (including reallowing this version)
* type adaptions for urllib
* replacement of SQLAlchemy's `await_` (see #4202)
* some general cleanup of the CI scripts including moving linting into
its own task so it will fail quickly when it fails
Secondary main tags that lose the class/type selection are now kept in
extratags when extra tags are collected. Update the affected BDD tests
to expect the secondary main tags in the extratags column:
* main_with_extra: the losing bridge/highway main tag now shows up in
extratags.
* simple.feature: split the extratags style into its own scenario
because only that style collects the secondary place tag into
extratags.
When an object carries several main tags, Nominatim converts all of
them into categories but only places a single one into class/type.
The remaining main tags were dropped entirely, so they were no longer
accessible in the results (categories are not returned).
Keep secondary main tags in extratags instead of removing them from
the extratags set. Only the winning main tag that became class/type is
excluded from extratags. Because extratags are only collected when the
Lua style is configured to keep extra tags, the behaviour in the
default import (extra tags disabled) is unchanged.
Adds a BDD test covering secondary main tags being kept in extratags.
justpriyanshu [Fri, 28 Aug 2026 08:58:09 +0000 (08:58 +0000)]
ignore unwanted categories when computing ranks
The checks for unwanted highway and boundary categories were only used
to decide whether a place is rankable at all. The offending categories
remained in the category list, so compute_place_rank() would still pick
up their ranks. Move the checks into drop_unwanted_categories() and
filter the categories before computing ranks.
Itz-Agasta [Fri, 14 Aug 2026 07:24:37 +0000 (12:54 +0530)]
Document categories in the import style documentation
An object with several main tags gets one database entry with a category
per tag now, instead of one entry per tag. Describe how a category is
built from a main tag and which of the main tags ends up as the class and
type of the place. Fallback tags always contribute a category now, they
only lose the race for class and type.
Itz-Agasta [Fri, 14 Aug 2026 07:24:37 +0000 (12:54 +0530)]
Update the database layout documentation
The place table holds a single row per OSM object now, with a categories
column that carries all of its main tags. Document that, the categories
column on placex and the tables that were added since the page was last
touched: place_entrance, placex_entrance, place_associated_street and
location_area_country. The place_classtype tables are gone, so drop them
from the list of auxiliary tables.
Also bring the diagrams back in line with the schema. search_name lost
its search_rank column a while ago and location_postcodes gained is_area,
while its centroid was missing from the diagram because of a typo.
pawank925 [Sun, 16 Aug 2026 18:01:26 +0000 (23:31 +0530)]
Add search terms for POIs on unnamed streets
When a POI is parented to an unnamed street, the street has no entry in
the search_name table. The POI then gets no address terms at all and
can only be found by its name. Use the address objects of the parent
from place_addressline to fill in the missing address terms.
Itz-Agasta [Sat, 15 Aug 2026 15:34:30 +0000 (21:04 +0530)]
Use the categories column for category matching on SQLite as well
CategoryMatch fell back to the class/type columns on SQLite only
because the categories column was unreadable there. With the result
processor fixed, the substring emulation works for all cases, so the
two functions collapse into one. It also matches merged rows, where
the class/type pair only carries the first of several categories.
The placex(class_, type) index stays, it is still used by the
qualifier restriction of the name searches.
Itz-Agasta [Sat, 15 Aug 2026 15:34:22 +0000 (21:04 +0530)]
Only accept underscores in category filters
The conversion from hyphens to underscores in the Lua code exists for
compatibility with OSM tags we have no influence over. The API can be
strict instead.