Skip to content

Instantly share code, notes, and snippets.

@wiesehahn
Last active March 21, 2025 19:02
Show Gist options
  • Save wiesehahn/7e865a04a8d8646944fd13ffc1f483de to your computer and use it in GitHub Desktop.
Save wiesehahn/7e865a04a8d8646944fd13ffc1f483de to your computer and use it in GitHub Desktop.
Compare image compression algorithms regarding size and performance.
# pak::pak("USDAForestService/gdalraster")
library(gdalraster)
library(purrr)
library(fs)
# sset gdal configurations
set_config_option("GDAL_NUM_THREADS", "16")
set_config_option("GDAL_CACHEMAX", "4000")
set_config_option("OVERVIEWS", "IGNORE_EXISTING")
# get orthoimages from LGLN open data
urls <- c(
"https://dop20-rgbi.s3.eu-de.cloud-object-storage.appdomain.cloud/324905842/2024-09-05/dop20rgbi_32_490_5842_2_ni_2024-09-05.tif",
"https://dop20-rgbi.s3.eu-de.cloud-object-storage.appdomain.cloud/326085740/2022-05-09/dop20rgbi_32_608_5740_2_ni_2022-05-09.tif",
"https://dop20-rgbi.s3.eu-de.cloud-object-storage.appdomain.cloud/326085732/2022-05-09/dop20rgbi_32_608_5732_2_ni_2022-05-09.tif",
"https://dop20-rgbi.s3.eu-de.cloud-object-storage.appdomain.cloud/326125846/2024-09-21/dop20rgbi_32_612_5846_2_ni_2024-09-21.tif",
"https://dop20-rgbi.s3.eu-de.cloud-object-storage.appdomain.cloud/326045852/2024-09-21/dop20rgbi_32_604_5852_2_ni_2024-09-21.tif")
dsns <- paste0("/vsicurl/", urls) # prefix for virtual file source
# convert list of image paths to gdalraster objects
dss <- dsns |>
map(\(datasourcename) new(GDALRaster, datasourcename, read_only = TRUE))
# list compression types for images
compression <- dss |>
map(\(datasource) datasource$getMetadataItem(band = 0, mdi_name = "COMPRESSION", domain = "IMAGE_STRUCTURE"))
# list image sizes
size <- dsns |>
map(\(datasourcename) utils:::format.object_size(vsi_stat(datasourcename, "size"), "auto"))
# Create a function to measure performance for each datasource and compression
raster_compression <- function(dsns, options) {
results <- list()
for (datasourcename in dsns) {
for (option in options) {
# Start timer
start_time <- Sys.time()
output_file <- file_temp(ext = ".tif")
temp_raster <- createCopy(
src_filename = datasourcename,
dst_filename = output_file,
format = "COG",
options = option$setting
)
# End timer
end_time <- Sys.time()
runtime <- as.numeric(difftime(end_time, start_time, units = "secs"))
# Get file size in MB
data_size_uncompressed <- vsi_stat(datasourcename, "size") / (1024^2)
data_size_compressed <- vsi_stat(output_file, "size") / (1024^2)
# Store results
results[[length(results) + 1]] <- list(
datasource = basename(datasourcename),
settings = option$naming,
uncompressed_mb = data_size_uncompressed,
compressed_mb = data_size_compressed,
runtime_sec = runtime
)
}
}
# Convert list to data frame
results_df <- do.call(rbind, lapply(results, data.frame))
return(results_df)
}
# create list of options
options <- list(
# lossless
list(setting = c("COMPRESS=NONE")),
list(setting = c("COMPRESS=LZW", "PREDICTOR=YES")),
list(setting = c("COMPRESS=LZW", "PREDICTOR=NO")),
list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=YES", "LEVEL=1")),
list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=YES", "LEVEL=6")),
list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=YES", "LEVEL=9")),
list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=NO", "LEVEL=1")),
list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=NO", "LEVEL=6")),
list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=NO", "LEVEL=9")),
list(setting = c("COMPRESS=LZMA", "LEVEL=1")),
list(setting = c("COMPRESS=LZMA", "LEVEL=9")),
list(setting = c("COMPRESS=ZSTD", "PREDICTOR=YES", "LEVEL=1")),
list(setting = c("COMPRESS=ZSTD", "PREDICTOR=YES", "LEVEL=9")),
list(setting = c("COMPRESS=ZSTD", "PREDICTOR=YES", "LEVEL=22")),
list(setting = c("COMPRESS=ZSTD", "PREDICTOR=NO", "LEVEL=1")),
list(setting = c("COMPRESS=ZSTD", "PREDICTOR=NO", "LEVEL=9")),
list(setting = c("COMPRESS=ZSTD", "PREDICTOR=NO", "LEVEL=22")),
list(setting = c("COMPRESS=WEBP", "QUALITY=100")),
list(setting = c("COMPRESS=LERC", "MAX_Z_ERROR=0")),
list(setting = c("COMPRESS=LERC_DEFLATE", "LEVEL=1")),
list(setting = c("COMPRESS=LERC_DEFLATE", "LEVEL=6")),
list(setting = c("COMPRESS=LERC_DEFLATE", "LEVEL=9")),
list(setting = c("COMPRESS=LERC_ZSTD", "LEVEL=1")),
list(setting = c("COMPRESS=LERC_ZSTD", "LEVEL=9")),
list(setting = c("COMPRESS=LERC_ZSTD", "LEVEL=22")),
# lossy overviews
list(setting = c("COMPRESS=WEBP", "QUALITY=100", "OVERVIEW_QUALITY=75")),
# lossy
list(setting = c("COMPRESS=WEBP", "QUALITY=75"))
)
for (i in 1:length(options)) {
options[[i]]$naming <- paste(options[[i]]$setting, collapse = ", ")
}
# apply function on list
benchmark_results <- raster_compression(dsns, options)
@wiesehahn
Copy link
Author

wiesehahn commented Mar 17, 2025

Results for Orthophotos

# pak::pak("USDAForestService/gdalraster")
library(gdalraster)
library(purrr)
library(fs)

# Sset gdal configurations (for reproducability?)
set_config_option("GDAL_NUM_THREADS", "16")
set_config_option("GDAL_CACHEMAX", "4000")
set_config_option("OVERVIEWS", "IGNORE_EXISTING")

# get orthoimages from LGLN open data
urls <- c(
  "https://dop20-rgbi.s3.eu-de.cloud-object-storage.appdomain.cloud/324905842/2024-09-05/dop20rgbi_32_490_5842_2_ni_2024-09-05.tif",
  "https://dop20-rgbi.s3.eu-de.cloud-object-storage.appdomain.cloud/326085740/2022-05-09/dop20rgbi_32_608_5740_2_ni_2022-05-09.tif",
  "https://dop20-rgbi.s3.eu-de.cloud-object-storage.appdomain.cloud/326085732/2022-05-09/dop20rgbi_32_608_5732_2_ni_2022-05-09.tif",
  "https://dop20-rgbi.s3.eu-de.cloud-object-storage.appdomain.cloud/326125846/2024-09-21/dop20rgbi_32_612_5846_2_ni_2024-09-21.tif",
  "https://dop20-rgbi.s3.eu-de.cloud-object-storage.appdomain.cloud/326045852/2024-09-21/dop20rgbi_32_604_5852_2_ni_2024-09-21.tif")

dsns <- paste0("/vsicurl/", urls)  # prefix for virtual file source



# Create a function to measure performance for each datasource and compression
raster_compression <- function(dsns, options) {
  results <- list()
  
  for (datasourcename in dsns) {
    for (option in options) {

      output_file <- file_temp(ext = ".tif")
      
      write_ds <- function(){
        createCopy(
          src_filename = datasourcename,
          dst_filename = output_file,
          format = "COG",
          options = option$setting
        )
      } 
      
      # Get write time
      writetime <- system.time(write_ds())["elapsed"]
      
      # Get file size in MB
      data_size_uncompressed <- vsi_stat(datasourcename, "size") / (1024^2) 
      data_size_compressed <- vsi_stat(output_file, "size") / (1024^2) 
      
      # Get read time
      img <- new(GDALRaster, output_file)
      readtime <- system.time(read_ds(img))["elapsed"]
      
      # Store results
      results[[length(results) + 1]] <- list(
        datasource = basename(datasourcename),
        settings = option$naming,
        uncompressed_mb = data_size_uncompressed,
        compressed_mb = data_size_compressed,
        writetime_sec = writetime,
        readtime_sec = readtime
      )
    }
  }
  
  # Convert list to data frame
  results_df <- do.call(rbind, lapply(results, data.frame))
  return(results_df)
}


# create list of options
options <- list(
  # lossless
  list(setting = c("COMPRESS=NONE")),
  list(setting = c("COMPRESS=LZW", "PREDICTOR=YES")),
  list(setting = c("COMPRESS=LZW", "PREDICTOR=NO")),
  list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=YES", "LEVEL=1")),
  list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=YES", "LEVEL=6")),
  list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=YES", "LEVEL=9")),
  list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=NO", "LEVEL=1")),
  list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=NO", "LEVEL=6")),
  list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=NO", "LEVEL=9")),
  list(setting = c("COMPRESS=LZMA", "LEVEL=1")),
  list(setting = c("COMPRESS=LZMA", "LEVEL=9")),
  list(setting = c("COMPRESS=ZSTD", "PREDICTOR=YES", "LEVEL=1")),
  list(setting = c("COMPRESS=ZSTD", "PREDICTOR=YES", "LEVEL=9")),
  list(setting = c("COMPRESS=ZSTD", "PREDICTOR=YES", "LEVEL=22")),
  list(setting = c("COMPRESS=ZSTD", "PREDICTOR=NO", "LEVEL=1")),
  list(setting = c("COMPRESS=ZSTD", "PREDICTOR=NO", "LEVEL=9")),
  list(setting = c("COMPRESS=ZSTD", "PREDICTOR=NO", "LEVEL=22")),
  list(setting = c("COMPRESS=WEBP", "QUALITY=100")),
  list(setting = c("COMPRESS=LERC", "MAX_Z_ERROR=0")),
  list(setting = c("COMPRESS=LERC_DEFLATE", "LEVEL=1")),
  list(setting = c("COMPRESS=LERC_DEFLATE", "LEVEL=6")),
  list(setting = c("COMPRESS=LERC_DEFLATE", "LEVEL=9")),
  list(setting = c("COMPRESS=LERC_ZSTD", "LEVEL=1")),
  list(setting = c("COMPRESS=LERC_ZSTD", "LEVEL=9")),
  list(setting = c("COMPRESS=LERC_ZSTD", "LEVEL=22")),
  # lossy overviews
  list(setting = c("COMPRESS=WEBP", "QUALITY=100", "OVERVIEW_QUALITY=75")),
  # lossy
  list(setting = c("COMPRESS=WEBP", "QUALITY=75"))
)

for (i in 1:length(options)) {
  options[[i]]$naming <- paste(options[[i]]$setting, collapse = ", ")
}


# apply function on list
benchmark_results <- raster_compression(dsns, options)



# plot
library(gt)
library(dplyr)

benchmark_results |> 
  mutate(relative_size = compressed_mb / uncompressed_mb) |> 
  group_by(settings) |> 
  summarise(across(c(compressed_mb, relative_size, writetime_sec, readtime_sec), 
                   list(mean = mean, sd = sd),
                   .names = "{.col}_{.fn}")) |> 
  arrange(desc(relative_size_mean)) |>
  # Create the gt table
  gt() |> 
  fmt_number(columns = ends_with("_mean"), decimals = 2) |> 
  fmt_number(columns = ends_with("_sd"), decimals = 2) |>
  fmt_number(
    columns = contains("size"),
    scale_by = 100,
    decimals = 1
  ) |>
  cols_merge_uncert(
    col_val = compressed_mb_mean,
    col_uncert = compressed_mb_sd
  ) |>
  cols_merge_uncert(
    col_val = relative_size_mean,
    col_uncert = relative_size_sd
  )  |>
  cols_merge_uncert(
    col_val = readtime_sec_mean,
    col_uncert = readtime_sec_sd
  )  |>
  cols_merge_uncert(
    col_val = writetime_sec_mean,
    col_uncert = writetime_sec_sd
  )   |>
  cols_label(
    settings = "Settings",
    compressed_mb_mean = "Absolute Size (MB)",
    relative_size_mean = "Relative Size (%)",
    writetime_sec_mean = "Runtime Write (s)",
    readtime_sec_mean = "Runtime Read (s)"
  ) |>
  tab_header(
    title = "Comparison of Compression Performances",
    subtitle = "Size and processing time (mean ± standard deviation) depending on compression settings",
  ) |>
  tab_footnote(
    footnote = "WEBP compression with QUALITY < 100 implies lossy compression, all other tested algorithms are lossless compression",
    locations = cells_column_labels(columns = settings)
  ) |> 
  tab_source_note(source_note = md(
    "Source: Original images were five uncompressed Orthophotos (2x2km) from LGLN. Original images were stored as uncompressed COGs (size: 539 ± 0 MB)."
  ))
Comparison of Compression Performances
Size and processing time (mean ± standard deviation) depending on compression settings
Settings1 Absolute Size (MB) Relative Size (%) Runtime Write (s) Runtime Read (s)
COMPRESS=NONE 539.01 ± 0.00 100.0 ± 0.0 64.25 ± 2.24 4.70 ± 0.20
COMPRESS=LZW, PREDICTOR=NO 512.31 ± 23.92 95.0 ± 4.4 63.06 ± 2.74 4.73 ± 0.22
COMPRESS=ZSTD, PREDICTOR=NO, LEVEL=1 429.05 ± 5.72 79.6 ± 1.1 62.48 ± 3.06 4.64 ± 0.18
COMPRESS=ZSTD, PREDICTOR=NO, LEVEL=9 408.49 ± 20.43 75.8 ± 3.8 62.25 ± 2.89 4.71 ± 0.23
COMPRESS=DEFLATE, PREDICTOR=NO, LEVEL=1 407.46 ± 17.74 75.6 ± 3.3 63.03 ± 4.32 4.57 ± 0.16
COMPRESS=DEFLATE, PREDICTOR=NO, LEVEL=6 401.81 ± 20.18 74.5 ± 3.7 62.45 ± 3.79 4.62 ± 0.19
COMPRESS=DEFLATE, PREDICTOR=NO, LEVEL=9 390.00 ± 22.05 72.4 ± 4.1 63.63 ± 2.66 4.53 ± 0.18
COMPRESS=ZSTD, PREDICTOR=NO, LEVEL=22 377.84 ± 25.58 70.1 ± 4.7 76.64 ± 2.12 4.79 ± 0.17
COMPRESS=LZW, PREDICTOR=YES 367.70 ± 26.28 68.2 ± 4.9 63.26 ± 4.43 4.70 ± 0.15
COMPRESS=LERC, MAX_Z_ERROR=0 334.84 ± 26.37 62.1 ± 4.9 63.50 ± 5.60 4.84 ± 0.22
COMPRESS=LZMA, LEVEL=1 333.96 ± 23.96 62.0 ± 4.4 64.04 ± 4.61 6.01 ± 0.18
COMPRESS=ZSTD, PREDICTOR=YES, LEVEL=1 332.79 ± 26.91 61.7 ± 5.0 62.27 ± 4.57 4.54 ± 0.21
COMPRESS=LERC_ZSTD, LEVEL=1 332.39 ± 26.72 61.7 ± 5.0 62.37 ± 3.63 4.77 ± 0.30
COMPRESS=LERC_ZSTD, LEVEL=9 332.30 ± 26.73 61.6 ± 5.0 62.16 ± 4.37 4.79 ± 0.33
COMPRESS=LERC_ZSTD, LEVEL=22 331.67 ± 27.09 61.5 ± 5.0 62.69 ± 3.00 4.82 ± 0.16
COMPRESS=LERC_DEFLATE, LEVEL=1 331.51 ± 27.19 61.5 ± 5.0 62.67 ± 2.62 4.86 ± 0.22
COMPRESS=LERC_DEFLATE, LEVEL=6 331.50 ± 27.23 61.5 ± 5.1 62.54 ± 4.49 4.88 ± 0.17
COMPRESS=LERC_DEFLATE, LEVEL=9 331.43 ± 27.25 61.5 ± 5.1 63.07 ± 2.74 4.80 ± 0.22
COMPRESS=DEFLATE, PREDICTOR=YES, LEVEL=1 321.08 ± 20.25 59.6 ± 3.8 64.22 ± 4.22 4.62 ± 0.20
COMPRESS=DEFLATE, PREDICTOR=YES, LEVEL=6 319.74 ± 20.60 59.3 ± 3.8 66.13 ± 5.44 4.65 ± 0.20
COMPRESS=ZSTD, PREDICTOR=YES, LEVEL=9 317.56 ± 24.16 58.9 ± 4.5 63.03 ± 3.84 4.70 ± 0.07
COMPRESS=LZMA, LEVEL=9 316.09 ± 22.77 58.6 ± 4.2 66.39 ± 3.72 6.08 ± 0.10
COMPRESS=DEFLATE, PREDICTOR=YES, LEVEL=9 313.28 ± 19.18 58.1 ± 3.6 65.60 ± 4.87 4.63 ± 0.19
COMPRESS=ZSTD, PREDICTOR=YES, LEVEL=22 300.32 ± 15.05 55.7 ± 2.8 80.12 ± 3.24 4.79 ± 0.16
COMPRESS=WEBP, QUALITY=100 223.93 ± 16.76 41.5 ± 3.1 67.12 ± 4.37 4.84 ± 0.26
COMPRESS=WEBP, QUALITY=100, OVERVIEW_QUALITY=75 212.20 ± 15.42 39.4 ± 2.9 67.05 ± 3.73 4.81 ± 0.18
COMPRESS=WEBP, QUALITY=75 89.34 ± 12.27 16.6 ± 2.3 64.94 ± 3.83 4.75 ± 0.13
Source: Original images were five uncompressed Orthophotos (2x2km) from LGLN. Original images were stored as uncompressed COGs (size: 539 ± 0 MB).
1 WEBP compression with QUALITY < 100 implies lossy compression, all other tested algorithms are lossless compression

@wiesehahn
Copy link
Author

wiesehahn commented Mar 18, 2025

Results for Digital Terrain Models

# pak::pak("USDAForestService/gdalraster")
library(gdalraster)
library(purrr)
library(fs)

# Sset gdal configurations (for reproducability?)
set_config_option("GDAL_NUM_THREADS", "16")
set_config_option("GDAL_CACHEMAX", "4000")
set_config_option("OVERVIEWS", "IGNORE_EXISTING")

urls <- c(
  "https://dgm.s3.eu-de.cloud-object-storage.appdomain.cloud/325735712/2016-04-04/dgm1_32_573_5712_1_ni_2016.tif",
  "https://dgm.s3.eu-de.cloud-object-storage.appdomain.cloud/326025743/2018-04-09/dgm1_32_602_5743_1_ni_2018.tif",
  "https://dgm.s3.eu-de.cloud-object-storage.appdomain.cloud/326065787/2019-02-27/dgm1_32_606_5787_1_ni_2019.tif",
  "https://dgm.s3.eu-de.cloud-object-storage.appdomain.cloud/326065850/2020-04-07/dgm1_32_606_5850_1_ni_2020.tif",
  "https://dgm.s3.eu-de.cloud-object-storage.appdomain.cloud/325045930/2017-02-15/dgm1_32_504_5930_1_ni_2017.tif")

dsns <- paste0("/vsicurl/", urls)  # prefix for virtual file source



# Create a function to measure performance for each datasource and compression
raster_compression <- function(dsns, options) {
  results <- list()
  
  for (datasourcename in dsns) {
    for (option in options) {

      output_file <- file_temp(ext = ".tif")
      
      write_ds <- function(){
        createCopy(
          src_filename = datasourcename,
          dst_filename = output_file,
          format = "COG",
          options = option$setting
        )
      } 
      
      # Get write time
      writetime <- system.time(write_ds())["elapsed"]
      
      # Get file size in MB
      data_size_uncompressed <- vsi_stat(datasourcename, "size") / (1024^2) 
      data_size_compressed <- vsi_stat(output_file, "size") / (1024^2) 
      
      # Get read time
      img <- new(GDALRaster, output_file)
      readtime <- system.time(read_ds(img))["elapsed"]
      
      # Store results
      results[[length(results) + 1]] <- list(
        datasource = basename(datasourcename),
        settings = option$naming,
        uncompressed_mb = data_size_uncompressed,
        compressed_mb = data_size_compressed,
        writetime_sec = writetime,
        readtime_sec = readtime
      )
    }
  }
  
  # Convert list to data frame
  results_df <- do.call(rbind, lapply(results, data.frame))
  return(results_df)
}


# create list of options
options <- list(
  # lossless
  list(setting = c("COMPRESS=NONE")),
  list(setting = c("COMPRESS=LZW", "PREDICTOR=YES")),
  list(setting = c("COMPRESS=LZW", "PREDICTOR=NO")),
  list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=YES", "LEVEL=1")),
  list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=YES", "LEVEL=6")),
  list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=YES", "LEVEL=9")),
  list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=NO", "LEVEL=1")),
  list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=NO", "LEVEL=6")),
  list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=NO", "LEVEL=9")),
  list(setting = c("COMPRESS=LZMA", "LEVEL=1")),
  list(setting = c("COMPRESS=LZMA", "LEVEL=9")),
  list(setting = c("COMPRESS=ZSTD", "PREDICTOR=YES", "LEVEL=1")),
  list(setting = c("COMPRESS=ZSTD", "PREDICTOR=YES", "LEVEL=9")),
  list(setting = c("COMPRESS=ZSTD", "PREDICTOR=YES", "LEVEL=22")),
  list(setting = c("COMPRESS=ZSTD", "PREDICTOR=NO", "LEVEL=1")),
  list(setting = c("COMPRESS=ZSTD", "PREDICTOR=NO", "LEVEL=9")),
  list(setting = c("COMPRESS=ZSTD", "PREDICTOR=NO", "LEVEL=22")),
  list(setting = c("COMPRESS=LERC", "MAX_Z_ERROR=0")),
  list(setting = c("COMPRESS=LERC_DEFLATE", "LEVEL=1")),
  list(setting = c("COMPRESS=LERC_DEFLATE", "LEVEL=6")),
  list(setting = c("COMPRESS=LERC_DEFLATE", "LEVEL=9")),
  list(setting = c("COMPRESS=LERC_ZSTD", "LEVEL=1")),
  list(setting = c("COMPRESS=LERC_ZSTD", "LEVEL=9")),
  list(setting = c("COMPRESS=LERC_ZSTD", "LEVEL=22"))
)

for (i in 1:length(options)) {
  options[[i]]$naming <- paste(options[[i]]$setting, collapse = ", ")
}


# apply function on list
benchmark_results <- raster_compression(dsns, options)



# plot
library(gt)
library(dplyr)

benchmark_results |> 
  mutate(relative_size = compressed_mb / uncompressed_mb) |> 
  group_by(settings) |> 
  summarise(across(c(compressed_mb, relative_size, writetime_sec, readtime_sec), 
                   list(mean = mean, sd = sd),
                   .names = "{.col}_{.fn}")) |> 
  arrange(desc(relative_size_mean)) |>
  # Create the gt table
  gt() |> 
  fmt_number(columns = ends_with("_mean"), decimals = 2) |> 
  fmt_number(columns = ends_with("_sd"), decimals = 2) |>
  fmt_number(
    columns = contains("size"),
    scale_by = 100,
    decimals = 1
  ) |>
  cols_merge_uncert(
    col_val = compressed_mb_mean,
    col_uncert = compressed_mb_sd
  ) |>
  cols_merge_uncert(
    col_val = relative_size_mean,
    col_uncert = relative_size_sd
  )  |>
  cols_merge_uncert(
    col_val = readtime_sec_mean,
    col_uncert = readtime_sec_sd
  )  |>
  cols_merge_uncert(
    col_val = writetime_sec_mean,
    col_uncert = writetime_sec_sd
  )   |>
    cols_label(
    settings = "Settings",
    compressed_mb_mean = "Absolute Size (MB)",
    relative_size_mean = "Relative Size (%)",
    writetime_sec_mean = "Runtime Write (s)",
    readtime_sec_mean = "Runtime Read (s)"
  ) |>
  tab_header(
    title = "Comparison of Compression Performances",
    subtitle = "Size and processing time (mean ± standard deviation) depending on compression settings",
  ) |>
  tab_footnote(
    footnote = "All tested algorithms are lossless compression, WEBP which performes best with Orthoimages is not possible with 1-band data.",
    locations = cells_column_labels(columns = settings)
  ) |> 
  tab_source_note(source_note = md(
    "Source: Original images were five DTM tiles (1x1km) from LGLN. Original images were stored as LZW compressed COGs (size: 3.96 ± 0.31 MB)."
  ))
Comparison of Compression Performances
Size and processing time (mean ± standard deviation) depending on compression settings
Settings1 Absolute Size (MB) Relative Size (%) Runtime Write (s) Runtime Read (s)
COMPRESS=NONE 5.00 ± 0.00 126.9 ± 9.6 1.32 ± 0.33 0.02 ± 0.01
COMPRESS=LERC, MAX_Z_ERROR=0 4.80 ± 0.00 121.8 ± 9.2 0.06 ± 0.01 0.03 ± 0.01
COMPRESS=LZW, PREDICTOR=NO 3.96 ± 0.31 100.0 ± 0.0 0.07 ± 0.01 0.02 ± 0.01
COMPRESS=LERC_ZSTD, LEVEL=1 3.76 ± 0.18 95.2 ± 6.3 0.06 ± 0.01 0.02 ± 0.01
COMPRESS=ZSTD, PREDICTOR=NO, LEVEL=1 3.73 ± 0.18 94.5 ± 6.0 0.05 ± 0.01 0.02 ± 0.01
COMPRESS=LZW, PREDICTOR=YES 3.37 ± 0.30 85.6 ± 11.8 0.07 ± 0.01 0.03 ± 0.01
COMPRESS=DEFLATE, PREDICTOR=NO, LEVEL=1 3.13 ± 0.35 78.8 ± 2.9 0.06 ± 0.01 0.03 ± 0.01
COMPRESS=LERC_DEFLATE, LEVEL=1 3.09 ± 0.37 77.9 ± 3.2 0.06 ± 0.01 0.02 ± 0.01
COMPRESS=DEFLATE, PREDICTOR=NO, LEVEL=6 3.02 ± 0.32 76.2 ± 2.4 0.07 ± 0.01 0.02 ± 0.01
COMPRESS=LERC_DEFLATE, LEVEL=6 2.96 ± 0.32 74.6 ± 2.3 0.08 ± 0.00 0.02 ± 0.01
COMPRESS=LERC_ZSTD, LEVEL=9 2.92 ± 0.24 73.8 ± 2.0 0.12 ± 0.01 0.02 ± 0.01
COMPRESS=ZSTD, PREDICTOR=NO, LEVEL=9 2.89 ± 0.23 72.9 ± 1.8 0.10 ± 0.01 0.02 ± 0.01
COMPRESS=DEFLATE, PREDICTOR=NO, LEVEL=9 2.86 ± 0.30 72.1 ± 2.6 0.13 ± 0.01 0.03 ± 0.01
COMPRESS=LERC_DEFLATE, LEVEL=9 2.81 ± 0.30 70.9 ± 2.2 0.14 ± 0.01 0.02 ± 0.01
COMPRESS=ZSTD, PREDICTOR=NO, LEVEL=22 2.74 ± 0.25 69.1 ± 1.8 1.04 ± 0.01 0.02 ± 0.01
COMPRESS=LERC_ZSTD, LEVEL=22 2.71 ± 0.26 68.5 ± 1.8 0.42 ± 0.02 0.02 ± 0.01
COMPRESS=DEFLATE, PREDICTOR=YES, LEVEL=1 2.60 ± 0.24 66.0 ± 8.9 0.06 ± 0.02 0.02 ± 0.01
COMPRESS=ZSTD, PREDICTOR=YES, LEVEL=1 2.56 ± 0.25 65.0 ± 9.1 0.06 ± 0.01 0.02 ± 0.01
COMPRESS=DEFLATE, PREDICTOR=YES, LEVEL=6 2.56 ± 0.24 65.0 ± 8.9 0.08 ± 0.00 0.02 ± 0.01
COMPRESS=DEFLATE, PREDICTOR=YES, LEVEL=9 2.53 ± 0.24 64.2 ± 8.9 0.22 ± 0.02 0.02 ± 0.01
COMPRESS=ZSTD, PREDICTOR=YES, LEVEL=9 2.51 ± 0.23 63.7 ± 8.5 0.10 ± 0.01 0.03 ± 0.01
COMPRESS=ZSTD, PREDICTOR=YES, LEVEL=22 2.48 ± 0.22 63.0 ± 8.5 1.41 ± 0.10 0.03 ± 0.01
COMPRESS=LZMA, LEVEL=1 2.20 ± 0.09 55.9 ± 3.7 0.29 ± 0.02 0.05 ± 0.01
COMPRESS=LZMA, LEVEL=9 1.98 ± 0.13 50.0 ± 1.8 0.72 ± 0.02 0.04 ± 0.01
Source: Original images were five DTM tiles (1x1km) from LGLN. Original images were stored as LZW compressed COGs (size: 3.96 ± 0.31 MB).
1 All tested algorithms are lossless compression, WEBP which performes best with Orthoimages is not possible with 1-band data.

@wiesehahn
Copy link
Author

wiesehahn commented Mar 21, 2025

Results for Orthophotos

# pak::pak("USDAForestService/gdalraster")
library(gdalraster)
library(purrr)
library(fs)

# Set gdal configurations (for reproducability?)
# Multi-threaded (comment GDAL_NUM_THREADS to use default single threaded
set_config_option("GDAL_NUM_THREADS", "32") 
set_config_option("GDAL_CACHEMAX", "8000")
set_config_option("OVERVIEWS", "IGNORE_EXISTING")

# get orthoimages from LGLN open data
urls <- c(
  "https://dop20-rgbi.s3.eu-de.cloud-object-storage.appdomain.cloud/324905842/2024-09-05/dop20rgbi_32_490_5842_2_ni_2024-09-05.tif",
  "https://dop20-rgbi.s3.eu-de.cloud-object-storage.appdomain.cloud/326085740/2022-05-09/dop20rgbi_32_608_5740_2_ni_2022-05-09.tif",
  "https://dop20-rgbi.s3.eu-de.cloud-object-storage.appdomain.cloud/326085732/2022-05-09/dop20rgbi_32_608_5732_2_ni_2022-05-09.tif",
  "https://dop20-rgbi.s3.eu-de.cloud-object-storage.appdomain.cloud/326125846/2024-09-21/dop20rgbi_32_612_5846_2_ni_2024-09-21.tif",
  "https://dop20-rgbi.s3.eu-de.cloud-object-storage.appdomain.cloud/326045852/2024-09-21/dop20rgbi_32_604_5852_2_ni_2024-09-21.tif")

dsns <- paste0("/vsicurl/", urls)  # prefix for virtual file source



# Create a function to measure performance for each datasource and compression
raster_compression <- function(dsns, options) {
  results <- list()
  
  for (datasourcename in dsns) {
    for (option in options) {
      
      output_file <- file_temp(ext = ".tif")
      
      write_ds <- function(){
        createCopy(
          src_filename = datasourcename,
          dst_filename = output_file,
          format = "COG",
          options = option$setting
        )
      } 
      
      # Get write time
      writetime <- system.time(write_ds())["elapsed"]
      
      # Get file size in MB
      data_size_uncompressed <- vsi_stat(datasourcename, "size") / (1024^2) 
      data_size_compressed <- vsi_stat(output_file, "size") / (1024^2) 
      
      # Get read time
      img <- new(GDALRaster, output_file)
      readtime <- system.time(read_ds(img))["elapsed"]
      
      # Store results
      results[[length(results) + 1]] <- list(
        datasource = basename(datasourcename),
        settings = option$naming,
        uncompressed_mb = data_size_uncompressed,
        compressed_mb = data_size_compressed,
        writetime_sec = writetime,
        readtime_sec = readtime
      )
    }
  }
  
  # Convert list to data frame
  results_df <- do.call(rbind, lapply(results, data.frame))
  return(results_df)
}


# create list of options
options <- list(
  # lossless
  list(setting = c("COMPRESS=NONE")),
  list(setting = c("COMPRESS=LZW", "PREDICTOR=YES")),
  list(setting = c("COMPRESS=LZW", "PREDICTOR=NO")),
  list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=YES", "LEVEL=1")),
  list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=YES", "LEVEL=6")),
  list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=YES", "LEVEL=9")),
  list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=NO", "LEVEL=1")),
  list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=NO", "LEVEL=6")),
  list(setting = c("COMPRESS=DEFLATE", "PREDICTOR=NO", "LEVEL=9")),
  list(setting = c("COMPRESS=LZMA", "LEVEL=1")),
  list(setting = c("COMPRESS=LZMA", "LEVEL=9")),
  list(setting = c("COMPRESS=ZSTD", "PREDICTOR=YES", "LEVEL=1")),
  list(setting = c("COMPRESS=ZSTD", "PREDICTOR=YES", "LEVEL=9")),
  list(setting = c("COMPRESS=ZSTD", "PREDICTOR=YES", "LEVEL=22")),
  list(setting = c("COMPRESS=ZSTD", "PREDICTOR=NO", "LEVEL=1")),
  list(setting = c("COMPRESS=ZSTD", "PREDICTOR=NO", "LEVEL=9")),
  list(setting = c("COMPRESS=ZSTD", "PREDICTOR=NO", "LEVEL=22")),
  list(setting = c("COMPRESS=WEBP", "QUALITY=100")),
  list(setting = c("COMPRESS=LERC", "MAX_Z_ERROR=0")),
  list(setting = c("COMPRESS=LERC_DEFLATE", "LEVEL=1")),
  list(setting = c("COMPRESS=LERC_DEFLATE", "LEVEL=6")),
  list(setting = c("COMPRESS=LERC_DEFLATE", "LEVEL=9")),
  list(setting = c("COMPRESS=LERC_ZSTD", "LEVEL=1")),
  list(setting = c("COMPRESS=LERC_ZSTD", "LEVEL=9")),
  list(setting = c("COMPRESS=LERC_ZSTD", "LEVEL=22")),
  # lossy overviews
  list(setting = c("COMPRESS=WEBP", "QUALITY=100", "OVERVIEW_QUALITY=75")),
  # lossy
  list(setting = c("COMPRESS=WEBP", "QUALITY=75"))
)

for (i in 1:length(options)) {
  options[[i]]$naming <- paste(options[[i]]$setting, collapse = ", ")
}


# apply function on list
benchmark_results <- raster_compression(dsns, options)



# plot
library(gt)
library(dplyr)

benchmark_results |> 
  mutate(relative_size = compressed_mb / uncompressed_mb,
         relative_write_speed = uncompressed_mb / writetime_sec,
         relative_read_speed = compressed_mb / readtime_sec) |> 
  group_by(settings) |> 
  summarise(across(c(compressed_mb, relative_size, writetime_sec, readtime_sec, relative_write_speed, relative_read_speed), 
                   list(mean = mean, sd = sd),
                   .names = "{.col}_{.fn}")) |> 
  arrange(desc(relative_size_mean)) |>
  # Create the gt table
  gt() |> 
  fmt_number(columns = ends_with(c("_mean", "_sd")), decimals = 0) |> 
  fmt_number(columns = contains(c("time", "speed")), decimals = 1) |>
  fmt_number(
    columns = contains("size"),
    scale_by = 100,
    decimals = 0
  ) |>
  cols_merge_uncert(
    col_val = compressed_mb_mean,
    col_uncert = compressed_mb_sd
  ) |>
  cols_merge_uncert(
    col_val = relative_size_mean,
    col_uncert = relative_size_sd
  )  |>
  cols_merge_uncert(
    col_val = readtime_sec_mean,
    col_uncert = readtime_sec_sd
  )  |>
  cols_merge_uncert(
    col_val = writetime_sec_mean,
    col_uncert = writetime_sec_sd
  )   |>
  cols_merge_uncert(
    col_val = relative_write_speed_mean,
    col_uncert = relative_write_speed_sd
  )  |>
  cols_merge_uncert(
    col_val = relative_read_speed_mean,
    col_uncert = relative_read_speed_sd
  ) |>
  cols_label(
    settings = "Settings",
    compressed_mb_mean = "Absolute (MB)",
    relative_size_mean = "Relative (%)",
    writetime_sec_mean = "Write (s)",
    readtime_sec_mean = "Read (s)",
    relative_write_speed_mean = "Write (MB/s)",
    relative_read_speed_mean = "Read (MB/s)"
  )  |>
  tab_spanner(
    label = "Size",
    columns = c(compressed_mb_mean, relative_size_mean)
  )  |>
  tab_spanner(
    label = "Time",
    columns = c(writetime_sec_mean, readtime_sec_mean)
  )  |>
  tab_spanner(
    label = "Speed",
    columns = c(relative_write_speed_mean, relative_read_speed_mean)
  ) |>
  tab_header(
    title = "Comparison of Compression Performances",
    subtitle = "Size and processing time (mean ± standard deviation) depending on compression settings",
  ) |>
  tab_footnote(
    footnote = "WEBP compression with QUALITY < 100 implies lossy compression, all other tested algorithms are lossless compression",
    locations = cells_column_labels(columns = settings)
  ) |> 
  tab_source_note(source_note = md(
    "Source: Original images were five uncompressed Orthophotos (2x2km) from LGLN. Original images were stored as uncompressed COGs (size: 539 ± 0 MB)."
  ))

Single-threaded (GDAL_NUM_THREADS=DEFAULT, GDAL_CacheMAX=4000)

Comparison of Compression Performances
Size and processing time (mean ± standard deviation) depending on compression settings
Settings1
Size
Time
Speed
Absolute (MB) Relative (%) Write (s) Read (s) Write (MB/s) Read (MB/s)
COMPRESS=NONE 539 ± 0 100 ± 0 62.2 ± 1.7 4.5 ± 0.3 8.7 ± 0.2 120.3 ± 7.4
COMPRESS=LZW, PREDICTOR=NO 512 ± 24 95 ± 4 64.7 ± 2.5 5.8 ± 0.2 8.3 ± 0.3 88.6 ± 6.9
COMPRESS=ZSTD, PREDICTOR=NO, LEVEL=1 429 ± 6 80 ± 1 62.8 ± 3.7 4.7 ± 0.2 8.6 ± 0.5 91.7 ± 4.1
COMPRESS=ZSTD, PREDICTOR=NO, LEVEL=9 408 ± 20 76 ± 4 69.0 ± 4.2 4.8 ± 0.2 7.8 ± 0.5 85.8 ± 8.0
COMPRESS=DEFLATE, PREDICTOR=NO, LEVEL=1 407 ± 18 76 ± 3 64.8 ± 2.6 5.1 ± 0.2 8.3 ± 0.4 79.5 ± 7.0
COMPRESS=DEFLATE, PREDICTOR=NO, LEVEL=6 402 ± 20 75 ± 4 67.4 ± 2.8 5.2 ± 0.3 8.0 ± 0.3 77.5 ± 7.5
COMPRESS=DEFLATE, PREDICTOR=NO, LEVEL=9 390 ± 22 72 ± 4 80.7 ± 3.1 5.2 ± 0.2 6.7 ± 0.3 74.6 ± 7.1
COMPRESS=ZSTD, PREDICTOR=NO, LEVEL=22 378 ± 26 70 ± 5 278.4 ± 3.3 5.0 ± 0.2 1.9 ± 0.0 75.7 ± 8.0
COMPRESS=LZW, PREDICTOR=YES 368 ± 26 68 ± 5 65.9 ± 2.3 5.7 ± 0.2 8.2 ± 0.3 64.3 ± 6.4
COMPRESS=LERC, MAX_Z_ERROR=0 335 ± 26 62 ± 5 67.2 ± 4.9 6.4 ± 0.3 8.1 ± 0.6 52.4 ± 6.4
COMPRESS=LZMA, LEVEL=1 334 ± 24 62 ± 4 119.7 ± 4.5 17.0 ± 0.4 4.5 ± 0.2 19.6 ± 1.1
COMPRESS=ZSTD, PREDICTOR=YES, LEVEL=1 333 ± 27 62 ± 5 62.7 ± 3.0 4.7 ± 0.2 8.6 ± 0.4 71.3 ± 8.2
COMPRESS=LERC_ZSTD, LEVEL=1 332 ± 27 62 ± 5 65.4 ± 3.3 6.4 ± 0.3 8.3 ± 0.4 52.2 ± 6.4
COMPRESS=LERC_ZSTD, LEVEL=9 332 ± 27 62 ± 5 68.4 ± 3.8 6.4 ± 0.3 7.9 ± 0.4 52.3 ± 6.3
COMPRESS=LERC_ZSTD, LEVEL=22 332 ± 27 62 ± 5 90.1 ± 3.7 6.5 ± 0.3 6.0 ± 0.2 51.4 ± 6.3
COMPRESS=LERC_DEFLATE, LEVEL=1 332 ± 27 62 ± 5 69.1 ± 6.8 6.6 ± 0.3 7.9 ± 0.7 50.4 ± 6.6
COMPRESS=LERC_DEFLATE, LEVEL=6 331 ± 27 62 ± 5 71.4 ± 6.2 6.6 ± 0.3 7.6 ± 0.6 50.2 ± 6.5
COMPRESS=LERC_DEFLATE, LEVEL=9 331 ± 27 61 ± 5 74.7 ± 3.4 6.7 ± 0.3 7.2 ± 0.3 49.7 ± 6.5
COMPRESS=DEFLATE, PREDICTOR=YES, LEVEL=1 321 ± 20 60 ± 4 63.4 ± 1.7 5.3 ± 0.2 8.5 ± 0.2 60.6 ± 6.3
COMPRESS=DEFLATE, PREDICTOR=YES, LEVEL=6 320 ± 21 59 ± 4 67.9 ± 1.8 5.3 ± 0.2 7.9 ± 0.2 60.9 ± 6.1
COMPRESS=ZSTD, PREDICTOR=YES, LEVEL=9 318 ± 24 59 ± 4 73.1 ± 4.4 4.9 ± 0.2 7.4 ± 0.4 64.6 ± 7.0
COMPRESS=LZMA, LEVEL=9 316 ± 23 59 ± 4 185.6 ± 3.5 17.3 ± 0.7 2.9 ± 0.1 18.3 ± 0.6
COMPRESS=DEFLATE, PREDICTOR=YES, LEVEL=9 313 ± 19 58 ± 4 85.8 ± 2.4 5.3 ± 0.2 6.3 ± 0.2 59.6 ± 5.9
COMPRESS=ZSTD, PREDICTOR=YES, LEVEL=22 300 ± 15 56 ± 3 310.4 ± 4.8 5.1 ± 0.2 1.7 ± 0.0 59.1 ± 4.7
COMPRESS=WEBP, QUALITY=100 224 ± 17 42 ± 3 218.0 ± 16.0 6.3 ± 0.2 2.5 ± 0.2 35.7 ± 3.6
COMPRESS=WEBP, QUALITY=100, OVERVIEW_QUALITY=75 212 ± 15 39 ± 3 222.4 ± 20.9 6.3 ± 0.2 2.4 ± 0.2 33.9 ± 3.4
COMPRESS=WEBP, QUALITY=75 89 ± 12 17 ± 2 105.9 ± 5.2 6.3 ± 0.1 5.1 ± 0.3 14.1 ± 1.9
Source: Original images were five uncompressed Orthophotos (2x2km) from LGLN. Original images were stored as uncompressed COGs (size: 539 ± 0 MB).
1 WEBP compression with QUALITY < 100 implies lossy compression, all other tested algorithms are lossless compression

Multi-threaded (GDAL_NUM_THREADS=32, GDAL_CacheMAX=8000)

Comparison of Compression Performances
Size and processing time (mean ± standard deviation) depending on compression settings
Settings1
Size
Time
Speed
Absolute (MB) Relative (%) Write (s) Read (s) Write (MB/s) Read (MB/s)
COMPRESS=NONE 539 ± 0 100 ± 0 65.0 ± 1.9 4.2 ± 0.2 8.3 ± 0.2 128.8 ± 5.1
COMPRESS=LZW, PREDICTOR=NO 512 ± 24 95 ± 4 61.7 ± 3.5 4.2 ± 0.2 8.8 ± 0.5 122.4 ± 11.4
COMPRESS=ZSTD, PREDICTOR=NO, LEVEL=1 429 ± 6 80 ± 1 61.9 ± 4.4 4.2 ± 0.1 8.7 ± 0.6 101.8 ± 3.1
COMPRESS=ZSTD, PREDICTOR=NO, LEVEL=9 408 ± 20 76 ± 4 63.5 ± 4.9 4.2 ± 0.2 8.5 ± 0.7 97.3 ± 9.4
COMPRESS=DEFLATE, PREDICTOR=NO, LEVEL=1 407 ± 18 76 ± 3 61.3 ± 3.4 4.1 ± 0.2 8.8 ± 0.5 99.5 ± 9.6
COMPRESS=DEFLATE, PREDICTOR=NO, LEVEL=6 402 ± 20 75 ± 4 62.9 ± 5.2 4.1 ± 0.2 8.6 ± 0.7 97.5 ± 9.1
COMPRESS=DEFLATE, PREDICTOR=NO, LEVEL=9 390 ± 22 72 ± 4 62.5 ± 4.2 4.2 ± 0.2 8.7 ± 0.6 93.7 ± 8.4
COMPRESS=ZSTD, PREDICTOR=NO, LEVEL=22 378 ± 26 70 ± 5 75.2 ± 7.8 4.3 ± 0.2 7.2 ± 0.7 87.4 ± 10.3
COMPRESS=LZW, PREDICTOR=YES 368 ± 26 68 ± 5 61.8 ± 2.6 4.2 ± 0.2 8.7 ± 0.4 88.4 ± 10.1
COMPRESS=LERC, MAX_Z_ERROR=0 335 ± 26 62 ± 5 63.0 ± 3.9 4.3 ± 0.2 8.6 ± 0.6 77.7 ± 9.4
COMPRESS=LZMA, LEVEL=1 334 ± 24 62 ± 4 63.3 ± 6.3 5.6 ± 0.1 8.6 ± 0.9 59.8 ± 5.4
COMPRESS=ZSTD, PREDICTOR=YES, LEVEL=1 333 ± 27 62 ± 5 69.7 ± 20.8 4.1 ± 0.2 8.2 ± 1.8 81.3 ± 10.5
COMPRESS=LERC_ZSTD, LEVEL=1 332 ± 27 62 ± 5 61.6 ± 4.6 4.4 ± 0.2 8.8 ± 0.7 76.0 ± 10.0
COMPRESS=LERC_ZSTD, LEVEL=9 332 ± 27 62 ± 5 62.9 ± 3.5 4.4 ± 0.2 8.6 ± 0.5 76.0 ± 9.8
COMPRESS=LERC_ZSTD, LEVEL=22 332 ± 27 62 ± 5 63.9 ± 4.0 4.4 ± 0.2 8.5 ± 0.5 75.8 ± 10.6
COMPRESS=LERC_DEFLATE, LEVEL=1 332 ± 27 62 ± 5 66.1 ± 8.3 4.4 ± 0.2 8.3 ± 1.0 75.2 ± 9.8
COMPRESS=LERC_DEFLATE, LEVEL=6 331 ± 27 62 ± 5 64.6 ± 2.9 4.4 ± 0.2 8.4 ± 0.4 75.2 ± 9.5
COMPRESS=LERC_DEFLATE, LEVEL=9 331 ± 27 61 ± 5 63.0 ± 5.7 4.5 ± 0.1 8.6 ± 0.8 74.5 ± 8.4
COMPRESS=DEFLATE, PREDICTOR=YES, LEVEL=1 321 ± 20 60 ± 4 61.2 ± 3.1 4.1 ± 0.2 8.8 ± 0.4 78.3 ± 9.5
COMPRESS=DEFLATE, PREDICTOR=YES, LEVEL=6 320 ± 21 59 ± 4 62.4 ± 4.3 4.1 ± 0.2 8.7 ± 0.6 77.6 ± 9.6
COMPRESS=ZSTD, PREDICTOR=YES, LEVEL=9 318 ± 24 59 ± 4 61.0 ± 3.7 4.2 ± 0.2 8.9 ± 0.6 76.1 ± 10.0
COMPRESS=LZMA, LEVEL=9 316 ± 23 59 ± 4 71.7 ± 20.5 5.7 ± 0.1 7.9 ± 1.7 55.9 ± 4.6
COMPRESS=DEFLATE, PREDICTOR=YES, LEVEL=9 313 ± 19 58 ± 4 61.9 ± 3.7 4.1 ± 0.2 8.7 ± 0.5 75.9 ± 8.1
COMPRESS=ZSTD, PREDICTOR=YES, LEVEL=22 300 ± 15 56 ± 3 74.2 ± 4.0 4.4 ± 0.1 7.3 ± 0.4 68.9 ± 4.9
COMPRESS=WEBP, QUALITY=100 224 ± 17 42 ± 3 66.9 ± 3.8 4.3 ± 0.2 8.1 ± 0.4 52.5 ± 6.6
COMPRESS=WEBP, QUALITY=100, OVERVIEW_QUALITY=75 212 ± 15 39 ± 3 64.1 ± 5.9 4.3 ± 0.2 8.5 ± 0.8 49.7 ± 6.2
COMPRESS=WEBP, QUALITY=75 89 ± 12 17 ± 2 62.7 ± 3.3 4.4 ± 0.1 8.6 ± 0.5 20.5 ± 3.0
Source: Original images were five uncompressed Orthophotos (2x2km) from LGLN. Original images were stored as uncompressed COGs (size: 539 ± 0 MB).
1 WEBP compression with QUALITY < 100 implies lossy compression, all other tested algorithms are lossless compression

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment