Source: helpers/stitchmerge.js

/**
 * Takes a GeoJSON and processes each MultiPolygon feature that
 * crosses the antimeridian, converting it into a continuous Polygon feature.
 */
export function stitchmerge(geojson) {
  // Helper: adjust longitude relative to previous one to handle wrapping around ±180°
  function adjustLonToPrev(lon, prevLon) {
    if (prevLon === null || prevLon === undefined) return lon;
    // Normalize angular difference to be within [-180, 180]
    while (lon - prevLon > 180) lon -= 360;
    while (prevLon - lon > 180) lon += 360;
    return lon;
  }

  // Helper: remove consecutive duplicate points (with a small tolerance)
  function dedupeConsecutive(points) {
    const out = [];
    for (let i = 0; i < points.length; i++) {
      const p = points[i];
      const prev = out.length ? out[out.length - 1] : null;
      if (
        !prev ||
        Math.abs(prev[0] - p[0]) > 1e-12 ||
        Math.abs(prev[1] - p[1]) > 1e-12
      ) {
        out.push(p);
      }
    }
    return out;
  }

  // Helper: ensure the ring is closed (first == last point)
  function closeRing(ring) {
    if (!ring.length) return ring;
    const first = ring[0];
    const last = ring[ring.length - 1];
    if (
      Math.abs(first[0] - last[0]) > 1e-12 ||
      Math.abs(first[1] - last[1]) > 1e-12
    ) {
      ring.push([first[0], first[1]]);
    }
    return ring;
  }

  // Process one MultiPolygon geometry -> returns a merged Polygon geometry
  function processMultiPolygonGeometry(multiCoords) {
    // multiCoords: Array of polygons; each polygon is an array of rings.
    // We treat the outer ring (index 0) of each polygon as a possible fragment
    // of a single continuous outer ring that needs to be reconnected.
    // Additional rings (holes) are collected and preserved as holes.
    const exteriorRings = [];
    const interiorRings = [];

    for (let p = 0; p < multiCoords.length; p++) {
      const polygon = multiCoords[p];
      if (!polygon || !polygon.length) continue;
      // polygon[0] is the exterior ring
      exteriorRings.push(polygon[0].slice());
      // if polygon has holes, collect them
      for (let r = 1; r < polygon.length; r++) {
        interiorRings.push(polygon[r].slice());
      }
    }

    // Unwrap all exterior rings so that longitudes are continuous
    const unwrappedExteriors = [];
    let prevLon = null;
    for (let i = 0; i < exteriorRings.length; i++) {
      const ring = exteriorRings[i];
      const newRing = [];
      for (let j = 0; j < ring.length; j++) {
        let [lon, lat] = ring[j];
        lon = adjustLonToPrev(lon, prevLon);
        newRing.push([lon, lat]);
        prevLon = lon;
      }
      const trimmed = dedupeConsecutive(newRing);
      unwrappedExteriors.push(trimmed);
    }

    // Merge all unwrapped exterior rings into a single continuous ring
    // We remove internal closures and only close at the end
    let mergedExterior = [];
    for (let i = 0; i < unwrappedExteriors.length; i++) {
      const ring = unwrappedExteriors[i].slice();
      // remove last point if it's equal to the first (closed ring)
      if (ring.length > 1) {
        const f = ring[0];
        const l = ring[ring.length - 1];
        if (Math.abs(f[0] - l[0]) < 1e-12 && Math.abs(f[1] - l[1]) < 1e-12) {
          ring.pop();
        }
      }
      mergedExterior = mergedExterior.concat(ring);
    }

    // Deduplicate consecutive identical points
    mergedExterior = dedupeConsecutive(mergedExterior);
    // Close the final merged ring
    mergedExterior = closeRing(mergedExterior);

    // Unwrap interior rings (holes) so they match the unwrapped longitude system
    const unwrappedInteriors = interiorRings.map((ring) => {
      const out = [];
      for (let i = 0; i < ring.length; i++) {
        const anchorLon = mergedExterior.length ? mergedExterior[0][0] : null;
        let lon = ring[i][0];
        if (anchorLon !== null) {
          while (lon - anchorLon > 180) lon -= 360;
          while (anchorLon - lon > 180) lon += 360;
        }
        out.push([lon, ring[i][1]]);
      }
      return closeRing(dedupeConsecutive(out));
    });

    // Return a single Polygon geometry: outer + holes
    const coords = [mergedExterior].concat(unwrappedInteriors);
    return { type: "Polygon", coordinates: coords };
  }

  // MAIN: process all features
  const out = {
    ...geojson,
    features: (geojson.features || []).map((feature) => {
      if (!feature || !feature.geometry) return feature;
      const geom = feature.geometry;
      if (geom.type === "MultiPolygon") {
        try {
          const newGeom = processMultiPolygonGeometry(geom.coordinates);
          return { ...feature, geometry: newGeom };
        } catch (e) {
          console.warn("Error stitching a MultiPolygon:", e);
          return feature; // fallback to original geometry
        }
      }
      // Leave other geometries unchanged
      return feature;
    }),
  };

  return out;
}