Swiftpack.co -  patrick-zippenfenig/SwiftNetCDF as Swift Package
Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.
patrick-zippenfenig/SwiftNetCDF
Read and write NetCDF files in Swift
.package(url: "https://github.com/patrick-zippenfenig/SwiftNetCDF.git", from: "1.0.0")

SwiftNetCDF

Swift 5 SPM Platforms codebeat badge CircleCI

SwiftNetCDF is a library to read and write NetCDF files in Swift with type safety.

Installation

  1. SwiftNetCDF requires the NetCDF C client library which can be installed on Mac with brew install netcdf or on Linux with sudo apt install libnetcdf-dev.

  2. Add SwiftNetCDF as a dependency to your Package.swift

  dependencies: [
    .package(url: "https://github.com/patrick-zippenfenig/SwiftNetCDF.git", from: "1.0.0")
  ],
  targets: [
    .target(name: "MyApp", dependencies: ["SwiftNetCDF"])
  ]
  1. Build your project:
$ swift build

Usage

  1. Write NetCDF files
import SwiftNetCDF

let data = [Int32(0), 3, 4, 6, 12, 45, 89, ...]

var file = try NetCDF.create(path: "test.nc", overwriteExisting: true)

try file.setAttribute("TITLE", "My data set")

let dimensions = [
  try file.createDimension(name: "LAT", length: 10),
  try file.createDimension(name: "LON", length: 5)
]

let variable = try file.createVariable(name: "MyData", type: Int32.self, dimensions: dimensions)
try variable.write(data)
  1. Read NetCDF files
import SwiftNetCDF

guard let file = try NetCDF.open(path: "test.nc", allowUpdate: false) else {
    fatalError("File test.nc does not exist")
}

guard let title: String = try file.getAttribute("TITLE")?.read() else {
    fatalError("TITLE attribute not available or not a String")
}

guard let variable = file.getVariable(name: "MyData") else {
    fatalError("No variable named MyData available")
}
guard let typedVariable = variable.asType(Int32.self) else {
    fatalError("MyData is not a Int32 type")
}
let data2 = try typedVariable.read(offset: [1,1], count: [2,2])
  1. Using groups, unlimited dimensions and compression
import SwiftNetCDF

let file = try NetCDF.create(path: "test.nc", overwriteExisting: true)

// Create new group. Analog the `getGroup(name: )` function can be used for existing groups
let subGroup = try file.createGroup(name: "GROUP1")

let dimLat = try subGroup.createDimension(name: "LAT", length: 10)
let dimLon = try subGroup.createDimension(name: "LON", length: 5, isUnlimited: true)

var lats = try subGroup.createVariable(name: "LATITUDES", type: Float.self, dimensions: [dimLat])
var lons = try subGroup.createVariable(name: "LONGITUDES", type: Float.self, dimensions: [dimLon])

try lats.write((0..<10).map(Float.init))
try lons.write((0..<5).map(Float.init))

// `data` is of type `VariableGeneric<Float>`. Define functions can be accessed via `data.variable`
var data = try subGroup.createVariable(name: "DATA", type: Float.self, dimensions: [dimLat, dimLon])

// Enable compression, shuffle filter and chunking
try data.defineDeflate(enable: true, level: 6, shuffle: true)
try data.defineChunking(chunking: .chunked, chunks: [1, 5])

/// Because the latitude dimension is unlimted, we can write more than the defined size
let array = (0..<1000).map(Float.init)
try data.write(array, offset: [0, 0], count: [10, 100])

/// The check the new dimension count
XCTAssertEqual(data.dimensionsFlat, [10, 100])

// even more data at an offset
try data.write(array, offset: [0, 100], count: [10, 100])

XCTAssertEqual(data.dimensionsFlat, [10, 200])
  1. Discover the structure of a NetCDF file
import SwiftNetCDF

guard let file = try NetCDF.open(path: "test.nc", allowUpdate: false) else {
    fatalError("File test.nc does not exist")
}

/// Recursively print all groups
func printGroup(_ group: Group) {
    print("Group: \(group.name)")
    
    for d in group.getDimensions() {
        print("Dimension: \(d.name) \(d.length) \(d.isUnlimited)")
    }
    
    for v in group.getVariables() {
        print("Variable: \(v.name) \(v.type.asExternalDataType()!)")
        for d in v.dimensions {
            print("Variable dimension: \(d.name) \(d.length) \(d.isUnlimited)")
        }
    }
    
    for a in try! group.getAttributes() {
        print("Attribute: \(a.name) \(a.length) \(a.type.asExternalDataType()!)")
    }
    
    for subgroup in group.getGroups() {
        printGroup(subgroup)
    }
}

// The root entry point of a NetCDF file is also a `Group`
printGroup(file)

Output:

Group: /
Group: GROUP1
Dimension: LAT 10 false
Dimension: LON 200 true
Variable: LATITUDES float
Variable dimension: LAT 10 false
Variable: LONGITUDES float
Variable dimension: LON 200 true
Variable: DATA float
Variable dimension: LAT 10 false
Variable dimension: LON 200 true

Features

  • Abstract Swift data types to NetCDF external types
  • Supported data types: Float, Double, String, Int8, Int16, Int32, Int64, Int, UInt16, UInt32, UInt64 and UInt
  • Returns nil for missing files, variables, attributes or data-type mismatch
  • Exceptions are thrown for NetCDF library errors
  • Uses generics to ensure the correct type is being used
  • Thread safe. Access to the netCDF C API is serialised with thread locks

Limitations

  • User defined data tyes not yet implemented

Quick function reference

SwiftNetCDF uses a simple data structures to organise access to NetCDF functions. The most important once are listed below.

struct NetCDF {
    static func create(path: String, overwriteExisting: Bool) -> Group
    static func open(path: String, allowUpdate: Bool) -> Group?
}

struct Group {
    let name: String
    
    func getGroup(name: String) -> Group?
    func getGroups() -> [Group]
    func createGroup(name: String) -> Group
    
    func getDimensions() -> [Dimension]
    func createDimension(name: String, length: Int, isUnlimited: Bool = false) -> Dimension
    
    func getVariable(name: String) -> Variable?
    func getVariables() -> [Variable]
    func createVariable<T>(name: String, type: T.Type, dimensions: [Dimension]) -> VariableGeneric<T>

    func getAttribute(_ key: String) -> Attribute?
    func getAttributes() -> [Attribute]
    func setAttribute<T>(_ name: String, _ value: T)
    func setAttribute<T: NetcdfConvertible>(_ name: String, _ value: [T])
}

struct Variable {
    let name: String
    
    var dimensions: [Dimension]
    var dimensionsFlat: [Int]
    
    /// `Nil` in case of type mismatch
    func asType<T>(_ of: T.Type) -> VariableGeneric<T>?
    
    func defineDeflate(enable: Bool, level: Int = 6, shuffle: Bool = false)
    func defineChunking(chunking: VarId.Chunking, chunks: [Int])
    
    // Same get/set attribute functions as a Group
}

struct VariableGeneric<T> {
    func read() -> [T]
    func read(offset: [Int], count: [Int]) -> [T]
    func read(offset: [Int], count: [Int], stride: [Int]) -> [T]
    
    func write(_ data: [T])
    func write(_ data: [T], offset: [Int], count: [Int])
    func write(_ data: [T], offset: [Int], count: [Int], stride: [Int])
    
    // Same get/set attribute functions as a Group
    // Same define functions as Variable
}

struct Dimension {
    let name: String
    let length: Int
    let isUnlimited: Bool
}

struct Attribute {
    let name: String
    let length: Int
    
    func read<T: NetcdfConvertible>() throws -> T?
    func read<T: NetcdfConvertible>() throws -> [T]?
}

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Please make sure to update tests as appropriate.

License

MIT

GitHub

link
Stars: 7
Last commit: 1 year ago

Ad: Job Offers

iOS Software Engineer @ Perry Street Software
Perry Street Software is Jack’d and SCRUFF. We are two of the world’s largest gay, bi, trans and queer social dating apps on iOS and Android. Our brands reach more than 20 million members worldwide so members can connect, meet and express themselves on a platform that prioritizes privacy and security. We invest heavily into SwiftUI and using Swift Packages to modularize the codebase.

Release Notes

Initial version 1.0.0
1 year ago

First release of SwiftNetCDF! This version is already used at www.meteoblue.com to read and write NetCDF files in production APIs.

Most commonly used features in NetCDF should be available and working. Hopefully no interface changes are required.

Support for user defined data types would be possible, but not on an agenda, because it is really used and more complicated to implement with Swifts fixed type system

If you have any comments and suggestions, feel free to open an issue ticket

Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API