BookmarkSubscribeSubscribe to RSS Feed
Craige_Hales

Staff

Joined:

Mar 21, 2013

Space Station Video

update 15feb2018: see this cookbook item for an easier way

update 10apr2018: see Bryan's script for a modern approach

Early Memory

When I was almost three, my Dad, W4FNB, SK, let me hang out in the radio room while he was listening to the signal from Sputnik.  I remember him explaining how the doppler shift required him to continuously adjust the tuning on the Hammarlund receiver HQ-129X, and how it was important for the receiver to warm up for several hours to stabilize the radio's internal oscillators.  I think he tried to show me something in the sky too, but it would be another four years before the school nurse realized how badly I needed glasses.  Sounds from the First Satellites 

 

More Recently

 

The International Space Station has been in orbit since 1998, or 2000.  This post presented JSL to fetch the ISS current location and plot it on a map built from another NASA image.  I tweaked the JSL a bit and made a video showing a couple of days of 90 minute orbits in less than a minute.

 

 

A few improvements but pretty similar.  I messed up the file names by not filling in the leading zeros that Blender expects; I used JSL to rename the files.

// code repaired 15feb2018, requires JMP 11 or later

// trimmed down JSON parser (go get the real one, there is more support for tables)
/*
* JSL JSON Parser, Xan Gregg, 2014-12-29
* pattern matching speedup, Craige Hales, 2015-01-01
*/
New Namespace( "JSON" );
JSON:Char Escape = Function( {c},
    Match( c, "\!"", "\!"", "b", "\!u0008", "f", "\!u000C", "n", "\!u000A", "r", "\!u000D", "t", "\!u0009", c )
);
JSON:Unicode Escape = Function( {u},
    u = "\!"\!\!u" || u || "\!"";
    Parse( u );
);
JSON:Pattern Name = "\!"" // leading quotation mark
+ Pat Test(
    value = "";
    1;
) // init string accumulator
+ Pat Repeat(
    (Pat Break( "\!\\!"" ) >> temp + Pat Test(
        value ||= temp;
        1;
    )) // run of characters that are not \ or "
    | ("\u" + Pat Len( 4 ) >> temp + Pat Test(
        value ||= JSON:Unicode Escape( temp );
        1;
    )) // \uXXXX
    | ("\" + Pat Len( 1 ) >> temp + Pat Test(
        value ||= JSON:Char Escape( temp );
        1;
    )) // \", for example...embedded quotation mark
) + "\!""; // trailing quotation mark
JSON:Pattern Number = Pat Span( "0123456789-+eE." ) >> temp + Pat Test(
    value = Num( temp );
    1;
);
JSON:PatternParse = Pat Pos( 0 ) + Pat Repeat(
    Pat Pos( /* remember how far we parsed for error message */ ) >> failpos// /* comment out this line for a little more speed */+pattest(if(tickseconds()-lasttime>1,lasttime=tickseconds();wait(0));1)
    + Pat Fence( /* fence off previously parsed text...ain't no going back */ ) + (Pat Regex( "\s*" ) // ignore whitespace
    | JSON:Pattern Name | (":" + Pat Test(
        key = value;
        1;
    )) | JSON:Pattern Number | ("true" + Pat Test(
        value = 1;
        1;
    )) | ("false" + Pat Test(
        value = 0;
        1;
    )) | ("null" + Pat Test(
        value = .;
        1;
    )) | ("[" + Pat Test(
        Insert Into( stack, {{}}, 1 );
        value = Empty();
        1;
    )) | ("{" + Pat Test(
        Insert Into( stack, Associative Array(), 1 );
        value = Empty();
        Insert Into( keys, key, 1 );
        1;
    )) | ("]" + Pat Test(
        If( !Is Empty( value ),
            Insert Into( stack[1], value, 1 /*needs reverse later*/ )
        );
        value = stack[1];
        value = Reverse( value );
        Remove From( stack, 1 );
        1;
    )) | ("}" + Pat Test(
        If( !Is Empty( value ),
            stack[1][key] = value
        );
        value = stack[1];
        Remove From( stack, 1 );
        key = keys[1];
        Remove From( keys, 1 );
        1;
    )) | ("," + Pat Test(
        If( Is List( stack[1] ),
            Insert Into( stack[1], value, 1 /*needs reverse later*/ ),
            stack[1][key] = value
        );
        value = Empty();
        1;
    )))
) + Pat R Pos( 0 );
JSON:jsonParse = Function( {json},
    {stack = {}, keys = {}, key = "", value = Empty(), temp = 0, totalLen = Length( json ), failpos = 0},
    If( !Try( Pat Match( json, JSON:PatternParse ), 0 ),
        Throw( "unrecognized JSON at " || Char( failpos ) )
    );
    value;
);
// end namespace
 
lat = J( 3600, 1, . ); // trail of previous points, you can make it longer
lon = J( N Rows( lat ), 1, . );
idx = 1;
cur = idx;
error = "";
time = "";
dir = "D:\directory\ISS\m";
seq = 0;
/* open-notify.org (Thanks!) returns JSON data like this:
{
  "iss_position": {
    "latitude": 24.94415308475734,
    "longitude": 116.70173230986174
  },
  "message": "success",
  "timestamp": 1462990726
}
*/
updateLatLon = Function( {},
    {where = Try( 
    
    loadtextfile( "http://api.open-notify.org/iss-now.json?" || Char( Random Integer( 1, 1e6 ) ) )
    //Open( "http://api.open-notify.org/iss-now.json?" || Char( Random Integer( 1, 1e6 ) ) )
    
    , "" ), // fetch a JSON string
    locationData = JSON:jsonParse( where ) // use XAN's parser
    },
    If( !Is Empty( locationData ) & locationData["message"] == "success",
        error = ""; // clear any previous message
        lat[idx] = Num( locationData["iss_position"]["latitude"] ); // get the two numbers
        lon[idx] = Num( locationData["iss_position"]["longitude"] ); // used in the script below to plot a point
        time = locationData["timestamp"]; // unix time, not JMP time
        time = Format( 1jan1970 + time, /*"ddMonyyyyh:m:s"*/ "yyyy-mm-ddThh:mm:ss" ) || " GMT";
        cur = idx;
        idx++;
        If( idx > N Rows( lat ),
            idx = 1
        );
    ,
        error = "no location data available"
    );
    Try(
        g << inval;
        Wait( 1 );
        seq++;
        g[framebox( 1 )] << savepicture( dir || Char( seq ) || ".png", "png" );
    ); // if the map exists, update it.
    Schedule( 28, updateLatLon() ); // schedule this function to run again. it would be rude to ask for updates much faster than this.
);
updateLatLon(); // bootstrap call, then it calls itself every 30 seconds
earthLight = Open( "http://eoimages.gsfc.nasa.gov/images/imagerecords/55000/55167/earth_lights_lrg.jpg", "jpg" );
{width, height} = earthLight << size; // need the ratio for the frame size...
New Window( "NASA's Night Light Map",
    g = Graph Box(
        framesize( width / 2, height / 2 ),
        X Scale( -180, 180 ),
        Y Scale( -90, 90 ),
        Text Color( "white" );
        Text( {0, -50}, error );
        Text( {-150, -88}, time );
        Text( {-179, 80}, "International Space Station" );
        Text( RightJustified, {160, -88}, "http://visibleearth.nasa.gov" ); // credits
        Text( {-50, -88}, "http://open-notify.org/Open-Notify-API/ISS-Location-Now/" ); // credits
        //Marker Size( 2 ); Marker( Combine States( Color State( "RED" ), Marker State( 12 ) ), lon, lat ); // space station trail
        //Marker Size( 4 ); Marker( Combine States( Color State( "RED" ), Marker State( 12 ) ), {lon[cur], lat[cur]} ); // head
        bright = N Rows( lon );
        Marker Size( 4 );
        For( i = cur, i >= 1, i--,
            Marker( Combine States( Color State( RGB Color( bright / N Rows( lon ), 0.1, 0.1 ) ), Marker State( 12 ) ), {lon[i], lat[i]} );
            bright--;
            Marker Size( 2 );
        );
        For( i = N Rows( lon ), i > cur, i--,
            Marker( Combine States( Color State( RGB Color( bright / N Rows( lon ), 0.1, 0.1 ) ), Marker State( 12 ) ), {lon[i], lat[i]} );
            bright--;
        );
    )
);
frameBox = g[framebox( 1 )]; // the framebox, in the graphbox, is just inside the axes
frameBox << Background Map( Boundaries( "World" ) ); // add boundaries first; they wind up on top of the next bitmap...
g[axisbox( 1 )] << Scale( "Linear" ) << remove axis label; // the geo coordinates were set when the map was added, but
g[axisbox( 2 )] << Scale( "Linear" ) << remove axis label; // we need linear coords to line up the map...
frameBox << AddImage( Image( earthLight ), bounds( Left( -180 ), Right( 180 ), top( 90 ), bottom( -90 ) ) ); // -180..180 seems correct
imgSeg = frameBox << FindSeg( PictSeg( 1 ) ); // the most recently added one is first
imgSeg << lock( 1 ); // and lock it so mouse won't move it by mistake
frameBox << xaxis( show major ticks( false ), show minor ticks( false ), show labels( false ) );
frameBox << yaxis( show major ticks( false ), show minor ticks( false ), show labels( false ) );

 

Article Tags