sara_oplotΒΆ

Orbit plotter for CY1

USAGE: sara_oplot.py [-dAgg] [-h] [-v] [-b baseuri] [-o output] orbit_nr

OPTS: -dAgg No display of window. Should be placed as first argument
-h

help

-v

verbose

-b basename

Specify baseuri of the orbit data.

-o output

Specify the output file name. If not specified (default), X window output.

-m

Moon plot enabled.

-p basename

Specify basuuri for Moon orbit data.

#!/usr/bin/python

'''Orbit plotter for CY1

USAGE:  sara_oplot.py [-dAgg] [-h] [-v] [-b baseuri] [-o output] orbit_nr

OPTS:     -dAgg   No display of window.  Should be placed as first argument
          -h      help
          -v      verbose
          -b basename   Specify baseuri of the orbit data.
          -o output     Specify the output file name.  If not specified (default), X window output.
          -m      Moon plot enabled.
          -p basename   Specify basuuri for Moon orbit data.
'''
import os
import sys

argv = sys.argv
if len(argv) > 1 and argv[1] == '-dAgg':
    import matplotlib
    print("Using Agg.", file=sys.stderr)
    matplotlib.use('Agg')
    argv.remove('-dAgg')

from irfpy.util.julday import Julday
from irfpy.util import utc 

from pylab import *
from matplotlib.patches import Arc
from numpy.ma import masked_where

import getopt
import datetime
import dateutil.parser

import logging

import subprocess

import re

from irfpy.cy1orb.Cy1Orbit import Cy1Orbit

def orbit_plot(axis, timelist, xlist, ylist, symbol):
    print(len(timelist), len(xlist), len(ylist))
    if len(timelist) != len(xlist) or len(timelist) != len(ylist):
        raise RuntimeError('Data provided has inconsistency in length.\ntimelist=%d, xlist=%d, ylist=%d: but all 3 must be the same'%(len(timelist), len(xlist), len(ylist)))

    return axis.plot(xlist, ylist, symbol)


class DataAndPlot:
    '''Container class for data and plot.
    '''
    _hol_ax="+0"
    _ver_ax="+1"
    _dep_ax="+2"
    _axis=None
    
    def __init__(self, horizon_ax, vertical_ax, depth_ax):
       ''' Constructor specifying 

       horizon_ax, vertical_ax and depth_ax must be a string such as +x or -z and so on.
       horizon_ax specifies which axis (x,y, or z) should be taken as a horizontal axis in the plot.
       Similar way as vertical_ax.
       depth_ax is how to decide in front of the plot plane.
       In theory, depth_ax can be calculated as it complets the right hand frame, but here one must
       specify whole the three axis.

       Sample: Usual plot, horizon=+x, vertical=+y, depth=+z: __init__('+x','+y','+z')
               Seen from North: horizon=+y, vertical=-x, depth=+z: __init__('+y','-x','+z')
       '''
       usable_strings=['+x','-x','+y','-y','+z','-z']
       if not horizon_ax in usable_strings:
           raise RuntimeError('horizon_ax must be specified from: '+str(usable_strings))
       self._hor_ax=horizon_ax.replace('x','0').replace('y','1').replace('z','2')
       if not vertical_ax in usable_strings:
           raise RuntimeError('vertical_ax must be specified from: '+str(usable_strings))
       self._ver_ax=vertical_ax.replace('x','0').replace('y','1').replace('z','2')
       if not depth_ax in usable_strings:
           raise RuntimeError('depth_ax must be specified from: '+str(usable_strings))
       self._dep_ax=depth_ax.replace('x','0').replace('y','1').replace('z','2')

    def plot_to_fig(self, fig, pos, timelist, poslist, title='Orbit plot', xlabel=None, ylabel=None):
        '''Plot to figure.
 
        fig is the Figure isntance, pos is the argument to be passed to fig.add_subplot(pos),
        and timelist and poslist should be ndarray.
        '''
        if len(timelist)!=len(poslist):
            raise RuntimeError('Data provided has inconsistency in length\ntimelist=%d, poslist=%d: but both are the same and must have more than 2')

        Rm=1738.64

        hor_sign = 1
        if self._hor_ax[0]=='-':
          hor_sign=-1
        hor_nr = int(self._hor_ax[1])
        if xlabel == None:
          xlabel=self._hor_ax[1].replace('0', 'x [km]').replace('1', 'y [km]').replace('2', 'z [km]')

        ver_sign = 1
        if self._ver_ax[0]=='-':
          ver_sign=-1
        ver_nr = int(self._ver_ax[1])
        if ylabel == None:
          ylabel=self._ver_ax[1].replace('0', 'x [km]').replace('1', 'y [km]').replace('2', 'z [km]')

        logging.info('HOR %d  %d'%(hor_sign, hor_nr))
        logging.info('VER %d  %d'%(ver_sign, ver_nr))

        self._axis=fig.add_subplot(pos, aspect='equal', xlim=[-2500*hor_sign,2500*hor_sign], ylim=[-2500*ver_sign,2500*ver_sign])

        sphere = Arc((0,0), 2*Rm, 2*Rm, fill=False)
        self._axis.add_patch(sphere)

        orbit_plot(self._axis, timelist, poslist[:,hor_nr], poslist[:,ver_nr], 'b--')
        self._axis.set_title(title)
        self._axis.set_xlabel(xlabel)
        self._axis.set_ylabel(ylabel)

        dep_sign = 1
        if self._dep_ax[0]=='-':
           dep_sign=-1
        dep_nr=int(self._dep_ax[1])

        logging.info('DEP %d  %d'%(dep_sign, dep_nr))

        invisible=[(dep_sign)*poslist[i,dep_nr]<0 and (poslist[i,hor_nr]**2+poslist[i,ver_nr]**2)<Rm**2 for i in range(len(poslist[:,dep_nr]))]

        hor_data = masked_where(invisible , poslist[:,hor_nr])
        ver_data = masked_where(invisible , poslist[:,ver_nr])
        line=self._axis.plot(hor_data, ver_data, 'b-', linewidth=2)

        self._axis.text(-2150*hor_sign, -2150*ver_sign, timelist[0].strftime('%FT%T')+"--"+timelist[-1].strftime('%T'))

        return self._axis

    def set_limit(self):
        '''Set the x and y limit (range).

        By unknown reason, (probably auto range is enabled somewhere), the limit is not as expected.
        Invoke the method at the end to modify the limit.
        '''

        hor_sign = 1
        if self._hor_ax[0]=='-':
          hor_sign=-1
        ver_sign = 1
        if self._ver_ax[0]=='-':
          ver_sign=-1

        self._axis.set_xlim(-2200*hor_sign,2200*hor_sign)
        self._axis.set_ylim(-2200*ver_sign,2200*ver_sign)


    def get_axis(self):
        return self._axis

    def ticks_plot(self, timelist, poslist):
        logging.info('---- TICKS PLOT ----')
        hor_nr = int(self._hor_ax[1])
        ver_nr = int(self._ver_ax[1])
        xlist = poslist[:,hor_nr]
        ylist = poslist[:,ver_nr]
        ticks_plot(self._axis, timelist, xlist, ylist)


def ticks_plot(axis, timelist, xlist, ylist):
    logging.info("%d %d %d"%( len(timelist), len(xlist), len(ylist)))
    if len(timelist) != len(xlist) or len(timelist) != len(ylist):
        raise RuntimeError('Data provided has inconsistency in length.\ntimelist=%d, xlist=%d, ylist=%d: but all 3 must be the same'%(len(timelist), len(xlist), len(ylist)))

    axis.plot(xlist, ylist, 'ro')
    for i in range(len(timelist)):
        axis.text(xlist[i], ylist[i], timelist[i].time())


def plot_data(timelist, poslist, verbose=False, Rm=1738.64, Rmp=1735.66, ticks=None, output=None, posttitle=''):
    if verbose:
        logging.basicConfig(level=logging.DEBUG)
        logging.debug('DEBUG mode entered')
    else:
        logging.basicConfig(level=logging.INFO)
        logging.info('Non DEBUG mode entered')

    # Orbit data

    logging.debug('position list (poslist) is converted to the numpy array.')
    plist=array(poslist)
    logging.debug(plist.shape)

    if len(timelist)!=len(poslist):
        raise RuntimeError('Data provided has inconsistency in length\ntimelist=%d, poslist=%d: but both are the same and must have more than 2')


    timelist_t,plist_t=None,None
    # Ticks data
    if ticks!=None:
        if len(ticks)!=2:
            logging.warning('Specified ticks should have 2 component list: time and position.  No plot is made.')
            timelist_t,plist_t=None,None
        else:
            timelist_t, poslist_t = ticks
            if len(timelist_t)!=len(poslist_t):
                logging.warning('Specified ticks data has inconsistency in length of each data.\nlen(time)=%d len(pos)=%d\nNo plot is made.'%(len(timelist_t), len(poslist_t)))
                timelist_t,plist_t=None,None
            else:
                plist_t = array(poslist_t)

    d=arange(0, 181)*math.pi/180.   # angle in radian for half circle
    x=cos(d)*Rm                     # X-coords for half circle
    y=sin(d)*Rm                     # Y-coords for half circle

    fig=figure(figsize=(10,8))

    ### North view

    nview=DataAndPlot('+y','-x','+z')
    plt2=nview.plot_to_fig(fig, 222, timelist, plist, title='North view '+posttitle, xlabel='y [km]', ylabel='x [km]')

    # nightside
    xy=array([x,-y]).transpose()
    night = Polygon(xy, closed=True, fill=True, alpha=0.2, facecolor='k')
    plt2.add_patch(night)

    if timelist_t != None:
        nview.ticks_plot(timelist_t, plist_t)


    ### Dawn view

    dview=DataAndPlot('+x','+z','-y')
    plt3=dview.plot_to_fig(fig, 223, timelist, plist, title='Dawn view '+posttitle)

    #night side
    xy=array([-y,x]).transpose()
    night = Polygon(xy, closed=True, fill=True, alpha=0.2, facecolor='k')
    plt3.add_patch(night)

    if timelist_t != None:
        dview.ticks_plot(timelist_t, plist_t)

    ### Sun view

    sview=DataAndPlot('+y','+z','+x')
    plt4=sview.plot_to_fig(fig, 224, timelist, plist, title='Sun view '+posttitle)
    
    if timelist_t != None:
        sview.ticks_plot(timelist_t, plist_t)

    nview.set_limit()
    dview.set_limit()
    sview.set_limit()



    return (fig, (nview,dview,sview))


def _reformat(arrdata, dt=None):
    tlist=[]
    plist=[]
    
    for i in range(len(arrdata[0,:])):
        jd=Julday(arrdata[0,i]+0.5*Julday.SECOND).getDatetime()
        jd=datetime.datetime(jd.year, jd.month, jd.day, jd.hour, jd.minute, jd.second, 0)
        tlist.append(jd)
        plist.append([ arrdata[1,i], arrdata[2,i], arrdata[3,i] ])

    dtlist=[]
    dplist=[]

    if dt!=None:

        # Search for 10 minutes ticks
        tt = tlist[0]
        microsecond=0
        second=0
        minute=int(tt.minute/10)*10
        logging.debug('tt=%s'%str(tt))
        t0 = datetime.datetime(tt.year, tt.month, tt.day, tt.hour, minute, second, microsecond)
        logging.debug('t0=%s'%str(t0))

        # Last data in the orbit
        te=tlist[-1]
        logging.debug('te=%s'%str(te))

        while t0 < te:
            if t0 in tlist:
                logging.debug('%s %s %s'%(t0, tlist.index(t0), plist[(tlist.index(t0))]))
                dtlist.append(t0)
                dplist.append(plist[(tlist.index(t0))])
            else:
                logging.debug('Not found...%s'%t0)

            t0=t0+datetime.timedelta(0, dt)

        tlist=dtlist
        plist=dplist

    return (tlist, plist)


def oplot(args):
    try:
        opts, arg = getopt.getopt(args, 'hvb:o:p:m')
    except getopt.GetoptError:
        usage()
        raise
    basedir=None
    output=None
    withMoon=False
    moonBasedir=None

    for o,oa in opts:
        if o in ('-h'):
            usage()
            return
        if o in ('-v'):
            logging.basicConfig(level=logging.DEBUG)
        if o in ('-b'):
            logging.debug('Basedir specified.')
            basedir=oa
        if o in ('-o'):
            logging.debug('Output specified.')
            output=oa
        if o in ('-p'):
            logging.debug('Basedir for Moon orbit set to %s'%oa)
            moonBasedir=oa
        if o in ('-m'):
            logging.debug('Moon plot in upper-left')
            withMoon=True

    logging.info('Basedir=%s'%basedir)
    logging.info('Output=%s'%output)

    if len(arg) != 1:
        usage()
        return
    else:
        orbnr=int(arg[0])

    logging.info('OrbitNr=%d'%orbnr)
    
    if basedir==None:
        cy1orb=Cy1Orbit()
    else:
        cy1orb=Cy1Orbit(baseurl=basedir)

    arrdata = cy1orb.getPosOfOrb(orbnr)

    dt=1200

    timelist,poslist = _reformat(arrdata)
    timelist_t, poslist_t = _reformat(arrdata, dt=dt)

    fig, axes = plot_data(timelist, poslist, ticks=[timelist_t, poslist_t], output=output, posttitle='@%04d'%orbnr)
#    fig, axes = plot_data(timelist, poslist)

    if withMoon:
      options=[]
      if moonBasedir!=None:
        options=['-b', moonBasedir]
      options.append(timelist[0].strftime('%FT%T'))

      import moon_oplot
      moon_oplot.oplot(options, p_figure=fig, p_subplot='221')
        
    if output==None:
        print('plt.show() here')
        plt.show()
        fig.canvas.draw()
    else:
        print('savefig here')
        plt.savefig(output)

def usage(fd=sys.stderr, exit=False):
    print(__doc__, file=fd)


if __name__ == '__main__':
    if len(argv)==1:
        usage()
        sys.exit(-1)
    oplot(argv[1:])