SpaceIndices.jl API

This package defines an API to allow user to defin new space indices. We describe this API in the following.

Structure

Each space index set must have a structure that has SpaceIndexSet as its super-type. This structure must contain all the required field to process and return the indices provided by the set.

struct MySpaceIndex <: SpaceIndexSet
    ...
end

Required API Functions

We must define the following functions for every space index set defined as in the previous section.

function SpaceIndices.urls(::Type{T}) where T<:SpaceIndexSet -> Vector{String}

This function must return a Vector{String} with the URLs to download the files for the indices. For example:

SpaceIndices.urls(::Type{MySpaceIndex}) = ["https://url.for.my/space.file.txt"]

function SpaceIndices.expiry_periods(::Type{T}) where T<:SpaceIndexSet -> Vector{DatePeriod}

This function must return the list with the expiry periods for the files in the space index set T. The remote files will always be downloaded if a time greater than this period has elapsed after the last download. For example:

get_filenames(::Type{MySpaceIndex}) = [Day(7)]

SpaceIndices.parse_files(::Type{T}, filepaths::Vector{String}) where T<:SpaceIndexSet -> T

This function must parse the files related to the space index set T using the files in filepaths and return an instance of T with the parsed data. For example,

function SpaceIndices.parse_files(::Type{MySpaceIndex}, filepaths::Vector{String})
    for filepath in filepaths
        open(filepath, "r") do f
            ...
        end
    end
        
    return MySpaceIndex(...)
end

Finally, the new space index set must also implement a set of functions with the following signature:

SpaceIndices.space_index(::Val{:index}, instant::DateTime; kwargs...) -> Number
SpaceIndices.space_index(::Val{:index}, jd::Number; kwargs...) -> Number

where the space index for the instant will be returned.

Optional API Functions

function SpaceIndices.auto_init(::Type{T}) where T<:SpaceIndexSet -> Bool

This function returns whether the space index set T should be initialized automatically by the no-argument init(). Sets that return false must be initialized explicitly via init(T). By default, it always returns true.


function SpaceIndices.filenames(::Type{T}) where T<:SpaceIndexSet -> Vector{String}

This function can return a Vector{String} with the names of the remote files. The system will used this information to save the data in the package scratch space. For example:

SpaceIndices.filenames(::Type{MySpaceIndex}) = ["my_space_file.txt"]

If this function is not defined, the filename will be obtained from the URL using the function basename.


function SpaceIndices.indices(::Type{T}) where T<:SpaceIndexSet -> Tuple

This function can return a tuple with the symbols of the space indices provided by the space index set T. This information is used, for example, by the function SpaceIndices.status to list the indices of the registered sets. For example:

SpaceIndices.indices(::Type{MySpaceIndex}) = (:MyIndex,)

If this function is not defined, it returns an empty tuple.


function SpaceIndices.timespan(::Val{:index}) -> Union{Nothing, NTuple{2, Float64}}

This function can return a tuple with the initial and final Julian days (UTC) in which the data for the space index is available. If this information is returned, it must be guaranteed that calling space_index for the same index does not fail for any instant inside the returned interval. For example:

function SpaceIndices.timespan(::Val{:MyIndex})
    obj = SpaceIndices.@object(MySpaceIndex)
    return (first(obj.vjd), last(obj.vjd))
end

If this function is not defined for the index, it returns nothing, meaning that there is no information about the interval in which the index is available.

Warning

All functions that return a Vector must return an array with the same number of elements.

Example: Leap Seconds

We will use the API to define a new space index set that has the GPS leap seconds. The file has a CSV-like format but the values are separated by ;. It has two columns. The first contains the Julian days in which the leap seconds were modified to the values in the second column:

Julian Day;Leap Seconds
2441499.500000;11.0
2441683.500000;12.0
2442048.500000;13.0
2442413.500000;14.0
...

First, we need to load the required packages to process the information in the space index file:

julia> using DelimitedFiles, Dates

Now, we need to create its structure:

struct LeapSeconds <: SpaceIndexSet
    jd::Vector{Float64}
    leap_seconds::Vector{Float64}
end

where jd contains the Julian days in which the leap seconds were modified to the values in leap_seconds.

We also need to overload the API functions:

SpaceIndices.urls(::Type{LeapSeconds}) = ["https://ronanarraes.com/space-indices/leap_seconds.csv"]
SpaceIndices.expiry_periods(::Type{LeapSeconds}) = [Day(365)]

function SpaceIndices.parse_files(::Type{LeapSeconds}, filepaths::Vector{String})
    filepath = first(filepaths)
    raw_data, ~ = readdlm(filepath, ';'; header = true)
    return LeapSeconds(raw_data[:, 1], raw_data[:, 2])
end

We also need to populate the function space_index with the supported index in this file:

function SpaceIndices.space_index(::Val{:LeapSeconds}, instant::DateTime)
    obj = SpaceIndices.@object(LeapSeconds)
    jd = datetime2julian(instant)
    id = findfirst(>=(jd_utc), obj.jd)

    if isnothing(id)
        id = length(obj.jd)
    end

    return obj.leap_seconds[id]
end

Finally, we need to register the new space index file:

SpaceIndices.@register LeapSeconds

We can now use the SpaceIndices.jl system to fetch the information:

julia> SpaceIndices.init()
[ Info: Downloading the file 'DTCFILE.TXT' from 'http://sol.spacenvironment.net/jb2008/indices/DTCFILE.TXT'...
[ Info: Downloading the file 'SOLFSMY.TXT' from 'http://sol.spacenvironment.net/jb2008/indices/SOLFSMY.TXT'...
[ Info: Downloading the file 'SW-All.csv' from 'https://celestrak.org/SpaceData/SW-All.csv'...

julia> space_index(Val(:LeapSeconds), now())
37.0