Now I need to replace the node reader section (lines 2255-2272) and the entire Pass 3 section with a single reader that handles both. This is a big restructure. Let me find the exact boundaries.


  2264→                            index.set_batch(loc_batch);
  2265→                        }
  2266→                    });
  2267→                }
  2268→
  2269→                // Reader thread (producer)
  2270→                {
  2271→                    osmium::io::Reader reader_nodes{input_file, osmium::osm_entity_bits::node};
  2272→                    while (auto buf = reader_nodes.read()) {
  2273→                        {
  2274→                            std::unique_lock<std::mutex> lock(queue_mutex);
  2275→                            queue_cv.wait(lock, [&]{ return block_queue.size() < MAX_QUEUE; });
  2276→                            block_queue.push_back(std::move(buf));
  2277→                        }
  2278→                        queue_cv.notify_one();
  2279→                    }
  2280→                    reader_nodes.close();
  2281→                    {
  2282→                        std::lock_guard<std::mutex> lock(queue_mutex);
  2283→                        reader_done = true;
  2284→                    }
  2285→                    queue_cv.notify_all();
  2286→                }
  2287→
  2288→                for (auto& w : node_workers) w.join();
  2289→
  2290→                // Merge address points
  2291→                uint64_t total_addrs = 0;
  2292→                for (auto& local : ntld) {
  2293→                    for (size_t j = 0; j < local.addr_coords.size(); j++) {
  2294→                        uint64_t dummy = 0;
  2295→                        add_addr_point(data, local.addr_coords[j].first, local.addr_coords[j].second,
  2296→                                       local.addr_strings[j].first.c_str(),
  2297→                                       local.addr_strings[j].second.c_str(), dummy);
  2298→                    }
  2299→                    total_addrs += local.count;
  2300→                }
  2301→                std::cerr << "  Node processing complete: " << total_addrs
  2302→                          << " address points collected." << std::endl;
  2303→            }

                            index.set_batch(loc_batch);
                        }
                    });
                }

                // Reader thread (producer)
                {
                    osmium::io::Reader reader_nodes{input_file, osmium::osm_entity_bits::node};
                    while (auto buf = reader_nodes.read()) {
                        {
                            std::unique_lock<std::mutex> lock(queue_mutex);
                            queue_cv.wait(lock, [&]{ return block_queue.size() < MAX_QUEUE; });
                            block_queue.push_back(std::move(buf));
                        }
                        queue_cv.notify_one();
                    }
                    reader_nodes.close();
                    {
                        std::lock_guard<std::mutex> lock(queue_mutex);
                        reader_done = true;
                    }
                    queue_cv.notify_all();
                }

                for (auto& w : node_workers) w.join();

                // Merge address points
                uint64_t total_addrs = 0;
                for (auto& local : ntld) {
                    for (size_t j = 0; j < local.addr_coords.size(); j++) {
                        uint64_t dummy = 0;
                        add_addr_point(data, local.addr_coords[j].first, local.addr_coords[j].second,
                                       local.addr_strings[j].first.c_str(),
                                       local.addr_strings[j].second.c_str(), dummy);
                    }
                    total_addrs += local.count;
                }
                std::cerr << "  Node processing complete: " << total_addrs
                          << " address points collected." << std::endl;
            }


Edit

                // Reader thread (producer)
                {
                    osmium::io::Reader reader_nodes{input_file, osmium::osm_entity_bits::node};
                    while (auto buf = reader_nodes.read()) {
                        {
                            std::unique_lock<std::mutex> lock(queue_mutex);
                            queue_cv.wait(lock, [&]{ return block_queue.size() < MAX_QUEUE; });
                            block_queue.push_back(std::move(buf));
                        }
                        queue_cv.notify_one();
                    }
                    reader_nodes.close();
                    {
                        std::lock_guard<std::mutex> lock(queue_mutex);
                        reader_done = true;
                    }
                    queue_cv.notify_all();
                }

                for (auto& w : node_workers) w.join();

                // Merge address points
                uint64_t total_addrs = 0;
                for (auto& local : ntld) {
                    for (size_t j = 0; j < local.addr_coords.size(); j++) {
                        uint64_t dummy = 0;
                        add_addr_point(data, local.addr_coords[j].first, local.addr_coords[j].second,
                                       local.addr_strings[j].first.c_str(),
                                       local.addr_strings[j].second.c_str(), dummy);
                    }
                    total_addrs += local.count;
                }
                std::cerr << "  Node processing complete: " << total_addrs
                          << " address points collected." << std::endl;
            }

            // --- Pass 3: ways + relations (parallel way processing) ---
            // Build set of way IDs needed for admin assembly (to avoid storing all way geometries)
            std::unordered_set<int64_t> admin_way_ids;
            if (parallel_admin) {
                for (const auto& rel : data.collected_relations) {
                    for (const auto& [way_id, role] : rel.members) {
                        admin_way_ids.insert(way_id);
                    }
                }
                std::cerr << "  Admin assembly needs " << admin_way_ids.size() << " way geometries." << std::endl;
            }

            log_phase("Pass 2: node processing", _pt);
            std::cerr << "  Pass 3: processing ways and areas with " << num_threads
                      << " threads..." << std::endl;

            {
                osmium::io::Reader reader_ways{input_file,
                    osmium::osm_entity_bits::way | osmium::osm_entity_bits::relation};

                // Single reader for nodes + ways + relations
                osmium::io::Reader combined_reader{input_file};

                // Phase 1: Feed node blocks to node workers
                {
                    while (auto buf = combined_reader.read()) {
                        // Check if this buffer contains nodes or ways
                        bool has_nodes = false, has_ways = false;
                        for (const auto& item : buf) {
                            if (item.type() == osmium::item_type::node) { has_nodes = true; break; }
                            if (item.type() == osmium::item_type::way) { has_ways = true; break; }
                        }

                        if (has_nodes) {
                            std::unique_lock<std::mutex> lock(queue_mutex);
                            queue_cv.wait(lock, [&]{ return block_queue.size() < MAX_QUEUE; });
                            block_queue.push_back(std::move(buf));
                            queue_cv.notify_one();
                        } else {
                            // First non-node block: signal node workers done, transition to ways
                            {
                                std::lock_guard<std::mutex> lock(queue_mutex);
                                reader_done = true;
                            }
                            queue_cv.notify_all();

                            // Wait for node workers to finish
                            for (auto& w : node_workers) w.join();

                            // Merge address points
                            uint64_t total_addrs = 0;
                            for (auto& local : ntld) {
                                for (size_t j = 0; j < local.addr_coords.size(); j++) {
                                    uint64_t dummy = 0;
                                    add_addr_point(data, local.addr_coords[j].first, local.addr_coords[j].second,
                                                   local.addr_strings[j].first.c_str(),
                                                   local.addr_strings[j].second.c_str(), dummy);
                                }
                                total_addrs += local.count;
                            }
                            std::cerr << "  Node processing complete: " << total_addrs
                                      << " address points collected." << std::endl;
                            log_phase("Pass 2: node processing", _pt);
                            std::cerr << "  Processing ways with " << num_threads << " threads..." << std::endl;

                            // Transition: this buffer is the first way/relation block
                            // Push it to the way queue (set up below) and continue reading
                            // We need to break out of this loop and start the way processing loop
                            // with this buffer as the first item.

                            // --- Way processing setup (was Pass 3) ---