<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://nominatim.org/feed.xml" rel="self" type="application/atom+xml" /><link href="https://nominatim.org/" rel="alternate" type="text/html" /><updated>2026-09-01T18:09:28+02:00</updated><id>https://nominatim.org/feed.xml</id><title type="html">Nominatim</title><subtitle>Open source geocoding with OpenStreetMap data</subtitle><entry><title type="html">GSoC 2026: Giving Nominatim a Category Model</title><link href="https://nominatim.org/2026/09/01/gsoc-2026-nominatim-categories.html" rel="alternate" type="text/html" title="GSoC 2026: Giving Nominatim a Category Model" /><published>2026-09-01T00:00:00+02:00</published><updated>2026-09-01T18:08:46+02:00</updated><id>https://nominatim.org/2026/09/01/gsoc-2026-nominatim-categories</id><content type="html" xml:base="https://nominatim.org/2026/09/01/gsoc-2026-nominatim-categories.html"><![CDATA[<p>Oh hey! I’m Agasta… I believe you don’t know me, so here’s my <a href="https://www.openstreetmap.org/user/Agasta07/diary/408790">intro</a>. This summer I was <a href="https://wiki.openstreetmap.org/wiki/Google_Summer_of_Code/2026/Accepted_projects">selected for GSoC</a> to work on Nominatim with Sarah and Marc.</p>

<p>For anyone unfamiliar: Nominatim is a geocoder that uses OpenStreetMap data to turn place names and addresses into coordinates. Every place in OSM gets tagged with a key/value pair like <code class="language-plaintext highlighter-rouge">amenity=restaurant</code> or <code class="language-plaintext highlighter-rouge">tourism=hotel</code>, which Nominatim stores internally as a <code class="language-plaintext highlighter-rouge">class</code>/<code class="language-plaintext highlighter-rouge">type</code> combination. That’s the system this project changed.</p>

<p>At the start of GSoC, I wanted to give Nominatim a proper category model. By the end of the coding period, I had changed the import pipeline, the PostgreSQL schema, ranking and trigger logic, the search indexes, the migration path, the API, the SQLite adaptor, and quite a few tests.</p>

<p>That sounds nicely planned when written as one sentence. It did not feel that way while I was doing it.</p>

<p>The project started with a fairly clear problem: Nominatim only allowed one <code class="language-plaintext highlighter-rouge">class</code>/<code class="language-plaintext highlighter-rouge">type</code> pair per place. That works for a simple object, but it becomes awkward as soon as one object has multiple main tags. A hotel that also contains a restaurant could become two database rows. Administrative boundaries needed special handling through <code class="language-plaintext highlighter-rouge">admin_level</code>, and there was no useful way to express hierarchical filters such as “anything under <code class="language-plaintext highlighter-rouge">osm.amenity</code>”.</p>

<p><img src="/img/2608-gsoc-intro.png" alt="One object, more than one category" /></p>

<p>My <a href="https://www.openstreetmap.org/user/Agasta07/diary/409035">midterm post</a> covered the first half of the implementation. This is the final part of that story. If you don’t know Nominatim’s database schema, that is fine. I will explain the pieces that matter as they come up.</p>

<h2 id="the-project-became-a-data-model-change">the project became a data-model change</h2>

<p>The original model looked roughly like this:</p>

<p><img src="/img/2608-gsoc-old-model.png" alt="Original class/type model" /></p>

<p>The category model keeps both identities on one row:</p>

<p><img src="/img/2608-gsoc-category-model.png" alt="Category model with ltree paths" /></p>

<p>The existing <code class="language-plaintext highlighter-rouge">class</code> and <code class="language-plaintext highlighter-rouge">type</code> columns did not disappear. They are still useful in API responses and for compatibility with existing consumers. Their role changed: categories became the source for filtering and classification logic, while <code class="language-plaintext highlighter-rouge">class</code> and <code class="language-plaintext highlighter-rouge">type</code> remained the familiar presentation fields.</p>

<p>The main storage choice was <a href="https://www.postgresql.org/docs/current/ltree.html">PostgreSQL’s <code class="language-plaintext highlighter-rouge">ltree</code> extension</a>. A category is a dot-separated path, so PostgreSQL can understand that <code class="language-plaintext highlighter-rouge">osm.amenity.restaurant</code> is below <code class="language-plaintext highlighter-rouge">osm.amenity</code> without making the importer store every prefix explicitly.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Match all descendants of osm.amenity</span>
<span class="k">WHERE</span> <span class="n">categories</span> <span class="o">&lt;@</span> <span class="s1">'osm.amenity'</span><span class="p">::</span><span class="n">ltree</span>

<span class="c1">-- Match one exact category</span>
<span class="k">WHERE</span> <span class="s1">'osm.amenity.restaurant'</span><span class="p">::</span><span class="n">ltree</span> <span class="o">=</span> <span class="k">ANY</span><span class="p">(</span><span class="n">categories</span><span class="p">)</span>
</code></pre></div></div>

<p>I had considered a <code class="language-plaintext highlighter-rouge">TEXT[]</code> column with prefix expansion and a GIN index. That approach would have avoided the extension dependency, but it would also have moved hierarchy handling into application code and stored more data. After testing the alternatives on real Nominatim data, <code class="language-plaintext highlighter-rouge">ltree[]</code> was the better fit.</p>

<p>There was one compatibility detail that mattered immediately. Nominatim supports PostgreSQL versions where <code class="language-plaintext highlighter-rouge">ltree</code> labels cannot contain all the characters that can appear in OSM tag values. The importer therefore normalizes labels, replacing hyphens with <code class="language-plaintext highlighter-rouge">_</code> and falling back to <code class="language-plaintext highlighter-rouge">yes</code> for values that cannot be represented. The original value remains available through the normal class/type and extratags data.</p>

<p>That means a value such as:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>shop=car-repair
</code></pre></div></div>

<p>becomes a category that can be stored safely across the supported PostgreSQL versions:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>osm.shop.car_repair
</code></pre></div></div>

<h2 id="pr-4106-generating-one-row-instead-of-merging-rows-later">PR #4106: generating one row instead of merging rows later</h2>

<p>The first major implementation landed in <a href="https://github.com/osm-search/Nominatim/pull/4106">PR #4106</a>. It added the <code class="language-plaintext highlighter-rouge">categories ltree[]</code> column to <code class="language-plaintext highlighter-rouge">place</code> and <code class="language-plaintext highlighter-rouge">placex</code>, generated categories in the Lua import code, updated the SQL ranking and trigger functions, and added migration support.</p>

<p>One of the most useful review comments came from Sarah. My first implementation still produced one row per main tag and merged the rows afterwards. That model could work, but it created several rows only to immediately collapse them again.</p>

<p>So I moved the merge into <code class="language-plaintext highlighter-rouge">process_tags()</code>. The importer now collects the categories first and writes one row:</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="n">categories</span> <span class="o">=</span> <span class="p">{}</span>

<span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">tag</span> <span class="k">in</span> <span class="nb">ipairs</span><span class="p">(</span><span class="n">main_tags</span><span class="p">)</span> <span class="k">do</span>
    <span class="nb">table.insert</span><span class="p">(</span><span class="n">categories</span><span class="p">,</span> <span class="n">get_category</span><span class="p">(</span><span class="n">tag</span><span class="p">.</span><span class="n">key</span><span class="p">,</span> <span class="n">tag</span><span class="p">.</span><span class="n">value</span><span class="p">))</span>
<span class="k">end</span>

<span class="n">insert</span> <span class="p">{</span>
    <span class="n">class</span> <span class="o">=</span> <span class="n">selected_class</span><span class="p">,</span>
    <span class="nb">type</span> <span class="o">=</span> <span class="n">selected_type</span><span class="p">,</span>
    <span class="n">categories</span> <span class="o">=</span> <span class="n">categories</span><span class="p">,</span>
    <span class="n">extratags</span> <span class="o">=</span> <span class="n">extratags</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">class</code>/<code class="language-plaintext highlighter-rouge">type</code> winner is selected deterministically. The current rule is deliberately boring: use a stable ordering so that the same set of tags always produces the same legacy value. That stability matters during updates. If the winner changed randomly, an update could look like a different place to downstream logic even when the OSM tags had not changed.</p>

<p>Ranking was a bigger part of this PR than I expected. Nominatim calculates <code class="language-plaintext highlighter-rouge">search_rank</code> and <code class="language-plaintext highlighter-rouge">address_rank</code> from the place classification. Once one row can carry several categories, ranking has to inspect all of them and choose the best applicable result. The SQL functions that used to check conditions such as this:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="o">=</span> <span class="s1">'boundary'</span> <span class="k">AND</span> <span class="k">type</span> <span class="o">=</span> <span class="s1">'administrative'</span>
</code></pre></div></div>

<p>now use category paths instead:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">categories</span> <span class="o">&lt;@</span> <span class="s1">'osm.boundary.administrative'</span><span class="p">::</span><span class="n">ltree</span>
</code></pre></div></div>

<p>The same idea had to be applied to trigger code and the indexer. Yk I realized changing a db column in a mature system is rarely a local schema task. Every place that quietly depended on the old representation has to be found.</p>

<p><img src="/img/2608-gsoc-blast-radius.png" alt="Cross-cutting category data flow" /></p>

<h2 id="migrating-a-planet-database-without-starting-over">migrating a planet database without starting over</h2>

<p>Fresh imports were straightforward once the schema and Lua code were in place. Existing installations were harder because <code class="language-plaintext highlighter-rouge">placex</code> is large enough that “just backfill everything” is a real operational decision.</p>

<p>The migration initially used lazy backfilling. When an existing row was touched, its category could be derived from the old <code class="language-plaintext highlighter-rouge">class</code> and <code class="language-plaintext highlighter-rouge">type</code> values. A smaller proactive backfill was still needed for places used as linking targets, especially higher-level address objects.</p>

<p>I tested the migration several times on a planet database. The first version created indexes before the bulk update and took about 63 minutes for 22,221,508 rows. Disabling the update trigger during the backfill and creating the indexes afterwards reduced that to about 47 minutes.</p>

<p>The temporary-table experiment was worse:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>indexes first, triggers enabled       ~63 min
backfill first, indexes afterwards   ~47 min
temporary table approach              1 h 40 min
</code></pre></div></div>

<p>PostgreSQL’s plan for the temporary-table insert was poor, so the more complicated approach gave us a slower migration. The final process was simpler:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. Add the column
2. Disable the relevant trigger
3. Backfill categories
4. Build the indexes
5. Re-enable the trigger
6. ANALYZE the affected tables
</code></pre></div></div>

<p>The final production-style migration took about 42 minutes on my planet database. The exact time depends on the machine, storage, and the state of the database, but the important result was that an operator did not need to wait three days for a complete reimport just to get the new column.</p>

<p><img src="/img/2608-gsoc-migration.png" alt="Migration timing comparison" /></p>

<h2 id="testing-the-first-half-and-one-testing-mistake">testing the first half, and one testing mistake</h2>

<p>The <a href="https://github.com/geocoders/geocoder-tester">geocoder tester</a> became the main way to check whether the category changes affected ordinary search. On a full planet database, the corrected comparison was:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>master       7919 failed, 11113 passed, 3264 skipped
PR #4146     7919 failed, 11113 passed, 3264 skipped
</code></pre></div></div>

<p>The first run made the PR branch look roughly twice as fast, but that was a cache artifact. When I changed the order and ran the tests repeatedly, whichever branch ran first was slow and the later runs settled around 15 to 16 minutes. The failure counts were the more important signal, and they were identical after the database was correctly indexed. For the larger tests, Marc gave me access to a server with a planet database and the extra postcode and ranking files. That setup used PostgreSQL 18 instead of PostgreSQL 17, so its absolute failure count was not directly comparable to mine.</p>

<p>Before that correction, I had blamed the category migration for a large group of airport regressions. The real problem was an interruption. In <a href="https://github.com/osm-search/Nominatim/pull/4106#issuecomment-4854681707">last PR testing</a> I ran <code class="language-plaintext highlighter-rouge">nominatim replication --catch-up</code>, which left about 4.5 million rows at <code class="language-plaintext highlighter-rouge">indexed_status = 2</code>. Those places were not searchable because indexing had stopped part-way through.</p>

<p>That was my own testing mistake. I had changed the database state, failed to check the indexing status, and then started explaining the results as if the code were the only variable. A benchmark is only useful when the database behind it is understood.</p>

<p><img src="/img/2608-gsoc-testing.png" alt="Testing mistake illustration" /></p>

<h2 id="pr-4146-replacing-the-old-category-search-path">PR #4146: replacing the old category search path</h2>

<p><a href="https://github.com/osm-search/Nominatim/pull/4146">PR #4146</a> moved POI and near searches away from the <code class="language-plaintext highlighter-rouge">place_classtype_*</code> tables and onto the categories column.</p>

<p>Those old tables were materialized per class/type pair. A large installation could have hundreds of them, each with its own centroid index and trigger maintenance. The new query was conceptually much smaller:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Old path</span>
<span class="k">SELECT</span> <span class="n">place_id</span>
<span class="k">FROM</span> <span class="n">place_classtype_amenity_restaurant</span>
<span class="k">WHERE</span> <span class="n">centroid</span> <span class="o">@</span> <span class="n">box</span><span class="p">;</span>

<span class="c1">-- New path</span>
<span class="k">SELECT</span> <span class="n">place_id</span>
<span class="k">FROM</span> <span class="n">placex</span>
<span class="k">WHERE</span> <span class="n">categories</span> <span class="o">&lt;@</span> <span class="s1">'osm.amenity.restaurant'</span><span class="p">::</span><span class="n">ltree</span>
  <span class="k">AND</span> <span class="n">ST_CoveredBy</span><span class="p">(</span><span class="n">centroid</span><span class="p">,</span> <span class="n">box</span><span class="p">);</span>
</code></pre></div></div>

<p>The first version of the new path exposed an index problem. The categories index could find every restaurant, but it knew nothing about the requested area. The geometry index knew about the area, but it was very large. PostgreSQL ended up building large bitmaps and combining them.</p>

<p>On a fully backfilled planet, <code class="language-plaintext highlighter-rouge">osm.amenity.restaurant</code> matched roughly 1.8 million rows. A near search could therefore pay to build a bitmap for almost every restaurant on Earth before applying the spatial filter.</p>

<p><img src="/img/2608-gsoc-index-problem.png" alt="Index problem illustration" /></p>

<p>The first numbers looked bad:</p>

<table>
  <thead>
    <tr>
      <th>Configuration</th>
      <th style="text-align: right">Time</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Old <code class="language-plaintext highlighter-rouge">place_classtype</code> path</td>
      <td style="text-align: right">~8 ms</td>
    </tr>
    <tr>
      <td>Categories + geometry path (warm)</td>
      <td style="text-align: right">~655 ms</td>
    </tr>
    <tr>
      <td>Categories + geometry path (cold)</td>
      <td style="text-align: right">~2617 ms</td>
    </tr>
  </tbody>
</table>

<p>The fix was a combined GiST index and a return to centroid-based filtering:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">INDEX</span> <span class="n">idx_placex_centroid_categories</span> <span class="k">ON</span> <span class="n">placex</span>
<span class="k">USING</span> <span class="n">GIST</span> <span class="p">(</span>
    <span class="n">centroid</span><span class="p">,</span>
    <span class="n">categories</span> <span class="n">gist__ltree_ops</span><span class="p">(</span><span class="n">siglen</span><span class="o">=</span><span class="mi">8</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>

<p>The two changes had to land together. Switching only to <code class="language-plaintext highlighter-rouge">centroid</code> while keeping the old categories-only index was actually worse. With the combined index, the same tests looked much better:</p>

<table>
  <thead>
    <tr>
      <th>Configuration</th>
      <th style="text-align: right">POI</th>
      <th style="text-align: right">Near</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Master / place_classtype tables</td>
      <td style="text-align: right">0.69 ms</td>
      <td style="text-align: right">22.6 ms</td>
    </tr>
    <tr>
      <td>Category path, old index</td>
      <td style="text-align: right">106.5 ms</td>
      <td style="text-align: right">510 ms</td>
    </tr>
    <tr>
      <td>Combined centroid/categories index</td>
      <td style="text-align: right">1.28 ms</td>
      <td style="text-align: right">75 ms</td>
    </tr>
  </tbody>
</table>

<p>The new index was still slower than the specialized old tables in some cases, but it replaced 428 tables and about 8.2 GB of separate table/index storage with one general-purpose index. The design also gave us a single place to extend category filtering later.</p>

<p>So, is it faster? The answer depends on the query. The combined index brings the new POI path close to the old specialized tables and makes near searches much better than the first category-only version. The bigger win is that the database no longer needs hundreds of separately maintained tables.</p>

<p>The index discussion changed my understanding of PostgreSQL GiST indexes. I initially explained the column order using an incomplete argument about which columns could be used by a multicolumn index. The real advantages of the chosen order were the measured index size, build time, and the way the centroid queries behaved. I remember we had some crazy testing and discussions over email about this before the change was finalized.</p>

<h2 id="pr-4163-removing-428-tables">PR #4163: removing 428 tables</h2>

<p>Once searches no longer depended on the old tables, <a href="https://github.com/osm-search/Nominatim/pull/4163">PR #4163</a> removed the <code class="language-plaintext highlighter-rouge">place_classtype_*</code> table creation and maintenance code.</p>

<p>That removed more than a database object. It removed the special-phrase importer code that created those tables, trigger paths that maintained them, SQLite export code that copied them, and the <code class="language-plaintext highlighter-rouge">--min</code> option whose meaning only existed because those tables existed.</p>

<p>This was a satisfying change because the result is easy to explain:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>before: one materialized table per class/type combination
after: one categories column and one indexed search path
</code></pre></div></div>

<p>It also made the architecture easier to reason about. A category is now data on the place, not a collection of side tables that happen to represent the same idea.</p>

<p><img src="/img/2608-gsoc-remove-tables.png" alt="Before/after storage architecture" /></p>

<h2 id="the-api-was-originally-a-stretch-goal">the API was originally a stretch goal</h2>

<p>The project proposal treated API filtering as a stretch goal. Since the database work landed early enough, I added <code class="language-plaintext highlighter-rouge">include</code> and <code class="language-plaintext highlighter-rouge">exclude</code> to <code class="language-plaintext highlighter-rouge">/search</code> in <a href="https://github.com/osm-search/Nominatim/pull/4164">PR #4164</a>. This means users can now ask Nominatim for results under a category, or leave out a category, without knowing how the database stores the place.</p>

<p>Examples:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/search?q=restaurant+berlin&amp;include=osm.amenity.restaurant
/search?q=berlin&amp;include=osm.amenity
/search?q=hilton&amp;include=osm.tourism.hotel&amp;include=osm.amenity.restaurant
/search?q=restaurants+in+berlin&amp;exclude=osm.amenity.fast_food
</code></pre></div></div>

<p>The semantics follow Photon’s category filters. A comma and a repeated parameter mean different things:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>include=a.b,c.d       -&gt; match a.b OR c.d
include=a.b&amp;include=c.d -&gt; match a.b AND c.d

exclude=a.b,c.d       -&gt; exclude when both are present
exclude=a.b&amp;exclude=c.d -&gt; exclude when either is present
</code></pre></div></div>

<p>The last two rules look strange until the boolean logic is written down. They follow from applying De Morgan’s law to the exclusion groups, and they are compatible with the behaviour users already see in Photon.</p>

<p>The filter is applied across the search paths that return <code class="language-plaintext highlighter-rouge">placex</code> rows, rather than silently doing nothing on a normal name search. Sources without categories, such as postcodes, interpolations, TIGER data, and some country fallback tables, cannot satisfy an <code class="language-plaintext highlighter-rouge">include</code> filter.</p>

<p>The API work also found a bug in the PostgreSQL array result processor. It was returning the raw <code class="language-plaintext highlighter-rouge">'{a,b}'</code> array literal as a string. Once the new API started reading categories, SQLite export could interpret that string as individual characters.</p>

<p><img src="/img/2608-gsoc-api-array-bug.png" alt="Array bug illustration" /></p>

<p>That was a latent bug in the earlier category work, not something I had planned to fix. It is one reason I now try to test new data paths through every supported backend instead of only testing the path that motivated the change.</p>

<p>The final API tests cover validation, repeated parameters, hierarchy matching, AND/OR semantics, SQLite behaviour, and the POI, near, place, address, and country search paths.</p>

<p><img src="/img/2608-gsoc-api-flow.png" alt="API request flow" /></p>

<h2 id="documentation-is-part-of-the-implementation">documentation is part of the implementation</h2>

<p>The last open piece is documentation. <a href="https://github.com/osm-search/Nominatim/pull/4166">PR #4166</a> updates the migration, API, customization, and developer documentation for the category series.</p>

<p>I also had to be careful with terminology. Nominatim already uses “category” in a few older contexts, while the new data is stored in <code class="language-plaintext highlighter-rouge">categories</code>. Now calling the old <code class="language-plaintext highlighter-rouge">class</code>/<code class="language-plaintext highlighter-rouge">type</code> values categories made the documentation ambiguous. The final docs use “main tag” for the legacy class/type identity and reserve “categories” for the new paths.</p>

<h2 id="wrapping-up">wrapping up</h2>

<p>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. Once these changes land in a release, users will be able to filter search results by category directly through the API. For example:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/search?q=hilton&amp;include=osm.tourism.hotel
/search?q=berlin&amp;include=osm.amenity
/search?q=restaurants+in+berlin&amp;exclude=osm.amenity.fast_food
</code></pre></div></div>

<p>No more second-guessing which “restaurant” result is the one you meant. You ask for hotels, you get hotels.</p>

<p>Getting to that simple API surface meant tracing a single concept across Lua, SQL, PostgreSQL indexes, Python search builders, HTTP adaptors, SQLite conversion, migrations, BDD tests. I also learned that reviews are part of the design process. The most important changes in this project came from questions such as:</p>

<ul>
  <li>Why create several rows and merge them later?</li>
  <li>Which old class/type checks still need to become category checks?</li>
  <li>How much of the planet needs proactive backfilling?</li>
  <li>What happens when the category index sees 1.8 million restaurants?</li>
  <li>Can SQLite read the same category data?</li>
</ul>

<p>Some of my first answers were wrong. Yk I remember my mentors telling me at the very first meet that we might get surprises and unplanned turns that always happen when a good plan meets reality. I get it now.</p>

<p>The GSoC period is ending and, according to our scope plan, the project is done. There is no unfinished follow-up task needed to use the feature. The category work can still grow later: current categories are derived from main OSM tags, and a future step could add richer categories such as <code class="language-plaintext highlighter-rouge">cuisine.italian</code> or <code class="language-plaintext highlighter-rouge">access.wheelchair.yes</code> once there is a clearer set of real use cases. The foundation now exists for that work without requiring another redesign of the search database.</p>

<p>For me, this summer turned a side-project curiosity about maps into a much better understanding of how a geocoder works under load. I got to work with a planet database, learned a lot about PostgreSQL, ranking, triggers, migrations, and so on. I had a really great time. It was crazy, in the best way.</p>

<p>Thanks to Sarah and Marc for their guidance, patient reviews, and all the unexpected questions that made the implementation better. Thanks to OpenCage for supporting the project with the server I used for the large database tests. And thanks to the OpenStreetMap community and the OpenStreetMap Foundation for making this work possible.</p>

<p>I had a great summer. Thanks for reading :)</p>

<p>If you wanna connect, find me on <a href="https://x.com/idkAgasta">X</a> or <a href="https://github.com/Itz-Agasta">GitHub</a>.</p>

<p><em>Agasta signing out.</em></p>]]></content><author><name>Agasta</name></author><summary type="html"><![CDATA[Oh hey! I’m Agasta… I believe you don’t know me, so here’s my intro. This summer I was selected for GSoC to work on Nominatim with Sarah and Marc.]]></summary></entry><entry><title type="html">Photon 1.0.0 released</title><link href="https://nominatim.org/2026/02/11/photon-1.0-released.html" rel="alternate" type="text/html" title="Photon 1.0.0 released" /><published>2026-02-11T00:00:00+01:00</published><updated>2026-02-12T08:53:16+01:00</updated><id>https://nominatim.org/2026/02/11/photon-1.0-released</id><content type="html" xml:base="https://nominatim.org/2026/02/11/photon-1.0-released.html"><![CDATA[<p>We are happy to announce the release of Photon 1.0.0. With its first major
release Photon fully switches to OpenSearch, sees a lot of improvements
in performance and gets some new features on the query side.</p>

<p>This release marks a major milestone in the journey to make Photon a more
efficient and flexible geocoder. Over the last year the code has seen a lot
of modernization. We are moving away from being a simple search front end
to Nominatim, towards becoming a fully featured geocoder where OpenStreetMap
data can be one of many sources.</p>

<p>Here are the most important highlights for this 1.0 release:</p>

<h4 id="streamlined-database-structure">Streamlined database structure</h4>

<p>Photon’s internal database structure has been streamlined. Any metrics that
are not relevant for the geocoding problem have been dropped. We’ve also
dropped all language-specific indexes. Using those has slowed down queries
for very little gain in accuracy.</p>

<p>Altogether the database is now about half the size than a 0.7 database.
A planet needs about 95GB of disk space as of early 2026.</p>

<h4 id="revamped-cli">Revamped CLI</h4>

<p>Over the years Photon has collected quite a few command-line options to tweak
the import and the operation of the server. To bring a bit of order into this
mess, the command-line is now organised in git-style subcommands for import,
update and serving. You can ask for help for each of the commands with the
‘-h’ parameter and will get a more compact response with parameters neatly
organised in groups. Have a look at the new
<a href="https://github.com/komoot/photon/blob/master/docs/usage.md">usage documentation</a>
to learn more about the available commands.</p>

<p>The changes to the CLI are backwards-compatible. When no command is given then
Photon will fall back to the old-style command-line parameters. This will
give everybody some time to adapt their scripts to the new layout. But
don’t wait too long. The old-style parsing will be removed with the next
major version.</p>

<h4 id="new-query-features">New query features</h4>

<p>The <a href="https://github.com/komoot/photon/blob/master/docs/api-v1.md#structured-search">/structured</a>
endpoint, which was optional in previous releases, is now available by default
and can be used together with the database dumps from the export server. Be
aware though that structured search is still somewhat new and little tested.
You are welcome to provide feedback on the Github discussion page.</p>

<p>Version 1.0 newly introduces <a href="https://github.com/komoot/photon/blob/master/docs/categories.md">categories</a>.
These are custom tags that can be added to the import data and can then be used
for filtering queries. This is much more powerful than the current layer
and osm-tag filters. In fact, categories will replace the osm-tag filters
eventually. There are currently special categories included except for a category that represents
the OSM main tag. So this is mainly something you can use right now when
customizing your data to create a specialised search engine.</p>

<p>Finally, there is a new <code class="language-plaintext highlighter-rouge">dedupe</code> parameter which switches off the internal
result deduplication. This can for example be useful when a street is cut
into many sections in OSM and you would like to get all sections instead of
just one representative.</p>

<h4 id="json-import-and-export">JSON import and export</h4>

<p>Version 0.7 already introduced an experimental feature for exporting to and
importing from JSON dumps. With version 1.0 we have finalized the format
and published the <a href="https://github.com/komoot/photon/blob/master/docs/json-dump-format-0.1.0.md">official specification</a>.
JSON dumps of the OSM planet and selected abstracts are available on the
export server. Use them to filter and adapt the data before importing into
Photon, to create databases with custom settings (like additional languages)
or add your own custom data. We are looking forward to hear what you are
doing with this new feature.</p>

<h4 id="server-metrics">Server metrics</h4>

<p>If you are running Photon in a production environment that is monitored by
<a href="https://prometheus.io/">prometheus</a>, then the server is now able to export
internal metrics like number of queries and query duration or memory usage.
The endpoint isn’t enabled by the default, you need to switch it on when
starting the server.</p>

<p>&nbsp;</p>

<p>With this major release, Photon will switch to a more conventional use of the
semantic versioning schema. From now on, patch releases will only bring bug
fixes and dependency updates. Minor releases may contain new features or
change existing ones trying to maintain backwards compatibility. Major
releases are reserved for breaking changes in functionality and for changes
that require a database reimport. We have a few ideas for more major changes
to come, so don’t expect another 12 years to pass before Photon 2.0.</p>

<p>Many thanks to <a href="https://graphhopper.com">Graphhopper</a>, <a href="https://komoot.com">Komoot</a>
and <a href="https://entur.no/">Entur</a> for their continued support of Photon
development, which has made this release possible.</p>]]></content><author><name>Sarah Hoffmann (lonvia)</name></author><summary type="html"><![CDATA[We are happy to announce the release of Photon 1.0.0. With its first major release Photon fully switches to OpenSearch, sees a lot of improvements in performance and gets some new features on the query side.]]></summary></entry><entry><title type="html">New Feature: Entrance information</title><link href="https://nominatim.org/2025/09/13/entrances.html" rel="alternate" type="text/html" title="New Feature: Entrance information" /><published>2025-09-13T00:00:00+02:00</published><updated>2025-09-13T17:42:15+02:00</updated><id>https://nominatim.org/2025/09/13/entrances</id><content type="html" xml:base="https://nominatim.org/2025/09/13/entrances.html"><![CDATA[<p>Nominatim has recently added a new kind of details to its results: entrances.
This post explains what the new feature looks like
and why that is useful.</p>

<p>One of the important applications for geocoding is routing: you give the
router a start and a destination address and then expect it to find a route
in between. Start and destination address are usually sent to a geocoder to
convert them into a set of coordinates. The problem here is that geocoders
and routers have a slightly different view of the world: when a geocoder is
asked for an address, it will return the building that belongs to the address.
The router on the other hand only knows about the road network. So it has
to make an educated guess on which street you actually want to start your
journey. That usually works okay for smaller buildings but when it comes
to larger structures like parks or airports the outcome might not
be what you expect:</p>

<p><img src="/img/2509-routing-chicago-ohare.png" alt="Example routing from Chicago O´Hare airport" /></p>

<p>That’s where entrances come into play. Instead of just returning the center
point of a location, Nominatim now adds the information where the location
can be entered and exited. An entrance is usually much closer to a street
from which the router can start the journey. Here is an example for
<a href="https://nominatim.openstreetmap.org/ui/details.html?osmtype=W&amp;osmid=1353860602&amp;class=aeroway">Comox Airport, British Columbia</a>:</p>

<p><img src="/img/2509-comox-airport.png" alt="Nominatim entry for Comox Airport" /></p>

<p>The blue circle describes the point that Nominatim returns to the router
per default. The red circles are the entrances. Using them, them router can
bring you right to the terminal.</p>

<p>To use the new information, add the
<a href="https://nominatim.org/release-docs/develop/api/Search/#output-details"><code class="language-plaintext highlighter-rouge">entrances=1</code></a>
parameter to your request. If the result has entrance information an
<code class="language-plaintext highlighter-rouge">entrances</code> field with a list of entrances will be added. For each entrance
you get its <a href="https://wiki.openstreetmap.org/wiki/Key:entrance">type</a>, coordinates
and any extra tags that might be available. Please give it a try, especially
if you are writing or using a routing engine, and
<a href="https://github.com/osm-search/Nominatim/discussions">let us know</a>
what kind of information about the entrance is useful for you.</p>

<p>Entrances are widely mapped in OpenStreetMap but not much used so far. That
means that the tags around entrances are not well standardized yet. Making them
more visible and usable through Nominatim hopefully helps getting a wider
discussion going. There are a couple of limitations to the new entrance
feature that need more discussion:</p>

<ul>
  <li>Entrances are only added for OSM ways. Multipolygons or sites that are
mapped as OSM relations are currently ignored because it is unclear where
to look for entrances. There is a proposal for a special <code class="language-plaintext highlighter-rouge">entrance</code> member
for relations but it isn’t widely used yet. Another option would be to look
for entrances on <code class="language-plaintext highlighter-rouge">outer</code> members.</li>
  <li>When dealing with complex locations like shopping malls or airports, then
finding the routing endpoint can become much more complex than just going
to the main door. The right access point may depend on your mode of transport,
the purpose of your visit (departure or arrival) and which parts of the
location you want to access. This might need more fine-grained tagging in
OSM or it might be solved through more complex routing algorithms or
more precise geocoding.</li>
  <li>Entrances are of limited use, when the location you want to get to is part
of a larger complex with special entrances. To reach an address in a gated
community, you need to be routed to the guest entrance of the community,
not the address directly. As before, there is no suitable tagging for
OSM yet, to express such a fact.</li>
</ul>

<p>Entrance output is available in preview on
<a href="https://nominatim.openstreetmap.org">https://nominatim.openstreetmap.org</a>.
Right now only entrances for recently edited data is available. You can
check in the details view of the Nominatim UI, if entrances for a location
are in the database: search for the location, then click on ‘Details’.</p>

<p><em>Many thanks to <a href="https://github.com/emlove">@emlove</a> for implementing this
new feature and to <a href="https://github.com/mtmail">@mtmail</a> for adding entrance
display to the Nominatim UI.</em></p>]]></content><author><name>Marc Tobias (mtmail)</name></author><summary type="html"><![CDATA[Nominatim has recently added a new kind of details to its results: entrances. This post explains what the new feature looks like and why that is useful.]]></summary></entry><entry><title type="html">A New Look for Photon Dumps</title><link href="https://nominatim.org/2025/08/13/photon-exports-renewed.html" rel="alternate" type="text/html" title="A New Look for Photon Dumps" /><published>2025-08-13T00:00:00+02:00</published><updated>2025-08-14T21:24:54+02:00</updated><id>https://nominatim.org/2025/08/13/photon-exports-renewed</id><content type="html" xml:base="https://nominatim.org/2025/08/13/photon-exports-renewed.html"><![CDATA[<p>If you have recently visited the
<a href="https://download1.graphhopper.com/public/">download site for Photon dump files</a>
you may have noticed the new site layout. The site has received a complete overhaul
with new types of dumps, new options for extracts and a nicer presentation.
Read here what has changed.</p>

<h2 id="region-extracts-replace-country-extracts">Region extracts replace country extracts</h2>

<p>The old site used to host one large planet dump and then country extracts in the
<code class="language-plaintext highlighter-rouge">extracts/by-country-code/</code> directory. The new layout rearranges how you can
find extracts: they are now organised by continent with each continent giving
you a list of available country extracts. For you as a user that means that
<strong>all file locations and names have changed</strong>.</p>

<p>Some of the country extracts were really tiny and didn’t make much sense to
have on their own. They have been merged to subregions like the
<a href="https://download1.graphhopper.com/public/north-america/caribbean/index.html">Caribbean islands</a>.
If you need a single country from that subregion, download the JSON dump and
create a Photon database with a country filter. The section on JSON dumps
below explains how this works.</p>

<p>With the new structure, there will now also be extracts for continents on
offer. This will hopefully reduce the hardware requirements on disk space for
those of you that don’t need the entire planet but still want to have
multiple countries.</p>

<h2 id="photon-database-dumps">Photon database dumps</h2>

<p>The database dumps are the unzip-and-go database dumps we have always provided.
These are now available for the planet, all continents and some selected
countries. Do remember that the naming schema has changed slightly.
Database dump file names now start with ‘photon-db-‘ and have the suffix
‘.tar.bz2’.</p>

<p>Usage of these dumps hasn’t changed: download the dump, unpack it and
start Photon.</p>

<p>There are no database dumps anymore for smaller, less popular countries. If
you need a Photon database for those countries, you have to create your own
database from the JSON dumps as described in the next section. This usually
won’t take more than an hour.</p>

<p>With version 0.7 being a transitional release between the old ElasticSearch
backend and the new OpenSearch backend, we publish dumps for both versions.
Please make sure that you download the right dump. Photon 0.7.3 and later
will refuse to start if you use the wrong file.</p>

<h2 id="photon-json-dumps">Photon JSON dumps</h2>

<p>The download site now also allows you to download
<a href="https://github.com/komoot/photon/pull/885">JSON dumps</a> of the Photon data.
These dumps are available for the planet, continents and as country extracts.
JSON dump files start with ‘photon-dump-‘ and have the suffix ‘.jsonl.zst’. They
use the newer <a href="https://en.wikipedia.org/wiki/Zstd">zstd</a> for compression.
Make sure you have the appropriate tool installed.</p>

<p>JSON dumps give you the raw data with which you can build your own Photon
database. Apart from being much smaller than the database dumps, they are
also more versatile: they contain a lot more languages<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>, the (almost)
full set of OSM tags as ‘extratags’ and the full
geometries of the objects. Starting from a JSON dump, you can create
localized databases, databases that allow
<a href="https://github.com/komoot/photon/blob/master/docs/structured.md">structured search</a>
and databases that <a href="https://github.com/komoot/photon/pull/823">return the full geometry</a>.</p>

<p>You can also combine smaller extracts into one database. Need a geocoding
server for <a href="https://en.wikipedia.org/wiki/Benelux">Benelux</a>? Download
the extracts for
<a href="https://download1.graphhopper.com/public/europe/belgium/index.html">Belgium</a>,
the <a href="https://download1.graphhopper.com/public/europe/netherlands/index.html">Netherlands</a>
and <a href="https://download1.graphhopper.com/public/europe/luxemburg/index.html">Luxemburg</a>.
Then create the database by concatenating the downloaded files together:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zstd --stdout -d photon-dump-*.jsonl.zst | java -jar photon.jar -nominatim-import -import-file -
</code></pre></div></div>

<p>Or you can go the other way around. Download the JSON dump for
<a href="https://download1.graphhopper.com/public/europe/index.html">Europe</a>
and then filter for the countries you are interested in during the import:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zstd --stdout -d photon-dump-europe-0.7-latest.jsonl.zst | java -jar photon.jar -nominatim-import -import-file - -countries be,nl,lu
</code></pre></div></div>
<h3 id="updating-a-database-from-json-dumps">Updating a database from JSON dumps</h3>

<p>Once you have created your Photon database from a JSON dump, you might want
to update the data from time to time using a more recent JSON dump. You can
do this while the old database is up and running. Just make sure you use
a different cluster name and database location while you do the reimport.</p>

<p>In short, the steps are:</p>

<ol>
  <li>Create a directory for the reimport and change into the directory:
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir /srv/photon-reimport
cd /srv/photon-reimport
</code></pre></div>    </div>
  </li>
  <li>Download the newest extract. Then import it with the same options you used
for the original import, adding the cluster parameter:
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zstd --stdout -d photon-dump-*.jsonl.zst | java -jar photon.jar -nominatim-import -import-file - -cluster photon-reimport
</code></pre></div>    </div>
  </li>
  <li>Switch out the original database with the newly imported one
(here we assume the original one is in <code class="language-plaintext highlighter-rouge">/srv/photon</code>):
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mv /srv/photon/photon-data /srv/photon/photon-data.old
mv photon-data /srv/photon/photon-data
</code></pre></div>    </div>
  </li>
  <li>Restart your Photon server. The newly imported database will now run inside
the original cluster ‘photon’.</li>
</ol>

<h2 id="which-version-do-i-need">Which version do I need?</h2>

<p>The new download site points you now directly to the right dump to match
the Photon version you are using. We will usually provide extracts for one
or two versions back, so you have a bit of time to switch over your setup.
Keep in mind though that only the dumps for the latest version are
guaranteed to receive weekly updates.</p>

<p>You will also always find a <em>master</em> version of the dumps. These are the dumps
created from the current main development branch. (They used to be found in
the ‘experimental’ directory before the restructuring.) They are mainly meant
for testing for developers but you are welcome to try out the latest features
and give us feedback.</p>

<h2 id="try-it-out">Try it out!</h2>

<p>The new layout will hopefully make it easier to find and use the appropriate
data to create your own customized geocoding database. The old file locations
will keep working for another couple of months. So you have some time to
adapt your setups. The old files won’t receive updates though. If you need
it fresh, switch now.</p>

<p><em>Many thanks to <a href="https://graphhopper.com/">Graphhopper</a> who make the Photon
export service possible by generously sponsoring the server.</em></p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>At the time of writing this blog the full list of supported languages is: en, ru, zh, ja, uk, ar, ko, ca, fr, de, fi, be, pl, es, sr, br, he, sv, el, it, th, ga, oc, kn, ur, ms, nl, my, eu, ka, hu, fa, hi, pt, lt, ro, cs. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Sarah Hoffmann (lonvia)</name></author><summary type="html"><![CDATA[If you have recently visited the download site for Photon dump files you may have noticed the new site layout. The site has received a complete overhaul with new types of dumps, new options for extracts and a nicer presentation. Read here what has changed.]]></summary></entry><entry><title type="html">The Road to Nominatim 6</title><link href="https://nominatim.org/2025/07/14/roadmap-nominatim6.html" rel="alternate" type="text/html" title="The Road to Nominatim 6" /><published>2025-07-14T00:00:00+02:00</published><updated>2025-07-14T14:40:53+02:00</updated><id>https://nominatim.org/2025/07/14/roadmap-nominatim6</id><content type="html" xml:base="https://nominatim.org/2025/07/14/roadmap-nominatim6.html"><![CDATA[<p>With version 5 Nominatim has finished the long transition from a simple PHP
frontend to a complex Python application. The change wasn’t just about changing
the programming language but also about making Nominatim more flexible and
easy to use. With that out of the way, the question is what comes next. What
can you expect to see in version 6. The road for the next major version isn’t 
completely paved out yet. This post outlines the major open issues and some
of possible next developments.</p>

<h2 id="auto-completion-and-spelling-correction">Auto-completion and spelling correction</h2>

<p>Search-as-you-type and some leniency towards spelling mistakes are without a
doubt on the top of the list of feature requests for Nominatim.
Search-as-you-type will require to change how the internal search indexes
are built and accessed. Nominatim’s current search model
is not compatible with resolving incomplete queries. When it comes to
spelling correction, we are going to need a good model for estimating the
similarity between a query and the place names in the database. Simple
approaches like Levenshtein distance are difficult for a multi-lingual
database of proper names.</p>

<h2 id="performance-and-index-optimisations">Performance and Index Optimisations</h2>

<p>A full planet database requires now more than 1TB in disk space. This means
that it reaches the limits of what can be done with of-the-shelf hardware. Worse,
the search indexes in our backing PostgreSQL database have grown to a size
where lookups are becoming noticeably slow. It is time to revisit our database
schema, see where tables can be optimised and trimmed down, and consider how
search indexes might be better organised differently to trim them down to what
is relevant for finding the right place.</p>

<h2 id="complex-osm-objects">Complex OSM objects</h2>

<p>Nominatim’s entire processing pipeline is built in a way that it considers one
OSM object at the time. That makes processing and updating easy but it doesn’t
fit well anymore with how data is modelled in OSM. We increasingly see detailed
mapping where multiple OSM objects make up a single real-world object that you
may want to find with search. To accommodate that Nominatim’s processing pipeline
needs to be adapted, so that it can work with places that do not have a 1:1
equivalent in the OSM world. This also means that the output needs to change.
Every result of a search is currently tied to an OSM object. In the future,
it is more likely that you will get an abstract place description with references
to all the relevant OSM objects.</p>

<h2 id="addresses-as-first-class-citizens">Addresses as first-class citizens</h2>

<p><a href="https://community.openstreetmap.org/t/are-addr-tags-for-postal-addresses-only-or-for-locations-in-general/132565/44">Physical addresses</a> are not considered searchable places on its own in Nominatim right now.
Addresses only appear as an attribute of a place and when you search for
an address, you will in fact get all place objects which happen to have the
address assigned. That can cause a lot of issues. For example, the more
detailed the mapping in OSM becomes, the more objects will be returned for
an address search, even though you would have expected exactly one result.
Inversely, there are sometimes OSM objects that have more than one address.
For example, some house entrances come with multiple house numbers. Or there are
houses where the address has changed and which you’d still want to find under
its former address. All this cannot be modelled in Nominatim right now.</p>

<p>To enable a true address search, addresses need to become first class citizens
in Nominatim that can be directly returned as a result. Places would of course
still keep their address attributes but those will only be references to
one or more address they can be found under.</p>

<h2 id="complex-categories">Complex categories</h2>

<p>Every place in Nominatim currently gets a simple category which is derived
from the main tag of its OSM object. This puts some limitation on what kind
of category search Nominatim can do. For example, you cannot search for a
“vegan restaurant” or a “catholic church” because the main tags only
classify “restaurants” and “places of worship of any religion”.</p>

<p>Another issue with the current classification system is that it is bad at
handling OSM objects with multiple functions (say, a hotel with an attached
restaurant mapped with the same POI node). Nominatim will simply duplicate
the OSM object in its database to cover both functions. That unnecessarily
blows up the database size.</p>

<p>So it is time to get a way from using OSM tags directly and introduce the
ability to define custom classifications. The idea here is to have hierarchical
categories (e.g. food.restaurant.vegan) and allow to assign an arbitrary number
of categories to each object.</p>

<hr />

<p>These are the main open issues right now. If one of them sparks your interest
and you’d like to help moving them along, don’t hesitate to get in touch.
The <a href="https://github.com/osm-search/Nominatim/discussions">discussion section on Github</a>
and the <a href="https://community.openstreetmap.org/c/general/38/none">OSM community forum</a>
are great places to start a discussion.</p>]]></content><author><name>Sarah Hoffmann (lonvia)</name></author><summary type="html"><![CDATA[With version 5 Nominatim has finished the long transition from a simple PHP frontend to a complex Python application. The change wasn’t just about changing the programming language but also about making Nominatim more flexible and easy to use. With that out of the way, the question is what comes next. What can you expect to see in version 6. The road for the next major version isn’t completely paved out yet. This post outlines the major open issues and some of possible next developments.]]></summary></entry><entry><title type="html">Nominatim 5.0.0 released</title><link href="https://nominatim.org/2025/02/07/release-50.html" rel="alternate" type="text/html" title="Nominatim 5.0.0 released" /><published>2025-02-07T00:00:00+01:00</published><updated>2025-02-07T15:54:42+01:00</updated><id>https://nominatim.org/2025/02/07/release-50</id><content type="html" xml:base="https://nominatim.org/2025/02/07/release-50.html"><![CDATA[<p>We are happy to announce the release of Nominatim 5.0.0. This major release
marks the end of a 4-year journey to modernize and modularize the Nominatim
codebase in order to make it easier to use and maintain.</p>

<p>This release finishes the mutation of Nominatim into a Python package. The
PHP frontend, bundled osm2pgsql and cmake build scripts have now been removed
for good. If you are still using one of these features, then you should
update your software to Nominatim 4.5 and then move to the new Python frontend
and pip installation. Once done, you can easily update to the latest
version 5 release.</p>

<p>Also in this release, the osm2pgsql import style configuration has been
largely be rewritten. If you are using one of the built-in styles, this
will not make much of a difference. If you are maintaining your own custom
style, however, this should become much easier. Most notable, it is now
possible to start with one of the existing styles and add your customizations
on top. That should make it much easier to keep in sync with the latest
changes in Nominatim. Have a look at the
<a href="https://nominatim.org/release-docs/latest/customize/Import-Styles/">updated documentation</a>
for details. The new implementation is largely backwards compatible,
so your old scripts will keep working for now.</p>

<p>With the new osm2pgsql style implementation comes the ability to use
Nominatim together with <a href="https://osm2pgsql.org/themepark/">osm2pgsql-themepark</a>.
This comes in handy when you want to combine Nominatim with other osm2pgsql
flex styles in order to host OSM data for different purposes in the same
database. Check out the updated
<a href="https://nominatim.org/tutorials/running-nomintim-and-rendering-together.html">cookbook about how to run Nominatim with osm-carto</a>
to learn how to use this feature.</p>

<p>Finally, Nominatim has a new hook for adding pre-processing functions
for incoming search queries, allowing to apply custom filtering. The first
filter to use this new functionality breaks up Japanese addresses into their
parts.</p>

<p>A full list of changes can as always be found in the
<a href="https://github.com/osm-search/Nominatim/blob/v5.0.0/ChangeLog">Changelog</a>.</p>]]></content><author><name>Sarah Hoffmann (lonvia)</name></author><summary type="html"><![CDATA[We are happy to announce the release of Nominatim 5.0.0. This major release marks the end of a 4-year journey to modernize and modularize the Nominatim codebase in order to make it easier to use and maintain.]]></summary></entry><entry><title type="html">Joining the Sovereign Tech Fellowship Programme</title><link href="https://nominatim.org/2025/02/06/sovereign-tech-fellowship.html" rel="alternate" type="text/html" title="Joining the Sovereign Tech Fellowship Programme" /><published>2025-02-06T00:00:00+01:00</published><updated>2025-02-06T14:33:15+01:00</updated><id>https://nominatim.org/2025/02/06/sovereign-tech-fellowship</id><content type="html" xml:base="https://nominatim.org/2025/02/06/sovereign-tech-fellowship.html"><![CDATA[<p>I’m happy to announce that I have been selected for the one-year pilot
of the <a href="https://www.sovereign.tech/news/meet-the-sovereign-tech-fellows">Sovereign Tech Fellowship programme</a>
of the Sovereign Tech Agency.
The fellowship programme will support maintenance of Nominatim, Photon, osm2pgsql
and pyosmium over the next year.</p>

<p>Participating in an open-source software project like Nominatim or Photon
is not just about the implementation of fancy new features or clever
algorithms to improve performance or the user experience. A lot of work
happens quietly behind the scene: user questions need to be answered and bug
reports followed up. Dependent software needs to be monitored and updated
as necessary. The own code needs to be reviewed and polished regularly
to prevent it from ageing and slowly falling apart.
CI pipelines are a great tool for a maintainer but they do break with an
astonishing regularity and therefore need regular attention. Not to
mention that a CI is useless without a set of well-maintained tests.</p>

<p>With its new Sovereign Tech Fellowship programme, the Sovereign Tech Agency
recognises the importance of this maintenance work for the general functioning
of the open source ecosystem. 
The programme will financially support my day-to-day tasks of software maintainership:
responding to issues on Github, reviewing and merging pull requests, fixing
reported bugs and addressing security issues, improving documentation and tests,
preparing releases etc. On top of that there are some other not so glorious
maintenance tasks that are planned for this year.</p>

<p>Nominatim’s import module relies on a datrie library, which has been
unmaintained for some years and <a href="https://github.com/osm-search/Nominatim/issues/3534">no longer compiles</a>
with the newest GCC compilers. We need to find a solution to that by either
switching to a new library or taking over maintenance. A similar fate is likely
in store for the testing library <a href="https://github.com/behave/behave">behave</a>.
With the latest stable release from 2018 and very few activity since, it is
unclear how long it will remain functional with new versions of Python.</p>

<p>Photon has seen the move to OpenSearch 8 last year. This transition is far
from finished. For example, there is still no proper support for using an
external instance of OpenSearch instead of the embedded one. And
ElasticSearch/OpenSearch itself has also seen some improvements in the last
three versions we have skipped. It’s well worth investigating how geocoding can
benefit from them.</p>

<p>These will, of course, not be the only things happening in 2025 for Nominatim and
Photon. There will also be shiny new features and I have some ideas for
improving performance and how to better handle the increasing complexity of
OSM data. However, none of this can really happen, if the basis isn’t there and
the general maintenance isn’t cared for.</p>

<p>Many thanks to the Sovereign Tech Agency for this great opportunity and the
recognition of the importance of maintenance work for software.</p>

<p><em>If you want to support development and maintenance of Nominatim, too, please
consider becoming a <a href="https://github.com/sponsors/lonvia">Github sponsor</a>.</em></p>]]></content><author><name>Sarah Hoffmann (lonvia)</name></author><summary type="html"><![CDATA[I’m happy to announce that I have been selected for the one-year pilot of the Sovereign Tech Fellowship programme of the Sovereign Tech Agency. The fellowship programme will support maintenance of Nominatim, Photon, osm2pgsql and pyosmium over the next year.]]></summary></entry><entry><title type="html">Nominatim 4.5.0</title><link href="https://nominatim.org/2024/09/12/release-450.html" rel="alternate" type="text/html" title="Nominatim 4.5.0" /><published>2024-09-12T00:00:00+02:00</published><updated>2024-09-18T12:06:22+02:00</updated><id>https://nominatim.org/2024/09/12/release-450</id><content type="html" xml:base="https://nominatim.org/2024/09/12/release-450.html"><![CDATA[<p>We are happy to announce the release of version 4.5.0 of Nominatim.
This is a transition release, helping you to prepare the move
towards the upcoming <a href="/2023/12/19/roadmap-nominatim5.html">Nominatim 5.0</a>,
which will be a pure Python application.</p>

<p>The most important change in this release is that Nominatim is now available
via <a href="https://pypi.org/project/nominatim-db/">pypi.org</a> and can be installed
with a simple <code class="language-plaintext highlighter-rouge">pip install nominatim-db nominatim-api</code>. We had to change the
package structure slightly to make this possible, splitting the <code class="language-plaintext highlighter-rouge">nominatim</code>
package into two parts: <code class="language-plaintext highlighter-rouge">nominatim-db</code> (the database importer) and
<code class="language-plaintext highlighter-rouge">nominatim-api</code> (the search frontend). Please carefully read the
<a href="https://nominatim.org/release-docs/latest/admin/Migration/#440-450">migration guide</a>
about how this change might affect you. The old way of installing with
CMake is still available in this release. So you can take your time updating
to the new installation method. Nominatim 5 will then only be installable via
pip.</p>

<p>Other new features in this release include the possibility to customize
API output for web installations, a streamlined file format for wiki
importances, improvements to ordering results according to how well 
address parts match and a more consistent assignment of countries in disputed areas.
A more complete list of changes can be found in the
<a href="https://github.com/osm-search/Nominatim/blob/4.5.x/ChangeLog">Changelog</a>.</p>

<p>This is the last release to have support for PostgreSQL 9.6 and 10 and
Postgis 2.x. These version have long gone out of support, so it is time
to drop them.</p>

<p>Furthermore, the following Nominatim features will be removed with the
next release:</p>

<ul>
  <li><a href="https://nominatim.org/release-docs/latest/admin/Deployment-PHP/">PHP frontend</a>.
Please switch to the newer <a href="https://nominatim.org/release-docs/latest/admin/Deployment-Python/">Python frontend</a> instead.</li>
  <li><a href="https://nominatim.org/release-docs/latest/customize/Tokenizers/#legacy-tokenizer">Legacy tokenizer</a>.
If your database still uses this tokenizer, you need to reimport using
the <a href="https://nominatim.org/release-docs/latest/customize/Tokenizers/#icu-tokenizer">ICU tokenizer</a>.</li>
  <li>Installation via CMake. Switch to using <code class="language-plaintext highlighter-rouge">pip install</code> instead.</li>
  <li>Bundeling of osm2pgsql. Use a stock osm2pgsql version 1.8 or higher.
Ubuntu 24.04 and Debian &gt;= 11 have appropriate packages. The packages of
older Ubuntu version are too old and you need to compile a newer osm2pgsql
from source.</li>
</ul>]]></content><author><name>Sarah Hoffmann (lonvia)</name></author><summary type="html"><![CDATA[We are happy to announce the release of version 4.5.0 of Nominatim. This is a transition release, helping you to prepare the move towards the upcoming Nominatim 5.0, which will be a pure Python application.]]></summary></entry><entry><title type="html">New Wikimedia-based scoring file available</title><link href="https://nominatim.org/2024/08/07/wikimedia-file.html" rel="alternate" type="text/html" title="New Wikimedia-based scoring file available" /><published>2024-08-07T00:00:00+02:00</published><updated>2024-08-07T12:09:03+02:00</updated><id>https://nominatim.org/2024/08/07/wikimedia-file</id><content type="html" xml:base="https://nominatim.org/2024/08/07/wikimedia-file.html"><![CDATA[<p>Nominatim tries to assign each place in its database a base “importance”
score number to answer the question “If 30 places have the name ‘Berlin’,
which one is the most likely a user meant?” For Berlin that’s the one in
Germany. We humans say “of course” but that’s not how computers work.</p>

<p>Looking at a places’ type (city vs village), size, OpenStreetMap tags,
population all have disadvantages, usually simply lacking a good data
source for the whole world. We even tried looking at how often tiles
were loaded for regions on tile.openstreetmap.org (Google Summer of Code
project 2022) but that’s not granular enough.</p>

<p>Wikipedia turned out to be a good approximation or “importance”. Basically
if a place has a Wikipedia article, and how many other articles link
to it? And we can turn that into a single number.</p>

<p>As early as 2014 (version 2.2) Nominatim had the option to imported
additional scoring files. Those files were created from Wikipedia
metadata. First using their pageviews, later based on links between
Wikipedia article, links between Wikipedia projects (languages), then
including redirects and now even taking Wikidata into account.</p>

<p>The scoring file contains language code, Wikipedia article
title, Wikidata id, redirect titles and a score number for 17 million
titles. Nominatim matches the title against place names.</p>

<p>Many places in OpenStreetMap data already have the
<a href="https://wiki.openstreetmap.org/wiki/Key:wikipedia">wikipedia</a>
and <a href="https://wiki.openstreetmap.org/wiki/Key:wikidata">wikidata</a>
tags. That’s helpful for us, please continue to add those. On Wikipedia
many places
<a href="https://en.wikipedia.org/wiki/Wikipedia:How_to_add_geocodes_to_articles">contain coordinates</a>.
Again that’s helpful: we can determine if an article is about a
place instead of for example a movie title, band name or
<a href="https://en.wikipedia.org/wiki/Krapfen_(doughnut)">pastry</a>.</p>

<p>Over the years creating the scoring file became unmaintained while
the data to process grew massively. Just English Wikipedia contains
900 million links! The metadata for the 40 largest languages
is 40GB compressed (about 90% compression rate). We had a major rewrite
of the processing during the Google Summer of Code 2019 project
but it loaded all data into a database. Processing took several days
each time, was error-prone and we rarely ran it.</p>

<p>In the last year we got the processing down to a manageable 12 hours.
This will allow us to publish updated importance file much more frequently.
We have also streamlined the format and publish it now as a simple CSV file.
If you have other uses besides geocoding in mind, you are welcome to
use it. You can read the full details about how the file is made and
what it contains at
<a href="https://github.com/osm-search/wikipedia-wikidata">https://github.com/osm-search/wikipedia-wikidata#readme</a>.
The updated file can be downloaded at
<a href="https://nominatim.org/data/wikimedia-importance.csv.gz">https://nominatim.org/data/wikimedia-importance.csv.gz</a>.</p>

<p>Nominatim itself will receive full support for reading then new CSV file
format in the upcoming version 4.5.</p>]]></content><author><name>Marc Tobias (mtmail)</name></author><summary type="html"><![CDATA[Nominatim tries to assign each place in its database a base “importance” score number to answer the question “If 30 places have the name ‘Berlin’, which one is the most likely a user meant?” For Berlin that’s the one in Germany. We humans say “of course” but that’s not how computers work.]]></summary></entry><entry><title type="html">Nominatim 4.4.0 and Photon 0.5.0 released</title><link href="https://nominatim.org/2024/03/08/release-440.html" rel="alternate" type="text/html" title="Nominatim 4.4.0 and Photon 0.5.0 released" /><published>2024-03-08T00:00:00+01:00</published><updated>2024-03-08T18:07:57+01:00</updated><id>https://nominatim.org/2024/03/08/release-440</id><content type="html" xml:base="https://nominatim.org/2024/03/08/release-440.html"><![CDATA[<p>We are happy to announce that this week new versions of Nominatim and its
ElasticSearch frontend Photon have been released.</p>

<p>Version 4.4.0 of Nominatim brings many bug fixes and performance improvements
for the new Python frontend, which was introduced in version 4.3.0. It can
now be considered stable and has become the frontend recommended to be used
for new installations. You find <a href="https://nominatim.org/release-docs/latest/admin/Deployment-Python/">deployment guides</a>
for the Python frontend in the documentation. There is no need to reimport
your existing database. It will work perfectly fine with the new frontend.</p>

<p>A new feature in this release is experimental support for exporting a
Nominatim database to SQLite. The SQLite database can then be used with the
new Python frontend or when using the Nominatim library. For more information
about this, watch the talk at <a href="https://www.youtube.com/watch?v=dBLuSZ4TOfw">SotM-EU 2023</a>
or read about it in the <a href="https://nominatim.org/2023/10/25/sqlite-reverse.html">SQLite blog posts</a>.</p>

<p>Version 0.5.0 of Photon brings back the ability to update a Photon database
from a Nominatim database. The new mechanism is better decoupled from the
Nominatim update process, making it easier to handle. A long-standing issue
around UIDs of house numbers documents has been fixed so that all data should
now be correctly handled. This version also introduces a new API endpoint
<code class="language-plaintext highlighter-rouge">/nominatim-update/status</code> which allows scripts to check if an update is
already in progress. To find out if your Photon database is indeed up-to-date
use the newlt added <code class="language-plaintext highlighter-rouge">/status</code> endpoint.</p>

<p>If you do not use the update facility, then the Photon release remains
compatible with version 0.4 database dumps including the ones available on
<a href="https://download1.graphhopper.com/public">https://download1.graphhopper.com/public</a>.
If you want to run updates, you need to create a fresh import using this
release. Please be aware that updates now require an additional preparation
step. Consult the README for more information.</p>]]></content><author><name>Sarah Hoffmann (lonvia)</name></author><summary type="html"><![CDATA[We are happy to announce that this week new versions of Nominatim and its ElasticSearch frontend Photon have been released.]]></summary></entry></feed>