@Override
  public void finish(TileArchiveMetadata tileArchiveMetadata) {
    if (!isClustered) {
      LOGGER.info("Tile data was not written in order, sorting entries...");
      Collections.sort(entries);
      LOGGER.info("Done sorting.");
    }
    try {
      Directories directories = makeDirectories(entries);
      // use treemap to ensure consistent ouput between runs
      var otherMetadata = new TreeMap<>(tileArchiveMetadata.toMap());

      // exclude keys included in top-level header
      otherMetadata.remove(TileArchiveMetadata.CENTER_KEY);
      otherMetadata.remove(TileArchiveMetadata.ZOOM_KEY);
      otherMetadata.remove(TileArchiveMetadata.BOUNDS_KEY);
      otherMetadata.remove(TileArchiveMetadata.FORMAT_KEY);
      otherMetadata.remove(TileArchiveMetadata.MINZOOM_KEY);
      otherMetadata.remove(TileArchiveMetadata.MAXZOOM_KEY);
      otherMetadata.remove(TileArchiveMetadata.VECTOR_LAYERS_KEY);
      otherMetadata.remove(TileArchiveMetadata.COMPRESSION_KEY);

      byte[] jsonBytes =
        new Pmtiles.JsonMetadata(tileArchiveMetadata.vectorLayers(), otherMetadata).toBytes();
      jsonBytes = Gzip.gzip(jsonBytes);

      String formatString = tileArchiveMetadata.format();
      var outputFormat =
        TileArchiveMetadata.MVT_FORMAT.equals(formatString) ? Pmtiles.TileType.MVT : Pmtiles.TileType.UNKNOWN;

      var bounds = tileArchiveMetadata.bounds() == null ? GeoUtils.WORLD_LAT_LON_BOUNDS : tileArchiveMetadata.bounds();
      var center = tileArchiveMetadata.center() == null ? bounds.centre() : tileArchiveMetadata.center();
      int zoom = (int) Math.ceil(tileArchiveMetadata.zoom() == null ? GeoUtils.getZoomFromLonLatBounds(bounds) :
        tileArchiveMetadata.zoom());
      int minzoom = tileArchiveMetadata.minzoom() == null ? 0 : tileArchiveMetadata.minzoom();
      int maxzoom =
        tileArchiveMetadata.maxzoom() == null ? PlanetilerConfig.MAX_MAXZOOM : tileArchiveMetadata.maxzoom();


      Pmtiles.Compression tileCompression = switch (tileArchiveMetadata.tileCompression()) {
        case GZIP -> Pmtiles.Compression.GZIP;
        case NONE -> Pmtiles.Compression.NONE;
        default -> Pmtiles.Compression.UNKNOWN;
      };

      Pmtiles.Header header = new Pmtiles.Header(
        (byte) 3,
        Pmtiles.HEADER_LEN,
        directories.root.length,
        INIT_SECTION + currentOffset,
        jsonBytes.length,
        INIT_SECTION + currentOffset + jsonBytes.length,
        directories.leaves.length,
        INIT_SECTION,
        currentOffset,
        numAddressedTiles,
        entries.size(),
        hashToOffset.size() + numUnhashedTiles,
        isClustered,
        Pmtiles.Compression.GZIP,
        tileCompression,
        outputFormat,
        (byte) minzoom,
        (byte) maxzoom,
        (int) (bounds.getMinX() * 10_000_000),
        (int) (bounds.getMinY() * 10_000_000),
        (int) (bounds.getMaxX() * 10_000_000),
        (int) (bounds.getMaxY() * 10_000_000),
        (byte) zoom,
        (int) (center.x * 10_000_000),
        (int) (center.y * 10_000_000)
      );

      LOGGER.info("Writing metadata and leaf directories...");

      out.write(ByteBuffer.wrap(jsonBytes));
      out.write(ByteBuffer.wrap(directories.leaves));

      LOGGER.info("Writing header...");
      out.position(0);
      out.write(ByteBuffer.wrap(header.toBytes()));
      out.write(ByteBuffer.wrap(directories.root));

      Format format = Format.defaultInstance();

      if (LOGGER.isInfoEnabled()) {
        LOGGER.info("# addressed tiles: {}", numAddressedTiles);
        LOGGER.info("# of tile entries: {}", entries.size());
        LOGGER.info("# of tile contents: {}", (hashToOffset.size() + numUnhashedTiles));
        LOGGER.info("Root directory: {}B", format.storage(directories.root.length, false));

        LOGGER.info("# leaves: {}", directories.numLeaves);
        if (directories.numLeaves > 0) {
          LOGGER.info("Leaf directories: {}B", format.storage(directories.leaves.length, false));
          LOGGER
            .info("Avg leaf size: {}B", format.storage(directories.leaves.length / directories.numLeaves, false));
        }

        LOGGER
          .info("Total dir bytes: {}B", format.storage(directories.root.length + directories.leaves.length, false));
        double tot = (double) directories.root.length + directories.leaves.length;
        LOGGER.info("Average bytes per addressed tile: {}", tot / numAddressedTiles);
      }
    } catch (IOException e) {
      LOGGER.error(e.getMessage());
    }
  }
Serialize all entries into bytes, choosing the # of leaf directories to ensure the header+root fits in 16 KB.

@param entries a sorted ObjectArrayList of all entries in the tileset.
@return byte arrays of the root and all leaf directories, and the # of leaves.
@throws IOException if compression fails
planetiler-core/src/main/java/com/onthegomap/planetiler/pmtiles/WriteablePmtiles.java