Files
sjy01-image-proc/vendor/github.com/airbusgeo/godal/godal.go
2024-10-24 15:46:01 +08:00

4222 lines
120 KiB
Go

// Copyright 2021 Airbus Defence and Space
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package godal
/*
#include "godal.h"
#include <stdlib.h>
#cgo pkg-config: gdal
#cgo CXXFLAGS: -std=c++11
#cgo LDFLAGS: -ldl
*/
import "C"
import (
"errors"
"fmt"
"io"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"unsafe"
)
// DataType is a pixel data types
type DataType int
const (
//Unknown / Unset Datatype
Unknown = DataType(C.GDT_Unknown)
//Byte / UInt8
Byte = DataType(C.GDT_Byte)
//UInt16 DataType
UInt16 = DataType(C.GDT_UInt16)
//Int16 DataType
Int16 = DataType(C.GDT_Int16)
//UInt32 DataType
UInt32 = DataType(C.GDT_UInt32)
//Int32 DataType
Int32 = DataType(C.GDT_Int32)
//Float32 DataType
Float32 = DataType(C.GDT_Float32)
//Float64 DataType
Float64 = DataType(C.GDT_Float64)
//CInt16 is a complex Int16
CInt16 = DataType(C.GDT_CInt16)
//CInt32 is a complex Int32
CInt32 = DataType(C.GDT_CInt32)
//CFloat32 is a complex Float32
CFloat32 = DataType(C.GDT_CFloat32)
//CFloat64 is a complex Float64
CFloat64 = DataType(C.GDT_CFloat64)
)
// ErrorCategory wraps GDAL's error types
type ErrorCategory int
const (
// CE_None is not an error
CE_None = ErrorCategory(C.CE_None)
// CE_Debug is a debug level
CE_Debug = ErrorCategory(C.CE_Debug)
// CE_Warning is a warning levele
CE_Warning = ErrorCategory(C.CE_Warning)
// CE_Failure is an error
CE_Failure = ErrorCategory(C.CE_Failure)
// CE_Fatal is an unrecoverable error
CE_Fatal = ErrorCategory(C.CE_Fatal)
)
// String implements Stringer
func (dtype DataType) String() string {
return C.GoString(C.GDALGetDataTypeName(C.GDALDataType(dtype)))
}
// Size retruns the number of bytes needed for one instance of DataType
func (dtype DataType) Size() int {
switch dtype {
case Byte:
return 1
case Int16, UInt16:
return 2
case Int32, UInt32, Float32, CInt16:
return 4
case CInt32, Float64, CFloat32:
return 8
case CFloat64:
return 16
default:
panic("unsupported type")
}
}
// ColorInterp is a band's color interpretation
type ColorInterp int
const (
//CIUndefined is an undefined ColorInterp
CIUndefined = ColorInterp(C.GCI_Undefined)
//CIGray is a gray level ColorInterp
CIGray = ColorInterp(C.GCI_GrayIndex)
//CIPalette is an undefined ColorInterp
CIPalette = ColorInterp(C.GCI_PaletteIndex)
//CIRed is a paletted ColorInterp
CIRed = ColorInterp(C.GCI_RedBand)
//CIGreen is a Green ColorInterp
CIGreen = ColorInterp(C.GCI_GreenBand)
//CIBlue is a Blue ColorInterp
CIBlue = ColorInterp(C.GCI_BlueBand)
//CIAlpha is an Alpha/Transparency ColorInterp
CIAlpha = ColorInterp(C.GCI_AlphaBand)
//CIHue is an HSL Hue ColorInterp
CIHue = ColorInterp(C.GCI_HueBand)
//CISaturation is an HSL Saturation ColorInterp
CISaturation = ColorInterp(C.GCI_SaturationBand)
//CILightness is an HSL Lightness ColorInterp
CILightness = ColorInterp(C.GCI_LightnessBand)
//CICyan is an CMYK Cyan ColorInterp
CICyan = ColorInterp(C.GCI_CyanBand)
//CIMagenta is an CMYK Magenta ColorInterp
CIMagenta = ColorInterp(C.GCI_MagentaBand)
//CIYellow is an CMYK Yellow ColorInterp
CIYellow = ColorInterp(C.GCI_YellowBand)
//CIBlack is an CMYK Black ColorInterp
CIBlack = ColorInterp(C.GCI_BlackBand)
//CIY is a YCbCr Y ColorInterp
CIY = ColorInterp(C.GCI_YCbCr_YBand)
//CICb is a YCbCr Cb ColorInterp
CICb = ColorInterp(C.GCI_YCbCr_CbBand)
//CICr is a YCbCr Cr ColorInterp
CICr = ColorInterp(C.GCI_YCbCr_CrBand)
//CIMax is an maximum ColorInterp
CIMax = ColorInterp(C.GCI_Max)
)
// Name returns the ColorInterp's name
func (colorInterp ColorInterp) Name() string {
return C.GoString(C.GDALGetColorInterpretationName(C.GDALColorInterp(colorInterp)))
}
// Band is a wrapper around a GDALRasterBandH
type Band struct {
majorObject
}
// handle() returns a pointer to the underlying GDALRasterBandH
func (band Band) handle() C.GDALRasterBandH {
return C.GDALRasterBandH(band.majorObject.cHandle)
}
// Structure returns the dataset's Structure
func (band Band) Structure() BandStructure {
var sx, sy, bsx, bsy, dtype C.int
var scale, offset C.double
C.godalBandStructure(band.handle(), &sx, &sy, &bsx, &bsy, &scale, &offset, &dtype)
return BandStructure{
SizeX: int(sx),
SizeY: int(sy),
BlockSizeX: int(bsx),
BlockSizeY: int(bsy),
Scale: float64(scale),
Offset: float64(offset),
DataType: DataType(int(dtype)),
}
}
// NoData returns the band's nodata value. if ok is false, the band does not
// have a nodata value set
func (band Band) NoData() (nodata float64, ok bool) {
cok := C.int(0)
cn := C.GDALGetRasterNoDataValue(band.handle(), &cok)
if cok != 0 {
return float64(cn), true
}
return 0, false
}
// SetNoData sets the band's nodata value
func (band Band) SetNoData(nd float64, opts ...SetNoDataOption) error {
sndo := &setNodataOpts{}
for _, opt := range opts {
opt.setSetNoDataOpt(sndo)
}
cgc := createCGOContext(nil, sndo.errorHandler)
C.godalSetRasterNoDataValue(cgc.cPointer(), band.handle(), C.double(nd))
return cgc.close()
}
// ClearNoData clears the band's nodata value
func (band Band) ClearNoData(opts ...SetNoDataOption) error {
sndo := &setNodataOpts{}
for _, opt := range opts {
opt.setSetNoDataOpt(sndo)
}
cgc := createCGOContext(nil, sndo.errorHandler)
C.godalDeleteRasterNoDataValue(cgc.cPointer(), band.handle())
return cgc.close()
}
// SetScaleOffset sets the band's scale and offset
func (band Band) SetScaleOffset(scale, offset float64, opts ...SetScaleOffsetOption) error {
setterOpts := &setScaleOffsetOpts{}
for _, opt := range opts {
opt.setSetScaleOffsetOpt(setterOpts)
}
cgc := createCGOContext(nil, setterOpts.errorHandler)
C.godalSetRasterScaleOffset(cgc.cPointer(), band.handle(), C.double(scale), C.double(offset))
return cgc.close()
}
// ClearScaleOffset clears the band's scale and offset
func (band Band) ClearScaleOffset(opts ...SetScaleOffsetOption) error {
return band.SetScaleOffset(1.0, 0.0, opts...)
}
// ColorInterp returns the band's color interpretation (defaults to Gray)
func (band Band) ColorInterp() ColorInterp {
colorInterp := C.GDALGetRasterColorInterpretation(band.handle())
return ColorInterp(colorInterp)
}
// SetColorInterp sets the band's color interpretation
func (band Band) SetColorInterp(colorInterp ColorInterp, opts ...SetColorInterpOption) error {
scio := &setColorInterpOpts{}
for _, opt := range opts {
opt.setSetColorInterpOpt(scio)
}
cgc := createCGOContext(nil, scio.errorHandler)
C.godalSetRasterColorInterpretation(cgc.cPointer(), band.handle(), C.GDALColorInterp(colorInterp))
return cgc.close()
}
// MaskFlags returns the mask flags associated with this band.
//
// See https://gdal.org/development/rfc/rfc15_nodatabitmask.html for how this flag
// should be interpreted
func (band Band) MaskFlags() int {
return int(C.GDALGetMaskFlags(band.handle()))
}
// MaskBand returns the mask (nodata) band for this band. May be generated from nodata values.
func (band Band) MaskBand() Band {
hndl := C.GDALGetMaskBand(band.handle())
return Band{majorObject{C.GDALMajorObjectH(hndl)}}
}
// CreateMask creates a mask (nodata) band for this band.
//
// Any handle returned by a previous call to MaskBand() should not be used after a call to CreateMask
// See https://gdal.org/development/rfc/rfc15_nodatabitmask.html for how flag should be used
func (band Band) CreateMask(flags int, opts ...BandCreateMaskOption) (Band, error) {
gopts := bandCreateMaskOpts{}
for _, opt := range opts {
opt.setBandCreateMaskOpt(&gopts)
}
cgc := createCGOContext(gopts.config, gopts.errorHandler)
hndl := C.godalCreateMaskBand(cgc.cPointer(), band.handle(), C.int(flags))
if err := cgc.close(); err != nil {
return Band{}, err
}
return Band{majorObject{C.GDALMajorObjectH(hndl)}}, nil
}
// Fill sets the whole band uniformely to (real,imag)
func (band Band) Fill(real, imag float64, opts ...FillBandOption) error {
fo := &fillBandOpts{}
for _, o := range opts {
o.setFillBandOpt(fo)
}
cgc := createCGOContext(nil, fo.errorHandler)
C.godalFillRaster(cgc.cPointer(), band.handle(), C.double(real), C.double(imag))
return cgc.close()
}
// Read populates the supplied buffer with the pixels contained in the supplied window
func (band Band) Read(srcX, srcY int, buffer interface{}, bufWidth, bufHeight int, opts ...BandIOOption) error {
return band.IO(IORead, srcX, srcY, buffer, bufWidth, bufHeight, opts...)
}
// Write sets the dataset's pixels contained in the supplied window to the content of the supplied buffer
func (band Band) Write(srcX, srcY int, buffer interface{}, bufWidth, bufHeight int, opts ...BandIOOption) error {
return band.IO(IOWrite, srcX, srcY, buffer, bufWidth, bufHeight, opts...)
}
// IO reads or writes the pixels contained in the supplied window
func (band Band) IO(rw IOOperation, srcX, srcY int, buffer interface{}, bufWidth, bufHeight int, opts ...BandIOOption) error {
ro := bandIOOpts{}
for _, opt := range opts {
opt.setBandIOOpt(&ro)
}
if ro.dsHeight == 0 {
ro.dsHeight = bufHeight
}
if ro.dsWidth == 0 {
ro.dsWidth = bufWidth
}
dtype := bufferType(buffer)
dsize := dtype.Size()
pixelSpacing := dsize
if ro.pixelSpacing > 0 {
pixelSpacing = ro.pixelSpacing
}
if ro.pixelStride > 0 {
pixelSpacing = ro.pixelStride * dsize
}
lineSpacing := bufWidth * pixelSpacing
if ro.lineSpacing > 0 {
lineSpacing = ro.lineSpacing
}
if ro.lineStride > 0 {
lineSpacing = ro.lineStride * dsize
}
minsize := (lineSpacing*(bufHeight-1) + (bufWidth-1)*pixelSpacing + dsize) / dsize
cBuf := cBuffer(buffer, minsize)
//fmt.Fprintf(os.Stderr, "%v %d %d %d\n", ro.bands, pixelSpacing, lineSpacing, bandSpacing)
ralg, err := ro.resampling.rioAlg()
if err != nil {
return err
}
cgc := createCGOContext(ro.config, ro.errorHandler)
C.godalBandRasterIO(cgc.cPointer(), band.handle(), C.GDALRWFlag(rw),
C.int(srcX), C.int(srcY), C.int(ro.dsWidth), C.int(ro.dsHeight),
cBuf,
C.int(bufWidth), C.int(bufHeight), C.GDALDataType(dtype),
C.int(pixelSpacing), C.int(lineSpacing), ralg)
return cgc.close()
}
// Polygonize wraps GDALPolygonize
func (band Band) Polygonize(dstLayer Layer, opts ...PolygonizeOption) error {
popt := polygonizeOpts{
pixFieldIndex: -1,
}
maskBand := band.MaskBand()
popt.mask = &maskBand
for _, opt := range opts {
opt.setPolygonizeOpt(&popt)
}
copts := sliceToCStringArray(popt.options)
defer copts.free()
var cMaskBand C.GDALRasterBandH = nil
if popt.mask != nil {
cMaskBand = popt.mask.handle()
}
cgc := createCGOContext(nil, popt.errorHandler)
C.godalPolygonize(cgc.cPointer(), band.handle(), cMaskBand, dstLayer.handle(), C.int(popt.pixFieldIndex), copts.cPointer())
return cgc.close()
}
// FillNoData wraps GDALFillNodata()
func (band Band) FillNoData(opts ...FillNoDataOption) error {
popt := fillnodataOpts{
maxDistance: 100,
iterations: 0,
}
for _, opt := range opts {
opt.setFillnodataOpt(&popt)
}
//copts := sliceToCStringArray(popt.options)
//defer copts.free()
var cMaskBand C.GDALRasterBandH = nil
if popt.mask != nil {
cMaskBand = popt.mask.handle()
}
cgc := createCGOContext(nil, popt.errorHandler)
C.godalFillNoData(cgc.cPointer(), band.handle(), cMaskBand, C.int(popt.maxDistance), C.int(popt.iterations), nil)
return cgc.close()
}
// SieveFilter wraps GDALSieveFilter
func (band Band) SieveFilter(sizeThreshold int, opts ...SieveFilterOption) error {
sfopt := sieveFilterOpts{
dstBand: &band,
connectedness: 4,
}
maskBand := band.MaskBand()
sfopt.mask = &maskBand
for _, opt := range opts {
opt.setSieveFilterOpt(&sfopt)
}
var cMaskBand C.GDALRasterBandH = nil
if sfopt.mask != nil {
cMaskBand = sfopt.mask.handle()
}
cgc := createCGOContext(nil, sfopt.errorHandler)
C.godalSieveFilter(cgc.cPointer(), band.handle(), cMaskBand, sfopt.dstBand.handle(),
C.int(sizeThreshold), C.int(sfopt.connectedness))
return cgc.close()
}
// Overviews returns all overviews of band
func (band Band) Overviews() []Band {
cbands := C.godalBandOverviews(band.handle())
if cbands == nil {
return nil
}
defer C.free(unsafe.Pointer(cbands))
//https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
sBands := (*[1 << 30]C.GDALRasterBandH)(unsafe.Pointer(cbands))
bands := []Band{}
i := 0
for {
if sBands[i] == nil {
return bands
}
bands = append(bands, Band{majorObject{C.GDALMajorObjectH(sBands[i])}})
i++
}
}
// Histogram returns or computes the bands histogram
func (band Band) Histogram(opts ...HistogramOption) (Histogram, error) {
hopt := histogramOpts{}
for _, o := range opts {
o.setHistogramOpt(&hopt)
}
var values *C.ulonglong = nil
defer C.VSIFree(unsafe.Pointer(values))
cgc := createCGOContext(nil, hopt.errorHandler)
C.godalRasterHistogram(cgc.cPointer(), band.handle(), (*C.double)(&hopt.min), (*C.double)(&hopt.max), (*C.int)(&hopt.buckets),
&values, C.int(hopt.includeOutside), C.int(hopt.approx))
if err := cgc.close(); err != nil {
return Histogram{}, err
}
counts := (*[1 << 30]C.ulonglong)(unsafe.Pointer(values))
h := Histogram{
min: hopt.min,
max: hopt.max,
counts: make([]uint64, hopt.buckets),
}
for i := int32(0); i < hopt.buckets; i++ {
h.counts[i] = uint64(counts[i])
}
return h, nil
}
// GetStatistics returns if present and flag as true.
//
// Only cached statistics are returned and no new statistics are computed.
// Return false and no error if no statistics are availables.
// Available options are:
// - Aproximate() to allow the satistics to be computed on overviews or a subset of all tiles.
// - ErrLogger
func (band Band) GetStatistics(opts ...StatisticsOption) (Statistics, bool, error) {
sopt := statisticsOpts{}
for _, s := range opts {
s.setStatisticsOpt(&sopt)
}
var min, max, mean, std C.double
cgc := createCGOContext(nil, sopt.errorHandler)
ret := C.godalGetRasterStatistics(cgc.cPointer(), band.handle(),
(C.int)(sopt.approx), &min, &max, &mean, &std)
if err := cgc.close(); err != nil {
return Statistics{}, false, err
}
if ret == 0 {
return Statistics{}, false, nil
}
var ap bool = sopt.approx != 0
s := Statistics{
Approximate: ap,
Min: float64(min),
Max: float64(max),
Mean: float64(mean),
Std: float64(std),
}
return s, true, nil
}
// ComputeStatistics returns from exact computation or approximation.
//
// Band full scan might be necessary.
// Available options are:
// - Aproximate() to allow the satistics to be computed on overviews or a subset of all tiles.
// - ErrLogger
func (band Band) ComputeStatistics(opts ...StatisticsOption) (Statistics, error) {
sopt := statisticsOpts{}
for _, s := range opts {
s.setStatisticsOpt(&sopt)
}
var min, max, mean, std C.double
cgc := createCGOContext(nil, sopt.errorHandler)
C.godalComputeRasterStatistics(cgc.cPointer(), band.handle(),
(C.int)(sopt.approx), &min, &max, &mean, &std)
if err := cgc.close(); err != nil {
return Statistics{}, err
}
var ap bool = sopt.approx != 0
s := Statistics{
Min: float64(min),
Max: float64(max),
Mean: float64(mean),
Std: float64(std),
Approximate: ap,
}
return s, nil
}
// SetStatistics set statistics (Min, Max, Mean & STD).
//
// Available options are:
//
// -ErrLogger
func (band Band) SetStatistics(min, max, mean, std float64, opts ...SetStatisticsOption) error {
stso := setStatisticsOpt{}
for _, opt := range opts {
opt.setSetStatisticsOpt(&stso)
}
cgc := createCGOContext(nil, stso.errorHandler)
C.godalSetRasterStatistics(cgc.cPointer(), band.handle(), C.double(min),
C.double(max), C.double(mean), C.double(std))
if err := cgc.close(); err != nil {
return err
}
return nil
}
func cIntArray(in []int) *C.int {
var ptr *C.int
if len(in) > 0 {
ret := make([]C.int, len(in))
for i := range in {
ret[i] = C.int(in[i])
}
ptr = (*C.int)(unsafe.Pointer(&ret[0]))
}
return ptr
}
func cLongArray(in []int64) *C.longlong {
var ptr *C.longlong
if len(in) > 0 {
ret := make([]C.longlong, len(in))
for i := range in {
ret[i] = C.longlong(in[i])
}
ptr = (*C.longlong)(unsafe.Pointer(&ret[0]))
}
return ptr
}
func cDoubleArray(in []float64) *C.double {
var ptr *C.double
if len(in) > 0 {
ret := make([]C.double, len(in))
for i := range in {
ret[i] = C.double(in[i])
}
ptr = (*C.double)(unsafe.Pointer(&ret[0]))
}
return ptr
}
type cStringArray struct {
arr **C.char
l int
}
func (ca cStringArray) free() {
if ca.l > 0 {
garr := (*[1 << 30]*C.char)(unsafe.Pointer(ca.arr))[0:ca.l:ca.l]
for i := 0; i < ca.l-1; i++ {
C.free(unsafe.Pointer(garr[i]))
}
C.free(unsafe.Pointer(ca.arr))
}
}
func (ca cStringArray) cPointer() **C.char {
return ca.arr
}
func sliceToCStringArray(in []string) cStringArray {
if len(in) > 0 {
csa := cStringArray{l: len(in) + 1}
csa.arr = (**C.char)(C.malloc(C.size_t(csa.l) * C.size_t(unsafe.Sizeof((*C.char)(nil)))))
garr := (*[1 << 30]*C.char)(unsafe.Pointer(csa.arr))[0:csa.l:csa.l]
for i := range in {
garr[i] = C.CString(in[i])
}
garr[len(in)] = nil
return csa
}
return cStringArray{}
}
func cStringArrayToSlice(in **C.char) []string {
if in == nil {
return nil
}
//https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
cStrs := (*[1 << 30]*C.char)(unsafe.Pointer(in))
i := 0
ret := []string{}
for {
if cStrs[i] == nil {
return ret
}
ret = append(ret, C.GoString(cStrs[i]))
i++
}
}
func cIntArrayToSlice(in *C.int, length C.int) []int64 {
if in == nil {
return nil
}
cSlice := (*[1 << 28]C.int)(unsafe.Pointer(in))[:length:length]
slice := make([]int64, length)
for i, cval := range cSlice {
slice[i] = int64(cval)
}
return slice
}
func cLongArrayToSlice(in *C.longlong, length C.int) []int64 {
if in == nil {
return nil
}
cSlice := (*[1 << 28]C.longlong)(unsafe.Pointer(in))[:length:length]
slice := make([]int64, length)
for i, cval := range cSlice {
slice[i] = int64(cval)
}
return slice
}
func cDoubleArrayToSlice(in *C.double, length C.int) []float64 {
if in == nil {
return nil
}
cSlice := (*[1 << 28]C.double)(unsafe.Pointer(in))[:length:length]
slice := make([]float64, length)
for i, cval := range cSlice {
slice[i] = float64(cval)
}
return slice
}
// PaletteInterp defines the color interpretation of a ColorTable
type PaletteInterp C.GDALPaletteInterp
const (
//GrayscalePalette is a grayscale palette with a single component per entry
GrayscalePalette PaletteInterp = C.GPI_Gray
//RGBPalette is a RGBA palette with 4 components per entry
RGBPalette PaletteInterp = C.GPI_RGB
//CMYKPalette is a CMYK palette with 4 components per entry
CMYKPalette PaletteInterp = C.GPI_CMYK
//HLSPalette is a HLS palette with 3 components per entry
HLSPalette PaletteInterp = C.GPI_HLS
)
// ColorTable is a color table associated with a Band
type ColorTable struct {
PaletteInterp PaletteInterp
Entries [][4]int16
}
func cColorTableArray(in [][4]int16) *C.short {
ret := make([]C.short, len(in)*4)
for i := range in {
ret[4*i] = C.short(in[i][0])
ret[4*i+1] = C.short(in[i][1])
ret[4*i+2] = C.short(in[i][2])
ret[4*i+3] = C.short(in[i][3])
}
return (*C.short)(unsafe.Pointer(&ret[0]))
}
func ctEntriesFromCshorts(arr *C.short, nEntries int) [][4]int16 {
int16s := (*[1 << 30]C.short)(unsafe.Pointer(arr))
ret := make([][4]int16, nEntries)
for i := 0; i < nEntries; i++ {
ret[i][0] = int16(int16s[i*4])
ret[i][1] = int16(int16s[i*4+1])
ret[i][2] = int16(int16s[i*4+2])
ret[i][3] = int16(int16s[i*4+3])
}
return ret
}
// ColorTable returns the bands color table. The returned ColorTable will have
// a 0-length Entries if the band has no color table assigned
func (band Band) ColorTable() ColorTable {
var interp C.GDALPaletteInterp
var nEntries C.int
var cEntries *C.short
C.godalGetColorTable(band.handle(), &interp, &nEntries, &cEntries)
if cEntries != nil {
defer C.free(unsafe.Pointer(cEntries))
}
return ColorTable{
PaletteInterp: PaletteInterp(interp),
Entries: ctEntriesFromCshorts(cEntries, int(nEntries)),
}
}
// SetColorTable sets the band's color table. if passing in a 0-length ct.Entries,
// the band's color table will be cleared
func (band Band) SetColorTable(ct ColorTable, opts ...SetColorTableOption) error {
cto := &setColorTableOpts{}
for _, o := range opts {
o.setSetColorTableOpt(cto)
}
var cshorts *C.short
if len(ct.Entries) > 0 {
cshorts = cColorTableArray(ct.Entries)
}
cgc := createCGOContext(nil, cto.errorHandler)
C.godalSetColorTable(cgc.cPointer(), band.handle(), C.GDALPaletteInterp(ct.PaletteInterp), C.int(len(ct.Entries)), cshorts)
return cgc.close()
}
// Bands returns all dataset bands.
func (ds *Dataset) Bands() []Band {
cbands := C.godalRasterBands(ds.handle())
if cbands == nil {
return nil
}
defer C.free(unsafe.Pointer(cbands))
//https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
sBands := (*[1 << 30]C.GDALRasterBandH)(unsafe.Pointer(cbands))
bands := []Band{}
i := 0
for {
if sBands[i] == nil {
return bands
}
bands = append(bands, Band{majorObject{C.GDALMajorObjectH(sBands[i])}})
i++
}
}
// Bounds returns the dataset's bounding box in the order
//
// [MinX, MinY, MaxX, MaxY]
func (ds *Dataset) Bounds(opts ...BoundsOption) ([4]float64, error) {
bo := boundsOpts{}
for _, o := range opts {
o.setBoundsOpt(&bo)
}
ret := [4]float64{}
st := ds.Structure()
gt, err := ds.GeoTransform()
if err != nil {
return ret, fmt.Errorf("get geotransform: %w", err)
}
ret[0] = gt[0]
ret[1] = gt[3]
ret[2] = gt[0] + float64(st.SizeX)*gt[1] + float64(st.SizeY)*gt[2]
ret[3] = gt[3] + float64(st.SizeX)*gt[4] + float64(st.SizeY)*gt[5]
if bo.sr != nil {
srcsr := ds.SpatialRef()
defer srcsr.Close()
ret, err = reprojectBounds(ret, srcsr, bo.sr)
if err != nil {
return ret, err
}
}
if ret[0] > ret[2] {
ret[2], ret[0] = ret[0], ret[2]
}
if ret[1] > ret[3] {
ret[3], ret[1] = ret[1], ret[3]
}
return ret, nil
}
// CreateMaskBand creates a mask (nodata) band shared for all bands of this dataset.
//
// Any handle returned by a previous call to Band.MaskBand() should not be used after a call to CreateMaskBand
// See https://gdal.org/development/rfc/rfc15_nodatabitmask.html for how flag should be used
func (ds *Dataset) CreateMaskBand(flags int, opts ...DatasetCreateMaskOption) (Band, error) {
gopts := dsCreateMaskOpts{}
for _, opt := range opts {
opt.setDatasetCreateMaskOpt(&gopts)
}
cgc := createCGOContext(gopts.config, gopts.errorHandler)
hndl := C.godalCreateDatasetMaskBand(cgc.cPointer(), ds.handle(), C.int(flags))
if err := cgc.close(); err != nil {
return Band{}, err
}
return Band{majorObject{C.GDALMajorObjectH(hndl)}}, nil
}
// Driver returns dataset driver.
func (ds *Dataset) Driver() Driver {
return Driver{majorObject{C.GDALMajorObjectH(C.GDALGetDatasetDriver(ds.handle()))}}
}
// Projection returns the WKT projection of the dataset. May be empty.
func (ds *Dataset) Projection() string {
str := C.GDALGetProjectionRef(ds.handle())
return C.GoString(str)
}
// SetProjection sets the WKT projection of the dataset. May be empty.
func (ds *Dataset) SetProjection(wkt string, opts ...SetProjectionOption) error {
po := &setProjectionOpts{}
for _, o := range opts {
o.setSetProjectionOpt(po)
}
var cwkt = (*C.char)(nil)
if len(wkt) > 0 {
cwkt = C.CString(wkt)
defer C.free(unsafe.Pointer(cwkt))
}
cgc := createCGOContext(nil, po.errorHandler)
C.godalSetProjection(cgc.cPointer(), ds.handle(), cwkt)
return cgc.close()
}
// SpatialRef returns dataset projection.
func (ds *Dataset) SpatialRef() *SpatialRef {
hndl := C.GDALGetSpatialRef(ds.handle())
return &SpatialRef{handle: hndl, isOwned: false}
}
// SetSpatialRef sets dataset's projection.
//
// sr can be set to nil to clear an existing projection
func (ds *Dataset) SetSpatialRef(sr *SpatialRef, opts ...SetSpatialRefOption) error {
sro := &setSpatialRefOpts{}
for _, o := range opts {
o.setSetSpatialRefOpt(sro)
}
var hndl C.OGRSpatialReferenceH
if sr == nil {
hndl = nil
} else {
hndl = sr.handle
}
cgc := createCGOContext(nil, sro.errorHandler)
C.godalDatasetSetSpatialRef(cgc.cPointer(), ds.handle(), hndl)
return cgc.close()
}
// GeoTransform returns the affine transformation coefficients
func (ds *Dataset) GeoTransform(opts ...GetGeoTransformOption) ([6]float64, error) {
gto := &getGeoTransformOpts{}
for _, o := range opts {
o.setGetGeoTransformOpt(gto)
}
ret := [6]float64{}
gt := make([]C.double, 6)
cgt := (*C.double)(unsafe.Pointer(&gt[0]))
cgc := createCGOContext(nil, gto.errorHandler)
C.godalGetGeoTransform(cgc.cPointer(), ds.handle(), cgt)
if err := cgc.close(); err != nil {
return ret, err
}
for i := range ret {
ret[i] = float64(gt[i])
}
return ret, nil
}
// SetGeoTransform sets the affine transformation coefficients
func (ds *Dataset) SetGeoTransform(transform [6]float64, opts ...SetGeoTransformOption) error {
gto := &setGeoTransformOpts{}
for _, o := range opts {
o.setSetGeoTransformOpt(gto)
}
gt := cDoubleArray(transform[:])
cgc := createCGOContext(nil, gto.errorHandler)
C.godalSetGeoTransform(cgc.cPointer(), ds.handle(), gt)
return cgc.close()
}
// SetNoData sets the band's nodata value
func (ds *Dataset) SetNoData(nd float64, opts ...SetNoDataOption) error {
sndo := &setNodataOpts{}
for _, opt := range opts {
opt.setSetNoDataOpt(sndo)
}
cgc := createCGOContext(nil, sndo.errorHandler)
C.godalSetDatasetNoDataValue(cgc.cPointer(), ds.handle(), C.double(nd))
return cgc.close()
}
// SetScaleOffset sets the band's scale and offset
func (ds *Dataset) SetScaleOffset(scale, offset float64, opts ...SetScaleOffsetOption) error {
setterOpts := &setScaleOffsetOpts{}
for _, opt := range opts {
opt.setSetScaleOffsetOpt(setterOpts)
}
cgc := createCGOContext(nil, setterOpts.errorHandler)
C.godalSetDatasetScaleOffset(cgc.cPointer(), ds.handle(), C.double(scale), C.double(offset))
return cgc.close()
}
// Translate runs the library version of gdal_translate.
// See the gdal_translate doc page to determine the valid flags/opts that can be set in switches.
//
// Example switches :
//
// []string{
// "-a_nodata", 0,
// "-a_srs", "epsg:4326"}
//
// Creation options and driver may be set either in the switches slice with
//
// switches:=[]string{"-co","TILED=YES","-of","GTiff"}
//
// or through Options with
//
// ds.Translate(dst, switches, CreationOption("TILED=YES","BLOCKXSIZE=256"), GTiff)
func (ds *Dataset) Translate(dstDS string, switches []string, opts ...DatasetTranslateOption) (*Dataset, error) {
gopts := dsTranslateOpts{}
for _, opt := range opts {
opt.setDatasetTranslateOpt(&gopts)
}
for _, copt := range gopts.creation {
switches = append(switches, "-co", copt)
}
if gopts.driver != "" {
dname := string(gopts.driver)
if dm, ok := driverMappings[gopts.driver]; ok {
dname = dm.rasterName
}
switches = append(switches, "-of", dname)
}
cswitches := sliceToCStringArray(switches)
defer cswitches.free()
cname := unsafe.Pointer(C.CString(dstDS))
defer C.free(cname)
cgc := createCGOContext(gopts.config, gopts.errorHandler)
hndl := C.godalTranslate(cgc.cPointer(), (*C.char)(cname), ds.handle(), cswitches.cPointer())
if err := cgc.close(); err != nil {
return nil, err
}
return &Dataset{majorObject{C.GDALMajorObjectH(hndl)}}, nil
}
// Warp runs the library version of gdalwarp
// See the gdalwarp doc page to determine the valid flags/opts that can be set in switches.
//
// Example switches :
//
// []string{
// "-t_srs","epsg:3857",
// "-dstalpha"}
//
// Creation options and driver may be set either in the switches slice with
//
// switches:=[]string{"-co","TILED=YES","-of","GTiff"}
//
// or through Options with
//
// ds.Warp(dst, switches, CreationOption("TILED=YES","BLOCKXSIZE=256"), GTiff)
func (ds *Dataset) Warp(dstDS string, switches []string, opts ...DatasetWarpOption) (*Dataset, error) {
return Warp(dstDS, []*Dataset{ds}, switches, opts...)
}
// Warp writes provided sourceDS Datasets into new dataset and runs the library version of gdalwarp
// See the gdalwarp doc page to determine the valid flags/opts that can be set in switches.
//
// Example switches :
//
// []string{
// "-t_srs","epsg:3857",
// "-dstalpha"}
//
// Creation options and driver may be set either in the switches slice with
//
// switches:=[]string{"-co","TILED=YES","-of","GTiff"}
//
// or through Options with
//
// ds.Warp(dst, switches, CreationOption("TILED=YES","BLOCKXSIZE=256"), GTiff)
func Warp(dstDS string, sourceDS []*Dataset, switches []string, opts ...DatasetWarpOption) (*Dataset, error) {
gopts := dsWarpOpts{}
for _, opt := range opts {
opt.setDatasetWarpOpt(&gopts)
}
for _, copt := range gopts.creation {
switches = append(switches, "-co", copt)
}
if gopts.driver != "" {
dname := string(gopts.driver)
if dm, ok := driverMappings[gopts.driver]; ok {
dname = dm.rasterName
}
switches = append(switches, "-of", dname)
}
srcDS := make([]C.GDALDatasetH, len(sourceDS))
for i, dataset := range sourceDS {
srcDS[i] = dataset.handle()
}
cswitches := sliceToCStringArray(switches)
defer cswitches.free()
cname := unsafe.Pointer(C.CString(dstDS))
defer C.free(cname)
cgc := createCGOContext(gopts.config, gopts.errorHandler)
hndl := C.godalDatasetWarp(cgc.cPointer(), (*C.char)(cname), C.int(len(sourceDS)), (*C.GDALDatasetH)(unsafe.Pointer(&srcDS[0])), cswitches.cPointer())
if err := cgc.close(); err != nil {
return nil, err
}
return &Dataset{majorObject{C.GDALMajorObjectH(hndl)}}, nil
}
// WarpInto writes provided sourceDS Datasets into self existing dataset and runs the library version of gdalwarp
// See the gdalwarp doc page to determine the valid flags/opts that can be set in switches.
//
// Example switches :
//
// []string{
// "-t_srs","epsg:3857",
// "-dstalpha"}
func (ds *Dataset) WarpInto(sourceDS []*Dataset, switches []string, opts ...DatasetWarpIntoOption) error {
gopts := dsWarpIntoOpts{}
for _, opt := range opts {
opt.setDatasetWarpIntoOpt(&gopts)
}
cswitches := sliceToCStringArray(switches)
defer cswitches.free()
dstDS := ds.handle()
srcDS := make([]C.GDALDatasetH, len(sourceDS))
for i, dataset := range sourceDS {
srcDS[i] = dataset.handle()
}
cgc := createCGOContext(gopts.config, gopts.errorHandler)
C.godalDatasetWarpInto(cgc.cPointer(),
dstDS,
C.int(len(sourceDS)),
(*C.GDALDatasetH)(unsafe.Pointer(&srcDS[0])),
cswitches.cPointer())
return cgc.close()
}
// BuildOverviews computes overviews for the dataset.
//
// If neither Levels() or MinSize() is specified, will compute overview
// levels such that the smallest overview is just under the block size.
//
// Not Setting OvrLevels() or OvrMinSize() if the dataset is not internally tiled
// is not an error but will probably not create the expected result (i.e. only a
// single overview will be created).
func (ds *Dataset) BuildOverviews(opts ...BuildOverviewsOption) error {
bands := ds.Bands()
if len(bands) == 0 {
return fmt.Errorf("cannot compute overviews on dataset with no raster bands")
}
oopts := buildOvrOpts{
resampling: Average,
}
structure := bands[0].Structure()
//default size is to stop when just under the blocksize (so the band contains a single block)
if structure.BlockSizeX > structure.BlockSizeY {
oopts.minSize = structure.BlockSizeX
} else {
oopts.minSize = structure.BlockSizeY
}
for _, opt := range opts {
opt.setBuildOverviewsOpt(&oopts)
}
if len(oopts.levels) == 0 { //levels need to be computed automatically
lvl := 1
sx, sy := structure.SizeX, structure.SizeY
for sx > oopts.minSize || sy > oopts.minSize {
lvl *= 2
oopts.levels = append(oopts.levels, lvl)
sx /= 2
sy /= 2
}
}
if len(oopts.levels) == 0 {
return nil //nothing to do
}
for _, l := range oopts.levels {
if l < 2 {
return fmt.Errorf("cannot compute overview of level %d", l)
}
}
nLevels := C.int(len(oopts.levels))
cLevels := cIntArray(oopts.levels)
nBands := C.int(len(oopts.bands))
cBands := (*C.int)(nil)
if nBands > 0 {
cBands = cIntArray(oopts.bands)
}
cResample := unsafe.Pointer(C.CString(oopts.resampling.String()))
defer C.free(cResample)
cgc := createCGOContext(oopts.config, oopts.errorHandler)
C.godalBuildOverviews(cgc.cPointer(), ds.handle(), (*C.char)(cResample), nLevels, cLevels,
nBands, cBands)
return cgc.close()
}
// ClearOverviews deletes all dataset overviews
func (ds *Dataset) ClearOverviews(opts ...ClearOverviewsOption) error {
co := &clearOvrOpts{}
for _, o := range opts {
o.setClearOverviewsOpt(co)
}
cgc := createCGOContext(nil, co.errorHandler)
C.godalClearOverviews(cgc.cPointer(), ds.handle())
return cgc.close()
}
// ClearStatistics delete dataset statisitics
//
// Since GDAL 3.2
// Available options are:
//
// -ErrLogger
func (ds *Dataset) ClearStatistics(opts ...ClearStatisticsOption) error {
cls := &clearStatisticsOpt{}
for _, o := range opts {
o.setClearStatisticsOpt(cls)
}
cgc := createCGOContext(nil, cls.errorHandler)
C.godalClearRasterStatistics(cgc.cPointer(), ds.handle())
return cgc.close()
}
// Structure returns the dataset's Structure
func (ds *Dataset) Structure() DatasetStructure {
var sx, sy, bsx, bsy, bandCount, dtype C.int
var scale, offset C.double
C.godalDatasetStructure(ds.handle(), &sx, &sy, &bsx, &bsy, &scale, &offset, &bandCount, &dtype)
return DatasetStructure{
BandStructure: BandStructure{
SizeX: int(sx),
SizeY: int(sy),
BlockSizeX: int(bsx),
BlockSizeY: int(bsy),
Scale: float64(scale),
Offset: float64(offset),
DataType: DataType(int(dtype)),
},
NBands: int(bandCount),
}
}
// Read populates the supplied buffer with the pixels contained in the supplied window
func (ds *Dataset) Read(srcX, srcY int, buffer interface{}, bufWidth, bufHeight int, opts ...DatasetIOOption) error {
return ds.IO(IORead, srcX, srcY, buffer, bufWidth, bufHeight, opts...)
}
// Write sets the dataset's pixels contained in the supplied window to the content of the supplied buffer
func (ds *Dataset) Write(srcX, srcY int, buffer interface{}, bufWidth, bufHeight int, opts ...DatasetIOOption) error {
return ds.IO(IOWrite, srcX, srcY, buffer, bufWidth, bufHeight, opts...)
}
// IO reads or writes the pixels contained in the supplied window
func (ds *Dataset) IO(rw IOOperation, srcX, srcY int, buffer interface{}, bufWidth, bufHeight int, opts ...DatasetIOOption) error {
var bands []Band
ro := datasetIOOpts{}
for _, opt := range opts {
opt.setDatasetIOOpt(&ro)
}
if ro.dsHeight == 0 {
ro.dsHeight = bufHeight
}
if ro.dsWidth == 0 {
ro.dsWidth = bufWidth
}
if ro.bands == nil {
bands = ds.Bands()
if len(bands) == 0 {
return fmt.Errorf("cannot perform io on dataset with no bands")
}
for i := range bands {
ro.bands = append(ro.bands, i+1)
}
}
dtype := bufferType(buffer)
dsize := dtype.Size()
pixelSpacing := dsize * len(ro.bands)
if ro.pixelSpacing > 0 {
pixelSpacing = ro.pixelSpacing
}
if ro.pixelStride > 0 {
pixelSpacing = ro.pixelStride * dsize
}
lineSpacing := bufWidth * pixelSpacing
if ro.lineSpacing > 0 {
lineSpacing = ro.lineSpacing
}
if ro.lineStride > 0 {
lineSpacing = ro.lineStride * dsize
}
bandSpacing := dsize
if ro.bandSpacing > 0 {
bandSpacing = ro.bandSpacing
}
if ro.bandStride > 0 {
bandSpacing = ro.bandStride * dsize
}
if ro.bandInterleave {
pixelSpacing = dsize
lineSpacing = bufWidth * dsize
bandSpacing = bufHeight * bufWidth * dsize
}
minsize := ((len(ro.bands)-1)*bandSpacing + (bufHeight-1)*lineSpacing + (bufWidth-1)*pixelSpacing + dsize) / dsize
cBuf := cBuffer(buffer, minsize)
ralg, err := ro.resampling.rioAlg()
if err != nil {
return err
}
cgc := createCGOContext(ro.config, ro.errorHandler)
C.godalDatasetRasterIO(cgc.cPointer(), ds.handle(), C.GDALRWFlag(rw),
C.int(srcX), C.int(srcY), C.int(ro.dsWidth), C.int(ro.dsHeight),
cBuf,
C.int(bufWidth), C.int(bufHeight), C.GDALDataType(dtype),
C.int(len(ro.bands)), cIntArray(ro.bands),
C.int(pixelSpacing), C.int(lineSpacing), C.int(bandSpacing), ralg)
return cgc.close()
}
// RegisterAll calls GDALAllRegister which registers all available raster and vector
// drivers.
//
// Alternatively, you may also register a select number of drivers by calling one or more of
// - godal.RegisterInternal() // MEM, VRT, GTiff and GeoJSON
// - godal.RegisterRaster(godal.GTiff,godal.VRT)
// - godal.RegisterVector(godal.Shapefile)
func RegisterAll() {
C.GDALAllRegister()
}
func RegisterPlugins() {
C.godalRegisterPlugins()
}
func RegisterPlugin(name string, opts ...RegisterPluginOption) error {
ro := registerPluginOpts{}
for _, o := range opts {
o.setRegisterPluginOpt(&ro)
}
cgc := createCGOContext(nil, ro.errorHandler)
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
C.godalRegisterPlugin(cgc.cPointer(), cname)
return cgc.close()
}
// RegisterRaster registers a raster driver by name.
//
// Calling RegisterRaster(DriverName) with one of the predefined DriverNames provided by the library will
// register the corresponding raster driver.
//
// Calling RegisterRaster(DriverName("XXX")) with "XXX" any string will result in calling the function
// GDALRegister_XXX() if it could be found inside the ligdal.so binary. This allows to register any raster driver
// known to gdal but not explicitly defined inside this golang wrapper. Note that "XXX" must be provided
// exactly (i.e. respecting uppercase/lowercase) the same as the names of the C functions GDALRegister_XXX()
// that can be found in gdal.h
func RegisterRaster(drivers ...DriverName) error {
for _, driver := range drivers {
switch driver {
case Memory:
C.GDALRegister_MEM()
case VRT:
C.GDALRegister_VRT()
case HFA:
C.GDALRegister_HFA()
case GTiff:
C.GDALRegister_GTiff()
default:
fnname := fmt.Sprintf("GDALRegister_%s", driver)
drv, ok := driverMappings[driver]
if ok {
fnname = drv.rasterRegister
}
if fnname == "" {
return fmt.Errorf("%s driver does not handle rasters", fnname)
}
if err := registerDriver(fnname); err != nil {
return err
}
}
}
return nil
}
// RegisterVector registers a vector driver by name.
//
// Calling RegisterVector(DriverName) with one of the predefined DriverNames provided by the library will
// register the corresponding vector driver.
//
// Calling RegisterVector(DriverName("XXX")) with "XXX" any string will result in calling the function
// RegisterOGRXXX() if it could be found inside the ligdal.so binary. This allows to register any vector driver
// known to gdal but not explicitly defined inside this golang wrapper. Note that "XXX" must be provided
// exactly (i.e. respecting uppercase/lowercase) the same as the names of the C functions RegisterOGRXXX()
// that can be found in ogrsf_frmts.h
func RegisterVector(drivers ...DriverName) error {
for _, driver := range drivers {
switch driver {
/* TODO: speedup for OGR drivers
case VRT:
C.RegisterOGRVRT()
case Memory:
C.RegisterOGRMEM()
case Mitab:
C.RegisterOGRTAB()
case GeoJSON:
C.RegisterOGRGeoJSON()
*/
default:
fnname := fmt.Sprintf("RegisterOGR%s", driver)
drv, ok := driverMappings[driver]
if ok {
fnname = drv.vectorRegister
}
if fnname == "" {
return fmt.Errorf("%s driver does not handle vectors", fnname)
}
if err := registerDriver(fnname); err != nil {
return err
}
}
}
return nil
}
func registerDriver(fnname string) error {
cfnname := C.CString(fnname)
defer C.free(unsafe.Pointer(cfnname))
ret := C.godalRegisterDriver(cfnname)
if ret != 0 {
return fmt.Errorf("failed to call function %s", fnname)
}
return nil
}
// RegisterInternalDrivers is a shorthand for registering "essential" gdal/ogr drivers.
//
// It is equivalent to calling RegisterRaster("VRT","MEM","GTiff") and
// RegisterVector("MEM","VRT","GeoJSON")
func RegisterInternalDrivers() {
//These are always build in and should never error
_ = RegisterRaster(VRT, Memory, GTiff)
_ = RegisterVector(VRT, Memory, GeoJSON)
}
// Driver is a gdal format driver
type Driver struct {
majorObject
}
// handle() returns a pointer to the underlying GDALDriverH
func (drv Driver) handle() C.GDALDriverH {
return C.GDALDriverH(drv.majorObject.cHandle)
}
// LongName returns the driver long name.
func (drv Driver) LongName() string {
return C.GoString(C.GDALGetDriverLongName(drv.handle()))
}
// ShortName returns the driver short name.
func (drv Driver) ShortName() string {
return C.GoString(C.GDALGetDriverShortName(drv.handle()))
}
// VectorDriver returns a Driver by name. It returns false if the named driver does
// not exist
func VectorDriver(name DriverName) (Driver, bool) {
if dn, ok := driverMappings[name]; ok {
if dn.vectorName == "" {
return Driver{}, false
}
return getDriver(dn.vectorName)
}
return getDriver(string(name))
}
// RasterDriver returns a Driver by name. It returns false if the named driver does
// not exist
func RasterDriver(name DriverName) (Driver, bool) {
if dn, ok := driverMappings[name]; ok {
if dn.rasterName == "" {
return Driver{}, false
}
return getDriver(dn.rasterName)
}
return getDriver(string(name))
}
func getDriver(name string) (Driver, bool) {
cname := C.CString(string(name))
defer C.free(unsafe.Pointer(cname))
hndl := C.GDALGetDriverByName((*C.char)(unsafe.Pointer(cname)))
if hndl != nil {
return Driver{majorObject{C.GDALMajorObjectH(hndl)}}, true
}
return Driver{}, false
}
// Create wraps GDALCreate and uses driver to creates a new raster dataset with the given name (usually filename), size, type and bands.
func Create(driver DriverName, name string, nBands int, dtype DataType, width, height int, opts ...DatasetCreateOption) (*Dataset, error) {
drvname := string(driver)
if drv, ok := driverMappings[driver]; ok {
if drv.rasterName == "" {
return nil, fmt.Errorf("%s does not support raster creation", driver)
}
drvname = drv.rasterName
}
drv, ok := getDriver(drvname)
if !ok {
return nil, fmt.Errorf("failed to get driver %s", drvname)
}
gopts := dsCreateOpts{}
for _, opt := range opts {
opt.setDatasetCreateOpt(&gopts)
}
createOpts := sliceToCStringArray(gopts.creation)
cname := C.CString(name)
defer createOpts.free()
defer C.free(unsafe.Pointer(cname))
cgc := createCGOContext(gopts.config, gopts.errorHandler)
hndl := C.godalCreate(cgc.cPointer(), drv.handle(), (*C.char)(unsafe.Pointer(cname)),
C.int(width), C.int(height), C.int(nBands), C.GDALDataType(dtype),
createOpts.cPointer())
if err := cgc.close(); err != nil {
return nil, err
}
return &Dataset{majorObject{C.GDALMajorObjectH(hndl)}}, nil
}
// CreateVector wraps GDALCreate and uses driver to create a new vector dataset with the given name
// (usually filename) and options
func CreateVector(driver DriverName, name string, opts ...DatasetCreateOption) (*Dataset, error) {
drvname := string(driver)
if drv, ok := driverMappings[driver]; ok {
if drv.vectorName == "" {
return nil, fmt.Errorf("%s does not support vector creation", driver)
}
drvname = drv.vectorName
}
drv, ok := getDriver(drvname)
if !ok {
return nil, fmt.Errorf("failed to get driver %s", drvname)
}
gopts := dsCreateOpts{}
for _, opt := range opts {
opt.setDatasetCreateOpt(&gopts)
}
createOpts := sliceToCStringArray(gopts.creation)
cname := C.CString(name)
defer createOpts.free()
defer C.free(unsafe.Pointer(cname))
cgc := createCGOContext(gopts.config, gopts.errorHandler)
hndl := C.godalCreate(cgc.cPointer(), drv.handle(), (*C.char)(unsafe.Pointer(cname)),
0, 0, 0, C.GDT_Unknown, createOpts.cPointer())
if err := cgc.close(); err != nil {
return nil, err
}
return &Dataset{majorObject{C.GDALMajorObjectH(hndl)}}, nil
}
type majorObject struct {
cHandle C.GDALMajorObjectH
}
// Dataset is a wrapper around a GDALDatasetH
type Dataset struct {
majorObject
}
// handle returns a pointer to the underlying GDALDatasetH
func (ds *Dataset) handle() C.GDALDatasetH {
return C.GDALDatasetH(ds.majorObject.cHandle)
}
// Open calls GDALOpenEx() with the provided options. It returns nil and an error
// in case there was an error opening the provided dataset name.
//
// name may be a filename or any supported string supported by gdal (e.g. a /vsixxx path,
// the xml string representing a vrt dataset, etc...)
func Open(name string, options ...OpenOption) (*Dataset, error) {
oopts := openOpts{
flags: C.GDAL_OF_READONLY | C.GDAL_OF_VERBOSE_ERROR,
siblingFiles: []string{filepath.Base(name)},
}
for _, opt := range options {
opt.setOpenOpt(&oopts)
}
csiblings := sliceToCStringArray(oopts.siblingFiles)
coopts := sliceToCStringArray(oopts.options)
cdrivers := sliceToCStringArray(oopts.drivers)
defer csiblings.free()
defer coopts.free()
defer cdrivers.free()
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
cgc := createCGOContext(oopts.config, oopts.errorHandler)
retds := C.godalOpen(cgc.cPointer(), cname, C.uint(oopts.flags),
cdrivers.cPointer(), coopts.cPointer(), csiblings.cPointer())
if err := cgc.close(); err != nil {
return nil, err
}
return &Dataset{majorObject{C.GDALMajorObjectH(retds)}}, nil
}
// Close releases the dataset
func (ds *Dataset) Close(opts ...CloseOption) error {
co := &closeOpts{}
for _, o := range opts {
o.setCloseOpt(co)
}
if ds.cHandle == nil {
return fmt.Errorf("close called more than once")
}
cgc := createCGOContext(nil, co.errorHandler)
C.godalClose(cgc.cPointer(), ds.handle())
ds.cHandle = nil
return cgc.close()
}
// LibVersion is the GDAL lib versioning scheme
type LibVersion int
// Major returns the GDAL major version (e.g. "3" in 3.2.1)
func (lv LibVersion) Major() int {
return int(lv) / 1000000
}
// Minor return the GDAL minor version (e.g. "2" in 3.2.1)
func (lv LibVersion) Minor() int {
return (int(lv) - lv.Major()*1000000) / 10000
}
// Revision returns the GDAL revision number (e.g. "1" in 3.2.1)
func (lv LibVersion) Revision() int {
return (int(lv) - lv.Major()*1000000 - lv.Minor()*10000) / 100
}
// AssertMinVersion will panic if the runtime version is not at least major.minor.revision
func AssertMinVersion(major, minor, revision int) {
runtimeVersion := Version()
if runtimeVersion.Major() < major ||
(runtimeVersion.Major() == major && runtimeVersion.Minor() < minor) ||
(runtimeVersion.Major() == major && runtimeVersion.Minor() == minor && runtimeVersion.Revision() < revision) {
panic(fmt.Errorf("runtime version %d.%d.%d < %d.%d.%d",
runtimeVersion.Major(), runtimeVersion.Minor(), runtimeVersion.Revision(), major, minor, revision))
}
}
func init() {
compiledVersion := LibVersion(C.GDAL_VERSION_NUM)
AssertMinVersion(compiledVersion.Major(), compiledVersion.Minor(), 0)
}
//export goErrorHandler
func goErrorHandler(loggerID C.int, ec C.int, code C.int, msg *C.char) C.int {
//returns 0 if the received ec/code/msg is not an actual error
//returns !0 if msg should be considered an error
lfn := getErrorHandler(int(loggerID))
err := lfn.fn(ErrorCategory(ec), int(code), C.GoString(msg))
if err != nil {
lfn.err = combine(lfn.err, err)
return 1
}
return 0
}
func testErrorAndLogging(opts ...errorAndLoggingOption) error {
ealo := errorAndLoggingOpts{}
for _, o := range opts {
o.setErrorAndLoggingOpt(&ealo)
}
cctx := createCGOContext(ealo.config, ealo.eh)
C.test_godal_error_handling(cctx.cPointer())
return cctx.close()
}
// Version returns the runtime version of the gdal library
func Version() LibVersion {
cstr := C.CString("VERSION_NUM")
defer C.free(unsafe.Pointer(cstr))
version := C.GoString(C.GDALVersionInfo(cstr))
iversion, _ := strconv.Atoi(version)
return LibVersion(iversion)
}
// IOOperation determines wether Band.IO or Dataset.IO will read pixels into the
// provided buffer, or write pixels from the provided buffer
type IOOperation C.GDALRWFlag
const (
//IORead makes IO copy pixels from the band/dataset into the provided buffer
IORead IOOperation = C.GF_Read
//IOWrite makes IO copy pixels from the provided buffer into the band/dataset
IOWrite = C.GF_Write
)
// ResamplingAlg is a resampling method
type ResamplingAlg int
const (
//Nearest resampling
Nearest ResamplingAlg = iota
// Bilinear resampling
Bilinear
// Cubic resampling
Cubic
// CubicSpline resampling
CubicSpline
// Lanczos resampling
Lanczos
// Average resampling
Average
// Gauss resampling
Gauss
// Mode resampling
Mode
// Max resampling
Max
// Min resampling
Min
// Median resampling
Median
// Sum resampling
Sum
// Q1 resampling
Q1
// Q3 resampling
Q3
//RMS gdal >=3.3
)
func (ra ResamplingAlg) String() string {
switch ra {
case Nearest:
return "nearest"
case Average:
return "average"
case Bilinear:
return "bilinear"
case Cubic:
return "cubic"
case CubicSpline:
return "cubicspline"
case Lanczos:
return "lanczos"
case Gauss:
return "gauss"
case Mode:
return "mode"
//case RMS:
// return "rms"
case Q1:
return "Q1"
case Q3:
return "Q3"
case Median:
return "med"
case Max:
return "max"
case Min:
return "min"
case Sum:
return "sum"
default:
panic("unsupported resampling")
}
}
func (ra ResamplingAlg) rioAlg() (C.GDALRIOResampleAlg, error) {
switch ra {
case Nearest:
return C.GRIORA_NearestNeighbour, nil
case Average:
return C.GRIORA_Average, nil
case Bilinear:
return C.GRIORA_Bilinear, nil
case Cubic:
return C.GRIORA_Cubic, nil
case CubicSpline:
return C.GRIORA_CubicSpline, nil
case Lanczos:
return C.GRIORA_Lanczos, nil
case Gauss:
return C.GRIORA_Gauss, nil
case Mode:
return C.GRIORA_Mode, nil
//case RMS:
// return C.GRIORA_RMS, nil
default:
return C.GRIORA_NearestNeighbour, fmt.Errorf("%s resampling not supported for IO", ra.String())
}
}
func gridAlgFromString(str string) (C.GDALGridAlgorithm, error) {
switch str {
case "invdist":
return C.GGA_InverseDistanceToAPower, nil
case "average":
return C.GGA_MovingAverage, nil
case "nearest":
return C.GGA_NearestNeighbor, nil
case "minimum":
return C.GGA_MetricMinimum, nil
case "maximum":
return C.GGA_MetricMaximum, nil
case "range":
return C.GGA_MetricRange, nil
case "count":
return C.GGA_MetricCount, nil
case "average_distance":
return C.GGA_MetricAverageDistance, nil
case "average_distance_pts":
return C.GGA_MetricAverageDistancePts, nil
case "linear":
return C.GGA_Linear, nil
case "invdistnn":
return C.GGA_InverseDistanceToAPowerNearestNeighbor, nil
default:
return C.GGA_InverseDistanceToAPower, fmt.Errorf("unknown gridding algorithm %s", str)
}
}
func bufferType(buffer interface{}) DataType {
switch buffer.(type) {
case []byte:
return Byte
case []int16:
return Int16
case []uint16:
return UInt16
case []int32:
return Int32
case []uint32:
return UInt32
case []float32:
return Float32
case []float64:
return Float64
case []complex64:
return CFloat32
case []complex128:
return CFloat64
default:
panic("unsupported type")
}
}
// cBuffer returns the type of an individual element, and a pointer to the
// underlying memory array
func cBuffer(buffer interface{}, minsize int) unsafe.Pointer {
sizecheck := func(size int) {
if size < minsize {
panic(fmt.Sprintf("buffer len=%d less than min=%d", size, minsize))
}
}
switch buf := buffer.(type) {
case []byte:
sizecheck(len(buf))
return unsafe.Pointer(&buf[0])
case []int16:
sizecheck(len(buf))
return unsafe.Pointer(&buf[0])
case []uint16:
sizecheck(len(buf))
return unsafe.Pointer(&buf[0])
case []int32:
sizecheck(len(buf))
return unsafe.Pointer(&buf[0])
case []uint32:
sizecheck(len(buf))
return unsafe.Pointer(&buf[0])
case []float32:
sizecheck(len(buf))
return unsafe.Pointer(&buf[0])
case []float64:
sizecheck(len(buf))
return unsafe.Pointer(&buf[0])
case []complex64:
sizecheck(len(buf))
return unsafe.Pointer(&buf[0])
case []complex128:
sizecheck(len(buf))
return unsafe.Pointer(&buf[0])
default:
panic("unsupported type")
}
}
func (mo majorObject) Metadata(key string, opts ...MetadataOption) string {
mopts := metadataOpts{}
for _, opt := range opts {
opt.setMetadataOpt(&mopts)
}
ckey := C.CString(key)
cdom := C.CString(mopts.domain)
defer C.free(unsafe.Pointer(ckey))
defer C.free(unsafe.Pointer(cdom))
str := C.GDALGetMetadataItem(mo.cHandle, ckey, cdom)
return C.GoString(str)
}
func (mo majorObject) Metadatas(opts ...MetadataOption) map[string]string {
mopts := metadataOpts{}
for _, opt := range opts {
opt.setMetadataOpt(&mopts)
}
cdom := C.CString(mopts.domain)
defer C.free(unsafe.Pointer(cdom))
strs := C.GDALGetMetadata(mo.cHandle, cdom)
strslice := cStringArrayToSlice(strs)
if len(strslice) == 0 {
return nil
}
ret := make(map[string]string)
for _, str := range strslice {
idx := strings.Index(str, "=")
if idx == -1 || idx == len(str)-1 {
ret[str[0:len(str)-1]] = ""
} else {
ret[str[0:idx]] = str[idx+1:]
}
}
return ret
}
func (mo majorObject) SetMetadata(key, value string, opts ...MetadataOption) error {
mopts := metadataOpts{}
for _, opt := range opts {
opt.setMetadataOpt(&mopts)
}
ckey := C.CString(key)
cval := C.CString(value)
cdom := C.CString(mopts.domain)
defer C.free(unsafe.Pointer(ckey))
defer C.free(unsafe.Pointer(cdom))
defer C.free(unsafe.Pointer(cval))
cgc := createCGOContext(nil, mopts.errorHandler)
C.godalSetMetadataItem(cgc.cPointer(), mo.cHandle, ckey, cval, cdom)
return cgc.close()
}
func (mo majorObject) ClearMetadata(opts ...MetadataOption) error {
mopts := metadataOpts{}
for _, opt := range opts {
opt.setMetadataOpt(&mopts)
}
cdom := C.CString(mopts.domain)
defer C.free(unsafe.Pointer(cdom))
cgc := createCGOContext(nil, mopts.errorHandler)
C.godalClearMetadata(cgc.cPointer(), mo.cHandle, cdom)
return cgc.close()
}
func (mo majorObject) MetadataDomains() []string {
strs := C.GDALGetMetadataDomainList(mo.cHandle)
return cStringArrayToSlice(strs)
}
// Description returns the description/name
func (mo majorObject) Description() string {
desc := C.GDALGetDescription(mo.cHandle)
return C.GoString(desc)
}
// SetDescription sets the description
func (mo majorObject) SetDescription(description string, opts ...SetDescriptionOption) error {
scio := &setDescriptionOpts{}
for _, opt := range opts {
opt.setDescriptionOpt(scio)
}
cgc := createCGOContext(nil, scio.errorHandler)
cname := unsafe.Pointer(C.CString(description))
defer C.free(cname)
C.godalSetDescription(cgc.cPointer(), mo.cHandle, (*C.char)(cname))
return cgc.close()
}
type openUpdateOpt struct{}
// Update is an OpenOption that instructs gdal to open the dataset for writing/updating
func Update() interface {
OpenOption
} {
return openUpdateOpt{}
}
func (openUpdateOpt) setOpenOpt(oo *openOpts) {
//unset readonly
oo.flags = oo.flags &^ C.GDAL_OF_READONLY //actually a noop as OF_READONLY is 0
oo.flags |= C.GDAL_OF_UPDATE
}
type openSharedOpt struct{}
// Shared opens the dataset with OF_OPEN_SHARED
func Shared() interface {
OpenOption
} {
return openSharedOpt{}
}
func (openSharedOpt) setOpenOpt(oo *openOpts) {
oo.flags |= C.GDAL_OF_SHARED
}
type vectorOnlyOpt struct{}
// VectorOnly limits drivers to vector ones (incompatible with RasterOnly() )
func VectorOnly() interface {
OpenOption
} {
return vectorOnlyOpt{}
}
func (vectorOnlyOpt) setOpenOpt(oo *openOpts) {
oo.flags |= C.GDAL_OF_VECTOR
}
type rasterOnlyOpt struct{}
// RasterOnly limits drivers to vector ones (incompatible with VectorOnly() )
func RasterOnly() interface {
OpenOption
} {
return rasterOnlyOpt{}
}
func (rasterOnlyOpt) setOpenOpt(oo *openOpts) {
oo.flags |= C.GDAL_OF_RASTER
}
// SpatialRef is a wrapper around OGRSpatialReferenceH
type SpatialRef struct {
handle C.OGRSpatialReferenceH
isOwned bool
}
// WKT returns spatialrefernece as WKT
func (sr *SpatialRef) WKT(opts ...WKTExportOption) (string, error) {
wo := &srWKTOpts{}
for _, o := range opts {
o.setWKTExportOpt(wo)
}
cgc := createCGOContext(nil, wo.errorHandler)
cwkt := C.godalExportToWKT(cgc.cPointer(), sr.handle)
if err := cgc.close(); err != nil {
return "", err
}
wkt := C.GoString(cwkt)
C.CPLFree(unsafe.Pointer(cwkt))
return wkt, nil
}
// Close releases memory
func (sr *SpatialRef) Close() {
if sr.handle == nil {
return
//panic("handle already closed")
}
if !sr.isOwned {
sr.handle = nil
return
}
C.OSRRelease(sr.handle)
sr.handle = nil
}
// NewSpatialRef creates a SpatialRef from any "user" projection string, e.g.
// "epsg:4326", "+proj=lonlat", wkt, wkt2 or projjson (as supported by
// gdal's OSRCreateFromUserInput
func NewSpatialRef(userInput string, opts ...CreateSpatialRefOption) (*SpatialRef, error) {
cso := &createSpatialRefOpts{}
for _, o := range opts {
o.setCreateSpatialRefOpt(cso)
}
cstr := C.CString(userInput)
defer C.free(unsafe.Pointer(cstr))
cgc := createCGOContext(nil, cso.errorHandler)
hndl := C.godalCreateUserSpatialRef(cgc.cPointer(), (*C.char)(unsafe.Pointer(cstr)))
if err := cgc.close(); err != nil {
return nil, err
}
return &SpatialRef{handle: hndl, isOwned: true}, nil
}
// NewSpatialRefFromWKT creates a SpatialRef from an opengis WKT description
func NewSpatialRefFromWKT(wkt string, opts ...CreateSpatialRefOption) (*SpatialRef, error) {
cso := &createSpatialRefOpts{}
for _, o := range opts {
o.setCreateSpatialRefOpt(cso)
}
cstr := C.CString(wkt)
defer C.free(unsafe.Pointer(cstr))
cgc := createCGOContext(nil, cso.errorHandler)
hndl := C.godalCreateWKTSpatialRef(cgc.cPointer(), (*C.char)(unsafe.Pointer(cstr)))
if err := cgc.close(); err != nil {
return nil, err
}
return &SpatialRef{handle: hndl, isOwned: true}, nil
}
// NewSpatialRefFromProj4 creates a SpatialRef from a proj4 string
func NewSpatialRefFromProj4(proj string, opts ...CreateSpatialRefOption) (*SpatialRef, error) {
cso := &createSpatialRefOpts{}
for _, o := range opts {
o.setCreateSpatialRefOpt(cso)
}
cstr := C.CString(proj)
defer C.free(unsafe.Pointer(cstr))
cgc := createCGOContext(nil, cso.errorHandler)
hndl := C.godalCreateProj4SpatialRef(cgc.cPointer(), (*C.char)(unsafe.Pointer(cstr)))
if err := cgc.close(); err != nil {
return nil, err
}
return &SpatialRef{handle: hndl, isOwned: true}, nil
}
// NewSpatialRefFromEPSG creates a SpatialRef from an epsg code
func NewSpatialRefFromEPSG(code int, opts ...CreateSpatialRefOption) (*SpatialRef, error) {
cso := &createSpatialRefOpts{}
for _, o := range opts {
o.setCreateSpatialRefOpt(cso)
}
cgc := createCGOContext(nil, cso.errorHandler)
hndl := C.godalCreateEPSGSpatialRef(cgc.cPointer(), C.int(code))
if err := cgc.close(); err != nil {
return nil, err
}
return &SpatialRef{handle: hndl, isOwned: true}, nil
}
// IsSame returns whether two SpatiaRefs describe the same projection.
func (sr *SpatialRef) IsSame(other *SpatialRef) bool {
ret := C.OSRIsSame(sr.handle, other.handle)
return ret != 0
}
// Transform transforms coordinates from one SpatialRef to another
type Transform struct {
handle C.OGRCoordinateTransformationH
dst C.OGRSpatialReferenceH //TODO: refcounting/freeing on this?
}
// NewTransform creates a transformation object from src to dst
func NewTransform(src, dst *SpatialRef, opts ...TransformOption) (*Transform, error) {
to := &trnOpts{}
for _, o := range opts {
o.setTransformOpt(to)
}
cgc := createCGOContext(nil, to.errorHandler)
hndl := C.godalNewCoordinateTransformation(cgc.cPointer(), src.handle, dst.handle)
if err := cgc.close(); err != nil {
return nil, err
}
return &Transform{handle: hndl, dst: dst.handle}, nil
}
// Close releases the Transform object
func (trn *Transform) Close() {
if trn.handle == nil {
return
//panic("transform already closed")
}
C.OCTDestroyCoordinateTransformation(trn.handle)
trn.handle = nil
}
// TransformEx reprojects points in place
//
// x and y may not be nil and must be of the same length
//
// z may be nil, or of the same length as x and y
//
// successful may be nil or of the same length as x and y. If non nil, it will contain
// true or false depending on wether the corresponding point succeeded transformation or not.
//
// TODO: create a version of this function that accepts *C.double to avoid allocs?
// TODO: create a Transform() method that accepts z and successful as options
func (trn *Transform) TransformEx(x []float64, y []float64, z []float64, successful []bool) error {
cx := make([]C.double, len(x))
cy := make([]C.double, len(x))
pcx, pcy := (*C.double)(unsafe.Pointer(&cx[0])), (*C.double)(unsafe.Pointer(&cy[0]))
pcz := (*C.double)(nil)
pcs := (*C.int)(nil)
var cz []C.double
var cs []C.int
if len(z) > 0 {
cz = make([]C.double, len(x))
pcz = (*C.double)(unsafe.Pointer(&cz[0]))
}
if len(successful) > 0 {
cs = make([]C.int, len(x))
pcs = (*C.int)(unsafe.Pointer(&cs[0]))
}
for i := range x {
cx[i] = C.double(x[i])
cy[i] = C.double(y[i])
if cz != nil {
cz[i] = C.double(z[i])
}
}
ret := C.OCTTransformEx(trn.handle, C.int(len(x)), pcx, pcy, pcz, pcs)
for i := range x {
x[i] = float64(cx[i])
y[i] = float64(cy[i])
if cz != nil {
z[i] = float64(cz[i])
}
if cs != nil {
if cs[i] > 0 {
successful[i] = true
} else {
successful[i] = false
}
}
}
if ret == 0 {
return fmt.Errorf("some or all points failed to transform")
}
return nil
}
// EPSGTreatsAsLatLong returns TRUE if EPSG feels the SpatialRef should be treated as having lat/long coordinate ordering.
func (sr *SpatialRef) EPSGTreatsAsLatLong() bool {
ret := C.OSREPSGTreatsAsLatLong(sr.handle)
return ret != 0
}
// Geographic returns wether the SpatialRef is geographic
func (sr *SpatialRef) Geographic() bool {
ret := C.OSRIsGeographic(sr.handle)
return ret != 0
}
// Projected returns wether the SpatialRef is projected
func (sr *SpatialRef) Projected() bool {
ret := C.OSRIsProjected(sr.handle)
return ret != 0
}
// SemiMajor returns the SpatialRef's Semi Major Axis
func (sr *SpatialRef) SemiMajor() (float64, error) {
var err C.int
sm := C.OSRGetSemiMajor(sr.handle, &err)
if err != 0 {
return float64(sm), fmt.Errorf("ogr error %d", err)
}
return float64(sm), nil
}
// SemiMinor returns the SpatialRef's Semi Minor Axis
func (sr *SpatialRef) SemiMinor() (float64, error) {
var err C.int
sm := C.OSRGetSemiMinor(sr.handle, &err)
if err != 0 {
return float64(sm), fmt.Errorf("ogr error %d", err)
}
return float64(sm), nil
}
// AttrValue Fetch indicated attribute of named node from within the WKT tree.
func (sr *SpatialRef) AttrValue(name string, child int) (string, bool) {
cstr := C.CString(name)
defer C.free(unsafe.Pointer(cstr))
cret := C.OSRGetAttrValue(sr.handle, cstr, C.int(child))
if cret != nil {
return C.GoString(cret), true
}
return "", false
}
// AuthorityName is used to query an AUTHORITY[] node from within the WKT tree, and fetch the authority name value.
//
// target is the partial or complete path to the node to get an authority from. i.e. "PROJCS", "GEOGCS", "GEOGCS|UNIT"
// or "" to search for an authority node on the root element.
func (sr *SpatialRef) AuthorityName(target string) string {
cstr := (*C.char)(nil)
if len(target) > 0 {
cstr = C.CString(target)
defer C.free(unsafe.Pointer(cstr))
}
cret := C.OSRGetAuthorityName(sr.handle, cstr)
if cret != nil {
return C.GoString(cret)
}
return ""
}
// AuthorityCode is used to query an AUTHORITY[] node from within the WKT tree, and fetch the code value.
// target is the partial or complete path to the node to get an authority from. i.e. "PROJCS", "GEOGCS", "GEOGCS|UNIT"
// or "" to search for an authority node on the root element.
//
// While in theory values may be non-numeric, for the EPSG authority all code values should be integral.
func (sr *SpatialRef) AuthorityCode(target string) string {
cstr := (*C.char)(nil)
if len(target) > 0 {
cstr = C.CString(target)
defer C.free(unsafe.Pointer(cstr))
}
cret := C.OSRGetAuthorityCode(sr.handle, cstr)
if cret != nil {
return C.GoString(cret)
}
return ""
}
// AutoIdentifyEPSG sets EPSG authority info if possible.
func (sr *SpatialRef) AutoIdentifyEPSG() error {
ogrerr := C.OSRAutoIdentifyEPSG(sr.handle)
if ogrerr != 0 {
return fmt.Errorf("ogr error %d", ogrerr)
}
return nil
}
// Validate SRS tokens.
func (sr *SpatialRef) Validate(opts ...SpatialRefValidateOption) error {
vo := spatialRefValidateOpts{}
for _, opt := range opts {
opt.setSpatialRefValidateOpt(&vo)
}
cgc := createCGOContext(nil, vo.errorHandler)
C.godalValidateSpatialRef(cgc.cPointer(), sr.handle)
return cgc.close()
}
// Rasterize wraps GDALRasterize()
func (ds *Dataset) Rasterize(dstDS string, switches []string, opts ...RasterizeOption) (*Dataset, error) {
gopts := rasterizeOpts{}
for _, opt := range opts {
opt.setRasterizeOpt(&gopts)
}
for _, copt := range gopts.create {
switches = append(switches, "-co", copt)
}
if gopts.driver != "" {
dname := string(gopts.driver)
if dm, ok := driverMappings[gopts.driver]; ok {
dname = dm.rasterName
}
switches = append(switches, "-of", dname)
}
cswitches := sliceToCStringArray(switches)
defer cswitches.free()
cname := unsafe.Pointer(C.CString(dstDS))
defer C.free(cname)
cgc := createCGOContext(gopts.config, gopts.errorHandler)
hndl := C.godalRasterize(cgc.cPointer(), (*C.char)(cname), nil, ds.handle(), cswitches.cPointer())
if err := cgc.close(); err != nil {
return nil, err
}
return &Dataset{majorObject{C.GDALMajorObjectH(hndl)}}, nil
}
// RasterizeInto wraps GDALRasterize() and rasterizes the provided vectorDataset into the ds Dataset
func (ds *Dataset) RasterizeInto(vectorDS *Dataset, switches []string, opts ...RasterizeIntoOption) error {
gopts := rasterizeIntoOpts{}
for _, opt := range opts {
opt.setRasterizeIntoOpt(&gopts)
}
cswitches := sliceToCStringArray(switches)
defer cswitches.free()
cgc := createCGOContext(gopts.config, gopts.errorHandler)
C.godalRasterize(cgc.cPointer(), nil, ds.handle(), vectorDS.handle(), cswitches.cPointer())
if err := cgc.close(); err != nil {
return err
}
return nil
}
// RasterizeGeometry "burns" the provided geometry onto ds.
// By default, the "0" value is burned into all of ds's bands. This behavior can be modified
// with the following options:
// - Bands(bnd ...int) the list of bands to affect
// - Values(val ...float64) the pixel value to burn. There must be either 1 or len(bands) values
//
// provided
// - AllTouched() pixels touched by lines or polygons will be updated, not just those on the line
//
// render path, or whose center point is within the polygon.
func (ds *Dataset) RasterizeGeometry(g *Geometry, opts ...RasterizeGeometryOption) error {
opt := rasterizeGeometryOpts{}
for _, o := range opts {
o.setRasterizeGeometryOpt(&opt)
}
if len(opt.bands) == 0 {
bnds := ds.Bands()
opt.bands = make([]int, len(bnds))
for i := range bnds {
opt.bands[i] = i + 1
}
}
if len(opt.values) == 0 {
opt.values = make([]float64, len(opt.bands))
for i := range opt.values {
opt.values[i] = 0
}
}
if len(opt.values) == 1 && len(opt.values) != len(opt.bands) {
for i := 1; i < len(opt.bands); i++ {
opt.values = append(opt.values, opt.values[0])
}
}
if len(opt.values) != len(opt.bands) {
return fmt.Errorf("must pass in same number of values as bands")
}
cgc := createCGOContext(nil, opt.errorHandler)
C.godalRasterizeGeometry(cgc.cPointer(), ds.handle(), g.handle,
cIntArray(opt.bands), C.int(len(opt.bands)), cDoubleArray(opt.values), C.int(opt.allTouched))
return cgc.close()
}
// GeometryType is a geometry type
type GeometryType uint32
const (
//GTUnknown is a GeometryType
GTUnknown = GeometryType(C.wkbUnknown)
//GTPoint is a GeometryType
GTPoint = GeometryType(C.wkbPoint)
//GTPoint25D is a GeometryType
GTPoint25D = GeometryType(C.wkbPoint25D)
//GTLinearRing is a GeometryType
GTLinearRing = GeometryType(C.wkbLinearRing)
//GTLineString is a GeometryType
GTLineString = GeometryType(C.wkbLineString)
//GTLineString25D is a GeometryType
GTLineString25D = GeometryType(C.wkbLineString25D)
//GTPolygon is a GeometryType
GTPolygon = GeometryType(C.wkbPolygon)
//GTPolygon25D is a GeometryType
GTPolygon25D = GeometryType(C.wkbPolygon25D)
//GTMultiPoint is a GeometryType
GTMultiPoint = GeometryType(C.wkbMultiPoint)
//GTMultiPoint25D is a GeometryType
GTMultiPoint25D = GeometryType(C.wkbMultiPoint25D)
//GTMultiLineString is a GeometryType
GTMultiLineString = GeometryType(C.wkbMultiLineString)
//GTMultiLineString25D is a GeometryType
GTMultiLineString25D = GeometryType(C.wkbMultiLineString25D)
//GTMultiPolygon is a GeometryType
GTMultiPolygon = GeometryType(C.wkbMultiPolygon)
//GTMultiPolygon25D is a GeometryType
GTMultiPolygon25D = GeometryType(C.wkbMultiPolygon25D)
//GTGeometryCollection is a GeometryType
GTGeometryCollection = GeometryType(C.wkbGeometryCollection)
//GTGeometryCollection25D is a GeometryType
GTGeometryCollection25D = GeometryType(C.wkbGeometryCollection25D)
//GTNone is a GeometryType
GTNone = GeometryType(C.wkbNone)
)
// FieldType is a vector field (attribute/column) type
type FieldType C.OGRFieldType
const (
//FTInt is a Simple 32bit integer.
FTInt = FieldType(C.OFTInteger)
//FTReal is a Double Precision floating point.
FTReal = FieldType(C.OFTReal)
//FTString is a String of ASCII chars.
FTString = FieldType(C.OFTString)
//FTInt64 is a Single 64bit integer.
FTInt64 = FieldType(C.OFTInteger64)
//FTIntList is a List of 32bit integers.
FTIntList = FieldType(C.OFTIntegerList)
//FTRealList is a List of doubles.
FTRealList = FieldType(C.OFTRealList)
//FTStringList is a Array of strings.
FTStringList = FieldType(C.OFTStringList)
//FTBinary is a Raw Binary data.
FTBinary = FieldType(C.OFTBinary)
//FTDate is a Date.
FTDate = FieldType(C.OFTDate)
//FTTime is a Time.
FTTime = FieldType(C.OFTTime)
//FTDateTime is a Date and Time.
FTDateTime = FieldType(C.OFTDateTime)
//FTInt64List is a List of 64bit integers.
FTInt64List = FieldType(C.OFTInteger64List)
//FTUnknown allow to handle deprecated types like WideString or WideStringList
FTUnknown = FieldType(C.OFTMaxType + 1)
)
// FieldDefinition defines a single attribute
type FieldDefinition struct {
name string
ftype FieldType
}
// NewFieldDefinition creates a FieldDefinition
func NewFieldDefinition(name string, fdtype FieldType) *FieldDefinition {
return &FieldDefinition{
name: name,
ftype: fdtype,
}
}
func (fd *FieldDefinition) setCreateLayerOpt(o *createLayerOpts) {
o.fields = append(o.fields, fd)
}
func (fd *FieldDefinition) createHandle() C.OGRFieldDefnH {
cfname := unsafe.Pointer(C.CString(fd.name))
defer C.free(cfname)
cfd := C.OGR_Fld_Create((*C.char)(cfname), C.OGRFieldType(fd.ftype))
return cfd
}
// VectorTranslate runs the library version of ogr2ogr
// See the ogr2ogr doc page to determine the valid flags/opts that can be set in switches.
//
// Example switches :
//
// []string{
// "-f", "GeoJSON",
// "-t_srs","epsg:3857",
// "-dstalpha"}
//
// Creation options and Driver may be set either in the switches slice with
//
// switches:=[]string{"-dsco","TILED=YES", "-f","GeoJSON"}
//
// or through Options with
//
// ds.VectorTranslate(dst, switches, CreationOption("TILED=YES","BLOCKXSIZE=256"), GeoJSON)
func (ds *Dataset) VectorTranslate(dstDS string, switches []string, opts ...DatasetVectorTranslateOption) (*Dataset, error) {
gopts := dsVectorTranslateOpts{}
for _, opt := range opts {
opt.setDatasetVectorTranslateOpt(&gopts)
}
for _, copt := range gopts.creation {
switches = append(switches, "-dsco", copt)
}
if gopts.driver != "" {
dname := string(gopts.driver)
if dm, ok := driverMappings[gopts.driver]; ok {
dname = dm.vectorName
}
switches = append(switches, "-f", dname)
}
cswitches := sliceToCStringArray(switches)
defer cswitches.free()
cname := unsafe.Pointer(C.CString(dstDS))
defer C.free(cname)
cgc := createCGOContext(gopts.config, gopts.errorHandler)
hndl := C.godalDatasetVectorTranslate(cgc.cPointer(), (*C.char)(cname), ds.handle(), cswitches.cPointer())
if err := cgc.close(); err != nil {
return nil, err
}
return &Dataset{majorObject{C.GDALMajorObjectH(hndl)}}, nil
}
// Layer wraps an OGRLayerH
type Layer struct {
majorObject
}
// handle returns a pointer to the underlying GDALRasterBandH
func (layer Layer) handle() C.OGRLayerH {
return C.OGRLayerH(layer.majorObject.cHandle)
}
// Name returns the layer name
func (layer Layer) Name() string {
return C.GoString(C.OGR_L_GetName(layer.handle()))
}
// Type returns the layer geometry type.
func (layer Layer) Type() GeometryType {
return GeometryType(C.OGR_L_GetGeomType(layer.handle()))
}
// Bounds returns the layer's envelope in the order minx,miny,maxx,maxy
func (layer Layer) Bounds(opts ...BoundsOption) ([4]float64, error) {
bo := boundsOpts{}
for _, o := range opts {
o.setBoundsOpt(&bo)
}
var env C.OGREnvelope
cgc := createCGOContext(nil, bo.errorHandler)
C.godalLayerGetExtent(cgc.cPointer(), layer.handle(), &env)
if err := cgc.close(); err != nil {
return [4]float64{}, err
}
bnds := [4]float64{
float64(env.MinX),
float64(env.MinY),
float64(env.MaxX),
float64(env.MaxY),
}
if bo.sr == nil {
return bnds, nil
}
sr := layer.SpatialRef()
defer sr.Close()
bnds, err := reprojectBounds(bnds, sr, bo.sr)
if err != nil {
return [4]float64{}, err
}
return bnds, nil
}
// FeatureCount returns the number of features in the layer
func (layer Layer) FeatureCount(opts ...FeatureCountOption) (int, error) {
fco := &featureCountOpts{}
for _, o := range opts {
o.setFeatureCountOpt(fco)
}
var count C.int
cgc := createCGOContext(nil, fco.errorHandler)
C.godalLayerFeatureCount(cgc.cPointer(), layer.handle(), &count)
if err := cgc.close(); err != nil {
return 0, err
}
return int(count), nil
}
// Layers returns all dataset layers
func (ds *Dataset) Layers() []Layer {
clayers := C.godalVectorLayers(ds.handle())
if clayers == nil {
return nil
}
defer C.free(unsafe.Pointer(clayers))
//https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
sLayers := (*[1 << 30]C.OGRLayerH)(unsafe.Pointer(clayers))
layers := []Layer{}
i := 0
for {
if sLayers[i] == nil {
return layers
}
layers = append(layers, Layer{majorObject{C.GDALMajorObjectH(sLayers[i])}})
i++
}
}
// SpatialRef returns dataset projection.
func (layer Layer) SpatialRef() *SpatialRef {
hndl := C.OGR_L_GetSpatialRef(layer.handle())
return &SpatialRef{handle: hndl, isOwned: false}
}
// Geometry wraps a OGRGeometryH
type Geometry struct {
isOwned bool
handle C.OGRGeometryH
}
// Area computes the area for geometries of type LinearRing, Polygon or MultiPolygon (returns zero for other types).
// The area is in square units of the spatial reference system in use.
func (g *Geometry) Area() float64 {
return float64(C.OGR_G_Area(g.handle))
}
// Name fetch WKT name for geometry type.
func (g *Geometry) Name() string {
return C.GoString(C.OGR_G_GetGeometryName(g.handle))
}
// GeometryCount fetch the number of elements in a geometry or number of geometries in container.
// Only geometries of type Polygon, MultiPoint, MultiLineString, MultiPolygon or GeometryCollection may return a valid value.
// Other geometry types will silently return 0.
// For a polygon, the returned number is the number of rings (exterior ring + interior rings).
func (g *Geometry) GeometryCount() int {
return int(C.OGR_G_GetGeometryCount(g.handle))
}
// Type fetch geometry type.
func (g *Geometry) Type() GeometryType {
return GeometryType(C.OGR_G_GetGeometryType(g.handle))
}
// Simplify simplifies the geometry with the given tolerance
func (g *Geometry) Simplify(tolerance float64, opts ...SimplifyOption) (*Geometry, error) {
so := &simplifyOpts{}
for _, o := range opts {
o.setSimplifyOpt(so)
}
cgc := createCGOContext(nil, so.errorHandler)
hndl := C.godal_OGR_G_Simplify(cgc.cPointer(), g.handle, C.double(tolerance))
if err := cgc.close(); err != nil {
return nil, err
}
return &Geometry{
isOwned: true,
handle: hndl,
}, nil
}
// Buffer buffers the geometry
func (g *Geometry) Buffer(distance float64, segments int, opts ...BufferOption) (*Geometry, error) {
bo := &bufferOpts{}
for _, o := range opts {
o.setBufferOpt(bo)
}
cgc := createCGOContext(nil, bo.errorHandler)
hndl := C.godal_OGR_G_Buffer(cgc.cPointer(), g.handle, C.double(distance), C.int(segments))
if err := cgc.close(); err != nil {
return nil, err
}
return &Geometry{
isOwned: true,
handle: hndl,
}, nil
}
// Difference generates a new geometry which is the region of this geometry with the region of the other geometry removed.
func (g *Geometry) Difference(other *Geometry, opts ...DifferenceOption) (*Geometry, error) {
// If other geometry is nil, GDAL crashes
if other == nil || other.handle == nil {
return nil, errors.New("other geometry is empty")
}
do := &differenceOpts{}
for _, o := range opts {
o.setDifferenceOpt(do)
}
cgc := createCGOContext(nil, do.errorHandler)
hndl := C.godal_OGR_G_Difference(cgc.cPointer(), g.handle, other.handle)
if err := cgc.close(); err != nil {
return nil, err
}
return &Geometry{
isOwned: true,
handle: hndl,
}, nil
}
// AddGeometry add a geometry to a geometry container.
func (g *Geometry) AddGeometry(subGeom *Geometry, opts ...AddGeometryOption) error {
ago := &addGeometryOpts{}
for _, o := range opts {
o.setAddGeometryOpt(ago)
}
cgc := createCGOContext(nil, ago.errorHandler)
C.godal_OGR_G_AddGeometry(cgc.cPointer(), g.handle, subGeom.handle)
return cgc.close()
}
// ForceToMultiPolygon convert to multipolygon.
func (g *Geometry) ForceToMultiPolygon() *Geometry {
hndl := C.OGR_G_ForceToMultiPolygon(g.handle)
return &Geometry{
isOwned: true,
handle: hndl,
}
}
// ForceToPolygon convert to polygon.
func (g *Geometry) ForceToPolygon() *Geometry {
hndl := C.OGR_G_ForceToPolygon(g.handle)
return &Geometry{
isOwned: true,
handle: hndl,
}
}
// SubGeometry Fetch geometry from a geometry container.
func (g *Geometry) SubGeometry(subGeomIndex int, opts ...SubGeometryOption) (*Geometry, error) {
so := &subGeometryOpts{}
for _, o := range opts {
o.setSubGeometryOpt(so)
}
cgc := createCGOContext(nil, so.errorHandler)
hndl := C.godal_OGR_G_GetGeometryRef(cgc.cPointer(), g.handle, C.int(subGeomIndex))
if err := cgc.close(); err != nil {
return nil, err
}
return &Geometry{
isOwned: false,
handle: hndl,
}, nil
}
// Intersects determines whether two geometries intersect. If GEOS is enabled, then
// this is done in rigorous fashion otherwise TRUE is returned if the
// envelopes (bounding boxes) of the two geometries overlap.
func (g *Geometry) Intersects(other *Geometry, opts ...IntersectsOption) (bool, error) {
bo := &intersectsOpts{}
for _, o := range opts {
o.setIntersectsOpt(bo)
}
cgc := createCGOContext(nil, bo.errorHandler)
ret := C.godal_OGR_G_Intersects(cgc.cPointer(), g.handle, other.handle)
if err := cgc.close(); err != nil {
return false, err
}
return ret != 0, nil
}
// Intersection generates a new geometry which is the region of intersection of the two geometries operated on.
func (g *Geometry) Intersection(other *Geometry, opts ...IntersectionOption) (*Geometry, error) {
// If other geometry is nil, GDAL crashes
if other == nil || other.handle == nil {
return nil, errors.New("other geometry is empty")
}
io := &intersectionOpts{}
for _, o := range opts {
o.setIntersectionOpt(io)
}
cgc := createCGOContext(nil, io.errorHandler)
hndl := C.godal_OGR_G_Intersection(cgc.cPointer(), g.handle, other.handle)
if err := cgc.close(); err != nil {
return nil, err
}
return &Geometry{
isOwned: true,
handle: hndl,
}, nil
}
// Union generates a new geometry which is the region of union of the two geometries operated on.
func (g *Geometry) Union(other *Geometry, opts ...UnionOption) (*Geometry, error) {
// If other geometry is nil, GDAL crashes
if other == nil || other.handle == nil {
return nil, errors.New("other geometry is empty")
}
uo := &unionOpts{}
for _, o := range opts {
o.setUnionOpt(uo)
}
cgc := createCGOContext(nil, uo.errorHandler)
hndl := C.godal_OGR_G_Union(cgc.cPointer(), g.handle, other.handle)
if err := cgc.close(); err != nil {
return nil, err
}
return &Geometry{
isOwned: true,
handle: hndl,
}, nil
}
// Contains tests if this geometry contains the other geometry.
func (g *Geometry) Contains(other *Geometry) bool {
ret := C.OGR_G_Contains(g.handle, other.handle)
return ret != 0
}
// Empty returns true if the geometry is empty
func (g *Geometry) Empty() bool {
ret := C.OGR_G_IsEmpty(g.handle)
return ret != 0
}
// Valid returns true is the geometry is valid
func (g *Geometry) Valid() bool {
ret := C.OGR_G_IsValid(g.handle)
return ret != 0
}
// Bounds returns the geometry's envelope in the order minx,miny,maxx,maxy
func (g *Geometry) Bounds(opts ...BoundsOption) ([4]float64, error) {
bo := boundsOpts{}
for _, o := range opts {
o.setBoundsOpt(&bo)
}
var env C.OGREnvelope
C.OGR_G_GetEnvelope(g.handle, &env)
bnds := [4]float64{
float64(env.MinX),
float64(env.MinY),
float64(env.MaxX),
float64(env.MaxY),
}
if bo.sr == nil {
return bnds, nil
}
sr := g.SpatialRef()
defer sr.Close()
ret, err := reprojectBounds(bnds, sr, bo.sr)
if err != nil {
return bnds, err
}
return ret, nil
}
// Close may reclaim memory from geometry. Must be called exactly once.
func (g *Geometry) Close() {
if g.handle == nil {
return
//panic("geometry already closed")
}
if g.isOwned {
C.OGR_G_DestroyGeometry(g.handle)
}
g.handle = nil
}
// Feature is a Layer feature
type Feature struct {
handle C.OGRFeatureH
}
// Geometry returns a handle to the feature's geometry
func (f *Feature) Geometry() *Geometry {
hndl := C.OGR_F_GetGeometryRef(f.handle)
return &Geometry{
isOwned: false,
handle: hndl,
}
}
// SetGeometry overwrites the feature's geometry
func (f *Feature) SetGeometry(geom *Geometry, opts ...SetGeometryOption) error {
sgo := &setGeometryOpts{}
for _, o := range opts {
o.setSetGeometryOpt(sgo)
}
cgc := createCGOContext(nil, sgo.errorHandler)
C.godalFeatureSetGeometry(cgc.cPointer(), f.handle, geom.handle)
return cgc.close()
}
// SetGeometryColumnName set the name of feature first geometry field.
// Deprecated when running with GDAL 3.6+, use SetGeometryColumnName on Layer instead.
// No more supported when running with GDAL 3.9+.
func (f *Feature) SetGeometryColumnName(name string, opts ...SetGeometryColumnNameOption) error {
so := &setGeometryColumnNameOpts{}
for _, o := range opts {
o.setGeometryColumnNameOpt(so)
}
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
cgc := createCGOContext(nil, so.errorHandler)
C.godalFeatureSetGeometryColumnName(cgc.cPointer(), f.handle, (*C.char)(cname))
return cgc.close()
}
// SetFID set feature identifier
func (f *Feature) SetFID(fid int64) {
// OGR error returned is always none, so we don't handle it
C.OGR_F_SetFID(f.handle, C.GIntBig(fid))
}
// SetFieldValue set feature's field value
func (f *Feature) SetFieldValue(field Field, value interface{}, opts ...SetFieldValueOption) error {
sfvo := &setFieldValueOpts{}
for _, o := range opts {
o.setSetFieldValueOpt(sfvo)
}
cgc := createCGOContext(nil, sfvo.errorHandler)
switch field.ftype {
case FTInt:
intValue, ok := value.(int)
if !ok {
return errors.New("value for this field must be of type 'int'")
}
C.godalFeatureSetFieldInteger(cgc.cPointer(), f.handle, C.int(field.index), C.int(intValue))
case FTInt64:
int64Value, ok := value.(int64)
if !ok {
return errors.New("value for this field must be of type 'int64'")
}
C.godalFeatureSetFieldInteger64(cgc.cPointer(), f.handle, C.int(field.index), C.longlong(int64Value))
case FTReal:
floatValue, ok := value.(float64)
if !ok {
return errors.New("value for this field must be of type 'float64'")
}
C.godalFeatureSetFieldDouble(cgc.cPointer(), f.handle, C.int(field.index), C.double(floatValue))
case FTString:
stringValue, ok := value.(string)
if !ok {
return errors.New("value for this field must be of type 'string'")
}
cval := C.CString(stringValue)
defer C.free(unsafe.Pointer(cval))
C.godalFeatureSetFieldString(cgc.cPointer(), f.handle, C.int(field.index), cval)
case FTDate, FTTime, FTDateTime:
timeValue, ok := value.(time.Time)
if !ok {
return errors.New("value for this field must be of type 'time.Time'")
}
timeZone := 0 // 0=unknown, 1=localtime, 100=GMT, 101=GMT+15minute, 99=GMT-15minute...
if timeValue.Location() == time.Local {
timeZone = 1
} else {
_, offset := timeValue.Zone()
timeZone = offset/60/15 + 100
}
C.godalFeatureSetFieldDateTime(
cgc.cPointer(),
f.handle,
C.int(field.index),
C.int(timeValue.Year()),
C.int(timeValue.Month()),
C.int(timeValue.Day()),
C.int(timeValue.Hour()),
C.int(timeValue.Minute()),
C.int(timeValue.Second()),
C.int(timeZone),
)
case FTIntList:
intListValue, ok := value.([]int)
if !ok {
return errors.New("value for this field must be of type '[]int'")
}
C.godalFeatureSetFieldIntegerList(cgc.cPointer(), f.handle, C.int(field.index), C.int(len(intListValue)), cIntArray(intListValue))
case FTInt64List:
int64ListValue, ok := value.([]int64)
if !ok {
return errors.New("value for this field must be of type '[]int64'")
}
C.godalFeatureSetFieldInteger64List(cgc.cPointer(), f.handle, C.int(field.index), C.int(len(int64ListValue)), cLongArray(int64ListValue))
case FTRealList:
float64ListValue, ok := value.([]float64)
if !ok {
return errors.New("value for this field must be of type '[]float64'")
}
C.godalFeatureSetFieldDoubleList(cgc.cPointer(), f.handle, C.int(field.index), C.int(len(float64ListValue)), cDoubleArray(float64ListValue))
case FTStringList:
stringListValue, ok := value.([]string)
if !ok {
return errors.New("value for this field must be of type '[]float64'")
}
cArray := sliceToCStringArray(stringListValue)
C.godalFeatureSetFieldStringList(cgc.cPointer(), f.handle, C.int(field.index), cArray.cPointer())
cArray.free()
case FTBinary:
bytesValue, ok := value.([]byte)
if !ok {
return errors.New("value for this field must be of type '[]byte'")
}
C.godalFeatureSetFieldBinary(cgc.cPointer(), f.handle, C.int(field.index), C.int(len(bytesValue)), unsafe.Pointer(&bytesValue[0]))
default:
cgc.close() //avoid resource leak
return errors.New("setting value is not implemented for this type of field")
}
return cgc.close()
}
// Field is a Feature attribute
type Field struct {
index int
isSet bool
ftype FieldType
val interface{}
}
// IsSet returns if the field has ever been assigned a value or not.
func (fld Field) IsSet() bool {
return fld.isSet
}
// Type returns the field's native type
func (fld Field) Type() FieldType {
return fld.ftype
}
// Int returns the Field as an integer
func (fld Field) Int() int64 {
switch fld.ftype {
case FTInt, FTInt64:
return fld.val.(int64)
case FTReal:
return int64(fld.val.(float64))
case FTString:
ii, _ := strconv.Atoi(fld.val.(string))
return int64(ii)
default:
return 0
}
}
// Float returns the field as a float64
func (fld Field) Float() float64 {
switch fld.ftype {
case FTInt, FTInt64:
return float64(fld.val.(int64))
case FTReal:
return fld.val.(float64)
case FTString:
ii, _ := strconv.ParseFloat(fld.val.(string), 64)
return ii
default:
return 0
}
}
// String returns the field as a string
func (fld Field) String() string {
switch fld.ftype {
case FTInt, FTInt64:
return fmt.Sprintf("%d", fld.val.(int64))
case FTReal:
return fmt.Sprintf("%f", fld.val.(float64))
case FTString:
return fld.val.(string)
default:
return ""
}
}
// Bytes returns the field as a byte slice
func (fld Field) Bytes() []byte {
switch fld.ftype {
case FTBinary:
return fld.val.([]byte)
default:
return nil
}
}
// DateTime returns the field as a date time
func (fld Field) DateTime() *time.Time {
switch fld.ftype {
case FTDate, FTTime, FTDateTime:
return fld.val.(*time.Time)
default:
return nil
}
}
// IntList returns the field as a list of integer
func (fld Field) IntList() []int64 {
switch fld.ftype {
case FTIntList, FTInt64List:
return fld.val.([]int64)
default:
return nil
}
}
// FloatList returns the field as a list of float64
func (fld Field) FloatList() []float64 {
switch fld.ftype {
case FTRealList:
return fld.val.([]float64)
default:
return nil
}
}
// StringList returns the field as a list of string
func (fld Field) StringList() []string {
switch fld.ftype {
case FTStringList:
return fld.val.([]string)
default:
return nil
}
}
// Fields returns all the Feature's fields
func (f *Feature) Fields() map[string]Field {
fcount := C.OGR_F_GetFieldCount(f.handle)
if fcount == 0 {
return nil
}
retm := make(map[string]Field)
for fid := C.int(0); fid < fcount; fid++ {
fdefn := C.OGR_F_GetFieldDefnRef(f.handle, fid)
fname := C.GoString(C.OGR_Fld_GetNameRef(fdefn))
ftype := C.OGR_Fld_GetType(fdefn)
fld := Field{
index: int(fid),
isSet: C.OGR_F_IsFieldSet(f.handle, fid) != 0,
}
switch ftype {
case C.OFTInteger:
fld.ftype = FTInt
fld.val = int64(C.OGR_F_GetFieldAsInteger64(f.handle, fid))
case C.OFTInteger64:
fld.ftype = FTInt64
fld.val = int64(C.OGR_F_GetFieldAsInteger64(f.handle, fid))
case C.OFTReal:
fld.ftype = FTReal
fld.val = float64(C.OGR_F_GetFieldAsDouble(f.handle, fid))
case C.OFTString:
fld.ftype = FTString
fld.val = C.GoString(C.OGR_F_GetFieldAsString(f.handle, fid))
case C.OFTDate:
fld.ftype = FTDate
fld.val = f.getFieldAsDateTime(fid)
case C.OFTTime:
fld.ftype = FTTime
fld.val = f.getFieldAsDateTime(fid)
case C.OFTDateTime:
fld.ftype = FTDateTime
fld.val = f.getFieldAsDateTime(fid)
case C.OFTIntegerList:
fld.ftype = FTIntList
var length C.int
cArray := C.OGR_F_GetFieldAsIntegerList(f.handle, fid, &length)
fld.val = cIntArrayToSlice(cArray, length)
case C.OFTInteger64List:
fld.ftype = FTInt64List
var length C.int
cArray := C.OGR_F_GetFieldAsInteger64List(f.handle, fid, &length)
fld.val = cLongArrayToSlice(cArray, length)
case C.OFTRealList:
fld.ftype = FTRealList
var length C.int
cArray := C.OGR_F_GetFieldAsDoubleList(f.handle, fid, &length)
fld.val = cDoubleArrayToSlice(cArray, length)
case C.OFTStringList:
fld.ftype = FTStringList
cArray := C.OGR_F_GetFieldAsStringList(f.handle, fid)
fld.val = cStringArrayToSlice(cArray)
case C.OFTBinary:
fld.ftype = FTBinary
var length C.int
cArray := C.OGR_F_GetFieldAsBinary(f.handle, fid, &length)
var slice []byte
if cArray != nil {
slice = C.GoBytes(unsafe.Pointer(cArray), length)
}
fld.val = slice
default:
// Only deprecated field types like FTWideString & WideStringList should be handled by default case
fld.ftype = FTUnknown
}
retm[fname] = fld
}
return retm
}
// Fetch field as date and time
func (f *Feature) getFieldAsDateTime(index C.int) *time.Time {
var year, month, day, hour, minute, second, tzFlag int
ret := C.OGR_F_GetFieldAsDateTime(
f.handle,
index,
(*C.int)(unsafe.Pointer(&year)),
(*C.int)(unsafe.Pointer(&month)),
(*C.int)(unsafe.Pointer(&day)),
(*C.int)(unsafe.Pointer(&hour)),
(*C.int)(unsafe.Pointer(&minute)),
(*C.int)(unsafe.Pointer(&second)),
(*C.int)(unsafe.Pointer(&tzFlag)),
)
if ret != 0 {
var location *time.Location
// 0=unknown, 1=localtime, 100=GMT, 101=GMT+15minute, 99=GMT-15minute...
switch tzFlag {
case 0:
location = &time.Location{}
case 1:
location = time.Local
default:
location = time.FixedZone(fmt.Sprintf("zone_%d", tzFlag), (tzFlag-100)*15*60)
}
t := time.Date(year, time.Month(month), day, hour, minute, second, 0, location)
return &t
}
return nil
}
// Close releases resources associated to a feature
func (f *Feature) Close() {
if f.handle == nil {
return
//panic("feature closed more than once")
}
C.OGR_F_Destroy(f.handle)
f.handle = nil
}
// ResetReading makes Layer.NextFeature return the first feature of the layer
func (layer Layer) ResetReading() {
C.OGR_L_ResetReading(layer.handle())
}
// NextFeature returns the layer's next feature, or nil if there are no mo features
func (layer Layer) NextFeature() *Feature {
hndl := C.OGR_L_GetNextFeature(layer.handle())
if hndl == nil {
return nil
}
return &Feature{hndl}
}
// CreateFeature creates a feature on Layer
func (layer Layer) CreateFeature(feat *Feature, opts ...CreateFeatureOption) error {
cfo := createFeatureOpts{}
for _, opt := range opts {
opt.setCreateFeatureOpt(&cfo)
}
cgc := createCGOContext(nil, cfo.errorHandler)
C.godalLayerCreateFeature(cgc.cPointer(), layer.handle(), feat.handle)
if err := cgc.close(); err != nil {
return err
}
return nil
}
// NewFeature creates a feature on Layer from a geometry
func (layer Layer) NewFeature(geom *Geometry, opts ...NewFeatureOption) (*Feature, error) {
nfo := newFeatureOpts{}
for _, opt := range opts {
opt.setNewFeatureOpt(&nfo)
}
ghandle := C.OGRGeometryH(nil)
if geom != nil {
ghandle = geom.handle
}
cgc := createCGOContext(nil, nfo.errorHandler)
hndl := C.godalLayerNewFeature(cgc.cPointer(), layer.handle(), ghandle)
if err := cgc.close(); err != nil {
return nil, err
}
return &Feature{hndl}, nil
}
// UpdateFeature rewrites an updated feature in the Layer
func (layer Layer) UpdateFeature(feat *Feature, opts ...UpdateFeatureOption) error {
uo := &updateFeatureOpts{}
for _, o := range opts {
o.setUpdateFeatureOpt(uo)
}
cgc := createCGOContext(nil, uo.errorHandler)
C.godalLayerSetFeature(cgc.cPointer(), layer.handle(), feat.handle)
return cgc.close()
}
// DeleteFeature deletes feature from the Layer.
func (layer Layer) DeleteFeature(feat *Feature, opts ...DeleteFeatureOption) error {
do := &deleteFeatureOpts{}
for _, o := range opts {
o.setDeleteFeatureOpt(do)
}
cgc := createCGOContext(nil, do.errorHandler)
C.godalLayerDeleteFeature(cgc.cPointer(), layer.handle(), feat.handle)
return cgc.close()
}
// SetGeometryColumnName set the name of feature first geometry field.
// Only supported when running with GDAL 3.6+.
func (layer Layer) SetGeometryColumnName(name string, opts ...SetGeometryColumnNameOption) error {
so := &setGeometryColumnNameOpts{}
for _, o := range opts {
o.setGeometryColumnNameOpt(so)
}
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
cgc := createCGOContext(nil, so.errorHandler)
C.godalLayerSetGeometryColumnName(cgc.cPointer(), layer.handle(), (*C.char)(cname))
return cgc.close()
}
// CreateLayer creates a new vector layer
//
// Available CreateLayerOptions are
// - FieldDefinition (may be used multiple times) to add attribute fields to the layer
func (ds *Dataset) CreateLayer(name string, sr *SpatialRef, gtype GeometryType, opts ...CreateLayerOption) (Layer, error) {
co := createLayerOpts{}
for _, opt := range opts {
opt.setCreateLayerOpt(&co)
}
srHandle := C.OGRSpatialReferenceH(nil)
if sr != nil {
srHandle = sr.handle
}
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
cgc := createCGOContext(nil, co.errorHandler)
hndl := C.godalCreateLayer(cgc.cPointer(), ds.handle(), (*C.char)(unsafe.Pointer(cname)), srHandle, C.OGRwkbGeometryType(gtype))
if err := cgc.close(); err != nil {
return Layer{}, err
}
if len(co.fields) > 0 {
for _, fld := range co.fields {
fhndl := fld.createHandle()
//TODO error checking
C.OGR_L_CreateField(hndl, fhndl, C.int(0))
C.OGR_Fld_Destroy(fhndl)
}
}
return Layer{majorObject{C.GDALMajorObjectH(hndl)}}, nil
}
// CopyLayer Duplicate an existing layer.
func (ds *Dataset) CopyLayer(source Layer, name string, opts ...CopyLayerOption) (Layer, error) {
co := copyLayerOpts{}
for _, opt := range opts {
opt.setCopyLayerOpt(&co)
}
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
cgc := createCGOContext(nil, co.errorHandler)
hndl := C.godalCopyLayer(cgc.cPointer(), ds.handle(), source.handle(), (*C.char)(unsafe.Pointer(cname)))
if err := cgc.close(); err != nil {
return Layer{}, err
}
return Layer{majorObject{C.GDALMajorObjectH(hndl)}}, nil
}
// LayerByName fetch a layer by name. Returns nil if not found.
func (ds *Dataset) LayerByName(name string) *Layer {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
hndl := C.GDALDatasetGetLayerByName(ds.handle(), (*C.char)(unsafe.Pointer(cname)))
if hndl == nil {
return nil
}
return &Layer{majorObject{C.GDALMajorObjectH(hndl)}}
}
// NewGeometryFromGeoJSON creates a new Geometry from its GeoJSON representation
func NewGeometryFromGeoJSON(geoJSON string, opts ...NewGeometryOption) (*Geometry, error) {
no := &newGeometryOpts{}
for _, o := range opts {
o.setNewGeometryOpt(no)
}
cgeoJSON := C.CString(geoJSON)
defer C.free(unsafe.Pointer(cgeoJSON))
cgc := createCGOContext(nil, no.errorHandler)
hndl := C.godalNewGeometryFromGeoJSON(cgc.cPointer(), (*C.char)(unsafe.Pointer(cgeoJSON)))
if err := cgc.close(); err != nil {
return nil, err
}
return &Geometry{isOwned: true, handle: hndl}, nil
}
// NewGeometryFromWKT creates a new Geometry from its WKT representation
func NewGeometryFromWKT(wkt string, sr *SpatialRef, opts ...NewGeometryOption) (*Geometry, error) {
no := &newGeometryOpts{}
for _, o := range opts {
o.setNewGeometryOpt(no)
}
srHandle := C.OGRSpatialReferenceH(nil)
if sr != nil {
srHandle = sr.handle
}
cwkt := C.CString(wkt)
defer C.free(unsafe.Pointer(cwkt))
cgc := createCGOContext(nil, no.errorHandler)
hndl := C.godalNewGeometryFromWKT(cgc.cPointer(), (*C.char)(unsafe.Pointer(cwkt)), srHandle)
if err := cgc.close(); err != nil {
return nil, err
}
return &Geometry{isOwned: true, handle: hndl}, nil
}
// NewGeometryFromWKB creates a new Geometry from its WKB representation
func NewGeometryFromWKB(wkb []byte, sr *SpatialRef, opts ...NewGeometryOption) (*Geometry, error) {
no := &newGeometryOpts{}
for _, o := range opts {
o.setNewGeometryOpt(no)
}
srHandle := C.OGRSpatialReferenceH(nil)
if sr != nil {
srHandle = sr.handle
}
cgc := createCGOContext(nil, no.errorHandler)
hndl := C.godalNewGeometryFromWKB(cgc.cPointer(), unsafe.Pointer(&wkb[0]), C.int(len(wkb)), srHandle)
if err := cgc.close(); err != nil {
return nil, err
}
return &Geometry{isOwned: true, handle: hndl}, nil
}
// WKT returns the Geomtry's WKT representation
func (g *Geometry) WKT(opts ...GeometryWKTOption) (string, error) {
wo := &geometryWKTOpts{}
for _, o := range opts {
o.setGeometryWKTOpt(wo)
}
cgc := createCGOContext(nil, wo.errorHandler)
cwkt := C.godalExportGeometryWKT(cgc.cPointer(), g.handle)
if err := cgc.close(); err != nil {
return "", err
}
wkt := C.GoString(cwkt)
C.CPLFree(unsafe.Pointer(cwkt))
return wkt, nil
}
// WKB returns the Geomtry's WKB representation
func (g *Geometry) WKB(opts ...GeometryWKBOption) ([]byte, error) {
wo := &geometryWKBOpts{}
for _, o := range opts {
o.setGeometryWKBOpt(wo)
}
var cwkb unsafe.Pointer
clen := C.int(0)
cgc := createCGOContext(nil, wo.errorHandler)
C.godalExportGeometryWKB(cgc.cPointer(), &cwkb, &clen, g.handle)
if err := cgc.close(); err != nil {
return nil, err
}
wkb := C.GoBytes(unsafe.Pointer(cwkb), clen)
C.free(unsafe.Pointer(cwkb))
return wkb, nil
}
// SpatialRef returns the geometry's SpatialRef
func (g *Geometry) SpatialRef() *SpatialRef {
hndl := C.OGR_G_GetSpatialReference(g.handle)
return &SpatialRef{
handle: hndl,
isOwned: false,
}
}
// SetSpatialRef assigns the given SpatialRef to the Geometry. It does not
// perform an actual reprojection.
func (g *Geometry) SetSpatialRef(sr *SpatialRef) {
C.OGR_G_AssignSpatialReference(g.handle, sr.handle)
}
// Reproject reprojects the given geometry to the given SpatialRef
func (g *Geometry) Reproject(to *SpatialRef, opts ...GeometryReprojectOption) error {
gr := &geometryReprojectOpts{}
for _, o := range opts {
o.setGeometryReprojectOpt(gr)
}
cgc := createCGOContext(nil, gr.errorHandler)
C.godalGeometryTransformTo(cgc.cPointer(), g.handle, to.handle)
return cgc.close()
}
// Transform transforms the given geometry. g is expected to already be
// in the supplied Transform source SpatialRef.
func (g *Geometry) Transform(trn *Transform, opts ...GeometryTransformOption) error {
gt := &geometryTransformOpts{}
for _, o := range opts {
o.setGeometryTransformOpt(gt)
}
cgc := createCGOContext(nil, gt.errorHandler)
C.godalGeometryTransform(cgc.cPointer(), g.handle, trn.handle, trn.dst)
return cgc.close()
}
// GeoJSON returns the geometry in geojson format. The geometry is expected to be in epsg:4326
// projection per RFCxxx
//
// Available GeoJSONOptions are
// - SignificantDigits(n int) to keep n significant digits after the decimal separator (default: 8)
func (g *Geometry) GeoJSON(opts ...GeoJSONOption) (string, error) {
gjo := geojsonOpts{
precision: 7,
}
for _, opt := range opts {
opt.setGeojsonOpt(&gjo)
}
cgc := createCGOContext(nil, gjo.errorHandler)
gjdata := C.godalExportGeometryGeoJSON(cgc.cPointer(), g.handle, C.int(gjo.precision))
if err := cgc.close(); err != nil {
return "", err
}
wkt := C.GoString(gjdata)
C.CPLFree(unsafe.Pointer(gjdata))
return wkt, nil
}
// GML returns the geometry in GML format.
// See the GDAL exportToGML doc page to determine the GML conversion options that can be set through CreationOption.
//
// Example of conversion options :
//
// g.GML(CreationOption("FORMAT=GML3","GML3_LONGSRS=YES"))
func (g *Geometry) GML(opts ...GMLExportOption) (string, error) {
gmlo := &gmlExportOpts{}
for _, o := range opts {
o.setGMLExportOpt(gmlo)
}
switches := make([]string, len(gmlo.creation))
for i, copt := range gmlo.creation {
switches[i] = copt
}
cswitches := sliceToCStringArray(switches)
defer cswitches.free()
cgc := createCGOContext(nil, gmlo.errorHandler)
cgml := C.godalExportGeometryGML(cgc.cPointer(), g.handle, cswitches.cPointer())
if err := cgc.close(); err != nil {
return "", err
}
gml := C.GoString(cgml)
C.CPLFree(unsafe.Pointer(cgml))
return gml, nil
}
// VSIFile is a handler around gdal's vsi handlers
type VSIFile struct {
handle *C.VSILFILE
}
// VSIOpen opens path. path can be virtual, eg beginning with /vsimem/
func VSIOpen(path string, opts ...VSIOpenOption) (*VSIFile, error) {
vo := &vsiOpenOpts{}
for _, o := range opts {
o.setVSIOpenOpt(vo)
}
cname := unsafe.Pointer(C.CString(path))
defer C.free(cname)
cgc := createCGOContext(nil, vo.errorHandler)
hndl := C.godalVSIOpen(cgc.cPointer(), (*C.char)(cname))
if err := cgc.close(); err != nil {
return nil, err
}
return &VSIFile{hndl}, nil
}
// Close closes the VSIFile. Must be called exactly once.
func (vf *VSIFile) Close() error {
if vf.handle == nil {
return fmt.Errorf("already closed")
}
errmsg := C.godalVSIClose(vf.handle)
vf.handle = nil
if errmsg != nil {
defer C.free(unsafe.Pointer(errmsg))
return errors.New(C.GoString(errmsg))
}
return nil
}
// VSIUnlink deletes path
func VSIUnlink(path string, opts ...VSIUnlinkOption) error {
vo := &vsiUnlinkOpts{}
for _, o := range opts {
o.setVSIUnlinkOpt(vo)
}
cname := unsafe.Pointer(C.CString(path))
defer C.free(cname)
cgc := createCGOContext(nil, vo.errorHandler)
C.godalVSIUnlink(cgc.cPointer(), (*C.char)(cname))
return cgc.close()
}
var _ io.ReadCloser = &VSIFile{}
// Read is the standard io.Reader interface
func (vf *VSIFile) Read(buf []byte) (int, error) {
if len(buf) == 0 {
return 0, nil
}
var errmsg *C.char
n := C.godalVSIRead(vf.handle, unsafe.Pointer(&buf[0]), C.int(len(buf)), &errmsg)
if errmsg != nil {
defer C.free(unsafe.Pointer(errmsg))
return int(n), errors.New(C.GoString(errmsg))
}
if int(n) != len(buf) {
return int(n), io.EOF
}
return int(n), nil
}
// KeySizerReaderAt is the interface expected when calling RegisterVSIHandler
//
// ReadAt() is a standard io.ReaderAt that takes a key (i.e. filename) as argument.
//
// Size() is used as a probe to determine wether the given key exists, and should return
// an error if no such key exists. The actual file size may or may not be effectively used
// depending on the underlying GDAL driver opening the file
//
// It may also optionally implement KeyMultiReader which will be used (only?) by
// the GTiff driver when reading pixels. If not provided, this
// VSI implementation will concurrently call ReadAt([]byte,int64)
type KeySizerReaderAt interface {
ReadAt(key string, buf []byte, off int64) (int, error)
Size(key string) (int64, error)
}
// KeyMultiReader is an optional interface that can be implemented by KeyReaderAtSizer that
// will be used (only?) by the GTiff driver when reading pixels. If not provided, this
// VSI implementation will concurrently call ReadAt(key,[]byte,int64)
type KeyMultiReader interface {
ReadAtMulti(key string, bufs [][]byte, offs []int64) ([]int, error)
}
//export _gogdalSizeCallback
func _gogdalSizeCallback(ckey *C.char, errorString **C.char) C.longlong {
key := C.GoString(ckey)
cbd, err := getGoGDALReader(key)
if err != nil {
*errorString = C.CString(err.Error())
return -1
}
if cbd.prefix > 0 {
key = key[cbd.prefix:]
}
l, err := cbd.Size(key)
if err != nil {
*errorString = C.CString(err.Error())
}
return C.longlong(l)
}
//export _gogdalMultiReadCallback
func _gogdalMultiReadCallback(ckey *C.char, nRanges C.int, pocbuffers unsafe.Pointer, coffsets unsafe.Pointer, clengths unsafe.Pointer, errorString **C.char) C.int {
key := C.GoString(ckey)
cbd, err := getGoGDALReader(key)
if err != nil {
*errorString = C.CString(err.Error())
return -1
}
/* cbd == nil would be a bug elsewhere */
if cbd.prefix > 0 {
key = key[cbd.prefix:]
}
n := int(nRanges)
cbuffers := (*[1 << 28]unsafe.Pointer)(unsafe.Pointer(pocbuffers))[:n:n]
lengths := (*[1 << 28]C.size_t)(unsafe.Pointer(clengths))[:n:n]
offsets := (*[1 << 28]C.ulonglong)(unsafe.Pointer(coffsets))[:n:n]
buffers := make([][]byte, n)
goffsets := make([]int64, n)
ret := int64(0)
for b := range buffers {
l := int(lengths[b])
buffers[b] = (*[1 << 28]byte)(unsafe.Pointer(cbuffers[b]))[:l:l]
goffsets[b] = int64(offsets[b])
}
_, err = cbd.ReadAtMulti(key, buffers, goffsets)
if err != nil && err != io.EOF {
*errorString = C.CString(err.Error())
ret = -1
}
return C.int(ret)
}
//export _gogdalReadCallback
func _gogdalReadCallback(ckey *C.char, buffer unsafe.Pointer, off C.size_t, clen C.size_t, errorString **C.char) C.size_t {
l := int(clen)
key := C.GoString(ckey)
cbd, err := getGoGDALReader(key)
if err != nil {
*errorString = C.CString(err.Error())
return 0
}
if cbd.prefix > 0 {
key = key[cbd.prefix:]
}
slice := (*[1 << 28]byte)(buffer)[:l:l]
rlen, err := cbd.ReadAt(key, slice, int64(off))
if err != nil && err != io.EOF {
*errorString = C.CString(err.Error())
}
return C.size_t(rlen)
}
var handlers map[string]vsiHandler
func getGoGDALReader(key string) (vsiHandler, error) {
for prefix, handler := range handlers {
if strings.HasPrefix(key, prefix) {
return handler, nil
}
}
return vsiHandler{}, fmt.Errorf("no handler registered")
}
type vsiHandler struct {
KeySizerReaderAt
prefix int
}
func (sp vsiHandler) ReadAtMulti(key string, bufs [][]byte, offs []int64) ([]int, error) {
if mcbd, ok := sp.KeySizerReaderAt.(KeyMultiReader); ok {
return mcbd.ReadAtMulti(key, bufs, offs)
}
var wg sync.WaitGroup
wg.Add(len(bufs))
lens := make([]int, len(bufs))
var err error
var errmu sync.Mutex
for b := range bufs {
go func(bidx int) {
var berr error
defer wg.Done()
lens[bidx], berr = sp.ReadAt(key, bufs[bidx], offs[bidx])
if berr != nil && berr != io.EOF {
errmu.Lock()
if err == nil {
err = berr
}
errmu.Unlock()
}
if lens[bidx] != int(len(bufs[bidx])) {
errmu.Lock()
if err == nil {
if berr != nil {
err = berr
} else {
err = fmt.Errorf("short read")
}
}
errmu.Unlock()
}
}(b)
}
wg.Wait()
return lens, err
}
// RegisterVSIHandler registers an osio.Adapter on the given prefix.
// When registering an adapter with
//
// RegisterVSIHandler("scheme://",handler)
//
// calling Open("scheme://myfile.txt") will result in godal making calls to
//
// adapter.Reader("myfile.txt").ReadAt(buf,offset)
func RegisterVSIHandler(prefix string, handler KeySizerReaderAt, opts ...VSIHandlerOption) error {
opt := vsiHandlerOpts{
bufferSize: 64 * 1024,
cacheSize: 2 * 64 * 1024,
stripPrefix: false,
}
for _, o := range opts {
o.setVSIHandlerOpt(&opt)
}
if handlers == nil {
handlers = make(map[string]vsiHandler)
}
if _, ok := handlers[prefix]; ok {
return fmt.Errorf("handler already registered on prefix")
}
cgc := createCGOContext(nil, opt.errorHandler)
C.VSIInstallGoHandler(cgc.cPointer(), C.CString(prefix), C.size_t(opt.bufferSize), C.size_t(opt.cacheSize))
if err := cgc.close(); err != nil {
return err
}
if opt.stripPrefix {
handlers[prefix] = vsiHandler{handler, len(prefix)}
} else {
handlers[prefix] = vsiHandler{handler, 0}
}
return nil
}
// BuildVRT runs the GDALBuildVRT function and creates a VRT dataset from a list of datasets
func BuildVRT(dstVRTName string, sourceDatasets []string, switches []string, opts ...BuildVRTOption) (*Dataset, error) {
bvo := buildVRTOpts{}
for _, o := range opts {
o.setBuildVRTOpt(&bvo)
}
if bvo.resampling != Nearest {
switches = append(switches, "-r", bvo.resampling.String())
}
for _, b := range bvo.bands {
switches = append(switches, "-b", fmt.Sprintf("%d", b))
}
for _, oo := range bvo.openOptions {
switches = append(switches, "-oo", oo)
}
cswitches := sliceToCStringArray(switches)
defer cswitches.free()
cname := unsafe.Pointer(C.CString(dstVRTName))
defer C.free(cname)
csources := sliceToCStringArray(sourceDatasets)
defer csources.free()
cgc := createCGOContext(bvo.config, bvo.errorHandler)
hndl := C.godalBuildVRT(cgc.cPointer(), (*C.char)(cname), csources.cPointer(),
cswitches.cPointer())
if err := cgc.close(); err != nil {
return nil, err
}
return &Dataset{majorObject{C.GDALMajorObjectH(hndl)}}, nil
}
// GridCreate, creates a grid from scattered data, given provided gridding parameters as a string (pszAlgorithm)
// and the arguments required for `godalGridCreate()` (binding for GDALGridCreate)
//
// NOTE: For valid gridding algorithm strings see: https://gdal.org/programs/gdal_grid.html#interpolation-algorithms
func GridCreate(pszAlgorithm string,
xCoords []float64,
yCoords []float64,
zCoords []float64,
dfXMin float64,
dfXMax float64,
dfYMin float64,
dfYMax float64,
nXSize int,
nYSize int,
buffer interface{},
opts ...GridCreateOption,
) error {
if len(xCoords) != len(yCoords) || len(yCoords) != len(zCoords) {
return errors.New("`xCoords`, `yCoords` and `zCoords` are not all equal length")
}
gco := gridCreateOpts{}
for _, o := range opts {
o.setGridCreateOpt(&gco)
}
griddingAlgStr := strings.Split(pszAlgorithm, ":")[0]
algCEnum, err := gridAlgFromString(griddingAlgStr)
if err != nil {
return err
}
var (
params = unsafe.Pointer(C.CString(pszAlgorithm))
cgc = createCGOContext(nil, gco.errorHandler)
)
defer C.free(params)
var (
dtype = bufferType(buffer)
dsize = dtype.Size()
numGridBytes = C.int(nXSize * nYSize * dsize)
cBuf = cBuffer(buffer, int(numGridBytes)/dsize)
)
cgc = createCGOContext(nil, gco.errorHandler)
C.godalGridCreate(cgc.cPointer(), (*C.char)(params), algCEnum, C.uint(len(xCoords)), cDoubleArray(xCoords), cDoubleArray(yCoords), cDoubleArray(zCoords), C.double(dfXMin), C.double(dfXMax), C.double(dfYMin), C.double(dfYMax), C.uint(nXSize), C.uint(nYSize), C.GDALDataType(dtype), cBuf)
if err := cgc.close(); err != nil {
return err
}
return nil
}
// Grid runs the library version of gdal_grid.
// See the gdal_grid doc page to determine the valid flags/opts that can be set in switches.
//
// Example switches :
//
// []string{"-a", "maximum", "-txe", "0", "1"}
//
// Creation options and driver may be set in the switches slice with
//
// switches:=[]string{"-co","TILED=YES","-of","GTiff"}
//
// NOTE: Some switches are NOT compatible with this binding, as a `nullptr` is passed to a later call to
// `GDALGridOptionsNew()` (as the 2nd argument). Those switches are: "-oo", "-q", "-quiet"
func (ds *Dataset) Grid(destPath string, switches []string, opts ...GridOption) (*Dataset, error) {
gridOpts := gridOpts{}
for _, opt := range opts {
opt.setGridOpt(&gridOpts)
}
cswitches := sliceToCStringArray(switches)
defer cswitches.free()
dest := unsafe.Pointer(C.CString(destPath))
cgc := createCGOContext(nil, gridOpts.errorHandler)
var dsRet C.GDALDatasetH
defer C.free(unsafe.Pointer(dest))
dsRet = C.godalGrid(cgc.cPointer(), (*C.char)(dest), ds.handle(), cswitches.cPointer())
if err := cgc.close(); err != nil {
return nil, err
}
return &Dataset{majorObject{C.GDALMajorObjectH(dsRet)}}, nil
}
// Dem runs the library version of gdaldem.
// See the gdaldem doc page to determine the valid flags/opts that can be set in switches.
//
// Example switches (for "hillshade", switches differ per mode):
//
// []string{"-s", "111120", "-alt", "45"}
//
// Creation options and driver may be set in the switches slice with
//
// switches:=[]string{"-co","TILED=YES","-of","GTiff"}
//
// NOTE: `colorFilename` is a "text-based color configuration file" that MUST ONLY be
// provided when `processingMode` == "color-relief"
func (ds *Dataset) Dem(destPath, processingMode string, colorFilename string, switches []string, opts ...DemOption) (*Dataset, error) {
demOpts := demOpts{}
for _, opt := range opts {
opt.setDemOpt(&demOpts)
}
cswitches := sliceToCStringArray(switches)
defer cswitches.free()
dest := unsafe.Pointer(C.CString(destPath))
defer C.free(unsafe.Pointer(dest))
alg := unsafe.Pointer(C.CString(processingMode))
defer C.free(unsafe.Pointer(alg))
var colorFn *C.char
if colorFilename != "" {
colorFn = C.CString(colorFilename)
defer C.free(unsafe.Pointer(colorFn))
}
cgc := createCGOContext(nil, demOpts.errorHandler)
dsRet := C.godalDem(cgc.cPointer(), (*C.char)(dest), (*C.char)(alg), colorFn, ds.handle(), cswitches.cPointer())
if err := cgc.close(); err != nil {
return nil, err
}
return &Dataset{majorObject{C.GDALMajorObjectH(dsRet)}}, nil
}
// Nearblack runs the library version of nearblack
//
// See the nearblack doc page to determine the valid flags/opts that can be set in switches.
//
// Example switches :
//
// []string{"-white", "-near", "10"}
//
// Creation options and driver may be set in the switches slice with
//
// switches:=[]string{"-co","TILED=YES","-of","GTiff"}
//
// NOTE: Some switches are NOT compatible with this binding, as a `nullptr` is passed to a later call to
// `GDALNearblackOptionsNew()` (as the 2nd argument). Those switches are: "-o", "-q", "-quiet"
func (ds *Dataset) Nearblack(dstDS string, switches []string, opts ...NearblackOption) (*Dataset, error) {
nearBlackOpts := nearBlackOpts{}
for _, opt := range opts {
opt.setNearblackOpt(&nearBlackOpts)
}
cswitches := sliceToCStringArray(switches)
defer cswitches.free()
dest := unsafe.Pointer(C.CString(dstDS))
defer C.free(dest)
cgc := createCGOContext(nil, nearBlackOpts.errorHandler)
ret, err := C.godalNearblack(cgc.cPointer(), (*C.char)(dest), nil, ds.handle(), cswitches.cPointer())
if err = cgc.close(); err != nil {
return nil, err
}
return &Dataset{majorObject{C.GDALMajorObjectH(ret)}}, nil
}
// NearblackInto writes the provided `sourceDs` into the Dataset that this method was called on, and
// runs the library version of nearblack.
//
// See the nearblack doc page to determine the valid flags/opts that can be set in switches.
//
// Example switches :
//
// []string{"-white", "-near", "10"}
//
// Creation options and driver may be set in the switches slice with
//
// switches:=[]string{"-co","TILED=YES","-of","GTiff"}
//
// NOTE: Some switches are NOT compatible with this binding, as a `nullptr` is passed to a later call to
// `GDALNearblackOptionsNew()` (as the 2nd argument). Those switches are: "-o", "-q", "-quiet"
func (ds *Dataset) NearblackInto(sourceDs *Dataset, switches []string, opts ...NearblackOption) error {
nearBlackOpts := nearBlackOpts{}
for _, opt := range opts {
opt.setNearblackOpt(&nearBlackOpts)
}
cswitches := sliceToCStringArray(switches)
defer cswitches.free()
cgc := createCGOContext(nil, nearBlackOpts.errorHandler)
var srcDsHandle C.GDALDatasetH = nil
if sourceDs != nil {
srcDsHandle = sourceDs.handle()
}
_ = C.godalNearblack(cgc.cPointer(), nil, ds.handle(), srcDsHandle, cswitches.cPointer())
if err := cgc.close(); err != nil {
return err
}
return nil
}
// GCP mirrors the structure of the GDAL_GCP type
type GCP struct {
PszId string
PszInfo string
DfGCPPixel float64
DfGCPLine float64
DfGCPX float64
DfGCPY float64
DfGCPZ float64
}
// gdalGCPToGoGCPArray is a utility function for conversion from `C.GCPsAndCount` (GDAL) to `[]GCP` (Go)
func gdalGCPToGoGCPArray(gcp C.GCPsAndCount) []GCP {
var ret []GCP
if gcp.gcpList == nil {
return ret
}
//https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
gcps := (*[1 << 30]C.GDAL_GCP)(unsafe.Pointer(gcp.gcpList))
ret = make([]GCP, gcp.numGCPs)
for i := 0; i < len(ret); i++ {
ret[i] = GCP{
PszId: C.GoString(gcps[i].pszId),
PszInfo: C.GoString(gcps[i].pszInfo),
DfGCPPixel: float64(gcps[i].dfGCPPixel),
DfGCPLine: float64(gcps[i].dfGCPLine),
DfGCPX: float64(gcps[i].dfGCPX),
DfGCPY: float64(gcps[i].dfGCPY),
DfGCPZ: float64(gcps[i].dfGCPZ),
}
}
return ret
}
// GetGCPSpatialRef runs the GDALGetGCPSpatialRef function
func (ds *Dataset) GCPSpatialRef() *SpatialRef {
return &SpatialRef{handle: C.godalGetGCPSpatialRef(ds.handle()), isOwned: false}
}
// GetGCPs runs the GDALGetGCPs function
func (ds *Dataset) GCPs() []GCP {
gcpsAndCount := C.godalGetGCPs(ds.handle())
return gdalGCPToGoGCPArray(gcpsAndCount)
}
// GetGCPProjection runs the GDALGetGCPProjection function
func (ds *Dataset) GCPProjection() string {
return C.GoString(C.godalGetGCPProjection(ds.handle()))
}
// SetGCPs runs the GDALSetGCPs function
func (ds *Dataset) SetGCPs(GCPList []GCP, opts ...SetGCPsOption) error {
setGCPsOpts := setGCPsOpts{}
for _, opt := range opts {
opt.setSetGCPsOpt(&setGCPsOpts)
}
// Convert `[]GCP` -> `C.goGCPList`
var gcpList C.goGCPList
var (
ids = make([]string, len(GCPList))
infos = make([]string, len(GCPList))
gcpPixels = make([]float64, len(GCPList))
gcpLines = make([]float64, len(GCPList))
gcpXs = make([]float64, len(GCPList))
gcpYs = make([]float64, len(GCPList))
gcpZs = make([]float64, len(GCPList))
)
for i, g := range GCPList {
ids[i] = g.PszId
infos[i] = g.PszInfo
gcpPixels[i] = (g.DfGCPPixel)
gcpLines[i] = (g.DfGCPLine)
gcpXs[i] = (g.DfGCPX)
gcpYs[i] = (g.DfGCPY)
gcpZs[i] = (g.DfGCPZ)
}
cIds := sliceToCStringArray(ids)
defer cIds.free()
cInfos := sliceToCStringArray(infos)
defer cInfos.free()
gcpList.pszIds = cIds.cPointer()
gcpList.pszInfos = cInfos.cPointer()
gcpList.dfGCPPixels = cDoubleArray(gcpPixels)
gcpList.dfGCPLines = cDoubleArray(gcpLines)
gcpList.dfGCPXs = cDoubleArray(gcpXs)
gcpList.dfGCPYs = cDoubleArray(gcpYs)
gcpList.dfGCPZs = cDoubleArray(gcpZs)
cgc := createCGOContext(nil, setGCPsOpts.errorHandler)
if setGCPsOpts.sr != nil {
C.godalSetGCPs2(cgc.cPointer(), ds.handle(), C.int(len(GCPList)), gcpList, setGCPsOpts.sr.handle)
} else {
GCPProj := C.CString(setGCPsOpts.projString)
defer C.free(unsafe.Pointer(GCPProj))
C.godalSetGCPs(cgc.cPointer(), ds.handle(), C.int(len(GCPList)), gcpList, GCPProj)
}
if err := cgc.close(); err != nil {
return err
}
return nil
}
// Convert list of GCPs to a GDAL GeoTransorm array
func GCPsToGeoTransform(GCPList []GCP, opts ...GCPsToGeoTransformOption) ([6]float64, error) {
gco := gcpsToGeoTransformOpts{}
for _, opt := range opts {
opt.setGCPsToGeoTransformOpts(&gco)
}
// Convert `[]GCP` -> `C.goGCPList`
var gcpList C.goGCPList
var (
ids = make([]string, len(GCPList))
infos = make([]string, len(GCPList))
gcpPixels = make([]float64, len(GCPList))
gcpLines = make([]float64, len(GCPList))
gcpXs = make([]float64, len(GCPList))
gcpYs = make([]float64, len(GCPList))
gcpZs = make([]float64, len(GCPList))
)
for i, g := range GCPList {
ids[i] = g.PszId
infos[i] = g.PszInfo
gcpPixels[i] = (g.DfGCPPixel)
gcpLines[i] = (g.DfGCPLine)
gcpXs[i] = (g.DfGCPX)
gcpYs[i] = (g.DfGCPY)
gcpZs[i] = (g.DfGCPZ)
}
cIds := sliceToCStringArray(ids)
defer cIds.free()
cInfos := sliceToCStringArray(infos)
defer cInfos.free()
gcpList.pszIds = cIds.cPointer()
gcpList.pszInfos = cInfos.cPointer()
gcpList.dfGCPPixels = cDoubleArray(gcpPixels)
gcpList.dfGCPLines = cDoubleArray(gcpLines)
gcpList.dfGCPXs = cDoubleArray(gcpXs)
gcpList.dfGCPYs = cDoubleArray(gcpYs)
gcpList.dfGCPZs = cDoubleArray(gcpZs)
gt := make([]C.double, 6)
cgt := (*C.double)(unsafe.Pointer(&gt[0]))
ret := [6]float64{}
var cgc = createCGOContext(nil, gco.errorHandler)
C.godalGCPListToGeoTransform(cgc.cPointer(), gcpList, C.int(len(GCPList)), cgt)
if err := cgc.close(); err != nil {
return ret, err
}
// Copy the values from the C Array into a Go array
for i := range gt {
ret[i] = float64(gt[i])
}
return ret, nil
}
type cgoContext struct {
cctx *C.cctx
opts cStringArray
}
func createCGOContext(configOptions []string, eh ErrorHandler) cgoContext {
cgc := cgoContext{
opts: sliceToCStringArray(configOptions),
cctx: (*C.cctx)(C.malloc(C.size_t(unsafe.Sizeof(C.cctx{})))),
}
cgc.cctx.configOptions = cgc.opts.cPointer()
cgc.cctx.failed = 0
cgc.cctx.errMessage = nil
if eh != nil {
cgc.cctx.handlerIdx = C.int(registerErrorHandler(eh))
} else {
cgc.cctx.handlerIdx = 0
}
return cgc
}
func (cgc cgoContext) cPointer() *C.cctx {
return cgc.cctx
}
// frees the context and returns any error it may contain
func (cgc cgoContext) close() error {
cgc.opts.free()
defer C.free(unsafe.Pointer(cgc.cctx))
if cgc.cctx.errMessage != nil {
/* debug code
if cgc.cctx.handlerIdx != 0 {
panic("bug!")
}
*/
defer C.free(unsafe.Pointer(cgc.cctx.errMessage))
return errors.New(C.GoString(cgc.cctx.errMessage))
}
if cgc.cctx.handlerIdx != 0 {
defer unregisterErrorHandler(int(cgc.cctx.handlerIdx))
return getErrorHandler(int(cgc.cctx.handlerIdx)).err
}
return nil
}