Reading HDF5 files

file:src/Output/H5Reader.jl
module H5Reader

using HDF5
using Unitful
using ..Storage

import ..Abstract: AbstractBundle, load, load_volume, load_slice, load_column
import ..Storage: data_kind, Header, DataHeader, Data, Pack, parse_multi_slice,
    data_sets, data_volumes, data_slices, data_columns

function data_kind(gid::HDF5.Group)
	slice = parse_multi_slice(attrs(gid)["slice"])
	return data_kind(slice...)
end

function data_kind(fid::HDF5.File, group)
	group_name = string(group)
	if group_name == "input"
		return :metadata
	end
    return data_kind(fid[group_name])
end

function group_datasets(fid::HDF5.File)
	result = Dict{Symbol, Vector{String}}(
		:metadata => [],
		:volume => [],
		:slice => [],
		:column => [])

	for k in keys(fid)
		kind = data_kind(fid, k)
		push!(result[kind], k)
	end
	return result
end

function data_header(gid::HDF5.Group)
	slice = parse_multi_slice(attrs(gid)["slice"])
    kind = data_kind(slice...)
    write_interval = attrs(gid)["write_interval"]
    return DataHeader(
        slice=slice, kind=kind, write_interval=write_interval)
end

function read_header(fid)
    attrs = HDF5.attributes(fid["input"])

    axes = Axes(
        fid["input/x"][] * u"m",
        fid["input/y"][] * u"m",
        fid["input/t"][] * u"Myr")

    data_sets = Dict()
    for k in keys(fid)
        if k == "input"
            continue
        end
        data_sets[Symbol(k)] = data_header(fid[k])
    end

    grid_size = (length(axes.x), length(axes.y))
    n_facies = attrs["n_facies"][]

    return Header(
        tag = attrs["tag"][],
        axes = axes,
        Δt = attrs["delta_t"][] * u"Myr",
        time_steps = attrs["time_steps"][],
        grid_size = grid_size,
        n_facies = n_facies,
        initial_topography = fid["input/initial_topography"][] * u"m",
        sea_level = fid["input/sea_level"][] * u"m",
        subsidence_rate = attrs["subsidence_rate"][] * u"m/Myr",
        data_sets = data_sets)
end

function read_data(::Type{Val{dim}}, gid::Union{HDF5.File, HDF5.Group}) where {dim}
	slice = parse_multi_slice(string(attrs(gid)["slice"]))
	write_interval = attrs(gid)["write_interval"]

	reduce(_) = (:)
	reduce(::Int) = 1

	Data{dim+1,dim}(
		slice, write_interval,
		gid["disintegration"][:, reduce.(slice)..., :] * u"m",
		gid["production"][:, reduce.(slice)..., :] * u"m",
		gid["deposition"][:, reduce.(slice)..., :] * u"m",
		gid["bathymetry"][reduce.(slice)..., :] * u"m",
		"active_layer" in keys(gid) ?
		    gid["active_layer"][:, reduce.(slice)..., :] * u"m" :
			nothing, nothing)
end

function read_data(D::Type{Val{dim}}, filename::AbstractString, group) where {dim}
    h5open(filename) do fid
        header = read_header(fid)
		gid = fid[string(group)]
		data = read_data(D, gid)
        header, data
    end
end

read_volume(args...) = read_data(Val{3}, args...)
read_slice(args...) = read_data(Val{2}, args...)
read_column(args...) = read_data(Val{1}, args...)

struct H5Bundle <: AbstractBundle
    fid::HDF5.File
    header::Header
end

Base.close(bundle::H5Bundle) = close(bundle.fid)
header(bundle::H5Bundle) = bundle.header

function load(filename::AbstractString)
    fid = h5open(filename)
    header = read_header(fid)
    bundle = H5Bundle(fid, header)
    return bundle
end

function load_group(D::Type{Val{dim}}, bundle::H5Bundle, group) where {dim}
    gid = bundle.fid[string(group)]
	data = read_data(D, gid)
    return Pack(bundle.header, data)
end

load_volume(bundle::H5Bundle, group::Symbol) = load_group(Val{3}, bundle, group)
load_volume(filename::AbstractString, group::Symbol) = load_volume(load(filename), group)
load_slice(bundle::H5Bundle, group::Symbol) = load_group(Val{2}, bundle, group)
load_slice(filename::AbstractString, group::Symbol) = load_slice(load(filename), group)
load_column(bundle::H5Bundle, group::Symbol) = load_group(Val{1}, bundle, group)
load_column(filename::AbstractString, group::Symbol) = load_column(load(filename), group)

end