Edit

// --- Ring self-intersection check (matches osmium's segment crossing detection) ---

static bool segments_intersect(double ax1, double ay1, double ax2, double ay2,
                                double bx1, double by1, double bx2, double by2) {
    // Check if segment (a1,a2) crosses segment (b1,b2)
    // Using cross product orientation test
    auto cross = [](double ox, double oy, double ax, double ay, double bx, double by) -> double {
        return (ax - ox) * (by - oy) - (ay - oy) * (bx - ox);
    };
    double d1 = cross(bx1, by1, bx2, by2, ax1, ay1);
    double d2 = cross(bx1, by1, bx2, by2, ax2, ay2);
    double d3 = cross(ax1, ay1, ax2, ay2, bx1, by1);
    double d4 = cross(ax1, ay1, ax2, ay2, bx2, by2);
    if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) &&
        ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) {
        return true;
    }
    return false;
}

static bool ring_has_self_intersection(const std::vector<std::pair<double,double>>& ring) {
    // Sweep-line inspired check: sort segments by min-x, then only check
    // overlapping x-ranges. Much faster than O(n²) for large rings.
    size_t n = ring.size();
    if (n < 4) return false;

    // For small rings, brute force is fine
    if (n <= 32) {
        for (size_t i = 0; i + 1 < n; i++) {
            for (size_t j = i + 2; j + 1 < n; j++) {
                if (j == i + 1 || (i == 0 && j == n - 2)) continue;
                if (segments_intersect(ring[i].first, ring[i].second,
                                        ring[i+1].first, ring[i+1].second,
                                        ring[j].first, ring[j].second,
                                        ring[j+1].first, ring[j+1].second))
                    return true;
            }
        }
        return false;
    }

    // For larger rings, sort segments by min-x and prune non-overlapping
    struct Seg { double min_x, max_x; size_t idx; };
    std::vector<Seg> segs(n - 1);
    for (size_t i = 0; i + 1 < n; i++) {
        double x1 = ring[i].second, x2 = ring[i+1].second; // use lng as x
        segs[i] = {std::min(x1,x2), std::max(x1,x2), i};
    }
    std::sort(segs.begin(), segs.end(), [](const Seg& a, const Seg& b) {
        return a.min_x < b.min_x;
    });

    for (size_t a = 0; a + 1 < segs.size(); a++) {
        for (size_t b = a + 1; b < segs.size(); b++) {
            if (segs[b].min_x > segs[a].max_x) break; // no more x-overlap
            size_t i = segs[a].idx, j = segs[b].idx;
            // Skip adjacent segments
            if (j == i + 1 || i == j + 1) continue;
            if ((i == 0 && j == n - 2) || (j == 0 && i == n - 2)) continue;
            if (segments_intersect(ring[i].first, ring[i].second,
                                    ring[i+1].first, ring[i+1].second,
                                    ring[j].first, ring[j].second,
                                    ring[j+1].first, ring[j+1].second))
                return true;
        }
    }
    return false;
}

// Check if a ring has duplicate coordinates (spikes/figure-8 from merged holes)
// A valid simple polygon visits each coordinate exactly once (except closing point).
static bool ring_has_duplicate_coords(const std::vector<std::pair<double,double>>& ring,
                                       decltype(std::function<int64_t(double,double)>()) coord_key_fn) {
    std::unordered_set<int64_t> seen;
    for (size_t i = 0; i + 1 < ring.size(); i++) { // skip closing point
        int64_t k = coord_key_fn(ring[i].first, ring[i].second);
        if (!seen.insert(k).second) return true; // duplicate
    }
    return false;
}

// segments_intersect, ring_has_self_intersection, ring_has_duplicate_coords are in geometry.h