The self-intersection check is already applied in `assemble_outer_rings` (line 658: `ring_has_self_intersection`). If Reislingen's retry creates a self-intersecting ring, it should be caught. Let me verify on Germany first.


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-rolefix5 && mkdir -p /tmp/de-par-rolefix5 && /tmp/traccar-geocoder/build/build-index /tmp/de-par-rolefix5 ~/pbf/germany.osm.pbf --parallel-admin 2>&1 | grep -E 'polygon|Done'" 2>&1

Build Germany with always-retry


[ 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.


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

Quick Germany check


  '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


Reislingen is back (+1). The self-intersection check doesn't catch it — the ring formed with the inner way isn't self-intersecting, it's just wrong (hole merged with outer boundary). I need to exclude this case.

The difference: Albanian villages have inner ways that form the ONLY path to close the ring. Reislingen has inner ways that create an ALTERNATIVE path (outer ways exist but are disconnected at ~2km). The Albanian inner ways bridge a ~0m gap; Reislingen's inner way bridges a ~2km gap.

Quick fix: only retry if the all-ways assembly produces rings that the outer-only didn't. AND filter out rings from the retry that have significantly different area than expected.

Actually simplest: just go back to the "no outer ways" check and handle the Albanian cases by also including inner ways when they share endpoints with outer ways. But that's complex.

Given we're running out of time, let me keep the no-outer-only fallback (which fixes 5 of 9) and start migrating data to node 2a.


Edit

                                if (rings.empty()) {
                                    // Retry with all ways (inner + outer) when outer-only fails.
                                    // Osmium ignores roles during assembly. Inner ways sometimes
                                    // form the actual boundary or bridge gaps between outer ways.
                                    rings = assemble_outer_rings(rel.members, data.way_geometries, true);
                                }

                                if (rings.empty()) {
                                    // Retry with all ways only when outer-only produced
                                    // 0 rings AND the relation has non-outer ways available.
                                    // Only retry when there are NO outer ways (the boundary IS
                                    // the inner-role ways). When outer ways exist but don't close,
                                    // including inner ways creates merged outer+hole geometry.
                                    bool has_outer_geom = false;
                                    for (const auto& [wid, role] : rel.members) {
                                        if (role == "outer" || role.empty()) {
                                            if (data.way_geometries.count(wid)) {
                                                has_outer_geom = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (!has_outer_geom) {
                                        rings = assemble_outer_rings(rel.members, data.way_geometries, true);
                                    }
                                }