// 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 #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(>[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(>[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 }