Bash

cat /root/traccar-geocoder/builder/src/build_index.cpp | ssh node3 "cat > /tmp/traccar-geocoder/builder/src/build_index.cpp" && ssh node3 "cd /tmp/traccar-geocoder/build && make -j\$(nproc) 2>&1 | tail -3 && rm -rf /tmp/de-par-rolefix2 && mkdir -p /tmp/de-par-rolefix2 && /tmp/traccar-geocoder/build/build-index /tmp/de-par-rolefix2 ~/pbf/germany.osm.pbf --parallel-admin 2>&1 | grep -E 'polygon|Done|closed way'" 2>&1

Build with fallback role fix


[ 50%] Building CXX object CMakeFiles/build-index.dir/src/build_index.cpp.o
[100%] Linking CXX executable build-index
[100%] Built target build-index
  Added 37 admin polygons from closed ways.
  Assembling admin polygons in parallel (40080 relations, 112858 way geometries)...
  Parallel admin assembly complete: 43186 polygon rings from 40080 relations.
Done reading:
  43223 admin polygon rings
Waiting for admin polygon S2 covering to complete...
Admin polygon S2 covering complete (6103 cells).
admin index: 6103 cells, 43223 polygons
Done.


43,223 vs 43,221. Let me check what changed.


ssh node3 "/tmp/freq_compare /tmp/de-par-rolefix2 /tmp/de-sequential-final" 2>&1

Check Germany freq differences


  'Krefeld-Ost|9': A=2 B=1 diff=+1
  'Krefeld-Uerdingen|9': A=1 B=2 diff=-1
  'Neuhaus|10': A=5 B=4 diff=+1
  'Reislingen|10': A=1 B=0 diff=+1
Total frequency differences: 4


Same 3 as before (Krefeld swap, Neuhaus extra) plus now Reislingen (+1) — the inner-way fallback created a ring for Reislingen that wasn't there before. But osmium doesn't create it either.

Reislingen's ways are 2km apart (we checked earlier). The inner-way fallback must be creating a ring by including inner ways that bridge the 2km gap. Let me check — the missing-ways filter should have caught this since Reislingen has disconnected endpoints.

Actually, the Reislingen relation might have all ways present (0 missing) but the ring doesn't close with outer-only. The all-ways retry then includes inner ways which bridge the gap. But this creates an invalid ring.

The fix: in the all-ways retry, still apply the self-intersection check.


ssh node3 "grep 'WARN.*Reislingen' ~/de-par-rolefix2-log 2>/dev/null || /tmp/traccar-geocoder/build/build-index /tmp/de-check ~/pbf/germany.osm.pbf --parallel-admin 2>&1 | grep 'Reislingen'" 2>&1

Check Reislingen in new build


Command running in background with ID: bds3bj3bh. Output is being written to: /tmp/claude-0/-root-traccar-geocoder/af1c4baa-0f9a-4420-a8b3-ce20dc28c8b9/tasks/bds3bj3bh.output


While that runs, the Reislingen ring is likely self-intersecting (2km gap closed by inner ways creates a bowtie shape). The self-intersection check should catch it. Let me verify it's applied to the retry path too — looking at the code, `ring_has_self_intersection` is already called on all assembled rings before they're added. So the Reislingen ring must NOT be self-intersecting — it's just a different ring shape.

Actually, the issue is simpler: with the all-ways retry, Reislingen's inner way `w1448056003` (which was shared with Neuhaus-Reislingen) provides the missing link. This creates a valid ring that shouldn't exist as a standalone admin polygon.

The fix is more nuanced: the all-ways retry should only be used for relations where the outer ways genuinely need inner ways to close (like the Albanian villages), not for relations where outer ways are disconnected. Let me only retry when the relation has MORE inner ways than the gap count.

Actually, the simplest correct fix: only retry with all ways when the relation has **0 outer/empty-role ways** (like La Mare aux Bourguignons). For relations that DO have outer ways but they don't close, the issue is genuinely disconnected geometry, not missing inner bridging.


Edit

                                auto rings = assemble_outer_rings(rel.members, data.way_geometries);
                                // If outer-only assembly failed, retry with all ways
                                // (inner ways can bridge gaps between outer ways)
                                if (rings.empty()) {
                                    rings = assemble_outer_rings(rel.members, data.way_geometries, true);
                                }

                                auto rings = assemble_outer_rings(rel.members, data.way_geometries);
                                // If outer-only assembly failed, retry with all ways.
                                // Osmium ignores roles during assembly — inner ways
                                // sometimes form the actual boundary or bridge gaps.
                                if (rings.empty()) {
                                    // Check if there are non-outer ways we haven't tried
                                    bool has_other_roles = false;
                                    for (const auto& [wid, role] : rel.members) {
                                        if (role != "outer" && !role.empty()) {
                                            if (data.way_geometries.find(wid) != data.way_geometries.end()) {
                                                has_other_roles = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (has_other_roles) {
                                        rings = assemble_outer_rings(rel.members, data.way_geometries, true);
                                    }
                                }