Degenerate Conic

Algorithms • Modern Fortran Programming • Orbital Mechanics

Feb 05, 2018

JSON + SPICE

NAIF

I have mentioned various kinds of configuration file formats used in Fortran here before. One that I haven't mentioned is the text PCK file format used by the NAIF SPICE Toolkit. This is a format that is similar in some ways to Fortran namelists, but with a better API for reading the file and querying the contents. Variables read from a PCK file are inserted into the SPICE variable pool. An example PCK file (pck00010.tpc) can be found here.

The PCK file format has some limitations. It doesn't allow inline comments (only block comments separate from the variable declarations). Integers are stored as rounded doubles, and logical variables are not supported at all (you have to use 0.0 or 1.0 for this). One particular annoyance is that the files are not cross platform (the line breaks must match the platform they are being used on). However, SPICELIB also provides routines to programmatically enter variables into the pool. This allows us to create files in other formats that can be used in a SPICE application. For example, we can use JSON-Fortran to read variables in JSON files and insert them into the SPICE pool.

First, let's declare the interfaces to the SPICE routines we need (since SPICELIB is straight up Fortran 77, they are not in a module):

interface
    subroutine pcpool ( name, n, cvals )
    implicit none
    character(len=*) :: name
    integer :: n
    character(len=*) :: cvals ( * )
    end subroutine pcpool

    subroutine pdpool ( name, n, values )
    import :: wp
    implicit none
    character(len=*) :: name
    integer :: n
    real(wp) :: values ( * )
    end subroutine pdpool

    subroutine pipool ( name, n, ivals )
    implicit none
    character(len=*) :: name
    integer :: n
    integer :: ivals ( * )
    end subroutine pipool

    subroutine setmsg ( msg )
    implicit none
    character(len=*) :: msg
    end subroutine setmsg

    subroutine sigerr ( msg )
    implicit none
    character(len=*) :: msg
    end subroutine sigerr
end interface

Then, we can use the following routine:

subroutine json_to_spice(jsonfile)

implicit none

character(len=*),intent(in) :: jsonfile
!! the JSON file to load

integer :: i,j
type(json_core) :: json
type(json_value),pointer :: p,p_var,p_element
real(wp) :: real_val
integer :: var_type,int_val,n_vars,n_children,element_var_type
logical :: found
integer,dimension(:),allocatable :: int_vec
real(wp),dimension(:),allocatable :: real_vec
character(len=:),dimension(:),allocatable :: char_vec
character(len=:),allocatable :: char_val,name
integer,dimension(:),allocatable :: ilen

nullify(p)

! allow for // style comments:
call json%initialize(comment_char='/')

! read the file:
call json%parse(file=jsonfile, p=p)

if (json%failed()) then
    ! we will use the SPICE error handler:
    call setmsg ( 'json_to_spice: Could not load file: '&
                  trim(jsonfile) )
    call sigerr ( 'SPICE(INVALIDJSONFILE)' )
else

    ! how many variables are in the file:
    call json%info(p,n_children=n_vars)
    main : do i = 1, n_vars
        call json%get_child(p, i, p_var, found)

        ! what kind of variable is it?:
        call json%info(p_var,var_type=var_type,name=name)
        if (var_type == json_array) then

            ! how many elements in this array?:
            call json%info(p_var,n_children=n_children)

            ! first make sure all the variables are the same type
            ! [must be integer, real, or character]
            do j = 1, n_children
                call json%get_child(p_var, j, p_element, found)
                call json%info(p_element,var_type=element_var_type)
                if (j==1) then
                    var_type = element_var_type
                else
                    if (var_type /= element_var_type) then
                        call setmsg ( 'json_to_spice: Invalid array ('&
                                      trim(name)//') in file: '//trim(jsonfile) )
                                      call sigerr ( 'SPICE(INVALIDJSONVAR)' )
                        exit main
                    end if
                end if
            end do

            ! now we know the var type, so get as a vector:
            select case (var_type)
            case(json_integer); call json%get(p_var,int_vec)
            case(json_double ); call json%get(p_var,real_vec)
            case(json_string ); call json%get(p_var,char_vec,ilen)
            case default
                call setmsg ( 'json_to_spice: Invalid array ('&
                              trim(name)//') in file: '//trim(jsonfile) )
                call sigerr ( 'SPICE(INVALIDJSONVAR)' )
                exit main
            end select

        else

            ! scalar:
            n_children = 1
            select case (var_type)
            case(json_integer)
                call json%get(p_var,int_val); int_vec = [int_val]
            case(json_double )
                call json%get(p_var,real_val); real_vec = [real_val]
            case(json_string )
                call json%get(p_var,char_val); char_vec = [char_val]
            case default
                call setmsg ( 'json_to_spice: Invalid variable ('&
                              trim(name)//') in file: '//trim(jsonfile) )
                call sigerr ( 'SPICE(INVALIDJSONVAR)' )
                exit main
            end select

        end if

        ! now, add them to the pool:
        select case (var_type)
        case(json_integer)
            call pipool ( name, n_children, int_vec )
        case(json_double )
            call pdpool ( name, n_children, real_vec )
        case(json_string )
            call pcpool ( name, n_children, char_vec )
        end select

    end do main

end if

call json%destroy(p)

end subroutine json_to_spice

Here we are using the SPICE routines PIPOOL, PDPOOL, and PCPOOL to insert variables into the pool. We are also using the built-in SPICE error handling routines (SETMSG and SIGERR) to manage errors. The code works for scalar and array variables. It can be used to read the following example JSON file:

{
    "str": "qwerty", // a scalar string
    "strings": ["a", "ab", "abc", "d"], // a vector of strings
    "i": 8, // a scalar integer
    "ints": [255,127,0], // a vector of integers
    "r": 123.345, // a scalar real
    "reals": [999.345, 5671.8] // a vector of reals
}

After reading it, we can verify that the variables were added to the pool by printing the pool contents using the routine WRPOOL. This produces:

\begindata

str     = 'qwerty'
i       = 0.80000000000000000D+01
strings = ( 'a',
            'ab',
            'abc',
            'd' )
ints    = ( 0.25500000000000000D+03,
            0.12700000000000000D+03,
            0.00000000000000000D+00 )
r       = 0.12334500000000000D+03
reals   = ( 0.99934500000000003D+03,
            0.56718000000000002D+04 )

\begintext

See also