private static Set<Ring> groupParentChildShells(List<Ring> polygons) {
    // use linked hash set to ensure the same input always produces the same output
    Set<Ring> shells = new LinkedHashSet<>();
    int numPolygons = polygons.size();
    if (numPolygons == 0) {
      return shells;
    }
    shells.add(polygons.getFirst());
    if (numPolygons == 1) {
      return shells;
    }
    for (int i = 0; i < numPolygons; i++) {
      Ring outer = polygons.get(i);
      if (i < numPolygons - 1) {
        PreparedPolygon prepared = new PreparedPolygon(outer.geom);
        // since the rings are sorted by area descending, the inner loop is only smaller rings
        for (int j = i + 1; j < numPolygons; j++) {
          Ring inner = polygons.get(j);
          if (prepared.contains(inner.geom)) {
            // keep searching until we find the smallest ring that contains this one
            // that one is the direct parent
            if (inner.containedBy != null) {
              inner.containedBy.holes.remove(inner);
              shells.remove(inner);
            }
            inner.containedBy = outer;
            if (inner.isHole()) {
              outer.holes.add(inner);
            } else {
              shells.add(inner);
            }
          }
        }
      }
      if (outer.containedBy == null) {
        shells.add(outer);
      }
    }
    return shells;
  }
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