SwiftNetCDF

SwiftNetCDF is a library to read and write NetCDF files in Swift with type safety.
Installation
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
.
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"])
]
- Build your project:
$ swift build
Usage
- 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)
- 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])
- 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])
- 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
SwiftNetCDF
SwiftNetCDF is a library to read and write NetCDF files in Swift with type safety.
Installation
SwiftNetCDF requires the NetCDF C client library which can be installed on Mac with
brew install netcdf
or on Linux withsudo apt install libnetcdf-dev
.Add
SwiftNetCDF
as a dependency to yourPackage.swift
Usage
Output:
Features
Float
,Double
,String
,Int8
,Int16
,Int32
,Int64
,Int
,UInt16
,UInt32
,UInt64
andUInt
nil
for missing files, variables, attributes or data-type mismatchLimitations
Quick function reference
SwiftNetCDF uses a simple data structures to organise access to NetCDF functions. The most important once are listed below.
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