"""
NOTES
-----
Data is being read into the file correctly.  Data is NOT being retrieved correctly
when making changes in date.  There is a clipping plane problem that is seen (get
rid of reset clipping?).  Might want to consider fixed minimum and maximum so
that data is more understandable.  Glyphs are not correct...they are probably not
being correctly sized.  Want to get text for cities correct instead of them floating
on the wrong side of the plane.
"""


# Allan Spale
# CS 526 - Project 3

import Tkinter
import tkFont
import os
import sys
import vtk
import time
import random

from vtk.tk.vtkTkRenderWindowInteractor import vtkTkRenderWindowInteractor

from array import array

global I_TRUE
global I_FALSE

I_TRUE = 1
I_FALSE = 0



#------------------------- GUI BUILDER CLASSES -----------------------------------
"""
This class is more like a factory than an instantiatable class.  It helps
create Tkinter widgets without a lot of repetitive configuration.
"""
class GUIFactory:
    global I_TRUE, I_FALSE
    
    def __init__( self ):
        self.__dictStandardGridSettings = { 'padx':5, 'pady':5, 'sticky':Tkinter.SW}
        self.__defaultFont = tkFont.Font( size=11 )


    def newLabel( self, rootPanel, labelText, dictExtraConfig ):
        l = Tkinter.Label( rootPanel, font=self.__defaultFont, text=labelText )

        if ( self.__isConfigEmpty( dictExtraConfig ) == I_FALSE ):
            l.config( dictExtraConfig )
            
        return l


    def newEntry( self, rootPanel, fieldIdentifier, dictExtraConfig ):
        e = Tkinter.Entry( rootPanel, font=self.__defaultFont, text=fieldIdentifier )

        if ( self.__isConfigEmpty( dictExtraConfig ) == I_FALSE ):
            e.config( dictExtraConfig )

        print 'Entry...', e
        return e


    def newButton( self, rootPanel, buttonText, buttonCommand, dictExtraConfig ):
        b = Tkinter.Button( rootPanel, font=self.__defaultFont, text=buttonText,
                            command=buttonCommand )
        
        if ( self.__isConfigEmpty( dictExtraConfig ) == I_FALSE ):
            b.config( dictExtraConfig )

        return b


    def newCheckbox( self, rootPanel, checkText, checkCommand, dictExtraConfig ):
        c = Tkinter.Checkbutton( rootPanel, font=self.__defaultFont, text=checkText,
                                 command=checkCommand, indicatoron=I_FALSE )
        
        if ( self.__isConfigEmpty( dictExtraConfig ) == I_FALSE ):
            c.config( dictExtraConfig )

        return c


    def newRadiobutton( self, rootPanel, radioText, radioCommand, sharedVariable,
                        radioValue, dictExtraConfig ):
        r = Tkinter.Radiobutton( rootPanel, font=self.__defaultFont, text=radioText,
                                 command=radioCommand, variable=sharedVariable,
                                 value=radioValue, indicatoron=I_FALSE )

        if ( self.__isConfigEmpty( dictExtraConfig ) == I_FALSE ):
            r.config( dictExtraConfig )

        return r


    def newSlider( self, rootPanel, sliderCommand, valueStart, valueEnd,
                   dictExtraConfig ):

        dictExtraConfig[ 'from' ] = valueStart
        dictExtraConfig[ 'to' ] = valueEnd
        dictExtraConfig[ 'orient' ] = Tkinter.HORIZONTAL
        dictExtraConfig[ 'showvalue' ] = I_FALSE

        s = Tkinter.Scale( rootPanel )
        s.config( dictExtraConfig )
        
        s.bind( '<B1-Motion>', sliderCommand )
        s.bind( '<B2-Motion>', sliderCommand )
        s.bind( '<Button-1>', sliderCommand )
        s.bind( '<Button-2>', sliderCommand )
        s.bind( '<ButtonRelease-1>', sliderCommand )
        s.bind( '<ButtonRelease-2>', sliderCommand )
        
        return s
    

    def newListbox( self, rootPanel, dictExtraConfig ):
        dictExtraConfig[ 'selectmode' ] = Tkinter.SINGLE

        l = Tkinter.Listbox( rootPanel, font=self.__defaultFont )
        l.config( dictExtraConfig )

        return l
    
        
    def packItem( self, object, gridSettings ):
        items = self.__dictStandardGridSettings.keys()

        for x in items:
            gridSettings[ x ] = self.__dictStandardGridSettings[ x ]

        object.grid( gridSettings )


    def __isConfigEmpty( self, dictConfig ):
        if ( dictConfig == None ):
            return I_TRUE
        elif ( len( dictConfig ) > 0 ):
            return I_FALSE
        else:
            return I_TRUE



"""
This is a composited widget made from Tkinter.Scale and Tkinter.Label.
Class methods are available to allow some level of customizability.
"""
class SliderWithValue:
    global I_TRUE, I_FALSE
    
    def __init__( self, rootPanel, startValue, endValue ):
        guiFactory = GUIFactory()
        #self.count = 0
        self.__root = rootPanel
        self.__frame = Tkinter.Frame( self.__root )
                                 
        self.__label = guiFactory.newLabel( self.__frame, '',
                                            { 'padx':5, 'pady':5,
                                              'anchor':Tkinter.SW,
                                              'width':3,
                                              'justify':Tkinter.RIGHT} )
        self.__label.grid(row=0, rowspan=1, column=0, columnspan=3,
                          sticky=Tkinter.NW )

        filler = guiFactory.newLabel( self.__frame, ' ', None )
        filler.grid( row=0, rowspan=1, column=3, columnspan=1 )
                            
        self.__slider = guiFactory.newSlider( self.__frame, self.__updateLabel,
                                              startValue, endValue,
                                              {} )
        self.__slider.grid( row=0, rowspan=1, column=4, columnspan=3,
                            sticky=Tkinter.NE)
        self.__slider.set( startValue )

        self.__modified = I_FALSE
        self.__sliderValue = startValue
        
        self.__updateLabel()


    def addSliderCommandBinding( self, callback ):
        self.__slider.bind( '<B1-Motion>', callback )
        self.__slider.bind( '<B2-Motion>', callback )
        self.__slider.bind( '<Button-1>', callback )
        self.__slider.bind( '<Button-2>', callback )
        self.__slider.bind( '<ButtonRelease-1>', callback )
        self.__slider.bind( '<ButtonRelease-2>', callback )
        

    def configureScale( self, dictConfiguration ):
        self.__slider.config( dictConfiguration )


    def setScaleValue( self, value ):
        self.__slider.set( value )
        self.__updateLabel()

    
    def reset( self ):
        value = self.__slider.cget( 'from' )
        self.__slider.set( value )
        self.__updateLabel()
        self.__modified = I_FALSE

    def configureLabel( self, dictConfiguration ):
        self.__label.config( dictConfiguration )


    def getWidget( self ):
        return self.__frame


    def getValue( self ):
        return self.__slider.get()

    def getValue2( self ):
        return int( self.__label.cget( 'text' ) )


    def getValue3( self ):
        return self.__sliderValue



    def __updateLabel( self, *unusedArgument ):
        #print self.count, self.__valueLabel
        #self.count = self.count+1
        #self.__label = self.__slider.get()
        self.__label.config( text=self.__slider.get() )
        self.__modified = I_TRUE
        self.__sliderValue = self.__slider.get()


    def getModifiedStatus():
        return self.__modified



"""
This is a class that creates a modal dialog.  To prevent confusion of having
the same dialog appear twice (if the user made a selection that would cause
multiple (conflicting) copies of the dialog to appear twice), all dialogs will
be modal.  All dialog boxes will extend this class.
"""
class ModalDialog:
    global I_TRUE, I_FALSE

    
    def __init__( self, topWindow, dialogTitle, useStatusBarFlag ):
        self.__firstTime = I_TRUE
        self.__buildDialog( topWindow, dialogTitle, useStatusBarFlag )

        self.config = {}
        self.config[ 'text' ] = ''
        self.config[ 'foreground' ] = ''
        self.config[ 'background' ] = ''



    def __buildDialog( self, root, title, statusBarFlag ):
        self.__rootWindow = root
        self.__title = title
        self.__statusBarFlag = statusBarFlag
        self.__root = Tkinter.Toplevel( self.__rootWindow )
        self.__root.title( title )
        #self.__root.resizable( 0,0 )
        self.dialog = Tkinter.Frame( self.__root )
        self.dialog.pack()
        

        # constants
        self.B_USE_ERROR_COLOR = I_TRUE
        self.B_NO_ERROR_COLOR = I_FALSE


        """if ( self.__statusBarFlag == I_TRUE ):
            self.__statusBar = Tkinter.Label( self.__root, relief=Tkinter.SUNKEN,
                                              text=' ', justify=Tkinter.LEFT,
                                              anchor=Tkinter.W )
            self.__statusBar.pack()
            self.__statusOriginalBGColor = self.__statusBar.cget( 'background' )
            self.__statusOriginalFGColor = self.__statusBar.cget( 'foreground' )
        """
        self.hide()


    def rebuildDialog( self, newRoot ):
        self.__rootWindow = newRoot
        self.__buildDialog( newRoot, self.__title, self.__statusBarFlag )

        
    def show( self ):
        # modal dialog settings...adopted from "An Introduction to Tkinter"
        print self.__root.state()

        if ( self.__firstTime == I_TRUE ):
            #self.__root.transient( self.__rootWindow )
            self.__firstTime = I_FALSE

        self.__root.deiconify()            
        #self.__root.grab_set() #events sent to this dialog
        #self.__root.focus_set()
        #self.__root.wait_window( self.__root )


    def destroy( self ):
        #self.__root.grab_release()
        self.__root.destroy()


    def hide( self ):
        #self.__root.grab_release()
        #self.__root.withdraw()
        self.__root.iconify()


    def setStatusBarMessage( self, useErrorColorFlag, statusText ):
        if ( self.__statusBarFlag == I_TRUE ):
            self.config[ 'text' ] = statusText

            if ( useErrorColorFlag == I_TRUE ):
                self.config[ 'foreground' ] = '#0000AA'
                self.config[ 'background' ] = '#FFFFFF'
                
            else:
                self.config[ 'foreground' ] = self.__statusOriginalFGColor
                self.config[ 'background' ] = self.__statusOriginalBGColor
    
            self.__statusBar.config( self.config )


    def configStatusBar( self, dictConfiguration ):
        self.__statusBar.config( dictConfiguration )
#------------------------- GUI BUILDER CLASS -----------------------------------


#------------------------- STATION CLASS ----------------------------------------
class Station:
    global I_TRUE, I_FALSE

    # class constants
    I_AVG_WIND_SPEED = 20
    I_AVG_WIND_DIRECTION = 30
    I_AVG_AIR_TEMP = 40
    I_AVG_4_IN_SOIL_TEMP = 50
    I_AVG_HUMIDITY = 60
    I_TOTAL_PRECIP = 70
    I_MISSING_DATA = -9999999

    
    def __init__( self ):
        
        # member variables
        self.__strCodeName = '???'
        self.__strFullName = 'Nowhere'
        self.__hashAvgWindSpeed = {}
        self.__hashAvgWindDirection = {}
        self.__hashAvgAirTemp = {}
        self.__hashAvg4InSoilTemp = {}
        self.__hashAvgHumidity = {}
        self.__hashTotalPrecip = {}
        self.__tupScreenLocation = (0,0)



    def zSetCodeName( self, strCode ):
        self.__strCodeName = strCode


    def strGetCodeName( self ):
        return self.__strCodeName


    def zSetFullName( self, strName ):
        self.__strFullName = strName


    def strGetFullName( self ):
        return self.__strFullName    


    def zSetScreenLocation( self, tupLocation ):
        self.__tupScreenLocation = tupLocation


    def tupGetScreenLocation( self ):
        return self.__tupScreenLocation

        
    def zAddDataRow( self, intDate, hashDataRow ):
        print 'Adding row data: ', hashDataRow
        self.__hashAvgWindSpeed[ intDate ] = hashDataRow[ Station.I_AVG_WIND_SPEED ]
        self.__hashAvgWindDirection[ intDate ] = hashDataRow[ Station.I_AVG_WIND_DIRECTION ]
        self.__hashAvgAirTemp[ intDate ] = hashDataRow[ Station.I_AVG_AIR_TEMP ]
        self.__hashAvg4InSoilTemp[ intDate ] = hashDataRow[ Station.I_AVG_4_IN_SOIL_TEMP ]
        self.__hashAvgHumidity[ intDate ] = hashDataRow[ Station.I_AVG_HUMIDITY ]
        self.__hashTotalPrecip[ intDate ] = hashDataRow[ Station.I_TOTAL_PRECIP ]

        """
        print "ADDED VALUES - ", intDate
        print "============================="
        print 'SPEED: ', self.__hashAvgWindSpeed[ intDate ]
        print 'DIR: ',self.__hashAvgWindDirection[ intDate ]
        print 'AIR TEMP: ',self.__hashAvgAirTemp[ intDate ]
        print '4" SOIL: ',self.__hashAvg4InSoilTemp[ intDate ]
        print 'HUM: ',self.__hashAvgHumidity[ intDate ]
        print 'PRECIP: ',self.__hashTotalPrecip[ intDate ]
        time.sleep( 3 )
        """
        
    # Returns hash function with data...if fails, return empty hash
    def hashGetDataRow( self, intDate ):
        hashData = {}

        # Just have to test if date exists for one hash data type
        if ( self.__hashAvgWindSpeed.has_key( intDate ) == I_TRUE ):
            hashData[ Station.I_AVG_WIND_SPEED ] = self.__hashAvgWindSpeed[ intDate ]
            hashData[ Station.I_AVG_WIND_DIRECTION ] = self.__hashAvgWindDirection[ intDate ]
            hashData[ Station.I_AVG_AIR_TEMP ] = self__hashAvgAirTemp[ intDate ]
            hashData[ Station.I_AVG_4_IN_SOIL_TEMP ] = self.__hashAvg4InSoilTemp[ intDate ]
            hashData[ Station.I_AVG_HUMIDITY ] = self.__hashAvgHumidity[ intDate ]
            hashData[ Station.I_TOTAL_PRECIP ] = self.__hashTotalPrecip[ intDate ]
        else:
            print 'hashGetDataRow'
            print 'At station ', self.__strFullName, '...data is not available for date:', intDate

        return hashData


    # Request a data item for a specific date
    def numGetDataItem( self, intDate, intDataType ):
        print 'Get data item ', intDataType, 'for ', intDate
        
        numDataItem = Station.I_MISSING_DATA

        # Just have to test if date exists for one hash data type
        if ( self.__hashAvgWindSpeed.has_key( intDate ) == I_TRUE ):

            if ( intDataType == Station.I_AVG_WIND_SPEED ):
                numDataItem = self.__hashAvgWindSpeed[ intDate ]
                
            elif ( intDataType == Station.I_AVG_WIND_DIRECTION ):
                numDataItem = self.__hashAvgWindDirection[ intDate ]
                
            elif ( intDataType == Station.I_AVG_AIR_TEMP ):
                numDataItem = self.__hashAvgAirTemp[ intDate ]
                
            elif ( intDataType == Station.I_AVG_4_IN_SOIL_TEMP ):
                numDataItem = self.__hashAvg4InSoilTemp[ intDate ]
                
            elif ( intDataType == Station.I_AVG_HUMIDITY ):
                numDataItem = self.__hashAvgHumidity[ intDate ]
                
            elif ( intDataType == Station.I_TOTAL_PRECIP ):
                numDataItem = self.__hashTotalPrecip[ intDate ]
                
            else:
                print 'Data type ', intDataType, 'does not exist.'

        else:
            print 'GET DATA ITEM FOR SPECIFIC DATE'
            print 'At station ', self.__strFullName, '...data is not available for date:', intDate

        return numDataItem
#------------------------- END OF STATION CLASS ----------------------------------------        



#------------------------- STATIONSET CLASS ----------------------------------------
class StationSet:
    global I_TRUE, I_FALSE

    # Constants (used to extract data from file)
    I_YEAR = 0
    I_MONTH = 1
    I_DAY = 2
    I_AVG_WIND_SPEED = 5
    I_AVG_WIND_DIRECTION = 7
    I_AVG_AIR_TEMP = 15
    I_AVG_HUMIDITY = 21
    I_TOTAL_PRECIP = 25
    I_AVG_4_IN_SOIL_TEMP = 33
    
    I_ERR_AVG_WIND_SPEED = 6
    I_ERR_AVG_WIND_DIRECTION = 8
    I_ERR_AVG_AIR_TEMP = 16
    I_ERR_AVG_HUMIDITY = 22
    I_ERR_TOTAL_PRECIP = 26
    I_ERR_AVG_4_IN_SOIL_TEMP = 34

    CHAR_MISSING_DATA = 'M'

    __I_EARLIEST_YEAR_CODE = 20031100


    def __init__( self ):
        self.__hashStationToFilename = {}
        self.__hashStationToDataObject = {}

        intSuccess = self.iSetupStationFiles( './station-config.txt' )

        if ( intSuccess == I_FALSE ):
            print 'Could not open station-config.txt.'
            print 'Please make sure that this file appears in the same directory as project3.py.'
            sys.exit( 1 )
            

    def iSetupStationFiles( self, strConfigFilename ):
        iStatus = I_FALSE
        iFileStatus = I_FALSE
        listItems = []
        iStationCount = 0

        strFilePath = os.path.normpath( strConfigFilename )
        
        if ( os.path.exists( strFilePath ) == I_TRUE ):
            iStatus = I_TRUE

            fileStationData = open( strFilePath, 'r' )
            strFileLine = fileStationData.readline()
            iLineSize = len( strFileLine )

            while ( iLineSize > 0 ):
                listItems = strFileLine.split( ',' )

                # Order is assumed: Full Name, Code Name, X Location, Y Location,
                #                   Data File Path
                iFileStatus = self.__iAddNewStation( listItems[ 0 ], listItems[ 1 ], 
                    ( int(listItems[ 2 ]), int(listItems[ 3 ]) ), listItems[ 4 ] )

                if ( iFileStatus == I_TRUE ):
                    # Read related data file
                    iFileStatus = self.__iSetStationData( listItems[ 1 ] )

                    if ( iFileStatus == I_TRUE ):
                        iStationCount = iStationCount + 1
                    else:
                        print 'Error reading data file for', listItems[ 0 ], '.'

                strFileLine = fileStationData.readline()
                iLineSize = len( strFileLine )

            # end of reading loop
            fileStationData.close()
            print 'Added', iStationCount, 'station(s).'

        # else, nothing

        return iStatus


    def hashGetStationNames( self ):
        hashStations = {}
        listKeys = self.__hashStationToDataObject.keys()

        for strItem in listKeys:
            hashStations[ strItem ] = self.__hashStationToDataObject[
                strItem ].strGetFullName()
        
        return hashStations


    def hashGetStationLocations( self ):
        hashStations = {}
        listKeys = self.__hashStationToDataObject.keys()

        for strItem in listKeys:
            hashStations[ strItem ] = self.__hashStationToDataObject[
                strItem ].tupGetScreenLocation()
        
        return hashStations        

    
    def __iSetStationData( self, strCodeName ):
        iStatus = I_FALSE
        iLineSize = 0
        strFileLine = ''
        fileStationData = None
        listDataItems = []
        stationCurrent = None
        hashCurrentData = {}
        iDateCode = 0

        if ( self.__hashStationToFilename.has_key( strCodeName ) == I_TRUE ):
            stationCurrent = self.__hashStationToDataObject[ strCodeName ]
            fileStationData = open( self.__hashStationToFilename[ strCodeName ], 'r' )

            # Skip first 2 lines because of labels
            for i in range( 0, 3 ):
                strFileLine = fileStationData.readline()
                
            iLineSize = len( strFileLine )

            while ( iLineSize > 0 ):
                listDataItems = strFileLine.split( ',' )

                # Create year integer (YYYYMMDD)
                iYear = int(listDataItems[ StationSet.I_YEAR ])
                iMonth= int(listDataItems[ StationSet.I_MONTH ])
                iDay = int(listDataItems[ StationSet.I_DAY ])
               
                iDateCode = iYear * 10000
                iDateCode = iDateCode + iMonth * 100
                iDateCode = iDateCode + iDay

                if ( iDateCode > StationSet.__I_EARLIEST_YEAR_CODE ):

                    # Get average wind speed
                    if ( listDataItems[ StationSet.I_ERR_AVG_WIND_SPEED ] ==
                         StationSet.CHAR_MISSING_DATA ):

                        hashCurrentData[ Station.I_AVG_WIND_SPEED ] = float(
                            Station.I_MISSING_DATA )
                        
                    else:
                        hashCurrentData[ Station.I_AVG_WIND_SPEED ] = float(
                            listDataItems[ StationSet.I_AVG_WIND_SPEED ] )


                    # Get average wind direction
                    if ( listDataItems[ StationSet.I_ERR_AVG_WIND_DIRECTION ] ==
                         StationSet.CHAR_MISSING_DATA ):

                        hashCurrentData[ Station.I_AVG_WIND_DIRECTION ] = float(
                            Station.I_MISSING_DATA )
                        
                    else:
                        hashCurrentData[ Station.I_AVG_WIND_DIRECTION ] = float(
                            listDataItems[ StationSet.I_AVG_WIND_DIRECTION ] )


                    # Get average air temperature
                    if ( listDataItems[ StationSet.I_ERR_AVG_AIR_TEMP ] ==
                         StationSet.CHAR_MISSING_DATA ):

                        hashCurrentData[ Station.I_AVG_AIR_TEMP ] = float(
                            Station.I_MISSING_DATA )
                        
                    else:
                        hashCurrentData[ Station.I_AVG_AIR_TEMP ] = float(
                            listDataItems[ StationSet.I_AVG_AIR_TEMP ] )


                    # Get average relative humidity
                    if ( listDataItems[ StationSet.I_ERR_AVG_HUMIDITY ] ==
                         StationSet.CHAR_MISSING_DATA ):

                        hashCurrentData[ Station.I_AVG_HUMIDITY ] = float(
                            Station.I_MISSING_DATA )
                        
                    else:
                        hashCurrentData[ Station.I_AVG_HUMIDITY ] = float(
                            listDataItems[ StationSet.I_AVG_HUMIDITY ] )


                    # Get total precipitation
                    if ( listDataItems[ StationSet.I_ERR_TOTAL_PRECIP ] ==
                         StationSet.CHAR_MISSING_DATA ):

                        hashCurrentData[ Station.I_TOTAL_PRECIP ] = float(
                            Station.I_MISSING_DATA )
                        
                    else:
                        hashCurrentData[ Station.I_TOTAL_PRECIP ] = float(
                            listDataItems[ StationSet.I_TOTAL_PRECIP ] )

                        
                    # Get average 4" soil temperature
                    if ( listDataItems[ StationSet.I_ERR_AVG_4_IN_SOIL_TEMP ] ==
                         StationSet.CHAR_MISSING_DATA ):

                        hashCurrentData[ Station.I_AVG_4_IN_SOIL_TEMP ] = float(
                            Station.I_MISSING_DATA )
                        
                    else:
                        hashCurrentData[ Station.I_AVG_4_IN_SOIL_TEMP ] = float(
                            listDataItems[ StationSet.I_AVG_4_IN_SOIL_TEMP ] )

                    # Place data in "database"
                    stationCurrent.zAddDataRow( iDateCode, hashCurrentData )

                # else, do not process data if less than earliest year
                
                # Get next data line
                strFileLine = fileStationData.readline()
                iLineSize = len( strFileLine )
                
            # End of data reading loop
            iStatus = I_TRUE
            fileStationData.close()

        # End of valid key for processing data

        else:
            print 'Station code ', strCodeName, ' does not exist.'

        return iStatus


    def hashGetDataTypeForDate( self, intDate, intDataType ):
        print "Hash get data type ", intDataType, " for date ", intDate
        hashDateData = {}
        stationCurrent = None
        listStations = []

        listStations = self.__hashStationToDataObject.keys()

        for aStation in listStations:
            stationCurrent = self.__hashStationToDataObject[ aStation ]
            hashDateData[
                aStation ] = stationCurrent.numGetDataItem( intDate, intDataType )
        
        return hashDateData


    def viewAllData( self ):
        for date in range( 20031101, 20031131 ):
            print date
            print "==============="

            for type in range( 20, 80, 10 ):
                print type
                print "-------"
                print self.hashGetDataTypeForDate( date, type )
                #time.sleep( 1 )
            print


    def __iAddNewStation( self, strFullName, strCodeName, tupLocation, strDataFilename ):
        iStatus = I_FALSE

        strFilePath = os.path.normpath( strDataFilename )
        
        if ( os.path.exists( strFilePath ) == I_TRUE ):
            station = Station()
            station.zSetCodeName( strCodeName )
            station.zSetFullName( strFullName )
            station.zSetScreenLocation( tupLocation )

            self.__hashStationToFilename[ strCodeName ] = strDataFilename
            self.__hashStationToDataObject[ strCodeName ] = station
            print 'Added ', strFullName, ' (', strCodeName, ') at ', tupLocation, ' tied to filename ', strDataFilename
                
            iStatus = I_TRUE

        else:
            print 'File ' +  strDataFilename + ' does not exist.'

        return iStatus
    
        
#------------------------- END OF STATIONSET CLASS ----------------------------------------

    

#------------------------- VTK CLASS ----------------------------------------
class VTKRender:
    global I_TRUE, I_FALSE


    # Assumption...clockwise direction for wind measurement
    HASH_WIND_DIRECTION = { 0.0:'S', 22.5:'SSE', 45.0:'SE', 67.5:'ESE',
                            90.0:'E', 115.5:'ENE', 135.0:'NE', 157.5:'NNE',
                            180.0:'N', 202.5:'NNW', 225.0:'NW', 247.5:'WNW',
                            270.0:'W', 292.5:'WSW', 315.0:'SW', 337.5:'SSW' }

    # Misc
    B_USE_RANGE = I_TRUE
    B_CALCULATED_RANGE = I_FALSE

    # Visualization types
    I_NO_VIZ = 0
    I_HUMIDITY_VIZ = 100
    I_PRECIPITATION_VIZ = 150
    I_WIND_VIZ = 200
    I_AIR_TEMP_VIZ = 250
    I_SOIL_TEMP_VIZ = 300

    # Map properties
    I_MAP_WIDTH = 338
    I_MAP_HEIGHT = 599
    I_MAP_POINTS = I_MAP_WIDTH * I_MAP_HEIGHT
    I_MAP_HALF_WIDTH = I_MAP_WIDTH / 2

    I_MAP_TYPE_BOUNDARY = 1000
    I_MAP_TYPE_COUNTY = 1100
    I_MAP_TYPE_ROADS = 1200

    # Miscellaneous
    I_DATE_UNCHANGED = 99999999
    F_MAX_FLOAT = 99999999.99
    F_MIN_FLOAT = -99999999.99
    
    
    def __init__(self, frame ):
        self.__frame = frame
        self.__zSetupDataStructures()
        self.__zSetupVizPipelines()
        

    def __zSetupDataStructures( self ):
        self.__iVizType = VTKRender.I_AIR_TEMP_VIZ
    
        # Station information
        self.__vpStationPoints = vtk.vtkPoints()
        self.__hashStationLocations = {}
        self.__hashStationNames = {}        

        self.__hashCurrentColormapStationData = {}
        self.__iColormapStationDataMin = 0
        self.__iColormapStationDataMax = 0
        
        self.__hashCurrentGlyphStationData = {}
        self.__iGlyphStationDataMin = 0
        self.__iGlyphStationDataMax = 0

        self.vfaGlyphScalarValues = vtk.vtkFloatArray()
        self.vfaColormapScalarValues = vtk.vtkFloatArray()

        # City map information
        self.__listvvtStationText = []
        self.__listvpdmStationTextMapper = []
        self.__listvaStationTextActor = []
        
        self.__iYear = 2003
        self.__iMonth = 11
        self.__iDay = 11
        self.__iDateCode = 20031101

        self.__hashMapNamesAndFiles = {
            VTKRender.I_MAP_TYPE_COUNTY:'./maps/counties.jpg',
            VTKRender.I_MAP_TYPE_BOUNDARY:'./maps/border.jpg',
            VTKRender.I_MAP_TYPE_ROADS:'./maps/roads.jpg' }

        self.__iMapHeight = 0
        self.__iCityLabelHeight = self.__iMapHeight + 1


    def __zSetupVizPipelines( self ):
        ################### MAP PIPELINE #############################
        # Map types
        ###### County
        ###### Adapted from: http://www.censusfinder.com/mapil.htm
        self.vjrMapCountyFile = vtk.vtkJPEGReader()
        self.vjrMapCountyFile.SetFileName( os.path.normpath(
            self.__hashMapNamesAndFiles[ VTKRender.I_MAP_TYPE_COUNTY ] ) )

        ###### State border
        ###### Adapted from: http://www.sws.uiuc.edu/warm/icnstationmap.asp
        self.vjrMapStateBorderFile = vtk.vtkJPEGReader()
        self.vjrMapStateBorderFile.SetFileName( os.path.normpath(
            self.__hashMapNamesAndFiles[ VTKRender.I_MAP_TYPE_BOUNDARY ] ) )

        ###### Roads
        ###### Adapted from: http://www.mrcusa.com/page4.htm
        self.vjrMapRoadsFile = vtk.vtkJPEGReader()
        self.vjrMapRoadsFile.SetFileName( os.path.normpath(
            self.__hashMapNamesAndFiles[ VTKRender.I_MAP_TYPE_ROADS ] ) )        

        self.vdsmMapImage = vtk.vtkDataSetMapper()
        self.vdsmMapImage.SetInput( self.vjrMapCountyFile.GetOutput() )
        self.vdsmMapImage.SetScalarRange(0, 255)
        self.vdsmMapImage.ImmediateModeRenderingOff()

        self.vaMapActor = vtk.vtkActor()
        self.vaMapActor.SetMapper( self.vdsmMapImage )
        self.vaMapActor.GetProperty().SetOpacity( 1 )
        self.vaMapActor.SetPosition( 0,0,self.__iMapHeight )


        ############## CITY LABELS PIPELINE ##########################
        # Taken care of later in self.zSetupStations()
                
        """
        ################ DATA PREPARATION PIPELINE ####################
        # self.__vpStationPoints has points added in self.zSetupStations()
        # self.vpdStationViz.GetPointData().SetScalars( scalars )
        # will be done when needed for data for each viz
        
        self.vpdColormapStationViz = vtk.vtkPolyData()
        self.vpdStationViz.SetPoints( self.__vpStationPoints )
        """


        ############## COLORMAP PIPELINE ############################
        # Polydata
        self.vpdColormapStationViz = vtk.vtkPolyData()
        self.vpdColormapStationViz.SetPoints( self.__vpStationPoints )
        self.vpdColormapStationViz.GetPointData().SetScalars(
            self.vfaColormapScalarValues )
        
        # Interpolation
        self.vsmColormapInterpolation = vtk.vtkShepardMethod()
        self.vsmColormapInterpolation.SetInput( self.vpdColormapStationViz )
        self.vsmColormapInterpolation.SetMaximumDistance( 1 )
        self.vsmColormapInterpolation.SetModelBounds( 0,VTKRender.I_MAP_WIDTH-1,
            0,VTKRender.I_MAP_HEIGHT-1, 0,1 )
        self.vsmColormapInterpolation.SetSampleDimensions( VTKRender.I_MAP_WIDTH / 1.5,
            VTKRender.I_MAP_WIDTH / 1.5, 2 )
        self.vsmColormapInterpolation.Update()

        # Lookup table is adjusted according to each visualization
        self.vltColormapLookupTable = vtk.vtkLookupTable()

        # Mapper
        self.vdsmColormapMapper = vtk.vtkDataSetMapper()
        self.vdsmColormapMapper.SetInput( self.vsmColormapInterpolation.GetOutput() )
        self.vdsmColormapMapper.SetLookupTable( self.vltColormapLookupTable )

        # Actor
        self.vaColormapActor = vtk.vtkActor()
        self.vaColormapActor.SetMapper( self.vdsmColormapMapper )


        ################### GLYPH PIPELINE ##########################
        # Multiple shape sources for glyphs
        
        # Cubes for "bars" in elevation map in precipitation
        self.vcsGlyphCubeSource = vtk.vtkCubeSource()
        self.vcsGlyphCubeSource.SetBounds( 0,1, 0,1, 0,5)

        # Spheres for "clouds" in humidity data and wind speed
        self.vcsGlyphSphereSource = vtk.vtkSphereSource()
        self.vcsGlyphSphereSource.SetCenter( 0,0,0 )
        self.vcsGlyphSphereSource.SetRadius( 2 )
        self.vcsGlyphSphereSource.SetPhiResolution( 5 )
        self.vcsGlyphSphereSource.SetStartPhi( -90 )

        # Polydata
        self.vpdGlyphStationViz = vtk.vtkPolyData()
        self.vpdGlyphStationViz.SetPoints( self.__vpStationPoints )
        self.vpdGlyphStationViz.GetPointData().SetScalars(
            self.vfaGlyphScalarValues )

        # Interpolation
        self.vsmGlyphInterpolation = vtk.vtkShepardMethod()
        self.vsmGlyphInterpolation.SetInput( self.vpdGlyphStationViz )
        self.vsmGlyphInterpolation.SetMaximumDistance( 1 )
        self.vsmGlyphInterpolation.SetModelBounds( 0,VTKRender.I_MAP_WIDTH-1,
            0,VTKRender.I_MAP_HEIGHT-1, 0,1 )
        self.vsmGlyphInterpolation.SetSampleDimensions( VTKRender.I_MAP_WIDTH / 1.5,
            VTKRender.I_MAP_WIDTH / 1.5, 2 )
        self.vsmGlyphInterpolation.Update()

        # Mask
        self.vmpGlyphPointMask = vtk.vtkMaskPoints()
        self.vmpGlyphPointMask.SetInput( self.vsmGlyphInterpolation.GetOutput() )
        self.vmpGlyphPointMask.SetOnRatio( 40 )        

        # Lookup Table is adjusted according to each visualization
        self.vltGlyphColorLookupTable = vtk.vtkLookupTable()

        # Glyph 3D
        # SetSource: Set according to viz; either self.vcsGlyphCubeSource
        #            or self.vcsGlyphSphereSource
        # SetScaleFactor: Set according to viz
        self.vg3dGlyph = vtk.vtkGlyph3D()
        self.vg3dGlyph.SetInput( self.vmpGlyphPointMask.GetOutput() )
        self.vg3dGlyph.SetScaleModeToScaleByScalar()

        # Polydata mapper
        self.vpdmGlyphMapper = vtk.vtkPolyDataMapper()
        self.vpdmGlyphMapper.SetInput( self.vg3dGlyph.GetOutput() )
        self.vpdmGlyphMapper.SetLookupTable( self.vltGlyphColorLookupTable )

        # Actor
        self.vaGlyphGroupActor = vtk.vtkActor()
        self.vaGlyphGroupActor.SetMapper( self.vpdmGlyphMapper )
        

        ################ RENDERING PIPELINE ##########################
        # Renderer
        self.vrRenderer = vtk.vtkRenderer()
        self.vrRenderer.SetBackground( 0.329412, 0.34902, 0.427451 )

        ###### Add map actor to renderer'
        self.vrRenderer.AddActor( self.vaMapActor )
       
        # Render window
        self.vrwRenderWindow = vtk.vtkRenderWindow()
        self.vrwRenderWindow.AddRenderer( self.vrRenderer )
        self.vrwRenderWindow.SetSize( VTKRender.I_MAP_WIDTH,
                                        VTKRender.I_MAP_HEIGHT )

        # Render interactor
        self.vrwiWindowControl = vtkTkRenderWindowInteractor(
            self.__frame, rw=self.vrwRenderWindow,
            width=VTKRender.I_MAP_WIDTH + 10,
            height=VTKRender.I_MAP_HEIGHT + 10 )
        self.vrwiWindowControl.SetRenderWindow( self.vrwRenderWindow )
        self.vrwiWindowControl.pack( side='top', fill='both', expand=1 )


        #################### ACTIVATE RENDERING ########################
        self.vrwiWindowControl.Initialize()
        self.vrwiWindowControl.Start()
        self.vrwRenderWindow.Render()      
                

    # Store station screen locations
    def zSetupStations( self, hashStationLocations, hashStationNames ):
        # Convenience constants
        X = 0
        Y = 1
        
        tupLocation = ()
        
        self.__hashStationLocations = hashStationLocations
        self.__hashStationNames = hashStationNames
        

        ################# CITY LABELS PIPELINE ###########################
        self.__vpStationPoints = vtk.vtkPoints()
        iIndex = 0
        listStations= []
        strStationLocation = ''
        
        # To prevent any potential for data "misalignment" (i.e. being assigned
        # to the wrong station), sort keys first.
        listStations = self.__hashStationLocations.keys()
        listStations.sort()
        
        for aStation in listStations:
            print "list index =",iIndex
            #time.sleep( 1)
            # Vector text
            self.__listvvtStationText.append( vtk.vtkVectorText() )
            strStationLocation = 'x\n' + self.__hashStationNames[ aStation ]
            self.__listvvtStationText[ iIndex ].SetText( strStationLocation )
                                                         
            # Polydata mapper
            self.__listvpdmStationTextMapper.append( vtk.vtkPolyDataMapper() )
            self.__listvpdmStationTextMapper[ iIndex ].SetInput(
                self.__listvvtStationText[ iIndex ].GetOutput() )

            # Follower (sort of acts as an actor)
            self.__listvaStationTextActor.append( vtk.vtkFollower() )
            self.__listvaStationTextActor[ iIndex ].SetMapper(
                self.__listvpdmStationTextMapper[ iIndex ] )                
            self.__listvaStationTextActor[ iIndex ].SetScale(10,10,1)

            # Add station location in __vpStationPoints
            tupLocation = self.__hashStationLocations[ aStation ]
            self.__vpStationPoints.InsertPoint( iIndex,
                tupLocation[ X ], tupLocation[ Y ], 0.0 )
            self.__listvaStationTextActor[ iIndex ].AddPosition(            
                tupLocation[ X ], tupLocation[ Y ], 0.0 )

            # Update float arrays
            self.vfaGlyphScalarValues.InsertValue( iIndex, 0.0 )
            self.vfaColormapScalarValues.InsertValue( iIndex, 0.0 )
            
            # Add actor to renderer
            #self.vrRenderer.AddActor( self.__listvaStationTextActor[ iIndex ] )

            # Increment index
            iIndex = iIndex + 1

        self.vpdColormapStationViz.SetPoints( self.__vpStationPoints )
        self.vpdColormapStationViz.GetPointData().SetScalars(
            self.vfaColormapScalarValues )

        self.vpdGlyphStationViz.SetPoints( self.__vpStationPoints )
        self.vpdGlyphStationViz.GetPointData().SetScalars(
            self.vfaGlyphScalarValues )
        
        self.vsmColormapInterpolation.Update()
        self.vsmGlyphInterpolation.Update()


    # Set the map type
    def zSetMapType( self, iMapID ):
        print "mapid = ", iMapID
        if ( iMapID == VTKRender.I_MAP_TYPE_BOUNDARY ):
            self.vdsmMapImage.SetInput( self.vjrMapStateBorderFile.GetOutput() )

        elif ( iMapID == VTKRender.I_MAP_TYPE_COUNTY ):
            self.vdsmMapImage.SetInput( self.vjrMapCountyFile.GetOutput() )

        elif ( iMapID == VTKRender.I_MAP_TYPE_ROADS ):
            self.vdsmMapImage.SetInput( self.vjrMapRoadsFile.GetOutput() )

        else:
            pass  # do nothing

        # Update render window
        self.vrwRenderWindow.Render()


    # Set the viz based on date and data type
    def zSetDateData( self, iDateCode, iDataType,
                      hashColormapStationData, hashGlyphStationData ):

        print "SetDateData..."
        print "date =", iDateCode
        print "type = ", iDataType
        print "colormap data = ", hashColormapStationData
        print "Glyph data = ", hashGlyphStationData
        ###### TO DO
        # 0. Map Station type to viz type
        # 1. Extract date
        # 2. Get data from StationData
        # 3. Update city label information
        # 4. Update viz objects
        # 5. Update render window

        # 0. Map station type to viz type
        self.__iLastVizType = self.__iVizType

        if ( iDataType == Station.I_AVG_AIR_TEMP ):
            self.__iVizType = VTKRender.I_AIR_TEMP_VIZ

        elif ( iDataType == Station.I_AVG_4_IN_SOIL_TEMP ):
            self.__iVizType = VTKRender.I_AIR_TEMP_VIZ

        elif ( iDataType == Station.I_AVG_HUMIDITY ):
            self.__iVizType = VTKRender.I_HUMIDITY_VIZ

        elif ( iDataType == Station.I_TOTAL_PRECIP ):
            self.__iVizType = VTKRender.I_PRECIPITATION_VIZ

        elif ( iDataType == Station.I_AVG_WIND_SPEED ):
            self.__iVizType = VTKRender.I_WIND_VIZ

        elif ( iDataType == Station.I_AVG_WIND_SPEED ):
            self.__iVizType = VTKRender.I_WIND_VIZ

        else:
            self.__iVizType = VTKRender.I_NO_VIZ


        # 1. Extract date
        if ( iDateCode != VTKRender.I_DATE_UNCHANGED ):
            self.__iDateCode = iDateCode
            self.__iYear = self.__iDateCode / 10000
            iWorkingDate = self.__iDateCode - self.__iYear * 10000

            self.__iMonth = iWorkingDate / 100
            iWorkingDate = iWorkingDate - self.__iMonth * 100

            self.__iDay = iWorkingDate

        else:
            pass  # use same date as before


        # 2. Get data from StationData
        # hashColormapStationData: Used by air temp, soil temp, wind viz
        # hashGlyphStationData: Used by humidity, precip, wind
        # wind is a combo because it uses color for direction & glyph for speed
        listStations = []
        listTempValueArray = []
        iIndex = 0
        fValue = 0.0

        listStations = hashColormapStationData.keys()
        listStations.sort()
        iNumberOfStations = len( listStations )



    
        print "STEP 2"
        # Colormap case
        if ( ( self.__iVizType == VTKRender.I_AIR_TEMP_VIZ )  or
             ( self.__iVizType == VTKRender.I_SOIL_TEMP_VIZ ) ):

            self.vpdColormapStationViz.GetPointData().SetScalars(
                self.__vfaNormalizeValues( hashColormapStationData,
                VTKRender.B_CALCULATED_RANGE, 0, 0 ) )

            
        elif ( self.__iVizType == VTKRender.I_WIND_VIZ ):
            self.vpdColormapStationViz.GetPointData().SetScalars(
                self.__vfaNormalizeValues( hashColormapStationData,
                VTKRender.B_USE_RANGE, 0, 360 ) ) #direction
            

        print "GLYPH..."
        # Glyph case
        if ( self.__iVizType == VTKRender.I_HUMIDITY_VIZ ):
            self.vpdGlyphStationViz.GetPointData().SetScalars(
                self.__vfaNormalizeValues( hashGlyphStationData,
                VTKRender.B_CALCULATED_RANGE, 0, 0 ) )

        elif ( self.__iVizType == VTKRender.I_PRECIPITATION_VIZ ):
            self.vpdGlyphStationViz.GetPointData().SetScalars(
                self.__vfaNormalizeValues( hashGlyphStationData,
                VTKRender.B_CALCULATED_RANGE, 0, 0 ) )
                
        elif ( self.__iVizType == VTKRender.I_WIND_VIZ ):
            self.vpdGlyphStationViz.GetPointData().SetScalars(
                self.__vfaNormalizeValues( hashGlyphStationData,
                VTKRender.B_CALCULATED_RANGE, 0, 80 ) )  #speed

        # 3. Update city label information (value,location text)
        iIndex = 0
        strTemp = ''
        "STEP 3..."
        if ( ( self.__iVizType == VTKRender.I_AIR_TEMP_VIZ )  or
             ( self.__iVizType == VTKRender.I_SOIL_TEMP_VIZ ) ):

            for station in listStations:
                self.__listvvtStationText[ iIndex ].SetText(
                    str( hashColormapStationData[
                    station ] ) + 'F \n' + str( self.__hashStationNames[ station ] ) )
    
        elif ( self.__iVizType == VTKRender.I_HUMIDITY_VIZ):
            for station in listStations:
                self.__listvvtStationText[ iIndex ].SetText(
                    str( hashGlyphStationData[
                    station ] ) + '% \n' + str( self.__hashStationNames[ station ] ) )


        elif ( self.__iVizType == VTKRender.I_PRECIPITATION_VIZ ):
            for station in listStations:
                self.__listvvtStationText[ iIndex ].SetText(
                    str( hashGlyphStationData[
                    station ] ) + '\" \n' + str( self.__hashStationNames[ station ] ) )
                

        elif ( self.__iVizType == VTKRender.I_WIND_VIZ ):
            for station in listStations:

                # Direction...get text equivalent of degree measurement
                iDirection = hashColormapStationData[ station ]
                strDirection = ''

                if ( iDirection >= 337.5 ): strDirection = 'SSW'
                elif ( iDirection >= 315.0 ): strDirection = 'SW'
                elif ( iDirection >= 292.5 ): strDirection = 'WSW'
                elif ( iDirection >= 270.0 ): strDirection = 'W'
                elif ( iDirection >= 247.5 ): strDirection = 'WNW'
                elif ( iDirection >= 225.0 ): strDirection = 'NW'
                elif ( iDirection >= 202.5 ): strDirection = 'NNW'
                elif ( iDirection >= 180.0 ): strDirection = 'N'
                elif ( iDirection >= 157.5 ): strDirection = 'NNE'
                elif ( iDirection >= 135.0 ): strDirection = 'NE'
                elif ( iDirection >= 115.5 ): strDirection = 'ENE'
                elif ( iDirection >= 90.0 ): strDirection = 'E'
                elif ( iDirection >= 67.5 ): strDirection = 'ESE'
                elif ( iDirection >= 45.0 ): strDirection = 'SE'
                elif ( iDirection >= 22.5 ): strDirection = 'SSE'
                elif ( iDirection >= 0.0 ): strDirection = 'S'
                else: strDirection = '?'

                # Speed
                iSpeed = hashGlyphStationData[ station ]
                
                self.__listvvtStationText[ iIndex ].SetText(
                    str( strDirection ) + ' @ ' + str( iSpeed ) + '\n'
                    + str( self.__hashStationNames[ station ] ) )

        else:
            pass  # do nothing
        
        
        print "STEP 4"
        # 4. Update viz objects
        ###### Remove both actors just to be safe from potential weirdness
        bValid = I_TRUE
        self.vrRenderer.RemoveActor( self.vaColormapActor )
        self.vrRenderer.RemoveActor( self.vaGlyphGroupActor )

        #self.vltColormapLookupTable.SetRange(
        #    self.__iColormapStationDataMin, self.__iColormapStationDataMax )
        #self.vltColormapLookupTable.Build()

        #self.vpdmGlyphMapper.SetScalarRange(
        #    self.__fGlyphStationDataMin, self.__fGlyphStationDataMax )
        #>>>self.vpdmGlyphMapper.SetScalarRange(
        #    self.__fGlyphStationDataMin, self.__fGlyphStationDataMax )
        #self.vltGlyphColorLookupTable.SetRange(
        #    self.__iGlyphStationDataMin, self.__iGlyphStationDataMax )
        self.vltGlyphColorLookupTable.Build()

        if ( ( self.__iVizType == VTKRender.I_AIR_TEMP_VIZ )  or
             ( self.__iVizType == VTKRender.I_SOIL_TEMP_VIZ ) ):
            # Colormap assignment
            self.vdsmColormapMapper.SetScalarRange(
                0,1 )

            # Fix lookup table
            self.vltColormapLookupTable.SetValueRange( 0.5, 1.0 )
            self.vltColormapLookupTable.SetHueRange( 0.833, 0.0 )
            self.vltColormapLookupTable.SetSaturationRange( 0.5, 1.0 )
            self.vltColormapLookupTable.SetAlphaRange( 1.0, 1.0 )
            self.vltColormapLookupTable.Build()

            # Adjust map properties
            self.__iMapHeight = 2
            self.vaMapActor.GetProperty().SetOpacity( 0.5 )
            self.__iCityLabelHeight = self.__iMapHeight + 1
            #self.vaMapActor.Render( self.vrRenderer, self.vdsmMapImage)
            self.vdsmMapImage.Update()
            
            # Add actor
            self.vrRenderer.AddActor( self.vaColormapActor )

                    
        elif ( self.__iVizType == VTKRender.I_HUMIDITY_VIZ ):
            # Glyph assignment
            self.vpdmGlyphMapper.SetScalarRange(
                0,1 )            

            # Fix lookup table
            self.vltGlyphColorLookupTable.SetValueRange( 0.1, 1.0 )
            self.vltGlyphColorLookupTable.SetHueRange( 0.0, 0.0 )            
            self.vltGlyphColorLookupTable.SetSaturationRange( 0.0, 0.0 )
            self.vltGlyphColorLookupTable.SetAlphaRange( 1.0, 0.3 )
            self.vltGlyphColorLookupTable.Build()

            # Adjust map properties
            self.__iMapHeight = 15
            self.vaMapActor.GetProperty().SetOpacity( 0.4 )
            self.__iCityLabelHeight = self.__iMapHeight + 1
            #self.vaMapActor.Render( self.vrRenderer, self.vdsmMapImage )
            self.vdsmMapImage.Update()
            
            # Adjust glyph properties
            self.vcsGlyphSphereSource.SetRadius( 2 )
            self.vg3dGlyph.SetScaleFactor( 6 )
            self.vg3dGlyph.SetSource( self.vcsGlyphSphereSource.GetOutput() )            

            # Add actor
            self.vrRenderer.AddActor( self.vaGlyphGroupActor )

        
        elif ( self.__iVizType == VTKRender.I_PRECIPITATION_VIZ ):
            # Glyph assignment
            self.vpdmGlyphMapper.SetScalarRange(
                0,1 )            

            # Fix lookup table
            self.vltGlyphColorLookupTable.SetValueRange( 1.0, 1.0 )
            self.vltGlyphColorLookupTable.SetHueRange( 0.833, 0.333 )            
            self.vltGlyphColorLookupTable.SetSaturationRange( 1.0, 1.0 )
            #self.vltGlyphColorLookupTable.SetAlphaRange( 1.0, 0.1 ) #1.0, 0.6
            self.vltGlyphColorLookupTable.Build()

            # Adjust map properties
            self.__iMapHeight = 0
            self.vaMapActor.GetProperty().SetOpacity( 0.45 )
            self.__iCityLabelHeight = self.__iMapHeight + 81
            #self.vaMapActor.Render(self.vrRenderer, self.vdsmMapImage )
            self.vdsmMapImage.Update()            

            # Adjust glyph properties
            self.vg3dGlyph.SetScaleFactor( 15 )
            self.vg3dGlyph.SetSource( self.vcsGlyphCubeSource.GetOutput() )            

            # Add actor
            self.vaGlyphGroupActor.GetProperty().SetOpacity( 0.2 )
            self.vrRenderer.AddActor( self.vaGlyphGroupActor )

            
        elif ( self.__iVizType == VTKRender.I_WIND_VIZ ):
            # Colormap assignment
            self.vdsmColormapMapper.SetScalarRange(
                0,1 )
            
            # Fix colormap lookup table (wind direction)
            self.vltColormapLookupTable.SetValueRange( 1.0, 1.0 )
            self.vltColormapLookupTable.SetHueRange( 0.0, 1.0 )            
            self.vltColormapLookupTable.SetSaturationRange( 1.0, 1.0 )
            self.vltColormapLookupTable.SetAlphaRange( 1.0, 0.3 )
            self.vltColormapLookupTable.Build()

            # Glyph assignment
            self.vpdmGlyphMapper.SetScalarRange(
                0,1 )            

            # Fix glyph color lookup table (wind speed)
            self.vltGlyphColorLookupTable.SetValueRange( 0.0, 1.0 )
            self.vltGlyphColorLookupTable.SetHueRange( 0.0, 0.0 )            
            self.vltGlyphColorLookupTable.SetSaturationRange( 0.0, 0.0 )
            self.vltGlyphColorLookupTable.SetAlphaRange( 0.7, 0.7 )            
            self.vltGlyphColorLookupTable.Build()            

            # Adjust glyph properties
            self.vcsGlyphSphereSource.SetRadius( 1 )
            self.vg3dGlyph.SetScaleFactor( 10 )
            self.vg3dGlyph.SetSource( self.vcsGlyphSphereSource.GetOutput() )            

            # Adjust map properties
            self.__iMapHeight = 15
            self.vaMapActor.GetProperty().SetOpacity( 0.45 )
            self.__iCityLabelHeight = self.__iMapHeight + 1
            #self.vaMapActor.Render(self.vrRenderer, self.vdsmMapImage )
            self.vdsmMapImage.Update()

            # Add actor
            self.vrRenderer.AddActor( self.vaColormapActor )
            self.vrRenderer.AddActor( self.vaGlyphGroupActor )

        else:
            bValid = I_FALSE
            pass   # do nothing


        ###### Update map height and city label heights
        if ( bValid == I_TRUE ):
            print "City label height=", self.__iCityLabelHeight
            # Convenience constants
            X = 0
            Y = 1

            listStations = self.__hashStationLocations.keys()
            listStations.sort()
            iIndex = 0
            tupLocation = ()
            
            self.vaMapActor.SetPosition( 0,0, self.__iMapHeight )
            
            for station in listStations:
                # Get station location
                tupLocation = self.__hashStationLocations[ station ]
                iXLocation = tupLocation[ X ]
                iYLocation = tupLocation[ Y ]

                # Update point data
                self.__vpStationPoints.SetPoint( iIndex, tupLocation[ X ],
                    tupLocation[ Y ], self.__iCityLabelHeight )
                self.__listvaStationTextActor[ iIndex ].SetPosition(            
                    tupLocation[ X ], tupLocation[ Y ], self.__iCityLabelHeight )
                self.vrRenderer.RemoveActor( self.__listvaStationTextActor[ iIndex ] )
                self.vrRenderer.AddActor( self.__listvaStationTextActor[ iIndex ] )
                
            # end of loop
            
        else:
            pass   # do nothing


        # 5. Update render window
        self.vrwRenderWindow.Render()        
        self.vsmColormapInterpolation.Update()
        self.vsmGlyphInterpolation.Update()



    def __vfaNormalizeValues( self, hashValues, bUseRange, fMin, fMax ):
        vfaReturnFloatArray = vtk.vtkFloatArray()
        fValueRange = 0.0
        listStations = hashValues.keys()
        listStations.sort()

        if ( bUseRange == VTKRender.B_CALCULATED_RANGE ):
            fMinValue = VTKRender.F_MAX_FLOAT
            fMaxValue = VTKRender.F_MIN_FLOAT

            # Find data range        
            for station in listStations:
                fValue = hashValues[ station ]

                if ( fValue < fMinValue ):
                    fMinValue = fValue

                elif ( fValue > fMaxValue ):
                    fMaxValue = fValue

        else:
            fMinValue = fMin
            fMaxValue = fMax
            
    
        # Normalize
        fValueRange = fMaxValue - fMinValue
        iIndex= 0
        
        for station in listStations:
            print 'index: ', iIndex
            fValue = ( hashValues[ station ] - fMinValue ) / fValueRange            
            print "Value = ", fValue
            vfaReturnFloatArray.InsertValue( iIndex, fValue )
            iIndex = iIndex + 1

        return vfaReturnFloatArray
            

    def destroy( self ):
        del self.vrRenderer
        del self.vrwRenderWindow
        del self.vrwiWindowControl
        
#------------------------- VTK CLASS ----------------------------------------



#-------------- DATE PICKER DIALOG -------------------------------------------
 
class DialogDatePicker( ModalDialog ):
    global I_TRUE, I_FALSE

    hashDaysInMonth = { 1:31, 2:28, 3:31, 4:30, 5:31, 6:30, 7:31, 8:31, 9:30,
                        10:31, 11:30, 12:31 }

    
    HASH_MONTH_NAME = { 1:'January', 2:'February', 3:'March', 4:'April',
                        5:'May', 6:'June', 7:'July', 8:'August', 9:'September',
                        10:'October', 11:'November', 12:'December' }    
    
    def __init__( self, topWindow, vtkWindow, labelStatus ):
        ModalDialog.__init__( self, topWindow, 'Date Picker', I_TRUE )
        self.__vtkWindow = vtkWindow
        self.__statusBar = labelStatus

        self.__hashCurrentColormapData = {}  # includes wind direction
        self.__hashCurrentGlyphData = {}

        self.__tkintFlag = Tkinter.IntVar()
        self.__stationSet = StationSet()

        # >>>>> Test station set
        hashStations = self.__stationSet.hashGetStationLocations()
        print "STATION INFORMATION"
        print hashStations

        #print "STATION DATA FOR EACH DATE"
        #print "=========================="
        #self.__stationSet.viewAllData()
        
        

        self.__strStatusText = ''
        
        self.__intStartYear = 0
        self.__intStartMonth = 0
        self.__intStartDay = 0
        self.__intEndYear = 0
        self.__intEndMonth = 0
        self.__intEndDay = 0

        self.__buildDialog()

        # Get data from StationSet to VTKRender
        self.__vtkWindow.zSetupStations(
            self.__stationSet.hashGetStationLocations(),
            self.__stationSet.hashGetStationNames() )

        # Set current viz type
        self.__intStartDateCode = 20031101
        self.__intEndDateCode = 0

        self.zSetCurrentVizType( Station.I_AVG_AIR_TEMP )
        


    def iGetStartDate( self ):
        return self.__intStartDateCode
      
        
    def iGetEndDate( self ):
        return self.__intEndDateCode


    def zSetCurrentVizType( self, iVizType ):                
        self.__iCurrentViz = iVizType

        if ( self.__iCurrentViz == Station.I_AVG_HUMIDITY ):
            #self.__strStatusText = 'Average Humidity for ' + self.__strStatusText
            self.__hashCurrentGlyphData = self.__stationSet.hashGetDataTypeForDate(
                self.__intStartDateCode, self.__iCurrentViz )
            print "Glyph humidity:",self.__hashCurrentGlyphData

        elif ( self.__iCurrentViz == Station.I_TOTAL_PRECIP ):
            #self.__strStatusText = 'Total Precipitation for ' + self.__strStatusText
            self.__hashCurrentGlyphData = self.__stationSet.hashGetDataTypeForDate(
                self.__intStartDateCode, self.__iCurrentViz )
            print "Glyph precip:",self.__hashCurrentGlyphData
                        
        elif ( self.__iCurrentViz == Station.I_AVG_AIR_TEMP):
            #self.__strStatusText = 'Average Air Temperature for ' + self.__strStatusText
            self.__hashCurrentColormapData = self.__stationSet.hashGetDataTypeForDate(
                self.__intStartDateCode, self.__iCurrentViz )
            
        elif ( self.__iCurrentViz == Station.I_AVG_4_IN_SOIL_TEMP ):
            #self.__strStatusText = 'Average 4" Soil Temperature for ' + self.__strStatusText
            self.__hashCurrentColormapData = self.__stationSet.hashGetDataTypeForDate(
                self.__intStartDateCode, self.__iCurrentViz )
            
        elif ( ( self.__iCurrentViz == Station.I_AVG_WIND_SPEED )  or
               ( self.__iCurrentViz == Station.I_AVG_WIND_DIRECTION ) ):
            #self.__strStatusText = 'Average Wind Speed and Direction for ' + self.__strStatusText
            self.__hashCurrentGlyphData = self.__stationSet.hashGetDataTypeForDate(
                self.__intStartDateCode, Station.I_AVG_WIND_SPEED )
            self.__hashCurrentColormapData = self.__stationSet.hashGetDataTypeForDate(
                self.__intStartDateCode, Station.I_AVG_WIND_DIRECTION )
            print "Glyph wind direction:",self.__hashCurrentGlyphData

        else:
            self.__iCurrentViz = VTKRender.I_NO_VIZ


        if ( self.__iCurrentViz != VTKRender.I_NO_VIZ ):
            self.__statusBar.config( text=self.__strStatusText )
            self.zUpdateViz()


    def zUpdateViz( self ):
        if ( ( self.__iCurrentViz == Station.I_AVG_HUMIDITY ) or
             ( self.__iCurrentViz == Station.I_TOTAL_PRECIP ) ):
            
            self.__hashCurrentGlyphData = self.__stationSet.hashGetDataTypeForDate(
                self.__intStartDateCode, self.__iCurrentViz )
            
        elif ( ( self.__iCurrentViz == Station.I_AVG_AIR_TEMP ) or
               ( self.__iCurrentViz == Station.I_AVG_4_IN_SOIL_TEMP ) ):
            
            self.__hashCurrentColormapData = self.__stationSet.hashGetDataTypeForDate(
                self.__intStartDateCode, self.__iCurrentViz )        
            
        elif ( ( self.__iCurrentViz == Station.I_AVG_WIND_SPEED ) or
               ( self.__iCurrentViz == Station.I_AVG_WIND_DIRECTION ) ):
            
            self.__hashCurrentGlyphData = self.__stationSet.hashGetDataTypeForDate(
                self.__intStartDateCode, Station.I_AVG_WIND_SPEED )
            
            self.__hashCurrentColormapData = self.__stationSet.hashGetDataTypeForDate(
                self.__intStartDateCode, Station.I_AVG_WIND_DIRECTION )
                
        else:
            self.__iCurrentViz = VTKRender.I_NO_VIZ


        if ( self.__iCurrentViz != VTKRender.I_NO_VIZ ):
            self.__statusBar.config( text=self.__strStatusText )
            self.__vtkWindow.zSetDateData( self.__intStartDateCode,
                self.__iCurrentViz, self.__hashCurrentColormapData,
                self.__hashCurrentGlyphData ) 

            
    def __buildDialog( self ):
        guiFactory = GUIFactory()
        
        gridSettings = { 'padx':5, 'pady':5, 'sticky':Tkinter.SW,
                         'row':0, 'rowspan':1, 'column':0, 'columnspan':1 }


        # Start date
        ###### Start label
        """labelStart = guiFactory.newLabel( self.dialog, 'Start',
                                          {'justify':Tkinter.CENTER} )
        gridSettings[ 'row' ] = 0
        gridSettings[ 'rowspan' ] = 1        
        gridSettings[ 'column' ] = 0
        gridSettings[ 'columnspan' ] = 3
        guiFactory.packItem( labelStart, gridSettings )
        """
        ###### Year slider
        labelStartYear = guiFactory.newLabel( self.dialog, 'Year',
                                        {'justify':Tkinter.LEFT} )
        gridSettings[ 'row' ] = 1
        gridSettings[ 'column' ] = 0
        gridSettings[ 'columnspan' ] = 1
        guiFactory.packItem( labelStartYear, gridSettings )

        self.__sliderStartYear = SliderWithValue( self.dialog, 2000, 2003 )
        self.__sliderStartYear.addSliderCommandBinding( self.__doUpdateStartSlider )
        self.__widgetStartYear = self.__sliderStartYear.getWidget()
        gridSettings[ 'column' ] = 1
        gridSettings[ 'columnspan' ] = 2
        guiFactory.packItem( self.__widgetStartYear, gridSettings )

        ###### Month slider
        labelStartMonth = guiFactory.newLabel( self.dialog, 'Month',
                                        {'justify':Tkinter.LEFT} )
        gridSettings[ 'row' ] = 2
        gridSettings[ 'column' ] = 0
        gridSettings[ 'columnspan' ] = 1
        guiFactory.packItem( labelStartMonth, gridSettings )

        self.__sliderStartMonth = SliderWithValue( self.dialog, 1, 12 )
        self.__sliderStartMonth.addSliderCommandBinding( self.__doUpdateStartSlider )
        self.__widgetStartMonth = self.__sliderStartMonth.getWidget()
        gridSettings[ 'column' ] = 1
        gridSettings[ 'columnspan' ] = 2
        guiFactory.packItem( self.__widgetStartMonth, gridSettings )

        ###### Day slider
        labelStartDay = guiFactory.newLabel( self.dialog, 'Day',
                                        {'justify':Tkinter.LEFT} )
        gridSettings[ 'row' ] = 3
        gridSettings[ 'column' ] = 0
        gridSettings[ 'columnspan' ] = 1
        guiFactory.packItem( labelStartDay, gridSettings )

        self.__sliderStartDay = SliderWithValue( self.dialog, 1, 31 )
        self.__sliderStartDay.addSliderCommandBinding( self.__doUpdateStartSlider )
        self.__widgetStartDay = self.__sliderStartDay.getWidget()
        gridSettings[ 'column' ] = 1
        gridSettings[ 'columnspan' ] = 2
        guiFactory.packItem( self.__widgetStartDay, gridSettings )

        ###### Default values
        self.__sliderStartMonth.setScaleValue( 11 )
        self.__sliderStartDay.setScaleValue( 1 )
        self.__sliderStartYear.setScaleValue( 2003 )


    def __doUpdateStartSlider( self, *unusedArgument ):
        self.__intStartYear = self.__sliderStartYear.getValue()
        self.__intStartMonth = self.__sliderStartMonth.getValue()
        self.__intStartDay = self.__sliderStartDay.getValue()

        self.__sliderStartYear.configureLabel( { 'text':self.__intStartYear } )
        self.__sliderStartMonth.configureLabel( { 'text':self.__intStartMonth } )
        self.__sliderStartDay.configureLabel( { 'text':self.__intStartDay } )
        
        self.__validateDate()
        self.__intStartDateCode = self.__intStartYear * 10000
        self.__intStartDateCode = self.__intStartDateCode + ( self.__intStartMonth * 100 )
        self.__intStartDateCode = self.__intStartDateCode + self.__intStartDay

        strMonth = DialogDatePicker.HASH_MONTH_NAME[ self.__intStartMonth ]
        self.__strStatusText = strMonth + ' ' + str(
            self.__intStartDay ) + ', ' + str( self.__intStartYear )
            
        #strDate = strMonth.join( ' ' ).join( str( self.__intStartDay ) ).join(
         #   ', ' ).join( str( self.__intStartYear ) )
        
        self.__statusBar.config( text=self.__strStatusText )
        
        # Update data in render window...
        self.__hashCurrentDateData = self.__stationSet.hashGetDataTypeForDate(
            self.__intStartDateCode, self.__iCurrentViz )
        self.zUpdateViz()
        #self.__vtkWindow.zUpdateRenderWindow()


    def __validateDate( self ):
        intMaxDaysThisMonth = DialogDatePicker.hashDaysInMonth[ self.__intStartMonth ]

        # Does not take into account year 2000 is a leap year just because
        # it is divisible by 400
        if ( self.__intStartMonth == 2 ):
            if ( self.__intStartYear % 4 == 0 ):
                intMaxDaysThisMonth = 29        

        if ( intMaxDaysThisMonth < self.__intStartDay ):
            self.__intStartDay = intMaxDaysThisMonth
            self.__sliderStartDay.setScaleValue( intMaxDaysThisMonth )

        # Force all months to be November and all years to be 2003
        self.__sliderStartMonth.setScaleValue( 11 )
        self.__sliderStartYear.setScaleValue( 2003 )

#----------------------- END OF DATE PICKER DIALOG --------------------------------



#------------------------ GUI CLASS ---------------------------------------
class GUI:
    def __init__( self, rootWindow ):
        global I_TRUE, I_FALSE

        # variables
        self.__tkintViz = Tkinter.IntVar()
        self.__tkintMap = Tkinter.IntVar()        

        guiFactory = GUIFactory()
        
        # Create window frame
        self.__rootWindow = rootWindow
        self.__rootWindow.title( 'CS 526, Project 3' )
        frameMain = Tkinter.Frame( self.__rootWindow )
        frameMain.pack( fill=Tkinter.BOTH, expand=1, side=Tkinter.LEFT )          


        # Add VTK rendering viewport [automatic packing]
        self.__vtkViewport = VTKRender( frameMain )


        # Add menu bar
        menuBar = Tkinter.Menu( frameMain )
        self.__rootWindow.config( menu=menuBar )
        self.__buildMenu( menuBar )
        print 'Add menu bar' #<<<<


        # Add pseudo-status bar (from "An Introduction to Tkinter" )
        self.__statusLabel = guiFactory.newLabel( frameMain, 'by: Allan Spale',
            {'relief':Tkinter.SUNKEN,'justify':Tkinter.CENTER, 'anchor':Tkinter.W }  )

        self.__statusErrorSettings = { 'background':'#ffffff',
                                       'foreground':'#000099' }
        self.__statusOKSettings = { 'background':self.__statusLabel.cget( 'background' ),
                                    'foreground':self.__statusLabel.cget( 'foreground' ) }

        self.__statusLabel.pack( fill=Tkinter.BOTH, side=Tkinter.BOTTOM )

        print 'Done with window' #<<<<


        # Setup date picker dialog
        self.__dialogDatePicker = DialogDatePicker( self.__rootWindow,
                                                    self.__vtkViewport,
                                                    self.__statusLabel )

        
        # Initialize
        #self.__vtkViewport.zSetupVTKPipeline()

        
        self.__tkintViz.set( Station.I_AVG_AIR_TEMP )
        self.__tkintMap.set( VTKRender.I_MAP_TYPE_COUNTY )
        
       
    def __buildMenu( self, menuMainMenu ):          
        menuFile = Tkinter.Menu( menuMainMenu, tearoff=0 )
        menuFile.add_command( label='Exit', command=self.__doMenuFileExit )
        menuMainMenu.add_cascade( label='File', menu=menuFile )


        self.__menuData = Tkinter.Menu( menuMainMenu, tearoff=0 )
        self.__menuData.add_radiobutton( label='4" Soil Temperature', 
            value=Station.I_AVG_4_IN_SOIL_TEMP, variable=self.__tkintViz,
            command=self.__doSetVizType )
        self.__menuData.add_radiobutton( label='Air Temperature', 
            value=Station.I_AVG_AIR_TEMP, variable=self.__tkintViz,
            command=self.__doSetVizType )   
        self.__menuData.add_radiobutton( label='Humidity', 
            value=Station.I_AVG_HUMIDITY, variable=self.__tkintViz,
            command=self.__doSetVizType )   
        self.__menuData.add_radiobutton( label='Precipitation', 
            value=Station.I_TOTAL_PRECIP, variable=self.__tkintViz,
            command=self.__doSetVizType )   
        self.__menuData.add_radiobutton( label='Wind', 
            value=Station.I_AVG_WIND_SPEED, variable=self.__tkintViz,
            command=self.__doSetVizType )   
        menuMainMenu.add_cascade( label='Data', menu=self.__menuData )

        self.__menuMaps = Tkinter.Menu( menuMainMenu, tearoff=0 )
        self.__menuMaps.add_radiobutton( label='County', 
            value=VTKRender.I_MAP_TYPE_COUNTY, variable=self.__tkintMap,
            command=self.__doSetMapType )
        self.__menuMaps.add_radiobutton( label='Roads', 
            value=VTKRender.I_MAP_TYPE_ROADS, variable=self.__tkintMap,
            command=self.__doSetMapType )
        self.__menuMaps.add_radiobutton( label='State Border', 
            value=VTKRender.I_MAP_TYPE_BOUNDARY, variable=self.__tkintMap,
            command=self.__doSetMapType )

        menuMainMenu.add_cascade( label='Maps', menu=self.__menuMaps )


    def __doSetVizType( self ):
        print "Viz = ", self.__tkintViz
        self.__dialogDatePicker.zSetCurrentVizType( self.__tkintViz.get() )


    def __doSetMapType( self ):
        self.__vtkViewport.zSetMapType( self.__tkintMap.get() )
        pass


    def __doMenuFileExit( self ):
        print 'Bye'
        self.__vtkViewport.destroy()
        self.__rootWindow.destroy()        
        sys.exit( 0 )
        print 'Done'

#------------------------ END OF GUI CLASS ----------------------------------    


        
#### main function ####
root = Tkinter.Tk()
gui = GUI(root)
root.mainloop()
