JSON + SPICE

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

Tagged with: , ,

Leave a Reply

Your email address will not be published. Required fields are marked *

*