Now update the local `coord_key` lambda in `assemble_outer_rings` to use the global function:


   345→static std::vector<std::vector<std::pair<double,double>>> assemble_outer_rings(
   346→    const std::vector<std::pair<int64_t, std::string>>& members,
   347→    const std::unordered_map<int64_t, ParsedData::WayGeometry>& way_geoms,
   348→    bool include_all_roles = false)
   349→{
   350→    // Coordinate-based ring assembly: split ways at shared internal nodes,
   351→    // then stitch sub-ways at endpoints with backtracking.
   352→    // This matches osmium's segment-level approach.
   353→
   354→    // Coordinate key matching osmium's Location (int32_t nanodegrees)
   355→    auto coord_key = [](double lat, double lng) -> int64_t {
   356→        int32_t ilat = static_cast<int32_t>(lat * 1e7 + (lat >= 0 ? 0.5 : -0.5));
   357→        int32_t ilng = static_cast<int32_t>(lng * 1e7 + (lng >= 0 ? 0.5 : -0.5));
   358→        return (static_cast<int64_t>(ilat) << 32) | static_cast<uint32_t>(ilng);
   359→    };
   360→
   361→    // Collect coordinates from member ways.
   362→    // First pass uses outer/empty-role ways only. If that produces 0 rings,
   363→    // retry with ALL ways (matching osmium's check_roles=false fallback) —
   364→    // inner ways sometimes bridge gaps between outer ways.
   365→    std::unordered_map<int64_t, int> coord_count;
   366→    std::vector<const std::vector<std::pair<double,double>>*> way_coords;
   367→    for (const auto& [way_id, role] : members) {
   368→        if (!include_all_roles && role != "outer" && !role.empty()) continue;
   369→        auto it = way_geoms.find(way_id);
   370→        if (it == way_geoms.end() || it->second.coords.empty()) continue;
   371→        way_coords.push_back(&it->second.coords);
   372→        for (const auto& [lat, lng] : it->second.coords) {
   373→            coord_count[coord_key(lat, lng)]++;
   374→        }
   375→    }
   376→
   377→    // A coordinate is a "split point" if it appears in 2+ ways' node lists.
   378→    // Endpoints always count, internal nodes that appear in multiple ways need splitting.
   379→    // Also, all way endpoints are natural split points.
   380→    std::unordered_set<int64_t> split_points;
   381→    for (auto* coords : way_coords) {
   382→        split_points.insert(coord_key(coords->front().first, coords->front().second));
   383→        split_points.insert(coord_key(coords->back().first, coords->back().second));
   384→    }
   385→    for (auto& [ck, cnt] : coord_count) {
   386→        if (cnt > 1) split_points.insert(ck);
   387→    }
   388→
   389→    // Split each way at split points to create sub-ways
   390→    struct SubWay {
   391→        std::vector<std::pair<double,double>> coords;
   392→    };
   393→    std::vector<SubWay> sub_ways;
   394→    for (auto* coords : way_coords) {
   395→        size_t seg_start = 0;
   396→        for (size_t i = 1; i < coords->size(); i++) {
   397→            int64_t ck = coord_key((*coords)[i].first, (*coords)[i].second);
   398→            if (split_points.count(ck) && i > seg_start) {
   399→                // Create sub-way from seg_start to i (inclusive)
   400→                SubWay sw;
   401→                sw.coords.assign(coords->begin() + seg_start, coords->begin() + i + 1);
   402→                if (sw.coords.size() >= 2) {
   403→                    sub_ways.push_back(std::move(sw));
   404→                }

static std::vector<std::vector<std::pair<double,double>>> assemble_outer_rings(
    const std::vector<std::pair<int64_t, std::string>>& members,
    const std::unordered_map<int64_t, ParsedData::WayGeometry>& way_geoms,
    bool include_all_roles = false)
{
    // Coordinate-based ring assembly: split ways at shared internal nodes,
    // then stitch sub-ways at endpoints with backtracking.
    // This matches osmium's segment-level approach.

    // Coordinate key matching osmium's Location (int32_t nanodegrees)
    auto coord_key = [](double lat, double lng) -> int64_t {
        int32_t ilat = static_cast<int32_t>(lat * 1e7 + (lat >= 0 ? 0.5 : -0.5));
        int32_t ilng = static_cast<int32_t>(lng * 1e7 + (lng >= 0 ? 0.5 : -0.5));
        return (static_cast<int64_t>(ilat) << 32) | static_cast<uint32_t>(ilng);
    };

    // Collect coordinates from member ways.
    // First pass uses outer/empty-role ways only. If that produces 0 rings,
    // retry with ALL ways (matching osmium's check_roles=false fallback) —
    // inner ways sometimes bridge gaps between outer ways.
    std::unordered_map<int64_t, int> coord_count;
    std::vector<const std::vector<std::pair<double,double>>*> way_coords;
    for (const auto& [way_id, role] : members) {
        if (!include_all_roles && role != "outer" && !role.empty()) continue;
        auto it = way_geoms.find(way_id);
        if (it == way_geoms.end() || it->second.coords.empty()) continue;
        way_coords.push_back(&it->second.coords);
        for (const auto& [lat, lng] : it->second.coords) {
            coord_count[coord_key(lat, lng)]++;
        }
    }

    // A coordinate is a "split point" if it appears in 2+ ways' node lists.
    // Endpoints always count, internal nodes that appear in multiple ways need splitting.
    // Also, all way endpoints are natural split points.
    std::unordered_set<int64_t> split_points;
    for (auto* coords : way_coords) {
        split_points.insert(coord_key(coords->front().first, coords->front().second));
        split_points.insert(coord_key(coords->back().first, coords->back().second));
    }
    for (auto& [ck, cnt] : coord_count) {
        if (cnt > 1) split_points.insert(ck);
    }

    // Split each way at split points to create sub-ways
    struct SubWay {
        std::vector<std::pair<double,double>> coords;
    };
    std::vector<SubWay> sub_ways;
    for (auto* coords : way_coords) {
        size_t seg_start = 0;
        for (size_t i = 1; i < coords->size(); i++) {
            int64_t ck = coord_key((*coords)[i].first, (*coords)[i].second);
            if (split_points.count(ck) && i > seg_start) {
                // Create sub-way from seg_start to i (inclusive)
                SubWay sw;
                sw.coords.assign(coords->begin() + seg_start, coords->begin() + i + 1);
                if (sw.coords.size() >= 2) {
                    sub_ways.push_back(std::move(sw));
                }


The lambda `coord_key` shadows the global `coord_key` from geometry.h. Since they're identical, I just need to remove the lambda:


Edit

    // Coordinate-based ring assembly: split ways at shared internal nodes,
    // then stitch sub-ways at endpoints with backtracking.
    // This matches osmium's segment-level approach.

    // Coordinate key matching osmium's Location (int32_t nanodegrees)
    auto coord_key = [](double lat, double lng) -> int64_t {
        int32_t ilat = static_cast<int32_t>(lat * 1e7 + (lat >= 0 ? 0.5 : -0.5));
        int32_t ilng = static_cast<int32_t>(lng * 1e7 + (lng >= 0 ? 0.5 : -0.5));
        return (static_cast<int64_t>(ilat) << 32) | static_cast<uint32_t>(ilng);
    };

    // Collect coordinates from member ways.

    // Coordinate-based ring assembly: split ways at shared internal nodes,
    // then stitch sub-ways at endpoints with backtracking.
    // This matches osmium's segment-level approach.
    // coord_key() from geometry.h provides coordinate hashing.

    // Collect coordinates from member ways.