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-parallel-seg && mkdir -p /tmp/de-parallel-seg && /tmp/traccar-geocoder/build/build-index /tmp/de-parallel-seg ~/pbf/germany.osm.pbf --parallel-admin 2>&1 | grep -E 'polygon|Done|Parallel admin|error|WARN.*0 missing'" 2>&1

Build and run segment-level assembler with Hierholzer


[ 50%] Building CXX object CMakeFiles/build-index.dir/src/build_index.cpp.o
[100%] Linking CXX executable build-index
[100%] Built target build-index
  Assembling admin polygons in parallel (40080 relations, 112858 way geometries)...
  WARN: relation 'Groningen' (level 4, 132 members): 0 rings from 132 outer ways (42 found, 90 missing)
  WARN: relation 'Höchst' (level 8, 13 members): 0 rings from 13 outer ways (3 found, 10 missing)
  WARN: relation 'Kufstein' (level 8  WARN: relation 'Eben am Achensee' (level 8, 15 members): 0 rings from 15 outer ways (5 found, 10 missing)
  WARN: relation 'Vomp' (level 8, 14 members): 0 rings from 14 outer ways (4 found, 10 missing)
  WARN: relation 'Lembach' (level 8, 25 members): 0 rings from 25 outer ways (5 found, 20 missing)
  WARN: relation 'France - Deutschland' (level 2, 269 members): 0 rings from 269 outer ways (269 found, 0 missing)
  WARN: relation 'Bezirk Rohrbach' (level 6, 59 members): 0 rings from 59 outer ways (19 found, 40 missing)
  WARN: relation 'gmina Police' (level 7, 23 members): 0 rings from 23 outer ways (3 found, 20 missing)
  WARN: relation 'Heerlen' (level 8, 16 members): 0 rings from 16 outer ways (6 found, 10 missing)
  WARN: relation 'Gennep' (level 8, 18 members): 0 rings from 18 outer ways (8 found, 10 missing)
  WARN: relation 'Coevorden' (level 8, 44 members): 0 rings from 44 outer ways (4 found, 40 missing)
  WARN: relation 'Bezirk Ried' (level 6, 51 members): 0 rings from 51 outer ways (11 found, 40 missing)
  WARN: relation 'Bezirk Schärding' (level 6, 52 members): 0 rings from 52 outer ways (22 found, 30 missing)
  WARN: relation '  WARN: relation '  WARN: relation 'Pomezí na Šumavě' (level 10, 6 members): 0 rings from 6 outer ways (3 found, Sruby na Šumavě' (level 310 missing)  WARN: relation 'Černý Potok' (level 10, 8 members): 0 rings from 8 outer ways (6 found, 2 missing)
  WARN: relation 'Hrádek nad Nisou' (level 8, 28 members): 0 rings from 28 outer ways (8 found, 20 missing)
  WARN: relation 'Nové Hamry' (level 8, 14 members): 0 rings from 14 outer ways (4 found, 10 missing)
  WARN: relation 'Prášily' (level 8, 15 members): 0 rings from 15 outer ways (5 found, 10 missing)
  WARN: relation 'Plesná' (level 8, 20 members): 0 rings from 20 outer ways (10 found, 10 missing)
  WARN: relation 'Krupka' (level 8, 26 members): 0 rings from 26 outer ways (6 found, 20 missing)
  WARN: relation 'Mařenice' (level 8, 15 members): 0 rings from 15 outer ways (5 found, 10 missing)
  WARN: relation 'Kryštofovy Hamry' (level 8, 18 members): 0 rings from 18 outer ways (8 found, 10 missing)
  WARN: relation 'okres Domažlice' (level 5, 121 members): 0 rings from 121 outer ways (31 found, 90 missing)
  WARN: relation 'powiat zgorzelecki' (level 6, 120 members): 0 rings from 120 outer ways (40 found, 80 missing)
  WARN: relation 'Canton Clervaux' (level 6, 25 members): 0 rings from 25 outer ways (5 found, 20 missing)
  WARN: relation '55546' (level 11, 42 members): 0 rings from 42 outer ways (42 found, 0 missing)
  WARN: relation '55543' (level 11, 13 members): 0 rings from 13 outer ways (13 found, 0 missing)
  WARN: relation 'Sturzelbronn' (level 8, 14 members): 0 rings from 14 outer ways (4 found, 10 missing)
  WARN: relation 'Verviers' (level 7, 147 members): 0 rings from 123 outer ways (53 found, 70 missing)
  WARN: relation 'Finsterwolde' (level 10, 16 members): 0 rings from 16 outer ways (6 found, 10 missing)
  WARN: relation 'Sélestat-Erstein' (level 7, 80 members): 0 rings from 80 outer ways (20 found, 60 missing)
  WARN: relation 'Bezirk Rheinfelden' (level 6, 46 members): 0 rings from 46 outer ways (16 found, 30 missing)
  WARN: relation 'Thurgau' (level 4, 101 members): 0 rings from 100 outer ways (20 found, 80 missing)
  WARN: relation 'Deutschland - Luxembourg / Luxemburg / Lëtzebuerg' (level 2, 25 members): 0 rings from 25 outer ways (25 found, 0 missing)
  WARN: relation 'Nederland' (level 2, 470 members): 0 rings from 444 outer ways (234 found, 210 missing)
  WARN: relation 'powiat gryfiński' (level 6, 47 members): 0 rings from 47 outer ways (27 found, 20 missing)
  WARN: relation 'gmina Przewóz' (level 7, 21 members): 0 rings from 21 outer ways (11 found, 10 missing)
  WARN: relation 'Grand Est' (level 4, 941 members): 0 rings from 941 outer ways (271 found, 670 missing)
  WARN: relation 'Derichsweiler' (level 10, 2 members): 0 rings from 2 outer ways (2 found, 0 missing)
  WARN: relation 'Düren' (level 10, 2 members): 0 rings from 2 outer ways (2 found, 0 missing)
  WARN: relation '6671' (level 11, 18 members): 0 rings from 18 outer ways (8 found, 10 missing)
  WARN: relation '6973' (level 11, 13 members): 0 rings from 13 outer ways (3 found, 10 missing)
  WARN: relation 'Beekdaelen' (level 8, 28 members): 0 rings from 28 outer ways (8 found, 20 missing)
  WARN: relation '57800' (level 11, 17 members): 0 rings from 17 outer ways (7 found, 10 missing)
  WARN: relation '68490' (level 11, 19 members): 0 rings from 19 outer ways (9 found, 10 missing)
  WARN: relation 'Eemsdelta' (level 8, 48 members): 0 rings from 48 outer ways (8 found, 40 missing)
  WARN: relation 'Hehlingen' (level 9, 11 members): 0 rings from 11 outer ways (11 found, 0 missing)
  WARN: relation 'Reislingen' (level 10, 6 members): 0 rings from 5 outer ways (5 found, 0 missing)
  WARN: relation 'Neuhaus-Reislingen' (level 9, 7 members): 0 rings from 7 outer ways (7 found, 0 missing)
  WARN: relation 'SO POÚ Varnsdorf' (level 7, 31 members): 0 rings from 31 outer ways (11 found, 20 missing)
  WARN: relation 'SO POÚ Krupka' (level 7, 26 members): 0 rings from 26 outer ways (6 found, 20 missing)
  WARN: relation 'SO ORP Varnsdorf' (level 6, 31 members): 0 rings from 31 outer ways (11 found, 20 missing)
  WARN: relation 'SO POÚ Vejprty' (level 7, 33 members): 0 rings from 33 outer ways (13 found, 20 missing)
  WARN: relation 'SO ORP Chomutov' (level 6, 60 members): 0 rings from 60 outer ways (10 found, 50 missing)
  WARN: relation 'SO POÚ Chomutov' (level 7, 60 members): 0 rings from 60 outer ways (10 found, 50 missing)
  WARN: relation 'SO POÚ Cvikov' (level 7, 27 members): 0 rings from 27 outer ways (7 found, 20 missing)
  WARN: relation 'SO POÚ Jablonné v Podještědí' (level 7, 27 members): 0 rings from 27 outer ways (7 found, 20 missing)
  WARN: relation 'SO ORP Liberec' (level 6, 91 members): 0 rings from 91 outer ways (11 found, 80 missing)
  Parallel admin assembly complete: 43194 polygon rings from 40080 relations.
Done reading:
  43194 admin polygon rings
Waiting for admin polygon S2 covering to complete...
Admin polygon S2 covering complete (6103 cells).
admin index: 6103 cells, 43194 polygons
Done.


**43,194** — up from 43,192 (gained 2 more). But still short. The "0 missing" cases that remain are:
- France-Deutschland (269 ways), Deutschland-Luxembourg (25 ways) — large borders
- 55543/55546 (postal codes) — 300m gaps
- Derichsweiler/Düren (2 ways each) — 170m+ gaps
- Reislingen/Hehlingen/Neuhaus-Reislingen — 2km+ gaps

These all have genuine coordinate gaps — the endpoints simply don't match. Osmium must be handling these differently. Let me check if osmium creates these as "broken" areas that our BuildHandler still accepts.


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

Quick diff check


New code: 43194 polygons
Old code: 43221 polygons
Missing from new: 46
Extra in new: 19

=== Missing by admin level ===
  Level 2: 1 polygons
  Level 6: 3 polygons
  Level 7: 1 polygons
  Level 8: 7 polygons
  Level 9: 1 polygons
  Level 10: 32 polygons
  Level 11: 1 polygons

=== Top 20 missing names ===
  2x Untrasried
  2x Landkreis Oberallgäu
  2x Großfischbach
  1x Wäschegrund
  1x Werl


The Hierholzer approach produces slightly worse results (46 vs 42 missing) — it forms different ring decompositions than osmium. Let me go back to the coordinate-based endpoint approach (which had 42 missing) and add **gap closing** for small gaps — connecting unclosed ring endpoints that are within a reasonable distance.


Edit

    // Store coordinate values for each key
    std::unordered_map<int64_t, std::pair<double,double>> key_to_coord;
    for (const auto& [way_id, role] : members) {
        if (role != "outer" && !role.empty()) continue;
        auto it = way_geoms.find(way_id);
        if (it == way_geoms.end()) continue;
        for (const auto& [lat, lng] : it->second.coords) {
            key_to_coord[coord_key(lat, lng)] = {lat, lng};
        }
    }

    // Track edge iterator position per node for O(E) traversal
    std::unordered_map<int64_t, size_t> edge_pos;
    for (auto& [k, _] : graph) edge_pos[k] = 0;

    std::vector<std::vector<std::pair<double,double>>> rings;

    // Find all closed rings using modified Hierholzer's algorithm
    // Process each connected component
    for (auto& [start_key, _] : graph) {
        // Check if this node has any unused edges
        bool has_edge = false;
        for (const auto& edge : graph[start_key]) {
            if (edge_count[make_edge_id(start_key, edge.target_key)] > 0) {
                has_edge = true; break;
            }
        }
        if (!has_edge) continue;

        // Hierholzer's: find an Euler circuit starting from start_key
        std::vector<int64_t> stack = {start_key};
        std::vector<int64_t> circuit;

        while (!stack.empty()) {
            int64_t v = stack.back();
            auto& edges = graph[v];
            bool found = false;

            // Find next unused edge from v
            while (edge_pos[v] < edges.size()) {
                auto& edge = edges[edge_pos[v]];
                auto eid = make_edge_id(v, edge.target_key);
                auto eit = edge_count.find(eid);
                if (eit != edge_count.end() && eit->second > 0) {
                    eit->second--;
                    stack.push_back(edge.target_key);
                    edge_pos[v]++;
                    found = true;
                    break;
                }
                edge_pos[v]++;
            }

            if (!found) {
                circuit.push_back(v);
                stack.pop_back();
            }
        }

        // The circuit may contain multiple loops joined at shared nodes.
        // Split into individual closed rings at repeated nodes.
        if (circuit.size() < 4) continue;

        // Find closed sub-circuits: scan for repeated coordinate keys
        std::unordered_map<int64_t, size_t> last_seen;
        for (size_t i = 0; i < circuit.size(); i++) {
            auto it2 = last_seen.find(circuit[i]);
            if (it2 != last_seen.end()) {
                // Found a closed loop from last_seen to i
                size_t ring_start = it2->second;
                std::vector<std::pair<double,double>> ring;
                for (size_t j = ring_start; j <= i; j++) {
                    auto cit = key_to_coord.find(circuit[j]);
                    if (cit != key_to_coord.end()) {
                        ring.push_back(cit->second);
                    }
                }
                if (ring.size() >= 4) {
                    rings.push_back(std::move(ring));
                }
                // Mark these positions as consumed by setting them to -1
                for (size_t j = ring_start; j < i; j++) {
                    last_seen.erase(circuit[j]);
                    circuit[j] = -1;
                }
                // Keep circuit[i] in last_seen for potential larger rings
            }
            last_seen[circuit[i]] = i;
        }
    }

    return rings;
}

    std::vector<bool> used(outer_ways.size(), false);
    std::vector<std::vector<std::pair<double,double>>> rings;

    // Backtracking ring closure using coordinate matching
    struct BacktrackState {
        const std::vector<WayRef>& ways;
        const std::unordered_map<int64_t, std::vector<std::pair<size_t, bool>>>& adj;
        std::vector<bool>& used;
        decltype(coord_key)& key_fn;

        bool try_close(int64_t first_key, int64_t last_key,
                       std::vector<std::pair<size_t, bool>>& path, int depth) {
            if (depth > 5000) return false;
            if (!path.empty() && first_key == last_key) return true;

            auto it = adj.find(last_key);
            if (it == adj.end()) return false;

            for (const auto& [wi, is_last] : it->second) {
                if (used[wi]) continue;
                const auto* g = ways[wi].geom;
                bool reversed = is_last;
                auto& endpoint = reversed ? g->coords.front() : g->coords.back();
                int64_t new_key = key_fn(endpoint.first, endpoint.second);

                used[wi] = true;
                path.push_back({wi, reversed});

                if (try_close(first_key, new_key, path, depth + 1)) return true;

                path.pop_back();
                used[wi] = false;
            }
            return false;
        }
    };

    BacktrackState state{outer_ways, coord_adj, used, coord_key};

    for (size_t start_idx = 0; start_idx < outer_ways.size(); start_idx++) {
        if (used[start_idx]) continue;

        const auto* sg = outer_ways[start_idx].geom;
        int64_t first_key = coord_key(sg->coords.front().first, sg->coords.front().second);
        int64_t last_key = coord_key(sg->coords.back().first, sg->coords.back().second);

        // Single way already closed
        if (sg->coords.size() >= 4 && first_key == last_key) {
            used[start_idx] = true;
            rings.push_back(sg->coords);
            continue;
        }

        // Try to build a closed ring
        used[start_idx] = true;
        std::vector<std::pair<size_t, bool>> path;
        if (state.try_close(first_key, last_key, path, 0)) {
            std::vector<std::pair<double,double>> ring = sg->coords;
            for (const auto& [wi, reversed] : path) {
                const auto& coords = outer_ways[wi].geom->coords;
                if (!reversed) {
                    ring.insert(ring.end(), coords.begin() + 1, coords.end());
                } else {
                    for (auto rit = coords.rbegin() + 1; rit != coords.rend(); ++rit) {
                        ring.push_back(*rit);
                    }
                }
            }
            if (ring.size() >= 4) {
                rings.push_back(std::move(ring));
            }
        } else {
            used[start_idx] = false;
        }
    }

    // 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;
}