Skip to content

Commit 9d4f443

Browse files
authored
implement support for gdal ghost area optimization (#5)
1 parent 48b3f71 commit 9d4f443

File tree

7 files changed

+67
-17
lines changed

7 files changed

+67
-17
lines changed

cog.go

Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,11 @@ func new() *cog {
323323
}
324324

325325
func (cog *cog) writeHeader(w io.Writer) error {
326+
glen := uint64(len(ghost))
327+
if len(cog.ifd.masks) > 0 {
328+
glen = uint64(len(ghostmask))
329+
}
330+
var err error
326331
if cog.bigtiff {
327332
buf := [16]byte{}
328333
if cog.enc == binary.LittleEndian {
@@ -333,9 +338,8 @@ func (cog *cog) writeHeader(w io.Writer) error {
333338
cog.enc.PutUint16(buf[2:], 43)
334339
cog.enc.PutUint16(buf[4:], 8)
335340
cog.enc.PutUint16(buf[6:], 0)
336-
cog.enc.PutUint64(buf[8:], 16)
337-
_, err := w.Write(buf[:])
338-
return err
341+
cog.enc.PutUint64(buf[8:], 16+glen)
342+
_, err = w.Write(buf[:])
339343
} else {
340344
buf := [8]byte{}
341345
if cog.enc == binary.LittleEndian {
@@ -344,10 +348,18 @@ func (cog *cog) writeHeader(w io.Writer) error {
344348
copy(buf[0:], []byte("MM"))
345349
}
346350
cog.enc.PutUint16(buf[2:], 42)
347-
cog.enc.PutUint32(buf[4:], 8)
348-
_, err := w.Write(buf[:])
351+
cog.enc.PutUint32(buf[4:], 8+uint32(glen))
352+
_, err = w.Write(buf[:])
353+
}
354+
if err != nil {
349355
return err
350356
}
357+
if len(cog.ifd.masks) > 0 {
358+
_, err = w.Write([]byte(ghostmask))
359+
} else {
360+
_, err = w.Write([]byte(ghost))
361+
}
362+
return err
351363
}
352364

353365
const (
@@ -388,6 +400,23 @@ func (cog *cog) computeStructure() {
388400
}
389401
}
390402

403+
const ghost = `GDAL_STRUCTURAL_METADATA_SIZE=000140 bytes
404+
LAYOUT=IFDS_BEFORE_DATA
405+
BLOCK_ORDER=ROW_MAJOR
406+
BLOCK_LEADER=SIZE_AS_UINT4
407+
BLOCK_TRAILER=LAST_4_BYTES_REPEATED
408+
KNOWN_INCOMPATIBLE_EDITION=NO
409+
` //2 spaces: 1 for the gdal spec, and one to ensure the actual start offset is on a word boundary
410+
411+
const ghostmask = `GDAL_STRUCTURAL_METADATA_SIZE=000174 bytes
412+
LAYOUT=IFDS_BEFORE_DATA
413+
BLOCK_ORDER=ROW_MAJOR
414+
BLOCK_LEADER=SIZE_AS_UINT4
415+
BLOCK_TRAILER=LAST_4_BYTES_REPEATED
416+
KNOWN_INCOMPATIBLE_EDITION=NO
417+
MASK_INTERLEAVED_WITH_IMAGERY=YES
418+
`
419+
391420
func (cog *cog) computeImageryOffsets() error {
392421
ifd := cog.ifd
393422
for ifd != nil {
@@ -418,6 +447,11 @@ func (cog *cog) computeImageryOffsets() error {
418447
if !cog.bigtiff {
419448
dataOffset = 8
420449
}
450+
if len(cog.ifd.masks) > 0 {
451+
dataOffset += uint64(len(ghostmask)) + 4
452+
} else {
453+
dataOffset += uint64(len(ghost)) + 4
454+
}
421455

422456
ifd = cog.ifd
423457
for ifd != nil {
@@ -444,7 +478,7 @@ func (cog *cog) computeImageryOffsets() error {
444478
}
445479
tile.ifd.NewTileOffsets32[tileidx] = uint32(dataOffset)
446480
}
447-
dataOffset += uint64(tile.ifd.TileByteCounts[tileidx])
481+
dataOffset += uint64(tile.ifd.TileByteCounts[tileidx]) + 8
448482
} else {
449483
if cog.bigtiff {
450484
tile.ifd.NewTileOffsets64[tileidx] = 0
@@ -470,6 +504,11 @@ func (cog *cog) write(out io.Writer) error {
470504
if !cog.bigtiff {
471505
strileData.Offset = 8
472506
}
507+
if len(cog.ifd.masks) > 0 {
508+
strileData.Offset += uint64(len(ghostmask))
509+
} else {
510+
strileData.Offset += uint64(len(ghost))
511+
}
473512

474513
ifd := cog.ifd
475514
for ifd != nil {
@@ -480,12 +519,16 @@ func (cog *cog) write(out io.Writer) error {
480519
ifd = ifd.overview
481520
}
482521

522+
glen := uint64(len(ghost))
523+
if len(cog.ifd.masks) > 0 {
524+
glen = uint64(len(ghostmask))
525+
}
483526
cog.writeHeader(out)
484527

485528
ifd = cog.ifd
486-
off := uint64(16)
529+
off := uint64(16 + glen)
487530
if !cog.bigtiff {
488-
off = 8
531+
off = 8 + glen
489532
}
490533
for ifd != nil {
491534
nmasks := len(ifd.masks)
@@ -511,19 +554,28 @@ func (cog *cog) write(out io.Writer) error {
511554

512555
datas := cog.dataInterlacing()
513556
tiles := datas.tiles()
514-
buf := &bytes.Buffer{}
557+
data := []byte{}
515558
for tile := range tiles {
516-
buf.Reset()
517559
idx := (tile.x+tile.y*tile.ifd.ntilesx)*tile.ifd.nplanes + tile.plane
518-
if tile.ifd.TileByteCounts[idx] > 0 {
560+
bc := tile.ifd.TileByteCounts[idx]
561+
if bc > 0 {
519562
_, err := tile.ifd.r.Seek(int64(tile.ifd.OriginalTileOffsets[idx]), io.SeekStart)
520563
if err != nil {
521564
return fmt.Errorf("seek to %d: %w", tile.ifd.OriginalTileOffsets[idx], err)
522565
}
523-
_, err = io.CopyN(out, tile.ifd.r, int64(tile.ifd.TileByteCounts[idx]))
566+
if uint32(len(data)) < bc+8 {
567+
data = make([]byte, (bc+8)*2)
568+
}
569+
binary.LittleEndian.PutUint32(data, bc) //header ghost: tile size
570+
_, err = tile.ifd.r.Read(data[4 : 4+bc])
571+
if err != nil {
572+
return fmt.Errorf("read %d from %d: %w",
573+
bc, tile.ifd.OriginalTileOffsets[idx], err)
574+
}
575+
copy(data[4+bc:8+bc], data[bc:4+bc]) //trailer ghost: repeat last 4 bytes
576+
_, err = out.Write(data[0 : bc+8])
524577
if err != nil {
525-
return fmt.Errorf("copy %d from %d: %w",
526-
tile.ifd.TileByteCounts[idx], tile.ifd.OriginalTileOffsets[idx], err)
578+
return fmt.Errorf("write %d: %w", bc, err)
527579
}
528580
}
529581
}
@@ -812,9 +864,7 @@ func (cog *cog) dataInterlacing() datas {
812864
ifdo = cog.ifd
813865
for idx := count - 1; idx >= 0; idx-- {
814866
ret[idx] = append(ret[idx], ifdo)
815-
for _, mi := range ifdo.masks {
816-
ret[idx] = append(ret[idx], mi)
817-
}
867+
ret[idx] = append(ret[idx], ifdo.masks...)
818868
ifdo = ifdo.overview
819869
}
820870
return ret

testdata/cog_band4.tif

344 Bytes
Binary file not shown.

testdata/cog_band4mask.tif

417 Bytes
Binary file not shown.

testdata/cog_gray.tif

224 Bytes
Binary file not shown.

testdata/cog_graymask.tif

297 Bytes
Binary file not shown.

testdata/cog_rgb.tif

224 Bytes
Binary file not shown.

testdata/cog_rgbmask.tif

297 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)