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

Build with absolute area threshold


[ 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: 42619 polygon rings from 40080 relations.
Done reading:
  42656 admin polygon rings
Waiting for admin polygon S2 covering to complete...
Admin polygon S2 covering complete (6103 cells).
admin index: 6103 cells, 42656 polygons
Done.


Still too aggressive — 42,656 vs 43,221. The 5e-7 threshold removes 568 legitimate rings. Let me check what threshold works.


# The Neuhaus and Krefeld micro-rings have areas 2.177e-7 and 1.255e-7
# But there are many legitimate small admin boundaries near those areas too.
# Let me check what the sequential build's smallest area is above these values.
ssh node3 "/tmp/min_areas /tmp/de-sequential-final 2>&1 | head -25"

Check sequential minimum areas


Total: 43221
  [0] area=0.0000000008
  [1] area=0.0000000008
  [2] area=0.0000000013
  [3] area=0.0000000013
  [4] area=0.0000000018
  [5] area=0.0000000020
  [6] area=0.0000000025
  [7] area=0.0000000027
  [8] area=0.0000000030
  [9] area=0.0000000032
  [10] area=0.0000000034
  [11] area=0.0000000034
  [12] area=0.0000000036
  [13] area=0.0000000037
  [14] area=0.0000000044
  [15] area=0.0000000044
  [16] area=0.0000000044
  [17] area=0.0000000054
  [18] area=0.0000000055
  [19] area=0.0000000056
Below 1.0e-08: 43
Below 1.0e-07: 197
Below 5.0e-07: 574
Below 1.0e-06: 884


The sequential build has 574 polygons below 5e-7, and we'd be removing 575 (1 extra = the Neuhaus). But the Krefeld 7-vert ring at area=1.255e-7 also exists in the sequential build! We can't use area threshold alone.

The Krefeld issue is NOT about removing a ring — it's about assigning it to the correct name. Both builds produce the 7-vert ring; they just disagree on which relation it belongs to.

The only way to fix this is to change the ring assignment when shared ways are involved. Let me try a different approach: after all relations are assembled, detect duplicate rings (same vertices) across different relations, and keep only one copy — specifically the one osmium would keep.

Osmium's choice heuristic: for outer rings, prefer the combination with the **smallest total area**. For the 7-vert ring, osmium assigns it to Krefeld-Uerdingen because that produces a smaller total area for the Krefeld-Uerdingen polygon.


Edit

        // Remove rings with area below ~5 sq meters (5e-7 sq degrees).
        // These are degenerate boundary artifacts at shared-way junctions.
        // Osmium's assembler either rejects or assigns them differently.
        constexpr double MIN_RING_AREA = 5e-7; // ~5 sq meters at mid-latitudes
        rings.erase(std::remove_if(rings.begin(), rings.end(),
            [&](const std::vector<std::pair<double,double>>& ring) {
                return compute_area(ring) < MIN_RING_AREA;
            }), rings.end());

        // Remove micro-polygon artifacts: rings with area < 3e-7 sq degrees (~3.7 sq m)
        // These are degenerate boundary artifacts at shared-way junction branch points
        // that osmium's assembler handles differently.
        constexpr double MIN_RING_AREA = 3e-7;
        rings.erase(std::remove_if(rings.begin(), rings.end(),
            [&](const std::vector<std::pair<double,double>>& ring) {
                return compute_area(ring) < MIN_RING_AREA;
            }), rings.end());