private static Geometry doBuild(
    List<LongArrayList> rings,
    OsmReader.NodeLocationProvider nodeCache,
    long osmId,
    double minGap,
    boolean fix
  ) throws GeometryException {
    try {
      if (rings.isEmpty()) {
        throw new GeometryException.Verbose("osm_invalid_multipolygon_empty",
          "error building multipolygon " + osmId + ": no rings to process");
      }
      List<LongArrayList> idSegments = connectPolygonSegments(rings);
      List<Ring> polygons = getClosedRings(nodeCache, minGap, fix, idSegments);
      if (polygons.isEmpty()) {
        throw new GeometryException.Verbose("osm_invalid_multipolygon_empty_after_fix",
          "error building multipolygon " + osmId + ": no rings to process after fixing");
      }
      polygons.sort(BY_AREA_DESCENDING);
      Set<Ring> shells = groupParentChildShells(polygons);
      if (shells.isEmpty()) {
        throw new GeometryException.Verbose("osm_invalid_multipolygon_not_closed",
          "error building multipolygon " + osmId + ": multipolygon not closed");
      } else if (shells.size() == 1) {
        return shells.iterator().next().toPolygon();
      } else {
        Polygon[] finished = shells.stream()
          .map(Ring::toPolygon)
          .toArray(Polygon[]::new);
        return GeoUtils.JTS_FACTORY.createMultiPolygon(finished);
      }
    } catch (Exception e) {
      if (e instanceof GeometryException geoe) {
        throw geoe;
      } else if (e instanceof TopologyException && !fix) {
        // retry but fix every polygon first
        return doBuild(rings, nodeCache, osmId, minGap, true);
      } else {
        throw new GeometryException("osm_invalid_multipolygon", "error building multipolygon " + osmId + ": " + e);
      }
    }
  }
Builds a multipolygon from linestrings containing the IDs of nodes in edge segments and a node ID location
provider.
<p>
Attempts to close rings where endpoints are within {@code minGap} units apart.

@param rings     edge segment node ID sequences
@param nodeCache node location provider
@param osmId     ID of this multipolygon
@param minGap    the minimum units apart to close a linestring
@return The new multipolygon
@throws GeometryException if building the polygon fails
planetiler-core/src/main/java/com/onthegomap/planetiler/reader/osm/OsmMultipolygon.java