SwiftNetCDF

Swift 5 SPM Platforms codebeat badge Test

SwiftNetCDF 是一个用于在 Swift 中读写 NetCDF 文件的库,并具有类型安全性。

安装

  1. SwiftNetCDF 需要 NetCDF C 客户端库,可以在 Mac 上使用 brew install netcdf 安装,或在 Linux 上使用 sudo apt install libnetcdf-dev 安装。

  2. SwiftNetCDF 作为依赖项添加到您的 Package.swift

  dependencies: [
    .package(url: "https://github.com/patrick-zippenfenig/SwiftNetCDF.git", from: "1.0.0")
  ],
  targets: [
    .target(name: "MyApp", dependencies: ["SwiftNetCDF"])
  ]
  1. 构建您的项目
$ swift build

用法

  1. 写入 NetCDF 文件
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. 读取 NetCDF 文件
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. 使用组、无限制维度和压缩
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. 了解 NetCDF 文件的结构
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)

输出

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

功能特性

局限性

快速函数参考

SwiftNetCDF 使用简单的数据结构来组织对 NetCDF 函数的访问。最重要的函数列在下面。

struct NetCDF {
    static func create(path: String, overwriteExisting: Bool) -> Group
    static func open(path: String, allowUpdate: Bool) -> Group?
    
    /// Opens a NetCDF file from memory in read-only mode
    static func open(memory: UnsafeRawBufferPointer)) -> 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]?
}

贡献代码

欢迎提交 Pull Request。对于重大更改,请先打开一个 issue 讨论您想要更改的内容。

请确保在适当的时候更新测试。

许可证

MIT