Previous: Remote access - introduction   Up: Remote access programming tutorial toc   Next: Matlab remote access

Madrigal web services tutorial

Top

Module services

@author: Bill Rideout @contact: brideout@haystack.mit.edu

$Id: views.py 7163 2020-08-21 14:15:04Z brideout $

Expand source code
'''

@author: Bill Rideout
@contact: brideout@haystack.mit.edu

$Id: views.py 7163 2020-08-21 14:15:04Z brideout $
'''


    
def get_version_service(request):
    """get_version_service runs the getVersionService.py service.  
    
    Inputs:
        request (ignored)
        
        Returns a single line of text, with the version in the form <major_version_int>.<minor_version_int>[.<sub_version_int>]
    """
    madDB = madrigal.metadata.MadrigalDB()
    siteID = madDB.getSiteID()
    madSiteObj = madrigal.metadata.MadrigalSite(madDB)
    return(HttpResponse(madSiteObj.getSiteVersion(siteID)))
    
    
    
def get_instruments_service(request):
    """get_instruments_service runs the getInstrumentsService.py service.  
    
    Inputs:
        request (ignored)
        
        Returns comma-delimited data, one line for each experiment, with the following fields:

        1. instrument.name  Example: 'Millstone Hill Incoherent Scatter Radar'

        2. instrument.code Example: 30

        3. instrument.mnemonic (3 char string) Example: 'mlh'

        4. instrument.latitude  Example: 45.0

        5. instrument.longitude  Example: 110.0

        6. instrument.altitude   Example: 0.015 (km) 
        
        7. instrument.category  Example: 'Incoherent Scatter Radars'
        
        8. contact name
        
        9. contact email
    """
    # create MadrigalDB obj
    madDBObj = madrigal.metadata.MadrigalDB()

    # create MadrigalInstument object
    madInst = madrigal.metadata.MadrigalInstrument(madDBObj)

    # get instrument list
    instList = madInst.getInstrumentList()

    # loop through each instrument
    instStr = ''
    for inst in instList:
        name = inst[0]
        code = inst[2]
        mnemonic = inst[1]
        latitude = madInst.getLatitude(code)
        if latitude == None:
            latitude = 0.0
        longitude = madInst.getLongitude(code)
        if longitude == None:
            longitude = 0.0
        altitude = madInst.getAltitude(code)
        if altitude == None:
            altitude = 0.0
        category = madInst.getCategory(code)
        if category == None:
            category = ''
        # print data
        contactName = madInst.getContactName(code)
        contactEmail = madInst.getContactEmail(code)
        instStr += '%s,%i,%s,%f,%f,%f,%s,%s,%s\n' % (name,
                                                     code,
                                                     mnemonic,
                                                     latitude,
                                                     longitude,
                                                     altitude,
                                                     category,
                                                     str(contactName),
                                                     str(contactEmail))
        
    return render(request, 'madweb/service.html', {'text': instStr})


def get_experiments_service(request):
    """get_experiments_service runs the getExperimentsService.py service.  
    
    Inputs:
        request/url - contains arguments:
        
            code - one or more kindat values
            
            startyear, startmonth, startday, starthour, startmin, startsec
            
            endyear, endmonth, endday, endhour, endmin, endsec
            
            local (defaults to True)
            
    Returns comma-delimited data, one line for each experiment, with the following fields:

        1. experiment.id (int) Example: 10000111
        
        2. experiment.url (string) Example: 'http://www.haystack.mit.edu/cgi-bin/madtoc/1997/mlh/03dec97'
        
        3. experiment.name (string) Example: 'Wide Latitude Substorm Study'
        
        4. experiment.siteid (int) Example: 1
        
        5. experiment.sitename (string) Example: 'Millstone Hill Observatory'
        
        6. experiment.instcode (int) Code of instrument. Example: 30
        
        7. experiment.instname (string) Instrument name. Example: 'Millstone Hill Incoherent Scatter Radar'
        
        8. experiment.start year (int) year of experiment start
        
        9. experiment.start month (int) month of experiment start
        
        10. experiment.start day (int) day of experiment start
        
        11. experiment.start hour (int) hour of experiment start
        
        12. experiment.start minute (int) min of experiment start
        
        13. experiment.start second (int) sec of experiment start
        
        14. experiment.end year (int) year of experiment end
        
        15. experiment.end month (int) month of experiment end
        
        16. experiment.end day (int) day of experiment end
        
        17. experiment.end hour (int) hour of experiment end
        
        18. experiment.end minute (int) min of experiment end
        
        19. experiment.end second (int) sec of experiment end
        
        20. experiment.isLocal (int) 1 if local, 0 if not
        
        21.experiment.PI (string) Experiment PI name Example: 'Phil Erickson'

        22. experiment.PIEmail (string) Experiment PI email Example: 'perickson@haystack.mit.edu'
        
        23. utc timestamp of last update to experiment
        
        24. security value
        
    """
    codeList = request.GET.getlist('code')
    codeList = [int(code) for code in codeList]
    startyear = int(request.GET['startyear'])
    startmonth = int(request.GET['startmonth'])
    startday = int(request.GET['startday'])
    starthour = int(request.GET['starthour'])
    startmin = int(request.GET['startmin'])
    startsec = int(request.GET['startsec'])
    endyear = int(request.GET['endyear'])
    endmonth = int(request.GET['endmonth'])
    endday = int(request.GET['endday'])
    endhour = int(request.GET['endhour'])
    endmin = int(request.GET['endmin'])
    endsec = int(request.GET['endsec'])
    try:
        local = int(request.GET['local'])
    except:
        local = 1
    
    
    # if startsec or endsec in (60, 61), handle correctly
    if startsec in (60, 61):
        tmpTime = datetime.datetime(startyear,
                                    startmonth,
                                    startday,
                                    starthour,
                                    startmin,
                                    59)
        tmpTime += datetime.timedelta(0, startsec - 59)
        startyear = tmpTime.year
        startmonth = tmpTime.month
        startday = tmpTime.day
        starthour = tmpTime.hour
        startmin = tmpTime.minute
        startsec = tmpTime.second

    if endsec in (60, 61):
        tmpTime = datetime.datetime(endyear,
                                    endmonth,
                                    endday,
                                    endhour,
                                    endmin,
                                    59)
        tmpTime += datetime.timedelta(0, endsec - 59)
        endyear = tmpTime.year
        endmonth = tmpTime.month
        endday = tmpTime.day
        endhour = tmpTime.hour
        endmin = tmpTime.minute
        endsec = tmpTime.second
        
    # if codeList is empty or contains 0, change it to only contain 0
    if len(codeList) == 0 or 0 in codeList:
        codeList = [0]
        
    retStr = ''

    # create MadrigalDB obj
    madDBObj = madrigal.metadata.MadrigalDB()

    # get the local site id
    localSiteId = madDBObj.getSiteID()

    # create MadrigalInstrument obj to convert kinst to instrument names
    madInstObj = madrigal.metadata.MadrigalInstrument(madDBObj)

    # create MadrigalSite obj to convert site id to site name
    madSiteObj = madrigal.metadata.MadrigalSite(madDBObj)
    
    madWebObj = madrigal.ui.web.MadrigalWeb(madDBObj)
    trusted = madWebObj.isTrusted()

    # create starttime for filter, if possible
    if startyear != None:
        startTimeFilter = datetime.datetime(startyear,
                        startmonth,
                        startday,
                        starthour,
                        startmin,
                        startsec) 
    else:
        startTimeFilter = None

    # create endtime for filter, if possible
    if endyear != None:
        endTimeFilter = datetime.datetime(endyear,
                          endmonth,
                      endday,
                      endhour,
                      endmin,
                      endsec) 
    else:
        endTimeFilter = None

    # create MadrigalExperiments for local or all files
    if local == 1:
        madExpObj = madrigal.metadata.MadrigalExperiment(madDBObj)
    else:
        # use file expTabAll.txt to get all experiments
        filename = madDBObj.getMadroot()
        if filename[-1] != '/':
            filename += '/'
        filename += 'metadata/expTabAll.txt'
        madExpObj = madrigal.metadata.MadrigalExperiment(madDBObj, filename)
        
    madExpObj.sortByDateSite()


    # loop through the data
    if not startTimeFilter is None:
        position = madExpObj.getStartPosition(startTimeFilter)
    else:
        position = 0
    while(True):
        thisId = madExpObj.getExpIdByPosition(position)
        # check for end
        if thisId == None:
            break
        thisUrl = madExpObj.getExpUrlByPosition(position)
        thisName = madExpObj.getExpNameByPosition(position)
        thisSiteId = madExpObj.getExpSiteIdByPosition(position)
        thisSiteName = madSiteObj.getSiteName(thisSiteId)
        thisInstCode = madExpObj.getKinstByPosition(position)
        thisInstName =madInstObj.getInstrumentName(thisInstCode)
        thisStart = madExpObj.getExpStartDateTimeByPosition(position)
        thisEnd = madExpObj.getExpEndDateTimeByPosition(position)
        thisSecurity = madExpObj.getSecurityByPosition(position)
        if thisSiteId == localSiteId:
            thisLocal = 1
        else:
            thisLocal = 0
        thisPI = madExpObj.getPIByPosition(position)
        if thisPI in (None, ''):
            thisPI = madInstObj.getContactName(thisInstCode)
        thisPIEmail = madExpObj.getPIEmailByPosition(position)
        if thisPIEmail in (None, ''):
            thisPIEmail = madInstObj.getContactEmail(thisInstCode)
        expDir = madExpObj.getExpDirByPosition(position)
            
        position += 1

        # some experiments set the end of the day to 24:00:00 - not
        # technically correct - reset to 23:59:59
        
        if (thisStart[3] == 24 and thisStart[4] == 0 and thisStart[5] == 0):
            thisStart[3] = 23
            thisStart[4] = 59
            thisStart[5] = 59

        if (thisEnd[3] == 24 and thisEnd[4] == 0 and thisEnd[5] == 0):
            thisEnd[3] = 23
            thisEnd[4] = 59
            thisEnd[5] = 59
        
        # apply filters
        
        # first apply instrument code filter
        if codeList[0] != 0:
            if thisInstCode not in codeList:
                continue

        # apply starttime and endtime filters
        thisStartTime = datetime.datetime(thisStart[0],
                                          thisStart[1],
                                          thisStart[2],
                                          thisStart[3],
                                          thisStart[4],
                                          thisStart[5])

        thisEndTime = datetime.datetime(thisEnd[0],
                                        thisEnd[1],
                                        thisEnd[2],
                                        thisEnd[3],
                                        thisEnd[4],
                                        thisEnd[5])
        
        if startTimeFilter != None:
            if thisEndTime < startTimeFilter:
                continue

        if endTimeFilter != None:
            if thisStartTime > endTimeFilter:
                continue

        # apply local filer
        if local == 1 and thisLocal == 0:
            continue

        # apply security filter
        if trusted == 0 and thisSecurity not in (0,2):
            continue
        
        # create exp timestamp
        if local == 1:
            thisUTTimestamp = int(os.stat(expDir).st_mtime + time.timezone)
        else:
            thisUTTimestamp = 0

        # add this experiment
        retStr += '%i,%s,%s,%i,%s,%i,%s,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%s,%s,%i,%i\n' % \
                (thisId,
                thisUrl,
                thisName,
                thisSiteId,
                thisSiteName,
                thisInstCode,
                thisInstName,
                thisStart[0],
                thisStart[1],
                thisStart[2],
                thisStart[3],
                thisStart[4],
                thisStart[5],
                thisEnd[0],
                thisEnd[1],
                thisEnd[2],
                thisEnd[3],
                thisEnd[4],
                thisEnd[5],
                thisLocal,
                str(thisPI),
                str(thisPIEmail),
                thisUTTimestamp,
                thisSecurity)
                
    return render(request, 'madweb/service.html', {'text': retStr})


def get_experiment_files_service(request):
    """get_experiment_files_service runs the getExperimentFilesService.py service.  
    
    Inputs:
        request/url - contains arguments:
        
            id - local experiment id
            
        Returns comma-delimited data, one line for each experiment file, with the following fields:

            1. file.name (string) Example '/opt/mdarigal/blah/mlh980120g.001'
            
            2. file.kindat (int) Kindat code.  Example: 3001
            
            3. file.kindat desc (string) Kindat description: Example 'Basic Derived Parameters'
            
            4. file.category (int) (1=default, 2=variant, 3=history, 4=real-time)
            
            5. file.status (string)('preliminary', 'final', or any other description)
            
            6. file.permission (int)  0 for public, 1 for private.  For now will not return private files.
            
            7. file DOI (string) - citable url to file
        
        Returns empty string if experiment id not found.  Skips files that are not Hdf5
    """
    id = int(request.GET['id'])
    
    # create MadrigalDB obj
    madDBObj = madrigal.metadata.MadrigalDB()

    # create MadrigalExperiments object to get full file name
    madExpObj = madrigal.metadata.MadrigalExperiment(madDBObj)

    # create Madrigal Kindat to get Kindat descriptions
    madKindatObj = madrigal.metadata.MadrigalKindat(madDBObj)
    
    madWebObj = madrigal.ui.web.MadrigalWeb(madDBObj)
    trusted = madWebObj.isTrusted()

        
    retStr = ''
    thisUrl = madExpObj.getExpUrlByExpId(id)
    if thisUrl is None:
        raise IOError('No such id: %i' % (id))
    expPath = madExpObj.getExpDirByExpId(id)
    kinst = madExpObj.getKinstByExpId(id)
    if os.access(os.path.join(expPath, 'fileTab.txt'), os.R_OK):
        madFileObj = madrigal.metadata.MadrigalMetaFile(madDBObj, os.path.join(expPath, 'fileTab.txt'))
        for i in range(madFileObj.getFileCount()):
            basename = madFileObj.getFilenameByPosition(i)
            name = os.path.join(expPath, basename)
            base_filename, file_extension = os.path.splitext(name)
            if file_extension not in ('.hdf5', '.hdf', '.h5'):
                continue
            kindat = madFileObj.getKindatByPosition(i)
            kindatdesc = madKindatObj.getKindatDescription(kindat, kinst)
            category = madFileObj.getCategoryByPosition(i)
            status = madFileObj.getStatusByPosition(i)
            permission = madFileObj.getAccessByPosition(i)
            doi = madFileObj.getFileDOIUrlByPosition(i)
    
            # skip private files if not trusted
            if trusted == 0 and int(permission) != 0:
                continue
                
            retStr += '%s,%i,%s,%i,%s,%i,%s\n' % \
                   (name,
                    kindat,
                    kindatdesc,
                    category,
                    status,
                    permission,
                    doi)
        
    
    
    return render(request, 'madweb/service.html', {'text': django.utils.safestring.mark_safe(retStr)})


def get_parameters_service(request):
    """get_parameters_service runs the getParametersService.py service.  
    
    Inputs:
        request/url - contains arguments:
        
            filename=<full path to data file>
            
        Returns backslash-delimited data, one for each parameter either measured or derivable, with the following fields:

            1. parameter.mnemonic (string) Example 'dti'
            
            2. parameter.description (string) Example:
                "F10.7 Multiday average observed (Ott)"
                
            3. parameter.isError (int) 1 if error parameter, 0 if not
            
            4. parameter.units (string) Example "W/m2/Hz"
            
            5. parameter.isMeasured (int) 1 if measured, 0 if derivable
            
            6. parameter.category (string) Example: "Time Related Parameter"
            
            7. parameter.isSure (int) - 1 if parameter can be found for every record, 0 if can only be found for some.
                Not relevant to Madrigal 3, where always 1
    
            8. parameter.isAddIncrement - 1 if additional increment, 0 if normal (Added in Madrigal 2.5)
                Not relevant to Madrigal 3, where always -1
    """
    filename = request.GET['filename']
    
    # create MadrigalDB obj
    madDBObj = madrigal.metadata.MadrigalDB()

    # create Madrigal File object 
    madFileObj = madrigal.data.MadrigalFile(filename, madDBObj)

    # create Madrigal Parameter object
    madParmObj = madrigal.data.MadrigalParameters(madDBObj)
    
    # create Madrigal web object 
    madWebObj = madrigal.ui.web.MadrigalWebFormat()
    

    # create lists of parameters
    measParmList = []
    derivedParmList = []
    allParmList = []
    sureParmList = []

    # use the comprehensive list of parameters to check if derivable
    parmList = madWebObj.getFormat('Comprehensive')

    # populate lists
    madFileObj.getMeasDervBothParmLists(parmList,
                                        measParmList,
                                        derivedParmList,
                                        allParmList,
                                        sureParmList)

    retStr = ''
    
    # loop through allParmList and output results
    for parm in allParmList:
        description = madParmObj.getSimpleParmDescription(parm)
        isNorm = madParmObj.getParmType(parm)
        if isNorm == 1:
            isError = 0
        else:
            isError = 1
        units = madParmObj.getParmUnits(parm)
        if parm in measParmList:
            isMeasured = 1
        else:
            isMeasured = 0
        if parm in sureParmList:
            isSure = 1
        else:
            isSure = 0
        category = madParmObj.getParmCategory(parm)
        try:
            if madParmObj.isAddIncrement(parm):
                isAddIncrement = 1
            else:
                isAddIncrement = 0
        except:
            isAddIncrement = -1
        # print out this parm
        retStr += '%s\\%s\\%i\\%s\\%i\\%s\\%i\\%i\n' % (parm,
                                                description,
                                                isError,
                                                units,
                                                isMeasured,
                                                category,
                                                isSure,
                                                isAddIncrement)
        
    return render(request, 'madweb/service.html', {'text': retStr})



def isprint_service(request):
    """isprint_service runs the isprintService.py service.  
    
    Inputs:
        request/url - contains arguments:
        
            'file': The full path to the file to be analyzed by isprint.  If over 50 MB, returns error message.
            
            'parms': Multiple requested parameters, space (+) separated.
            
            'filters': Multiple of filters desired, as in isprint command
            
            'user_fullname'     user name 
            
            'user_email'        user email
            
            'user_affiliation'  user affiliation
            
            'output' - option argument specifying output file basename.  Will be Hdf5 format if extension in
                ('hdf5', 'h5', 'hdf').  Will be netCDF4 is extension is '.nc'. Otherwise ascii. If not
                given, output is ascii.
                
            'header':  t for headers, f for no header.  Defaults to no header. Ignored if not ascii output
    
    Returns data as either column delimited ascii, Hdf5, or netCDF4.
    """
    madDB = madrigal.metadata.MadrigalDB()
    madWebObj = madrigal.ui.web.MadrigalWeb(madDB)
    
    # get required arguments
    thisFile = request.GET['file']
    parms = request.GET.getlist('parms')
    filters = request.GET.getlist('filters')
    user_fullname = request.GET['user_fullname']
    user_email = request.GET['user_email']
    user_affiliation = request.GET['user_affiliation']
        
    # get optional arguments
    try:
        output = os.path.basename(request.GET['output'])
        filename, file_extension = os.path.splitext(output)
        if file_extension in ('.hdf5', '.h5', '.hdf'):
            format = 'Hdf5'
        elif file_extension in ('.nc',):
            format = 'netCDF4'
        else:
            format = 'ascii'
    except:
        format = 'ascii'
        output = None
        
    # verify thisFile exists, not too big
    errorMessage = None
    if not os.access(thisFile, os.R_OK):
        errorMessage = 'File %s not found' % (thisFile)
    elif os.path.getsize(thisFile) > 200.0E6:
        errorMessage = 'File %s greater than 200 MB in size - running dynamic file creation not possible.  Please use  -- download as is -- instead.' % (thisFile)
    if not errorMessage is None:
        return render(request, 'madweb/service.html', {'text': errorMessage})
                
    if not output is None:
        # we need to write to a download file
        downloadFile = os.path.join(tempfile.gettempdir(), output)
        if os.access(downloadFile, os.R_OK):
            try:
                os.remove(downloadFile)
            except:
                pass
    try:
        header = request.GET['header']
        if header not in ('t', 'f'):
            raise ValueError('Unknown header value <%s>' % (header))
    except:
        header = 'f'
        
    # log data access
    madWebObj.logDataAccess(thisFile, user_fullname, user_email, user_affiliation)
        
    # run isprint command
    cmd = '%s/bin/isprint file=%s ' % (madDB.getMadroot(), thisFile)
    if not output is None:
        cmd += 'output=%s ' % (downloadFile)
    delimiter = ' '
    cmd += delimiter.join(parms) + ' '
    filterStr = delimiter.join(filters)
    cmd += filterStr + ' '
    if format == 'ascii':
        cmd += 'summary=f '
        cmd += 'header=%s ' % (header)
        
    if output is None:
        # text response
        #result = subprocess.check_output(cmd.split())
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        result,errtext = p.communicate()
        if p.returncode != 0:
            result = errtext
        if type(result) in (bytes, numpy.bytes_):
            result = result.decode('utf-8')
        if header == 'f':
            index = result.find('\n')
            result = result[index+1:]
        return render(request, 'madweb/service.html', {'text': result})
    else:
        # file download response
        #subprocess.check_call(cmd.split())
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        result,errtext = p.communicate()
        if p.returncode != 0:
            # write the error to result file
            f = open(downloadFile, 'w')
            if type(errtext) in (bytes, numpy.bytes_):
                errtext = errtext.decode('utf-8')
            f.write(errtext)
            f.close()
        
        f = open(downloadFile, 'rb')
        filename = os.path.basename(downloadFile)
        chunk_size = 8192
        file_type = mimetypes.guess_type(downloadFile)[0]
        if file_type is None:
            file_type = 'application/octet-stream'
        response = StreamingHttpResponse(FileWrapper(f, chunk_size),
                                         content_type=file_type)
        response['Content-Length'] = os.path.getsize(downloadFile)    
        response['Content-Disposition'] = "attachment; filename=%s" % (filename)
        os.remove(downloadFile)
        return(response)
    
    
def get_madfile_service(request):
    """get_madfile_service runs the getMadfile.cgi service.  
    
    Inputs:
        request/url - contains arguments:
        
            'fileName': The full path to the file to be downloaded as.
            
            'fileType': -1 for ascii, -2 for Hdf5, -3 for netCDF4. No other values supported
            
            'user_fullname'     user name 
            
            'user_email'        user email
            
            'user_affiliation'  user affiliation
    
    Returns file as either column delimited ascii, Hdf5, or netCDF4.
    """
    madDB = madrigal.metadata.MadrigalDB()
    madWebObj = madrigal.ui.web.MadrigalWeb(madDB)
    
    # get required arguments
    fileName = request.GET['fileName']
    fileType = int(request.GET['fileType'])
    user_fullname = request.GET['user_fullname']
    user_email = request.GET['user_email']
    user_affiliation = request.GET['user_affiliation']
    
    if fileType not in (-1, -2, -3):
        return(HttpResponse('<p>fileType %i not allowed: -1 for ascii, -2 for Hdf5, -3 for netCDF4</p>' % (fileType)))
    
    # log data access
    madWebObj.logDataAccess(fileName, user_fullname, user_email, user_affiliation)
    
    if fileType in (-1, -3):
        # may need to create temp file
        filepath, file_extension = os.path.splitext(fileName)
        basename = os.path.basename(filepath)
        dirname = os.path.dirname(fileName)
        if fileType == -1:
            cachedTxtFile = os.path.join(dirname, 'overview', os.path.basename(fileName) + '.txt.gz')
            tmpFile = os.path.join(tempfile.gettempdir(), basename + '.txt.gz')
            if os.access(cachedTxtFile, os.R_OK):
                shutil.copy(cachedTxtFile, tmpFile)
            else:
                tmpFile = os.path.join(tempfile.gettempdir(), basename + '.txt')
                madrigal.cedar.convertToText(fileName, tmpFile)
        else:
            cachedNCFile = os.path.join(dirname, 'overview', os.path.basename(fileName) + '.nc')
            tmpFile = os.path.join(tempfile.gettempdir(), basename + '.nc')
            if os.access(cachedNCFile, os.R_OK):
                shutil.copy(cachedNCFile, tmpFile)
            else:
                try:
                    madrigal.cedar.convertToNetCDF4(fileName, tmpFile)
                except IOError:
                    cedarObj = madrigal.cedar.MadrigalCedarFile(fileName)
                    cedarObj.write('netCDF4', tmpFile)
        
    else:
        tmpFile = fileName
        
    f = open(tmpFile, 'rb')
    filename = os.path.basename(tmpFile)
    chunk_size = 8192
    file_type = mimetypes.guess_type(tmpFile)[0]
    if file_type is None:
        file_type = 'application/octet-stream'
    response = StreamingHttpResponse(FileWrapper(f, chunk_size),
                                     content_type=file_type)
    response['Content-Length'] = os.path.getsize(tmpFile)    
    response['Content-Disposition'] = "attachment; filename=%s" % (filename)
    if fileType in (-1, -3):
        os.remove(tmpFile)
    return(response)
        
    
def mad_calculator_service(request):
    """mad_calculator_service runs the madCalculator service.  
    
    Inputs:
        request/url - contains arguments:
        
            year, month, day, hour, min, sec 
            
            startLat - Starting geodetic latitude, -90 to 90 (float)
            
            endLat - Ending geodetic latitude, -90 to 90 (float)
            
            stepLat - Latitude step (0.1 to 90) (float)
            
            startLong - Starting geodetic longitude, -180 to 180  (float)
            
            endLong - Ending geodetic longitude, -180 to 180 (float)
            
            stepLong - Longitude step (0.1 to 180) (float)
            
            startAlt - Starting geodetic altitude, >= 0 (float)
            
            endAlt - Ending geodetic altitude, > 0 (float)
            
            stepAlt - Altitude step (>= 0.1) (float)
            
            parms - comma delimited string of Madrigal parameters desired
            
            oneD - zero or more mnemonics,float values to set input 1D values
    
    Returns comma-delimited data, one line for each combination of lat, long, and alt,
    with the following fields:

        1. latitude
        
        2. longitude
        
        3. altitude
        
        4. Values for each Madrigal parameter listed in argument parms, separated by whitespace
    """
    year = int(request.GET['year'])
    month = int(request.GET['month'])
    day = int(request.GET['day'])
    hour = int(request.GET['hour'])
    minute = int(request.GET['min'])
    second = int(request.GET['sec'])
    try:
        dt = datetime.datetime(year, month, day, hour, minute, second)
    except:
        return(HttpResponse('Illegal time: year %i, month %i, day %i, hour %i, minute %i, second %i' % (year, month, day, hour, minute, second)))
    
    startLat = float(request.GET['startLat'])
    endLat = float(request.GET['endLat'])
    if startLat == endLat:
        endLat += 0.001
    elif startLat > endLat:
        return(HttpResponse('startLat %s cannot be greater than endLat %s' % (str(startLat), str(endLat))))
    stepLat = float(request.GET['stepLat'])
    if stepLat < 0.0:
        return(HttpResponse('stepLat %s cannot be less than zero' % (str(stepLat))))
    elif stepLat == 0.0:
        stepLat = 0.001
    latList = list(numpy.arange(startLat, endLat, stepLat))
    
    startLong = float(request.GET['startLong'])
    endLong = float(request.GET['endLong'])
    if startLong == endLong:
        endLong += 0.001
    elif startLong > endLong:
        return(HttpResponse('startLong %s cannot be greater than endLong %s' % (str(startLong), str(endLong))))
    stepLong = float(request.GET['stepLong'])
    if stepLong < 0.0:
        return(HttpResponse('stepLong %s cannot be less than zero' % (str(stepLong))))
    elif stepLong == 0.0:
        stepLong = 0.001
    lonList = list(numpy.arange(startLong, endLong, stepLong))
    
    startAlt = float(request.GET['startAlt'])
    endAlt = float(request.GET['endAlt'])
    if startAlt == endAlt:
        endAlt += 0.001
    elif startAlt > endAlt:
        return(HttpResponse('startAlt %s cannot be greater than endAlt %s' % (str(startAlt), str(endAlt))))
    stepAlt = float(request.GET['stepAlt'])
    if stepAlt < 0.0:
        return(HttpResponse('stepAlt %s cannot be less than zero' % (str(stepAlt))))
    elif stepAlt == 0.0:
        stepAlt = 0.01
    altList = list(numpy.arange(startAlt, endAlt, stepAlt))
    
    # limit total calculations to 1E5
    total = len(latList) * len(lonList) * len(altList)
    if total > 1.0E5:
        return(HttpResponse('Too many points for madCalculatorService: %i' % (total)))
    
    parms = request.GET['parms']
    desiredParmList = [item.strip() for item in ['gdlat','glon','gdalt'] + parms.split(',')]
    
    oneDList = request.GET.getlist('oneD')
    oneDParmDict = {}
    for oneDStr in oneDList:
        mnem, strValue = oneDStr.split(',')
        oneDParmDict[mnem] = [float(strValue)]
    
    # capture stdout
    old_stdout = sys.stdout
    sys.stdout = mystdout = io.StringIO()
    madrigal.isprint.MadCalculatorGrid(None, desiredParmList, [dt], latList, lonList, altList, 
                                   oneDParmDict, summary=None)
    text = mystdout.getvalue()
    sys.stdout = old_stdout
    
    return render(request, 'madweb/service.html', {'text': text})


def mad_time_calculator_service(request):
    """mad_time_calculator_service runs the madTimeCalculator service.  Input parameters must not be location dependent
    
    Inputs:
        request/url - contains arguments:
        
            1. startyear - int 
            
            2. startmonth - int 
            
            3. startday - int
            
            4. starthour - int 
            
            5. startmin - int 
            
            6. startsec - int
            
            7. endyear - int 
            
            8. endmonth - int 
            
            9. endday - int
            
            10. endhour - int 
            
            11. endmin - int 
            
            12. endsec - int
            
            13. stephours - float - number of hours per time step
            
            14. parms - comma delimited string of Madrigal parameters desired (must not depend on location)
    
    Returns comma-delimited data, one line for each year, month, day, hour, minute, and second,
    with the following fields:

        1-6: year, month, day, hour, minute, and second
        
        2. requested parm fields
    """
    startyear = int(request.GET['startyear'])
    startmonth = int(request.GET['startmonth'])
    startday = int(request.GET['startday'])
    starthour = int(request.GET['starthour'])
    startminute = int(request.GET['startmin'])
    startsecond = int(request.GET['startsec'])
    endyear = int(request.GET['endyear'])
    endmonth = int(request.GET['endmonth'])
    endday = int(request.GET['endday'])
    endhour = int(request.GET['endhour'])
    endminute = int(request.GET['endmin'])
    endsecond = int(request.GET['endsec'])
    dt1 = datetime.datetime(startyear, startmonth, startday, starthour, startminute, startsecond)
    dt2 = datetime.datetime(endyear, endmonth, endday, endhour, endminute, endsecond)
    if dt1 > dt2:
        return(HttpResponse('End Datetime %s cannot be before start datetime %s' % (str(dt2), str(dt1))))
    
    stephours = float(request.GET['stephours'])
    if stephours <= 0.0:
        return(HttpResponse('stephours cannot be non-positive: %f' % (stephours)))
    
    dtList = []
    while dt1 <= dt2:
        dtList.append(dt1)
        dt1 += datetime.timedelta(hours=stephours)
    
    parms = request.GET['parms']
    desiredParmList = [item.strip() for item in ['year','month','day','hour','min','sec'] + parms.split(',')]
    
    # no spatial data
    latList = lonList = altList = []
    # capture stdout
    old_stdout = sys.stdout
    sys.stdout = mystdout = io.StringIO()
    madrigal.isprint.MadCalculatorGrid(None, desiredParmList, dtList, latList, lonList, altList, 
                                   summary=None)
    text = mystdout.getvalue()
    sys.stdout = old_stdout
    
    return render(request, 'madweb/service.html', {'text': text})



def mad_calculator2_service(request):
    """mad_calculator2_service runs the madCalculator2 service.
    
    Differs from madCalulator in that positions are a list rather than a grid.  
    
    Inputs:
        request/url - contains arguments:
        
            year, month, day, hour, min, sec 
            
            lats - comma separated list of latitudes to analyze
            
            longs - comma separated list of longitudes to analyze. Len must == len(lats)
            
            alts - comma separated list of altitudes to analyze. Len must == len(lats)
            
            parms - comma delimited string of Madrigal parameters desired
            
            oneD - zero or more mnemonics,float values to set input 1D values
                Example:  &oneD=kinst,31.0&oneD=elm,45.0
                
            twoD - zero or more mnemonics,comma-separate float list of len(lats) to set input 2D values
                Example:  twoD=te,1000,1100,1200  twoD=ti,1000,1000,1000
                          where there are 3 lats
    
    Returns comma-delimited data, one line for each lat value,
    with the following fields:

        1. latitude
        
        2. longitude
        
        3. altitude
        
        4. Values for each Madrigal parameter listed in argument parms, separated by whitespace
    """
    if request.method == 'POST':
        reqDict = request.POST
    else:
        reqDict = request.GET
    try:
        year = int(reqDict.get('year'))
    except TypeError:
        return(HttpResponse('<p>madCalculator2Service requires year</p>'))
    month = int(reqDict['month'])
    day = int(reqDict['day'])
    hour = int(reqDict['hour'])
    minute = int(reqDict['min'])
    second = int(reqDict['sec'])
    dt = datetime.datetime(year, month, day, hour, minute, second)
    
    latsStr = reqDict['lats']
    lats = [float(item) for item in latsStr.split(',')]
    longsStr = reqDict['longs']
    longs = [float(item) for item in longsStr.split(',')]
    altsStr = reqDict['alts']
    alts = [float(item) for item in altsStr.split(',')]
    
    parms = reqDict['parms']
    desiredParmList = [item.strip() for item in ['gdlat','glon','gdalt'] + parms.split(',')]
    
    oneDList = reqDict.getlist('oneD')
    oneDParmDict = {}
    for oneDStr in oneDList:
        mnem, strValue = oneDStr.split(',')
        oneDParmDict[mnem] = [float(strValue)]
        
    twoDList = reqDict.getlist('twoD')
        
    twoDParmDict = {}
    for twoDStr in twoDList:
        items = twoDStr.split(',')
        if len(items) != 1 + len(lats):
            raise ValueError('twoDstr %s not correct number of points' % (str(twoDStr)))
        mnem = items[0]
        floatValues = [float(item) for item in items[1:]]
        # now we need to expand these values to be two dimensional 1 x len(lats)
        values = numpy.zeros((1,len(lats)), dtype=numpy.float)
        values[0][:] = floatValues
        twoDParmDict[mnem] = values

    # capture stdout
    old_stdout = sys.stdout
    sys.stdout = mystdout = io.StringIO()
    madrigal.isprint.MadCalculatorList(None, desiredParmList, [dt], lats, longs, alts, 
                                       oneDParmDict, twoDParmDict, summary=None)
    text = mystdout.getvalue()
    sys.stdout = old_stdout
    
    return render(request, 'madweb/service.html', {'text': text})
    
    
    
def mad_calculator3_service(request):
    """mad_calculator3_service runs the madCalculator3 service.
    
    Differs from madCalulator in that multiple times, each with a unique list of positions, can be passed in.
    
    Inputs:
      request/url - contains arguments:
      
        year - a comma-separated list of years - (required)
        
        month  - a comma-separated list of months - (required)
        
        day - a comma-separated list of days - (required)
        
        hour - a comma-separated list of hours - (required)
        
        min - a comma-separated list of minutes - (required)
        
        sec - a comma-separated list of seconds - (required)
        
        numPos - a comma-sepatated list of the number of positions for each time - (required)
        
        lats - a comma-separated list of geodetic latitudes, -90 to 90 (required).  Listed
                  for first time, then second, etc.  Total must be equal to the sum
                  of numPos.
                  
        longs - a comma-separated list of longitudes (required) Listed
                  for first time, then second, etc.  Total must be equal to the sum
                  of numPos.
                  
        alts - a comma-separated list of geodetic altitudes in km (required) Listed
                  for first time, then second, etc.  Total must be equal to the sum
                  of numPos.
                  
        parms - comma delimited string of Madrigal parameters desired (required)
        
        oneD - string in form <parm>,<comma-separated values> This argument allows the user to
                            set any number of one-D parameters to be used in the calculation.
                            Value must be parameter name, comma, list of values as double,
                            where length of list is equal to number of times.
                            Example:  &oneD=kinst,31.0,31.0&oneD=elm,45.0,50
                            (optional - 0 or more allowed)        
                            
         twoD=<parm>,<values>  (optional - 0 or more allowed) This argument allows the user to
                            set any number of two-D parameters to be used in the calculation.
                            Value must be parameter name, comma, comma-separated values.
                            Number of values must equal the sum of numPos.  Order is
                            first time values first, then second time values, etc
                            Example:  twoD=te,1000,1100,1200,1000,1100,1200 &twoD=ti,1000,1000,1000,1000,1000,1000
                            where numPos=3,3

    Returns comma-delimited data, one line for each location.  Separate times are delimited by line

    TIME MM/DD/YYYY HH:MM:SS
    
    Data lines have the following fields:
    
    1. latitude
    
    2. longitude
    
    3. altitude
    
    4. Values for each Madrigal parameter listed in argument parms, separated by whitespace
    """
    if request.method == 'POST':
        reqDict = request.POST
    else:
        reqDict = request.GET
    try:
        yearList = [int(item) for item in reqDict.get('year').split(',')]
    except AttributeError:
        return(HttpResponse('<p>madCalculator3Service requires year</p>'))
    monthList = [int(item) for item in reqDict.get('month').split(',')]
    dayList = [int(item) for item in reqDict.get('day').split(',')]
    hourList = [int(item) for item in reqDict.get('hour').split(',')]
    minList = [int(item) for item in reqDict.get('min').split(',')]
    secList = [int(item) for item in reqDict.get('sec').split(',')]
    dtList = [datetime.datetime(yearList[i], monthList[i], dayList[i],
                                hourList[i], minList[i], secList[i]) for i in range(len(yearList))]
    numPosStr = reqDict['numPos']
    numPosList = [int(item) for item in numPosStr.split(',')]
    totalPos = 0
    for numPos in numPosList:
        totalPos += numPos
    latsStr = reqDict['lats']
    lats = [float(item) for item in latsStr.split(',')]
    if len(lats) != totalPos:
        return(HttpResponse('wrong number of lats, expected %i' % (totalPos)))
    longsStr = reqDict['longs']
    longs = [float(item) for item in longsStr.split(',')]
    if len(longs) != totalPos:
        return(HttpResponse('wrong number of longs, expected %i' % (totalPos)))
    altsStr = reqDict['alts']
    alts = [float(item) for item in altsStr.split(',')]
    if len(alts) != totalPos:
        return(HttpResponse('wrong number of alts, expected %i' % (totalPos)))
    
    parms = reqDict['parms']
    desiredParmList = [item.strip() for item in ['gdlat','glon','gdalt'] + parms.split(',')]
    
    oneDList = reqDict.getlist('oneD')
    twoDList = reqDict.getlist('twoD')
    
    # since the positions can change with each call, we need to call madrigal.isprint.MadCalculatorGrid once for each time
    startIndex = 0
    endIndex = 0
    fullText = ''
    for timeIndex, numPos in enumerate(numPosList):
        startIndex = endIndex
        endIndex += numPos
        thisLats = lats[startIndex:endIndex]
        thisLongs = longs[startIndex:endIndex]
        thisAlts = alts[startIndex:endIndex]
    
        oneDParmDict = {}
        for oneDStr in oneDList:
            values = oneDStr.split(',')
            if len(values) != 1+len(dtList):
                return(HttpResponse('wrong number of values given for 1D parm %s' % (values[0])))
            oneDParmDict[values[0]] = [float(values[timeIndex+1])]
        
        twoDParmDict = {}
        
        for twoDStr in twoDList:
            values = twoDStr.split(',')
            if len(values) != 1 + totalPos:
                return(HttpResponse('twoDstr %s not correct number of points' % (str(twoDStr))))
            mnem = values[0]
            floatValues = [float(item) for item in values[1+startIndex:1+endIndex]]
            # now we need to expand these values to be two dimensional - 1,len(thisLats)
            values2D = numpy.zeros((1,len(thisLats)), dtype=numpy.float)
            values2D[0][:] = floatValues
            twoDParmDict[mnem] = values2D
            
            
    
        # capture stdout
        old_stdout = sys.stdout
        sys.stdout = mystdout = io.StringIO()
        madrigal.isprint.MadCalculatorList(None, desiredParmList, [dtList[timeIndex]], thisLats, 
                                           thisLongs, thisAlts, 
                                           oneDParmDict, twoDParmDict, summary=None)
        text = mystdout.getvalue()
        sys.stdout = old_stdout
        
        fullText += 'TIME %s\n' % (dtList[timeIndex].strftime('%m/%d/%Y %H:%M:%S'))
        fullText += text
    
    return render(request, 'madweb/service.html', {'text': fullText})
    
    
    
def geodetic_to_radar_service(request):
    """geodetic_to_radar_service runs the geodeticToRadar service.
    
    Inputs:
      request/url - contains arguments:
      
        slatgd  - radar geodetic latitude
        
        slon - radar longitude
        
        saltgd - radar geodetic altitude
        
        gdlat - a comma-separated list of geodetic latitude of point
        
        glon - a comma-separated list of longitude of point. Len must be same as gdlat
        
        gdalt - a comma-separated list of geodetic altitude of point. Len must be same as gdlat


    Returns comma-delimited data, one line for point in lists (points treated as individual combinations, not grids):

        1. radar azimuth in degrees (0 = north)
        
        2. radar elevation in degrees 
        
        3. radar range in km
    """
    slatgd = float(request.GET['slatgd'])
    slon = float(request.GET['slon'])
    saltgd = float(request.GET['saltgd'])
    oneDParmDict = {'GDLATR': [slatgd],
                    'GDLONR': [slon],
                    'GALTR': [saltgd]}
    gdlatStr = request.GET['gdlat']
    gdlatList = [float(item) for item in gdlatStr.split(',')]
    glonStr = request.GET['glon']
    glonList = [float(item) for item in glonStr.split(',')]
    gdaltStr = request.GET['gdalt']
    gdaltList = [float(item) for item in gdaltStr.split(',')]
    desiredParmList = ['azm', 'elm', 'range']
    dtList = [datetime.datetime(2001,1,1)] # not relevant
    if len(gdlatList) != len(glonList) or len(gdlatList) != len(gdaltList):
        return(HttpResponse('all point list lengths must be equal'))
    
    fullText = ''
    
    delimiter = ','
    for i in range(len(gdlatList)):
         # capture stdout
        old_stdout = sys.stdout
        sys.stdout = mystdout = io.StringIO()
        madrigal.isprint.MadCalculatorGrid(None, desiredParmList, dtList, [gdlatList[i]], 
                                       [glonList[i]], [gdaltList[i]], summary=None,
                                       oneDParmDict=oneDParmDict)
        text = mystdout.getvalue()
        sys.stdout = old_stdout
        for line in text.split('\n'):
            items = line.split()
            fullText += delimiter.join(items) + '\n'
    
    return render(request, 'madweb/service.html', {'text': fullText})


def radar_to_geodetic_service(request):
    """radar_to_geodetic_service runs the radarToGeodetic service.
    
    Inputs:
      request/url - contains arguments:
      
        slatgd  - radar geodetic latitude
        
        slon - radar longitude
        
        saltgd - radar geodetic altitude
        
        azs - a comma-separated list of azimuths of point
        
        els - a comma-separated list of elevations of point. Len must be same as azs
        
        ranges - a comma-separated list of ranges to point. Len must be same as azs


    Returns comma-delimited data, one line for point in lists  (points treated as individual combinations, not grids):

        1. geodetic latitude
        
        2. longitude (-180 to 180)
        
        3. geodetic altitude in km
    """
    slatgd = float(request.GET['slatgd'])
    slon = float(request.GET['slon'])
    saltgd = float(request.GET['saltgd'])
    azStr = request.GET['az']
    azList = [float(item) for item in azStr.split(',')]
    elStr = request.GET['el']
    elList = [float(item) for item in elStr.split(',')]
    rangeStr = request.GET['range']
    rangeList = [float(item) for item in rangeStr.split(',')]
    if len(azList) != len(elList) or len(azList) != len(rangeList):
        return(HttpResponse('all point list lengths must be equal'))
    
    fullText = ''
    
    for i in range(len(azList)):
        gdlat,glon,gdalt = madrigal._derive.radarToGeodetic(slatgd, slon, saltgd,
                                                            azList[i], elList[i], rangeList[i])
        fullText += '%f,%f,%f\n' % (gdlat,glon,gdalt)
        
    return render(request, 'madweb/service.html', {'text': fullText})
    
    

def list_file_times_service(request):
    """list_file_times_service runs the listFileTimes service.
    
    Inputs:
      request/url - contains arguments:
      
        Optional: expDir - experiment directory to list.  Can be absolute or relative to
            experiments[0-9]*. Default is all files in $MADROOT/experiments*

    Returns comma-delimited data, one for each file:
    
        1. Full path of file
        
        2. File modification time in form YYYY-MM-DD HH:MM:SS (UT time)
    """
    expDir = None
    try:
        expDir = request.GET['expDir']
    except:
        pass
    madDB = madrigal.metadata.MadrigalDB()
    fileList = madDB.listFileTimes(expDir)
    fullText = '\n\n'
    for filename, filetime in fileList:
        fullText += "\'%s\', %s\n" % (filename, filetime.strftime('%Y-%m-%d %H:%M:%S'))
        
    return render(request, 'madweb/service.html', {'text': django.utils.safestring.mark_safe(fullText)})


def download_web_file_service(request):
    """download_web_file_service runs the downloadWebFile service.
    
    Inputs:
      request/url - contains arguments:
      
        expPath - path to file starting at experiments*

    Returns comma-delimited data, one for each file:
    
        1. Full path of file
        
        2. File modification time in form YYYY-MM-DD HH:MM:SS (UT time)
    """
    expPath = request.GET['expPath']
    madDB = madrigal.metadata.MadrigalDB()
    downloadFile = os.path.join(madDB.getMadroot(), expPath)
    f = open(downloadFile, 'rb')
    thisFile = django.core.files.File(f)
    response = HttpResponse(thisFile, content_type='application/x-octet-stream')
    response['Content-Disposition'] = 'attachment; filename="' + os.path.basename(downloadFile) + '"'
    response['Content-Length'] = os.path.getsize(downloadFile)
    return(response)


def trace_magnetic_field_service(request):
    """trace_magnetic_field_service runs the traceMagneticField service.
    
    Inputs:
      request/url - contains arguments:
      
        year, month, day, hour, min, sec
        
        inputType (0 for geodetic, 1 for GSM)
        
        outputType (0 for geodetic, 1 for GSM)
        
            The following parameter depend on inputType:
            
        in1 - a comma-separated list of geodetic altitudes or ZGSMs of starting point
        
        in2 - a comma-separated list of geodetic latitudes or XGSMs of starting point
        
        in3 - a comma-separated list of longitude or YGSM of starting point

            Length of all three lists must be the same
        
        model - 0 for Tsyganenko, 1 for IGRF
        
        qualifier - 0 for conjugate, 1 for north_alt, 2 for south_alt, 3 for apex, 4 for GSM XY plane
        
        stopAlt - altitude in km to stop trace at, if qualifier is north_alt or south_alt.
        
        If other qualifier, this parameter is not required.

    Returns comma-delimited data, one line for point in in lists:

        1. geodetic altitude or ZGSM of ending point
        
        2. geodetic latitude or XGSM of ending point
        
        3. longitude or YGSM of ending point
    """
    year = int(request.GET['year'])
    month = int(request.GET['month'])
    day = int(request.GET['day'])
    hour = int(request.GET['hour'])
    minute = int(request.GET['min'])
    second = int(request.GET['sec'])
    dt = datetime.datetime(year, month, day, hour, minute, second)
    inputType = int(request.GET['inputType'])
    if inputType not in (0,1):
        return(HttpResponse('inputType must be 0 or 1, not %i' % (inputType)))
    outputType = int(request.GET['outputType'])
    if outputType not in (0,1):
        return(HttpResponse('outputType must be 0 or 1, not %i' % (outputType)))
    in1Str = request.GET['in1']
    in1List = [float(item) for item in in1Str.split(',')]
    in2Str = request.GET['in2']
    in2List = [float(item) for item in in2Str.split(',')]
    in3Str = request.GET['in3']
    in3List = [float(item) for item in in3Str.split(',')]
    if len(in1List) != len(in2List) or len(in1List) != len(in3List):
        return(HttpResponse('All three in* lists must have same length'))
    model = int(request.GET['model'])
    if model not in (0,1):
        return(HttpResponse('model must be 0 or 1, not %i' % (model)))
    qualifier = int(request.GET['qualifier'])
    if qualifier not in (0,1,2,3,4):
        return(HttpResponse('model must be in 0,1,2,3,4 not %i' % (model)))
    try:
        stopAlt = float(request.GET['stopAlt'])
    except:
        stopAlt = 0.0
        
    fullText = ''
    resultArr = numpy.zeros((3,), dtype='f8')
    madDB = madrigal.metadata.MadrigalDB()
    madDeriveObj = madrigal.derivation.MadrigalDerivationMethods(madDB.getMadroot())
    for i in range(len(in1List)):
        madDeriveObj.traceMagneticField(year, month, day, hour, minute, second, 
                                        inputType, outputType, in1List[i], in2List[i], in3List[i], 
                                        model, qualifier, stopAlt, resultArr)
        fullText += '%f,%f,%f\n' % (resultArr[0], resultArr[1], resultArr[2])

    return render(request, 'madweb/service.html', {'text': fullText})


def global_file_search_service(request):
    """global_file_search_service returns a list of full paths to files or citable urls based on search arguments
    
    Inputs:
        request/url - contains arguments:
        
            startDate: start date in form YYYY-MM-DD to filter experiments before
            endDate: end date in form YYYY-MM-DD to filter experiments after 
            inst: (optional, multiple allowed) an instrument code or name. For names,
                fnmatch will be used. If not set, all instruments used. 
            kindat: (optional, multiple allowed) a kind of data codes or name. For names,
                fnmatch will be used. If not set, all kinds of data used.
            seasonalStartDate: (optional) in form MM/DD, rejects all days earlier in year. If not set
                implies 01/01
            seasonalEndDate: (optional) in form MM/DD, rejects all days later in year. If not set
                implies 12/31
            includeNonDefault: (optional) if "True", include realtime files when there are no default. 
                If not set, only default files.
            expName: (optional)  - filter experiments by the experiment name.  fnmatch rules
                If not set, no filtering by experiment name.
            excludeExpName: (optional)  - exclude experiments by the experiment name.  fnmatch rules  
                If not set, no excluding experiments by experiment name.
            fileDesc: (optional) filter files using input file Description string via fnmatch. 
                If not set, in no filtering by file name
            returnCitation: (optional) if True, return a list of file citations.  If not set, return
                a list of full paths to the files selected
    
    """
    madDB = madrigal.metadata.MadrigalDB()
    madWebObj = madrigal.ui.web.MadrigalWeb(madDB)
    
    # get required arguments
    startDate = request.GET['startDate']
    endDate = request.GET['endDate']
    startDate = datetime.datetime.strptime(startDate, '%Y-%m-%d')
    endDate = datetime.datetime.strptime(endDate, '%Y-%m-%d')
        
    # get optional arguments
    inst = request.GET.getlist('inst')
    if inst == []:
        inst = None
    kindat = request.GET.getlist('kindat')
    if kindat == []:
        kindat = None
    seasonalStartDate = request.GET.get('seasonalStartDate', default = None)
    seasonalEndDate = request.GET.get('seasonalEndDate', default = None)
    includeNonDefault = bool(request.GET.get('includeNonDefault', default = False))
    expName = request.GET.get('expName', default = None)
    excludeExpName = request.GET.get('excludeExpName', default = None)
    fileDesc = request.GET.get('fileDesc', default = None)
    returnCitation = bool(request.GET.get('returnCitation', default = False))
    
    result = madWebObj.global_file_search(startDate, endDate, inst, kindat, 
                                          seasonalStartDate, seasonalEndDate, 
                                          includeNonDefault, expName, excludeExpName, 
                                          fileDesc, returnCitation)
    
    fullText = ''
    for item in result:
        fullText += '%s\n' % (item)
    
    return render(request, 'madweb/service.html', {'text': django.utils.safestring.mark_safe(fullText)})
    
    


def get_url_list_from_group_id_service(request):
    """get_url_list_from_group_id_service returns a list of citable urls associated with group id.  
    
    Inputs:
        request/url - contains arguments:
        
            id - group id
            
        Returns one line for each citable url
        
        Returns empty string if experiment id not found.  Skips files that are not Hdf5
    """
    id = int(request.GET['id'])
    
    # create MadrigalDB obj
    madDBObj = madrigal.metadata.MadrigalDB()

    urlList = madDBObj.getListFromGroupId(id)
        
    retStr = ''
    for url in urlList:
        retStr += '%s\n' % (url)
    
    return render(request, 'madweb/service.html', {'text': django.utils.safestring.mark_safe(retStr)})


def set_group_id_from_url_list_service(request):
    """set_group_id_from_url_list sets a list of citable urls to a group id .  
    
    Inputs:
        request/url - contains arguments:
            
            'user_fullname'     user name 
            
            'user_email'        user email
            
            'user_affiliation'  user affiliation
            
            'url' -  citable url.  Multiple arguments allowed
    
    Returns group id (integer) set
    """
    madDB = madrigal.metadata.MadrigalDB()
    
    print(request.GET)
    
    # get required arguments
    urls = request.GET.getlist('url')
    user_fullname = request.GET['user_fullname']
    user_email = request.GET['user_email']
    user_affiliation = request.GET['user_affiliation']
    
    id = madDB.createGroupIdWithList(user_fullname, user_email, user_affiliation, urls)
    
    return render(request, 'madweb/service.html', {'text': str(id)})


    

Functions

def download_web_file_service(request)

download_web_file_service runs the downloadWebFile service.

Inputs

request/url - contains arguments:

expPath - path to file starting at experiments*

Returns comma-delimited data, one for each file:

1. Full path of file

2. File modification time in form YYYY-MM-DD HH:MM:SS (UT time)
Expand source code
def download_web_file_service(request):
    """download_web_file_service runs the downloadWebFile service.
    
    Inputs:
      request/url - contains arguments:
      
        expPath - path to file starting at experiments*

    Returns comma-delimited data, one for each file:
    
        1. Full path of file
        
        2. File modification time in form YYYY-MM-DD HH:MM:SS (UT time)
    """
    expPath = request.GET['expPath']
    madDB = madrigal.metadata.MadrigalDB()
    downloadFile = os.path.join(madDB.getMadroot(), expPath)
    f = open(downloadFile, 'rb')
    thisFile = django.core.files.File(f)
    response = HttpResponse(thisFile, content_type='application/x-octet-stream')
    response['Content-Disposition'] = 'attachment; filename="' + os.path.basename(downloadFile) + '"'
    response['Content-Length'] = os.path.getsize(downloadFile)
    return(response)
def geodetic_to_radar_service(request)

geodetic_to_radar_service runs the geodeticToRadar service.

Inputs

request/url - contains arguments:

slatgd - radar geodetic latitude

slon - radar longitude

saltgd - radar geodetic altitude

gdlat - a comma-separated list of geodetic latitude of point

glon - a comma-separated list of longitude of point. Len must be same as gdlat

gdalt - a comma-separated list of geodetic altitude of point. Len must be same as gdlat

Returns comma-delimited data, one line for point in lists (points treated as individual combinations, not grids):

1. radar azimuth in degrees (0 = north)

2. radar elevation in degrees

3. radar range in km
Expand source code
def geodetic_to_radar_service(request):
    """geodetic_to_radar_service runs the geodeticToRadar service.
    
    Inputs:
      request/url - contains arguments:
      
        slatgd  - radar geodetic latitude
        
        slon - radar longitude
        
        saltgd - radar geodetic altitude
        
        gdlat - a comma-separated list of geodetic latitude of point
        
        glon - a comma-separated list of longitude of point. Len must be same as gdlat
        
        gdalt - a comma-separated list of geodetic altitude of point. Len must be same as gdlat


    Returns comma-delimited data, one line for point in lists (points treated as individual combinations, not grids):

        1. radar azimuth in degrees (0 = north)
        
        2. radar elevation in degrees 
        
        3. radar range in km
    """
    slatgd = float(request.GET['slatgd'])
    slon = float(request.GET['slon'])
    saltgd = float(request.GET['saltgd'])
    oneDParmDict = {'GDLATR': [slatgd],
                    'GDLONR': [slon],
                    'GALTR': [saltgd]}
    gdlatStr = request.GET['gdlat']
    gdlatList = [float(item) for item in gdlatStr.split(',')]
    glonStr = request.GET['glon']
    glonList = [float(item) for item in glonStr.split(',')]
    gdaltStr = request.GET['gdalt']
    gdaltList = [float(item) for item in gdaltStr.split(',')]
    desiredParmList = ['azm', 'elm', 'range']
    dtList = [datetime.datetime(2001,1,1)] # not relevant
    if len(gdlatList) != len(glonList) or len(gdlatList) != len(gdaltList):
        return(HttpResponse('all point list lengths must be equal'))
    
    fullText = ''
    
    delimiter = ','
    for i in range(len(gdlatList)):
         # capture stdout
        old_stdout = sys.stdout
        sys.stdout = mystdout = io.StringIO()
        madrigal.isprint.MadCalculatorGrid(None, desiredParmList, dtList, [gdlatList[i]], 
                                       [glonList[i]], [gdaltList[i]], summary=None,
                                       oneDParmDict=oneDParmDict)
        text = mystdout.getvalue()
        sys.stdout = old_stdout
        for line in text.split('\n'):
            items = line.split()
            fullText += delimiter.join(items) + '\n'
    
    return render(request, 'madweb/service.html', {'text': fullText})
def get_experiment_files_service(request)

get_experiment_files_service runs the getExperimentFilesService.py service.

Inputs

request/url - contains arguments:

id - local experiment id

Returns comma-delimited data, one line for each experiment file, with the following fields:

1. file.name (string) Example '/opt/mdarigal/blah/mlh980120g.001'

2. file.kindat (int) Kindat code.  Example: 3001

3. file.kindat desc (string) Kindat description: Example 'Basic Derived Parameters'

4. file.category (int) (1=default, 2=variant, 3=history, 4=real-time)

5. file.status (string)('preliminary', 'final', or any other description)

6. file.permission (int)  0 for public, 1 for private.  For now will not return private files.

7. file DOI (string) - citable url to file

Returns empty string if experiment id not found. Skips files that are not Hdf5

Expand source code
def get_experiment_files_service(request):
    """get_experiment_files_service runs the getExperimentFilesService.py service.  
    
    Inputs:
        request/url - contains arguments:
        
            id - local experiment id
            
        Returns comma-delimited data, one line for each experiment file, with the following fields:

            1. file.name (string) Example '/opt/mdarigal/blah/mlh980120g.001'
            
            2. file.kindat (int) Kindat code.  Example: 3001
            
            3. file.kindat desc (string) Kindat description: Example 'Basic Derived Parameters'
            
            4. file.category (int) (1=default, 2=variant, 3=history, 4=real-time)
            
            5. file.status (string)('preliminary', 'final', or any other description)
            
            6. file.permission (int)  0 for public, 1 for private.  For now will not return private files.
            
            7. file DOI (string) - citable url to file
        
        Returns empty string if experiment id not found.  Skips files that are not Hdf5
    """
    id = int(request.GET['id'])
    
    # create MadrigalDB obj
    madDBObj = madrigal.metadata.MadrigalDB()

    # create MadrigalExperiments object to get full file name
    madExpObj = madrigal.metadata.MadrigalExperiment(madDBObj)

    # create Madrigal Kindat to get Kindat descriptions
    madKindatObj = madrigal.metadata.MadrigalKindat(madDBObj)
    
    madWebObj = madrigal.ui.web.MadrigalWeb(madDBObj)
    trusted = madWebObj.isTrusted()

        
    retStr = ''
    thisUrl = madExpObj.getExpUrlByExpId(id)
    if thisUrl is None:
        raise IOError('No such id: %i' % (id))
    expPath = madExpObj.getExpDirByExpId(id)
    kinst = madExpObj.getKinstByExpId(id)
    if os.access(os.path.join(expPath, 'fileTab.txt'), os.R_OK):
        madFileObj = madrigal.metadata.MadrigalMetaFile(madDBObj, os.path.join(expPath, 'fileTab.txt'))
        for i in range(madFileObj.getFileCount()):
            basename = madFileObj.getFilenameByPosition(i)
            name = os.path.join(expPath, basename)
            base_filename, file_extension = os.path.splitext(name)
            if file_extension not in ('.hdf5', '.hdf', '.h5'):
                continue
            kindat = madFileObj.getKindatByPosition(i)
            kindatdesc = madKindatObj.getKindatDescription(kindat, kinst)
            category = madFileObj.getCategoryByPosition(i)
            status = madFileObj.getStatusByPosition(i)
            permission = madFileObj.getAccessByPosition(i)
            doi = madFileObj.getFileDOIUrlByPosition(i)
    
            # skip private files if not trusted
            if trusted == 0 and int(permission) != 0:
                continue
                
            retStr += '%s,%i,%s,%i,%s,%i,%s\n' % \
                   (name,
                    kindat,
                    kindatdesc,
                    category,
                    status,
                    permission,
                    doi)
        
    
    
    return render(request, 'madweb/service.html', {'text': django.utils.safestring.mark_safe(retStr)})
def get_experiments_service(request)

get_experiments_service runs the getExperimentsService.py service.

Inputs

request/url - contains arguments:

code - one or more kindat values

startyear, startmonth, startday, starthour, startmin, startsec

endyear, endmonth, endday, endhour, endmin, endsec

local (defaults to True)

Returns comma-delimited data, one line for each experiment, with the following fields:

1. experiment.id (int) Example: 10000111

2. experiment.url (string) Example: 'http://www.haystack.mit.edu/cgi-bin/madtoc/1997/mlh/03dec97'

3. experiment.name (string) Example: 'Wide Latitude Substorm Study'

4. experiment.siteid (int) Example: 1

5. experiment.sitename (string) Example: 'Millstone Hill Observatory'

6. experiment.instcode (int) Code of instrument. Example: 30

7. experiment.instname (string) Instrument name. Example: 'Millstone Hill Incoherent Scatter Radar'

8. experiment.start year (int) year of experiment start

9. experiment.start month (int) month of experiment start

10. experiment.start day (int) day of experiment start

11. experiment.start hour (int) hour of experiment start

12. experiment.start minute (int) min of experiment start

13. experiment.start second (int) sec of experiment start

14. experiment.end year (int) year of experiment end

15. experiment.end month (int) month of experiment end

16. experiment.end day (int) day of experiment end

17. experiment.end hour (int) hour of experiment end

18. experiment.end minute (int) min of experiment end

19. experiment.end second (int) sec of experiment end

20. experiment.isLocal (int) 1 if local, 0 if not

21.experiment.PI (string) Experiment PI name Example: 'Phil Erickson'

22. experiment.PIEmail (string) Experiment PI email Example: 'perickson@haystack.mit.edu'

23. utc timestamp of last update to experiment

24. security value
Expand source code
def get_experiments_service(request):
    """get_experiments_service runs the getExperimentsService.py service.  
    
    Inputs:
        request/url - contains arguments:
        
            code - one or more kindat values
            
            startyear, startmonth, startday, starthour, startmin, startsec
            
            endyear, endmonth, endday, endhour, endmin, endsec
            
            local (defaults to True)
            
    Returns comma-delimited data, one line for each experiment, with the following fields:

        1. experiment.id (int) Example: 10000111
        
        2. experiment.url (string) Example: 'http://www.haystack.mit.edu/cgi-bin/madtoc/1997/mlh/03dec97'
        
        3. experiment.name (string) Example: 'Wide Latitude Substorm Study'
        
        4. experiment.siteid (int) Example: 1
        
        5. experiment.sitename (string) Example: 'Millstone Hill Observatory'
        
        6. experiment.instcode (int) Code of instrument. Example: 30
        
        7. experiment.instname (string) Instrument name. Example: 'Millstone Hill Incoherent Scatter Radar'
        
        8. experiment.start year (int) year of experiment start
        
        9. experiment.start month (int) month of experiment start
        
        10. experiment.start day (int) day of experiment start
        
        11. experiment.start hour (int) hour of experiment start
        
        12. experiment.start minute (int) min of experiment start
        
        13. experiment.start second (int) sec of experiment start
        
        14. experiment.end year (int) year of experiment end
        
        15. experiment.end month (int) month of experiment end
        
        16. experiment.end day (int) day of experiment end
        
        17. experiment.end hour (int) hour of experiment end
        
        18. experiment.end minute (int) min of experiment end
        
        19. experiment.end second (int) sec of experiment end
        
        20. experiment.isLocal (int) 1 if local, 0 if not
        
        21.experiment.PI (string) Experiment PI name Example: 'Phil Erickson'

        22. experiment.PIEmail (string) Experiment PI email Example: 'perickson@haystack.mit.edu'
        
        23. utc timestamp of last update to experiment
        
        24. security value
        
    """
    codeList = request.GET.getlist('code')
    codeList = [int(code) for code in codeList]
    startyear = int(request.GET['startyear'])
    startmonth = int(request.GET['startmonth'])
    startday = int(request.GET['startday'])
    starthour = int(request.GET['starthour'])
    startmin = int(request.GET['startmin'])
    startsec = int(request.GET['startsec'])
    endyear = int(request.GET['endyear'])
    endmonth = int(request.GET['endmonth'])
    endday = int(request.GET['endday'])
    endhour = int(request.GET['endhour'])
    endmin = int(request.GET['endmin'])
    endsec = int(request.GET['endsec'])
    try:
        local = int(request.GET['local'])
    except:
        local = 1
    
    
    # if startsec or endsec in (60, 61), handle correctly
    if startsec in (60, 61):
        tmpTime = datetime.datetime(startyear,
                                    startmonth,
                                    startday,
                                    starthour,
                                    startmin,
                                    59)
        tmpTime += datetime.timedelta(0, startsec - 59)
        startyear = tmpTime.year
        startmonth = tmpTime.month
        startday = tmpTime.day
        starthour = tmpTime.hour
        startmin = tmpTime.minute
        startsec = tmpTime.second

    if endsec in (60, 61):
        tmpTime = datetime.datetime(endyear,
                                    endmonth,
                                    endday,
                                    endhour,
                                    endmin,
                                    59)
        tmpTime += datetime.timedelta(0, endsec - 59)
        endyear = tmpTime.year
        endmonth = tmpTime.month
        endday = tmpTime.day
        endhour = tmpTime.hour
        endmin = tmpTime.minute
        endsec = tmpTime.second
        
    # if codeList is empty or contains 0, change it to only contain 0
    if len(codeList) == 0 or 0 in codeList:
        codeList = [0]
        
    retStr = ''

    # create MadrigalDB obj
    madDBObj = madrigal.metadata.MadrigalDB()

    # get the local site id
    localSiteId = madDBObj.getSiteID()

    # create MadrigalInstrument obj to convert kinst to instrument names
    madInstObj = madrigal.metadata.MadrigalInstrument(madDBObj)

    # create MadrigalSite obj to convert site id to site name
    madSiteObj = madrigal.metadata.MadrigalSite(madDBObj)
    
    madWebObj = madrigal.ui.web.MadrigalWeb(madDBObj)
    trusted = madWebObj.isTrusted()

    # create starttime for filter, if possible
    if startyear != None:
        startTimeFilter = datetime.datetime(startyear,
                        startmonth,
                        startday,
                        starthour,
                        startmin,
                        startsec) 
    else:
        startTimeFilter = None

    # create endtime for filter, if possible
    if endyear != None:
        endTimeFilter = datetime.datetime(endyear,
                          endmonth,
                      endday,
                      endhour,
                      endmin,
                      endsec) 
    else:
        endTimeFilter = None

    # create MadrigalExperiments for local or all files
    if local == 1:
        madExpObj = madrigal.metadata.MadrigalExperiment(madDBObj)
    else:
        # use file expTabAll.txt to get all experiments
        filename = madDBObj.getMadroot()
        if filename[-1] != '/':
            filename += '/'
        filename += 'metadata/expTabAll.txt'
        madExpObj = madrigal.metadata.MadrigalExperiment(madDBObj, filename)
        
    madExpObj.sortByDateSite()


    # loop through the data
    if not startTimeFilter is None:
        position = madExpObj.getStartPosition(startTimeFilter)
    else:
        position = 0
    while(True):
        thisId = madExpObj.getExpIdByPosition(position)
        # check for end
        if thisId == None:
            break
        thisUrl = madExpObj.getExpUrlByPosition(position)
        thisName = madExpObj.getExpNameByPosition(position)
        thisSiteId = madExpObj.getExpSiteIdByPosition(position)
        thisSiteName = madSiteObj.getSiteName(thisSiteId)
        thisInstCode = madExpObj.getKinstByPosition(position)
        thisInstName =madInstObj.getInstrumentName(thisInstCode)
        thisStart = madExpObj.getExpStartDateTimeByPosition(position)
        thisEnd = madExpObj.getExpEndDateTimeByPosition(position)
        thisSecurity = madExpObj.getSecurityByPosition(position)
        if thisSiteId == localSiteId:
            thisLocal = 1
        else:
            thisLocal = 0
        thisPI = madExpObj.getPIByPosition(position)
        if thisPI in (None, ''):
            thisPI = madInstObj.getContactName(thisInstCode)
        thisPIEmail = madExpObj.getPIEmailByPosition(position)
        if thisPIEmail in (None, ''):
            thisPIEmail = madInstObj.getContactEmail(thisInstCode)
        expDir = madExpObj.getExpDirByPosition(position)
            
        position += 1

        # some experiments set the end of the day to 24:00:00 - not
        # technically correct - reset to 23:59:59
        
        if (thisStart[3] == 24 and thisStart[4] == 0 and thisStart[5] == 0):
            thisStart[3] = 23
            thisStart[4] = 59
            thisStart[5] = 59

        if (thisEnd[3] == 24 and thisEnd[4] == 0 and thisEnd[5] == 0):
            thisEnd[3] = 23
            thisEnd[4] = 59
            thisEnd[5] = 59
        
        # apply filters
        
        # first apply instrument code filter
        if codeList[0] != 0:
            if thisInstCode not in codeList:
                continue

        # apply starttime and endtime filters
        thisStartTime = datetime.datetime(thisStart[0],
                                          thisStart[1],
                                          thisStart[2],
                                          thisStart[3],
                                          thisStart[4],
                                          thisStart[5])

        thisEndTime = datetime.datetime(thisEnd[0],
                                        thisEnd[1],
                                        thisEnd[2],
                                        thisEnd[3],
                                        thisEnd[4],
                                        thisEnd[5])
        
        if startTimeFilter != None:
            if thisEndTime < startTimeFilter:
                continue

        if endTimeFilter != None:
            if thisStartTime > endTimeFilter:
                continue

        # apply local filer
        if local == 1 and thisLocal == 0:
            continue

        # apply security filter
        if trusted == 0 and thisSecurity not in (0,2):
            continue
        
        # create exp timestamp
        if local == 1:
            thisUTTimestamp = int(os.stat(expDir).st_mtime + time.timezone)
        else:
            thisUTTimestamp = 0

        # add this experiment
        retStr += '%i,%s,%s,%i,%s,%i,%s,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%s,%s,%i,%i\n' % \
                (thisId,
                thisUrl,
                thisName,
                thisSiteId,
                thisSiteName,
                thisInstCode,
                thisInstName,
                thisStart[0],
                thisStart[1],
                thisStart[2],
                thisStart[3],
                thisStart[4],
                thisStart[5],
                thisEnd[0],
                thisEnd[1],
                thisEnd[2],
                thisEnd[3],
                thisEnd[4],
                thisEnd[5],
                thisLocal,
                str(thisPI),
                str(thisPIEmail),
                thisUTTimestamp,
                thisSecurity)
                
    return render(request, 'madweb/service.html', {'text': retStr})
def get_instruments_service(request)

get_instruments_service runs the getInstrumentsService.py service.

Inputs

request (ignored)

Returns comma-delimited data, one line for each experiment, with the following fields:

  1. instrument.name Example: 'Millstone Hill Incoherent Scatter Radar'

  2. instrument.code Example: 30

  3. instrument.mnemonic (3 char string) Example: 'mlh'

  4. instrument.latitude Example: 45.0

  5. instrument.longitude Example: 110.0

  6. instrument.altitude Example: 0.015 (km)

  7. instrument.category Example: 'Incoherent Scatter Radars'

  8. contact name

  9. contact email

Expand source code
def get_instruments_service(request):
    """get_instruments_service runs the getInstrumentsService.py service.  
    
    Inputs:
        request (ignored)
        
        Returns comma-delimited data, one line for each experiment, with the following fields:

        1. instrument.name  Example: 'Millstone Hill Incoherent Scatter Radar'

        2. instrument.code Example: 30

        3. instrument.mnemonic (3 char string) Example: 'mlh'

        4. instrument.latitude  Example: 45.0

        5. instrument.longitude  Example: 110.0

        6. instrument.altitude   Example: 0.015 (km) 
        
        7. instrument.category  Example: 'Incoherent Scatter Radars'
        
        8. contact name
        
        9. contact email
    """
    # create MadrigalDB obj
    madDBObj = madrigal.metadata.MadrigalDB()

    # create MadrigalInstument object
    madInst = madrigal.metadata.MadrigalInstrument(madDBObj)

    # get instrument list
    instList = madInst.getInstrumentList()

    # loop through each instrument
    instStr = ''
    for inst in instList:
        name = inst[0]
        code = inst[2]
        mnemonic = inst[1]
        latitude = madInst.getLatitude(code)
        if latitude == None:
            latitude = 0.0
        longitude = madInst.getLongitude(code)
        if longitude == None:
            longitude = 0.0
        altitude = madInst.getAltitude(code)
        if altitude == None:
            altitude = 0.0
        category = madInst.getCategory(code)
        if category == None:
            category = ''
        # print data
        contactName = madInst.getContactName(code)
        contactEmail = madInst.getContactEmail(code)
        instStr += '%s,%i,%s,%f,%f,%f,%s,%s,%s\n' % (name,
                                                     code,
                                                     mnemonic,
                                                     latitude,
                                                     longitude,
                                                     altitude,
                                                     category,
                                                     str(contactName),
                                                     str(contactEmail))
        
    return render(request, 'madweb/service.html', {'text': instStr})
def get_madfile_service(request)

get_madfile_service runs the getMadfile.cgi service.

Inputs

request/url - contains arguments:

'fileName': The full path to the file to be downloaded as.

'fileType': -1 for ascii, -2 for Hdf5, -3 for netCDF4. No other values supported

'user_fullname'     user name

'user_email'        user email

'user_affiliation'  user affiliation

Returns file as either column delimited ascii, Hdf5, or netCDF4.

Expand source code
def get_madfile_service(request):
    """get_madfile_service runs the getMadfile.cgi service.  
    
    Inputs:
        request/url - contains arguments:
        
            'fileName': The full path to the file to be downloaded as.
            
            'fileType': -1 for ascii, -2 for Hdf5, -3 for netCDF4. No other values supported
            
            'user_fullname'     user name 
            
            'user_email'        user email
            
            'user_affiliation'  user affiliation
    
    Returns file as either column delimited ascii, Hdf5, or netCDF4.
    """
    madDB = madrigal.metadata.MadrigalDB()
    madWebObj = madrigal.ui.web.MadrigalWeb(madDB)
    
    # get required arguments
    fileName = request.GET['fileName']
    fileType = int(request.GET['fileType'])
    user_fullname = request.GET['user_fullname']
    user_email = request.GET['user_email']
    user_affiliation = request.GET['user_affiliation']
    
    if fileType not in (-1, -2, -3):
        return(HttpResponse('<p>fileType %i not allowed: -1 for ascii, -2 for Hdf5, -3 for netCDF4</p>' % (fileType)))
    
    # log data access
    madWebObj.logDataAccess(fileName, user_fullname, user_email, user_affiliation)
    
    if fileType in (-1, -3):
        # may need to create temp file
        filepath, file_extension = os.path.splitext(fileName)
        basename = os.path.basename(filepath)
        dirname = os.path.dirname(fileName)
        if fileType == -1:
            cachedTxtFile = os.path.join(dirname, 'overview', os.path.basename(fileName) + '.txt.gz')
            tmpFile = os.path.join(tempfile.gettempdir(), basename + '.txt.gz')
            if os.access(cachedTxtFile, os.R_OK):
                shutil.copy(cachedTxtFile, tmpFile)
            else:
                tmpFile = os.path.join(tempfile.gettempdir(), basename + '.txt')
                madrigal.cedar.convertToText(fileName, tmpFile)
        else:
            cachedNCFile = os.path.join(dirname, 'overview', os.path.basename(fileName) + '.nc')
            tmpFile = os.path.join(tempfile.gettempdir(), basename + '.nc')
            if os.access(cachedNCFile, os.R_OK):
                shutil.copy(cachedNCFile, tmpFile)
            else:
                try:
                    madrigal.cedar.convertToNetCDF4(fileName, tmpFile)
                except IOError:
                    cedarObj = madrigal.cedar.MadrigalCedarFile(fileName)
                    cedarObj.write('netCDF4', tmpFile)
        
    else:
        tmpFile = fileName
        
    f = open(tmpFile, 'rb')
    filename = os.path.basename(tmpFile)
    chunk_size = 8192
    file_type = mimetypes.guess_type(tmpFile)[0]
    if file_type is None:
        file_type = 'application/octet-stream'
    response = StreamingHttpResponse(FileWrapper(f, chunk_size),
                                     content_type=file_type)
    response['Content-Length'] = os.path.getsize(tmpFile)    
    response['Content-Disposition'] = "attachment; filename=%s" % (filename)
    if fileType in (-1, -3):
        os.remove(tmpFile)
    return(response)
def get_parameters_service(request)

get_parameters_service runs the getParametersService.py service.

Inputs

request/url - contains arguments:

filename=<full path to data file>

Returns backslash-delimited data, one for each parameter either measured or derivable, with the following fields:

1. parameter.mnemonic (string) Example 'dti'

2. parameter.description (string) Example:
    "F10.7 Multiday average observed (Ott)"

3. parameter.isError (int) 1 if error parameter, 0 if not

4. parameter.units (string) Example "W/m2/Hz"

5. parameter.isMeasured (int) 1 if measured, 0 if derivable

6. parameter.category (string) Example: "Time Related Parameter"

7. parameter.isSure (int) - 1 if parameter can be found for every record, 0 if can only be found for some.
    Not relevant to Madrigal 3, where always 1

8. parameter.isAddIncrement - 1 if additional increment, 0 if normal (Added in Madrigal 2.5)
    Not relevant to Madrigal 3, where always -1
Expand source code
def get_parameters_service(request):
    """get_parameters_service runs the getParametersService.py service.  
    
    Inputs:
        request/url - contains arguments:
        
            filename=<full path to data file>
            
        Returns backslash-delimited data, one for each parameter either measured or derivable, with the following fields:

            1. parameter.mnemonic (string) Example 'dti'
            
            2. parameter.description (string) Example:
                "F10.7 Multiday average observed (Ott)"
                
            3. parameter.isError (int) 1 if error parameter, 0 if not
            
            4. parameter.units (string) Example "W/m2/Hz"
            
            5. parameter.isMeasured (int) 1 if measured, 0 if derivable
            
            6. parameter.category (string) Example: "Time Related Parameter"
            
            7. parameter.isSure (int) - 1 if parameter can be found for every record, 0 if can only be found for some.
                Not relevant to Madrigal 3, where always 1
    
            8. parameter.isAddIncrement - 1 if additional increment, 0 if normal (Added in Madrigal 2.5)
                Not relevant to Madrigal 3, where always -1
    """
    filename = request.GET['filename']
    
    # create MadrigalDB obj
    madDBObj = madrigal.metadata.MadrigalDB()

    # create Madrigal File object 
    madFileObj = madrigal.data.MadrigalFile(filename, madDBObj)

    # create Madrigal Parameter object
    madParmObj = madrigal.data.MadrigalParameters(madDBObj)
    
    # create Madrigal web object 
    madWebObj = madrigal.ui.web.MadrigalWebFormat()
    

    # create lists of parameters
    measParmList = []
    derivedParmList = []
    allParmList = []
    sureParmList = []

    # use the comprehensive list of parameters to check if derivable
    parmList = madWebObj.getFormat('Comprehensive')

    # populate lists
    madFileObj.getMeasDervBothParmLists(parmList,
                                        measParmList,
                                        derivedParmList,
                                        allParmList,
                                        sureParmList)

    retStr = ''
    
    # loop through allParmList and output results
    for parm in allParmList:
        description = madParmObj.getSimpleParmDescription(parm)
        isNorm = madParmObj.getParmType(parm)
        if isNorm == 1:
            isError = 0
        else:
            isError = 1
        units = madParmObj.getParmUnits(parm)
        if parm in measParmList:
            isMeasured = 1
        else:
            isMeasured = 0
        if parm in sureParmList:
            isSure = 1
        else:
            isSure = 0
        category = madParmObj.getParmCategory(parm)
        try:
            if madParmObj.isAddIncrement(parm):
                isAddIncrement = 1
            else:
                isAddIncrement = 0
        except:
            isAddIncrement = -1
        # print out this parm
        retStr += '%s\\%s\\%i\\%s\\%i\\%s\\%i\\%i\n' % (parm,
                                                description,
                                                isError,
                                                units,
                                                isMeasured,
                                                category,
                                                isSure,
                                                isAddIncrement)
        
    return render(request, 'madweb/service.html', {'text': retStr})
def get_url_list_from_group_id_service(request)

get_url_list_from_group_id_service returns a list of citable urls associated with group id.

Inputs

request/url - contains arguments:

id - group id

Returns one line for each citable url

Returns empty string if experiment id not found. Skips files that are not Hdf5

Expand source code
def get_url_list_from_group_id_service(request):
    """get_url_list_from_group_id_service returns a list of citable urls associated with group id.  
    
    Inputs:
        request/url - contains arguments:
        
            id - group id
            
        Returns one line for each citable url
        
        Returns empty string if experiment id not found.  Skips files that are not Hdf5
    """
    id = int(request.GET['id'])
    
    # create MadrigalDB obj
    madDBObj = madrigal.metadata.MadrigalDB()

    urlList = madDBObj.getListFromGroupId(id)
        
    retStr = ''
    for url in urlList:
        retStr += '%s\n' % (url)
    
    return render(request, 'madweb/service.html', {'text': django.utils.safestring.mark_safe(retStr)})
def get_version_service(request)

get_version_service runs the getVersionService.py service.

Inputs

request (ignored)

Returns a single line of text, with the version in the form .[.]

Expand source code
def get_version_service(request):
    """get_version_service runs the getVersionService.py service.  
    
    Inputs:
        request (ignored)
        
        Returns a single line of text, with the version in the form <major_version_int>.<minor_version_int>[.<sub_version_int>]
    """
    madDB = madrigal.metadata.MadrigalDB()
    siteID = madDB.getSiteID()
    madSiteObj = madrigal.metadata.MadrigalSite(madDB)
    return(HttpResponse(madSiteObj.getSiteVersion(siteID)))
def global_file_search_service(request)

global_file_search_service returns a list of full paths to files or citable urls based on search arguments

Inputs

request/url - contains arguments:

startDate: start date in form YYYY-MM-DD to filter experiments before
endDate: end date in form YYYY-MM-DD to filter experiments after 
inst: (optional, multiple allowed) an instrument code or name. For names,
    fnmatch will be used. If not set, all instruments used. 
kindat: (optional, multiple allowed) a kind of data codes or name. For names,
    fnmatch will be used. If not set, all kinds of data used.
seasonalStartDate: (optional) in form MM/DD, rejects all days earlier in year. If not set
    implies 01/01
seasonalEndDate: (optional) in form MM/DD, rejects all days later in year. If not set
    implies 12/31
includeNonDefault: (optional) if "True", include realtime files when there are no default. 
    If not set, only default files.
expName: (optional)  - filter experiments by the experiment name.  fnmatch rules
    If not set, no filtering by experiment name.
excludeExpName: (optional)  - exclude experiments by the experiment name.  fnmatch rules  
    If not set, no excluding experiments by experiment name.
fileDesc: (optional) filter files using input file Description string via fnmatch. 
    If not set, in no filtering by file name
returnCitation: (optional) if True, return a list of file citations.  If not set, return
    a list of full paths to the files selected
Expand source code
def global_file_search_service(request):
    """global_file_search_service returns a list of full paths to files or citable urls based on search arguments
    
    Inputs:
        request/url - contains arguments:
        
            startDate: start date in form YYYY-MM-DD to filter experiments before
            endDate: end date in form YYYY-MM-DD to filter experiments after 
            inst: (optional, multiple allowed) an instrument code or name. For names,
                fnmatch will be used. If not set, all instruments used. 
            kindat: (optional, multiple allowed) a kind of data codes or name. For names,
                fnmatch will be used. If not set, all kinds of data used.
            seasonalStartDate: (optional) in form MM/DD, rejects all days earlier in year. If not set
                implies 01/01
            seasonalEndDate: (optional) in form MM/DD, rejects all days later in year. If not set
                implies 12/31
            includeNonDefault: (optional) if "True", include realtime files when there are no default. 
                If not set, only default files.
            expName: (optional)  - filter experiments by the experiment name.  fnmatch rules
                If not set, no filtering by experiment name.
            excludeExpName: (optional)  - exclude experiments by the experiment name.  fnmatch rules  
                If not set, no excluding experiments by experiment name.
            fileDesc: (optional) filter files using input file Description string via fnmatch. 
                If not set, in no filtering by file name
            returnCitation: (optional) if True, return a list of file citations.  If not set, return
                a list of full paths to the files selected
    
    """
    madDB = madrigal.metadata.MadrigalDB()
    madWebObj = madrigal.ui.web.MadrigalWeb(madDB)
    
    # get required arguments
    startDate = request.GET['startDate']
    endDate = request.GET['endDate']
    startDate = datetime.datetime.strptime(startDate, '%Y-%m-%d')
    endDate = datetime.datetime.strptime(endDate, '%Y-%m-%d')
        
    # get optional arguments
    inst = request.GET.getlist('inst')
    if inst == []:
        inst = None
    kindat = request.GET.getlist('kindat')
    if kindat == []:
        kindat = None
    seasonalStartDate = request.GET.get('seasonalStartDate', default = None)
    seasonalEndDate = request.GET.get('seasonalEndDate', default = None)
    includeNonDefault = bool(request.GET.get('includeNonDefault', default = False))
    expName = request.GET.get('expName', default = None)
    excludeExpName = request.GET.get('excludeExpName', default = None)
    fileDesc = request.GET.get('fileDesc', default = None)
    returnCitation = bool(request.GET.get('returnCitation', default = False))
    
    result = madWebObj.global_file_search(startDate, endDate, inst, kindat, 
                                          seasonalStartDate, seasonalEndDate, 
                                          includeNonDefault, expName, excludeExpName, 
                                          fileDesc, returnCitation)
    
    fullText = ''
    for item in result:
        fullText += '%s\n' % (item)
    
    return render(request, 'madweb/service.html', {'text': django.utils.safestring.mark_safe(fullText)})
def isprint_service(request)

isprint_service runs the isprintService.py service.

Inputs

request/url - contains arguments:

'file': The full path to the file to be analyzed by isprint.  If over 50 MB, returns error message.

'parms': Multiple requested parameters, space (+) separated.

'filters': Multiple of filters desired, as in isprint command

'user_fullname'     user name

'user_email'        user email

'user_affiliation'  user affiliation

'output' - option argument specifying output file basename.  Will be Hdf5 format if extension in
    ('hdf5', 'h5', 'hdf').  Will be netCDF4 is extension is '.nc'. Otherwise ascii. If not
    given, output is ascii.

'header':  t for headers, f for no header.  Defaults to no header. Ignored if not ascii output

Returns data as either column delimited ascii, Hdf5, or netCDF4.

Expand source code
def isprint_service(request):
    """isprint_service runs the isprintService.py service.  
    
    Inputs:
        request/url - contains arguments:
        
            'file': The full path to the file to be analyzed by isprint.  If over 50 MB, returns error message.
            
            'parms': Multiple requested parameters, space (+) separated.
            
            'filters': Multiple of filters desired, as in isprint command
            
            'user_fullname'     user name 
            
            'user_email'        user email
            
            'user_affiliation'  user affiliation
            
            'output' - option argument specifying output file basename.  Will be Hdf5 format if extension in
                ('hdf5', 'h5', 'hdf').  Will be netCDF4 is extension is '.nc'. Otherwise ascii. If not
                given, output is ascii.
                
            'header':  t for headers, f for no header.  Defaults to no header. Ignored if not ascii output
    
    Returns data as either column delimited ascii, Hdf5, or netCDF4.
    """
    madDB = madrigal.metadata.MadrigalDB()
    madWebObj = madrigal.ui.web.MadrigalWeb(madDB)
    
    # get required arguments
    thisFile = request.GET['file']
    parms = request.GET.getlist('parms')
    filters = request.GET.getlist('filters')
    user_fullname = request.GET['user_fullname']
    user_email = request.GET['user_email']
    user_affiliation = request.GET['user_affiliation']
        
    # get optional arguments
    try:
        output = os.path.basename(request.GET['output'])
        filename, file_extension = os.path.splitext(output)
        if file_extension in ('.hdf5', '.h5', '.hdf'):
            format = 'Hdf5'
        elif file_extension in ('.nc',):
            format = 'netCDF4'
        else:
            format = 'ascii'
    except:
        format = 'ascii'
        output = None
        
    # verify thisFile exists, not too big
    errorMessage = None
    if not os.access(thisFile, os.R_OK):
        errorMessage = 'File %s not found' % (thisFile)
    elif os.path.getsize(thisFile) > 200.0E6:
        errorMessage = 'File %s greater than 200 MB in size - running dynamic file creation not possible.  Please use  -- download as is -- instead.' % (thisFile)
    if not errorMessage is None:
        return render(request, 'madweb/service.html', {'text': errorMessage})
                
    if not output is None:
        # we need to write to a download file
        downloadFile = os.path.join(tempfile.gettempdir(), output)
        if os.access(downloadFile, os.R_OK):
            try:
                os.remove(downloadFile)
            except:
                pass
    try:
        header = request.GET['header']
        if header not in ('t', 'f'):
            raise ValueError('Unknown header value <%s>' % (header))
    except:
        header = 'f'
        
    # log data access
    madWebObj.logDataAccess(thisFile, user_fullname, user_email, user_affiliation)
        
    # run isprint command
    cmd = '%s/bin/isprint file=%s ' % (madDB.getMadroot(), thisFile)
    if not output is None:
        cmd += 'output=%s ' % (downloadFile)
    delimiter = ' '
    cmd += delimiter.join(parms) + ' '
    filterStr = delimiter.join(filters)
    cmd += filterStr + ' '
    if format == 'ascii':
        cmd += 'summary=f '
        cmd += 'header=%s ' % (header)
        
    if output is None:
        # text response
        #result = subprocess.check_output(cmd.split())
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        result,errtext = p.communicate()
        if p.returncode != 0:
            result = errtext
        if type(result) in (bytes, numpy.bytes_):
            result = result.decode('utf-8')
        if header == 'f':
            index = result.find('\n')
            result = result[index+1:]
        return render(request, 'madweb/service.html', {'text': result})
    else:
        # file download response
        #subprocess.check_call(cmd.split())
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        result,errtext = p.communicate()
        if p.returncode != 0:
            # write the error to result file
            f = open(downloadFile, 'w')
            if type(errtext) in (bytes, numpy.bytes_):
                errtext = errtext.decode('utf-8')
            f.write(errtext)
            f.close()
        
        f = open(downloadFile, 'rb')
        filename = os.path.basename(downloadFile)
        chunk_size = 8192
        file_type = mimetypes.guess_type(downloadFile)[0]
        if file_type is None:
            file_type = 'application/octet-stream'
        response = StreamingHttpResponse(FileWrapper(f, chunk_size),
                                         content_type=file_type)
        response['Content-Length'] = os.path.getsize(downloadFile)    
        response['Content-Disposition'] = "attachment; filename=%s" % (filename)
        os.remove(downloadFile)
        return(response)
def list_file_times_service(request)

list_file_times_service runs the listFileTimes service.

Inputs

request/url - contains arguments:

Optional: expDir - experiment directory to list. Can be absolute or relative to experiments[0-9]. Default is all files in $MADROOT/experiments

Returns comma-delimited data, one for each file:

1. Full path of file

2. File modification time in form YYYY-MM-DD HH:MM:SS (UT time)
Expand source code
def list_file_times_service(request):
    """list_file_times_service runs the listFileTimes service.
    
    Inputs:
      request/url - contains arguments:
      
        Optional: expDir - experiment directory to list.  Can be absolute or relative to
            experiments[0-9]*. Default is all files in $MADROOT/experiments*

    Returns comma-delimited data, one for each file:
    
        1. Full path of file
        
        2. File modification time in form YYYY-MM-DD HH:MM:SS (UT time)
    """
    expDir = None
    try:
        expDir = request.GET['expDir']
    except:
        pass
    madDB = madrigal.metadata.MadrigalDB()
    fileList = madDB.listFileTimes(expDir)
    fullText = '\n\n'
    for filename, filetime in fileList:
        fullText += "\'%s\', %s\n" % (filename, filetime.strftime('%Y-%m-%d %H:%M:%S'))
        
    return render(request, 'madweb/service.html', {'text': django.utils.safestring.mark_safe(fullText)})
def mad_calculator2_service(request)

mad_calculator2_service runs the madCalculator2 service.

Differs from madCalulator in that positions are a list rather than a grid.

Inputs

request/url - contains arguments:

year, month, day, hour, min, sec

lats - comma separated list of latitudes to analyze

longs - comma separated list of longitudes to analyze. Len must == len(lats)

alts - comma separated list of altitudes to analyze. Len must == len(lats)

parms - comma delimited string of Madrigal parameters desired

oneD - zero or more mnemonics,float values to set input 1D values
    Example:  &oneD=kinst,31.0&oneD=elm,45.0

twoD - zero or more mnemonics,comma-separate float list of len(lats) to set input 2D values
    Example:  twoD=te,1000,1100,1200  twoD=ti,1000,1000,1000
              where there are 3 lats

Returns comma-delimited data, one line for each lat value, with the following fields:

1. latitude

2. longitude

3. altitude

4. Values for each Madrigal parameter listed in argument parms, separated by whitespace
Expand source code
def mad_calculator2_service(request):
    """mad_calculator2_service runs the madCalculator2 service.
    
    Differs from madCalulator in that positions are a list rather than a grid.  
    
    Inputs:
        request/url - contains arguments:
        
            year, month, day, hour, min, sec 
            
            lats - comma separated list of latitudes to analyze
            
            longs - comma separated list of longitudes to analyze. Len must == len(lats)
            
            alts - comma separated list of altitudes to analyze. Len must == len(lats)
            
            parms - comma delimited string of Madrigal parameters desired
            
            oneD - zero or more mnemonics,float values to set input 1D values
                Example:  &oneD=kinst,31.0&oneD=elm,45.0
                
            twoD - zero or more mnemonics,comma-separate float list of len(lats) to set input 2D values
                Example:  twoD=te,1000,1100,1200  twoD=ti,1000,1000,1000
                          where there are 3 lats
    
    Returns comma-delimited data, one line for each lat value,
    with the following fields:

        1. latitude
        
        2. longitude
        
        3. altitude
        
        4. Values for each Madrigal parameter listed in argument parms, separated by whitespace
    """
    if request.method == 'POST':
        reqDict = request.POST
    else:
        reqDict = request.GET
    try:
        year = int(reqDict.get('year'))
    except TypeError:
        return(HttpResponse('<p>madCalculator2Service requires year</p>'))
    month = int(reqDict['month'])
    day = int(reqDict['day'])
    hour = int(reqDict['hour'])
    minute = int(reqDict['min'])
    second = int(reqDict['sec'])
    dt = datetime.datetime(year, month, day, hour, minute, second)
    
    latsStr = reqDict['lats']
    lats = [float(item) for item in latsStr.split(',')]
    longsStr = reqDict['longs']
    longs = [float(item) for item in longsStr.split(',')]
    altsStr = reqDict['alts']
    alts = [float(item) for item in altsStr.split(',')]
    
    parms = reqDict['parms']
    desiredParmList = [item.strip() for item in ['gdlat','glon','gdalt'] + parms.split(',')]
    
    oneDList = reqDict.getlist('oneD')
    oneDParmDict = {}
    for oneDStr in oneDList:
        mnem, strValue = oneDStr.split(',')
        oneDParmDict[mnem] = [float(strValue)]
        
    twoDList = reqDict.getlist('twoD')
        
    twoDParmDict = {}
    for twoDStr in twoDList:
        items = twoDStr.split(',')
        if len(items) != 1 + len(lats):
            raise ValueError('twoDstr %s not correct number of points' % (str(twoDStr)))
        mnem = items[0]
        floatValues = [float(item) for item in items[1:]]
        # now we need to expand these values to be two dimensional 1 x len(lats)
        values = numpy.zeros((1,len(lats)), dtype=numpy.float)
        values[0][:] = floatValues
        twoDParmDict[mnem] = values

    # capture stdout
    old_stdout = sys.stdout
    sys.stdout = mystdout = io.StringIO()
    madrigal.isprint.MadCalculatorList(None, desiredParmList, [dt], lats, longs, alts, 
                                       oneDParmDict, twoDParmDict, summary=None)
    text = mystdout.getvalue()
    sys.stdout = old_stdout
    
    return render(request, 'madweb/service.html', {'text': text})
def mad_calculator3_service(request)

mad_calculator3_service runs the madCalculator3 service.

Differs from madCalulator in that multiple times, each with a unique list of positions, can be passed in.

Inputs

request/url - contains arguments:

year - a comma-separated list of years - (required)

month - a comma-separated list of months - (required)

day - a comma-separated list of days - (required)

hour - a comma-separated list of hours - (required)

min - a comma-separated list of minutes - (required)

sec - a comma-separated list of seconds - (required)

numPos - a comma-sepatated list of the number of positions for each time - (required)

lats - a comma-separated list of geodetic latitudes, -90 to 90 (required). Listed for first time, then second, etc. Total must be equal to the sum of numPos.

longs - a comma-separated list of longitudes (required) Listed for first time, then second, etc. Total must be equal to the sum of numPos.

alts - a comma-separated list of geodetic altitudes in km (required) Listed for first time, then second, etc. Total must be equal to the sum of numPos.

parms - comma delimited string of Madrigal parameters desired (required)

oneD - string in form , This argument allows the user to set any number of one-D parameters to be used in the calculation. Value must be parameter name, comma, list of values as double, where length of list is equal to number of times. Example: &oneD=kinst,31.0,31.0&oneD=elm,45.0,50 (optional - 0 or more allowed)

twoD=, (optional - 0 or more allowed) This argument allows the user to set any number of two-D parameters to be used in the calculation. Value must be parameter name, comma, comma-separated values. Number of values must equal the sum of numPos. Order is first time values first, then second time values, etc Example: twoD=te,1000,1100,1200,1000,1100,1200 &twoD=ti,1000,1000,1000,1000,1000,1000 where numPos=3,3

Returns comma-delimited data, one line for each location. Separate times are delimited by line

TIME MM/DD/YYYY HH:MM:SS

Data lines have the following fields:

  1. latitude

  2. longitude

  3. altitude

  4. Values for each Madrigal parameter listed in argument parms, separated by whitespace

Expand source code
def mad_calculator3_service(request):
    """mad_calculator3_service runs the madCalculator3 service.
    
    Differs from madCalulator in that multiple times, each with a unique list of positions, can be passed in.
    
    Inputs:
      request/url - contains arguments:
      
        year - a comma-separated list of years - (required)
        
        month  - a comma-separated list of months - (required)
        
        day - a comma-separated list of days - (required)
        
        hour - a comma-separated list of hours - (required)
        
        min - a comma-separated list of minutes - (required)
        
        sec - a comma-separated list of seconds - (required)
        
        numPos - a comma-sepatated list of the number of positions for each time - (required)
        
        lats - a comma-separated list of geodetic latitudes, -90 to 90 (required).  Listed
                  for first time, then second, etc.  Total must be equal to the sum
                  of numPos.
                  
        longs - a comma-separated list of longitudes (required) Listed
                  for first time, then second, etc.  Total must be equal to the sum
                  of numPos.
                  
        alts - a comma-separated list of geodetic altitudes in km (required) Listed
                  for first time, then second, etc.  Total must be equal to the sum
                  of numPos.
                  
        parms - comma delimited string of Madrigal parameters desired (required)
        
        oneD - string in form <parm>,<comma-separated values> This argument allows the user to
                            set any number of one-D parameters to be used in the calculation.
                            Value must be parameter name, comma, list of values as double,
                            where length of list is equal to number of times.
                            Example:  &oneD=kinst,31.0,31.0&oneD=elm,45.0,50
                            (optional - 0 or more allowed)        
                            
         twoD=<parm>,<values>  (optional - 0 or more allowed) This argument allows the user to
                            set any number of two-D parameters to be used in the calculation.
                            Value must be parameter name, comma, comma-separated values.
                            Number of values must equal the sum of numPos.  Order is
                            first time values first, then second time values, etc
                            Example:  twoD=te,1000,1100,1200,1000,1100,1200 &twoD=ti,1000,1000,1000,1000,1000,1000
                            where numPos=3,3

    Returns comma-delimited data, one line for each location.  Separate times are delimited by line

    TIME MM/DD/YYYY HH:MM:SS
    
    Data lines have the following fields:
    
    1. latitude
    
    2. longitude
    
    3. altitude
    
    4. Values for each Madrigal parameter listed in argument parms, separated by whitespace
    """
    if request.method == 'POST':
        reqDict = request.POST
    else:
        reqDict = request.GET
    try:
        yearList = [int(item) for item in reqDict.get('year').split(',')]
    except AttributeError:
        return(HttpResponse('<p>madCalculator3Service requires year</p>'))
    monthList = [int(item) for item in reqDict.get('month').split(',')]
    dayList = [int(item) for item in reqDict.get('day').split(',')]
    hourList = [int(item) for item in reqDict.get('hour').split(',')]
    minList = [int(item) for item in reqDict.get('min').split(',')]
    secList = [int(item) for item in reqDict.get('sec').split(',')]
    dtList = [datetime.datetime(yearList[i], monthList[i], dayList[i],
                                hourList[i], minList[i], secList[i]) for i in range(len(yearList))]
    numPosStr = reqDict['numPos']
    numPosList = [int(item) for item in numPosStr.split(',')]
    totalPos = 0
    for numPos in numPosList:
        totalPos += numPos
    latsStr = reqDict['lats']
    lats = [float(item) for item in latsStr.split(',')]
    if len(lats) != totalPos:
        return(HttpResponse('wrong number of lats, expected %i' % (totalPos)))
    longsStr = reqDict['longs']
    longs = [float(item) for item in longsStr.split(',')]
    if len(longs) != totalPos:
        return(HttpResponse('wrong number of longs, expected %i' % (totalPos)))
    altsStr = reqDict['alts']
    alts = [float(item) for item in altsStr.split(',')]
    if len(alts) != totalPos:
        return(HttpResponse('wrong number of alts, expected %i' % (totalPos)))
    
    parms = reqDict['parms']
    desiredParmList = [item.strip() for item in ['gdlat','glon','gdalt'] + parms.split(',')]
    
    oneDList = reqDict.getlist('oneD')
    twoDList = reqDict.getlist('twoD')
    
    # since the positions can change with each call, we need to call madrigal.isprint.MadCalculatorGrid once for each time
    startIndex = 0
    endIndex = 0
    fullText = ''
    for timeIndex, numPos in enumerate(numPosList):
        startIndex = endIndex
        endIndex += numPos
        thisLats = lats[startIndex:endIndex]
        thisLongs = longs[startIndex:endIndex]
        thisAlts = alts[startIndex:endIndex]
    
        oneDParmDict = {}
        for oneDStr in oneDList:
            values = oneDStr.split(',')
            if len(values) != 1+len(dtList):
                return(HttpResponse('wrong number of values given for 1D parm %s' % (values[0])))
            oneDParmDict[values[0]] = [float(values[timeIndex+1])]
        
        twoDParmDict = {}
        
        for twoDStr in twoDList:
            values = twoDStr.split(',')
            if len(values) != 1 + totalPos:
                return(HttpResponse('twoDstr %s not correct number of points' % (str(twoDStr))))
            mnem = values[0]
            floatValues = [float(item) for item in values[1+startIndex:1+endIndex]]
            # now we need to expand these values to be two dimensional - 1,len(thisLats)
            values2D = numpy.zeros((1,len(thisLats)), dtype=numpy.float)
            values2D[0][:] = floatValues
            twoDParmDict[mnem] = values2D
            
            
    
        # capture stdout
        old_stdout = sys.stdout
        sys.stdout = mystdout = io.StringIO()
        madrigal.isprint.MadCalculatorList(None, desiredParmList, [dtList[timeIndex]], thisLats, 
                                           thisLongs, thisAlts, 
                                           oneDParmDict, twoDParmDict, summary=None)
        text = mystdout.getvalue()
        sys.stdout = old_stdout
        
        fullText += 'TIME %s\n' % (dtList[timeIndex].strftime('%m/%d/%Y %H:%M:%S'))
        fullText += text
    
    return render(request, 'madweb/service.html', {'text': fullText})
def mad_calculator_service(request)

mad_calculator_service runs the madCalculator service.

Inputs

request/url - contains arguments:

year, month, day, hour, min, sec

startLat - Starting geodetic latitude, -90 to 90 (float)

endLat - Ending geodetic latitude, -90 to 90 (float)

stepLat - Latitude step (0.1 to 90) (float)

startLong - Starting geodetic longitude, -180 to 180  (float)

endLong - Ending geodetic longitude, -180 to 180 (float)

stepLong - Longitude step (0.1 to 180) (float)

startAlt - Starting geodetic altitude, >= 0 (float)

endAlt - Ending geodetic altitude, > 0 (float)

stepAlt - Altitude step (>= 0.1) (float)

parms - comma delimited string of Madrigal parameters desired

oneD - zero or more mnemonics,float values to set input 1D values

Returns comma-delimited data, one line for each combination of lat, long, and alt, with the following fields:

1. latitude

2. longitude

3. altitude

4. Values for each Madrigal parameter listed in argument parms, separated by whitespace
Expand source code
def mad_calculator_service(request):
    """mad_calculator_service runs the madCalculator service.  
    
    Inputs:
        request/url - contains arguments:
        
            year, month, day, hour, min, sec 
            
            startLat - Starting geodetic latitude, -90 to 90 (float)
            
            endLat - Ending geodetic latitude, -90 to 90 (float)
            
            stepLat - Latitude step (0.1 to 90) (float)
            
            startLong - Starting geodetic longitude, -180 to 180  (float)
            
            endLong - Ending geodetic longitude, -180 to 180 (float)
            
            stepLong - Longitude step (0.1 to 180) (float)
            
            startAlt - Starting geodetic altitude, >= 0 (float)
            
            endAlt - Ending geodetic altitude, > 0 (float)
            
            stepAlt - Altitude step (>= 0.1) (float)
            
            parms - comma delimited string of Madrigal parameters desired
            
            oneD - zero or more mnemonics,float values to set input 1D values
    
    Returns comma-delimited data, one line for each combination of lat, long, and alt,
    with the following fields:

        1. latitude
        
        2. longitude
        
        3. altitude
        
        4. Values for each Madrigal parameter listed in argument parms, separated by whitespace
    """
    year = int(request.GET['year'])
    month = int(request.GET['month'])
    day = int(request.GET['day'])
    hour = int(request.GET['hour'])
    minute = int(request.GET['min'])
    second = int(request.GET['sec'])
    try:
        dt = datetime.datetime(year, month, day, hour, minute, second)
    except:
        return(HttpResponse('Illegal time: year %i, month %i, day %i, hour %i, minute %i, second %i' % (year, month, day, hour, minute, second)))
    
    startLat = float(request.GET['startLat'])
    endLat = float(request.GET['endLat'])
    if startLat == endLat:
        endLat += 0.001
    elif startLat > endLat:
        return(HttpResponse('startLat %s cannot be greater than endLat %s' % (str(startLat), str(endLat))))
    stepLat = float(request.GET['stepLat'])
    if stepLat < 0.0:
        return(HttpResponse('stepLat %s cannot be less than zero' % (str(stepLat))))
    elif stepLat == 0.0:
        stepLat = 0.001
    latList = list(numpy.arange(startLat, endLat, stepLat))
    
    startLong = float(request.GET['startLong'])
    endLong = float(request.GET['endLong'])
    if startLong == endLong:
        endLong += 0.001
    elif startLong > endLong:
        return(HttpResponse('startLong %s cannot be greater than endLong %s' % (str(startLong), str(endLong))))
    stepLong = float(request.GET['stepLong'])
    if stepLong < 0.0:
        return(HttpResponse('stepLong %s cannot be less than zero' % (str(stepLong))))
    elif stepLong == 0.0:
        stepLong = 0.001
    lonList = list(numpy.arange(startLong, endLong, stepLong))
    
    startAlt = float(request.GET['startAlt'])
    endAlt = float(request.GET['endAlt'])
    if startAlt == endAlt:
        endAlt += 0.001
    elif startAlt > endAlt:
        return(HttpResponse('startAlt %s cannot be greater than endAlt %s' % (str(startAlt), str(endAlt))))
    stepAlt = float(request.GET['stepAlt'])
    if stepAlt < 0.0:
        return(HttpResponse('stepAlt %s cannot be less than zero' % (str(stepAlt))))
    elif stepAlt == 0.0:
        stepAlt = 0.01
    altList = list(numpy.arange(startAlt, endAlt, stepAlt))
    
    # limit total calculations to 1E5
    total = len(latList) * len(lonList) * len(altList)
    if total > 1.0E5:
        return(HttpResponse('Too many points for madCalculatorService: %i' % (total)))
    
    parms = request.GET['parms']
    desiredParmList = [item.strip() for item in ['gdlat','glon','gdalt'] + parms.split(',')]
    
    oneDList = request.GET.getlist('oneD')
    oneDParmDict = {}
    for oneDStr in oneDList:
        mnem, strValue = oneDStr.split(',')
        oneDParmDict[mnem] = [float(strValue)]
    
    # capture stdout
    old_stdout = sys.stdout
    sys.stdout = mystdout = io.StringIO()
    madrigal.isprint.MadCalculatorGrid(None, desiredParmList, [dt], latList, lonList, altList, 
                                   oneDParmDict, summary=None)
    text = mystdout.getvalue()
    sys.stdout = old_stdout
    
    return render(request, 'madweb/service.html', {'text': text})
def mad_time_calculator_service(request)

mad_time_calculator_service runs the madTimeCalculator service. Input parameters must not be location dependent

Inputs

request/url - contains arguments:

1. startyear - int

2. startmonth - int

3. startday - int

4. starthour - int

5. startmin - int

6. startsec - int

7. endyear - int

8. endmonth - int

9. endday - int

10. endhour - int

11. endmin - int

12. endsec - int

13. stephours - float - number of hours per time step

14. parms - comma delimited string of Madrigal parameters desired (must not depend on location)

Returns comma-delimited data, one line for each year, month, day, hour, minute, and second, with the following fields:

1-6: year, month, day, hour, minute, and second

2. requested parm fields
Expand source code
def mad_time_calculator_service(request):
    """mad_time_calculator_service runs the madTimeCalculator service.  Input parameters must not be location dependent
    
    Inputs:
        request/url - contains arguments:
        
            1. startyear - int 
            
            2. startmonth - int 
            
            3. startday - int
            
            4. starthour - int 
            
            5. startmin - int 
            
            6. startsec - int
            
            7. endyear - int 
            
            8. endmonth - int 
            
            9. endday - int
            
            10. endhour - int 
            
            11. endmin - int 
            
            12. endsec - int
            
            13. stephours - float - number of hours per time step
            
            14. parms - comma delimited string of Madrigal parameters desired (must not depend on location)
    
    Returns comma-delimited data, one line for each year, month, day, hour, minute, and second,
    with the following fields:

        1-6: year, month, day, hour, minute, and second
        
        2. requested parm fields
    """
    startyear = int(request.GET['startyear'])
    startmonth = int(request.GET['startmonth'])
    startday = int(request.GET['startday'])
    starthour = int(request.GET['starthour'])
    startminute = int(request.GET['startmin'])
    startsecond = int(request.GET['startsec'])
    endyear = int(request.GET['endyear'])
    endmonth = int(request.GET['endmonth'])
    endday = int(request.GET['endday'])
    endhour = int(request.GET['endhour'])
    endminute = int(request.GET['endmin'])
    endsecond = int(request.GET['endsec'])
    dt1 = datetime.datetime(startyear, startmonth, startday, starthour, startminute, startsecond)
    dt2 = datetime.datetime(endyear, endmonth, endday, endhour, endminute, endsecond)
    if dt1 > dt2:
        return(HttpResponse('End Datetime %s cannot be before start datetime %s' % (str(dt2), str(dt1))))
    
    stephours = float(request.GET['stephours'])
    if stephours <= 0.0:
        return(HttpResponse('stephours cannot be non-positive: %f' % (stephours)))
    
    dtList = []
    while dt1 <= dt2:
        dtList.append(dt1)
        dt1 += datetime.timedelta(hours=stephours)
    
    parms = request.GET['parms']
    desiredParmList = [item.strip() for item in ['year','month','day','hour','min','sec'] + parms.split(',')]
    
    # no spatial data
    latList = lonList = altList = []
    # capture stdout
    old_stdout = sys.stdout
    sys.stdout = mystdout = io.StringIO()
    madrigal.isprint.MadCalculatorGrid(None, desiredParmList, dtList, latList, lonList, altList, 
                                   summary=None)
    text = mystdout.getvalue()
    sys.stdout = old_stdout
    
    return render(request, 'madweb/service.html', {'text': text})
def radar_to_geodetic_service(request)

radar_to_geodetic_service runs the radarToGeodetic service.

Inputs

request/url - contains arguments:

slatgd - radar geodetic latitude

slon - radar longitude

saltgd - radar geodetic altitude

azs - a comma-separated list of azimuths of point

els - a comma-separated list of elevations of point. Len must be same as azs

ranges - a comma-separated list of ranges to point. Len must be same as azs

Returns comma-delimited data, one line for point in lists (points treated as individual combinations, not grids):

1. geodetic latitude

2. longitude (-180 to 180)

3. geodetic altitude in km
Expand source code
def radar_to_geodetic_service(request):
    """radar_to_geodetic_service runs the radarToGeodetic service.
    
    Inputs:
      request/url - contains arguments:
      
        slatgd  - radar geodetic latitude
        
        slon - radar longitude
        
        saltgd - radar geodetic altitude
        
        azs - a comma-separated list of azimuths of point
        
        els - a comma-separated list of elevations of point. Len must be same as azs
        
        ranges - a comma-separated list of ranges to point. Len must be same as azs


    Returns comma-delimited data, one line for point in lists  (points treated as individual combinations, not grids):

        1. geodetic latitude
        
        2. longitude (-180 to 180)
        
        3. geodetic altitude in km
    """
    slatgd = float(request.GET['slatgd'])
    slon = float(request.GET['slon'])
    saltgd = float(request.GET['saltgd'])
    azStr = request.GET['az']
    azList = [float(item) for item in azStr.split(',')]
    elStr = request.GET['el']
    elList = [float(item) for item in elStr.split(',')]
    rangeStr = request.GET['range']
    rangeList = [float(item) for item in rangeStr.split(',')]
    if len(azList) != len(elList) or len(azList) != len(rangeList):
        return(HttpResponse('all point list lengths must be equal'))
    
    fullText = ''
    
    for i in range(len(azList)):
        gdlat,glon,gdalt = madrigal._derive.radarToGeodetic(slatgd, slon, saltgd,
                                                            azList[i], elList[i], rangeList[i])
        fullText += '%f,%f,%f\n' % (gdlat,glon,gdalt)
        
    return render(request, 'madweb/service.html', {'text': fullText})
def set_group_id_from_url_list_service(request)

set_group_id_from_url_list sets a list of citable urls to a group id .

Inputs

request/url - contains arguments:

'user_fullname'     user name

'user_email'        user email

'user_affiliation'  user affiliation

'url' -  citable url.  Multiple arguments allowed

Returns group id (integer) set

Expand source code
def set_group_id_from_url_list_service(request):
    """set_group_id_from_url_list sets a list of citable urls to a group id .  
    
    Inputs:
        request/url - contains arguments:
            
            'user_fullname'     user name 
            
            'user_email'        user email
            
            'user_affiliation'  user affiliation
            
            'url' -  citable url.  Multiple arguments allowed
    
    Returns group id (integer) set
    """
    madDB = madrigal.metadata.MadrigalDB()
    
    print(request.GET)
    
    # get required arguments
    urls = request.GET.getlist('url')
    user_fullname = request.GET['user_fullname']
    user_email = request.GET['user_email']
    user_affiliation = request.GET['user_affiliation']
    
    id = madDB.createGroupIdWithList(user_fullname, user_email, user_affiliation, urls)
    
    return render(request, 'madweb/service.html', {'text': str(id)})
def trace_magnetic_field_service(request)

trace_magnetic_field_service runs the traceMagneticField service.

Inputs

request/url - contains arguments:

year, month, day, hour, min, sec

inputType (0 for geodetic, 1 for GSM)

outputType (0 for geodetic, 1 for GSM)

  The following parameter depend on inputType:

in1 - a comma-separated list of geodetic altitudes or ZGSMs of starting point

in2 - a comma-separated list of geodetic latitudes or XGSMs of starting point

in3 - a comma-separated list of longitude or YGSM of starting point

  Length of all three lists must be the same

model - 0 for Tsyganenko, 1 for IGRF

qualifier - 0 for conjugate, 1 for north_alt, 2 for south_alt, 3 for apex, 4 for GSM XY plane

stopAlt - altitude in km to stop trace at, if qualifier is north_alt or south_alt.

If other qualifier, this parameter is not required.

Returns comma-delimited data, one line for point in in lists:

1. geodetic altitude or ZGSM of ending point

2. geodetic latitude or XGSM of ending point

3. longitude or YGSM of ending point
Expand source code
def trace_magnetic_field_service(request):
    """trace_magnetic_field_service runs the traceMagneticField service.
    
    Inputs:
      request/url - contains arguments:
      
        year, month, day, hour, min, sec
        
        inputType (0 for geodetic, 1 for GSM)
        
        outputType (0 for geodetic, 1 for GSM)
        
            The following parameter depend on inputType:
            
        in1 - a comma-separated list of geodetic altitudes or ZGSMs of starting point
        
        in2 - a comma-separated list of geodetic latitudes or XGSMs of starting point
        
        in3 - a comma-separated list of longitude or YGSM of starting point

            Length of all three lists must be the same
        
        model - 0 for Tsyganenko, 1 for IGRF
        
        qualifier - 0 for conjugate, 1 for north_alt, 2 for south_alt, 3 for apex, 4 for GSM XY plane
        
        stopAlt - altitude in km to stop trace at, if qualifier is north_alt or south_alt.
        
        If other qualifier, this parameter is not required.

    Returns comma-delimited data, one line for point in in lists:

        1. geodetic altitude or ZGSM of ending point
        
        2. geodetic latitude or XGSM of ending point
        
        3. longitude or YGSM of ending point
    """
    year = int(request.GET['year'])
    month = int(request.GET['month'])
    day = int(request.GET['day'])
    hour = int(request.GET['hour'])
    minute = int(request.GET['min'])
    second = int(request.GET['sec'])
    dt = datetime.datetime(year, month, day, hour, minute, second)
    inputType = int(request.GET['inputType'])
    if inputType not in (0,1):
        return(HttpResponse('inputType must be 0 or 1, not %i' % (inputType)))
    outputType = int(request.GET['outputType'])
    if outputType not in (0,1):
        return(HttpResponse('outputType must be 0 or 1, not %i' % (outputType)))
    in1Str = request.GET['in1']
    in1List = [float(item) for item in in1Str.split(',')]
    in2Str = request.GET['in2']
    in2List = [float(item) for item in in2Str.split(',')]
    in3Str = request.GET['in3']
    in3List = [float(item) for item in in3Str.split(',')]
    if len(in1List) != len(in2List) or len(in1List) != len(in3List):
        return(HttpResponse('All three in* lists must have same length'))
    model = int(request.GET['model'])
    if model not in (0,1):
        return(HttpResponse('model must be 0 or 1, not %i' % (model)))
    qualifier = int(request.GET['qualifier'])
    if qualifier not in (0,1,2,3,4):
        return(HttpResponse('model must be in 0,1,2,3,4 not %i' % (model)))
    try:
        stopAlt = float(request.GET['stopAlt'])
    except:
        stopAlt = 0.0
        
    fullText = ''
    resultArr = numpy.zeros((3,), dtype='f8')
    madDB = madrigal.metadata.MadrigalDB()
    madDeriveObj = madrigal.derivation.MadrigalDerivationMethods(madDB.getMadroot())
    for i in range(len(in1List)):
        madDeriveObj.traceMagneticField(year, month, day, hour, minute, second, 
                                        inputType, outputType, in1List[i], in2List[i], in3List[i], 
                                        model, qualifier, stopAlt, resultArr)
        fullText += '%f,%f,%f\n' % (resultArr[0], resultArr[1], resultArr[2])

    return render(request, 'madweb/service.html', {'text': fullText})
Previous: Remote access - introduction   Up: Remote access programming tutorial toc   Next: Matlab remote access