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 -5" 2>&1

Build with fixed code


[ 50%] Building CXX object CMakeFiles/build-index.dir/src/build_index.cpp.o
[100%] Linking CXX executable build-index
[100%] Built target build-index


ssh node3 "rm -rf /tmp/de-parallel-gap && mkdir -p /tmp/de-parallel-gap && /tmp/traccar-geocoder/build/build-index /tmp/de-parallel-gap ~/pbf/germany.osm.pbf --parallel-admin 2>&1 | grep -E 'polygon|Done|Parallel admin'" 2>&1

Run with gap closing


  Assembling admin polygons in parallel (40080 relations, 112858 way geometries)...
  Parallel admin assembly complete: 43274 polygon rings from 40080 relations.
Done reading:
  43274 admin polygon rings
Waiting for admin polygon S2 covering to complete...
Admin polygon S2 covering complete (6103 cells).
admin index: 6103 cells, 43274 polygons
Done.


**43,274!** That's MORE than osmium's 43,221. The gap closing is now forming rings that osmium rejects. Let me compare.


ssh node3 "/tmp/diff_admin /tmp/de-parallel-gap /tmp/de-sequential-final 2>&1 | head -20"

Compare gap closing results


New code: 43274 polygons
Old code: 43221 polygons
Missing from new: 42
Extra in new: 95

=== Missing by admin level ===
  Level 2: 1 polygons
  Level 8: 4 polygons
  Level 9: 4 polygons
  Level 10: 33 polygons

=== Top 20 missing names ===
  2x Großfischbach
  1x Wäschegrund
  1x Werl
  1x Wense-Fallingbostel
  1x Walchensee
  1x Verr
  1x Umbertshausen
  1x Süd


42 still missing (same ones), but now 95 extra (up from 13). The gap closer is creating 82 extra polygons that osmium doesn't. The tolerance is too loose (1km). Let me tighten it — osmium likely doesn't close gaps at all, it uses a different mechanism.

Let me check what the 13 "extra" polygons from the coordinate-matching version were. Those were correct polygons osmium missed. The new 82 extras from gap closing are likely bogus.

Let me reduce the gap tolerance to something very small (matching osmium's exact coordinate matching), and just accept that the remaining 42 missing are from genuine topology differences.


Edit

    // Second pass: try to form rings from remaining unused ways by greedy chaining
    // with gap closing. This handles cases where endpoints don't exactly match
    // (split nodes with slightly different coordinates, partial extracts, etc.)
    for (size_t start_idx = 0; start_idx < outer_ways.size(); start_idx++) {
        if (used[start_idx]) continue;

        used[start_idx] = true;
        const auto* sg = outer_ways[start_idx].geom;
        std::vector<std::pair<double,double>> ring = sg->coords;
        auto ring_first = sg->coords.front();
        auto ring_last = sg->coords.back();

        // Greedy: extend by finding closest unused way endpoint
        bool extended = true;
        while (extended) {
            extended = false;
            int64_t last_key = coord_key(ring_last.first, ring_last.second);
            int64_t first_key = coord_key(ring_first.first, ring_first.second);
            if (ring.size() >= 4 && last_key == first_key) break;

            // Find closest unused way endpoint to ring_last
            double best_dist = 1e18;
            size_t best_idx = SIZE_MAX;
            bool best_reversed = false;
            for (size_t ci = 0; ci < outer_ways.size(); ci++) {
                if (used[ci]) continue;
                const auto* cg = outer_ways[ci].geom;
                if (cg->coords.empty()) continue;

                auto& cf = cg->coords.front();
                auto& cl = cg->coords.back();
                double df = (cf.first - ring_last.first) * (cf.first - ring_last.first) +
                            (cf.second - ring_last.second) * (cf.second - ring_last.second);
                double dl = (cl.first - ring_last.first) * (cl.first - ring_last.first) +
                            (cl.second - ring_last.second) * (cl.second - ring_last.second);
                if (df < best_dist) { best_dist = df; best_idx = ci; best_reversed = false; }
                if (dl < best_dist) { best_dist = dl; best_idx = ci; best_reversed = true; }
            }
            if (best_idx == SIZE_MAX) break;

            // Accept if within ~1km (0.01 degrees squared ≈ 1km at mid-latitudes)
            if (best_dist > 0.01 * 0.01) break;

            used[best_idx] = true;
            const auto& coords = outer_ways[best_idx].geom->coords;
            if (!best_reversed) {
                ring.insert(ring.end(), coords.begin() + 1, coords.end());
                ring_last = coords.back();
            } else {
                for (auto rit = coords.rbegin() + 1; rit != coords.rend(); ++rit) {
                    ring.push_back(*rit);
                }
                ring_last = coords.front();
            }
            extended = true;
        }

        // Check if ring closed (with tolerance)
        double gap = (ring_first.first - ring_last.first) * (ring_first.first - ring_last.first) +
                     (ring_first.second - ring_last.second) * (ring_first.second - ring_last.second);
        if (ring.size() >= 4 && gap < 0.01 * 0.01) {
            // Close the ring by connecting back to first point
            if (gap > 0) ring.push_back(ring_first);
            rings.push_back(std::move(ring));
        } else {
            // Failed — unmark all used ways from this attempt
            // (simplified: we leave them marked since they likely can't form any ring)
        }
    }

    return rings;