cogger

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 19, 2025 License: Apache-2.0 Imports: 8 Imported by: 3

README

Cogger

Go Reference License

Cogger is a standalone binary and a golang library that reads an internally tiled geotiff (optionally with overviews and masks) and rewrites it as a Cloud Optimized Geotiff (COG). This process being a reshuffling of the original geotiff's bytes, it should run as fast as the underlying disk or network i/o.

Cogger does not do any pixel manipulation on the provided image, it is up to you to provide an input geotiff which can be suitably transformed to a COG, namely:

  • it must be internally tiled
  • it should be compressed with one of the standard supported tiff compression mechanisms
  • it should contain overviews

Installation

Binaries

We publish the cogger binaries for the major platforms/cpus, which you can grab from our releases

From source

The library version of cogger can be used in go code with:

import "github.com/airbusgeo/cogger"

The cogger binary can be installed directly to your $GOPATH/bin with:

go install github.com/airbusgeo/cogger/cmd/cogger@latest

Usage

Binary
With internal overviews
gdal_translate -of GTIFF -co BIGTIFF=YES -co TILED=YES -co COMPRESS=ZSTD -co NUM_THREADS=4 input.file geotif.tif
gdaladdo --config GDAL_NUM_THREADS 4 --config COMPRESS_OVERVIEW ZSTD geotif.tif 2 4 8 16 32
cogger -output mycog.tif geotif.tif
With external overviews
gdal_translate -of GTIFF -co BIGTIFF=YES -co TILED=YES -co COMPRESS=ZSTD -co NUM_THREADS=4 input.file geotif.tif
gdaladdo -ro --config GDAL_NUM_THREADS 4 --config COMPRESS_OVERVIEW ZSTD geotif.tif 2 4 8 16 32 #creates geotif.tif.ovr
cogger -output mycog.tif geotif.tif geotif.tif.ovr
Library

The cogger API consists of a single function:

func Rewrite(out io.Writer, readers ...tiff.ReadAtReadSeeker) error

with the reader allowing random read access to the input file, i.e. implementing

Read(buf []byte) (int,error)
ReatAt(buf []byte, offset int64) (int,error)
Seek(off int64, whence int) (int64,error)

The writer is a plain io.Writer which means that the output cog can be directly streamed to http/cloud storage without having to be stored in an intermediate file.

For an full example of library usage, see the main.go file in cmd/cogger.

Advanced

Cogger is able to assemble a single COG from a main tif file and overviews that have been computed in distinct files. This may be useful as gdaladdo is missing some features to fine tune the options of each individual overview.

gdal_translate -of GTIFF -co BIGTIFF=YES -co TILED=YES -co COMPRESS=ZSTD -co NUM_THREADS=4 input.file geotif.tif
# compute first overview
gdal_translate -of GTIFF -outsize 50% 50% -co BLOCKXSIZE=128 -co TILED=YES -co COMPRESS=ZSTD -co NUM_THREADS=4  geotif.tif ovr.tif.1
# compute second overview
gdal_translate -of GTIFF -outsize 50% 50% -co BLOCKXSIZE=256 -co TILED=YES -co COMPRESS=ZSTD -co NUM_THREADS=4  ovr.tif.1 ovr.tif.2
# compute third overview
gdal_translate -of GTIFF -outsize 50% 50% -co BLOCKXSIZE=512 -co TILED=YES -co COMPRESS=ZSTD -co NUM_THREADS=4  ovr.tif.2 ovr.tif.3
# compute COG from geotif.tif and ovr.tif.* overviews
cogger -output mycog.tif geotif.tif ovr.tif.1 ovr.tif.2 ovr.tif.3

Contributing

Contributions are welcome. Please read the contribution guidelines before submitting fixes or enhancements.

Licensing

Cogger is licensed under the Apache License, Version 2.0. See LICENSE for the full license text.

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Rewrite

func Rewrite(out io.Writer, readers ...tiff.ReadAtReadSeeker) error

Rewrite reshuffles the tiff bytes provided as readers into a COG output to out

Use Config.Rewrite in order to use non-default options

Types

type Config added in v0.1.0

type Config struct {
	//Encoding selects big or little endian tiff encoding. Default: little
	Encoding binary.ByteOrder

	//BigTIFF forces bigtiff creation. Default: false, i.e. only if needed
	BigTIFF bool

	// PlanarInterleaving for separate-plane files.
	// Default: nil resulting in {{0,1,...n}} i.e. interleaved planes
	PlanarInterleaving PlanarInterleaving

	//WithGDALGhostArea inserts gdal specific read optimizations
	WithGDALGhostArea bool
}

func DefaultConfig added in v0.1.0

func DefaultConfig() Config

func (Config) Rewrite added in v0.1.0

func (cfg Config) Rewrite(out io.Writer, readers ...tiff.ReadAtReadSeeker) error

func (Config) RewriteIFDHeader added in v0.1.0

func (cfg Config) RewriteIFDHeader(ifd *IFD, out io.Writer) error

func (Config) RewriteIFDTree added in v0.1.0

func (cfg Config) RewriteIFDTree(ifd *IFD, out io.Writer) error

func (Config) RewriteIFDTreeSplitted added in v0.1.0

func (cfg Config) RewriteIFDTreeSplitted(ifd *IFD, headerOut, tileDataOut io.Writer) error

func (Config) RewriteSplitted added in v0.1.0

func (cfg Config) RewriteSplitted(headerOut, dataOut io.Writer, readers ...tiff.ReadAtReadSeeker) error

type Dag added in v0.1.0

type Dag [][]Node

A DAG can be used further optimize parrallel strip computing, by explicitely referencing which parent strips are needed to be used to downsample the given strip of an overview. For advanced usage; it is simpler to process all images of a pyramid level in parallel before moving down to the next level

type ErrInvalidOption added in v0.1.0

type ErrInvalidOption struct {
	// contains filtered or unexported fields
}

func (ErrInvalidOption) Error added in v0.1.0

func (err ErrInvalidOption) Error() string

type IFD

type IFD struct {
	//Any field added here should also be accounted for in computeStructure and writeIFD
	SubfileType               uint32   `tiff:"field,tag=254"`
	ImageWidth                uint64   `tiff:"field,tag=256"`
	ImageHeight               uint64   `tiff:"field,tag=257"`
	BitsPerSample             []uint16 `tiff:"field,tag=258"`
	Compression               uint16   `tiff:"field,tag=259"`
	PhotometricInterpretation uint16   `tiff:"field,tag=262"`
	DocumentName              string   `tiff:"field,tag=269"`
	SamplesPerPixel           uint16   `tiff:"field,tag=277"`
	PlanarConfiguration       uint16   `tiff:"field,tag=284"`
	Software                  string   `tiff:"field,tag=305"`
	DateTime                  string   `tiff:"field,tag=306"`
	Predictor                 uint16   `tiff:"field,tag=317"`
	Colormap                  []uint16 `tiff:"field,tag=320"`
	TileWidth                 uint16   `tiff:"field,tag=322"`
	TileHeight                uint16   `tiff:"field,tag=323"`
	TileOffsets               []uint64 `tiff:"field,tag=324"`
	TileByteCounts            []uint64 `tiff:"field,tag=325"`
	ExtraSamples              []uint16 `tiff:"field,tag=338"`
	SampleFormat              []uint16 `tiff:"field,tag=339"`
	JPEGTables                []byte   `tiff:"field,tag=347"`

	Copyright              string    `tiff:"field,tag=33432"`
	ModelPixelScaleTag     []float64 `tiff:"field,tag=33550"`
	ModelTiePointTag       []float64 `tiff:"field,tag=33922"`
	ModelTransformationTag []float64 `tiff:"field,tag=34264"`
	GeoKeyDirectoryTag     []uint16  `tiff:"field,tag=34735"`
	GeoDoubleParamsTag     []float64 `tiff:"field,tag=34736"`
	GeoAsciiParamsTag      string    `tiff:"field,tag=34737"`
	GDALMetaData           string    `tiff:"field,tag=42112"`
	NoData                 string    `tiff:"field,tag=42113"`
	LERCParams             []uint32  `tiff:"field,tag=50674"`
	RPCs                   []float64 `tiff:"field,tag=50844"`
	LoadTile               func(idx int, data []byte) error
	// contains filtered or unexported fields
}

func (*IFD) AddMask

func (ifd *IFD) AddMask(msk *IFD) error

AddMask sets msk as a mask for ifd. Must not be called after SetPlanarInterleaving

func (*IFD) AddOverview

func (ifd *IFD) AddOverview(ovr *IFD) error

func (*IFD) NPlanes added in v0.1.0

func (ifd *IFD) NPlanes() int

func (*IFD) NTilesX added in v0.1.0

func (ifd *IFD) NTilesX() int

func (*IFD) NTilesY added in v0.1.0

func (ifd *IFD) NTilesY() int

func (*IFD) SetPlanarInterleaving added in v0.1.0

func (ifd *IFD) SetPlanarInterleaving(pi PlanarInterleaving) error

SetPlanarInterleaving configures a non-default planar interleaving for this ifd. Must be called after AddMask.

func (*IFD) TileFromIdx added in v0.1.0

func (ifd *IFD) TileFromIdx(idx int) (x, y, plane int)

func (*IFD) TileIdx added in v0.1.0

func (ifd *IFD) TileIdx(x, y, plane int) int

type Image added in v0.1.0

type Image struct {
	Width, Height int
	Strips        []Strip
	// contains filtered or unexported fields
}

An Image is a Width*Height rectangle of pixels (i.e. there are no overviews at this level) and its decompostion into Strips that can be processed concurrently

type Node added in v0.1.0

type Node struct {
	// Parents are the indexes of the parent strips that will be used to compose the current strip
	Parents []int
	//ParentOffset is the Y position in the parent image of the top-most parent strip
	ParentOffset int
}

A Node represents a single Strip in the Dag

type PlanarInterleaving added in v0.1.0

type PlanarInterleaving [][]int

PlanarInterleaving describes how the band data should be interleaved for tiffs with more than 1 plane and with PlanarConfiguration=2

This is an advanced usage option that does not modify the actual image data but tweaks the order in which each plane's (i.e. band's) tile is written in the resulting file.

Examples for a 3-band rgb image:

  • [[0,1,2]] will result in tiles written in the order r1,g1,b1,r2,g2,b2...rn,gn,bn. This is the default.
  • [0],[1],[2]] => r1,r2...rn,g1,g2....gn,b1,b2...bn
  • [0],[2],[1]] => r1,r2...rn,b1,b2....bn,g1,g2...gn
  • [0,1],[2]] => r1,g1,r2,g2...rn,gn,b1,b2....bn

Examples for a 3-band rgb image with mask:

  • [[0,1,2,3]] will result in tiles written in the order r1,g1,b1,m1,r2,g2,b2,m2...rn,gn,bn,mn. This is the default.
  • [0],[1],[2],[3]] => r1,r2...rn,g1,g2...gn,b1,b2...bn,m1,m2...mn
  • [0],[3],[2],[1]] => r1,r2...rn,m1,m2...m3,b1,b2...bn,g1,g2...gn
  • [0,1],[2],[3]] => r1,g1,r2,g2...rn,gn,b1,b2....bn,m1m2...mn

For a n-band image, each band index from 0 to n-1 must appear exactly once in the array. If the image also has a mask, the index n must also appear exactly once and represents the mask position.

type Pyramid added in v0.1.0

type Pyramid []Image

The Image at index 0 is the full resolution image, and Pyramid[0]'s Strip's Source properties reference the pixel frame of the input image.

The Images at index >0 are the overviews, who's Strip's sources reference the image at the previous index

func (Pyramid) DAG added in v0.1.0

func (pyr Pyramid) DAG() Dag

Compute the DAG for a Pyramid.

type Strip added in v0.1.0

type Strip struct {
	Width, Height            int
	TopLeftX, TopLeftY       int
	SrcTopLeftX, SrcTopLeftY float64
	SrcWidth, SrcHeight      float64
}

A Strip is the basic building block of an Image, and corresponds to a rectangle of Width*Height pixels who's upper left corner is TopLeftX,TopLeftY. The TopLeftX and TopLeftY properties are informative only and are not needed in order to process an image with this API.

In order to populate the pixels of a given strip, data must be copied or downsampled from a window of a source Image. Depending on the context, the source can either be the original image when creating the full-resolution IFD, or the full-resolution IFD when creating the first overview, or the previous overview when creating another overview. The source window is defined by it's upper left corner (SrcTopLeftX and SrcTopLeftY) and size (SrcWidth and SrcHeight)

In gdal lingo, this strip can be calculated by running

gdal_translate source_dataset.tif strip.tif -srcwin <SrcTopLeftX> <SrcTopLeftY> <SrcWidth> <SrcHeight> -outsize <Width> <Height> -r average

type Stripper added in v0.1.0

type Stripper struct {
	// contains filtered or unexported fields
}

A Stripper holds the information to inform how to split an image and its overviews into strips of roughly similar sizes, allowing these strips to be produced outside of the cogger library itself, and ensuring that the internal tiling of these individaul strips will be compatible with a cog reconstruction.

The usual workflow is to create a Stripper matching the input image's dimensions, rough size of each strip to be processed, and output cog parameters (namely internal tiling size)

For a full example, see the cmd/pcogger utility

Example
// let's imagine we have an input.jp2 file of size 12345x23456 pixels, that we want to convert to a cog
//
// we will do this in parallel using gdal and cogger, using the stripper class to subdivide the task

inputFile := os.Getenv("STRIPPER_INPUT")
if inputFile == "" {
	inputFile = "input.jp2"
}
width, _ := strconv.Atoi(os.Getenv("STRIPPER_WIDTH"))
if width == 0 {
	width = 12345
}
height, _ := strconv.Atoi(os.Getenv("STRIPPER_HEIGHT"))
if height == 0 {
	height = 23456
}

stripper, _ := NewStripper(width, height)

pyr := stripper.Pyramid()

tx, ty := stripper.InternalTileSize()
copts := fmt.Sprintf("-co TILED=YES -co COMPRESS=LZW -co BLOCKXSIZE=%d -co BLOCKYSIZE=%d", tx, ty)

srcStrips := [][]tiff.ReadAtReadSeeker{} //used to accumulate the readers of intermediate strips. the ordering must be identical to Pyramid/Pyramid.Strips

vrt_accum := []string{}
for l := range pyr {
	fmt.Printf("\nbatch %d\n\n", l+1)
	infile := inputFile
	if l > 0 {
		// if we are in an overview level, optionally build a vrt file making a single gdal image from the all the strips created at the previous level
		if len(vrt_accum) > 1 {
			infile = fmt.Sprintf("l_%d.vrt", l-1)
			fmt.Printf("gdalbuildvrt %s %s\n", infile, strings.Join(vrt_accum, " "))
		} else {
			infile = vrt_accum[0]
		}
		vrt_accum = []string{}
	}

	lStrips := []tiff.ReadAtReadSeeker{}
	for s, strip := range pyr[l].Strips {
		stripname := fmt.Sprintf("tmp_%d_%d.tif", l, s)
		vrt_accum = append(vrt_accum, stripname)
		resizeOpts := ""
		if l > 0 {
			resizeOpts = fmt.Sprintf("-outsize %d %d -r average", strip.Width, strip.Height)
		}
		fmt.Printf("gdal_translate %s %s %s %s -srcwin 0 %g %g %g\n", infile, stripname, copts, resizeOpts,
			strip.SrcTopLeftY, strip.SrcWidth, strip.SrcHeight)
		rs, _ := os.Open(stripname)
		lStrips = append(lStrips, rs)
	}
	srcStrips = append(srcStrips, lStrips)
}

outcog, _ := os.Create("stripper-example-output.tif")
//defer os.Remove(outcog.Name())

ifdtree, _ := stripper.AssembleStrips(srcStrips)
_ = ifdtree

DefaultConfig().RewriteIFDTree(ifdtree, outcog)
outcog.Close()

////output: foo

func NewStripper added in v0.1.0

func NewStripper(width, height int, options ...StripperOption) (Stripper, error)

NewStripper create a stripper for an image of given width and height. Default options are: - 64 MPixel strips - 256x256 internal tiling - overviews down to just under 256 pixels

func (Stripper) AssembleStrips added in v0.1.0

func (t Stripper) AssembleStrips(srcStrips [][]tiff.ReadAtReadSeeker) (*IFD, error)

AssembleStrips takes the strips that have been created by following the corresponding Pyramid structure. The ordering of the strip readers in the srcStrips double array must be the same as that of the pyramid structure.

This create a "virtual" IFD tree that has all the caracteristics of final COG file, with the tile loading functions that will reference which tile from which strip to use.

func (Stripper) FullresStripHeightMultiple added in v0.1.0

func (s Stripper) FullresStripHeightMultiple() int

func (Stripper) InternalTileSize added in v0.1.0

func (s Stripper) InternalTileSize() (int, int)

func (Stripper) MinOverviewSize added in v0.1.0

func (s Stripper) MinOverviewSize() int

func (Stripper) OverviewCount added in v0.1.0

func (s Stripper) OverviewCount() int

func (Stripper) Pyramid added in v0.1.0

func (t Stripper) Pyramid() Pyramid

func (Stripper) Size added in v0.1.0

func (s Stripper) Size() (int, int)

func (Stripper) TargetPixelCount added in v0.1.0

func (s Stripper) TargetPixelCount() int

type StripperOption added in v0.1.0

type StripperOption func(t *Stripper) error

func FullresStripHeightMultiple added in v0.1.0

func FullresStripHeightMultiple(heightBase int) StripperOption

FullresStripHeightMultiple forces the strip height to be a multiple of the given heightBase value provided. This can be useful to ensure that the full resolution strip heights are a multiple of the internal tiling height of the source dataset to avoid duplicated source-tile decompressions.

func InternalTileSize added in v0.1.0

func InternalTileSize(width, height int) StripperOption

InternalTileSize sets the internal tiling size of the TIF file

func MinOverviewSize added in v0.1.0

func MinOverviewSize(size int) StripperOption

MinOverviewSize sets the minimal overview size of the TIF file. i.e. overviews will stop being added once one of width or height has reached this value, even if the other dimension is still over the internal tile size

func OverviewCount added in v0.1.0

func OverviewCount(count int) StripperOption

OverviewCount forces the number of overviews to create. By default we create as many as needed in order to reach a size that fits into a single internal TIF tile

func TargetPixelCount added in v0.1.0

func TargetPixelCount(count int) StripperOption

Approximate number of pixels to use for a single strip. i.e. a single subjob will have to process approximately this number of pixels, whatever the size of the whole image. Will be adjusted to fit the internal tiling size

Directories

Path Synopsis
cmd
cogger command
pcogger command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL