Skip to content

Make POI tag ranking deterministic#889

Open
Symmetricity wants to merge 1 commit into
systemed:masterfrom
Symmetricity:fix/poi-tag-order
Open

Make POI tag ranking deterministic#889
Symmetricity wants to merge 1 commit into
systemed:masterfrom
Symmetricity:fix/poi-tag-order

Conversation

@Symmetricity
Copy link
Copy Markdown

@Symmetricity Symmetricity commented May 13, 2026

This PR is AI generated.

Make POI tag ranking deterministic

GetPOIRank() returns the first matching POI tag group. It currently iterates
poiTags with pairs(), but Lua does not specify table traversal order for
non-array tables. That means an object with more than one matching POI tag can
receive different class, subclass, and rank values depending on Lua table
iteration order.

Use an explicit poiTagKeys array and iterate it with ipairs() so the POI
priority order matches the order already written in the profile. Apply the same
change to both OpenMapTiles profiles, process-openmaptiles.lua and
process-debug.lua.

For example, an object with both of these tags has two valid POI matches:

amenity=school
tourism=attraction

Before this change, whichever key pairs(poiTags) returned first determined
the output. That could classify the same object as either:

4:attraction:attraction

or:

7:school:school

After this change, amenity is checked before tourism, matching the written
profile order, so the result is stable:

7:school:school

This is better because the profile already has a meaningful priority order, and
the rendered POI class/rank should not depend on Lua hash table traversal. Rank
affects POI prominence, while class and subclass affect downstream styling and
search semantics.

The change is intentionally small: it only changes the key iteration order used
by GetPOIRank(). It does not alter the POI tag sets, rank tables, output
layers, or tilemaker core code.

Reproduce

Load the OpenMapTiles process file and evaluate a POI that has multiple
matching tag groups, for example both amenity=school and
tourism=attraction:

lua - <<'LUA'
dofile('resources/process-openmaptiles.lua')
local tags = { amenity = 'school', tourism = 'attraction' }
function Find(k) return tags[k] or '' end
local rank, class, subclass = GetPOIRank()
print(rank .. ':' .. class .. ':' .. subclass)
LUA

Before this change, the selected POI group depends on the unspecified order of
pairs(poiTags), so the object can be classified from a different matching tag
group on another Lua version, platform, or table layout.

After this change, the result follows the explicit profile order and is stable:

7:school:school

The same issue and fix apply to resources/process-debug.lua.

Testing

  • git diff --check origin/master..HEAD
  • luac -p resources/process-openmaptiles.lua resources/process-debug.lua
  • Focused Lua checks for GetPOIRank() in both process files using a POI with
    amenity=school and tourism=attraction; both returned
    7:school:school

Related Issues And PRs

I did not find an existing upstream issue, PR, or discussion that directly
reports this pairs(poiTags) ordering issue.

Related POI context:

GetPOIRank returned the first matching POI tag group, but it iterated poiTags with pairs(). Lua does not specify table traversal order, so objects matching multiple POI groups could receive different class and rank values across runs or platforms.

Iterate an explicit POI key list with ipairs() so the profile priority order is stable, and keep the subclass temporary local to the ranking function.
@Symmetricity
Copy link
Copy Markdown
Author

I added visual checks for the determinism issue.

For this comparison I generated two local upstream outputs from the same binary,
same upstream pairs(poiTags) profile, same input, and same command. The same
multi-tag POIs resolved to different POI tags between those two upstream runs.
The PR output resolves those same objects by the explicit profile order, so the
selected tag is stable between runs.

I also generated the PR output twice for the same objects; the selected POI tags
matched between the two PR runs.

The screenshots use a local debug_osm_id attribute only to match generated
POIs back to source OSM ways; that attribute is not part of this PR.

Sportsplatz Rheinwiese

Source tags include leisure=pitch and sport=soccer.

  • Upstream run A: class=stadium, subclass=soccer, rank=8
  • Upstream run B: class=pitch, subclass=soccer, rank=25
  • PR result: class=pitch, subclass=soccer, rank=25

Sportsplatz Rheinwiese comparison

Schwimmbad Mühleholz

Source tags include leisure=sports_centre and sport=swimming.

  • Upstream run A: class=swimming, subclass=swimming, rank=25
  • Upstream run B: class=leisure, subclass=sports_centre, rank=25
  • PR result: class=leisure, subclass=sports_centre, rank=25

Schwimmbad Mühleholz comparison

Tennishalle Schaan

Source tags include leisure=sports_centre and sport=tennis.

  • Upstream run A: class=sport, subclass=tennis, rank=25
  • Upstream run B: class=leisure, subclass=sports_centre, rank=25
  • PR result: class=leisure, subclass=sports_centre, rank=25

Tennishalle Schaan comparison

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.

1 participant