#include "ring_assembly.h"
#include "geometry.h"

#include <unordered_set>

std::vector<std::vector<std::pair<double,double>>> assemble_outer_rings(
    const std::vector<std::pair<int64_t, std::string>>& members,
    const std::unordered_map<int64_t, ParsedData::WayGeometry>& way_geoms,
    bool include_all_roles)
{
    // Coordinate-based ring assembly: split ways at shared internal nodes,
    // then stitch sub-ways at endpoints with backtracking.
    // coord_key() from geometry.h provides coordinate hashing.

    std::unordered_map<int64_t, int> coord_count;
    std::vector<const std::vector<std::pair<double,double>>*> way_coords;
    for (const auto& [way_id, role] : members) {
        if (!include_all_roles && role != "outer" && !role.empty()) continue;
        auto it = way_geoms.find(way_id);
        if (it == way_geoms.end() || it->second.coords.empty()) continue;
        way_coords.push_back(&it->second.coords);
        for (const auto& [lat, lng] : it->second.coords) {
            coord_count[coord_key(lat, lng)]++;
        }
    }

    std::unordered_set<int64_t> split_points;
    for (auto* coords : way_coords) {
        split_points.insert(coord_key(coords->front().first, coords->front().second));
        split_points.insert(coord_key(coords->back().first, coords->back().second));
    }
    for (auto& [ck, cnt] : coord_count) {
        if (cnt > 1) split_points.insert(ck);
    }

    struct SubWay {
        std::vector<std::pair<double,double>> coords;
    };
    std::vector<SubWay> sub_ways;
    for (auto* coords : way_coords) {
        size_t seg_start = 0;
        for (size_t i = 1; i < coords->size(); i++) {
            int64_t ck = coord_key((*coords)[i].first, (*coords)[i].second);
            if (split_points.count(ck) && i > seg_start) {
                SubWay sw;
                sw.coords.assign(coords->begin() + seg_start, coords->begin() + i + 1);
                if (sw.coords.size() >= 2) {
                    sub_ways.push_back(std::move(sw));
                }
                seg_start = i;
            }
        }
        if (seg_start < coords->size() - 1) {
            SubWay sw;
            sw.coords.assign(coords->begin() + seg_start, coords->end());
            if (sw.coords.size() >= 2) {
                sub_ways.push_back(std::move(sw));
            }
        }
    }

    std::unordered_map<int64_t, std::vector<std::pair<size_t, bool>>> coord_adj;
    for (size_t i = 0; i < sub_ways.size(); i++) {
        auto& first = sub_ways[i].coords.front();
        auto& last = sub_ways[i].coords.back();
        coord_adj[coord_key(first.first, first.second)].push_back({i, false});
        coord_adj[coord_key(last.first, last.second)].push_back({i, true});
    }

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

    struct BacktrackState {
        const std::vector<SubWay>& sways;
        const std::unordered_map<int64_t, std::vector<std::pair<size_t, bool>>>& adj;
        std::vector<bool>& used;
        int calls;

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

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

            for (const auto& [wi, is_last] : it->second) {
                if (used[wi]) continue;
                auto& endpoint = is_last ? sways[wi].coords.front() : sways[wi].coords.back();
                int64_t new_key = coord_key(endpoint.first, endpoint.second);

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

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

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

    BacktrackState bt{sub_ways, coord_adj, used, 0};

    // Pass 1: Greedy
    for (size_t start_idx = 0; start_idx < sub_ways.size(); start_idx++) {
        if (used[start_idx]) continue;

        const auto& sg = sub_ways[start_idx];
        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);

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

        used[start_idx] = true;
        std::vector<size_t> attempt_used = {start_idx};
        std::vector<std::pair<double,double>> ring = sg.coords;
        int64_t current_last = last_key;
        bool extended = true;
        while (extended && current_last != first_key) {
            extended = false;
            auto it = coord_adj.find(current_last);
            if (it == coord_adj.end()) break;
            for (const auto& [wi, is_last] : it->second) {
                if (used[wi]) continue;
                const auto& sw = sub_ways[wi];
                bool reversed = is_last;
                auto& endpoint = reversed ? sw.coords.front() : sw.coords.back();
                used[wi] = true;
                attempt_used.push_back(wi);
                if (!reversed) ring.insert(ring.end(), sw.coords.begin() + 1, sw.coords.end());
                else for (auto rit = sw.coords.rbegin() + 1; rit != sw.coords.rend(); ++rit) ring.push_back(*rit);
                current_last = coord_key(endpoint.first, endpoint.second);
                extended = true;
                break;
            }
        }
        if (ring.size() >= 4 && current_last == first_key &&
            !ring_has_self_intersection(ring)) {
            rings.push_back(std::move(ring));
        } else {
            for (auto wi : attempt_used) used[wi] = false;
        }
    }

    // Pass 2: Backtracking retry
    {
        bool any_unused = false;
        for (size_t i = 0; i < used.size(); i++) {
            if (!used[i]) { any_unused = true; break; }
        }
        if (any_unused && sub_ways.size() <= MAX_SUBWAYS_FOR_RETRY) {
            std::vector<bool> bt_used(sub_ways.size(), false);
            std::vector<std::vector<std::pair<double,double>>> bt_rings;

            BacktrackState bt2{sub_ways, coord_adj, bt_used, 0};
            int total_bt_calls = 0;
            constexpr int MAX_TOTAL_BT_CALLS = BACKTRACK_TOTAL_BUDGET;

            for (size_t si = 0; si < sub_ways.size(); si++) {
                if (bt_used[si]) continue;
                if (total_bt_calls >= MAX_TOTAL_BT_CALLS) break;
                const auto& sg = sub_ways[si];
                int64_t fk = coord_key(sg.coords.front().first, sg.coords.front().second);
                int64_t lk = coord_key(sg.coords.back().first, sg.coords.back().second);
                if (sg.coords.size() >= 4 && fk == lk) {
                    bt_used[si] = true;
                    bt_rings.push_back(sg.coords);
                    continue;
                }
                bt_used[si] = true;
                bt2.calls = 0;
                std::vector<std::pair<size_t, bool>> path;
                if (bt2.try_close(fk, lk, path, 0)) {
                    std::vector<std::pair<double,double>> r = sg.coords;
                    for (const auto& [wi, rev] : path) {
                        const auto& c = sub_ways[wi].coords;
                        if (!rev) r.insert(r.end(), c.begin() + 1, c.end());
                        else for (auto it2 = c.rbegin() + 1; it2 != c.rend(); ++it2) r.push_back(*it2);
                    }
                    if (r.size() >= 4 && !ring_has_self_intersection(r)) {
                        bt_rings.push_back(std::move(r));
                        continue;
                    }
                }
                bt_used[si] = false;
                total_bt_calls += bt2.calls;
            }

            if (bt_rings.size() > rings.size()) {
                rings = std::move(bt_rings);
            }
        }
    }

    return rings;
}


File created successfully at: /root/traccar-geocoder/builder/src/ring_assembly.cpp

#include "ring_assembly.h"
#include "geometry.h"

#include <unordered_set>

std::vector<std::vector<std::pair<double,double>>> assemble_outer_rings(
    const std::vector<std::pair<int64_t, std::string>>& members,
    const std::unordered_map<int64_t, ParsedData::WayGeometry>& way_geoms,
    bool include_all_roles)
{
    // Coordinate-based ring assembly: split ways at shared internal nodes,
    // then stitch sub-ways at endpoints with backtracking.
    // coord_key() from geometry.h provides coordinate hashing.

    std::unordered_map<int64_t, int> coord_count;
    std::vector<const std::vector<std::pair<double,double>>*> way_coords;
    for (const auto& [way_id, role] : members) {
        if (!include_all_roles && role != "outer" && !role.empty()) continue;
        auto it = way_geoms.find(way_id);
        if (it == way_geoms.end() || it->second.coords.empty()) continue;
        way_coords.push_back(&it->second.coords);
        for (const auto& [lat, lng] : it->second.coords) {
            coord_count[coord_key(lat, lng)]++;
        }
    }

    std::unordered_set<int64_t> split_points;
    for (auto* coords : way_coords) {
        split_points.insert(coord_key(coords->front().first, coords->front().second));
        split_points.insert(coord_key(coords->back().first, coords->back().second));
    }
    for (auto& [ck, cnt] : coord_count) {
        if (cnt > 1) split_points.insert(ck);
    }

    struct SubWay {
        std::vector<std::pair<double,double>> coords;
    };
    std::vector<SubWay> sub_ways;
    for (auto* coords : way_coords) {
        size_t seg_start = 0;
        for (size_t i = 1; i < coords->size(); i++) {
            int64_t ck = coord_key((*coords)[i].first, (*coords)[i].second);
            if (split_points.count(ck) && i > seg_start) {
                SubWay sw;
                sw.coords.assign(coords->begin() + seg_start, coords->begin() + i + 1);
                if (sw.coords.size() >= 2) {
                    sub_ways.push_back(std::move(sw));
                }
                seg_start = i;
            }
        }
        if (seg_start < coords->size() - 1) {
            SubWay sw;
            sw.coords.assign(coords->begin() + seg_start, coords->end());
            if (sw.coords.size() >= 2) {
                sub_ways.push_back(std::move(sw));
            }
        }
    }

    std::unordered_map<int64_t, std::vector<std::pair<size_t, bool>>> coord_adj;
    for (size_t i = 0; i < sub_ways.size(); i++) {
        auto& first = sub_ways[i].coords.front();
        auto& last = sub_ways[i].coords.back();
        coord_adj[coord_key(first.first, first.second)].push_back({i, false});
        coord_adj[coord_key(last.first, last.second)].push_back({i, true});
    }

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

    struct BacktrackState {
        const std::vector<SubWay>& sways;
        const std::unordered_map<int64_t, std::vector<std::pair<size_t, bool>>>& adj;
        std::vector<bool>& used;
        int calls;

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

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

            for (const auto& [wi, is_last] : it->second) {
                if (used[wi]) continue;
                auto& endpoint = is_last ? sways[wi].coords.front() : sways[wi].coords.back();
                int64_t new_key = coord_key(endpoint.first, endpoint.second);

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

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

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

    BacktrackState bt{sub_ways, coord_adj, used, 0};

    // Pass 1: Greedy
    for (size_t start_idx = 0; start_idx < sub_ways.size(); start_idx++) {
        if (used[start_idx]) continue;

        const auto& sg = sub_ways[start_idx];
        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);

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

        used[start_idx] = true;
        std::vector<size_t> attempt_used = {start_idx};
        std::vector<std::pair<double,double>> ring = sg.coords;
        int64_t current_last = last_key;
        bool extended = true;
        while (extended && current_last != first_key) {
            extended = false;
            auto it = coord_adj.find(current_last);
            if (it == coord_adj.end()) break;
            for (const auto& [wi, is_last] : it->second) {
                if (used[wi]) continue;
                const auto& sw = sub_ways[wi];
                bool reversed = is_last;
                auto& endpoint = reversed ? sw.coords.front() : sw.coords.back();
                used[wi] = true;
                attempt_used.push_back(wi);
                if (!reversed) ring.insert(ring.end(), sw.coords.begin() + 1, sw.coords.end());
                else for (auto rit = sw.coords.rbegin() + 1; rit != sw.coords.rend(); ++rit) ring.push_back(*rit);
                current_last = coord_key(endpoint.first, endpoint.second);
                extended = true;
                break;
            }
        }
        if (ring.size() >= 4 && current_last == first_key &&
            !ring_has_self_intersection(ring)) {
            rings.push_back(std::move(ring));
        } else {
            for (auto wi : attempt_used) used[wi] = false;
        }
    }

    // Pass 2: Backtracking retry
    {
        bool any_unused = false;
        for (size_t i = 0; i < used.size(); i++) {
            if (!used[i]) { any_unused = true; break; }
        }
        if (any_unused && sub_ways.size() <= MAX_SUBWAYS_FOR_RETRY) {
            std::vector<bool> bt_used(sub_ways.size(), false);
            std::vector<std::vector<std::pair<double,double>>> bt_rings;

            BacktrackState bt2{sub_ways, coord_adj, bt_used, 0};
            int total_bt_calls = 0;
            constexpr int MAX_TOTAL_BT_CALLS = BACKTRACK_TOTAL_BUDGET;

            for (size_t si = 0; si < sub_ways.size(); si++) {
                if (bt_used[si]) continue;
                if (total_bt_calls >= MAX_TOTAL_BT_CALLS) break;
                const auto& sg = sub_ways[si];
                int64_t fk = coord_key(sg.coords.front().first, sg.coords.front().second);
                int64_t lk = coord_key(sg.coords.back().first, sg.coords.back().second);
                if (sg.coords.size() >= 4 && fk == lk) {
                    bt_used[si] = true;
                    bt_rings.push_back(sg.coords);
                    continue;
                }
                bt_used[si] = true;
                bt2.calls = 0;
                std::vector<std::pair<size_t, bool>> path;
                if (bt2.try_close(fk, lk, path, 0)) {
                    std::vector<std::pair<double,double>> r = sg.coords;
                    for (const auto& [wi, rev] : path) {
                        const auto& c = sub_ways[wi].coords;
                        if (!rev) r.insert(r.end(), c.begin() + 1, c.end());
                        else for (auto it2 = c.rbegin() + 1; it2 != c.rend(); ++it2) r.push_back(*it2);
                    }
                    if (r.size() >= 4 && !ring_has_self_intersection(r)) {
                        bt_rings.push_back(std::move(r));
                        continue;
                    }
                }
                bt_used[si] = false;
                total_bt_calls += bt2.calls;
            }

            if (bt_rings.size() > rings.size()) {
                rings = std::move(bt_rings);
            }
        }
    }

    return rings;
}


Updated task #8 status


Updated task #9 status


Now create the remaining modules. Let me create them all in parallel:


Write

#pragma once

#include <cstring>
#include <string>
#include <vector>

#include <osmium/handler.hpp>

#include "types.h"

struct RelationCollector : public osmium::handler::Handler {
    std::vector<CollectedRelation>& relations;

    explicit RelationCollector(std::vector<CollectedRelation>& r) : relations(r) {}

    void relation(const osmium::Relation& rel) {
        const char* boundary = rel.tags()["boundary"];
        if (!boundary) return;

        bool is_admin = (std::strcmp(boundary, "administrative") == 0);
        bool is_postal = (std::strcmp(boundary, "postal_code") == 0);
        if (!is_admin && !is_postal) return;

        uint8_t admin_level = 0;
        if (is_admin) {
            const char* level_str = rel.tags()["admin_level"];
            if (!level_str) return;
            admin_level = static_cast<uint8_t>(std::atoi(level_str));
            if (admin_level < 2 || admin_level > 10) return;
        } else {
            admin_level = 11;
        }
        if (kMaxAdminLevel > 0 && admin_level > kMaxAdminLevel) return;

        const char* name = rel.tags()["name"];
        if (!name && is_admin) return;

        std::string name_str;
        if (is_postal) {
            const char* postal_code = rel.tags()["postal_code"];
            if (!postal_code) postal_code = name;
            if (!postal_code) return;
            name_str = postal_code;
        } else {
            name_str = name;
        }

        std::string country_code;
        if (admin_level == 2) {
            const char* cc = rel.tags()["ISO3166-1:alpha2"];
            if (cc) country_code = cc;
        }

        CollectedRelation cr;
        cr.id = rel.id();
        cr.admin_level = admin_level;
        cr.name = std::move(name_str);
        cr.country_code = std::move(country_code);
        cr.is_postal = is_postal;

        for (const auto& member : rel.members()) {
            if (member.type() == osmium::item_type::way) {
                cr.members.emplace_back(member.ref(), member.role());
            }
        }

        if (!cr.members.empty()) {
            relations.push_back(std::move(cr));
        }
    }
};


File created successfully at: /root/traccar-geocoder/builder/src/relation_collector.h

#pragma once

#include <cstring>
#include <string>
#include <vector>

#include <osmium/handler.hpp>

#include "types.h"

struct RelationCollector : public osmium::handler::Handler {
    std::vector<CollectedRelation>& relations;

    explicit RelationCollector(std::vector<CollectedRelation>& r) : relations(r) {}

    void relation(const osmium::Relation& rel) {
        const char* boundary = rel.tags()["boundary"];
        if (!boundary) return;

        bool is_admin = (std::strcmp(boundary, "administrative") == 0);
        bool is_postal = (std::strcmp(boundary, "postal_code") == 0);
        if (!is_admin && !is_postal) return;

        uint8_t admin_level = 0;
        if (is_admin) {
            const char* level_str = rel.tags()["admin_level"];
            if (!level_str) return;
            admin_level = static_cast<uint8_t>(std::atoi(level_str));
            if (admin_level < 2 || admin_level > 10) return;
        } else {
            admin_level = 11;
        }
        if (kMaxAdminLevel > 0 && admin_level > kMaxAdminLevel) return;

        const char* name = rel.tags()["name"];
        if (!name && is_admin) return;

        std::string name_str;
        if (is_postal) {
            const char* postal_code = rel.tags()["postal_code"];
            if (!postal_code) postal_code = name;
            if (!postal_code) return;
            name_str = postal_code;
        } else {
            name_str = name;
        }

        std::string country_code;
        if (admin_level == 2) {
            const char* cc = rel.tags()["ISO3166-1:alpha2"];
            if (cc) country_code = cc;
        }

        CollectedRelation cr;
        cr.id = rel.id();
        cr.admin_level = admin_level;
        cr.name = std::move(name_str);
        cr.country_code = std::move(country_code);
        cr.is_postal = is_postal;

        for (const auto& member : rel.members()) {
            if (member.type() == osmium::item_type::way) {
                cr.members.emplace_back(member.ref(), member.role());
            }
        }

        if (!cr.members.empty()) {
            relations.push_back(std::move(cr));
        }
    }
};


#pragma once

#include "parsed_data.h"

void resolve_interpolation_endpoints(ParsedData& data);


File created successfully at: /root/traccar-geocoder/builder/src/interpolation.h

#pragma once

#include "parsed_data.h"

void resolve_interpolation_endpoints(ParsedData& data);


#include "interpolation.h"
#include "geometry.h"

#include <cstdint>
#include <cstring>
#include <iostream>
#include <unordered_map>

void resolve_interpolation_endpoints(ParsedData& data) {
    struct CoordKey {
        int32_t lat;
        int32_t lng;
        bool operator==(const CoordKey& o) const { return lat == o.lat && lng == o.lng; }
    };
    struct CoordHash {
        size_t operator()(const CoordKey& k) const {
            return std::hash<int64_t>()(((int64_t)k.lat << 32) | (uint32_t)k.lng);
        }
    };

    std::unordered_map<CoordKey, uint32_t, CoordHash> addr_by_coord;
    for (uint32_t i = 0; i < data.addr_points.size(); i++) {
        CoordKey key{
            static_cast<int32_t>(data.addr_points[i].lat * 100000),
            static_cast<int32_t>(data.addr_points[i].lng * 100000)
        };
        addr_by_coord[key] = i;
    }

    uint32_t resolved = 0;
    for (auto& iw : data.interp_ways) {
        if (iw.node_count < 2) continue;

        const auto& start = data.interp_nodes[iw.node_offset];
        CoordKey start_key{
            static_cast<int32_t>(start.lat * 100000),
            static_cast<int32_t>(start.lng * 100000)
        };
        auto it_start = addr_by_coord.find(start_key);

        const auto& end = data.interp_nodes[iw.node_offset + iw.node_count - 1];
        CoordKey end_key{
            static_cast<int32_t>(end.lat * 100000),
            static_cast<int32_t>(end.lng * 100000)
        };
        auto it_end = addr_by_coord.find(end_key);

        if (it_start != addr_by_coord.end()) {
            const char* hn = data.string_pool.data().data() + data.addr_points[it_start->second].housenumber_id;
            iw.start_number = parse_house_number(hn);
        }
        if (it_end != addr_by_coord.end()) {
            const char* hn = data.string_pool.data().data() + data.addr_points[it_end->second].housenumber_id;
            iw.end_number = parse_house_number(hn);
        }

        if (iw.start_number > 0 && iw.end_number > 0) resolved++;
    }

    std::cerr << "Resolved " << resolved << "/" << data.interp_ways.size()
              << " interpolation ways" << std::endl;
}


File created successfully at: /root/traccar-geocoder/builder/src/interpolation.cpp

#include "interpolation.h"
#include "geometry.h"

#include <cstdint>
#include <cstring>
#include <iostream>
#include <unordered_map>

void resolve_interpolation_endpoints(ParsedData& data) {
    struct CoordKey {
        int32_t lat;
        int32_t lng;
        bool operator==(const CoordKey& o) const { return lat == o.lat && lng == o.lng; }
    };
    struct CoordHash {
        size_t operator()(const CoordKey& k) const {
            return std::hash<int64_t>()(((int64_t)k.lat << 32) | (uint32_t)k.lng);
        }
    };

    std::unordered_map<CoordKey, uint32_t, CoordHash> addr_by_coord;
    for (uint32_t i = 0; i < data.addr_points.size(); i++) {
        CoordKey key{
            static_cast<int32_t>(data.addr_points[i].lat * 100000),
            static_cast<int32_t>(data.addr_points[i].lng * 100000)
        };
        addr_by_coord[key] = i;
    }

    uint32_t resolved = 0;
    for (auto& iw : data.interp_ways) {
        if (iw.node_count < 2) continue;

        const auto& start = data.interp_nodes[iw.node_offset];
        CoordKey start_key{
            static_cast<int32_t>(start.lat * 100000),
            static_cast<int32_t>(start.lng * 100000)
        };
        auto it_start = addr_by_coord.find(start_key);

        const auto& end = data.interp_nodes[iw.node_offset + iw.node_count - 1];
        CoordKey end_key{
            static_cast<int32_t>(end.lat * 100000),
            static_cast<int32_t>(end.lng * 100000)
        };
        auto it_end = addr_by_coord.find(end_key);

        if (it_start != addr_by_coord.end()) {
            const char* hn = data.string_pool.data().data() + data.addr_points[it_start->second].housenumber_id;
            iw.start_number = parse_house_number(hn);
        }
        if (it_end != addr_by_coord.end()) {
            const char* hn = data.string_pool.data().data() + data.addr_points[it_end->second].housenumber_id;
            iw.end_number = parse_house_number(hn);
        }

        if (iw.start_number > 0 && iw.end_number > 0) resolved++;
    }

    std::cerr << "Resolved " << resolved << "/" << data.interp_ways.size()
              << " interpolation ways" << std::endl;
}


Now the cache module. Let me read the cache section:


   770→        ids.erase(std::unique(ids.begin(), ids.end()), ids.end());
   771→    }
   772→}
   773→
   774→// --- Cache serialization ---
   775→
   776→enum class SectionType : uint32_t {
   777→    STRING_POOL = 0,
   778→    WAYS = 1,
   779→    STREET_NODES = 2,
   780→    CELL_TO_WAYS = 3,
   781→    ADDR_POINTS = 4,
   782→    CELL_TO_ADDRS = 5,
   783→    INTERP_WAYS = 6,
   784→    INTERP_NODES = 7,
   785→    CELL_TO_INTERPS = 8,
   786→    ADMIN_POLYGONS = 9,
   787→    ADMIN_VERTICES = 10,
   788→    CELL_TO_ADMIN = 11,
   789→};

        ids.erase(std::unique(ids.begin(), ids.end()), ids.end());
    }
}

// --- Cache serialization ---

enum class SectionType : uint32_t {
    STRING_POOL = 0,
    WAYS = 1,
    STREET_NODES = 2,
    CELL_TO_WAYS = 3,
    ADDR_POINTS = 4,
    CELL_TO_ADDRS = 5,
    INTERP_WAYS = 6,
    INTERP_NODES = 7,
    CELL_TO_INTERPS = 8,
    ADMIN_POLYGONS = 9,
    ADMIN_VERTICES = 10,
    CELL_TO_ADMIN = 11,
};


#pragma once

#include <string>

#include "parsed_data.h"

void serialize_cache(const ParsedData& data, const std::string& path);
bool deserialize_cache(ParsedData& data, const std::string& path);


File created successfully at: /root/traccar-geocoder/builder/src/cache.h

#pragma once

#include <string>

#include "parsed_data.h"

void serialize_cache(const ParsedData& data, const std::string& path);
bool deserialize_cache(ParsedData& data, const std::string& path);


   774→// --- Cache serialization ---
   775→
   776→enum class SectionType : uint32_t {
   777→    STRING_POOL = 0,
   778→    WAYS = 1,
   779→    STREET_NODES = 2,
   780→    CELL_TO_WAYS = 3,
   781→    ADDR_POINTS = 4,
   782→    CELL_TO_ADDRS = 5,
   783→    INTERP_WAYS = 6,
   784→    INTERP_NODES = 7,
   785→    CELL_TO_INTERPS = 8,
   786→    ADMIN_POLYGONS = 9,
   787→    ADMIN_VERTICES = 10,
   788→    CELL_TO_ADMIN = 11,
   789→};
   790→
   791→static const char CACHE_MAGIC[8] = {'T','G','C','A','C','H','E','\0'};
   792→static const uint32_t CACHE_VERSION = 1;
   793→static const uint32_t CACHE_SECTION_COUNT = 12;
   794→
   795→// Serialize a vector of structs to a binary blob
   796→template<typename T>
   797→static std::vector<char> serialize_vector(const std::vector<T>& vec) {
   798→    std::vector<char> buf;
   799→    uint64_t count = vec.size();
   800→    buf.resize(sizeof(uint64_t) + count * sizeof(T));
   801→    std::memcpy(buf.data(), &count, sizeof(uint64_t));
   802→    if (count > 0) {
   803→        std::memcpy(buf.data() + sizeof(uint64_t), vec.data(), count * sizeof(T));
   804→    }
   805→    return buf;
   806→}
   807→
   808→// Deserialize a vector of structs from a binary blob
   809→template<typename T>
   810→static bool deserialize_vector(const char* data, uint64_t length, std::vector<T>& vec) {
   811→    if (length < sizeof(uint64_t)) return false;
   812→    uint64_t count;
   813→    std::memcpy(&count, data, sizeof(uint64_t));
   814→    if (length < sizeof(uint64_t) + count * sizeof(T)) return false;
   815→    vec.resize(count);
   816→    if (count > 0) {
   817→        std::memcpy(vec.data(), data + sizeof(uint64_t), count * sizeof(T));
   818→    }
   819→    return true;
   820→}
   821→
   822→// Serialize a cell map (uint64_t -> vector<uint32_t>)
   823→static std::vector<char> serialize_cell_map(const std::unordered_map<uint64_t, std::vector<uint32_t>>& map) {
   824→    // Calculate total size
   825→    size_t total = sizeof(uint64_t); // entry count
   826→    for (const auto& [cell_id, ids] : map) {
   827→        total += sizeof(uint64_t) + sizeof(uint32_t) + ids.size() * sizeof(uint32_t);
   828→    }
   829→    std::vector<char> buf(total);
   830→    char* ptr = buf.data();
   831→
   832→    uint64_t entry_count = map.size();
   833→    std::memcpy(ptr, &entry_count, sizeof(uint64_t));
   834→    ptr += sizeof(uint64_t);
   835→
   836→    for (const auto& [cell_id, ids] : map) {
   837→        std::memcpy(ptr, &cell_id, sizeof(uint64_t));
   838→        ptr += sizeof(uint64_t);
   839→        uint32_t id_count = static_cast<uint32_t>(ids.size());
   840→        std::memcpy(ptr, &id_count, sizeof(uint32_t));
   841→        ptr += sizeof(uint32_t);
   842→        if (id_count > 0) {
   843→            std::memcpy(ptr, ids.data(), id_count * sizeof(uint32_t));
   844→            ptr += id_count * sizeof(uint32_t);
   845→        }
   846→    }
   847→    return buf;
   848→}
   849→
   850→// Deserialize a cell map
   851→static bool deserialize_cell_map(const char* data, uint64_t length,
   852→                                  std::unordered_map<uint64_t, std::vector<uint32_t>>& map) {
   853→    if (length < sizeof(uint64_t)) return false;
   854→    const char* ptr = data;
   855→    const char* end = data + length;
   856→
   857→    uint64_t entry_count;
   858→    std::memcpy(&entry_count, ptr, sizeof(uint64_t));
   859→    ptr += sizeof(uint64_t);
   860→
   861→    map.reserve(entry_count);
   862→    for (uint64_t i = 0; i < entry_count; i++) {
   863→        if (ptr + sizeof(uint64_t) + sizeof(uint32_t) > end) return false;
   864→        uint64_t cell_id;
   865→        std::memcpy(&cell_id, ptr, sizeof(uint64_t));
   866→        ptr += sizeof(uint64_t);
   867→        uint32_t id_count;
   868→        std::memcpy(&id_count, ptr, sizeof(uint32_t));
   869→        ptr += sizeof(uint32_t);
   870→        if (ptr + id_count * sizeof(uint32_t) > end) return false;
   871→        std::vector<uint32_t> ids(id_count);
   872→        if (id_count > 0) {
   873→            std::memcpy(ids.data(), ptr, id_count * sizeof(uint32_t));
   874→            ptr += id_count * sizeof(uint32_t);
   875→        }
   876→        map[cell_id] = std::move(ids);
   877→    }
   878→    return true;
   879→}
   880→
   881→static void serialize_cache(const ParsedData& data, const std::string& path) {
   882→    std::cerr << "Saving cache to " << path << "..." << std::endl;
   883→    std::ofstream f(path, std::ios::binary);
   884→    if (!f.is_open()) {
   885→        std::cerr << "Error: could not open cache file for writing: " << path << std::endl;
   886→        return;
   887→    }
   888→
   889→    f.write(CACHE_MAGIC, 8);
   890→    f.write(reinterpret_cast<const char*>(&CACHE_VERSION), sizeof(uint32_t));
   891→    f.write(reinterpret_cast<const char*>(&CACHE_SECTION_COUNT), sizeof(uint32_t));
   892→
   893→    // Helper to write a section
   894→    auto write_section = [&](SectionType type, const std::vector<char>& blob) {
   895→        uint32_t t = static_cast<uint32_t>(type);
   896→        uint64_t len = blob.size();
   897→        f.write(reinterpret_cast<const char*>(&t), sizeof(uint32_t));
   898→        f.write(reinterpret_cast<const char*>(&len), sizeof(uint64_t));
   899→        f.write(blob.data(), len);
   900→    };
   901→
   902→    // STRING_POOL: raw char vector
   903→    {
   904→        const auto& sp = data.string_pool.data();
   905→        std::vector<char> blob(sizeof(uint64_t) + sp.size());
   906→        uint64_t sz = sp.size();
   907→        std::memcpy(blob.data(), &sz, sizeof(uint64_t));
   908→        if (sz > 0) std::memcpy(blob.data() + sizeof(uint64_t), sp.data(), sz);
   909→        write_section(SectionType::STRING_POOL, blob);
   910→    }
   911→
   912→    write_section(SectionType::WAYS, serialize_vector(data.ways));
   913→    write_section(SectionType::STREET_NODES, serialize_vector(data.street_nodes));
   914→    write_section(SectionType::CELL_TO_WAYS, serialize_cell_map(data.cell_to_ways));
   915→    write_section(SectionType::ADDR_POINTS, serialize_vector(data.addr_points));
   916→    write_section(SectionType::CELL_TO_ADDRS, serialize_cell_map(data.cell_to_addrs));
   917→    write_section(SectionType::INTERP_WAYS, serialize_vector(data.interp_ways));
   918→    write_section(SectionType::INTERP_NODES, serialize_vector(data.interp_nodes));
   919→    write_section(SectionType::CELL_TO_INTERPS, serialize_cell_map(data.cell_to_interps));
   920→    write_section(SectionType::ADMIN_POLYGONS, serialize_vector(data.admin_polygons));
   921→    write_section(SectionType::ADMIN_VERTICES, serialize_vector(data.admin_vertices));
   922→    write_section(SectionType::CELL_TO_ADMIN, serialize_cell_map(data.cell_to_admin));
   923→
   924→    std::cerr << "Cache saved." << std::endl;
   925→}
   926→
   927→static bool deserialize_cache(ParsedData& data, const std::string& path) {
   928→    std::cerr << "Loading cache from " << path << "..." << std::endl;
   929→    std::ifstream f(path, std::ios::binary);
   930→    if (!f.is_open()) {
   931→        std::cerr << "Error: could not open cache file: " << path << std::endl;
   932→        return false;
   933→    }
   934→
   935→    char magic[8];
   936→    f.read(magic, 8);
   937→    if (std::memcmp(magic, CACHE_MAGIC, 8) != 0) {
   938→        std::cerr << "Error: invalid cache magic" << std::endl;
   939→        return false;
   940→    }
   941→
   942→    uint32_t version;
   943→    f.read(reinterpret_cast<char*>(&version), sizeof(uint32_t));
   944→    if (version != CACHE_VERSION) {
   945→        std::cerr << "Error: unsupported cache version " << version << std::endl;
   946→        return false;
   947→    }
   948→
   949→    uint32_t section_count;
   950→    f.read(reinterpret_cast<char*>(&section_count), sizeof(uint32_t));
   951→
   952→    for (uint32_t s = 0; s < section_count; s++) {
   953→        uint32_t type;
   954→        uint64_t length;
   955→        f.read(reinterpret_cast<char*>(&type), sizeof(uint32_t));
   956→        f.read(reinterpret_cast<char*>(&length), sizeof(uint64_t));
   957→
   958→        std::vector<char> blob(length);
   959→        f.read(blob.data(), length);
   960→
   961→        switch (static_cast<SectionType>(type)) {
   962→        case SectionType::STRING_POOL: {
   963→            if (length < sizeof(uint64_t)) return false;
   964→            uint64_t sz;
   965→            std::memcpy(&sz, blob.data(), sizeof(uint64_t));
   966→            auto& sp = data.string_pool.mutable_data();
   967→            sp.resize(sz);
   968→            if (sz > 0) std::memcpy(sp.data(), blob.data() + sizeof(uint64_t), sz);
   969→            break;
   970→        }
   971→        case SectionType::WAYS:
   972→            if (!deserialize_vector(blob.data(), length, data.ways)) return false;
   973→            break;
   974→        case SectionType::STREET_NODES:
   975→            if (!deserialize_vector(blob.data(), length, data.street_nodes)) return false;
   976→            break;
   977→        case SectionType::CELL_TO_WAYS:
   978→            if (!deserialize_cell_map(blob.data(), length, data.cell_to_ways)) return false;
   979→            break;
   980→        case SectionType::ADDR_POINTS:
   981→            if (!deserialize_vector(blob.data(), length, data.addr_points)) return false;
   982→            break;
   983→        case SectionType::CELL_TO_ADDRS:
   984→            if (!deserialize_cell_map(blob.data(), length, data.cell_to_addrs)) return false;
   985→            break;
   986→        case SectionType::INTERP_WAYS:
   987→            if (!deserialize_vector(blob.data(), length, data.interp_ways)) return false;
   988→            break;
   989→        case SectionType::INTERP_NODES:
   990→            if (!deserialize_vector(blob.data(), length, data.interp_nodes)) return false;
   991→            break;
   992→        case SectionType::CELL_TO_INTERPS:
   993→            if (!deserialize_cell_map(blob.data(), length, data.cell_to_interps)) return false;
   994→            break;
   995→        case SectionType::ADMIN_POLYGONS:
   996→            if (!deserialize_vector(blob.data(), length, data.admin_polygons)) return false;
   997→            break;
   998→        case SectionType::ADMIN_VERTICES:
   999→            if (!deserialize_vector(blob.data(), length, data.admin_vertices)) return false;
  1000→            break;
  1001→        case SectionType::CELL_TO_ADMIN:
  1002→            if (!deserialize_cell_map(blob.data(), length, data.cell_to_admin)) return false;
  1003→            break;
  1004→        default:
  1005→            std::cerr << "Warning: unknown cache section type " << type << ", skipping" << std::endl;
  1006→            break;
  1007→        }
  1008→    }
  1009→
  1010→    std::cerr << "Cache loaded: " << data.ways.size() << " ways, "
  1011→              << data.addr_points.size() << " addrs, "
  1012→              << data.interp_ways.size() << " interps, "
  1013→              << data.admin_polygons.size() << " admin polygons" << std::endl;
  1014→    return true;
  1015→}
  1016→
  1017→// --- Continent bounding box filtering ---
  1018→
  1019→struct ContinentBBox {
  1020→    const char* name;
  1021→    double min_lat, max_lat, min_lng, max_lng;
  1022→};
  1023→

// --- Cache serialization ---

enum class SectionType : uint32_t {
    STRING_POOL = 0,
    WAYS = 1,
    STREET_NODES = 2,
    CELL_TO_WAYS = 3,
    ADDR_POINTS = 4,
    CELL_TO_ADDRS = 5,
    INTERP_WAYS = 6,
    INTERP_NODES = 7,
    CELL_TO_INTERPS = 8,
    ADMIN_POLYGONS = 9,
    ADMIN_VERTICES = 10,
    CELL_TO_ADMIN = 11,
};

static const char CACHE_MAGIC[8] = {'T','G','C','A','C','H','E','\0'};
static const uint32_t CACHE_VERSION = 1;
static const uint32_t CACHE_SECTION_COUNT = 12;

// Serialize a vector of structs to a binary blob
template<typename T>
static std::vector<char> serialize_vector(const std::vector<T>& vec) {
    std::vector<char> buf;
    uint64_t count = vec.size();
    buf.resize(sizeof(uint64_t) + count * sizeof(T));
    std::memcpy(buf.data(), &count, sizeof(uint64_t));
    if (count > 0) {
        std::memcpy(buf.data() + sizeof(uint64_t), vec.data(), count * sizeof(T));
    }
    return buf;
}

// Deserialize a vector of structs from a binary blob
template<typename T>
static bool deserialize_vector(const char* data, uint64_t length, std::vector<T>& vec) {
    if (length < sizeof(uint64_t)) return false;
    uint64_t count;
    std::memcpy(&count, data, sizeof(uint64_t));
    if (length < sizeof(uint64_t) + count * sizeof(T)) return false;
    vec.resize(count);
    if (count > 0) {
        std::memcpy(vec.data(), data + sizeof(uint64_t), count * sizeof(T));
    }
    return true;
}

// Serialize a cell map (uint64_t -> vector<uint32_t>)
static std::vector<char> serialize_cell_map(const std::unordered_map<uint64_t, std::vector<uint32_t>>& map) {
    // Calculate total size
    size_t total = sizeof(uint64_t); // entry count
    for (const auto& [cell_id, ids] : map) {
        total += sizeof(uint64_t) + sizeof(uint32_t) + ids.size() * sizeof(uint32_t);
    }
    std::vector<char> buf(total);
    char* ptr = buf.data();

    uint64_t entry_count = map.size();
    std::memcpy(ptr, &entry_count, sizeof(uint64_t));
    ptr += sizeof(uint64_t);

    for (const auto& [cell_id, ids] : map) {
        std::memcpy(ptr, &cell_id, sizeof(uint64_t));
        ptr += sizeof(uint64_t);
        uint32_t id_count = static_cast<uint32_t>(ids.size());
        std::memcpy(ptr, &id_count, sizeof(uint32_t));
        ptr += sizeof(uint32_t);
        if (id_count > 0) {
            std::memcpy(ptr, ids.data(), id_count * sizeof(uint32_t));
            ptr += id_count * sizeof(uint32_t);
        }
    }
    return buf;
}

// Deserialize a cell map
static bool deserialize_cell_map(const char* data, uint64_t length,
                                  std::unordered_map<uint64_t, std::vector<uint32_t>>& map) {
    if (length < sizeof(uint64_t)) return false;
    const char* ptr = data;
    const char* end = data + length;

    uint64_t entry_count;
    std::memcpy(&entry_count, ptr, sizeof(uint64_t));
    ptr += sizeof(uint64_t);

    map.reserve(entry_count);
    for (uint64_t i = 0; i < entry_count; i++) {
        if (ptr + sizeof(uint64_t) + sizeof(uint32_t) > end) return false;
        uint64_t cell_id;
        std::memcpy(&cell_id, ptr, sizeof(uint64_t));
        ptr += sizeof(uint64_t);
        uint32_t id_count;
        std::memcpy(&id_count, ptr, sizeof(uint32_t));
        ptr += sizeof(uint32_t);
        if (ptr + id_count * sizeof(uint32_t) > end) return false;
        std::vector<uint32_t> ids(id_count);
        if (id_count > 0) {
            std::memcpy(ids.data(), ptr, id_count * sizeof(uint32_t));
            ptr += id_count * sizeof(uint32_t);
        }
        map[cell_id] = std::move(ids);
    }
    return true;
}

static void serialize_cache(const ParsedData& data, const std::string& path) {
    std::cerr << "Saving cache to " << path << "..." << std::endl;
    std::ofstream f(path, std::ios::binary);
    if (!f.is_open()) {
        std::cerr << "Error: could not open cache file for writing: " << path << std::endl;
        return;
    }

    f.write(CACHE_MAGIC, 8);
    f.write(reinterpret_cast<const char*>(&CACHE_VERSION), sizeof(uint32_t));
    f.write(reinterpret_cast<const char*>(&CACHE_SECTION_COUNT), sizeof(uint32_t));

    // Helper to write a section
    auto write_section = [&](SectionType type, const std::vector<char>& blob) {
        uint32_t t = static_cast<uint32_t>(type);
        uint64_t len = blob.size();
        f.write(reinterpret_cast<const char*>(&t), sizeof(uint32_t));
        f.write(reinterpret_cast<const char*>(&len), sizeof(uint64_t));
        f.write(blob.data(), len);
    };

    // STRING_POOL: raw char vector
    {
        const auto& sp = data.string_pool.data();
        std::vector<char> blob(sizeof(uint64_t) + sp.size());
        uint64_t sz = sp.size();
        std::memcpy(blob.data(), &sz, sizeof(uint64_t));
        if (sz > 0) std::memcpy(blob.data() + sizeof(uint64_t), sp.data(), sz);
        write_section(SectionType::STRING_POOL, blob);
    }

    write_section(SectionType::WAYS, serialize_vector(data.ways));
    write_section(SectionType::STREET_NODES, serialize_vector(data.street_nodes));
    write_section(SectionType::CELL_TO_WAYS, serialize_cell_map(data.cell_to_ways));
    write_section(SectionType::ADDR_POINTS, serialize_vector(data.addr_points));
    write_section(SectionType::CELL_TO_ADDRS, serialize_cell_map(data.cell_to_addrs));
    write_section(SectionType::INTERP_WAYS, serialize_vector(data.interp_ways));
    write_section(SectionType::INTERP_NODES, serialize_vector(data.interp_nodes));
    write_section(SectionType::CELL_TO_INTERPS, serialize_cell_map(data.cell_to_interps));
    write_section(SectionType::ADMIN_POLYGONS, serialize_vector(data.admin_polygons));
    write_section(SectionType::ADMIN_VERTICES, serialize_vector(data.admin_vertices));
    write_section(SectionType::CELL_TO_ADMIN, serialize_cell_map(data.cell_to_admin));

    std::cerr << "Cache saved." << std::endl;
}

static bool deserialize_cache(ParsedData& data, const std::string& path) {
    std::cerr << "Loading cache from " << path << "..." << std::endl;
    std::ifstream f(path, std::ios::binary);
    if (!f.is_open()) {
        std::cerr << "Error: could not open cache file: " << path << std::endl;
        return false;
    }

    char magic[8];
    f.read(magic, 8);
    if (std::memcmp(magic, CACHE_MAGIC, 8) != 0) {
        std::cerr << "Error: invalid cache magic" << std::endl;
        return false;
    }

    uint32_t version;
    f.read(reinterpret_cast<char*>(&version), sizeof(uint32_t));
    if (version != CACHE_VERSION) {
        std::cerr << "Error: unsupported cache version " << version << std::endl;
        return false;
    }

    uint32_t section_count;
    f.read(reinterpret_cast<char*>(&section_count), sizeof(uint32_t));

    for (uint32_t s = 0; s < section_count; s++) {
        uint32_t type;
        uint64_t length;
        f.read(reinterpret_cast<char*>(&type), sizeof(uint32_t));
        f.read(reinterpret_cast<char*>(&length), sizeof(uint64_t));

        std::vector<char> blob(length);
        f.read(blob.data(), length);

        switch (static_cast<SectionType>(type)) {
        case SectionType::STRING_POOL: {
            if (length < sizeof(uint64_t)) return false;
            uint64_t sz;
            std::memcpy(&sz, blob.data(), sizeof(uint64_t));
            auto& sp = data.string_pool.mutable_data();
            sp.resize(sz);
            if (sz > 0) std::memcpy(sp.data(), blob.data() + sizeof(uint64_t), sz);
            break;
        }
        case SectionType::WAYS:
            if (!deserialize_vector(blob.data(), length, data.ways)) return false;
            break;
        case SectionType::STREET_NODES:
            if (!deserialize_vector(blob.data(), length, data.street_nodes)) return false;
            break;
        case SectionType::CELL_TO_WAYS:
            if (!deserialize_cell_map(blob.data(), length, data.cell_to_ways)) return false;
            break;
        case SectionType::ADDR_POINTS:
            if (!deserialize_vector(blob.data(), length, data.addr_points)) return false;
            break;
        case SectionType::CELL_TO_ADDRS:
            if (!deserialize_cell_map(blob.data(), length, data.cell_to_addrs)) return false;
            break;
        case SectionType::INTERP_WAYS:
            if (!deserialize_vector(blob.data(), length, data.interp_ways)) return false;
            break;
        case SectionType::INTERP_NODES:
            if (!deserialize_vector(blob.data(), length, data.interp_nodes)) return false;
            break;
        case SectionType::CELL_TO_INTERPS:
            if (!deserialize_cell_map(blob.data(), length, data.cell_to_interps)) return false;
            break;
        case SectionType::ADMIN_POLYGONS:
            if (!deserialize_vector(blob.data(), length, data.admin_polygons)) return false;
            break;
        case SectionType::ADMIN_VERTICES:
            if (!deserialize_vector(blob.data(), length, data.admin_vertices)) return false;
            break;
        case SectionType::CELL_TO_ADMIN:
            if (!deserialize_cell_map(blob.data(), length, data.cell_to_admin)) return false;
            break;
        default:
            std::cerr << "Warning: unknown cache section type " << type << ", skipping" << std::endl;
            break;
        }
    }

    std::cerr << "Cache loaded: " << data.ways.size() << " ways, "
              << data.addr_points.size() << " addrs, "
              << data.interp_ways.size() << " interps, "
              << data.admin_polygons.size() << " admin polygons" << std::endl;
    return true;
}

// --- Continent bounding box filtering ---

struct ContinentBBox {
    const char* name;
    double min_lat, max_lat, min_lng, max_lng;
};


#include "cache.h"

#include <cstring>
#include <fstream>
#include <iostream>

enum class SectionType : uint32_t {
    STRING_POOL = 0, WAYS = 1, STREET_NODES = 2, CELL_TO_WAYS = 3,
    ADDR_POINTS = 4, CELL_TO_ADDRS = 5, INTERP_WAYS = 6, INTERP_NODES = 7,
    CELL_TO_INTERPS = 8, ADMIN_POLYGONS = 9, ADMIN_VERTICES = 10, CELL_TO_ADMIN = 11,
};

static const char CACHE_MAGIC[8] = {'T','G','C','A','C','H','E','\0'};
static const uint32_t CACHE_VERSION = 1;
static const uint32_t CACHE_SECTION_COUNT = 12;

template<typename T>
static std::vector<char> serialize_vector(const std::vector<T>& vec) {
    std::vector<char> buf;
    uint64_t count = vec.size();
    buf.resize(sizeof(uint64_t) + count * sizeof(T));
    std::memcpy(buf.data(), &count, sizeof(uint64_t));
    if (count > 0) std::memcpy(buf.data() + sizeof(uint64_t), vec.data(), count * sizeof(T));
    return buf;
}

template<typename T>
static bool deserialize_vector(const char* data, uint64_t length, std::vector<T>& vec) {
    if (length < sizeof(uint64_t)) return false;
    uint64_t count;
    std::memcpy(&count, data, sizeof(uint64_t));
    if (length < sizeof(uint64_t) + count * sizeof(T)) return false;
    vec.resize(count);
    if (count > 0) std::memcpy(vec.data(), data + sizeof(uint64_t), count * sizeof(T));
    return true;
}

static std::vector<char> serialize_cell_map(const std::unordered_map<uint64_t, std::vector<uint32_t>>& map) {
    size_t total = sizeof(uint64_t);
    for (const auto& [cell_id, ids] : map)
        total += sizeof(uint64_t) + sizeof(uint32_t) + ids.size() * sizeof(uint32_t);
    std::vector<char> buf(total);
    char* ptr = buf.data();
    uint64_t entry_count = map.size();
    std::memcpy(ptr, &entry_count, sizeof(uint64_t)); ptr += sizeof(uint64_t);
    for (const auto& [cell_id, ids] : map) {
        std::memcpy(ptr, &cell_id, sizeof(uint64_t)); ptr += sizeof(uint64_t);
        uint32_t id_count = static_cast<uint32_t>(ids.size());
        std::memcpy(ptr, &id_count, sizeof(uint32_t)); ptr += sizeof(uint32_t);
        if (id_count > 0) { std::memcpy(ptr, ids.data(), id_count * sizeof(uint32_t)); ptr += id_count * sizeof(uint32_t); }
    }
    return buf;
}

static bool deserialize_cell_map(const char* data, uint64_t length,
                                  std::unordered_map<uint64_t, std::vector<uint32_t>>& map) {
    if (length < sizeof(uint64_t)) return false;
    const char* ptr = data;
    const char* end = data + length;
    uint64_t entry_count;
    std::memcpy(&entry_count, ptr, sizeof(uint64_t)); ptr += sizeof(uint64_t);
    map.reserve(entry_count);
    for (uint64_t i = 0; i < entry_count; i++) {
        if (ptr + sizeof(uint64_t) + sizeof(uint32_t) > end) return false;
        uint64_t cell_id;
        std::memcpy(&cell_id, ptr, sizeof(uint64_t)); ptr += sizeof(uint64_t);
        uint32_t id_count;
        std::memcpy(&id_count, ptr, sizeof(uint32_t)); ptr += sizeof(uint32_t);
        if (ptr + id_count * sizeof(uint32_t) > end) return false;
        std::vector<uint32_t> ids(id_count);
        if (id_count > 0) { std::memcpy(ids.data(), ptr, id_count * sizeof(uint32_t)); ptr += id_count * sizeof(uint32_t); }
        map[cell_id] = std::move(ids);
    }
    return true;
}

void serialize_cache(const ParsedData& data, const std::string& path) {
    std::cerr << "Saving cache to " << path << "..." << std::endl;
    std::ofstream f(path, std::ios::binary);
    if (!f.is_open()) { std::cerr << "Error: could not open cache file for writing: " << path << std::endl; return; }
    f.write(CACHE_MAGIC, 8);
    f.write(reinterpret_cast<const char*>(&CACHE_VERSION), sizeof(uint32_t));
    f.write(reinterpret_cast<const char*>(&CACHE_SECTION_COUNT), sizeof(uint32_t));
    auto write_section = [&](SectionType type, const std::vector<char>& blob) {
        uint32_t t = static_cast<uint32_t>(type);
        uint64_t len = blob.size();
        f.write(reinterpret_cast<const char*>(&t), sizeof(uint32_t));
        f.write(reinterpret_cast<const char*>(&len), sizeof(uint64_t));
        f.write(blob.data(), len);
    };
    { const auto& sp = data.string_pool.data();
      std::vector<char> blob(sizeof(uint64_t) + sp.size());
      uint64_t sz = sp.size();
      std::memcpy(blob.data(), &sz, sizeof(uint64_t));
      if (sz > 0) std::memcpy(blob.data() + sizeof(uint64_t), sp.data(), sz);
      write_section(SectionType::STRING_POOL, blob); }
    write_section(SectionType::WAYS, serialize_vector(data.ways));
    write_section(SectionType::STREET_NODES, serialize_vector(data.street_nodes));
    write_section(SectionType::CELL_TO_WAYS, serialize_cell_map(data.cell_to_ways));
    write_section(SectionType::ADDR_POINTS, serialize_vector(data.addr_points));
    write_section(SectionType::CELL_TO_ADDRS, serialize_cell_map(data.cell_to_addrs));
    write_section(SectionType::INTERP_WAYS, serialize_vector(data.interp_ways));
    write_section(SectionType::INTERP_NODES, serialize_vector(data.interp_nodes));
    write_section(SectionType::CELL_TO_INTERPS, serialize_cell_map(data.cell_to_interps));
    write_section(SectionType::ADMIN_POLYGONS, serialize_vector(data.admin_polygons));
    write_section(SectionType::ADMIN_VERTICES, serialize_vector(data.admin_vertices));
    write_section(SectionType::CELL_TO_ADMIN, serialize_cell_map(data.cell_to_admin));
    std::cerr << "Cache saved." << std::endl;
}

bool deserialize_cache(ParsedData& data, const std::string& path) {
    std::cerr << "Loading cache from " << path << "..." << std::endl;
    std::ifstream f(path, std::ios::binary);
    if (!f.is_open()) { std::cerr << "Error: could not open cache file: " << path << std::endl; return false; }
    char magic[8]; f.read(magic, 8);
    if (std::memcmp(magic, CACHE_MAGIC, 8) != 0) { std::cerr << "Error: invalid cache magic" << std::endl; return false; }
    uint32_t version; f.read(reinterpret_cast<char*>(&version), sizeof(uint32_t));
    if (version != CACHE_VERSION) { std::cerr << "Error: unsupported cache version " << version << std::endl; return false; }
    uint32_t section_count; f.read(reinterpret_cast<char*>(&section_count), sizeof(uint32_t));
    for (uint32_t s = 0; s < section_count; s++) {
        uint32_t type; uint64_t length;
        f.read(reinterpret_cast<char*>(&type), sizeof(uint32_t));
        f.read(reinterpret_cast<char*>(&length), sizeof(uint64_t));
        std::vector<char> blob(length); f.read(blob.data(), length);
        switch (static_cast<SectionType>(type)) {
        case SectionType::STRING_POOL: {
            if (length < sizeof(uint64_t)) return false;
            uint64_t sz; std::memcpy(&sz, blob.data(), sizeof(uint64_t));
            auto& sp = data.string_pool.mutable_data(); sp.resize(sz);
            if (sz > 0) std::memcpy(sp.data(), blob.data() + sizeof(uint64_t), sz);
            break; }
        case SectionType::WAYS: if (!deserialize_vector(blob.data(), length, data.ways)) return false; break;
        case SectionType::STREET_NODES: if (!deserialize_vector(blob.data(), length, data.street_nodes)) return false; break;
        case SectionType::CELL_TO_WAYS: if (!deserialize_cell_map(blob.data(), length, data.cell_to_ways)) return false; break;
        case SectionType::ADDR_POINTS: if (!deserialize_vector(blob.data(), length, data.addr_points)) return false; break;
        case SectionType::CELL_TO_ADDRS: if (!deserialize_cell_map(blob.data(), length, data.cell_to_addrs)) return false; break;
        case SectionType::INTERP_WAYS: if (!deserialize_vector(blob.data(), length, data.interp_ways)) return false; break;
        case SectionType::INTERP_NODES: if (!deserialize_vector(blob.data(), length, data.interp_nodes)) return false; break;
        case SectionType::CELL_TO_INTERPS: if (!deserialize_cell_map(blob.data(), length, data.cell_to_interps)) return false; break;
        case SectionType::ADMIN_POLYGONS: if (!deserialize_vector(blob.data(), length, data.admin_polygons)) return false; break;
        case SectionType::ADMIN_VERTICES: if (!deserialize_vector(blob.data(), length, data.admin_vertices)) return false; break;
        case SectionType::CELL_TO_ADMIN: if (!deserialize_cell_map(blob.data(), length, data.cell_to_admin)) return false; break;
        default: std::cerr << "Warning: unknown cache section type " << type << ", skipping" << std::endl; break;
        }
    }
    std::cerr << "Cache loaded: " << data.ways.size() << " ways, " << data.addr_points.size() << " addrs, "
              << data.interp_ways.size() << " interps, " << data.admin_polygons.size() << " admin polygons" << std::endl;
    return true;
}


File created successfully at: /root/traccar-geocoder/builder/src/cache.cpp

#include "cache.h"

#include <cstring>
#include <fstream>
#include <iostream>

enum class SectionType : uint32_t {
    STRING_POOL = 0, WAYS = 1, STREET_NODES = 2, CELL_TO_WAYS = 3,
    ADDR_POINTS = 4, CELL_TO_ADDRS = 5, INTERP_WAYS = 6, INTERP_NODES = 7,
    CELL_TO_INTERPS = 8, ADMIN_POLYGONS = 9, ADMIN_VERTICES = 10, CELL_TO_ADMIN = 11,
};

static const char CACHE_MAGIC[8] = {'T','G','C','A','C','H','E','\0'};
static const uint32_t CACHE_VERSION = 1;
static const uint32_t CACHE_SECTION_COUNT = 12;

template<typename T>
static std::vector<char> serialize_vector(const std::vector<T>& vec) {
    std::vector<char> buf;
    uint64_t count = vec.size();
    buf.resize(sizeof(uint64_t) + count * sizeof(T));
    std::memcpy(buf.data(), &count, sizeof(uint64_t));
    if (count > 0) std::memcpy(buf.data() + sizeof(uint64_t), vec.data(), count * sizeof(T));
    return buf;
}

template<typename T>
static bool deserialize_vector(const char* data, uint64_t length, std::vector<T>& vec) {
    if (length < sizeof(uint64_t)) return false;
    uint64_t count;
    std::memcpy(&count, data, sizeof(uint64_t));
    if (length < sizeof(uint64_t) + count * sizeof(T)) return false;
    vec.resize(count);
    if (count > 0) std::memcpy(vec.data(), data + sizeof(uint64_t), count * sizeof(T));
    return true;
}

static std::vector<char> serialize_cell_map(const std::unordered_map<uint64_t, std::vector<uint32_t>>& map) {
    size_t total = sizeof(uint64_t);
    for (const auto& [cell_id, ids] : map)
        total += sizeof(uint64_t) + sizeof(uint32_t) + ids.size() * sizeof(uint32_t);
    std::vector<char> buf(total);
    char* ptr = buf.data();
    uint64_t entry_count = map.size();
    std::memcpy(ptr, &entry_count, sizeof(uint64_t)); ptr += sizeof(uint64_t);
    for (const auto& [cell_id, ids] : map) {
        std::memcpy(ptr, &cell_id, sizeof(uint64_t)); ptr += sizeof(uint64_t);
        uint32_t id_count = static_cast<uint32_t>(ids.size());
        std::memcpy(ptr, &id_count, sizeof(uint32_t)); ptr += sizeof(uint32_t);
        if (id_count > 0) { std::memcpy(ptr, ids.data(), id_count * sizeof(uint32_t)); ptr += id_count * sizeof(uint32_t); }
    }
    return buf;
}

static bool deserialize_cell_map(const char* data, uint64_t length,
                                  std::unordered_map<uint64_t, std::vector<uint32_t>>& map) {
    if (length < sizeof(uint64_t)) return false;
    const char* ptr = data;
    const char* end = data + length;
    uint64_t entry_count;
    std::memcpy(&entry_count, ptr, sizeof(uint64_t)); ptr += sizeof(uint64_t);
    map.reserve(entry_count);
    for (uint64_t i = 0; i < entry_count; i++) {
        if (ptr + sizeof(uint64_t) + sizeof(uint32_t) > end) return false;
        uint64_t cell_id;
        std::memcpy(&cell_id, ptr, sizeof(uint64_t)); ptr += sizeof(uint64_t);
        uint32_t id_count;
        std::memcpy(&id_count, ptr, sizeof(uint32_t)); ptr += sizeof(uint32_t);
        if (ptr + id_count * sizeof(uint32_t) > end) return false;
        std::vector<uint32_t> ids(id_count);
        if (id_count > 0) { std::memcpy(ids.data(), ptr, id_count * sizeof(uint32_t)); ptr += id_count * sizeof(uint32_t); }
        map[cell_id] = std::move(ids);
    }
    return true;
}

void serialize_cache(const ParsedData& data, const std::string& path) {
    std::cerr << "Saving cache to " << path << "..." << std::endl;
    std::ofstream f(path, std::ios::binary);
    if (!f.is_open()) { std::cerr << "Error: could not open cache file for writing: " << path << std::endl; return; }
    f.write(CACHE_MAGIC, 8);
    f.write(reinterpret_cast<const char*>(&CACHE_VERSION), sizeof(uint32_t));
    f.write(reinterpret_cast<const char*>(&CACHE_SECTION_COUNT), sizeof(uint32_t));
    auto write_section = [&](SectionType type, const std::vector<char>& blob) {
        uint32_t t = static_cast<uint32_t>(type);
        uint64_t len = blob.size();
        f.write(reinterpret_cast<const char*>(&t), sizeof(uint32_t));
        f.write(reinterpret_cast<const char*>(&len), sizeof(uint64_t));
        f.write(blob.data(), len);
    };
    { const auto& sp = data.string_pool.data();
      std::vector<char> blob(sizeof(uint64_t) + sp.size());
      uint64_t sz = sp.size();
      std::memcpy(blob.data(), &sz, sizeof(uint64_t));
      if (sz > 0) std::memcpy(blob.data() + sizeof(uint64_t), sp.data(), sz);
      write_section(SectionType::STRING_POOL, blob); }
    write_section(SectionType::WAYS, serialize_vector(data.ways));
    write_section(SectionType::STREET_NODES, serialize_vector(data.street_nodes));
    write_section(SectionType::CELL_TO_WAYS, serialize_cell_map(data.cell_to_ways));
    write_section(SectionType::ADDR_POINTS, serialize_vector(data.addr_points));
    write_section(SectionType::CELL_TO_ADDRS, serialize_cell_map(data.cell_to_addrs));
    write_section(SectionType::INTERP_WAYS, serialize_vector(data.interp_ways));
    write_section(SectionType::INTERP_NODES, serialize_vector(data.interp_nodes));
    write_section(SectionType::CELL_TO_INTERPS, serialize_cell_map(data.cell_to_interps));
    write_section(SectionType::ADMIN_POLYGONS, serialize_vector(data.admin_polygons));
    write_section(SectionType::ADMIN_VERTICES, serialize_vector(data.admin_vertices));
    write_section(SectionType::CELL_TO_ADMIN, serialize_cell_map(data.cell_to_admin));
    std::cerr << "Cache saved." << std::endl;
}

bool deserialize_cache(ParsedData& data, const std::string& path) {
    std::cerr << "Loading cache from " << path << "..." << std::endl;
    std::ifstream f(path, std::ios::binary);
    if (!f.is_open()) { std::cerr << "Error: could not open cache file: " << path << std::endl; return false; }
    char magic[8]; f.read(magic, 8);
    if (std::memcmp(magic, CACHE_MAGIC, 8) != 0) { std::cerr << "Error: invalid cache magic" << std::endl; return false; }
    uint32_t version; f.read(reinterpret_cast<char*>(&version), sizeof(uint32_t));
    if (version != CACHE_VERSION) { std::cerr << "Error: unsupported cache version " << version << std::endl; return false; }
    uint32_t section_count; f.read(reinterpret_cast<char*>(&section_count), sizeof(uint32_t));
    for (uint32_t s = 0; s < section_count; s++) {
        uint32_t type; uint64_t length;
        f.read(reinterpret_cast<char*>(&type), sizeof(uint32_t));
        f.read(reinterpret_cast<char*>(&length), sizeof(uint64_t));
        std::vector<char> blob(length); f.read(blob.data(), length);
        switch (static_cast<SectionType>(type)) {
        case SectionType::STRING_POOL: {
            if (length < sizeof(uint64_t)) return false;
            uint64_t sz; std::memcpy(&sz, blob.data(), sizeof(uint64_t));
            auto& sp = data.string_pool.mutable_data(); sp.resize(sz);
            if (sz > 0) std::memcpy(sp.data(), blob.data() + sizeof(uint64_t), sz);
            break; }
        case SectionType::WAYS: if (!deserialize_vector(blob.data(), length, data.ways)) return false; break;
        case SectionType::STREET_NODES: if (!deserialize_vector(blob.data(), length, data.street_nodes)) return false; break;
        case SectionType::CELL_TO_WAYS: if (!deserialize_cell_map(blob.data(), length, data.cell_to_ways)) return false; break;
        case SectionType::ADDR_POINTS: if (!deserialize_vector(blob.data(), length, data.addr_points)) return false; break;
        case SectionType::CELL_TO_ADDRS: if (!deserialize_cell_map(blob.data(), length, data.cell_to_addrs)) return false; break;
        case SectionType::INTERP_WAYS: if (!deserialize_vector(blob.data(), length, data.interp_ways)) return false; break;
        case SectionType::INTERP_NODES: if (!deserialize_vector(blob.data(), length, data.interp_nodes)) return false; break;
        case SectionType::CELL_TO_INTERPS: if (!deserialize_cell_map(blob.data(), length, data.cell_to_interps)) return false; break;
        case SectionType::ADMIN_POLYGONS: if (!deserialize_vector(blob.data(), length, data.admin_polygons)) return false; break;
        case SectionType::ADMIN_VERTICES: if (!deserialize_vector(blob.data(), length, data.admin_vertices)) return false; break;
        case SectionType::CELL_TO_ADMIN: if (!deserialize_cell_map(blob.data(), length, data.cell_to_admin)) return false; break;
        default: std::cerr << "Warning: unknown cache section type " << type << ", skipping" << std::endl; break;
        }
    }
    std::cerr << "Cache loaded: " << data.ways.size() << " ways, " << data.addr_points.size() << " addrs, "
              << data.interp_ways.size() << " interps, " << data.admin_polygons.size() << " admin polygons" << std::endl;
    return true;
}


Now continent_filter and cell_index. These are the two biggest remaining pieces:


#pragma once

#include "parsed_data.h"

struct ContinentBBox {
    const char* name;
    double min_lat, max_lat, min_lng, max_lng;
};

extern const ContinentBBox kContinents[];
extern const size_t kContinentCount;

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


File created successfully at: /root/traccar-geocoder/builder/src/continent_filter.h

#pragma once

#include "parsed_data.h"

struct ContinentBBox {
    const char* name;
    double min_lat, max_lat, min_lng, max_lng;
};

extern const ContinentBBox kContinents[];
extern const size_t kContinentCount;

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


#pragma once

#include <string>
#include <unordered_map>
#include <vector>

#include "types.h"
#include "parsed_data.h"

std::vector<uint32_t> write_entries(
    const std::string& path,
    const std::vector<uint64_t>& sorted_cells,
    const std::unordered_map<uint64_t, std::vector<uint32_t>>& cell_map);

std::vector<uint32_t> write_entries_from_sorted(
    const std::string& path,
    const std::vector<uint64_t>& sorted_cells,
    const std::vector<CellItemPair>& sorted_pairs);

void write_cell_index(
    const std::string& cells_path,
    const std::string& entries_path,
    const std::unordered_map<uint64_t, std::vector<uint32_t>>& cell_map);

void write_index(const ParsedData& data, const std::string& output_dir, IndexMode mode);


File created successfully at: /root/traccar-geocoder/builder/src/cell_index.h

#pragma once

#include <string>
#include <unordered_map>
#include <vector>

#include "types.h"
#include "parsed_data.h"

std::vector<uint32_t> write_entries(
    const std::string& path,
    const std::vector<uint64_t>& sorted_cells,
    const std::unordered_map<uint64_t, std::vector<uint32_t>>& cell_map);

std::vector<uint32_t> write_entries_from_sorted(
    const std::string& path,
    const std::vector<uint64_t>& sorted_cells,
    const std::vector<CellItemPair>& sorted_pairs);

void write_cell_index(
    const std::string& cells_path,
    const std::string& entries_path,
    const std::unordered_map<uint64_t, std::vector<uint32_t>>& cell_map);

void write_index(const ParsedData& data, const std::string& output_dir, IndexMode mode);


Now I need to create continent_filter.cpp and cell_index.cpp. Let me read those sections from build_index.cpp:


  1017→// --- Continent bounding box filtering ---
  1018→
  1019→struct ContinentBBox {
  1020→    const char* name;
  1021→    double min_lat, max_lat, min_lng, max_lng;
  1022→};
  1023→
  1024→static const ContinentBBox kContinents[] = {
  1025→    {"africa",            -35.0,  37.5,  -25.0,  55.0},
  1026→    {"asia",              -12.0,  82.0,   25.0, 180.0},

// --- Continent bounding box filtering ---

struct ContinentBBox {
    const char* name;
    double min_lat, max_lat, min_lng, max_lng;
};

static const ContinentBBox kContinents[] = {
    {"africa",            -35.0,  37.5,  -25.0,  55.0},
    {"asia",              -12.0,  82.0,   25.0, 180.0},


#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 (uint16_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;
}


File created successfully at: /root/traccar-geocoder/builder/src/continent_filter.cpp

#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 (uint16_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;
}


Now cell_index.cpp — this is the index writing module. Let me read the relevant section:


  1265→// --- Write cell index ---
  1266→
  1267→// NO_DATA is in types.h
  1268→
  1269→// Write entries file from a hash map (used for addr and interp cell maps).

// --- Write cell index ---

// NO_DATA is in types.h

// Write entries file from a hash map (used for addr and interp cell maps).