SwiftNetCDF 是一个用于在 Swift 中读写 NetCDF 文件的库,并具有类型安全性。
SwiftNetCDF 需要 NetCDF C 客户端库,可以在 Mac 上使用 brew install netcdf
安装,或在 Linux 上使用 sudo apt install libnetcdf-dev
安装。
将 SwiftNetCDF
作为依赖项添加到您的 Package.swift
dependencies: [
.package(url: "https://github.com/patrick-zippenfenig/SwiftNetCDF.git", from: "1.0.0")
],
targets: [
.target(name: "MyApp", dependencies: ["SwiftNetCDF"])
]
$ swift build
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)
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])
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])
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
Float
、Double
、String
、Int8
、Int16
、Int32
、Int64
、Int
、UInt16
、UInt32
、UInt64
和 UInt
nil
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 讨论您想要更改的内容。
请确保在适当的时候更新测试。