Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Nominatim’s old place model centered on one class/type pair, even when an OpenStreetMap object carried several useful tags. In his GSoC retrospective, contributor Rupam Golui describes replacing that limitation with hierarchical category paths attached to place records—while retaining the familiar fields for compatibility and API presentation. The change reached far beyond a database column: it affected imports, indexes, migrations, search queries, API filters, SQLite support and tests.
This is Golui’s account of the design decisions and trade-offs, including measurements from his own test environment. They are project results, not independently reproduced benchmarks or a statement about what is currently available in any particular Nominatim release. Read Golui’s original retrospective.
As an Amazon Associate I earn from qualifying purchases.
Why Nominatim needed a different category model
Nominatim geocodes OpenStreetMap data. As Golui explains, an OSM object can carry multiple main tags, but the earlier Nominatim model represented a place through a single class/type pair. That mismatch could split one multi-tag object—such as a hotel that also contains a restaurant—into multiple database rows. Administrative boundaries also needed special handling, and the model did not offer a useful hierarchical category filter.
The project kept class and type for API presentation and compatibility, but made categories the basis for classification and filtering. A category is stored as a dot-separated path: for example, osm.amenity.restaurant. Because paths have a hierarchy, a filter can target a parent such as osm.amenity and include its descendants.
#1 Best Overall
How the category paths were represented
Golui’s implementation used PostgreSQL’s ltree extension and an array of paths. He says he also considered representing categories as TEXT[] and expanding prefixes, but found the path type a better fit after testing alternatives on real Nominatim data.
ltree labels have character restrictions in the supported PostgreSQL versions. The import code therefore normalizes some tag values: for instance, shop=car-repair becomes osm.shop.car_repair. If a value cannot be represented, the category uses yes; the original value remains available through other fields. This is a compatibility adaptation, not a claim that the original OSM tag itself changed.
Why one place row mattered to the import and database
The import pipeline gathered categories before inserting a place. That let it create one row for a multi-tag place rather than inserting one row per main tag and merging the rows later. A stable ordering also determined which legacy class/type pair to expose, making updates deterministic.
Rank #2
- Book - 1, 000 books to read before you die: a life-changing list (1000 before you die)
- Language: english
- Binding: hardcover
The category model touched multiple layers of Nominatim: import code, PostgreSQL schema, SQL ranking and trigger logic, search indexes, migrations, search paths, API parameters, SQLite adaptation and export, documentation, and tests. A mentor named Sarah asked, “Why create several rows and merge them later?” Golui describes that question as one of the prompts that helped direct the design.
What the migration involved
Golui designed the migration to work on existing databases as well as fresh imports. His final sequence added the category column, disabled the relevant trigger, backfilled categories, built indexes, re-enabled the trigger, and analyzed affected tables.
The times below are Golui’s measurements from migration experiments on his planet database in 2026. They describe that particular setup, not a general estimate for another database.
| Migration approach | Reported time |
|---|---|
| Indexes created before the bulk update, with triggers enabled | About 63 minutes |
| Backfill before index creation | About 47 minutes |
| Temporary-table approach | About 1 hour 40 minutes |
| Final described sequence | About 42 minutes |
Search speed and storage: a trade-off, not a universal win
The project replaced POI and near-search paths that used many place_classtype_* tables with category filtering on placex. That simplified the table layout, but the first categories-only query path could build a bitmap for a very large set of matching places before applying the spatial filter. Golui gives about 1.8 million matching restaurant rows as an example of the scale behind that problem.
Free tools Windows power users keep installed
One-click scans. No signup required.
The figures below are comparisons reported by Golui in 2026. The categories-only path with the old index was substantially slower in his examples; combining a GiST index on centroid and categories with centroid-based filtering narrowed the gap. These timings depend on the author’s data and test conditions and should not be read as general latency expectations.
| Query | Master | Category path, old index | Combined index |
|---|---|---|---|
| POI | About 0.69 ms | About 106.5 ms | About 1.28 ms |
| Near search | About 22.6 ms | About 510 ms | About 75 ms |
In another example, Golui reports an old specialized POI path at about 8 ms, compared with about 655 ms warm and 2,617 ms cold for the first new path. He notes that some queries remained slower than with specialized tables even after the index improvement. In exchange, his estimate was that the change removed 428 tables and about 8.2 GB of separate table and index storage.
Rank #4
How the search API’s category filters work
The project added include and exclude category parameters to /search. The examples below show the intended syntax and distinctions in grouping; Golui’s article provides the accompanying examples for selecting descendants, combining categories, and excluding fast food.
include=osm.amenityselects that category and its descendants, allowing a request for anything underosm.amenity.- Comma-separated values within one parameter and repeated parameters have different AND/OR behavior. Do not treat the two forms as interchangeable when constructing a filter.
- Exclusion follows the inverse grouping logic, so the way exclusions are combined also depends on whether values are comma-separated or repeated.
For example, a reader looking for restaurants in Berlin while excluding osm.amenity.fast_food should follow the exact include/exclude form shown in the original API examples, rather than infer grouping from the parameter names alone. Data sources without categories, including postcodes and interpolations, cannot satisfy an include filter.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsWhat testing revealed—and what it did not
For a full-planet geocoder-tester comparison, Golui reports the same counts on master and PR #4146: 7,919 failed, 11,113 passed, and 3,264 skipped. These are the author’s reported test results, not an independently audited comparison.
He also recounts two debugging traps. An apparent speedup in an earlier comparison came from cache order. Later, airport regressions were initially attributed to the category change, but replication catch-up had left about 4.5 million rows at indexed_status = 2; because indexing was incomplete, those rows were unsearchable. The episode illustrates why test state and data readiness matter when interpreting a performance or result change.
What the project says about changing a mature system
Golui says the project was complete according to its scope plan and did not require a follow-up task to use the feature. He describes richer categories—for example, cuisine.italian or access.wheelchair.yes—as possible future expansion if clearer use cases emerge. That is a direction he identifies, not confirmation of current release availability.
His broader lesson is that a data-model change in a production-oriented geocoder cannot stop at the schema. It has to be followed through application code, database functions, query indexes, migration behavior, API paths, SQLite compatibility and tests. Golui writes: “The technical result is a category system, but the more useful outcome for me was learning how to make a cross-cutting change in a production-oriented open-source codebase.”
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




