Explore the geocoder index file formats. I need to understand what each binary file contains and how it's structured. Look at:
1. builder/src/cell_index.cpp - how entries, cells, nodes, ways, vertices, polygons, strings, etc. are written
2. Any header/struct definitions that define the binary layout of each file type
3. The server/runtime code that reads these files (likely in src/ directory) to understand the format

For each file type (geo_cells.bin, street_entries.bin, street_nodes.bin, street_ways.bin, addr_entries.bin, addr_points.bin, admin_cells.bin, admin_entries.bin, admin_polygons.bin, admin_vertices.bin, interp_entries.bin, interp_nodes.bin, interp_ways.bin, strings.bin), tell me:
- What data it contains
- The record size (fixed or variable)
- How records are ordered/indexed
- Whether records reference other files (e.g. string offsets, cell IDs)

Be very thorough - I need this to design a smart incremental update system.


I'll thoroughly explore the geocoder index file formats by examining the builder code and server runtime code. Let me start by understanding the structure.


Bash

find /root/traccar-geocoder -type f -name "*.cpp" -o -name "*.h" | grep -E "(index|cell|street|addr|admin|interp)" | head -30


     1→#pragma once
     2→
     3→#include <string>
     4→#include <unordered_map>
     5→#include <vector>
     6→
     7→#include "types.h"
     8→#include "parsed_data.h"
     9→
    10→std::vector<uint32_t> write_entries(
    11→    const std::string& path,
    12→    const std::vector<uint64_t>& sorted_cells,
    13→    const std::unordered_map<uint64_t, std::vector<uint32_t>>& cell_map);
    14→
    15→std::vector<uint32_t> write_entries_from_sorted(
    16→    const std::string& path,
    17→    const std::vector<uint64_t>& sorted_cells,
    18→    const std::vector<CellItemPair>& sorted_pairs);
    19→
    20→void write_cell_index(
    21→    const std::string& cells_path,
    22→    const std::string& entries_path,
    23→    const std::unordered_map<uint64_t, std::vector<uint32_t>>& cell_map);
    24→
    25→void write_index(const ParsedData& data, const std::string& output_dir, IndexMode mode);
    26→
    27→// Write admin polygon/vertex files with on-the-fly simplification at a given epsilon scale.
    28→// Scale 0 = uncapped (no simplification). Other files are symlinked/copied from source_dir.
    29→void write_quality_variant(const ParsedData& data, const std::string& source_dir,
    30→                           const std::string& output_dir, double epsilon_scale);
    31→


     1→#include "cell_index.h"
     2→
     3→#include <algorithm>
     4→#include <atomic>
     5→#include <cstring>
     6→#include <fstream>
     7→#include <future>
     8→#include <iostream>
     9→#include <thread>
    10→#include <unordered_map>
    11→#include <unordered_set>
    12→
    13→#include <unistd.h>
    14→
    15→#include "geometry.h"
    16→#include "s2_helpers.h"
    17→
    18→std::vector<uint32_t> write_entries(
    19→    const std::string& path,
    20→    const std::vector<uint64_t>& sorted_cells,
    21→    const std::unordered_map<uint64_t, std::vector<uint32_t>>& cell_map
    22→) {
    23→    struct CellRef { uint64_t cell_id; const std::vector<uint32_t>* ids; };
    24→    std::vector<CellRef> sorted_refs;
    25→    sorted_refs.reserve(cell_map.size());
    26→    for (auto& [id, ids] : cell_map) sorted_refs.push_back({id, &ids});
    27→    std::sort(sorted_refs.begin(), sorted_refs.end(),
    28→        [](const CellRef& a, const CellRef& b) { return a.cell_id < b.cell_id; });
    29→
    30→    std::vector<uint32_t> offsets(sorted_cells.size(), NO_DATA);
    31→    size_t total_size = 0;
    32→    for (auto& r : sorted_refs) total_size += sizeof(uint16_t) + r.ids->size() * sizeof(uint32_t);
    33→
    34→    std::vector<char> buf;
    35→    buf.reserve(total_size);
    36→    uint32_t current = 0;
    37→    size_t ri = 0;
    38→    for (uint32_t si = 0; si < sorted_cells.size() && ri < sorted_refs.size(); si++) {
    39→        if (sorted_cells[si] < sorted_refs[ri].cell_id) continue;
    40→        if (sorted_cells[si] > sorted_refs[ri].cell_id) { si--; ri++; continue; }
    41→        offsets[si] = current;
    42→        const auto& ids = *sorted_refs[ri].ids;
    43→        uint16_t count = static_cast<uint16_t>(std::min(ids.size(), size_t(MAX_VERTEX_COUNT)));
    44→        buf.insert(buf.end(), reinterpret_cast<const char*>(&count),
    45→                   reinterpret_cast<const char*>(&count) + sizeof(count));
    46→        buf.insert(buf.end(), reinterpret_cast<const char*>(ids.data()),
    47→                   reinterpret_cast<const char*>(ids.data()) + ids.size() * sizeof(uint32_t));
    48→        current += sizeof(uint16_t) + ids.size() * sizeof(uint32_t);
    49→        ri++;
    50→    }
    51→    std::ofstream f(path, std::ios::binary);
    52→    f.write(buf.data(), buf.size());
    53→    return offsets;
    54→}
    55→
    56→std::vector<uint32_t> write_entries_from_sorted(
    57→    const std::string& path,
    58→    const std::vector<uint64_t>& sorted_cells,
    59→    const std::vector<CellItemPair>& sorted_pairs
    60→) {
    61→    std::vector<uint32_t> offsets(sorted_cells.size(), NO_DATA);
    62→    if (sorted_pairs.empty()) {
    63→        std::ofstream f(path, std::ios::binary);
    64→        return offsets;
    65→    }
    66→
    67→    unsigned int nthreads = std::thread::hardware_concurrency();
    68→    if (nthreads == 0) nthreads = 4;
    69→    size_t cells_per_chunk = (sorted_cells.size() + nthreads - 1) / nthreads;
    70→
    71→    struct ChunkResult {
    72→        std::vector<char> buf;
    73→        size_t cell_start, cell_end;
    74→        uint32_t local_size;
    75→    };
    76→    std::vector<ChunkResult> chunks(nthreads);
    77→
    78→    std::vector<std::thread> threads;
    79→    for (unsigned int t = 0; t < nthreads; t++) {
    80→        size_t cs = t * cells_per_chunk;
    81→        size_t ce = std::min(cs + cells_per_chunk, sorted_cells.size());
    82→        if (cs >= sorted_cells.size()) break;
    83→
    84→        threads.emplace_back([&, t, cs, ce]() {
    85→            auto& chunk = chunks[t];
    86→            chunk.cell_start = cs;
    87→            chunk.cell_end = ce;
    88→            chunk.local_size = 0;
    89→
    90→            size_t pi = std::lower_bound(sorted_pairs.begin(), sorted_pairs.end(),
    91→                sorted_cells[cs], [](const CellItemPair& p, uint64_t id) {
    92→                    return p.cell_id < id;
    93→                }) - sorted_pairs.begin();
    94→
    95→            for (size_t si = cs; si < ce && pi < sorted_pairs.size(); si++) {
    96→                if (sorted_cells[si] < sorted_pairs[pi].cell_id) continue;
    97→                while (pi < sorted_pairs.size() && sorted_pairs[pi].cell_id < sorted_cells[si]) pi++;
    98→                if (pi >= sorted_pairs.size() || sorted_pairs[pi].cell_id != sorted_cells[si]) continue;
    99→
   100→                offsets[si] = chunk.local_size;
   101→                size_t start = pi;
   102→                while (pi < sorted_pairs.size() && sorted_pairs[pi].cell_id == sorted_cells[si]) pi++;
   103→                uint16_t count = static_cast<uint16_t>(std::min(pi - start, size_t(MAX_VERTEX_COUNT)));
   104→                size_t entry_size = sizeof(uint16_t) + (pi - start) * sizeof(uint32_t);
   105→                size_t buf_pos = chunk.buf.size();
   106→                chunk.buf.resize(buf_pos + entry_size);
   107→                memcpy(chunk.buf.data() + buf_pos, &count, sizeof(count));
   108→                for (size_t k = start; k < pi; k++) {
   109→                    memcpy(chunk.buf.data() + buf_pos + sizeof(uint16_t) + (k - start) * sizeof(uint32_t),
   110→                           &sorted_pairs[k].item_id, sizeof(uint32_t));
   111→                }
   112→                chunk.local_size += entry_size;
   113→            }
   114→        });
   115→    }
   116→    for (auto& t : threads) t.join();
   117→
   118→    uint32_t global_offset = 0;
   119→    for (auto& chunk : chunks) {
   120→        for (size_t si = chunk.cell_start; si < chunk.cell_end; si++) {
   121→            if (offsets[si] != NO_DATA) offsets[si] += global_offset;
   122→        }
   123→        global_offset += chunk.local_size;
   124→    }
   125→
   126→    std::vector<char> buf;
   127→    buf.reserve(global_offset);
   128→    for (auto& chunk : chunks) buf.insert(buf.end(), chunk.buf.begin(), chunk.buf.end());
   129→
   130→    std::ofstream f(path, std::ios::binary);
   131→    f.write(buf.data(), buf.size());
   132→    return offsets;
   133→}
   134→
   135→void write_cell_index(
   136→    const std::string& cells_path,
   137→    const std::string& entries_path,
   138→    const std::unordered_map<uint64_t, std::vector<uint32_t>>& cell_map
   139→) {
   140→    std::vector<std::pair<uint64_t, std::vector<uint32_t>>> sorted(cell_map.begin(), cell_map.end());
   141→    std::sort(sorted.begin(), sorted.end());
   142→
   143→    { std::ofstream f(cells_path, std::ios::binary);
   144→      uint32_t current_offset = 0;
   145→      for (const auto& [cell_id, ids] : sorted) {
   146→          f.write(reinterpret_cast<const char*>(&cell_id), sizeof(cell_id));
   147→          f.write(reinterpret_cast<const char*>(&current_offset), sizeof(current_offset));
   148→          current_offset += sizeof(uint16_t) + ids.size() * sizeof(uint32_t);
   149→      } }
   150→
   151→    { std::ofstream f(entries_path, std::ios::binary);
   152→      for (const auto& [cell_id, ids] : sorted) {
   153→          uint16_t count = static_cast<uint16_t>(std::min(ids.size(), size_t(MAX_VERTEX_COUNT)));
   154→          f.write(reinterpret_cast<const char*>(&count), sizeof(count));
   155→          f.write(reinterpret_cast<const char*>(ids.data()), ids.size() * sizeof(uint32_t));
   156→      } }
   157→}
   158→
   159→void write_index(const ParsedData& data, const std::string& output_dir, IndexMode mode) {
   160→    ensure_dir(output_dir);
   161→    auto _wt = std::chrono::steady_clock::now();
   162→
   163→    bool write_streets = (mode != IndexMode::AdminOnly);
   164→    bool write_addresses = (mode == IndexMode::Full);
   165→
   166→    if (write_streets) {
   167→        std::vector<uint64_t> sorted_geo_cells;
   168→        {
   169→            auto extract_unique_cells = [](const std::vector<CellItemPair>& pairs) {
   170→                std::vector<uint64_t> cells;
   171→                cells.reserve(pairs.size() / 2);
   172→                for (size_t i = 0; i < pairs.size(); ) {
   173→                    cells.push_back(pairs[i].cell_id);
   174→                    uint64_t cur = pairs[i].cell_id;
   175→                    while (i < pairs.size() && pairs[i].cell_id == cur) i++;
   176→                }
   177→                return cells;
   178→            };
   179→            auto extract_from_map = [](const std::unordered_map<uint64_t, std::vector<uint32_t>>& m) {
   180→                std::vector<uint64_t> cells;
   181→                cells.reserve(m.size());
   182→                for (auto& [id, _] : m) cells.push_back(id);
   183→                std::sort(cells.begin(), cells.end());
   184→                return cells;
   185→            };
   186→
   187→            std::vector<uint64_t> way_cells, addr_cells, interp_cells;
   188→            {
   189→                auto f1 = std::async(std::launch::async, [&] {
   190→                    return !data.sorted_way_cells.empty()
   191→                        ? extract_unique_cells(data.sorted_way_cells)
   192→                        : extract_from_map(data.cell_to_ways);
   193→                });
   194→                if (write_addresses) {
   195→                    auto f2 = std::async(std::launch::async, [&] {
   196→                        return !data.sorted_addr_cells.empty()
   197→                            ? extract_unique_cells(data.sorted_addr_cells)
   198→                            : extract_from_map(data.cell_to_addrs);
   199→                    });
   200→                    auto f3 = std::async(std::launch::async, [&] {
   201→                        return !data.sorted_interp_cells.empty()
   202→                            ? extract_unique_cells(data.sorted_interp_cells)
   203→                            : extract_from_map(data.cell_to_interps);
   204→                    });
   205→                    addr_cells = f2.get();
   206→                    interp_cells = f3.get();
   207→                }
   208→                way_cells = f1.get();
   209→            }
   210→
   211→            sorted_geo_cells.reserve(way_cells.size() + addr_cells.size() + interp_cells.size());
   212→            std::merge(way_cells.begin(), way_cells.end(), addr_cells.begin(), addr_cells.end(),
   213→                       std::back_inserter(sorted_geo_cells));
   214→            if (!interp_cells.empty()) {
   215→                std::vector<uint64_t> tmp;
   216→                tmp.reserve(sorted_geo_cells.size() + interp_cells.size());
   217→                std::merge(sorted_geo_cells.begin(), sorted_geo_cells.end(),
   218→                           interp_cells.begin(), interp_cells.end(), std::back_inserter(tmp));
   219→                sorted_geo_cells = std::move(tmp);
   220→            }
   221→            sorted_geo_cells.erase(std::unique(sorted_geo_cells.begin(), sorted_geo_cells.end()),
   222→                                    sorted_geo_cells.end());
   223→        }
   224→
   225→        std::vector<uint32_t> street_offsets, addr_offsets, interp_offsets;
   226→        {
   227→            auto f1 = std::async(std::launch::async, [&]() {
   228→                if (!data.sorted_way_cells.empty())
   229→                    return write_entries_from_sorted(output_dir + "/street_entries.bin", sorted_geo_cells, data.sorted_way_cells);
   230→                return write_entries(output_dir + "/street_entries.bin", sorted_geo_cells, data.cell_to_ways);
   231→            });
   232→            if (write_addresses) {
   233→                auto f2 = std::async(std::launch::async, [&]() {
   234→                    if (!data.sorted_addr_cells.empty())
   235→                        return write_entries_from_sorted(output_dir + "/addr_entries.bin", sorted_geo_cells, data.sorted_addr_cells);
   236→                    return write_entries(output_dir + "/addr_entries.bin", sorted_geo_cells, data.cell_to_addrs);
   237→                });
   238→                auto f3 = std::async(std::launch::async, [&]() {
   239→                    if (!data.sorted_interp_cells.empty())
   240→                        return write_entries_from_sorted(output_dir + "/interp_entries.bin", sorted_geo_cells, data.sorted_interp_cells);
   241→                    return write_entries(output_dir + "/interp_entries.bin", sorted_geo_cells, data.cell_to_interps);
   242→                });
   243→                addr_offsets = f2.get();
   244→                interp_offsets = f3.get();
   245→            }
   246→            street_offsets = f1.get();
   247→        }
   248→
   249→        log_phase("  Write: entry files", _wt);
   250→        {
   251→            size_t n = sorted_geo_cells.size();
   252→            size_t row_size = sizeof(uint64_t) + 3 * sizeof(uint32_t);
   253→            std::vector<char> buf(n * row_size);
   254→            if (addr_offsets.empty()) addr_offsets.resize(n, NO_DATA);
   255→            if (interp_offsets.empty()) interp_offsets.resize(n, NO_DATA);
   256→
   257→            unsigned int nthreads = std::thread::hardware_concurrency();
   258→            if (nthreads == 0) nthreads = 4;
   259→            size_t chunk = (n + nthreads - 1) / nthreads;
   260→            std::vector<std::thread> fill_threads;
   261→            for (unsigned int t = 0; t < nthreads; t++) {
   262→                size_t start = t * chunk;
   263→                size_t end = std::min(start + chunk, n);
   264→                if (start >= n) break;
   265→                fill_threads.emplace_back([&, start, end]() {
   266→                    char* ptr = buf.data() + start * row_size;
   267→                    for (size_t i = start; i < end; i++) {
   268→                        memcpy(ptr, &sorted_geo_cells[i], sizeof(uint64_t)); ptr += sizeof(uint64_t);
   269→                        memcpy(ptr, &street_offsets[i], sizeof(uint32_t)); ptr += sizeof(uint32_t);
   270→                        memcpy(ptr, &addr_offsets[i], sizeof(uint32_t)); ptr += sizeof(uint32_t);
   271→                        memcpy(ptr, &interp_offsets[i], sizeof(uint32_t)); ptr += sizeof(uint32_t);
   272→                    }
   273→                });
   274→            }
   275→            for (auto& t : fill_threads) t.join();
   276→
   277→            std::ofstream f(output_dir + "/geo_cells.bin", std::ios::binary);
   278→            f.write(buf.data(), buf.size());
   279→        }
   280→
   281→        std::cerr << "geo index: " << sorted_geo_cells.size() << " cells ("
   282→                  << data.ways.size() << " ways, " << data.addr_points.size() << " addrs, "
   283→                  << data.interp_ways.size() << " interps)" << std::endl;
   284→    }
   285→
   286→    log_phase("  Write: geo_cells.bin", _wt);
   287→    auto admin_future = std::async(std::launch::async, [&] {
   288→        write_cell_index(output_dir + "/admin_cells.bin", output_dir + "/admin_entries.bin", data.cell_to_admin);
   289→        std::cerr << "admin index: " << data.cell_to_admin.size() << " cells, " << data.admin_polygons.size() << " polygons" << std::endl;
   290→    });
   291→
   292→    std::vector<std::future<void>> write_futures;
   293→    if (write_streets) {
   294→        write_futures.push_back(std::async(std::launch::async, [&] {
   295→            std::ofstream f(output_dir + "/street_ways.bin", std::ios::binary);
   296→            f.write(reinterpret_cast<const char*>(data.ways.data()), data.ways.size() * sizeof(WayHeader));
   297→        }));
   298→        write_futures.push_back(std::async(std::launch::async, [&] {
   299→            std::ofstream f(output_dir + "/street_nodes.bin", std::ios::binary);
   300→            f.write(reinterpret_cast<const char*>(data.street_nodes.data()), data.street_nodes.size() * sizeof(NodeCoord));
   301→        }));
   302→        if (write_addresses) {
   303→            write_futures.push_back(std::async(std::launch::async, [&] {
   304→                std::ofstream f(output_dir + "/addr_points.bin", std::ios::binary);
   305→                f.write(reinterpret_cast<const char*>(data.addr_points.data()), data.addr_points.size() * sizeof(AddrPoint));
   306→            }));
   307→            write_futures.push_back(std::async(std::launch::async, [&] {
   308→                std::ofstream f(output_dir + "/interp_ways.bin", std::ios::binary);
   309→                f.write(reinterpret_cast<const char*>(data.interp_ways.data()), data.interp_ways.size() * sizeof(InterpWay));
   310→            }));
   311→            write_futures.push_back(std::async(std::launch::async, [&] {
   312→                std::ofstream f(output_dir + "/interp_nodes.bin", std::ios::binary);
   313→                f.write(reinterpret_cast<const char*>(data.interp_nodes.data()), data.interp_nodes.size() * sizeof(NodeCoord));
   314→            }));
   315→        }
   316→    }
   317→    // If not writing all data types, compact string pool and remap references
   318→    // before writing admin_polygons and strings.bin.
   319→    std::vector<AdminPolygon> compact_admin;
   320→    std::vector<char> compact_strings;
   321→    const bool need_compact = (!write_streets || !write_addresses);
   322→
   323→    if (need_compact) {
   324→        // Collect referenced string offsets
   325→        std::unordered_set<uint32_t> used_offsets;
   326→        if (write_streets) {
   327→            for (const auto& w : data.ways) used_offsets.insert(w.name_id);
   328→            if (write_addresses) {
   329→                for (const auto& a : data.addr_points) {
   330→                    used_offsets.insert(a.housenumber_id);
   331→                    used_offsets.insert(a.street_id);
   332→                }
   333→                for (const auto& iw : data.interp_ways) used_offsets.insert(iw.street_id);
   334→            }
   335→        }
   336→        for (const auto& ap : data.admin_polygons) used_offsets.insert(ap.name_id);
   337→
   338→        // Build compact pool and remap
   339→        const auto& old_sp = data.string_pool.data();
   340→        std::vector<uint32_t> sorted_offs(used_offsets.begin(), used_offsets.end());
   341→        std::sort(sorted_offs.begin(), sorted_offs.end());
   342→        std::unordered_map<uint32_t, uint32_t> remap;
   343→        compact_strings.reserve(used_offsets.size() * 16);
   344→        for (uint32_t off : sorted_offs) {
   345→            remap[off] = static_cast<uint32_t>(compact_strings.size());
   346→            size_t len = std::strlen(old_sp.data() + off);
   347→            compact_strings.insert(compact_strings.end(), old_sp.data() + off,
   348→                old_sp.data() + off + len + 1);
   349→        }
   350→
   351→        // Remap admin polygon name_ids
   352→        compact_admin = data.admin_polygons;
   353→        for (auto& ap : compact_admin) ap.name_id = remap[ap.name_id];
   354→
   355→        // Remap street/addr/interp if applicable (for no-addresses mode)
   356→        // These are written from data directly, so we'd need copies too.
   357→        // For simplicity, only compact when we have fewer types.
   358→    }
   359→
   360→    const auto& admin_to_write = need_compact ? compact_admin : data.admin_polygons;
   361→    write_futures.push_back(std::async(std::launch::async, [&] {
   362→        std::ofstream f(output_dir + "/admin_polygons.bin", std::ios::binary);
   363→        f.write(reinterpret_cast<const char*>(admin_to_write.data()), admin_to_write.size() * sizeof(AdminPolygon));
   364→    }));
   365→    write_futures.push_back(std::async(std::launch::async, [&] {
   366→        std::ofstream f(output_dir + "/admin_vertices.bin", std::ios::binary);
   367→        f.write(reinterpret_cast<const char*>(data.admin_vertices.data()), data.admin_vertices.size() * sizeof(NodeCoord));
   368→    }));
   369→    write_futures.push_back(std::async(std::launch::async, [&] {
   370→        if (need_compact) {
   371→            std::ofstream f(output_dir + "/strings.bin", std::ios::binary);
   372→            f.write(compact_strings.data(), compact_strings.size());
   373→            std::cerr << "strings.bin: " << compact_strings.size() << " bytes (compacted from "
   374→                      << data.string_pool.data().size() << ")" << std::endl;
   375→        } else {
   376→            std::ofstream f(output_dir + "/strings.bin", std::ios::binary);
   377→            f.write(data.string_pool.data().data(), data.string_pool.data().size());
   378→            std::cerr << "strings.bin: " << data.string_pool.data().size() << " bytes" << std::endl;
   379→        }
   380→    }));
   381→
   382→    log_phase("  Write: parallel data files launched", _wt);
   383→    admin_future.get();
   384→    for (auto& f : write_futures) f.get();
   385→}
   386→
   387→void write_quality_variant(const ParsedData& data, const std::string& source_dir,
   388→                           const std::string& output_dir, double epsilon_scale) {
   389→    ensure_dir(output_dir);
   390→
   391→    // Re-simplify admin polygons at the given epsilon scale
   392→    std::vector<AdminPolygon> new_polys;
   393→    std::vector<NodeCoord> new_verts;
   394→    new_polys.reserve(data.admin_polygons.size());
   395→
   396→    // Parallel simplification
   397→    struct SimplifiedPoly {
   398→        std::vector<std::pair<double,double>> verts;
   399→    };
   400→    std::vector<SimplifiedPoly> simplified(data.admin_polygons.size());
   401→
   402→    {
   403→        std::atomic<size_t> idx{0};
   404→        unsigned nthreads = std::thread::hardware_concurrency();
   405→        if (nthreads == 0) nthreads = 4;
   406→        std::vector<std::thread> workers;
   407→        for (unsigned t = 0; t < nthreads; t++) {
   408→            workers.emplace_back([&]() {
   409→                while (true) {
   410→                    size_t i = idx.fetch_add(1);
   411→                    if (i >= data.admin_polygons.size()) break;
   412→                    const auto& ap = data.admin_polygons[i];
   413→
   414→                    // Extract original vertices
   415→                    std::vector<std::pair<double,double>> pts;
   416→                    pts.reserve(ap.vertex_count);
   417→                    for (uint32_t j = 0; j < ap.vertex_count; j++) {
   418→                        const auto& v = data.admin_vertices[ap.vertex_offset + j];
   419→                        pts.emplace_back(v.lat, v.lng);
   420→                    }
   421→
   422→                    if (epsilon_scale > 0) {
   423→                        double eps_m = admin_epsilon_meters(ap.admin_level) * epsilon_scale;
   424→                        double lat = pts.empty() ? 0.0 : pts[0].first;
   425→                        double eps_deg = meters_to_degrees(eps_m, lat);
   426→                        simplified[i].verts = simplify_polygon_epsilon(pts, eps_deg);
   427→                    } else {
   428→                        simplified[i].verts = std::move(pts);
   429→                    }
   430→                }
   431→            });
   432→        }
   433→        for (auto& w : workers) w.join();
   434→    }
   435→
   436→    // Sequential: build new polygon and vertex arrays
   437→    for (size_t i = 0; i < data.admin_polygons.size(); i++) {
   438→        auto& sv = simplified[i].verts;
   439→        if (sv.size() < 3) continue;
   440→
   441→        AdminPolygon np = data.admin_polygons[i];
   442→        np.vertex_offset = static_cast<uint32_t>(new_verts.size());
   443→        np.vertex_count = static_cast<uint32_t>(sv.size());
   444→        np.area = polygon_area(sv);
   445→
   446→        for (const auto& [lat, lng] : sv) {
   447→            new_verts.push_back({static_cast<float>(lat), static_cast<float>(lng)});
   448→        }
   449→        new_polys.push_back(np);
   450→    }
   451→
   452→    std::cerr << "Quality " << epsilon_scale << "x: " << new_polys.size()
   453→              << " polygons, " << new_verts.size() << " vertices ("
   454→              << new_verts.size() * 8 / 1024 / 1024 << " MiB)" << std::endl;
   455→
   456→    // Write admin files
   457→    {
   458→        std::ofstream f(output_dir + "/admin_polygons.bin", std::ios::binary);
   459→        f.write(reinterpret_cast<const char*>(new_polys.data()), new_polys.size() * sizeof(AdminPolygon));
   460→    }
   461→    {
   462→        std::ofstream f(output_dir + "/admin_vertices.bin", std::ios::binary);
   463→        f.write(reinterpret_cast<const char*>(new_verts.data()), new_verts.size() * sizeof(NodeCoord));
   464→    }
   465→
   466→    // Quality directories only contain the two files that change.
   467→    // Shared files (admin_cells.bin, admin_entries.bin, strings.bin)
   468→    // stay in the parent directory.
   469→}
   470→


Now let me examine the types and structures:


     1→#pragma once
     2→
     3→#include <cstdint>
     4→#include <vector>
     5→
     6→// --- Binary format structs (must match server's index reader) ---
     7→
     8→struct WayHeader {
     9→    uint32_t node_offset;
    10→    uint8_t node_count;
    11→    uint32_t name_id;
    12→};
    13→
    14→struct AddrPoint {
    15→    float lat;
    16→    float lng;
    17→    uint32_t housenumber_id;
    18→    uint32_t street_id;
    19→};
    20→
    21→struct InterpWay {
    22→    uint32_t node_offset;
    23→    uint8_t node_count;
    24→    uint32_t street_id;
    25→    uint32_t start_number;
    26→    uint32_t end_number;
    27→    uint8_t interpolation;
    28→};
    29→
    30→struct AdminPolygon {
    31→    uint32_t vertex_offset;
    32→    uint32_t vertex_count;
    33→    uint32_t name_id;
    34→    uint8_t admin_level;
    35→    float area;
    36→    uint16_t country_code;
    37→};
    38→
    39→struct NodeCoord {
    40→    float lat;
    41→    float lng;
    42→};
    43→
    44→// Sorted cell-item pair for direct entry writing (avoids hash map)
    45→struct CellItemPair {
    46→    uint64_t cell_id;
    47→    uint32_t item_id;
    48→};
    49→
    50→// --- Constants ---
    51→
    52→static const uint32_t INTERIOR_FLAG = 0x80000000u;
    53→static const uint32_t ID_MASK       = 0x7FFFFFFFu;
    54→static const uint32_t NO_DATA       = 0xFFFFFFFFu;
    55→
    56→// Simplification mode
    57→enum class SimplifyMode { MaxVertices, ErrorBounded };
    58→
    59→// Processing limits
    60→static constexpr size_t MAX_BLOCK_QUEUE         = 64;    // producer-consumer queue depth
    61→static constexpr int    MAX_NODE_COUNT          = 255;   // uint8_t node_count
    62→static constexpr int    MAX_VERTEX_COUNT        = 0x7FFFFFFF; // uint32_t vertex_count
    63→static constexpr int    MAX_S2_CELLS_PER_POLY   = 200;   // S2RegionCoverer max_cells
    64→static constexpr int    BACKTRACK_CALL_BUDGET   = 100000; // per-ring backtracking limit
    65→static constexpr int    BACKTRACK_TOTAL_BUDGET  = 500000; // per-relation backtracking limit
    66→static constexpr int    BACKTRACK_MAX_DEPTH     = 200;    // recursion depth limit
    67→static constexpr size_t MAX_SUBWAYS_FOR_RETRY   = 30;     // only retry small relations
    68→static constexpr size_t MAX_NODE_ID_DEFAULT     = 15000000000ULL; // dense index capacity
    69→
    70→// --- Index mode ---
    71→
    72→enum class IndexMode { Full, NoAddresses, AdminOnly };
    73→
    74→// --- Deferred S2 work items ---
    75→
    76→struct DeferredWay {
    77→    uint32_t way_id;
    78→    uint32_t node_offset;
    79→    uint8_t node_count;
    80→};
    81→
    82→struct DeferredInterp {
    83→    uint32_t interp_id;
    84→    uint32_t node_offset;
    85→    uint8_t node_count;
    86→};
    87→
    88→// Collected relation data for parallel admin assembly
    89→struct CollectedRelation {
    90→    int64_t id;
    91→    uint8_t admin_level;
    92→    std::string name;
    93→    std::string country_code;
    94→    bool is_postal;
    95→    std::vector<std::pair<int64_t, std::string>> members; // (way_id, role)
    96→};
    97→


Now let me look at the server-side code that reads these files:


find /root/traccar-geocoder/src -type f \( -name "*.cpp" -o -name "*.h" \) | head -20


find: '/root/traccar-geocoder/src': No such file or directory


find /root/traccar-geocoder -type d -name "server" -o -name "runtime" -o -name "geocoder"


total 35
drwxr-xr-x 3 root root     5 Mar 21 11:08 .
drwxr-xr-x 7 root root    14 Mar 21 11:08 ..
-rw-r--r-- 1 root root 48488 Mar 21 11:08 Cargo.lock
-rw-r--r-- 1 root root   414 Mar 21 11:08 Cargo.toml
drwxr-xr-x 3 root root     5 Mar 23 23:04 src


find /root/traccar-geocoder/server/src -type f -name "*.rs" | head -20


/root/traccar-geocoder/server/src/auth.rs
/root/traccar-geocoder/server/src/main.rs


Error: EISDIR: illegal operation on a directory, read '/root/traccar-geocoder/server/src'


Error: File content (10030 tokens) exceeds maximum allowed tokens (10000). Use offset and limit parameters to read specific portions of the file, or search for specific content instead of reading the whole file.


     1→mod auth;
     2→
     3→use axum::extract::Query;
     4→use axum::http::StatusCode;
     5→use axum::response::{IntoResponse, Response};
     6→use axum::routing::get;
     7→use axum::Router;
     8→use memmap2::Mmap;
     9→use s2::cellid::CellID;
    10→use s2::latlng::LatLng;
    11→use serde::{Deserialize, Serialize};
    12→use std::borrow::Cow;
    13→use std::fs::File;
    14→use std::sync::{Arc, RwLock};
    15→
    16→// --- S2 helpers ---
    17→
    18→const DEFAULT_STREET_CELL_LEVEL: u64 = 17;
    19→const DEFAULT_ADMIN_CELL_LEVEL: u64 = 10;
    20→const DEFAULT_SEARCH_DISTANCE: f64 = 75.0;
    21→
    22→fn cell_id_at_level(lat: f64, lng: f64, level: u64) -> u64 {
    23→    let ll = LatLng::from_degrees(lat, lng);
    24→    CellID::from(ll).parent(level).0
    25→}
    26→
    27→fn cell_neighbors_at_level(cell_id: u64, level: u64) -> Vec<u64> {
    28→    let cell = CellID(cell_id);
    29→    cell.all_neighbors(level).into_iter().map(|c| c.0).collect()
    30→}
    31→
    32→// --- Binary format structs (must match C++ build pipeline) ---
    33→
    34→#[repr(C)]
    35→#[derive(Clone, Copy)]
    36→struct WayHeader {
    37→    node_offset: u32,
    38→    node_count: u8,
    39→    name_id: u32,
    40→}
    41→
    42→#[repr(C)]
    43→#[derive(Clone, Copy)]
    44→struct AddrPoint {
    45→    lat: f32,
    46→    lng: f32,
    47→    housenumber_id: u32,
    48→    street_id: u32,
    49→}
    50→
    51→#[repr(C)]
    52→#[derive(Clone, Copy)]
    53→struct InterpWay {
    54→    node_offset: u32,
    55→    node_count: u8,
    56→    street_id: u32,
    57→    start_number: u32,
    58→    end_number: u32,
    59→    interpolation: u8,
    60→}
    61→
    62→#[repr(C)]
    63→#[derive(Clone, Copy)]
    64→struct AdminPolygon {
    65→    vertex_offset: u32,
    66→    vertex_count: u32,
    67→    name_id: u32,
    68→    admin_level: u8,
    69→    area: f32,
    70→    country_code: u16,
    71→}
    72→
    73→#[repr(C)]
    74→#[derive(Clone, Copy)]
    75→struct NodeCoord {
    76→    lat: f32,
    77→    lng: f32,
    78→}
    79→
    80→// --- Index data ---
    81→
    82→struct Index {
    83→    geo_cells: Option<Mmap>,
    84→    street_entries: Option<Mmap>,
    85→    street_ways: Option<Mmap>,
    86→    street_nodes: Option<Mmap>,
    87→    addr_entries: Option<Mmap>,
    88→    addr_points: Option<Mmap>,
    89→    interp_entries: Option<Mmap>,
    90→    interp_ways: Option<Mmap>,
    91→    interp_nodes: Option<Mmap>,
    92→    admin_cells: Mmap,
    93→    admin_entries: Mmap,
    94→    admin_polygons: Mmap,
    95→    admin_vertices: Mmap,
    96→    strings: Mmap,
    97→    street_cell_level: u64,
    98→    admin_cell_level: u64,
    99→    max_distance_sq: f64,
   100→}


   100→}
   101→
   102→const NO_DATA: u32 = 0xFFFFFFFF;
   103→
   104→struct GeoCellOffsets {
   105→    street: u32,
   106→    addr: u32,
   107→    interp: u32,
   108→}
   109→
   110→fn mmap_file(path: &str) -> Result<Mmap, String> {
   111→    let file = File::open(path).map_err(|e| format!("Failed to open {}: {}", path, e))?;
   112→    unsafe { Mmap::map(&file).map_err(|e| format!("Failed to mmap {}: {}", path, e)) }
   113→}
   114→
   115→fn mmap_file_optional(path: &str) -> Option<Mmap> {
   116→    let file = File::open(path).ok()?;
   117→    unsafe { Mmap::map(&file).ok() }
   118→}
   119→
   120→impl Index {
   121→    fn load(dir: &str, street_cell_level: u64, admin_cell_level: u64, search_distance: f64) -> Result<Self, String> {
   122→        let meters_to_rad = search_distance / 111_320.0;
   123→        let max_distance_sq = meters_to_rad * meters_to_rad;
   124→
   125→        let geo_cells = mmap_file_optional(&format!("{}/geo_cells.bin", dir));
   126→        let has_geo = geo_cells.is_some();
   127→
   128→        let required_geo = |name: &str| -> Result<Option<Mmap>, String> {
   129→            let path = format!("{}/{}", dir, name);
   130→            let mmap = mmap_file_optional(&path);
   131→            if has_geo && mmap.is_none() {
   132→                return Err(format!("Partial geo index: {} is missing", name));
   133→            }
   134→            Ok(mmap)
   135→        };
   136→
   137→        Ok(Index {
   138→            geo_cells,
   139→            street_entries: required_geo("street_entries.bin")?,
   140→            street_ways: required_geo("street_ways.bin")?,
   141→            street_nodes: required_geo("street_nodes.bin")?,
   142→            addr_entries: mmap_file_optional(&format!("{}/addr_entries.bin", dir)),
   143→            addr_points: mmap_file_optional(&format!("{}/addr_points.bin", dir)),
   144→            interp_entries: mmap_file_optional(&format!("{}/interp_entries.bin", dir)),
   145→            interp_ways: mmap_file_optional(&format!("{}/interp_ways.bin", dir)),
   146→            interp_nodes: mmap_file_optional(&format!("{}/interp_nodes.bin", dir)),
   147→            admin_cells: mmap_file(&format!("{}/admin_cells.bin", dir))?,
   148→            admin_entries: mmap_file(&format!("{}/admin_entries.bin", dir))?,
   149→            admin_polygons: mmap_file(&format!("{}/admin_polygons.bin", dir))?,
   150→            admin_vertices: mmap_file(&format!("{}/admin_vertices.bin", dir))?,
   151→            strings: mmap_file(&format!("{}/strings.bin", dir))?,
   152→            street_cell_level,
   153→            admin_cell_level,
   154→            max_distance_sq,
   155→        })
   156→    }
   157→
   158→    fn get_string(&self, offset: u32) -> &str {
   159→        let bytes = &self.strings[offset as usize..];
   160→        let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
   161→        std::str::from_utf8(&bytes[..end]).unwrap_or("")
   162→    }
   163→
   164→    fn read_u16(data: &[u8], offset: usize) -> u16 {
   165→        u16::from_le_bytes([data[offset], data[offset + 1]])
   166→    }
   167→
   168→    fn read_u32(data: &[u8], offset: usize) -> u32 {
   169→        u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap())
   170→    }
   171→
   172→    fn read_u64(data: &[u8], offset: usize) -> u64 {
   173→        u64::from_le_bytes(data[offset..offset + 8].try_into().unwrap())
   174→    }
   175→
   176→    // Iterate entry IDs inline from entries file at given offset
   177→    fn for_each_entry(entries: &[u8], offset: u32, mut f: impl FnMut(u32)) {
   178→        if offset == NO_DATA { return; }
   179→        let offset = offset as usize;
   180→        if offset + 2 > entries.len() { return; }
   181→
   182→        let id_count = Self::read_u16(entries, offset) as usize;
   183→        let data_start = offset + 2;
   184→        if data_start + id_count * 4 > entries.len() { return; }
   185→
   186→        for i in 0..id_count {
   187→            f(Self::read_u32(entries, data_start + i * 4));
   188→        }
   189→    }
   190→
   191→    // Binary search geo cell index: 20 bytes per entry (u64 cell_id + u32 street + u32 addr + u32 interp)
   192→    fn lookup_geo_cell(cells: &[u8], cell_id: u64) -> GeoCellOffsets {
   193→        let entry_size: usize = 20;
   194→        let count = cells.len() / entry_size;
   195→        let empty = GeoCellOffsets { street: NO_DATA, addr: NO_DATA, interp: NO_DATA };
   196→        if count == 0 { return empty; }
   197→
   198→        let mut lo = 0usize;
   199→        let mut hi = count;
   200→        while lo < hi {
   201→            let mid = lo + (hi - lo) / 2;
   202→            let mid_id = Self::read_u64(cells, mid * entry_size);
   203→            if mid_id == cell_id {
   204→                return GeoCellOffsets {
   205→                    street: Self::read_u32(cells, mid * entry_size + 8),
   206→                    addr: Self::read_u32(cells, mid * entry_size + 12),
   207→                    interp: Self::read_u32(cells, mid * entry_size + 16),
   208→                };
   209→            } else if mid_id < cell_id {
   210→                lo = mid + 1;
   211→            } else {
   212→                hi = mid;
   213→            }
   214→        }
   215→        empty
   216→    }
   217→
   218→    // Binary search admin cell index: 12 bytes per entry (u64 cell_id + u32 offset)
   219→    fn lookup_admin_cell(cells: &[u8], cell_id: u64) -> u32 {
   220→        let entry_size: usize = 12;
   221→        let count = cells.len() / entry_size;
   222→        if count == 0 { return NO_DATA; }
   223→
   224→        let mut lo = 0usize;
   225→        let mut hi = count;
   226→        while lo < hi {
   227→            let mid = lo + (hi - lo) / 2;
   228→            let mid_id = Self::read_u64(cells, mid * entry_size);
   229→            if mid_id == cell_id {
   230→                return Self::read_u32(cells, mid * entry_size + 8);
   231→            } else if mid_id < cell_id {
   232→                lo = mid + 1;
   233→            } else {
   234→                hi = mid;
   235→            }
   236→        }
   237→        NO_DATA
   238→    }
   239→
   240→    // --- Geo lookup (streets, addresses, interpolation from merged index) ---
   241→
   242→    fn query_geo(&self, lat: f64, lng: f64) -> (Option<(f64, &AddrPoint)>, Option<(f64, &str, u32)>, Option<(f64, &WayHeader)>) {
   243→        let geo_cells = match &self.geo_cells {
   244→            Some(gc) => gc,
   245→            None => return (None, None, None),
   246→        };
   247→        let street_entries = self.street_entries.as_ref().unwrap();
   248→        let street_ways_mmap = self.street_ways.as_ref().unwrap();
   249→        let street_nodes_mmap = self.street_nodes.as_ref().unwrap();
   250→        let cell = cell_id_at_level(lat, lng, self.street_cell_level);
   251→        let neighbors = cell_neighbors_at_level(cell, self.street_cell_level);
   252→
   253→        let empty_addr: &[AddrPoint] = &[];
   254→        let all_points: &[AddrPoint] = if let Some(ref m) = self.addr_points {
   255→            unsafe {
   256→                std::slice::from_raw_parts(
   257→                    m.as_ptr() as *const AddrPoint,
   258→                    m.len() / std::mem::size_of::<AddrPoint>(),
   259→                )
   260→            }
   261→        } else {
   262→            empty_addr
   263→        };
   264→        let all_ways: &[WayHeader] = unsafe {
   265→            std::slice::from_raw_parts(
   266→                street_ways_mmap.as_ptr() as *const WayHeader,
   267→                street_ways_mmap.len() / std::mem::size_of::<WayHeader>(),
   268→            )
   269→        };
   270→        let all_street_nodes: &[NodeCoord] = unsafe {
   271→            std::slice::from_raw_parts(
   272→                street_nodes_mmap.as_ptr() as *const NodeCoord,
   273→                street_nodes_mmap.len() / std::mem::size_of::<NodeCoord>(),
   274→            )
   275→        };
   276→        let empty_interp: &[InterpWay] = &[];
   277→        let all_interps: &[InterpWay] = if let Some(ref m) = self.interp_ways {
   278→            unsafe {
   279→                std::slice::from_raw_parts(
   280→                    m.as_ptr() as *const InterpWay,
   281→                    m.len() / std::mem::size_of::<InterpWay>(),
   282→                )
   283→            }
   284→        } else {
   285→            empty_interp
   286→        };
   287→        let empty_interp_nodes: &[NodeCoord] = &[];
   288→        let all_interp_nodes: &[NodeCoord] = if let Some(ref m) = self.interp_nodes {
   289→            unsafe {
   290→                std::slice::from_raw_parts(
   291→                    m.as_ptr() as *const NodeCoord,
   292→                    m.len() / std::mem::size_of::<NodeCoord>(),
   293→                )
   294→            }
   295→        } else {
   296→            empty_interp_nodes
   297→        };
   298→
   299→        let cos_lat = lat.to_radians().cos();
   300→
   301→        let mut best_addr_dist = f64::MAX;
   302→        let mut best_addr: Option<&AddrPoint> = None;
   303→        let mut best_street_dist = f64::MAX;
   304→        let mut best_street: Option<&WayHeader> = None;
   305→        let mut best_interp_dist = f64::MAX;
   306→        let mut best_interp: Option<&InterpWay> = None;
   307→        let mut best_interp_t: f64 = 0.0;
   308→
   309→        // Fixed-size hash set on stack to skip duplicate street IDs across cells
   310→        let mut seen_streets: [u32; 64] = [u32::MAX; 64];
   311→
   312→        for c in std::iter::once(cell).chain(neighbors.into_iter()) {
   313→            let offsets = Self::lookup_geo_cell(geo_cells, c);
   314→
   315→            // Addresses
   316→            if let Some(ref addr_entries) = self.addr_entries {
   317→                Self::for_each_entry(addr_entries, offsets.addr, |id| {
   318→                    let point = &all_points[id as usize];
   319→                    let dlat = (point.lat as f64 - lat).to_radians();
   320→                    let dlng = (point.lng as f64 - lng).to_radians();
   321→                    let dist = dist_sq(dlat, dlng, cos_lat);
   322→                    if dist < best_addr_dist {
   323→                        best_addr_dist = dist;
   324→                        best_addr = Some(point);
   325→                    }
   326→                });
   327→            }
   328→
   329→            // Streets
   330→            Self::for_each_entry(street_entries, offsets.street, |id| {
   331→                let slot = (id as usize) & 0x3F;
   332→                if seen_streets[slot] == id { return; }
   333→                seen_streets[slot] = id;
   334→
   335→                let way = &all_ways[id as usize];
   336→                let offset = way.node_offset as usize;
   337→                let count = way.node_count as usize;
   338→                let nodes = &all_street_nodes[offset..offset + count];
   339→
   340→                for i in 0..nodes.len() - 1 {
   341→                    let dist = point_to_segment_distance(
   342→                        lat, lng,
   343→                        nodes[i].lat as f64, nodes[i].lng as f64,
   344→                        nodes[i + 1].lat as f64, nodes[i + 1].lng as f64,
   345→                        cos_lat,
   346→                    );
   347→                    if dist < best_street_dist {
   348→                        best_street_dist = dist;
   349→                        best_street = Some(way);
   350→                    }
   351→                }
   352→            });
   353→
   354→            // Interpolation
   355→            if let Some(ref interp_entries) = self.interp_entries {
   356→                Self::for_each_entry(interp_entries, offsets.interp, |id| {
   357→                    let iw = &all_interps[id as usize];
   358→                    if iw.start_number == 0 || iw.end_number == 0 { return; }
   359→
   360→                    let offset = iw.node_offset as usize;
   361→                    let count = iw.node_count as usize;
   362→                    let nodes = &all_interp_nodes[offset..offset + count];
   363→
   364→                    let mut total_len: f64 = 0.0;
   365→                    for i in 0..nodes.len() - 1 {
   366→                        let dlat = (nodes[i + 1].lat as f64 - nodes[i].lat as f64).to_radians();
   367→                        let dlng = (nodes[i + 1].lng as f64 - nodes[i].lng as f64).to_radians();
   368→                        total_len += dist_sq(dlat, dlng, cos_lat);
   369→                    }
   370→                    if total_len == 0.0 { return; }
   371→
   372→                    let mut best_seg_dist = f64::MAX;
   373→                    let mut best_seg_t: f64 = 0.0;
   374→                    let mut prev_accumulated: f64 = 0.0;
   375→
   376→                    for i in 0..nodes.len() - 1 {
   377→                        let dlat = (nodes[i + 1].lat as f64 - nodes[i].lat as f64).to_radians();
   378→                        let dlng = (nodes[i + 1].lng as f64 - nodes[i].lng as f64).to_radians();
   379→                        let seg_len = dist_sq(dlat, dlng, cos_lat);
   380→                        let (dist, seg_t) = point_to_segment_with_t(
   381→                            lat, lng,
   382→                            nodes[i].lat as f64, nodes[i].lng as f64,
   383→                            nodes[i + 1].lat as f64, nodes[i + 1].lng as f64,
   384→                            cos_lat,
   385→                        );
   386→                        if dist < best_seg_dist {
   387→                            best_seg_dist = dist;
   388→                            best_seg_t = (prev_accumulated + seg_t * seg_len) / total_len;
   389→                        }
   390→                        prev_accumulated += seg_len;
   391→                    }
   392→
   393→                    if best_seg_dist < best_interp_dist {
   394→                        best_interp_dist = best_seg_dist;
   395→                        best_interp = Some(iw);
   396→                        best_interp_t = best_seg_t;
   397→                    }
   398→                });
   399→            }


   400→        }
   401→
   402→        let addr_result = best_addr.map(|p| (best_addr_dist, p));
   403→        let street_result = best_street.map(|w| (best_street_dist, w));
   404→        let interp_result = best_interp.map(|iw| {
   405→            let start = iw.start_number as f64;
   406→            let end = iw.end_number as f64;
   407→            let raw = start + best_interp_t * (end - start);
   408→
   409→            let step: u32 = match iw.interpolation {
   410→                1 | 2 => 2,
   411→                _ => 1,
   412→            };
   413→
   414→            let number = if step == 2 {
   415→                let base = iw.start_number;
   416→                let offset = ((raw - base as f64) / step as f64).round() as u32 * step;
   417→                base + offset
   418→            } else {
   419→                raw.round() as u32
   420→            };
   421→
   422→            (best_interp_dist, self.get_string(iw.street_id), number)
   423→        });
   424→
   425→        (addr_result, interp_result, street_result)
   426→    }
   427→
   428→    // --- Admin boundary lookup (point-in-polygon) ---
   429→
   430→    fn find_admin(&self, lat: f64, lng: f64) -> AdminResult<'_> {
   431→        let cell = cell_id_at_level(lat, lng, self.admin_cell_level);
   432→        let neighbors = cell_neighbors_at_level(cell, self.admin_cell_level);
   433→
   434→        let all_polygons: &[AdminPolygon] = unsafe {
   435→            std::slice::from_raw_parts(
   436→                self.admin_polygons.as_ptr() as *const AdminPolygon,
   437→                self.admin_polygons.len() / std::mem::size_of::<AdminPolygon>(),
   438→            )
   439→        };
   440→        let all_vertices: &[NodeCoord] = unsafe {
   441→            std::slice::from_raw_parts(
   442→                self.admin_vertices.as_ptr() as *const NodeCoord,
   443→                self.admin_vertices.len() / std::mem::size_of::<NodeCoord>(),
   444→            )
   445→        };
   446→
   447→        // For each admin level, find the smallest-area polygon containing the point
   448→        let mut best_by_level: [Option<(f32, &AdminPolygon)>; 12] = [None; 12];
   449→
   450→        const INTERIOR_FLAG: u32 = 0x80000000;
   451→        const ID_MASK: u32 = 0x7FFFFFFF;
   452→
   453→        for c in std::iter::once(cell).chain(neighbors.into_iter()) {
   454→            Self::for_each_entry(&self.admin_entries, Self::lookup_admin_cell(&self.admin_cells, c), |id| {
   455→                let is_interior = (id & INTERIOR_FLAG) != 0;
   456→                let poly_id = (id & ID_MASK) as usize;
   457→                let poly = &all_polygons[poly_id];
   458→                let level = poly.admin_level as usize;
   459→                if level >= 12 { return; }
   460→
   461→                // Skip if we already have a smaller polygon at this level
   462→                if let Some((best_area, _)) = best_by_level[level] {
   463→                    if poly.area >= best_area { return; }
   464→                }
   465→
   466→                // Interior cells skip point-in-polygon test
   467→                if is_interior || point_in_polygon(lat as f32, lng as f32, {
   468→                    let offset = poly.vertex_offset as usize;
   469→                    let count = poly.vertex_count as usize;
   470→                    &all_vertices[offset..offset + count]
   471→                }) {
   472→                    best_by_level[level] = Some((poly.area, poly));
   473→                }
   474→            });
   475→        }
   476→
   477→        let mut result = AdminResult::default();
   478→
   479→        for level in 0..12 {
   480→            if let Some((_, poly)) = best_by_level[level] {
   481→                let name = self.get_string(poly.name_id);
   482→                match poly.admin_level {
   483→                    2 => {
   484→                        result.country = Some(name);
   485→                        if poly.country_code != 0 {
   486→                            result.country_code = Some([
   487→                                (poly.country_code >> 8) as u8,
   488→                                (poly.country_code & 0xFF) as u8,
   489→                            ]);
   490→                        }
   491→                    }
   492→                    4 => result.state = Some(name),
   493→                    6 => result.county = Some(name),
   494→                    8 => result.city = Some(name),
   495→                    11 => result.postcode = Some(name),
   496→                    _ => {}
   497→                }
   498→            }
   499→        }
   500→
   501→        result
   502→    }
   503→
   504→    // --- Combined query ---
   505→
   506→    fn query(&self, lat: f64, lng: f64) -> Address<'_> {
   507→        let max_dist = self.max_distance_sq;
   508→
   509→        let admin = self.find_admin(lat, lng);
   510→        let (addr, interp, street) = self.query_geo(lat, lng);
   511→
   512→        // Determine house_number and road from best geo match (priority: address > interpolation > street)
   513→        let mut house_number: Option<Cow<'_, str>> = None;
   514→        let mut road: Option<&str> = None;
   515→
   516→        if let Some((dist, point)) = addr {
   517→            if dist < max_dist {
   518→                house_number = Some(Cow::Borrowed(self.get_string(point.housenumber_id)));
   519→                road = Some(self.get_string(point.street_id));
   520→            }
   521→        }
   522→        if road.is_none() {
   523→            if let Some((dist, street_name, number)) = interp {
   524→                if dist < max_dist {
   525→                    house_number = Some(Cow::Owned(number.to_string()));
   526→                    road = Some(street_name);
   527→                }
   528→            }
   529→        }
   530→        if road.is_none() {
   531→            if let Some((dist, way)) = street {
   532→                if dist < max_dist {
   533→                    road = Some(self.get_string(way.name_id));
   534→                }
   535→            }
   536→        }
   537→
   538→        if road.is_none() && admin.country.is_none() && admin.city.is_none() && admin.state.is_none() {
   539→            return Address::default();
   540→        }
   541→
   542→        let address = AddressDetails {
   543→            house_number,
   544→            road,
   545→            city: admin.city,
   546→            state: admin.state,
   547→            county: admin.county,
   548→            postcode: admin.postcode,
   549→            country: admin.country,
   550→            country_code: admin.country_code.map(|c| String::from_utf8_lossy(&c).into_owned()),
   551→        };
   552→        let display_name = format_address(&address);
   553→        Address { display_name, address }
   554→    }
   555→}
   556→
   557→// --- Geometry helpers ---
   558→
   559→fn dist_sq(dlat: f64, dlng: f64, cos_lat: f64) -> f64 {
   560→    dlat * dlat + dlng * dlng * cos_lat * cos_lat
   561→}
   562→
   563→fn point_to_segment_with_t(
   564→    px: f64, py: f64,
   565→    ax: f64, ay: f64,
   566→    bx: f64, by: f64,
   567→    cos_lat: f64,
   568→) -> (f64, f64) {
   569→    let dx = bx - ax;
   570→    let dy = by - ay;
   571→    let len_sq = dx * dx + dy * dy;
   572→
   573→    let t = if len_sq == 0.0 {
   574→        0.0
   575→    } else {
   576→        (((px - ax) * dx + (py - ay) * dy) / len_sq).clamp(0.0, 1.0)
   577→    };
   578→
   579→    let proj_x = ax + t * dx;
   580→    let proj_y = ay + t * dy;
   581→    let dlat = (px - proj_x).to_radians();
   582→    let dlng = (py - proj_y).to_radians();
   583→    (dist_sq(dlat, dlng, cos_lat), t)
   584→}
   585→
   586→fn point_to_segment_distance(
   587→    px: f64, py: f64,
   588→    ax: f64, ay: f64,
   589→    bx: f64, by: f64,
   590→    cos_lat: f64,
   591→) -> f64 {
   592→    point_to_segment_with_t(px, py, ax, ay, bx, by, cos_lat).0
   593→}
   594→
   595→// Ray casting point-in-polygon test
   596→fn point_in_polygon(lat: f32, lng: f32, vertices: &[NodeCoord]) -> bool {
   597→    let mut inside = false;
   598→    let n = vertices.len();
   599→    let mut j = n - 1;
   600→
   601→    for i in 0..n {
   602→        let vi = &vertices[i];
   603→        let vj = &vertices[j];
   604→
   605→        if ((vi.lng > lng) != (vj.lng > lng))
   606→            && (lat < (vj.lat - vi.lat) * (lng - vi.lng) / (vj.lng - vi.lng) + vi.lat)
   607→        {
   608→            inside = !inside;
   609→        }
   610→        j = i;
   611→    }
   612→
   613→    inside
   614→}
   615→
   616→// --- API types ---
   617→
   618→#[derive(Default)]
   619→struct AdminResult<'a> {
   620→    country: Option<&'a str>,
   621→    country_code: Option<[u8; 2]>,
   622→    state: Option<&'a str>,
   623→    county: Option<&'a str>,
   624→    city: Option<&'a str>,
   625→    postcode: Option<&'a str>,
   626→}
   627→
   628→#[derive(Serialize, Default)]
   629→struct AddressDetails<'a> {
   630→    #[serde(skip_serializing_if = "Option::is_none")]
   631→    house_number: Option<Cow<'a, str>>,
   632→    #[serde(skip_serializing_if = "Option::is_none")]
   633→    road: Option<&'a str>,
   634→    #[serde(skip_serializing_if = "Option::is_none")]
   635→    city: Option<&'a str>,
   636→    #[serde(skip_serializing_if = "Option::is_none")]
   637→    state: Option<&'a str>,
   638→    #[serde(skip_serializing_if = "Option::is_none")]
   639→    county: Option<&'a str>,
   640→    #[serde(skip_serializing_if = "Option::is_none")]
   641→    postcode: Option<&'a str>,
   642→    #[serde(skip_serializing_if = "Option::is_none")]
   643→    country: Option<&'a str>,
   644→    #[serde(skip_serializing_if = "Option::is_none")]
   645→    country_code: Option<String>,
   646→}
   647→
   648→#[derive(Serialize, Default)]
   649→struct Address<'a> {
   650→    #[serde(skip_serializing_if = "Option::is_none")]
   651→    display_name: Option<String>,
   652→    address: AddressDetails<'a>,
   653→}
   654→
   655→// Address formatting patterns by country code
   656→// Returns (number_after_street, postcode_before_city, include_state)
   657→fn format_rules(country_code: Option<&str>) -> (bool, bool, bool) {
   658→    match country_code {
   659→        // Number before street, postcode after city, include state
   660→        Some("US") | Some("CA") | Some("AU") | Some("NZ")
   661→        | Some("GB") | Some("IE") | Some("ZA") | Some("IN")
   662→        | Some("NG") | Some("KE") | Some("GH") | Some("PK")
   663→        | Some("PH") | Some("TH") | Some("MY") => (false, false, true),
   664→
   665→        // Number before street, postcode before city, include state
   666→        Some("JP") | Some("KR") | Some("CN") | Some("TW") => (false, true, true),
   667→
   668→        // Number after street, postcode before city, no state (most of Europe, etc.)
   669→        _ => (true, true, false),
   670→    }
   671→}
   672→
   673→fn format_address(addr: &AddressDetails<'_>) -> Option<String> {
   674→    if addr.road.is_none() && addr.city.is_none() && addr.country.is_none() {
   675→        return None;
   676→    }
   677→
   678→    let (number_after, postcode_before_city, include_state) = format_rules(addr.country_code.as_deref());
   679→    let mut parts: Vec<String> = Vec::new();
   680→
   681→    // Street + house number
   682→    if let Some(road) = addr.road {
   683→        if let Some(ref hn) = addr.house_number {
   684→            if number_after {
   685→                parts.push(format!("{} {}", road, hn));
   686→            } else {
   687→                parts.push(format!("{} {}", hn, road));
   688→            }
   689→        } else {
   690→            parts.push(road.to_string());
   691→        }
   692→    }
   693→
   694→    // City + postcode + state
   695→    if postcode_before_city {
   696→        let mut city_part = String::new();
   697→        if let Some(pc) = addr.postcode {
   698→            city_part.push_str(pc);
   699→            city_part.push(' ');


Now let me look at the ParsedData structures and understand how the data is built:


     1→#pragma once
     2→
     3→#include <algorithm>
     4→#include <chrono>
     5→#include <iostream>
     6→#include <string>
     7→#include <unordered_map>
     8→#include <vector>
     9→
    10→#include <cstdio>
    11→#include <cstring>
    12→#include <dirent.h>
    13→#include <iomanip>
    14→#include <thread>
    15→#include <unistd.h>
    16→#include <sys/stat.h>
    17→
    18→#include "types.h"
    19→#include "string_pool.h"
    20→
    21→// --- Directory creation ---
    22→
    23→inline void ensure_dir(const std::string& path) {
    24→    // Recursive mkdir — create all parent directories
    25→    for (size_t i = 1; i < path.size(); i++) {
    26→        if (path[i] == '/') {
    27→            mkdir(path.substr(0, i).c_str(), 0755);
    28→        }
    29→    }
    30→    mkdir(path.c_str(), 0755);
    31→}
    32→
    33→// --- CPU tick reading (all threads via getrusage) ---
    34→
    35→#include <sys/resource.h>
    36→
    37→struct CpuTicks {
    38→    long long process_us = 0;  // user+system microseconds (all threads)
    39→
    40→    static CpuTicks now() {
    41→        CpuTicks ct;
    42→        struct rusage ru;
    43→        if (getrusage(RUSAGE_SELF, &ru) == 0) {
    44→            ct.process_us = (long long)ru.ru_utime.tv_sec * 1000000 + ru.ru_utime.tv_usec
    45→                          + (long long)ru.ru_stime.tv_sec * 1000000 + ru.ru_stime.tv_usec;
    46→        }
    47→        return ct;
    48→    }
    49→};
    50→
    51→// --- Phase timer with CPU utilization ---
    52→
    53→inline long get_rss_mb() {
    54→    long rss_pages = 0;
    55→    FILE* f = fopen("/proc/self/statm", "r");
    56→    if (f) {
    57→        long size;
    58→        if (fscanf(f, "%ld %ld", &size, &rss_pages) != 2) rss_pages = 0;
    59→        fclose(f);
    60→    }
    61→    return rss_pages * sysconf(_SC_PAGESIZE) / (1024 * 1024);
    62→}
    63→
    64→inline void log_phase(const char* name, std::chrono::steady_clock::time_point& t,
    65→                       CpuTicks& prev_cpu) {
    66→    auto now = std::chrono::steady_clock::now();
    67→    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now - t).count();
    68→    auto cpu_now = CpuTicks::now();
    69→    double cpu_sec = (cpu_now.process_us - prev_cpu.process_us) / 1e6;
    70→    double wall_sec = ms / 1000.0;
    71→    double cores_used = (wall_sec > 0.1) ? cpu_sec / wall_sec : 0;
    72→    unsigned ncpu = std::thread::hardware_concurrency();
    73→    int pct = (wall_sec > 0.1 && ncpu > 0) ? (int)(cores_used * 100.0 / ncpu) : 0;
    74→    long rss = get_rss_mb();
    75→    std::cerr << "  [" << ms/1000 << "." << (ms%1000)/100 << "s"
    76→              << " " << std::fixed << std::setprecision(1) << cores_used << "/" << ncpu << "cores"
    77→              << " " << pct << "%"
    78→              << " " << rss << "MiB] " << name << std::endl;
    79→    t = now;
    80→    prev_cpu = cpu_now;
    81→}
    82→
    83→// Backward compat overload (no CPU tracking)
    84→inline void log_phase(const char* name, std::chrono::steady_clock::time_point& t) {
    85→    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
    86→        std::chrono::steady_clock::now() - t).count();
    87→    std::cerr << "  [" << ms/1000 << "." << (ms%1000)/100 << "s] " << name << std::endl;
    88→    t = std::chrono::steady_clock::now();
    89→}
    90→
    91→// --- Parsed data container ---
    92→
    93→struct ParsedData {
    94→    StringPool string_pool;
    95→    std::vector<WayHeader> ways;
    96→    std::vector<NodeCoord> street_nodes;
    97→    std::unordered_map<uint64_t, std::vector<uint32_t>> cell_to_ways;
    98→    std::vector<AddrPoint> addr_points;
    99→    std::unordered_map<uint64_t, std::vector<uint32_t>> cell_to_addrs;
   100→    std::vector<InterpWay> interp_ways;
   101→    std::vector<NodeCoord> interp_nodes;
   102→    std::unordered_map<uint64_t, std::vector<uint32_t>> cell_to_interps;
   103→    std::vector<AdminPolygon> admin_polygons;
   104→    std::vector<NodeCoord> admin_vertices;
   105→    std::unordered_map<uint64_t, std::vector<uint32_t>> cell_to_admin;
   106→
   107→    // Deferred work for parallel S2 computation (ways + interps)
   108→    std::vector<DeferredWay> deferred_ways;
   109→    std::vector<DeferredInterp> deferred_interps;
   110→
   111→    // Sorted (cell_id, item_id) pairs — kept for direct entry writing
   112→    std::vector<CellItemPair> sorted_way_cells;
   113→    std::vector<CellItemPair> sorted_addr_cells;
   114→    std::vector<CellItemPair> sorted_interp_cells;
   115→
   116→    // Collected data for parallel admin assembly
   117→    std::vector<CollectedRelation> collected_relations;
   118→    struct WayGeometry {
   119→        std::vector<std::pair<double,double>> coords;
   120→        int64_t first_node_id;
   121→        int64_t last_node_id;
   122→    };
   123→    std::unordered_map<int64_t, WayGeometry> way_geometries;
   124→};
   125→
   126→// --- Deduplicate IDs per cell ---
   127→
   128→template<typename Map>
   129→inline void deduplicate(Map& cell_map) {
   130→    for (auto& [cell_id, ids] : cell_map) {
   131→        std::sort(ids.begin(), ids.end());
   132→        ids.erase(std::unique(ids.begin(), ids.end()), ids.end());
   133→    }
   134→}
   135→


Now let me search for more details on string pool and how strings are referenced:


     1→#pragma once
     2→
     3→#include <cstdint>
     4→#include <string>
     5→#include <unordered_map>
     6→#include <vector>
     7→
     8→class StringPool {
     9→public:
    10→    uint32_t intern(const std::string& s) {
    11→        auto it = index_.find(s);
    12→        if (it != index_.end()) {
    13→            return it->second;
    14→        }
    15→        uint32_t offset = static_cast<uint32_t>(data_.size());
    16→        index_[s] = offset;
    17→        data_.insert(data_.end(), s.begin(), s.end());
    18→        data_.push_back('\0');
    19→        return offset;
    20→    }
    21→
    22→    const std::vector<char>& data() const { return data_; }
    23→    std::vector<char>& mutable_data() { return data_; }
    24→
    25→private:
    26→    std::unordered_map<std::string, uint32_t> index_;
    27→    std::vector<char> data_;
    28→};
    29→


Now let me check the build process to understand how geo_cells are created:


grep -r "geo_cells\|street_entries\|admin_cells" /root/traccar-geocoder/builder/src/ --include="*.cpp" --include="*.h" | head -40


/root/traccar-geocoder/builder/src/cell_index.cpp:        std::vector<uint64_t> sorted_geo_cells;
/root/traccar-geocoder/builder/src/cell_index.cpp:            sorted_geo_cells.reserve(way_cells.size() + addr_cells.size() + interp_cells.size());
/root/traccar-geocoder/builder/src/cell_index.cpp:                       std::back_inserter(sorted_geo_cells));
/root/traccar-geocoder/builder/src/cell_index.cpp:                tmp.reserve(sorted_geo_cells.size() + interp_cells.size());
/root/traccar-geocoder/builder/src/cell_index.cpp:                std::merge(sorted_geo_cells.begin(), sorted_geo_cells.end(),
/root/traccar-geocoder/builder/src/cell_index.cpp:                sorted_geo_cells = std::move(tmp);
/root/traccar-geocoder/builder/src/cell_index.cpp:            sorted_geo_cells.erase(std::unique(sorted_geo_cells.begin(), sorted_geo_cells.end()),
/root/traccar-geocoder/builder/src/cell_index.cpp:                                    sorted_geo_cells.end());
/root/traccar-geocoder/builder/src/cell_index.cpp:                    return write_entries_from_sorted(output_dir + "/street_entries.bin", sorted_geo_cells, data.sorted_way_cells);
/root/traccar-geocoder/builder/src/cell_index.cpp:                return write_entries(output_dir + "/street_entries.bin", sorted_geo_cells, data.cell_to_ways);
/root/traccar-geocoder/builder/src/cell_index.cpp:                        return write_entries_from_sorted(output_dir + "/addr_entries.bin", sorted_geo_cells, data.sorted_addr_cells);
/root/traccar-geocoder/builder/src/cell_index.cpp:                    return write_entries(output_dir + "/addr_entries.bin", sorted_geo_cells, data.cell_to_addrs);
/root/traccar-geocoder/builder/src/cell_index.cpp:                        return write_entries_from_sorted(output_dir + "/interp_entries.bin", sorted_geo_cells, data.sorted_interp_cells);
/root/traccar-geocoder/builder/src/cell_index.cpp:                    return write_entries(output_dir + "/interp_entries.bin", sorted_geo_cells, data.cell_to_interps);
/root/traccar-geocoder/builder/src/cell_index.cpp:            size_t n = sorted_geo_cells.size();
/root/traccar-geocoder/builder/src/cell_index.cpp:                        memcpy(ptr, &sorted_geo_cells[i], sizeof(uint64_t)); ptr += sizeof(uint64_t);
/root/traccar-geocoder/builder/src/cell_index.cpp:            std::ofstream f(output_dir + "/geo_cells.bin", std::ios::binary);
/root/traccar-geocoder/builder/src/cell_index.cpp:        std::cerr << "geo index: " << sorted_geo_cells.size() << " cells ("
/root/traccar-geocoder/builder/src/cell_index.cpp:    log_phase("  Write: geo_cells.bin", _wt);
/root/traccar-geocoder/builder/src/cell_index.cpp:        write_cell_index(output_dir + "/admin_cells.bin", output_dir + "/admin_entries.bin", data.cell_to_admin);
/root/traccar-geocoder/builder/src/cell_index.cpp:    // Shared files (admin_cells.bin, admin_entries.bin, strings.bin)
/root/traccar-geocoder/builder/src/build_index.cpp:            "admin_cells.bin", "admin_entries.bin", "strings.bin"
/root/traccar-geocoder/builder/src/build_index.cpp:            "street_ways.bin", "street_nodes.bin", "street_entries.bin"
/root/traccar-geocoder/builder/src/build_index.cpp:        std::vector<std::string> geo_files = {"geo_cells.bin"};


Let me look at build_index.cpp to understand the complete picture:


     1→#include <algorithm>
     2→#include <atomic>
     3→#include <chrono>
     4→#include <condition_variable>
     5→#include <cstring>
     6→#include <deque>
     7→#include <fstream>
     8→#include <future>
     9→#include <iostream>
    10→#include <mutex>
    11→#include <numeric>
    12→#include <string>
    13→#include <thread>
    14→#include <unordered_set>
    15→#include <vector>
    16→
    17→#include <fcntl.h>
    18→#include <sys/mman.h>
    19→
    20→#include "pbf_reader.h"
    21→
    22→#include <s2/s2cell_id.h>
    23→#include <s2/s2latlng.h>
    24→
    25→#include "types.h"
    26→#include "string_pool.h"
    27→#include "geometry.h"
    28→#include "parsed_data.h"
    29→#include "s2_helpers.h"
    30→#include "ring_assembly.h"
    31→#include "continent_boundaries.h"
    32→#include "interpolation.h"
    33→#include "cache.h"
    34→#include "continent_filter.h"
    35→#include "cell_index.h"
    36→
    37→
    38→// --- Main ---
    39→
    40→int main(int argc, char* argv[]) {
    41→    if (argc < 2) {
    42→        std::cerr << "Usage: build-index <output-dir> <input.osm.pbf> [options]" << std::endl;
    43→        std::cerr << "       build-index <output-dir> --load-cache <path> [options]" << std::endl;
    44→        std::cerr << std::endl;
    45→        std::cerr << "Options:" << std::endl;
    46→        std::cerr << "  --street-level N       S2 cell level for streets (default: 17)" << std::endl;
    47→        std::cerr << "  --admin-level N        S2 cell level for admin (default: 10)" << std::endl;
    48→        std::cerr << "  --max-admin-level N    Only include admin levels <= N" << std::endl;
    49→        std::cerr << "  --save-cache <path>    Save parsed data to cache file" << std::endl;
    50→        std::cerr << "  --load-cache <path>    Load from cache instead of PBF" << std::endl;
    51→        std::cerr << "  --multi-output         Write full, no-addresses, admin-only indexes" << std::endl;
    52→        std::cerr << "  --continents           Also generate per-continent indexes" << std::endl;
    53→        std::cerr << "  --mode <mode>          Index mode: full, no-addresses, admin-only (default: full)" << std::endl;
    54→        std::cerr << "  --admin-only           Shorthand for --mode admin-only" << std::endl;
    55→        std::cerr << "  --no-addresses         Shorthand for --mode no-addresses" << std::endl;
    56→        std::cerr << "  --simplify-epsilon     Use error-bounded simplification (per-level defaults)" << std::endl;
    57→        std::cerr << "  --simplify-epsilon N   Use fixed epsilon of N meters for all levels" << std::endl;
    58→        std::cerr << "  --epsilon-scale F      Multiply all per-level epsilons by F (e.g. 2.0 = 2x coarser)" << std::endl;
    59→        std::cerr << "  --epsilon-levels L2,L3,...,L8  Set epsilon in meters per level (7 values)" << std::endl;
    60→        std::cerr << "  --multi-quality [S,S,...]  Write quality variants at given scales (default: 0,0.2,0.5,1,1.5,2,2.5)" << std::endl;
    61→        return 1;
    62→    }
    63→
    64→    std::string output_dir = argv[1];
    65→    std::vector<std::string> input_files;
    66→    std::string save_cache_path;
    67→    std::string load_cache_path;
    68→    bool multi_output = false;
    69→    bool generate_continents = false;
    70→    IndexMode mode = IndexMode::Full;
    71→    SimplifyMode simplify_mode = SimplifyMode::MaxVertices;
    72→    double simplify_epsilon_override = 0; // 0 = use per-level defaults
    73→    bool multi_quality = false;
    74→    std::vector<double> quality_scales;
    75→
    76→    for (int i = 2; i < argc; i++) {
    77→        std::string arg = argv[i];
    78→        if (arg == "--street-level" && i + 1 < argc) {
    79→            kStreetCellLevel = std::atoi(argv[++i]);
    80→        } else if (arg == "--admin-level" && i + 1 < argc) {
    81→            kAdminCellLevel = std::atoi(argv[++i]);
    82→        } else if (arg == "--admin-only") {
    83→            mode = IndexMode::AdminOnly;
    84→        } else if (arg == "--no-addresses") {
    85→            mode = IndexMode::NoAddresses;
    86→        } else if (arg == "--max-admin-level" && i + 1 < argc) {
    87→            kMaxAdminLevel = std::atoi(argv[++i]);
    88→        } else if (arg == "--save-cache" && i + 1 < argc) {
    89→            save_cache_path = argv[++i];
    90→        } else if (arg == "--load-cache" && i + 1 < argc) {
    91→            load_cache_path = argv[++i];
    92→        } else if (arg == "--multi-output") {
    93→            multi_output = true;
    94→        } else if (arg == "--continents") {
    95→            generate_continents = true;
    96→        } else if (arg == "--simplify-epsilon") {
    97→            simplify_mode = SimplifyMode::ErrorBounded;
    98→            // Optional: next arg might be a number (fixed epsilon in meters)
    99→            if (i + 1 < argc) {
   100→                char* end;
   101→                double val = std::strtod(argv[i+1], &end);
   102→                if (*end == '\0' && val > 0) {
   103→                    simplify_epsilon_override = val;
   104→                    i++;
   105→                }
   106→            }
   107→        } else if (arg == "--epsilon-scale" && i + 1 < argc) {
   108→            kEpsilonScale = std::strtod(argv[++i], nullptr);
   109→        } else if (arg == "--multi-quality") {
   110→            multi_quality = true;
   111→            // Optional: next arg might be comma-separated scales
   112→            if (i + 1 < argc && argv[i+1][0] != '-') {
   113→                std::string vals = argv[++i];
   114→                size_t pos = 0;
   115→                while (pos < vals.size()) {
   116→                    size_t next = vals.find(',', pos);
   117→                    if (next == std::string::npos) next = vals.size();
   118→                    double v = std::strtod(vals.substr(pos, next - pos).c_str(), nullptr);
   119→                    quality_scales.push_back(v);
   120→                    pos = next + 1;
   121→                }
   122→            }
   123→            if (quality_scales.empty()) {
   124→                quality_scales = {0, 0.2, 0.5, 1.0, 1.5, 2.0, 2.5};
   125→            }
   126→            // Build with uncapped polygons — quality variants simplify at write time.
   127→            // We need full vertex detail to produce all quality levels accurately.
   128→            simplify_mode = SimplifyMode::ErrorBounded;
   129→            simplify_epsilon_override = 0.1; // effectively uncapped
   130→        } else if (arg == "--epsilon-levels" && i + 1 < argc) {
   131→            // Parse comma-separated values: L2,L3,L4,L5,L6,L7,L8
   132→            std::string vals = argv[++i];
   133→            int level = 2;
   134→            size_t pos = 0;
   135→            while (pos < vals.size() && level <= 8) {
   136→                size_t next = vals.find(',', pos);
   137→                if (next == std::string::npos) next = vals.size();
   138→                double v = std::strtod(vals.substr(pos, next - pos).c_str(), nullptr);
   139→                if (v > 0) kAdminEpsilonMeters[level] = v;
   140→                level++;
   141→                pos = next + 1;
   142→            }
   143→            // Also set levels 9-11 to same as level 8
   144→            for (int l = level; l <= 11; l++) kAdminEpsilonMeters[l] = kAdminEpsilonMeters[8];
   145→        } else if (arg == "--mode" && i + 1 < argc) {
   146→            std::string mode_str = argv[++i];
   147→            if (mode_str == "full") {
   148→                mode = IndexMode::Full;
   149→            } else if (mode_str == "no-addresses") {
   150→                mode = IndexMode::NoAddresses;


Now let me check the complete write_index function to understand the complete flow and any additional details:


grep -n "size_t\|sizeof\|record\|entry" /root/traccar-geocoder/builder/src/cell_index.cpp | head -50


31:    size_t total_size = 0;
32:    for (auto& r : sorted_refs) total_size += sizeof(uint16_t) + r.ids->size() * sizeof(uint32_t);
37:    size_t ri = 0;
43:        uint16_t count = static_cast<uint16_t>(std::min(ids.size(), size_t(MAX_VERTEX_COUNT)));
45:                   reinterpret_cast<const char*>(&count) + sizeof(count));
47:                   reinterpret_cast<const char*>(ids.data()) + ids.size() * sizeof(uint32_t));
48:        current += sizeof(uint16_t) + ids.size() * sizeof(uint32_t);
69:    size_t cells_per_chunk = (sorted_cells.size() + nthreads - 1) / nthreads;
73:        size_t cell_start, cell_end;
80:        size_t cs = t * cells_per_chunk;
81:        size_t ce = std::min(cs + cells_per_chunk, sorted_cells.size());
90:            size_t pi = std::lower_bound(sorted_pairs.begin(), sorted_pairs.end(),
95:            for (size_t si = cs; si < ce && pi < sorted_pairs.size(); si++) {
101:                size_t start = pi;
103:                uint16_t count = static_cast<uint16_t>(std::min(pi - start, size_t(MAX_VERTEX_COUNT)));
104:                size_t entry_size = sizeof(uint16_t) + (pi - start) * sizeof(uint32_t);
105:                size_t buf_pos = chunk.buf.size();
106:                chunk.buf.resize(buf_pos + entry_size);
107:                memcpy(chunk.buf.data() + buf_pos, &count, sizeof(count));
108:                for (size_t k = start; k < pi; k++) {
109:                    memcpy(chunk.buf.data() + buf_pos + sizeof(uint16_t) + (k - start) * sizeof(uint32_t),
110:                           &sorted_pairs[k].item_id, sizeof(uint32_t));
112:                chunk.local_size += entry_size;
120:        for (size_t si = chunk.cell_start; si < chunk.cell_end; si++) {
146:          f.write(reinterpret_cast<const char*>(&cell_id), sizeof(cell_id));
147:          f.write(reinterpret_cast<const char*>(&current_offset), sizeof(current_offset));
148:          current_offset += sizeof(uint16_t) + ids.size() * sizeof(uint32_t);
153:          uint16_t count = static_cast<uint16_t>(std::min(ids.size(), size_t(MAX_VERTEX_COUNT)));
154:          f.write(reinterpret_cast<const char*>(&count), sizeof(count));
155:          f.write(reinterpret_cast<const char*>(ids.data()), ids.size() * sizeof(uint32_t));
172:                for (size_t i = 0; i < pairs.size(); ) {
249:        log_phase("  Write: entry files", _wt);
251:            size_t n = sorted_geo_cells.size();
252:            size_t row_size = sizeof(uint64_t) + 3 * sizeof(uint32_t);
259:            size_t chunk = (n + nthreads - 1) / nthreads;
262:                size_t start = t * chunk;
263:                size_t end = std::min(start + chunk, n);
267:                    for (size_t i = start; i < end; i++) {
268:                        memcpy(ptr, &sorted_geo_cells[i], sizeof(uint64_t)); ptr += sizeof(uint64_t);
269:                        memcpy(ptr, &street_offsets[i], sizeof(uint32_t)); ptr += sizeof(uint32_t);
270:                        memcpy(ptr, &addr_offsets[i], sizeof(uint32_t)); ptr += sizeof(uint32_t);
271:                        memcpy(ptr, &interp_offsets[i], sizeof(uint32_t)); ptr += sizeof(uint32_t);
296:            f.write(reinterpret_cast<const char*>(data.ways.data()), data.ways.size() * sizeof(WayHeader));
300:            f.write(reinterpret_cast<const char*>(data.street_nodes.data()), data.street_nodes.size() * sizeof(NodeCoord));
305:                f.write(reinterpret_cast<const char*>(data.addr_points.data()), data.addr_points.size() * sizeof(AddrPoint));
309:                f.write(reinterpret_cast<const char*>(data.interp_ways.data()), data.interp_ways.size() * sizeof(InterpWay));
313:                f.write(reinterpret_cast<const char*>(data.interp_nodes.data()), data.interp_nodes.size() * sizeof(NodeCoord));
346:            size_t len = std::strlen(old_sp.data() + off);
363:        f.write(reinterpret_cast<const char*>(admin_to_write.data()), admin_to_write.size() * sizeof(AdminPolygon));
367:        f.write(reinterpret_cast<const char*>(data.admin_vertices.data()), data.admin_vertices.size() * sizeof(NodeCoord));


Perfect! Now let me get the exact sizes from the struct definitions again:


cat > /tmp/sizeof_test.cpp << 'EOF'
#include <iostream>
#include <cstdint>

struct WayHeader {
    uint32_t node_offset;
    uint8_t node_count;
    uint32_t name_id;
};

struct AddrPoint {
    float lat;
    float lng;
    uint32_t housenumber_id;
    uint32_t street_id;
};

struct InterpWay {
    uint32_t node_offset;
    uint8_t node_count;
    uint32_t street_id;
    uint32_t start_number;
    uint32_t end_number;
    uint8_t interpolation;
};

struct AdminPolygon {
    uint32_t vertex_offset;
    uint32_t vertex_count;
    uint32_t name_id;
    uint8_t admin_level;
    float area;
    uint16_t country_code;
};

struct NodeCoord {
    float lat;
    float lng;
};

int main() {
    std::cout << "WayHeader: " << sizeof(WayHeader) << std::endl;
    std::cout << "AddrPoint: " << sizeof(AddrPoint) << std::endl;
    std::cout << "InterpWay: " << sizeof(InterpWay) << std::endl;
    std::cout << "AdminPolygon: " << sizeof(AdminPolygon) << std::endl;
    std::cout << "NodeCoord: " << sizeof(NodeCoord) << std::endl;
    return 0;
}
EOF
g++ /tmp/sizeof_test.cpp -o /tmp/sizeof_test && /tmp/sizeof_test


WayHeader: 12
AddrPoint: 16
InterpWay: 24
AdminPolygon: 24
NodeCoord: 8


Excellent! Now let me look for any additional information on how data is organized and indexed:


grep -n "interior\|INTERIOR_FLAG\|ID_MASK" /root/traccar-geocoder/builder/src/*.cpp /root/traccar-geocoder/builder/src/*.h 2>/dev/null | head -20


/root/traccar-geocoder/builder/src/build_index.cpp:962:            // Remap poly_ids in cell_to_admin (preserving INTERIOR_FLAG)
/root/traccar-geocoder/builder/src/build_index.cpp:965:                    uint32_t flags = id & INTERIOR_FLAG;
/root/traccar-geocoder/builder/src/build_index.cpp:966:                    uint32_t old_id = id & ID_MASK;
/root/traccar-geocoder/builder/src/continent_filter.cpp:51:                                bool mask_interior = false) {
/root/traccar-geocoder/builder/src/continent_filter.cpp:57:                    ids.insert(mask_interior ? (id & ID_MASK) : id);
/root/traccar-geocoder/builder/src/continent_filter.cpp:67:                                   bool mask_interior = false) {
/root/traccar-geocoder/builder/src/continent_filter.cpp:97:                            local.insert(mask_interior ? (id & ID_MASK) : id);
/root/traccar-geocoder/builder/src/continent_filter.cpp:263:                uint32_t raw_id = handle_flags ? (id & ID_MASK) : id;
/root/traccar-geocoder/builder/src/continent_filter.cpp:264:                uint32_t flags = handle_flags ? (id & INTERIOR_FLAG) : 0;
/root/traccar-geocoder/builder/src/continent_filter.cpp:390:        // Admin still uses hash map (has interior flags)
/root/traccar-geocoder/builder/src/continent_filter.cpp:491:                                bool mask_interior = false) {
/root/traccar-geocoder/builder/src/continent_filter.cpp:496:                    ids.insert(mask_interior ? (id & ID_MASK) : id);
/root/traccar-geocoder/builder/src/continent_filter.cpp:636:                uint32_t raw_id = handle_flags ? (id & ID_MASK) : id;
/root/traccar-geocoder/builder/src/continent_filter.cpp:637:                uint32_t flags = handle_flags ? (id & INTERIOR_FLAG) : 0;
/root/traccar-geocoder/builder/src/s2_helpers.cpp:103:    S2CellUnion interior = coverer.GetInteriorCovering(polygon);
/root/traccar-geocoder/builder/src/s2_helpers.cpp:105:    std::unordered_set<uint64_t> interior_set;
/root/traccar-geocoder/builder/src/s2_helpers.cpp:106:    for (const auto& cell : interior.cell_ids()) {
/root/traccar-geocoder/builder/src/s2_helpers.cpp:111:                interior_set.insert(c.id());
/root/traccar-geocoder/builder/src/s2_helpers.cpp:113:            interior_set.insert(end.id());
/root/traccar-geocoder/builder/src/s2_helpers.cpp:115:            interior_set.insert(cell.parent(kAdminCellLevel).id());


Let me look at the s2_helpers to understand interior cells better:


     1→#include "s2_helpers.h"
     2→#include "geometry.h"
     3→
     4→#include <algorithm>
     5→#include <cmath>
     6→#include <iostream>
     7→#include <memory>
     8→
     9→#include <s2/s2latlng.h>
    10→#include <s2/s2region_coverer.h>
    11→#include <s2/s2polyline.h>
    12→#include <s2/s2polygon.h>
    13→#include <s2/s2loop.h>
    14→
    15→// --- S2 cell level globals ---
    16→
    17→int kStreetCellLevel = 17;
    18→int kAdminCellLevel = 10;
    19→int kMaxAdminLevel = 0;  // 0 means no filtering
    20→
    21→SimplifyMode kSimplifyMode = SimplifyMode::MaxVertices;
    22→double kSimplifyEpsilonOverride = 0;
    23→
    24→// --- cover_edge ---
    25→
    26→void cover_edge(double lat1, double lng1, double lat2, double lng2,
    27→                std::vector<S2CellId>& out) {
    28→    out.clear();
    29→
    30→    S2CellId c1 = S2CellId(S2LatLng::FromDegrees(lat1, lng1)).parent(kStreetCellLevel);
    31→    S2CellId c2 = S2CellId(S2LatLng::FromDegrees(lat2, lng2)).parent(kStreetCellLevel);
    32→
    33→    // Same cell — most common case for short edges
    34→    if (c1 == c2) {
    35→        out.push_back(c1);
    36→        return;
    37→    }
    38→
    39→    // Adjacent cells — check midpoint
    40→    S2CellId cm = S2CellId(S2LatLng::FromDegrees((lat1+lat2)/2, (lng1+lng2)/2)).parent(kStreetCellLevel);
    41→    if (cm == c1 || cm == c2) {
    42→        out.push_back(c1);
    43→        out.push_back(c2);
    44→        return;
    45→    }
    46→
    47→    // Longer edge — use S2RegionCoverer for correctness (handles geodesic curves)
    48→    thread_local S2RegionCoverer coverer = []() {
    49→        S2RegionCoverer::Options options;
    50→        options.set_fixed_level(kStreetCellLevel);
    51→        return S2RegionCoverer(options);
    52→    }();
    53→
    54→    S2Point p1 = S2LatLng::FromDegrees(lat1, lng1).ToPoint();
    55→    S2Point p2 = S2LatLng::FromDegrees(lat2, lng2).ToPoint();
    56→    if (p1 == p2) { out.push_back(c1); return; }
    57→
    58→    std::vector<S2Point> points = {p1, p2};
    59→    S2Polyline polyline(points);
    60→    S2CellUnion covering = coverer.GetCovering(polyline);
    61→    out = std::move(covering.cell_ids());
    62→}
    63→
    64→std::vector<S2CellId> cover_edge(double lat1, double lng1, double lat2, double lng2) {
    65→    std::vector<S2CellId> result;
    66→    cover_edge(lat1, lng1, lat2, lng2, result);
    67→    return result;
    68→}
    69→
    70→S2CellId point_to_cell(double lat, double lng) {
    71→    return S2CellId(S2LatLng::FromDegrees(lat, lng)).parent(kStreetCellLevel);
    72→}
    73→
    74→// --- cover_polygon ---
    75→
    76→std::vector<std::pair<S2CellId, bool>> cover_polygon(
    77→    const std::vector<std::pair<double,double>>& vertices) {
    78→    std::vector<S2Point> points;
    79→    points.reserve(vertices.size());
    80→    for (const auto& [lat, lng] : vertices) {
    81→        S2Point p = S2LatLng::FromDegrees(lat, lng).ToPoint();
    82→        if (!points.empty() && points.back() == p) continue;
    83→        points.push_back(p);
    84→    }
    85→    if (points.size() > 1 && points.front() == points.back()) {
    86→        points.pop_back();
    87→    }
    88→    if (points.size() < 3) return {};
    89→
    90→    S2Error error;
    91→    auto loop = std::make_unique<S2Loop>(points, S2Debug::DISABLE);
    92→    loop->Normalize();
    93→    if (loop->FindValidationError(&error)) return {};
    94→
    95→    S2Polygon polygon(std::move(loop));
    96→
    97→    S2RegionCoverer::Options options;
    98→    options.set_max_level(kAdminCellLevel);
    99→    options.set_max_cells(MAX_S2_CELLS_PER_POLY);
   100→
   101→    S2RegionCoverer coverer(options);
   102→    S2CellUnion covering = coverer.GetCovering(polygon);
   103→    S2CellUnion interior = coverer.GetInteriorCovering(polygon);
   104→
   105→    std::unordered_set<uint64_t> interior_set;
   106→    for (const auto& cell : interior.cell_ids()) {
   107→        if (cell.level() <= kAdminCellLevel) {
   108→            auto begin = cell.range_min().parent(kAdminCellLevel);
   109→            auto end = cell.range_max().parent(kAdminCellLevel);
   110→            for (auto c = begin; c != end; c = c.next()) {
   111→                interior_set.insert(c.id());
   112→            }
   113→            interior_set.insert(end.id());
   114→        } else {
   115→            interior_set.insert(cell.parent(kAdminCellLevel).id());
   116→        }
   117→    }
   118→
   119→    std::vector<std::pair<S2CellId, bool>> result;
   120→    for (const auto& cell : covering.cell_ids()) {
   121→        if (cell.level() <= kAdminCellLevel) {
   122→            auto begin = cell.range_min().parent(kAdminCellLevel);
   123→            auto end = cell.range_max().parent(kAdminCellLevel);
   124→            for (auto c = begin; c != end; c = c.next()) {
   125→                result.emplace_back(c, interior_set.count(c.id()) > 0);
   126→            }
   127→            result.emplace_back(end, interior_set.count(end.id()) > 0);
   128→        } else {
   129→            auto parent = cell.parent(kAdminCellLevel);
   130→            result.emplace_back(parent, interior_set.count(parent.id()) > 0);
   131→        }
   132→    }
   133→
   134→    std::sort(result.begin(), result.end(), [](const auto& a, const auto& b) {
   135→        return a.first < b.first;
   136→    });
   137→    auto it = result.begin();
   138→    for (auto curr = result.begin(); curr != result.end(); ) {
   139→        auto next = curr + 1;
   140→        bool is_interior = curr->second;
   141→        while (next != result.end() && next->first == curr->first) {
   142→            is_interior = is_interior || next->second;
   143→            ++next;
   144→        }
   145→        *it = {curr->first, is_interior};
   146→        ++it;
   147→        curr = next;
   148→    }
   149→    result.erase(it, result.end());
   150→    return result;


Perfect! Now let me get a comprehensive view by examining how the indexing works in the server:


   155→        })
   156→    }
   157→
   158→    fn get_string(&self, offset: u32) -> &str {
   159→        let bytes = &self.strings[offset as usize..];
   160→        let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
   161→        std::str::from_utf8(&bytes[..end]).unwrap_or("")
   162→    }
   163→
   164→    fn read_u16(data: &[u8], offset: usize) -> u16 {
   165→        u16::from_le_bytes([data[offset], data[offset + 1]])
   166→    }
   167→
   168→    fn read_u32(data: &[u8], offset: usize) -> u32 {
   169→        u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap())
   170→    }
   171→
   172→    fn read_u64(data: &[u8], offset: usize) -> u64 {
   173→        u64::from_le_bytes(data[offset..offset + 8].try_into().unwrap())
   174→    }
   175→
   176→    // Iterate entry IDs inline from entries file at given offset
   177→    fn for_each_entry(entries: &[u8], offset: u32, mut f: impl FnMut(u32)) {
   178→        if offset == NO_DATA { return; }
   179→        let offset = offset as usize;
   180→        if offset + 2 > entries.len() { return; }
   181→
   182→        let id_count = Self::read_u16(entries, offset) as usize;
   183→        let data_start = offset + 2;
   184→        if data_start + id_count * 4 > entries.len() { return; }
   185→
   186→        for i in 0..id_count {
   187→            f(Self::read_u32(entries, data_start + i * 4));
   188→        }
   189→    }
   190→
   191→    // Binary search geo cell index: 20 bytes per entry (u64 cell_id + u32 street + u32 addr + u32 interp)
   192→    fn lookup_geo_cell(cells: &[u8], cell_id: u64) -> GeoCellOffsets {
   193→        let entry_size: usize = 20;
   194→        let count = cells.len() / entry_size;
   195→        let empty = GeoCellOffsets { street: NO_DATA, addr: NO_DATA, interp: NO_DATA };
   196→        if count == 0 { return empty; }
   197→
   198→        let mut lo = 0usize;
   199→        let mut hi = count;
   200→        while lo < hi {
   201→            let mid = lo + (hi - lo) / 2;
   202→            let mid_id = Self::read_u64(cells, mid * entry_size);
   203→            if mid_id == cell_id {
   204→                return GeoCellOffsets {
   205→                    street: Self::read_u32(cells, mid * entry_size + 8),
   206→                    addr: Self::read_u32(cells, mid * entry_size + 12),
   207→                    interp: Self::read_u32(cells, mid * entry_size + 16),
   208→                };
   209→            } else if mid_id < cell_id {
   210→                lo = mid + 1;
   211→            } else {
   212→                hi = mid;
   213→            }
   214→        }
   215→        empty
   216→    }
   217→
   218→    // Binary search admin cell index: 12 bytes per entry (u64 cell_id + u32 offset)
   219→    fn lookup_admin_cell(cells: &[u8], cell_id: u64) -> u32 {
   220→        let entry_size: usize = 12;
   221→        let count = cells.len() / entry_size;
   222→        if count == 0 { return NO_DATA; }
   223→
   224→        let mut lo = 0usize;
   225→        let mut hi = count;
   226→        while lo < hi {
   227→            let mid = lo + (hi - lo) / 2;
   228→            let mid_id = Self::read_u64(cells, mid * entry_size);
   229→            if mid_id == cell_id {
   230→                return Self::read_u32(cells, mid * entry_size + 8);
   231→            } else if mid_id < cell_id {
   232→                lo = mid + 1;
   233→            } else {
   234→                hi = mid;
   235→            }
   236→        }
   237→        NO_DATA
   238→    }
   239→
   240→    // --- Geo lookup (streets, addresses, interpolation from merged index) ---
   241→
   242→    fn query_geo(&self, lat: f64, lng: f64) -> (Option<(f64, &AddrPoint)>, Option<(f64, &str, u32)>, Option<(f64, &WayHeader)>) {
   243→        let geo_cells = match &self.geo_cells {
   244→            Some(gc) => gc,
   245→            None => return (None, None, None),
   246→        };
   247→        let street_entries = self.street_entries.as_ref().unwrap();
   248→        let street_ways_mmap = self.street_ways.as_ref().unwrap();
   249→        let street_nodes_mmap = self.street_nodes.as_ref().unwrap();
   250→        let cell = cell_id_at_level(lat, lng, self.street_cell_level);
   251→        let neighbors = cell_neighbors_at_level(cell, self.street_cell_level);
   252→
   253→        let empty_addr: &[AddrPoint] = &[];
   254→        let all_points: &[AddrPoint] = if let Some(ref m) = self.addr_points {


Now let me create a comprehensive summary document. Let me verify the record packing in WayHeader: