Skip to content

Add optional -street-housenumber-full import switch - #1099

Closed
sinacek wants to merge 1 commit into
komoot:masterfrom
sinacek:feature/street-housenumber-full
Closed

Add optional -street-housenumber-full import switch#1099
sinacek wants to merge 1 commit into
komoot:masterfrom
sinacek:feature/street-housenumber-full

Conversation

@sinacek

@sinacek sinacek commented Jul 7, 2026

Copy link
Copy Markdown

What this does

Adds an opt-in import switch -street-housenumber-full that indexes street-based
addresses with the full combined house number (e.g. 2531/80) instead of the plain
street number.

Disabled by default — existing behaviour is unchanged for everyone who does not set it.

Why

In countries that split a building's address into a conscription (descriptive) number
and a street (orientation) number, Nominatim exposes the two joined as a combined house
number such as 2531/80 (e.g. Czechia and Slovakia).

Before the multi-address rework in #884 (released in 0.7.0), that combined form was indexed
and therefore searchable. Since then, street addresses are indexed with the plain street
number only, so the combined form is no longer found. For CZ/SK data this is a regression:
users routinely search by the full 2531/80 form.

Why opt-in rather than changing the default

The 0.7.0 rework deliberately assigns the conscription number to the place context and the
street number to the street context. This PR does not revert that decision — it only adds
an alternative, off-by-default indexing for the street number, so:

  • no behaviour changes for anyone unless they explicitly enable the switch;
  • other countries are unaffected — the switch only kicks in for addresses that actually carry
    a separate street number (the conscription/orientation split);
  • it composes cleanly with the current place/street/generic split.

How it works

When enabled, an address that has a separate streetnumber is indexed with its housenumber
value (2531/80) on the street instead of the bare streetnumber. The house number analyzer
(index_housenumber) splits the combined form on the delimiter, so the address is still found
by the street number, the conscription number or the full combined form — no extra
documents needed.

Ordinary addresses without a separate street number are left on the existing generic path
untouched.

The switch is threaded through all three import paths (Nominatim import, Nominatim update,
JSON dump import) and persisted in the database properties, so incremental updates keep the
same behaviour.

Testing

  • Existing testConscriptionAddress kept unchanged (verifies the default is untouched).
  • Added testConscriptionAddressWithFullStreetHousenumber (switch on → 34/50 on the street).
  • Added testFullStreetHousenumberIgnoredWithoutStreetNumber (switch on must not change
    ordinary addresses).
  • Docs updated in docs/usage.md.
  • ./gradlew build passes.

AI assistance disclosure

Per the contribution guidelines: this PR was prepared with AI assistance. The implementation,
the tests and this PR description were drafted with an AI coding assistant and then reviewed and
verified by me. All AI-assisted parts are the code and text of this PR in full.

Verification on a live installation

Verified end-to-end on a running photon server (embedded OpenSearch), not only in unit tests.
A small JSON dump with one Czech conscription address was imported twice — once with the
default settings and once with -street-housenumber-full — and the running server was queried
over HTTP.

Test address (address part of the dump): street=Kaprova, city=Praha, conscriptionnumber=2531, streetnumber=80, housenumber=2531/80.

java -jar photon.jar import -import-file cz.jsonl -data-dir default   -languages en
java -jar photon.jar import -import-file cz.jsonl -data-dir with-full -languages en -street-housenumber-full
# (each then started with: java -jar photon.jar serve -data-dir <dir>)
query default (switch off) -street-housenumber-full
Kaprova 2531/80 returns house number 80 returns 2531/80
Kaprova 80 returns 80 returns 2531/80
Kaprova 2531 (conscription no.) no result returns 2531/80

So with the default the conscription number 2531 is not searchable at all and the combined
form is never returned; with the switch enabled the address is found by the street number, the
conscription number or the full combined form, and the canonical 2531/80 is returned. The
combined value is indexed once — the house number analyzer splits it, so no duplicate documents
are created.

DatabaseProperties after import confirms the persisted setting: streetHousenumberFull=true
(and false for the default import), so incremental updates keep the same behaviour.

Related

@sinacek
sinacek force-pushed the feature/street-housenumber-full branch from 45011af to 8d07f27 Compare July 7, 2026 12:04
In countries that split a building's address into a conscription
(descriptive) number and a street (orientation) number, Nominatim also
exposes the two joined as a combined house number such as `2531/80`
(e.g. Czechia and Slovakia). Before the multi-address rework in 0.7.0,
that combined form was indexed and searchable; since then street
addresses are indexed with the plain street number only.

This adds an opt-in `-street-housenumber-full` import switch. When
enabled, street addresses that carry a separate street number are
indexed with the full combined house number instead of the plain street
number. The house number analyzer splits the combined form on the
delimiter, so the address is still found by the street number, the
conscription number or the combined form. Ordinary addresses without a
separate street number are unaffected.

Disabled by default, so existing behaviour is unchanged. The switch is
threaded through all three import paths (Nominatim import, Nominatim
update, JSON dump import) and persisted in the database properties so
incremental updates keep the same behaviour.
@sinacek

sinacek commented Jul 14, 2026

Copy link
Copy Markdown
Author

@henrik242 may I ask you for a code review pls?

@mtmail

mtmail commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

It's holiday season at the moment, it might take a couple of weeks.

@sinacek

sinacek commented Jul 15, 2026

Copy link
Copy Markdown
Author

It's holiday season at the moment, it might take a couple of weeks.

Ok, thanks for the reply

@henrik242

henrik242 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

@henrik242 may I ask you for a code review pls?

I think @lonvia is better suited to answer this, since I don't know the full implications. It seems to me that it could be solved without the street-housenumber-full argument, though. Also, Claude's comments are (as usual) too verbose.

@lonvia

lonvia commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

There really is no need for a switch here. To my knowledge, addr:streetnumber is only in use in the Czech Republic and Slovakia. So lets simply change the code so it works for those two countries.

Just to clarify. say you have a full tagging like:

addr:conscriptionnumber=100
addr:streetnumber=9
addr:housenumber=100/9
addr:place=mycity
addr:street=mystreet

What combinations of number and street/place would you expect a user to search for here? And what is the street-based and place-based address, you would expect to be displayed?

@barasoukup

barasoukup commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

There really is no need for a switch here. To my knowledge, addr:streetnumber is only in use in the Czech Republic and Slovakia. So lets simply change the code so it works for those two countries.

Just to clarify. say you have a full tagging like:

addr:conscriptionnumber=100
addr:streetnumber=9
addr:housenumber=100/9
addr:place=mycity
addr:street=mystreet

What combinations of number and street/place would you expect a user to search for here? And what is the street-based and place-based address, you would expect to be displayed?

Thanks for clarifying.

For the Czech Republic, I would expect the following searches to work for this example:

addr:conscriptionnumber=100
addr:streetnumber=9
addr:housenumber=100/9
addr:place=mycity
addr:street=mystreet
  • mystreet 9
  • mystreet 100
  • mystreet 100/9
  • mycity 100

The reason is that all three house number variants (9, 100, and 100/9) are used in practice. Some users know only the orientation number (9), some only the conscription number (100), and many sources display the combined form (100/9).

However, once a street is present, I would not expect users to search for mycity 100/9 or mycity 9. For place-based searches, the common form would be mycity 100.

For display, I would expect:

  • street-based: Mystreet 100/9
  • place-based: Mycity 100/9

So from my perspective, all three number representations should remain searchable when a street address exists, because they are all used by end users and external address datasets.

Previously, Photon supported searches for both mystreet 100 and mystreet 9. Recent changes altered this behavior, which has become an issue for our Czech use case, as both numbering schemes are commonly used and users may search using either of them.

@lonvia

lonvia commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Okay, so basically, it's better to completely ignore addr:streetnumber. All that is needed are these cases:

  • if addr:street is present: addr:housenumber + addr:street
  • if addr:place is present: if addr:conscriptionnumber is present: addr:conscriptionnumber + addr:place else if addr:street is not present: addr:housenumber + addr:place

The display for place-based addresses would come out as myplace 100, i.e. without the streetnumber. This would be preferable in this case because Photon will make all parts of the displayed name searchable, so that myplace 100/9 would give a lot of false positive results against the street number.

@barasoukup

Copy link
Copy Markdown
Contributor

Agreed — and it turns out two of the three cases are already implemented, so this needs almost no new code:

  • addPlaceAddress(base, address, "conscriptionnumber") covers the second case.
  • addGenericAddress covers the third: it only runs when nothing else produced a document, and it already checks !address.containsKey("street") before using addr:place.

So the whole change is one argument in the PhotonDocAddressSet constructor:

         addPlaceAddress(base, address, "conscriptionnumber");
-        addStreetAddress(base, address, "streetnumber");
+        addStreetAddress(base, address, "housenumber");

plus updating testConscriptionAddress to expect 34/50 on the street. And no country condition is needed, as you say: wherever there is no separate street number, addr:housenumber is already the number being indexed, so the change is effectively CZ/SK-only on its own.

I have reopened #1098 with exactly that, so the switch and all the plumbing here can go. The remaining tests are unaffected, since addStreetAddress still requires addr:street to be present — testSimpleHousenumber, testPlaceAndStreetAddress, testIrrelevantAddressParts and testBlockAddress all produce the same result as before.

One case that is not test-covered and does change behaviour: block_number + street + housenumber together. Today that falls through to addGenericAddress, where the block number deliberately replaces the street ("we assume that addr:street is a tagging error"). With housenumber on the street address, addStreetAddress produces a document first, docs is no longer empty and that rule is skipped. testBlockAddress does not catch it because it has no street key. Happy to add a guard if that combination occurs in real data.

(Drafted with AI assistance and reviewed by me, per the contribution guidelines.)

@sinacek

sinacek commented Jul 28, 2026

Copy link
Copy Markdown
Author

Solved in #1098

@sinacek sinacek closed this Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants