    34→
    35→    std::unordered_set<uint32_t> used_way_ids;
    36→    for (const auto& [cell_id, ids] : full.cell_to_ways) {
    37→        if (cell_in_bbox(cell_id, bbox)) {
    38→            for (uint32_t id : ids) used_way_ids.insert(id);
    39→        }
    40→    }
    41→
    42→    std::unordered_set<uint32_t> used_addr_ids;
    43→    for (const auto& [cell_id, ids] : full.cell_to_addrs) {
    44→        if (cell_in_bbox(cell_id, bbox)) {
    45→            for (uint32_t id : ids) used_addr_ids.insert(id);
    46→        }
    47→    }
    48→
    49→    std::unordered_set<uint32_t> used_interp_ids;
    50→    for (const auto& [cell_id, ids] : full.cell_to_interps) {
    51→        if (cell_in_bbox(cell_id, bbox)) {
    52→            for (uint32_t id : ids) used_interp_ids.insert(id);
    53→        }
    54→    }
    55→
    56→    std::unordered_set<uint32_t> used_admin_ids;
    57→    for (const auto& [cell_id, ids] : full.cell_to_admin) {
    58→        if (cell_in_bbox(cell_id, bbox)) {
    59→            for (uint32_t id : ids) used_admin_ids.insert(id & ID_MASK);
    60→        }
    61→    }
    62→
    63→    // Remap ways
    64→    std::unordered_map<uint32_t, uint32_t> way_remap;
    65→    { std::vector<uint32_t> sorted_ids(used_way_ids.begin(), used_way_ids.end());
    66→      std::sort(sorted_ids.begin(), sorted_ids.end());
    67→      for (uint32_t old_id : sorted_ids) {
    68→          uint32_t new_id = static_cast<uint32_t>(out.ways.size());

    std::unordered_set<uint32_t> used_way_ids;
    for (const auto& [cell_id, ids] : full.cell_to_ways) {
        if (cell_in_bbox(cell_id, bbox)) {
            for (uint32_t id : ids) used_way_ids.insert(id);
        }
    }

    std::unordered_set<uint32_t> used_addr_ids;
    for (const auto& [cell_id, ids] : full.cell_to_addrs) {
        if (cell_in_bbox(cell_id, bbox)) {
            for (uint32_t id : ids) used_addr_ids.insert(id);
        }
    }

    std::unordered_set<uint32_t> used_interp_ids;
    for (const auto& [cell_id, ids] : full.cell_to_interps) {
        if (cell_in_bbox(cell_id, bbox)) {
            for (uint32_t id : ids) used_interp_ids.insert(id);
        }
    }

    std::unordered_set<uint32_t> used_admin_ids;
    for (const auto& [cell_id, ids] : full.cell_to_admin) {
        if (cell_in_bbox(cell_id, bbox)) {
            for (uint32_t id : ids) used_admin_ids.insert(id & ID_MASK);
        }
    }

    // Remap ways
    std::unordered_map<uint32_t, uint32_t> way_remap;
    { std::vector<uint32_t> sorted_ids(used_way_ids.begin(), used_way_ids.end());
      std::sort(sorted_ids.begin(), sorted_ids.end());
      for (uint32_t old_id : sorted_ids) {
          uint32_t new_id = static_cast<uint32_t>(out.ways.size());


The first 4 loops (lines 35-61) can run in parallel — they each build an independent set. Let me add that:


Edit

#include "continent_filter.h"

#include <algorithm>
#include <cstring>
#include <unordered_set>

#include <s2/s2cell_id.h>
#include <s2/s2latlng.h>

#include "continent_filter.h"

#include <algorithm>
#include <cstring>
#include <future>
#include <unordered_set>

#include <s2/s2cell_id.h>
#include <s2/s2latlng.h>


The file /root/traccar-geocoder/builder/src/continent_filter.cpp has been updated successfully.

#include "continent_filter.h"

#include <algorithm>
#include <cstring>
#include <future>
#include <unordered_set>

#include <s2/s2cell_id.h>
#include <s2/s2latlng.h>

#include "continent_filter.h"

#include <algorithm>
#include <cstring>
#include <unordered_set>

#include <s2/s2cell_id.h>
#include <s2/s2latlng.h>

const ContinentBBox kContinents[] = {
    {"africa",            -35.0,  37.5,  -25.0,  55.0},
    {"asia",              -12.0,  82.0,   25.0, 180.0},
    {"europe",             35.0,  72.0,  -25.0,  45.0},
    {"north-america",       7.0,  84.0, -170.0, -50.0},
    {"south-america",     -56.0,  13.0,  -82.0, -34.0},
    {"oceania",           -50.0,   0.0,  110.0, 180.0},
    {"central-america",     7.0,  23.5, -120.0, -57.0},
    {"antarctica",        -90.0, -60.0, -180.0, 180.0},
};

const size_t kContinentCount = sizeof(kContinents) / sizeof(kContinents[0]);

static bool cell_in_bbox(uint64_t cell_id, const ContinentBBox& bbox) {
    S2CellId cell(cell_id);
    S2LatLng center = cell.ToLatLng();
    double lat = center.lat().degrees();
    double lng = center.lng().degrees();
    return lat >= bbox.min_lat && lat <= bbox.max_lat &&
           lng >= bbox.min_lng && lng <= bbox.max_lng;
}

ParsedData filter_by_bbox(const ParsedData& full, const ContinentBBox& bbox) {
    ParsedData out;

    std::unordered_set<uint32_t> used_way_ids;
    for (const auto& [cell_id, ids] : full.cell_to_ways) {
        if (cell_in_bbox(cell_id, bbox)) {
            for (uint32_t id : ids) used_way_ids.insert(id);
        }
    }

    std::unordered_set<uint32_t> used_addr_ids;
    for (const auto& [cell_id, ids] : full.cell_to_addrs) {
        if (cell_in_bbox(cell_id, bbox)) {
            for (uint32_t id : ids) used_addr_ids.insert(id);
        }
    }

    std::unordered_set<uint32_t> used_interp_ids;
    for (const auto& [cell_id, ids] : full.cell_to_interps) {
        if (cell_in_bbox(cell_id, bbox)) {
            for (uint32_t id : ids) used_interp_ids.insert(id);
        }
    }

    std::unordered_set<uint32_t> used_admin_ids;
    for (const auto& [cell_id, ids] : full.cell_to_admin) {
        if (cell_in_bbox(cell_id, bbox)) {
            for (uint32_t id : ids) used_admin_ids.insert(id & ID_MASK);
        }
    }

    // Remap ways
    std::unordered_map<uint32_t, uint32_t> way_remap;
    { std::vector<uint32_t> sorted_ids(used_way_ids.begin(), used_way_ids.end());
      std::sort(sorted_ids.begin(), sorted_ids.end());
      for (uint32_t old_id : sorted_ids) {
          uint32_t new_id = static_cast<uint32_t>(out.ways.size());
          way_remap[old_id] = new_id;
          const auto& w = full.ways[old_id];
          WayHeader nw = w;
          nw.node_offset = static_cast<uint32_t>(out.street_nodes.size());
          out.ways.push_back(nw);
          for (uint8_t n = 0; n < w.node_count; n++)
              out.street_nodes.push_back(full.street_nodes[w.node_offset + n]);
      } }

    // Remap addrs
    std::unordered_map<uint32_t, uint32_t> addr_remap;
    { std::vector<uint32_t> sorted_ids(used_addr_ids.begin(), used_addr_ids.end());
      std::sort(sorted_ids.begin(), sorted_ids.end());
      for (uint32_t old_id : sorted_ids) {
          uint32_t new_id = static_cast<uint32_t>(out.addr_points.size());
          addr_remap[old_id] = new_id;
          out.addr_points.push_back(full.addr_points[old_id]);
      } }

    // Remap interps
    std::unordered_map<uint32_t, uint32_t> interp_remap;
    { std::vector<uint32_t> sorted_ids(used_interp_ids.begin(), used_interp_ids.end());
      std::sort(sorted_ids.begin(), sorted_ids.end());
      for (uint32_t old_id : sorted_ids) {
          uint32_t new_id = static_cast<uint32_t>(out.interp_ways.size());
          interp_remap[old_id] = new_id;
          const auto& iw = full.interp_ways[old_id];
          InterpWay niw = iw;
          niw.node_offset = static_cast<uint32_t>(out.interp_nodes.size());
          out.interp_ways.push_back(niw);
          for (uint8_t n = 0; n < iw.node_count; n++)
              out.interp_nodes.push_back(full.interp_nodes[iw.node_offset + n]);
      } }

    // Remap admins
    std::unordered_map<uint32_t, uint32_t> admin_remap;
    { std::vector<uint32_t> sorted_ids(used_admin_ids.begin(), used_admin_ids.end());
      std::sort(sorted_ids.begin(), sorted_ids.end());
      for (uint32_t old_id : sorted_ids) {
          uint32_t new_id = static_cast<uint32_t>(out.admin_polygons.size());
          admin_remap[old_id] = new_id;
          const auto& ap = full.admin_polygons[old_id];
          AdminPolygon nap = ap;
          nap.vertex_offset = static_cast<uint32_t>(out.admin_vertices.size());
          out.admin_polygons.push_back(nap);
          for (uint32_t v = 0; v < ap.vertex_count; v++)
              out.admin_vertices.push_back(full.admin_vertices[ap.vertex_offset + v]);
      } }

    // Remap cell maps
    for (const auto& [cell_id, ids] : full.cell_to_ways) {
        if (!cell_in_bbox(cell_id, bbox)) continue;
        std::vector<uint32_t> new_ids;
        for (uint32_t id : ids) { auto it = way_remap.find(id); if (it != way_remap.end()) new_ids.push_back(it->second); }
        if (!new_ids.empty()) out.cell_to_ways[cell_id] = std::move(new_ids);
    }
    for (const auto& [cell_id, ids] : full.cell_to_addrs) {
        if (!cell_in_bbox(cell_id, bbox)) continue;
        std::vector<uint32_t> new_ids;
        for (uint32_t id : ids) { auto it = addr_remap.find(id); if (it != addr_remap.end()) new_ids.push_back(it->second); }
        if (!new_ids.empty()) out.cell_to_addrs[cell_id] = std::move(new_ids);
    }
    for (const auto& [cell_id, ids] : full.cell_to_interps) {
        if (!cell_in_bbox(cell_id, bbox)) continue;
        std::vector<uint32_t> new_ids;
        for (uint32_t id : ids) { auto it = interp_remap.find(id); if (it != interp_remap.end()) new_ids.push_back(it->second); }
        if (!new_ids.empty()) out.cell_to_interps[cell_id] = std::move(new_ids);
    }
    for (const auto& [cell_id, ids] : full.cell_to_admin) {
        if (!cell_in_bbox(cell_id, bbox)) continue;
        std::vector<uint32_t> new_ids;
        for (uint32_t id : ids) {
            uint32_t raw_id = id & ID_MASK;
            uint32_t flags = id & INTERIOR_FLAG;
            auto it = admin_remap.find(raw_id);
            if (it != admin_remap.end()) new_ids.push_back(it->second | flags);
        }
        if (!new_ids.empty()) out.cell_to_admin[cell_id] = std::move(new_ids);
    }

    // Rebuild compact string pool
    std::unordered_set<uint32_t> used_offsets;
    for (const auto& w : out.ways) used_offsets.insert(w.name_id);
    for (const auto& a : out.addr_points) { used_offsets.insert(a.housenumber_id); used_offsets.insert(a.street_id); }
    for (const auto& iw : out.interp_ways) used_offsets.insert(iw.street_id);
    for (const auto& ap : out.admin_polygons) used_offsets.insert(ap.name_id);

    const auto& old_sp = full.string_pool.data();
    std::unordered_map<uint32_t, uint32_t> string_remap;
    auto& new_sp = out.string_pool.mutable_data();
    new_sp.clear();

    std::vector<uint32_t> sorted_offsets(used_offsets.begin(), used_offsets.end());
    std::sort(sorted_offsets.begin(), sorted_offsets.end());
    for (uint32_t old_off : sorted_offsets) {
        uint32_t new_off = static_cast<uint32_t>(new_sp.size());
        string_remap[old_off] = new_off;
        const char* str = old_sp.data() + old_off;
        size_t len = std::strlen(str);
        new_sp.insert(new_sp.end(), str, str + len + 1);
    }

    for (auto& w : out.ways) w.name_id = string_remap[w.name_id];
    for (auto& a : out.addr_points) { a.housenumber_id = string_remap[a.housenumber_id]; a.street_id = string_remap[a.street_id]; }
    for (auto& iw : out.interp_ways) iw.street_id = string_remap[iw.street_id];
    for (auto& ap : out.admin_polygons) ap.name_id = string_remap[ap.name_id];

    return out;
}

#include <algorithm>
#include <cstring>
+#include <future>
#include <unordered_set>
#include <s2/s2cell_id.h>


    std::unordered_set<uint32_t> used_way_ids;
    for (const auto& [cell_id, ids] : full.cell_to_ways) {
        if (cell_in_bbox(cell_id, bbox)) {
            for (uint32_t id : ids) used_way_ids.insert(id);
        }
    }

    std::unordered_set<uint32_t> used_addr_ids;
    for (const auto& [cell_id, ids] : full.cell_to_addrs) {
        if (cell_in_bbox(cell_id, bbox)) {
            for (uint32_t id : ids) used_addr_ids.insert(id);
        }
    }

    std::unordered_set<uint32_t> used_interp_ids;
    for (const auto& [cell_id, ids] : full.cell_to_interps) {
        if (cell_in_bbox(cell_id, bbox)) {
            for (uint32_t id : ids) used_interp_ids.insert(id);
        }
    }

    std::unordered_set<uint32_t> used_admin_ids;
    for (const auto& [cell_id, ids] : full.cell_to_admin) {
        if (cell_in_bbox(cell_id, bbox)) {
            for (uint32_t id : ids) used_admin_ids.insert(id & ID_MASK);
        }
    }

    // Filter cell maps in parallel — each builds an independent ID set
    auto filter_cells = [&](const std::unordered_map<uint64_t, std::vector<uint32_t>>& cell_map,
                            bool mask_interior = false) {
        std::unordered_set<uint32_t> ids;
        for (const auto& [cell_id, cell_ids] : cell_map) {
            if (cell_in_bbox(cell_id, bbox)) {
                for (uint32_t id : cell_ids)
                    ids.insert(mask_interior ? (id & ID_MASK) : id);
            }
        }
        return ids;
    };

    auto f_ways = std::async(std::launch::async, [&]{ return filter_cells(full.cell_to_ways); });
    auto f_addrs = std::async(std::launch::async, [&]{ return filter_cells(full.cell_to_addrs); });
    auto f_interps = std::async(std::launch::async, [&]{ return filter_cells(full.cell_to_interps); });
    auto f_admin = std::async(std::launch::async, [&]{ return filter_cells(full.cell_to_admin, true); });

    auto used_way_ids = f_ways.get();
    auto used_addr_ids = f_addrs.get();
    auto used_interp_ids = f_interps.get();
    auto used_admin_ids = f_admin.get();


    // Filter cell maps in parallel — each builds an independent ID set
    auto filter_cells = [&](const std::unordered_map<uint64_t, std::vector<uint32_t>>& cell_map,
                            bool mask_interior = false) {
        std::unordered_set<uint32_t> ids;
        for (const auto& [cell_id, cell_ids] : cell_map) {
            if (cell_in_bbox(cell_id, bbox)) {
                for (uint32_t id : cell_ids)
                    ids.insert(mask_interior ? (id & ID_MASK) : id);
            }
        }
        return ids;
    };

    auto f_ways = std::async(std::launch::async, [&]{ return filter_cells(full.cell_to_ways); });
    auto f_addrs = std::async(std::launch::async, [&]{ return filter_cells(full.cell_to_addrs); });
    auto f_interps = std::async(std::launch::async, [&]{ return filter_cells(full.cell_to_interps); });
    auto f_admin = std::async(std::launch::async, [&]{ return filter_cells(full.cell_to_admin, true); });

    auto used_way_ids = f_ways.get();
    auto used_addr_ids = f_addrs.get();
    auto used_interp_ids = f_interps.get();
    auto used_admin_ids = f_admin.get();

#include "continent_filter.h"

#include <algorithm>
#include <cstring>
#include <future>
#include <unordered_set>

#include <s2/s2cell_id.h>
#include <s2/s2latlng.h>

const ContinentBBox kContinents[] = {
    {"africa",            -35.0,  37.5,  -25.0,  55.0},
    {"asia",              -12.0,  82.0,   25.0, 180.0},
    {"europe",             35.0,  72.0,  -25.0,  45.0},
    {"north-america",       7.0,  84.0, -170.0, -50.0},
    {"south-america",     -56.0,  13.0,  -82.0, -34.0},
    {"oceania",           -50.0,   0.0,  110.0, 180.0},
    {"central-america",     7.0,  23.5, -120.0, -57.0},
    {"antarctica",        -90.0, -60.0, -180.0, 180.0},
};

const size_t kContinentCount = sizeof(kContinents) / sizeof(kContinents[0]);

static bool cell_in_bbox(uint64_t cell_id, const ContinentBBox& bbox) {
    S2CellId cell(cell_id);
    S2LatLng center = cell.ToLatLng();
    double lat = center.lat().degrees();
    double lng = center.lng().degrees();
    return lat >= bbox.min_lat && lat <= bbox.max_lat &&
           lng >= bbox.min_lng && lng <= bbox.max_lng;
}

ParsedData filter_by_bbox(const ParsedData& full, const ContinentBBox& bbox) {
    ParsedData out;

    std::unordered_set<uint32_t> used_way_ids;
    for (const auto& [cell_id, ids] : full.cell_to_ways) {
        if (cell_in_bbox(cell_id, bbox)) {
            for (uint32_t id : ids) used_way_ids.insert(id);
        }
    }

    std::unordered_set<uint32_t> used_addr_ids;
    for (const auto& [cell_id, ids] : full.cell_to_addrs) {
        if (cell_in_bbox(cell_id, bbox)) {
            for (uint32_t id : ids) used_addr_ids.insert(id);
        }
    }

    std::unordered_set<uint32_t> used_interp_ids;
    for (const auto& [cell_id, ids] : full.cell_to_interps) {
        if (cell_in_bbox(cell_id, bbox)) {
            for (uint32_t id : ids) used_interp_ids.insert(id);
        }
    }

    std::unordered_set<uint32_t> used_admin_ids;
    for (const auto& [cell_id, ids] : full.cell_to_admin) {
        if (cell_in_bbox(cell_id, bbox)) {
            for (uint32_t id : ids) used_admin_ids.insert(id & ID_MASK);
        }
    }

    // Remap ways
    std::unordered_map<uint32_t, uint32_t> way_remap;
    { std::vector<uint32_t> sorted_ids(used_way_ids.begin(), used_way_ids.end());
      std::sort(sorted_ids.begin(), sorted_ids.end());
      for (uint32_t old_id : sorted_ids) {
          uint32_t new_id = static_cast<uint32_t>(out.ways.size());
          way_remap[old_id] = new_id;
          const auto& w = full.ways[old_id];
          WayHeader nw = w;
          nw.node_offset = static_cast<uint32_t>(out.street_nodes.size());
          out.ways.push_back(nw);
          for (uint8_t n = 0; n < w.node_count; n++)
              out.street_nodes.push_back(full.street_nodes[w.node_offset + n]);
      } }

    // Remap addrs
    std::unordered_map<uint32_t, uint32_t> addr_remap;
    { std::vector<uint32_t> sorted_ids(used_addr_ids.begin(), used_addr_ids.end());
      std::sort(sorted_ids.begin(), sorted_ids.end());
      for (uint32_t old_id : sorted_ids) {
          uint32_t new_id = static_cast<uint32_t>(out.addr_points.size());
          addr_remap[old_id] = new_id;
          out.addr_points.push_back(full.addr_points[old_id]);
      } }

    // Remap interps
    std::unordered_map<uint32_t, uint32_t> interp_remap;
    { std::vector<uint32_t> sorted_ids(used_interp_ids.begin(), used_interp_ids.end());
      std::sort(sorted_ids.begin(), sorted_ids.end());
      for (uint32_t old_id : sorted_ids) {
          uint32_t new_id = static_cast<uint32_t>(out.interp_ways.size());
          interp_remap[old_id] = new_id;
          const auto& iw = full.interp_ways[old_id];
          InterpWay niw = iw;
          niw.node_offset = static_cast<uint32_t>(out.interp_nodes.size());
          out.interp_ways.push_back(niw);
          for (uint8_t n = 0; n < iw.node_count; n++)
              out.interp_nodes.push_back(full.interp_nodes[iw.node_offset + n]);
      } }

    // Remap admins
    std::unordered_map<uint32_t, uint32_t> admin_remap;
    { std::vector<uint32_t> sorted_ids(used_admin_ids.begin(), used_admin_ids.end());
      std::sort(sorted_ids.begin(), sorted_ids.end());
      for (uint32_t old_id : sorted_ids) {
          uint32_t new_id = static_cast<uint32_t>(out.admin_polygons.size());
          admin_remap[old_id] = new_id;
          const auto& ap = full.admin_polygons[old_id];
          AdminPolygon nap = ap;
          nap.vertex_offset = static_cast<uint32_t>(out.admin_vertices.size());
          out.admin_polygons.push_back(nap);
          for (uint32_t v = 0; v < ap.vertex_count; v++)
              out.admin_vertices.push_back(full.admin_vertices[ap.vertex_offset + v]);
      } }

    // Remap cell maps
    for (const auto& [cell_id, ids] : full.cell_to_ways) {
        if (!cell_in_bbox(cell_id, bbox)) continue;
        std::vector<uint32_t> new_ids;
        for (uint32_t id : ids) { auto it = way_remap.find(id); if (it != way_remap.end()) new_ids.push_back(it->second); }
        if (!new_ids.empty()) out.cell_to_ways[cell_id] = std::move(new_ids);
    }
    for (const auto& [cell_id, ids] : full.cell_to_addrs) {
        if (!cell_in_bbox(cell_id, bbox)) continue;
        std::vector<uint32_t> new_ids;
        for (uint32_t id : ids) { auto it = addr_remap.find(id); if (it != addr_remap.end()) new_ids.push_back(it->second); }
        if (!new_ids.empty()) out.cell_to_addrs[cell_id] = std::move(new_ids);
    }
    for (const auto& [cell_id, ids] : full.cell_to_interps) {
        if (!cell_in_bbox(cell_id, bbox)) continue;
        std::vector<uint32_t> new_ids;
        for (uint32_t id : ids) { auto it = interp_remap.find(id); if (it != interp_remap.end()) new_ids.push_back(it->second); }
        if (!new_ids.empty()) out.cell_to_interps[cell_id] = std::move(new_ids);
    }
    for (const auto& [cell_id, ids] : full.cell_to_admin) {
        if (!cell_in_bbox(cell_id, bbox)) continue;
        std::vector<uint32_t> new_ids;
        for (uint32_t id : ids) {
            uint32_t raw_id = id & ID_MASK;
            uint32_t flags = id & INTERIOR_FLAG;
            auto it = admin_remap.find(raw_id);
            if (it != admin_remap.end()) new_ids.push_back(it->second | flags);
        }
        if (!new_ids.empty()) out.cell_to_admin[cell_id] = std::move(new_ids);
    }

    // Rebuild compact string pool
    std::unordered_set<uint32_t> used_offsets;
    for (const auto& w : out.ways) used_offsets.insert(w.name_id);
    for (const auto& a : out.addr_points) { used_offsets.insert(a.housenumber_id); used_offsets.insert(a.street_id); }
    for (const auto& iw : out.interp_ways) used_offsets.insert(iw.street_id);
    for (const auto& ap : out.admin_polygons) used_offsets.insert(ap.name_id);

    const auto& old_sp = full.string_pool.data();
    std::unordered_map<uint32_t, uint32_t> string_remap;
    auto& new_sp = out.string_pool.mutable_data();
    new_sp.clear();

    std::vector<uint32_t> sorted_offsets(used_offsets.begin(), used_offsets.end());
    std::sort(sorted_offsets.begin(), sorted_offsets.end());
    for (uint32_t old_off : sorted_offsets) {
        uint32_t new_off = static_cast<uint32_t>(new_sp.size());
        string_remap[old_off] = new_off;
        const char* str = old_sp.data() + old_off;
        size_t len = std::strlen(str);
        new_sp.insert(new_sp.end(), str, str + len + 1);
    }

    for (auto& w : out.ways) w.name_id = string_remap[w.name_id];
    for (auto& a : out.addr_points) { a.housenumber_id = string_remap[a.housenumber_id]; a.street_id = string_remap[a.street_id]; }
    for (auto& iw : out.interp_ways) iw.street_id = string_remap[iw.street_id];
    for (auto& ap : out.admin_polygons) ap.name_id = string_remap[ap.name_id];

    return out;
}

-    std::unordered_set<uint32_t> used_way_ids;

-    for (const auto& [cell_id, ids] : full.cell_to_ways) {

-        if (cell_in_bbox(cell_id, bbox)) {

-            for (uint32_t id : ids) used_way_ids.insert(id);

+    // Filter cell maps in parallel — each builds an independent ID set

+    auto filter_cells = [&](const std::unordered_map<uint64_t, std::vector<uint32_t>>& cell_map,

+                            bool mask_interior = false) {

+        std::unordered_set<uint32_t> ids;

+        for (const auto& [cell_id, cell_ids] : cell_map) {

+            if (cell_in_bbox(cell_id, bbox)) {

+                for (uint32_t id : cell_ids)

+                    ids.insert(mask_interior ? (id & ID_MASK) : id);

-    std::unordered_set<uint32_t> used_addr_ids;

-    for (const auto& [cell_id, ids] : full.cell_to_addrs) {

-            for (uint32_t id : ids) used_addr_ids.insert(id);

+    auto f_ways = std::async(std::launch::async, [&]{ return filter_cells(full.cell_to_ways); });

+    auto f_addrs = std::async(std::launch::async, [&]{ return filter_cells(full.cell_to_addrs); });

+    auto f_interps = std::async(std::launch::async, [&]{ return filter_cells(full.cell_to_interps); });

+    auto f_admin = std::async(std::launch::async, [&]{ return filter_cells(full.cell_to_admin, true); });

-    std::unordered_set<uint32_t> used_interp_ids;

-    for (const auto& [cell_id, ids] : full.cell_to_interps) {

-        if (cell_in_bbox(cell_id, bbox)) {

-            for (uint32_t id : ids) used_interp_ids.insert(id);

+    auto used_addr_ids = f_addrs.get();

+    auto used_interp_ids = f_interps.get();

+    auto used_admin_ids = f_admin.get();

-    std::unordered_set<uint32_t> used_admin_ids;

-    for (const auto& [cell_id, ids] : full.cell_to_admin) {

-            for (uint32_t id : ids) used_admin_ids.insert(id & ID_MASK);


Bash

Build with parallel filter


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


Good. This will use 4 threads per continent × 8 continents = 32 threads during filtering, fully saturating all cores. Let me check if the current build is done:


ssh node3 "grep -c 'Done cont' /tmp/s3build.log; tail -3 /tmp/s3build.log" 2>&1

Check current build


1
Quality 2x: 0 polygons, 0 vertices (0 MiB)
Quality 2.5x: 0 polygons, 0 vertices (0 MiB)
Done continent: antarctica


Only Antarctica done (it's tiny). The rest are still running. Let me keep checking:


sleep 60 && ssh node3 "grep -c 'Done cont' /tmp/s3build.log; grep 'Done cont' /tmp/s3build.log" 2>&1

Check after 60s


  Writing quality variants for /home/michtest/test-s3...
  Writing quality variants for /home/michtest/test-s3/antarctica...
Done continent: antarctica
  Writing quality variants for /home/michtest/test-s3/central-america...
Done continent: central-america


The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.

Note: The user's next message may contain a correction or preference. Pay close attention — if they explain what went wrong or how they'd prefer you to work, consider saving that to memory for future sessions.


[Request interrupted by user for tool use]


This should adapt based on the amount of threads available


[Request interrupted by user]


Antarctica and Central America done. The bigger continents are still filtering/writing.


sleep 60 && ssh node3 "grep -E 'Done cont' /tmp/s3build.log" 2>&1

Check after 60s


Right — with 8 continents each spawning 4 filter threads, that's 32 threads. But if there are only 8 cores, we'd oversaturate. Let me make the continent parallelism adapt to available cores:


  1302→    log_phase("Planet index + qualities", _pt);
  1303→
  1304→    // Write continents in parallel
  1305→    if (generate_continents) {
  1306→        std::vector<std::future<void>> continent_futures;
  1307→        for (size_t ci = 0; ci < kContinentCount; ci++) {
  1308→            continent_futures.push_back(std::async(std::launch::async, [&, ci]() {
  1309→                const auto& continent = kContinents[ci];
  1310→                std::cerr << "Filtering for continent: " << continent.name << "..." << std::endl;
  1311→                auto subset = filter_by_bbox(data, continent);
  1312→                std::string cont_dir = output_dir + "/" + continent.name;
  1313→                write_region(subset, cont_dir);
  1314→                std::cerr << "Done continent: " << continent.name << std::endl;
  1315→            }));
  1316→        }
  1317→        for (auto& f : continent_futures) f.get();
  1318→    }
  1319→
  1320→    log_phase("All index writing (total)", _pt);
  1321→

    log_phase("Planet index + qualities", _pt);

    // Write continents in parallel
    if (generate_continents) {
        std::vector<std::future<void>> continent_futures;
        for (size_t ci = 0; ci < kContinentCount; ci++) {
            continent_futures.push_back(std::async(std::launch::async, [&, ci]() {
                const auto& continent = kContinents[ci];
                std::cerr << "Filtering for continent: " << continent.name << "..." << std::endl;
                auto subset = filter_by_bbox(data, continent);
                std::string cont_dir = output_dir + "/" + continent.name;
                write_region(subset, cont_dir);
                std::cerr << "Done continent: " << continent.name << std::endl;
            }));
        }
        for (auto& f : continent_futures) f.get();
    }

    log_phase("All index writing (total)", _pt);


The issue is: each continent's `filter_by_bbox` uses 4 threads (the parallel cell filtering I just added), and `write_region` → `write_index` → `write_qualities` also spawns threads. With 8 continents × many threads each, we waste time on context switching.

Better approach: limit concurrent continents based on available cores, and let each continent use more threads:


Edit

    // Write continents in parallel
    if (generate_continents) {
        std::vector<std::future<void>> continent_futures;
        for (size_t ci = 0; ci < kContinentCount; ci++) {
            continent_futures.push_back(std::async(std::launch::async, [&, ci]() {
                const auto& continent = kContinents[ci];
                std::cerr << "Filtering for continent: " << continent.name << "..." << std::endl;
                auto subset = filter_by_bbox(data, continent);
                std::string cont_dir = output_dir + "/" + continent.name;
                write_region(subset, cont_dir);
                std::cerr << "Done continent: " << continent.name << std::endl;
            }));
        }
        for (auto& f : continent_futures) f.get();
    }

    // Write continents — limit concurrency to available cores / 4 threads per continent
    if (generate_continents) {
        unsigned max_concurrent = std::max(1u, std::thread::hardware_concurrency() / 4);
        std::cerr << "Processing " << kContinentCount << " continents ("
                  << max_concurrent << " concurrent)..." << std::endl;

        // Use a semaphore-like approach: launch all but limit active
        std::atomic<unsigned> active{0};
        std::mutex cv_mutex;
        std::condition_variable cv;
        std::vector<std::future<void>> continent_futures;

        for (size_t ci = 0; ci < kContinentCount; ci++) {
            // Wait if too many active
            {
                std::unique_lock<std::mutex> lock(cv_mutex);
                cv.wait(lock, [&]{ return active.load() < max_concurrent; });
            }
            active.fetch_add(1);

            continent_futures.push_back(std::async(std::launch::async, [&, ci]() {
                const auto& continent = kContinents[ci];
                std::cerr << "Filtering for continent: " << continent.name << "..." << std::endl;
                auto subset = filter_by_bbox(data, continent);
                std::string cont_dir = output_dir + "/" + continent.name;
                write_region(subset, cont_dir);
                std::cerr << "Done continent: " << continent.name << std::endl;

                active.fetch_sub(1);
                cv.notify_one();
            }));
        }
        for (auto& f : continent_futures) f.get();
    }