type: user

role: user

content:
In /root/geoshenanigans/geocoder/builder/src/build_index.cpp, find all code related to sorting and deduplicating admin_polygons and admin_vertices. I need to understand:

1. How admin polygons are sorted (what key/comparator)
2. Whether there's deduplication like there is for ways and addr_points
3. How admin_vertices are reordered when polygons are sorted

Search for "admin_polygon", "admin_vert", "sort" near admin-related code. Show me the relevant code sections with line numbers.

userType: external

entrypoint: cli

gitBranch: main

slug: concurrent-humming-sonnet


type: message

role: assistant

type: text

text: I'll search for code related to sorting and deduplicating admin_polygons and admin_vertices in the specified file.

service_tier: standard

type: assistant


Read

type: direct

speed: standard


toolUseResult: Error: File content (27586 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.


text: Let me search for the admin polygon and vertex related code sections:


content:
677-                {
678-                    uint64_t closed_way_admin_count = 0;
679-                    for (auto& local : tld) {
680-                        for (auto& cwa : local.closed_way_admins) {
681-                            const char* cc = cwa.country_code.empty() ? nullptr : cwa.country_code.c_str();
682:                            add_admin_polygon(data, cwa.vertices, cwa.name.c_str(),
683-                                              cwa.admin_level, cc, &admin_pool);
684-                            closed_way_admin_count++;
685-                        }
686-                        local.closed_way_admins.clear();
687-                    }
--
697-                    pbf.unmap(); // release 86 GiB PBF mmap before admin/S2 phases
698-                    std::cerr << "  Assembling admin polygons in parallel ("
699-                              << data.collected_relations.size() << " relations, "
700-                              << data.way_geometries.size() << " way geometries)..." << std::endl;
701-
702:                    // Thread-local admin results (to avoid locking add_admin_polygon)
703-                    struct AdminResult {
704-                        std::vector<std::pair<double,double>> vertices;
705-                        std::string name;
706-                        uint8_t admin_level;
707-                        std::string country_code;
--
855-                                        auto min_it = std::min_element(vertices.begin(), vertices.end());
856-                                        std::rotate(vertices.begin(), min_it, vertices.end());
857-                                        vertices.push_back(vertices.front());
858-                                    }
859-
860:                                    pp.simplified = simplify_admin_polygon(vertices, ar.admin_level);
861-                                    if (pp.simplified.size() >= 3) {
862-                                        pp.area = polygon_area(pp.simplified);
863-                                    }
864-                                    pp.name = std::move(ar.name);
865-                                    pp.admin_level = ar.admin_level;
--
874-
875-                    / Sequential append + submit to S2 pool (cheap: just vector push + string intern)
876-                    for (auto& pp : prepared) {
877-                        if (pp.simplified.size() < 3) continue;
878-
879:                        uint32_t poly_id = static_cast<uint32_t>(data.admin_polygons.size());
880:                        uint32_t vertex_offset = static_cast<uint32_t>(data.admin_vertices.size());
881-
882-                        for (const auto& [lat, lng] : pp.simplified) {
883:                            data.admin_vertices.push_back({static_cast<float>(lat), static_cast<float>(lng)});
884-                        }
885-
886-                        AdminPolygon poly{};
887-                        poly.vertex_offset = vertex_offset;
888-                        poly.vertex_count = static_cast<uint32_t>(pp.simplified.size());
--
890-                        poly.admin_level = pp.admin_level;
891-                        poly.area = pp.area;
892-                        const char* cc = pp.country_code.empty() ? nullptr : pp.country_code.c_str();
893-                        poly.country_code = (cc && cc[0] && cc[1])
894-                            ? static_cast<uint16_t>((cc[0] << 8) | cc[1]) : 0;
895:                        data.admin_polygons.push_back(poly);
896-
897-                        admin_pool.submit(poly_id, std::move(pp.simplified));
898-                    }
899-
900-                    std::cerr << "  Parallel admin assembly complete: "
--
913-
914-        std::cerr << "Done reading:" << std::endl;
915-        std::cerr << "  " << data.ways.size() << " street ways" << std::endl;
916-        std::cerr << "  " << data.addr_points.size() << " address points" << std::endl;
917-        std::cerr << "  " << data.interp_ways.size() << " interpolation ways" << std::endl;
918:        std::cerr << "  " << data.admin_polygons.size() << " admin polygon rings" << std::endl;
919-
920-        // Drain admin polygon thread pool (may still be processing)
921-        std::cerr << "Waiting for admin polygon S2 covering to complete..." << std::endl;
922-        auto admin_results = admin_pool.drain();
923-        log_phase("    Admin: S2 covering drain", _pt, _cpu);
--
930-        // Sort admin polygons largest-first (by vertex count descending).
931-        // This ensures the work-stealing loops in S2 computation and quality
932-        // variant simplification process expensive polygons first, avoiding
933-        // stragglers at the end.
934-        {
935:            size_t n = data.admin_polygons.size();
936-            std::vector<uint32_t> order(n);
937-            std::iota(order.begin(), order.end(), 0u);
938-            std::sort(order.begin(), order.end(), [&](uint32_t a, uint32_t b) {
939:                return data.admin_polygons[a].vertex_count > data.admin_polygons[b].vertex_count;
940-            });
941-
942-            // Build old→new ID mapping
943-            std::vector<uint32_t> old_to_new(n);
944-            for (uint32_t new_id = 0; new_id < n; new_id++)
945-                old_to_new[order[new_id]] = new_id;
946-
947-            // Reorder polygons and vertices
948-            std::vector<AdminPolygon> new_polys(n);
949-            std::vector<NodeCoord> new_verts;
950:            new_verts.reserve(data.admin_vertices.size());
951-            for (uint32_t new_id = 0; new_id < n; new_id++) {
952:                auto p = data.admin_polygons[order[new_id]];
953-                uint32_t old_offset = p.vertex_offset;
954-                p.vertex_offset = static_cast<uint32_t>(new_verts.size());
955-                new_polys[new_id] = p;
956-                for (uint32_t v = 0; v < p.vertex_count; v++)
957:                    new_verts.push_back(data.admin_vertices[old_offset + v]);
958-            }
959:            data.admin_polygons = std::move(new_polys);
960:            data.admin_vertices = std::move(new_verts);
961-
962-            // Remap poly_ids in cell_to_admin (preserving INTERIOR_FLAG)
963-            for (auto& [cell_id, ids] : data.cell_to_admin) {
964-                for (auto& id : ids) {
965-                    uint32_t flags = id & INTERIOR_FLAG;
--
1328-                for (auto& a : data.addr_points) {
1329-                    a.housenumber_id = rm.at(a.housenumber_id);
1330-                    a.street_id = rm.at(a.street_id);
1331-                }
1332-                for (auto& iw : data.interp_ways) iw.street_id = rm.at(iw.street_id);
1333:                for (auto& ap : data.admin_polygons) ap.name_id = rm.at(ap.name_id);
1334-                std::cerr << "  String pool sorted: " << strings.size() << " strings" << std::endl;
1335-            }
1336-        }
1337-        log_phase("  Sort strings", _st, _sc);
1338-
--
1517-            std::cerr << "  Interps sorted: " << n << std::endl;
1518-        }
1519-        log_phase("  Sort interps", _st, _sc);
1520-
1521-        // 5. Sort admin polygons by (name, level, country, vertex_count) + reorder vertices
1522:        if (!data.admin_polygons.empty()) {
1523:            size_t n = data.admin_polygons.size();
1524-            std::vector<uint32_t> order(n);
1525-            std::iota(order.begin(), order.end(), 0);
1526-            const auto& sp = data.string_pool.data();
1527-            std::sort(order.begin(), order.end(), [&](uint32_t a, uint32_t b) {
1528:                const auto& pa = data.admin_polygons[a];
1529:                const auto& pb = data.admin_polygons[b];
1530-                if (pa.name_id != pb.name_id) return pa.name_id < pb.name_id;
1531-                if (pa.admin_level != pb.admin_level) return pa.admin_level < pb.admin_level;
1532-                if (pa.country_code != pb.country_code) return pa.country_code < pb.country_code;
1533-                if (pa.vertex_count != pb.vertex_count) return pa.vertex_count < pb.vertex_count;
1534-                // Compare first few vertices for tiebreaking
1535-                uint32_t nc = std::min(pa.vertex_count, pb.vertex_count);
1536-                nc = std::min(nc, 20u); // limit comparison depth
1537-                for (uint32_t j = 0; j < nc; j++) {
1538:                    uint32_t la = float_bits(data.admin_vertices[pa.vertex_offset + j].lat);
1539:                    uint32_t lb = float_bits(data.admin_vertices[pb.vertex_offset + j].lat);
1540-                    if (la != lb) return la < lb;
1541:                    uint32_t ga = float_bits(data.admin_vertices[pa.vertex_offset + j].lng);
1542:                    uint32_t gb = float_bits(data.admin_vertices[pb.vertex_offset + j].lng);
1543-                    if (ga != gb) return ga < gb;
1544-                }
1545-                return false;
1546-            });
1547-            std::vector<uint32_t> old_to_new(n);
1548-            for (uint32_t i = 0; i < n; i++) old_to_new[order[i]] = i;
1549-            std::vector<AdminPolygon> new_polys(n);
1550-            std::vector<NodeCoord> new_verts;
1551:            new_verts.reserve(data.admin_vertices.size());
1552-            for (uint32_t i = 0; i < n; i++) {
1553:                auto p = data.admin_polygons[order[i]];
1554-                uint32_t old_off = p.vertex_offset;
1555-                p.vertex_offset = static_cast<uint32_t>(new_verts.size());
1556-                for (uint32_t j = 0; j < p.vertex_count; j++)
1557:                    new_verts.push_back(data.admin_vertices[old_off + j]);
1558-                new_polys[i] = p;
1559-            }
1560:            data.admin_polygons = std::move(new_polys);
1561:            data.admin_vertices = std::move(new_verts);
1562-            // Remap admin cell entries
1563-            for (auto& [cell_id, ids] : data.cell_to_admin) {
1564-                for (auto& id : ids) {
1565-                    uint32_t flags = id & 0x80000000u;
1566-                    uint32_t masked = id & 0x7FFFFFFFu;
--
1775-
1776-        std::vector<std::string> admin_base_files = {
1777-            "admin_cells.bin", "admin_entries.bin", "strings.bin"
1778-        };
1779-        std::vector<std::string> admin_quality_files = {
1780:            "admin_polygons.bin", "admin_vertices.bin"
1781-        };
1782-        std::vector<std::string> street_files = {
1783-            "street_ways.bin", "street_nodes.bin", "street_entries.bin"
1784-        };
1785-        std::vector<std::string> address_files = {


content: No matches found


text: Perfect! Now let me read the specific line ranges to get the full context of the sorting and deduplication logic:


content:
925	            auto& target = data.cell_to_admin[cell_id];
926	            target.insert(target.end(), ids.begin(), ids.end());
927	        }
928	        std::cerr << "Admin polygon S2 covering complete (" << data.cell_to_admin.size() << " cells)." << std::endl;
929
930	        // Sort admin polygons largest-first (by vertex count descending).
931	        // This ensures the work-stealing loops in S2 computation and quality
932	        // variant simplification process expensive polygons first, avoiding
933	        // stragglers at the end.
934	        {
935	            size_t n = data.admin_polygons.size();
936	            std::vector<uint32_t> order(n);
937	            std::iota(order.begin(), order.end(), 0u);
938	            std::sort(order.begin(), order.end(), [&](uint32_t a, uint32_t b) {
939	                return data.admin_polygons[a].vertex_count > data.admin_polygons[b].vertex_count;
940	            });
941
942	            // Build old→new ID mapping
943	            std::vector<uint32_t> old_to_new(n);
944	            for (uint32_t new_id = 0; new_id < n; new_id++)
945	                old_to_new[order[new_id]] = new_id;
946
947	            // Reorder polygons and vertices
948	            std::vector<AdminPolygon> new_polys(n);
949	            std::vector<NodeCoord> new_verts;
950	            new_verts.reserve(data.admin_vertices.size());
951	            for (uint32_t new_id = 0; new_id < n; new_id++) {
952	                auto p = data.admin_polygons[order[new_id]];
953	                uint32_t old_offset = p.vertex_offset;
954	                p.vertex_offset = static_cast<uint32_t>(new_verts.size());
955	                new_polys[new_id] = p;
956	                for (uint32_t v = 0; v < p.vertex_count; v++)
957	                    new_verts.push_back(data.admin_vertices[old_offset + v]);
958	            }
959	            data.admin_polygons = std::move(new_polys);
960	            data.admin_vertices = std::move(new_verts);
961
962	            // Remap poly_ids in cell_to_admin (preserving INTERIOR_FLAG)
963	            for (auto& [cell_id, ids] : data.cell_to_admin) {
964	                for (auto& id : ids) {
965	                    uint32_t flags = id & INTERIOR_FLAG;
966	                    uint32_t old_id = id & ID_MASK;
967	                    id = old_to_new[old_id] | flags;
968	                }
969	            }
970	            std::cerr << "Sorted admin polygons largest-first (" << n << " polygons)." << std::endl;
971	        }
972
973	        // --- Parallel S2 cell computation for ways and interpolation ---
974	        {


content:
1515	            std::sort(data.sorted_interp_cells.begin(), data.sorted_interp_cells.end(), cmp);
1516	            data.cell_to_interps.clear();
1517	            std::cerr << "  Interps sorted: " << n << std::endl;
1518	        }
1519	        log_phase("  Sort interps", _st, _sc);
1520
1521	        // 5. Sort admin polygons by (name, level, country, vertex_count) + reorder vertices
1522	        if (!data.admin_polygons.empty()) {
1523	            size_t n = data.admin_polygons.size();
1524	            std::vector<uint32_t> order(n);
1525	            std::iota(order.begin(), order.end(), 0);
1526	            const auto& sp = data.string_pool.data();
1527	            std::sort(order.begin(), order.end(), [&](uint32_t a, uint32_t b) {
1528	                const auto& pa = data.admin_polygons[a];
1529	                const auto& pb = data.admin_polygons[b];
1530	                if (pa.name_id != pb.name_id) return pa.name_id < pb.name_id;
1531	                if (pa.admin_level != pb.admin_level) return pa.admin_level < pb.admin_level;
1532	                if (pa.country_code != pb.country_code) return pa.country_code < pb.country_code;
1533	                if (pa.vertex_count != pb.vertex_count) return pa.vertex_count < pb.vertex_count;
1534	                // Compare first few vertices for tiebreaking
1535	                uint32_t nc = std::min(pa.vertex_count, pb.vertex_count);
1536	                nc = std::min(nc, 20u); // limit comparison depth
1537	                for (uint32_t j = 0; j < nc; j++) {
1538	                    uint32_t la = float_bits(data.admin_vertices[pa.vertex_offset + j].lat);
1539	                    uint32_t lb = float_bits(data.admin_vertices[pb.vertex_offset + j].lat);
1540	                    if (la != lb) return la < lb;
1541	                    uint32_t ga = float_bits(data.admin_vertices[pa.vertex_offset + j].lng);
1542	                    uint32_t gb = float_bits(data.admin_vertices[pb.vertex_offset + j].lng);
1543	                    if (ga != gb) return ga < gb;
1544	                }
1545	                return false;
1546	            });
1547	            std::vector<uint32_t> old_to_new(n);
1548	            for (uint32_t i = 0; i < n; i++) old_to_new[order[i]] = i;
1549	            std::vector<AdminPolygon> new_polys(n);
1550	            std::vector<NodeCoord> new_verts;
1551	            new_verts.reserve(data.admin_vertices.size());
1552	            for (uint32_t i = 0; i < n; i++) {
1553	                auto p = data.admin_polygons[order[i]];
1554	                uint32_t old_off = p.vertex_offset;
1555	                p.vertex_offset = static_cast<uint32_t>(new_verts.size());
1556	                for (uint32_t j = 0; j < p.vertex_count; j++)
1557	                    new_verts.push_back(data.admin_vertices[old_off + j]);
1558	                new_polys[i] = p;
1559	            }
1560	            data.admin_polygons = std::move(new_polys);
1561	            data.admin_vertices = std::move(new_verts);
1562	            // Remap admin cell entries
1563	            for (auto& [cell_id, ids] : data.cell_to_admin) {
1564	                for (auto& id : ids) {
1565	                    uint32_t flags = id & 0x80000000u;
1566	                    uint32_t masked = id & 0x7FFFFFFFu;
1567	                    if (masked < old_to_new.size())
1568	                        id = old_to_new[masked] | flags;
1569	                }
1570	                std::sort(ids.begin(), ids.end());
1571	            }
1572	            std::cerr << "  Admin polygons sorted: " << n << std::endl;
1573	        }
1574	        log_phase("  Sort admin", _st, _sc);


text: Now let me check if there's any deduplication logic for admin polygons by searching for relevant code:


content:
1216-                data.sorted_addr_cells = std::move(pairs);
1217-                log_phase("    Dedup: build sorted pairs", _dt, _dc);
1218-            });
1219:            auto f4 = std::async(std::launch::async, [&]{ deduplicate(data.cell_to_admin); });
1220-            f2.get(); f4.get();
1221-        }
1222-


Grep

pattern: Ways sorted|Address points sorted|dedup|duplicate


content:
751-                                // If outer-only assembly failed, retry with all ways.
752-                                // Osmium ignores roles during assembly (check_roles=false) —
753-                                // inner ways sometimes form the boundary or bridge gaps.
754:                                // Filter retry results: discard rings with duplicate coords
755-                                // (figure-8 shapes from merging holes with outer boundary).
756-                                if (rings.empty()) {
757-                                    auto retry = assemble_outer_rings(rel.members, data.way_geometries, true);
758-                                    for (auto& ring : retry) {
759:                                        if (!ring_has_duplicate_coords(ring)) {
760-                                            rings.push_back(std::move(ring));
761-                                        }
762-                                    }
--
1158-        std::cerr << "Resolving interpolation endpoints..." << std::endl;
1159-        resolve_interpolation_endpoints(data);
1160-
1161:        // Deduplicate + convert to sorted pairs for fast writing
1162-        log_phase("S2 cell computation", _pt, _cpu);
1163-        std::cerr << "Deduplicating + sorting for write..." << std::endl;
1164-        {
--
1180-                std::sort(sorted_cells.begin(), sorted_cells.end());
1181-                log_phase("    Dedup: sort cell IDs", _dt, _dc);
1182-
1183:                // Step 3: Parallel dedup within each cell
1184-                {
1185-                    unsigned nthreads = std::thread::hardware_concurrency();
1186-                    size_t chunk = (sorted_cells.size() + nthreads - 1) / nthreads;
--
1199-                    }
1200-                    for (auto& t : threads) t.join();
1201-                }
1202:                log_phase("    Dedup: per-cell sort+dedup", _dt, _dc);
1203-
1204-                / Step 4: Count total pairs
1205-                size_t total_pairs = 0;
--
1216-                data.sorted_addr_cells = std::move(pairs);
1217-                log_phase("    Dedup: build sorted pairs", _dt, _dc);
1218-            });
1219:            auto f4 = std::async(std::launch::async, [&]{ deduplicate(data.cell_to_admin); });
1220-            f2.get(); f4.get();
1221-        }
1222-
--
1366-            for (uint32_t i = 0; i < n; i++) old_to_new[order[i]] = i;
1367-            std::vector<AddrPoint> sorted(n);
1368-            for (uint32_t i = 0; i < n; i++) sorted[i] = data.addr_points[order[i]];
1369:            // Dedup consecutive identical records (planet has ~4M duplicates)
1370:            // Map duplicate old IDs to the first occurrence's new ID
1371:            std::vector<uint32_t> dedup_remap(n);
1372-            size_t write_pos = 0;
1373-            for (size_t i = 0; i < n; i++) {
1374-                if (write_pos == 0 || memcmp(&sorted[i], &sorted[write_pos - 1], sizeof(AddrPoint)) != 0) {
1375-                    sorted[write_pos] = sorted[i];
1376:                    dedup_remap[i] = static_cast<uint32_t>(write_pos);
1377-                    write_pos++;
1378-                } else {
1379:                    dedup_remap[i] = static_cast<uint32_t>(write_pos - 1);
1380-                }
1381-            }
1382-            sorted.resize(write_pos);
--
1384-            if (write_pos < n)
1385-                std::cerr << "  Deduped addr_points: " << n << " → " << write_pos
1386-                          << " (-" << (n - write_pos) << ")" << std::endl;
1387:            // Remap IDs through both old→new and dedup
1388-            for (auto& p : data.sorted_addr_cells)
1389:                p.item_id = dedup_remap[old_to_new[p.item_id]];
1390-            auto cmp = [](const CellItemPair& a, const CellItemPair& b) {
1391-                return a.cell_id < b.cell_id || (a.cell_id == b.cell_id && a.item_id < b.item_id);
1392-            };
--
1419-                }
1420-                return false;
1421-            });
1422:            // Reorder ways + nodes by sort order, dedup identical consecutive ways
1423-            std::vector<WayHeader> new_ways;
1424-            std::vector<NodeCoord> new_nodes;
1425-            new_ways.reserve(n);
--
1431-                uint32_t old_off = w.node_offset;
1432-                uint8_t nc = w.node_count;
1433-
1434:                // Check if this way is identical to the previous one (dedup)
1435-                bool is_dup = false;
1436-                if (!new_ways.empty()) {
1437-                    auto& prev = new_ways.back();
--
1456-                    new_ways.push_back(w);
1457-                }
1458-            }
1459:            size_t deduped = n - new_ways.size();
1460-            data.ways = std::move(new_ways);
1461-            data.street_nodes = std::move(new_nodes);
1462-            for (auto& p : data.sorted_way_cells) p.item_id = old_to_new[p.item_id];
--
1465-            };
1466-            std::sort(data.sorted_way_cells.begin(), data.sorted_way_cells.end(), cmp);
1467-            data.cell_to_ways.clear();
1468:            std::cerr << "  Ways sorted: " << n << " (" << deduped << " duplicates removed)" << std::endl;
1469-        }
1470-        log_phase("  Sort ways", _st, _sc);
1471-


text:
Perfect! Now I have all the information needed. Let me create a comprehensive summary of the findings.

## Summary: Admin Polygon Sorting and Deduplication in build_index.cpp

### 1. **First Sort Pass - Largest-First (Lines 930-971)**

**Location:** Lines 930-971

**Purpose:** Sort admin polygons by vertex count (descending) to ensure large/expensive polygons are processed first during S2 computation and simplification, avoiding stragglers.

**Sorting Key:**
```cpp
std::sort(order.begin(), order.end(), [&](uint32_t a, uint32_t b) {
    return data.admin_polygons[a].vertex_count > data.admin_polygons[b].vertex_count;
});
```

**Comparator:** `vertex_count` in descending order (larger polygons first)

**Admin Vertices Reordering (Lines 947-960):**
When polygons are reordered, vertices are also reordered to maintain locality:
- Each polygon's `vertex_offset` is updated to point to its new position in the compacted vertex array
- All vertices for each polygon are copied sequentially to the new `new_verts` vector
- The old vertex array is replaced with the compacted one

---

### 2. **Second Sort Pass - Final Canonical Order (Lines 1521-1573)**

**Location:** Lines 1521-1573

**Purpose:** Final stable sort for writing to disk, organized by name, admin level, country code, and vertex count.

**Sorting Key (Multi-level comparator):**
```cpp
std::sort(order.begin(), order.end(), [&](uint32_t a, uint32_t b) {
    const auto& pa = data.admin_polygons[a];
    const auto& pb = data.admin_polygons[b];
    if (pa.name_id != pb.name_id) return pa.name_id < pb.name_id;
    if (pa.admin_level != pb.admin_level) return pa.admin_level < pb.admin_level;
    if (pa.country_code != pb.country_code) return pa.country_code < pb.country_code;
    if (pa.vertex_count != pb.vertex_count) return pa.vertex_count < pb.vertex_count;
    // Tiebreaker: compare first 20 vertices by lat/lng float bits
    uint32_t nc = std::min(pa.vertex_count, pb.vertex_count);
    nc = std::min(nc, 20u);
    for (uint32_t j = 0; j < nc; j++) {
        uint32_t la = float_bits(data.admin_vertices[pa.vertex_offset + j].lat);
        uint32_t lb = float_bits(data.admin_vertices[pb.vertex_offset + j].lat);
        if (la != lb) return la < lb;
        uint32_t ga = float_bits(data.admin_vertices[pa.vertex_offset + j].lng);
        uint32_t gb = float_bits(data.admin_vertices[pb.vertex_offset + j].lng);
        if (ga != gb) return ga < gb;
    }
    return false;
});
```

**Comparator:** 4-level sort:
1. `name_id` (ascending)
2. `admin_level` (ascending)
3. `country_code` (ascending)
4. `vertex_count` (ascending)
5. Tiebreaker: First 20 vertex coordinates (by float bit representation)

**Admin Vertices Reordering (Lines 1549-1561):**
Same pattern as the first sort: reorder both polygons and their vertices into new arrays while updating vertex offsets.

---

### 3. **Deduplication Status**

**NO DEDUPLICATION** is performed on admin polygons.

Compare with other data types:
- **Ways** (Lines 1422-1468): Deduplicate identical consecutive ways after sorting; removes ~0-many duplicates
- **Address Points** (Lines 1369-1389): Deduplicate consecutive identical records; planet has ~4M duplicates
- **Admin Cell Entries** (Line 1219): Call `deduplicate(data.cell_to_admin)` to remove duplicate cell→admin mappings

Admin polygons are sorted but **never deduplicated** — each unique polygon is retained.

---

### 4. **Cell Entry Remapping**

After both sorts, polygon IDs in `cell_to_admin` are remapped:
- Lines 962-969 (first sort): Preserve `INTERIOR_FLAG` while remapping IDs
- Lines 1563-1571 (second sort): Preserve high bit flags while remapping, then sort cell entries