forked from NCAS-CMS/cf-view
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.py
2235 lines (1658 loc) · 90 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!usr/bin/env python
#Interface using PyGtk, CFPython and CFPlot
import pygtk
pygtk.require('2.0')
import gtk
import cf, cfplot as cfp
import os
### To Do ###
# View Data - Column and Row Titles
# View Data - Fix Scrolling Issue (Scroll to the very end)
# View Data - Information about the field
# View Maps - Option to load a single map in the default pop out window
# View Maps - Option to view data from a map
# View Maps - Load map from file
# Select Data - More output messages, e.g. when loading in a file
# View Maps - Map name is displayed on top of the area
# Select Data - Stash Code is displayed along with the field names
# Menu Bar - Options e.g. Zoom power, plot options
# Menu Bar - Save/Convert?
# View Maps - Zoom in and out towards centre
# Select Data - Button to make maps for all Z values
# Select Data - Other options from CF Plot
"""-------------------------------------------------------------------------------------------------------------------------------------|
| Data Class |
--------------------------------------------------------------------------------------------------------------------------------------"""
class CFData:
def __init__(self):
#DataSet is a store of the whole file; uses a lot of memory however is quicker than accessing data by re-reading the file
self.DataSet = []
#fieldDataSet is the list of field names
self.fieldDataSet = []
#dataIndex is the index of the field selected.
self.dataIndex = None
#Maximums and Minimums store [value,index]
self.MaximumX = [None, None]
self.MinimumX = [None, None]
self.MaximumY = [None, None]
self.MinimumY = [None, None]
self.MinimumZ = [None, None]
self.MaximumZ = [None, None]
#Only single Ts can be plotted so a maximum and minimum is not needed
self.currentTValue = [0,0]
#The common interval in the array.
self.xInterval = None
self.yInterval = None
self.zInterval = None
#The unit and name of each variable, e.g. (For X, the name may be Longitude and the unit is Degrees )
self.variableNames = []
self.unitNames = []
#readDataSet function stores the contents of the file being passed in into DataSet
def readDataSet(self, fileName):
self.DataSet = cf.read(fileName)
#getFiledNames function goes through the data from the file and retrieves the long names and the length of the arrays.
#The data retrieved is stored in order: Index - Field Name - Number of X Values - Number of Y Values - Number of Z Values - Number of T Values
def getFieldNames(self):
#The index is created so that data from the DataSet can easily be found.
index = 0
for DataPiece in self.DataSet:
#Dim3 is the X Data, Dim2 is the Y Data, Dim1 is the Z Data and Dim0 is the T Data
self.fieldDataSet.append([index, DataPiece.long_name, len(DataPiece.item('dim3').array), len(DataPiece.item('dim2').array),
len(DataPiece.item('dim1').array) , len(DataPiece.item('dim0').array)])
index += 1
#This function is called when the selected field changes and the XYZT values need to be retrieved/changed
def getXYZT(self):
#The functions finds the common interval in the array passed to it, if there isn't a common interval None is returned.
def findInterval (array):
interval = None
previousInterval = None
valid = True
for index in range(len(array)-1):
interval = array[index+1] - array[index]
if interval != previousInterval and previousInterval != None:
valid = False
previousInterval = interval
if valid == True:
return interval
#The variable names are retrieved from the data.
self.variableNames = [self.DataSet[self.dataIndex].item('dim3').ncvar, self.DataSet[self.dataIndex].item('dim2').ncvar,
self.DataSet[self.dataIndex].item('dim1').ncvar, self.DataSet[self.dataIndex].item('dim0').ncvar]
#The unit names are also retrieved.
self.unitNames = [self.DataSet[self.dataIndex].item('dim3').units,self.DataSet[self.dataIndex].item('dim2').units,
self.DataSet[self.dataIndex].item('dim1').units, self.DataSet[self.dataIndex].item('dim0').units]
#The arrays of each variable are stored.
self.xArray = self.DataSet[self.dataIndex].item('dim3').array
self.yArray = self.DataSet[self.dataIndex].item('dim2').array
self.zArray = self.DataSet[self.dataIndex].item('dim1').array
self.tArray = self.DataSet[self.dataIndex].item('dim0').array
#These are put into a list so that they can be more easily used.
self.xyztArrays = [self.xArray, self.yArray, self.zArray, self.tArray]
###The default values are set:
#The default for X includes all the values in its array
self.MaximumX = [self.xArray[len(self.xArray) - 1], len(self.xArray) -1]
self.MinimumX = [self.xArray[0], 0]
self.xInterval = findInterval(self.xArray)
#The default for Y includes all the values in its array
self.MinimumY = [self.yArray[0], 0]
self.MaximumY = [self.yArray[len(self.yArray) - 1], len(self.yArray) -1]
self.yInterval = findInterval(self.yArray)
#The default for Z contains the first value in its array
self.MinimumZ = [self.xArray[0], 0]
self.MaximumZ = self.MinimumZ
self.zInterval = findInterval(self.zArray)
#The default for T contains the first value in its array
self.currentTValue = [self.tArray[0], 0]
"""-------------------------------------------------------------------------------------------------------------------------------------|
| Interface Class |
--------------------------------------------------------------------------------------------------------------------------------------"""
class Interface:
#If this function returns false, the destroy function is called.
#ToDo: Edit function to create a pop-up window to ask if the user is sure they want to exit.
def delete_event(self, widget, event, data=None):
return False
#This function quits the interface.
def destroy(self, widget, data=None):
gtk.main_quit()
#This function is called when setting up the interface; it sets the properties of the main window.
def setWindowProperties(self, data=None):
#Set Window Design Properties
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.resize(int(self.window.get_screen().get_width() * 0.9), int(self.window.get_screen().get_height()*0.8))
self.window.move(int(self.window.get_screen().get_width()*0.05), int(self.window.get_screen().get_height()*0.1))
self.window.set_border_width(10)
self.window.set_title('CF View')
#Connect signals and functions
self.window.connect("delete_event", self.delete_event)
self.window.connect("destroy", self.destroy)
#This function is used to initialise the variables that will be used by the rest of the program.
def initialiseVariables(self):
#openFile is the window that pops-out when choosing the data file.
#This needs to be initialised so that there aren't multiple windows to choose a file.
self.openFile = None
#Sets the titles of the Field Selection tree view and also the alignment of the titles
self.fieldSelectionTitlesAlignment = 0.5
self.fieldSelectionTitles = ['Index', 'Field Name', 'X', 'Y', 'Z', 'T']
#xyztListStores are the ListStores containing the data from each variable in the form of [value, index]
self.xyztListStores = [gtk.ListStore(float, int), gtk.ListStore(float, int), gtk.ListStore(float, int), gtk.ListStore(float, int)]
#Set the label names so they can be changed when the user selects each variable
self.xyzLabelNames = ["Variable Name: ", "Minimum Value: ", "Common Interval: ", "Variable Units: ", "Maximum Value: ", "No. of Values Selected: "]
self.tLabelNames = ["Variable Name: ", "Variable Units: ", "Selected Value: ", "", "", ""]
#Set size and font of the labels.
self.labelSize = "<span font ='12'>"
self.labelEnd = "</span>"
#Set the title of the variables when displayed in the Tree View.
self.variablesTitle = ['X', 'Y', 'Z', 'T']
#Set the current selected variable to 0 (X)
self.currentXYZT = 0
#mapMode determines whether the plot is from the northern hemisphere, southern hemisphere or the whole world.
#The default (0) is the whole world
self.mapMode = 0
#Plot type refers to whether the plot is a Hovmuller plot
#The default (1) is not a Hovmuller plot
self.plotType = 1
#maps is the list of plots that the user has added to the viewing area.
self.maps = []
#new Maps is a list of plots to be added to the viewing area
self.newMaps = []
#The buttons start inactive.
self.buttonsActive = False
#Storing the height and width of the window
self.windowHeight = self.window.get_screen().get_height()
self.windowWidth = self.window.get_screen().get_width()
#Sets the size of the map thumbnails (Proportion of the screen)
self.mapThumbnailScalingX = 0.15
self.mapThumbnailScalingY = 0.15
#########################################################################################################################################
# Function #
# fileChanged #
# #
#########################################################################################################################################
#This function is called when a new file is selected.
def fileChanged(self, fileName):
#The functions from the CFData class to extract the data and update the field names are called
self.ClimateData.readDataSet(fileName)
self.ClimateData.getFieldNames()
#The List Store for field selection is cleared and the new data is put into it
self.fieldSelectionData.clear()
for fieldData in self.ClimateData.fieldDataSet:
self.fieldSelectionData.append(fieldData)
#########################################################################################################################################
# #
# Menu Region #
# #
#########################################################################################################################################
#This function is called when the open button is clicked from the menu bar.
#It creates a window to select which file to extract data from.
def addOpenFile(self, widget, Data=None):
#If the cancel button is clicked the widget is destroyed
def cancelOpenFile(widget, self, Data=None):
self.openFile.destroy()
self.openFile = None
#If the ok button is clicked the filename is checked to see if it is a .nc file.
#If it is it calls the function "fileChanged" and destroys the widget
#If not an error message is outputted.
def loadFile(widget, self, Data=None):
if self.openFile.get_filename()[-3:] == '.nc':
self.fileChanged(self.openFile.get_filename())
cancelOpenFile(None, self)
else:
self.addErrorMessage("Error - File type must be netCDF")
#If there is already an open file window, it is destroyed.
if self.openFile != None:
self.openFile.destroy()
#A new file selection window is created and the relevant signals connected to the corresponding functions.
self.openFile = gtk.FileSelection('Open File')
self.openFile.show()
self.openFile.cancel_button.connect("clicked", cancelOpenFile, self)
self.openFile.ok_button.connect("clicked", loadFile, self)
#This function is called when setting up the interface. It creates the menu bar at the top.
#To add this menu bar into the interface self.menuBar must be added to a container in the main code.
def addMenu(self, Data=None):
#Create Sub-menus
self.fileMenu = gtk.Menu()
self.optionsMenu = gtk.Menu()
#Create Items in File Sub-menu
self.openOption = gtk.MenuItem("Open")
self.fileMenu.append(self.openOption)
self.openOption.connect("activate", self.addOpenFile)
self.openOption.show()
self.quitOption = gtk.MenuItem("Quit")
self.fileMenu.append(self.quitOption)
self.quitOption.connect_object("activate", self.destroy, "file.quit")
self.quitOption.show()
#Create Items in Options Sub-menu
self.XConvOption = gtk.MenuItem("XConv Defaults")
self.optionsMenu.append(self.XConvOption)
self.XConvOption.show()
self.STASHOption = gtk.MenuItem("STASH Master File")
self.optionsMenu.append(self.STASHOption)
self.STASHOption.show()
self.netCDFOption = gtk.MenuItem("netCDF Attributes")
self.optionsMenu.append(self.netCDFOption)
self.netCDFOption.show()
self.globalnetCDFOption = gtk.MenuItem("Global netCDF Attributes")
self.optionsMenu.append(self.globalnetCDFOption)
self.globalnetCDFOption.show()
#Create Items to Display and Connect Sub-Menus
self.fileItem = gtk.MenuItem("File")
self.fileItem.show()
self.fileItem.set_submenu(self.fileMenu)
self.optionsItem = gtk.MenuItem("Options")
self.optionsItem.show()
self.optionsItem.set_submenu(self.optionsMenu)
#Create Menu Bar and Add Sub-Menus to Menu Bar
self.menuBar = gtk.MenuBar()
self.menuBar.append(self.fileItem)
self.menuBar.append(self.optionsItem)
self.menuBar.show()
#########################################################################################################################################
# Function #
# addErrorMessage #
# #
#########################################################################################################################################
#This function takes in a message and adds it to the last line of the output area.
#ToDo - Automatically scroll to the bottom of the area when a new message is added.
def addErrorMessage(self, message):
lastLine = self.outputBuffer.get_end_iter()
self.outputBuffer.insert(lastLine, message + "\n")
"""-------------------------------------------------------------------------------------------------------------------------------------|
| | |
| Create Maps Page |
| |
| ######################################################################################### |
| # # # # |
| # # # # |
| # # # # |
| # # # # |
| # # # 3 # |
| # # # # |
| # # # # |
| # 1 # 2 # # |
| # # # # |
| # # # # |
| # # # # |
| # # ######################### |
| # # # # |
| # # # # |
| # # # 4 # |
| # # # # |
| # # # # |
| ######################################################################################### |
| # # # # |
| # # # # |
| # # # # |
| # # # # |
| # 5 # 6 # 7 # |
| # # # # |
| # # # # |
| # # # # |
| # # # # |
| ######################################################################################### |
| |
| |
--------------------------------------------------------------------------------------------------------------------------------------"""
#########################################################################################################################################
# Function #
# selectionChanged #
# #
#########################################################################################################################################
#This function is called when a new field is selected. It updates all the relevant data.
def selectionChanged(self, widget, Data=None):
#The function adds every value in the array into the ListStore which are both passed into it. It also indexes each item
def createListStores (listStore, dataArray):
index = 0
for value in dataArray:
listStore.append([value, index])
index += 1
#Sets the index of the field currently selected
theModel, thePath = widget.get_selected()
self.ClimateData.dataIndex = theModel.get_value(thePath,0)
#Updates the variable data
self.ClimateData.getXYZT()
#Clear ListStores and add the contents of the respective arrays to each one
for i in range(4):
self.xyztListStores[i].clear()
createListStores(self.xyztListStores[i], self.ClimateData.xyztArrays[i])
#Buttons start inactive to avoid errors, so must be set to active
if self.buttonsActive == False:
for btn in self.xyztButtons:
btn.set_sensitive(True)
self.buttonsActive = True
self.xyztButtons[self.currentXYZT].set_active(True)
#Set the tree view to the currently active list store
self.xyztTreeview.set_model(self.xyztListStores[self.currentXYZT])
#Update the labels
self.updateLabels(self.currentXYZT)
#########################################################################################################################################
# Function #
# selectMaximum #
# #
#########################################################################################################################################
#This function is called when the select maximum button is clicked.
#It changes the Maximum of the selected variable to the last value in the selection
def selectMaximum(self, widget, Data=None):
#This function keeps updating TempValue, so the final time it is called TempValue will be the last value in the selection
def getLast (model, path, iter):
self.TempValue = [model.get_value(iter, 0),model.get_value(iter, 1)]
#Set temporary value as [] so if there is no selection the maximum will not be changed
self.TempValue = []
#For each row selected the getLast function is run
self.xyztTreeview.get_selection().selected_foreach(getLast)
#Checks to see if there was a selection
if self.TempValue != []:
#Checks which variable is selected and changes the respective maximum
if self.currentXYZT == 0:
#The selected maximum can not be lower than the minimum value, to avoid errors in plotting.
if self.TempValue[1] >= self.ClimateData.MinimumX[1]:
self.ClimateData.MaximumX = self.TempValue
else:
self.addErrorMessage("Error - Maximum value can not be lower than minimum value")
elif self.currentXYZT == 1:
if self.TempValue[1] >= self.ClimateData.MinimumY[1]:
self.ClimateData.MaximumY = self.TempValue
else:
self.addErrorMessage("Error - Maximum value can not be lower than minimum value")
elif self.currentXYZT == 2:
if self.TempValue[1] >= self.ClimateData.MinimumZ[1]:
self.ClimateData.MaximumZ = self.TempValue
else:
self.addErrorMessage("Error - Maximum value can not be lower than minimum value")
else:
self.addErrorMessage("Error - There are no values selected")
#Update the labels
self.updateLabels(self.currentXYZT)
#########################################################################################################################################
# Function #
# selectMinimum #
# #
#########################################################################################################################################
#This function is called when the Select Minimum button is clicked.
#It changes the Minimum of the selected variable to the first value in the selection
def selectMinimum(self, widget, Data=None):
#TempValue will only be empty on the first call of the function, so will only store the first value
def getFirst (model, path, iter):
if self.TempValue == []:
self.TempValue = [model.get_value(iter, 0), model.get_value(iter, 1)]
#Set temporary value as [] so if there is no selection the minimum will not be changed
self.TempValue = []
self.xyztTreeview.get_selection().selected_foreach(getFirst)
#Checks to see if there was a selection
if self.TempValue != []:
#Checks which variable is selected and changes the respective minimum
if self.currentXYZT == 0:
#The Minimum value can not be greater than the maximum value.
if self.TempValue[1] <= self.ClimateData.MaximumX[1]:
self.ClimateData.MinimumX = self.TempValue
else:
self.addErrorMessage("Error - Minimum value can not be greater than maximum value")
elif self.currentXYZT == 1:
if self.TempValue[1] <= self.ClimateData.MaximumY[1]:
self.ClimateData.MinimumY = self.TempValue
else:
self.addErrorMessage("Error - Minimum value can not be greater than maximum value")
elif self.currentXYZT == 2:
if self.TempValue[1] <= self.ClimateData.MaximumZ[1]:
self.ClimateData.MinimumZ = self.TempValue
else:
self.addErrorMessage("Error - Minimum value can not be greater than maximum value")
else:
self.addErrorMessage("Error - There are no values selected")
#Update the labels
self.updateLabels(self.currentXYZT)
#########################################################################################################################################
# Function #
# selectValue #
# #
#########################################################################################################################################
#This function is called when the Select button is clicked
#If T is selected, the function changes the chosen T value to the selected T value
#If another variable is selected the function changes both the maximum and minimum to the last and first value in the selection respectively.
def selectValue(self, widget, Data=None):
#Checks if T is selected.
if self.currentXYZT == 3:
#Get the location to the currently selected value
theModel, thePath = self.xyztTreeview.get_selection().get_selected()
#If there isn't a selected value thePath will be None, only update current values if there is a row selected
if thePath != None:
self.ClimateData.currentTValue = [theModel.get_value(thePath,0),theModel.get_value(thePath,1)]
else:
self.addErrorMessage("Error - There are no values selected")
#Else if X, Y or Z are selected
else:
def getFirstandLast(model, path, iter, Data):
#Functions runs multiple times, but the changes to MaxAndMin is passed through
#MaxAndMin[0] is only empty in the first pass so will get the first value
if MaxAndMin[0] == []:
MaxAndMin[0] = [model.get_value(iter, 0), model.get_value(iter, 1)]
#MaxAndMin[1] will keep updating with every pass of the function so will get the last value
MaxAndMin[1] = [model.get_value(iter, 0), model.get_value(iter, 1)]
#Set MaxAndMin to empty so if there is no selection, the maximum and minimum will not change
MaxAndMin = [[],[]]
#For each selected row run the getFirstandLast function
self.xyztTreeview.get_selection().selected_foreach(getFirstandLast, Data)
#If there are no rows selected MaxAndMin will be empty. Only update the minimums and maximums if it is not empty.
if MaxAndMin != [[],[]]:
#Check which variable is selected and change the maximum and minimum of that variable.
if self.currentXYZT == 0:
self.ClimateData.MinimumX = MaxAndMin[0]
self.ClimateData.MaximumX = MaxAndMin[1]
elif self.currentXYZT == 1:
self.ClimateData.MinimumY = MaxAndMin[0]
self.ClimateData.MaximumY = MaxAndMin[1]
else:
self.ClimateData.MinimumZ = MaxAndMin[0]
self.ClimateData.MaximumZ = MaxAndMin[1]
else:
self.addErrorMessage("Error - There are no values selected")
#Update the labels.
self.updateLabels(self.currentXYZT)
#########################################################################################################################################
# Function #
# reset #
# #
#########################################################################################################################################
#This function is called when the Reset button is clicked
#It resets the chosen values of the variable selected to default
def reset(self, widget):
#For X or Y (0 or 1) change minimum to lowest and maximum to highest value in the array.
#For Z or T (2 or 3) change the selected value to the first in the array
if self.currentXYZT == 0:
self.ClimateData.MaximumX = [self.ClimateData.xArray[len(self.ClimateData.xArray) - 1], len(self.ClimateData.xArray) -1]
self.ClimateData.MinimumX = [self.ClimateData.xArray[0], 0]
elif self.currentXYZT == 1:
self.ClimateData.MaximumY = [self.ClimateData.yArray[len(self.ClimateData.yArray) - 1], len(self.ClimateData.yArray) -1]
self.ClimateData.MinimumY = [self.ClimateData.yArray[0], 0]
elif self.currentXYZT == 2:
self.ClimateData.currentZValue = [self.ClimateData.zArray[0], 0]
self.ClimateData.MaximumZ = [self.ClimateData.zArray[0], 0]
self.ClimateData.MinimumZ = [self.ClimateData.zArray[0], 0]
else:
self.ClimateData.currentTValue = [self.ClimateData.tArray[0], 0]
#Update the lables
self.updateLabels(self.currentXYZT)
#########################################################################################################################################
# Function #
# xyztClicked #
# #
#########################################################################################################################################
#This function is called a variable button is clicked; whether to change the variable, when a .set_active() function is called or even if a toggled button is clicked again.
#It updates the labels, and the tree view to display the information about the newly selected variable
def xyztClicked(self, widget, variable):
#If widget is now active, it was either inactive before so the variable has changed, or the .set_active(True) function has been run on an already active button
if widget.get_active():
#Update current and previous variable choices
previousXYZT = self.currentXYZT
self.currentXYZT = variable
#Checks to see if the variable has changed, the current variable will be different to the previous variable.
if variable != previousXYZT:
#If it has changed the previous variables button is set to not be active (Toggled Off)
self.xyztButtons[previousXYZT].set_active(False)
#The tree view model and title are updated
self.xyztTreeview.set_model(self.xyztListStores[variable])
self.treeViewColumn.set_title(self.variablesTitle[variable])
#Change the label names according to the variable chosen
if variable != 3:
for informationLabel, labelName in zip(self.xyztInformationLabels, self.xyzLabelNames):
informationLabel.set_markup(self.labelSize + labelName + self.labelEnd)
#Allow multiple rows to be selected
self.xyztTreeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
#Activate the select maximum and minimum buttons
self.xyztButtons[4].set_sensitive(True)
self.xyztButtons[5].set_sensitive(True)
else:
for informationLabel, labelName in zip(self.xyztInformationLabels, self.tLabelNames):
informationLabel.set_markup(self.labelSize + labelName + self.labelEnd)
#Allow only a single row to be selected
self.xyztTreeview.get_selection().set_mode(gtk.SELECTION_SINGLE)
#Deactivate the maximum and minimum buttons
self.xyztButtons[4].set_sensitive(False)
self.xyztButtons[5].set_sensitive(False)
#Update the labels
self.updateLabels(self.currentXYZT)
#The widget will be inactive when the user clicks a toggled down button or the .set_active(False) function is run
else:
#Checks to see if the user clicked a toggled down button, and if so sets that button to active.
#There must be a button toggled down.
if variable == self.currentXYZT:
widget.set_active(True)
#########################################################################################################################################
# Function #
# updateLabels #
# #
#########################################################################################################################################
#This function is called whenever the data that the labels are showing changes.
#It simple updates the labels to show the new data.
def updateLabels(self, variable):
#shortcut is to avoid rewriting self.Climate data multiple times. Can be changed
shortcut = self.ClimateData
#The label information about the selected variable is loaded into a list, the data is converted to string if it is numerical
if variable == 0:
self.OutputData = [shortcut.variableNames[0], str(shortcut.MinimumX[0]), str(shortcut.xInterval),
shortcut.unitNames[0], str(shortcut.MaximumX[0]), str(shortcut.MaximumX[1]-shortcut.MinimumX[1] + 1)]
elif variable == 1:
self.OutputData = [shortcut.variableNames[1], str(shortcut.MinimumY[0]), str(shortcut.yInterval),
shortcut.unitNames[1], str(shortcut.MaximumY[0]), str(shortcut.MaximumY[1]-shortcut.MinimumY[1] + 1)]
elif variable == 2:
self.OutputData = [shortcut.variableNames[2], str(shortcut.MinimumZ[0]), str(shortcut.zInterval),
shortcut.unitNames[2], str(shortcut.MaximumZ[0]), str(shortcut.MaximumZ[1]-shortcut.MinimumZ[1] + 1)]
else:
self.OutputData = [shortcut.variableNames[3], shortcut.unitNames[3], str(shortcut.currentTValue[0]), '', '', '']
#For each item in the list create there is a corresponding label.
#These are cycled through and the labels text is updated.
for outputLabel, outputData in zip(self.xyztOutputLabels, self.OutputData):
outputLabel.set_markup(self.labelSize + outputData + self.labelEnd )
outputLabel.set_alignment(0, 0.5)
#########################################################################################################################################
# Function #
# plotMap #
# #
#########################################################################################################################################
#This method is called when the Add To Maps button or the Plot Map button are clicked
#It either plots the map to display or plots the map to add to the viewing area.
def plotMap(self, widget, mode):
#Checks to see if a field has actually been selected.
if self.ClimateData.dataIndex != None:
#Creating references to the data to make it easier to read.
i = self.ClimateData.dataIndex
Data = self.ClimateData.DataSet
tIndex = self.ClimateData.currentTValue[1]
#Mode is the variable passed in as a parameter, if it is 0 the Plot Map button has been clicked
#If it is 1 the Add to Maps button has been clicked.
if mode == 0:
#If the Plot Map button is clicked, the map should not be saved.
#The reset function is called to set file name to None inside CFPlot
#This will display the map in a pop out window when 'con' is called.
cfp.reset()
#If the add maps button has been clicked:
else:
#This loop checks to see if the name of the map entered into the entry has already been used
#x is the pixbuf of the map, name is the map name.
#If the map name has already been used an error message is displayed and the plotMap function is exited
for x, name in self.maps:
if self.mapNameEntry.get_text() == name:
self.addErrorMessage("Error - Map name already exists")
return None
#Checks to see if there is actually text in the map name entry, otherwise the plotMap function is exited.
if self.mapNameEntry.get_text() == "":
self.addErrorMessage("Error - Map name not specified")
return None
else:
#If all other validation has been passed a temporary file name is created to store the map in.
#This file will be deleted automatically in another function.
fileName = "temporarymap123456789.png"
cfp.setvars(file=fileName)
#Updates where the plot shows (e.g. Northern Hemisphere) depending on mapMode
if self.mapMode == 0:
cfp.mapset()
elif self.mapMode == 1:
cfp.mapset(proj='npstere')
else:
cfp.mapset(proj='spstere')
#Checks to see if the data is two dimensional.
#If it is the number of length 1 dimensions in the shape of the four dimensional array will be 2
if (Data[i].subspace[:, self.ClimateData.MinimumZ[1]:self.ClimateData.MaximumZ[1]+1,
self.ClimateData.MinimumY[1]:self.ClimateData.MaximumY[1]+1,
self.ClimateData.MinimumX[1]:self.ClimateData.MaximumX[1]+1]
).shape.count(1) == 2:
#Plot the Data
#plotType decides if the plot is a Hovmuller plot or not
cfp.con((Data[i].subspace[:, self.ClimateData.MinimumZ[1]:self.ClimateData.MaximumZ[1]+1,
self.ClimateData.MinimumY[1]:self.ClimateData.MaximumY[1]+1,
self.ClimateData.MinimumX[1]:self.ClimateData.MaximumX[1]+1]
), lines=self.plotType)
#If the data is not 2 dimensional
else:
self.addErrorMessage('Error - Incorrect number of dimensions')
#If the button add to maps was pressed
if mode == 1:
#Adds the new maps file name and map title to the newMaps list
self.newMaps.append([fileName, self.mapNameEntry.get_text()])
#Calls two other functions which add the map to the viewing area.
self.addNewMaps()
self.addMapsToDisplay()
#This function is called when any of the map property radio buttons are called.
#mapMode is changed depending on which button was pressed.
def mapModeSelection(self, widget, mode):
if widget.get_active():
self.mapMode = mode
#This function is called when the Hovmuller check button is clicked
#If changed to active the plotType is set to 0 (which means Hovmuller plot is true) else 1
def plotTypeSelection(self, widget):
if widget.get_active():
self.plotType = 0
else:
self.plotType = 1
#########################################################################################################################################
# Region #1 #
# Field Selection Region #
# (self.fieldSelectionInputWindow) #
#########################################################################################################################################
#This function is called when the Create Maps page is being created.
#It creates the Tree View for the user to select which field from the file they want to use
def addFieldSelection(self):
#The Tree View is put into a scrolled window which has a vertical scrolling bar but not a horizontal scrolled bar.
self.fieldSelectionInputWindow = gtk.ScrolledWindow()
self.fieldSelectionInputWindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
#The tree view is created and properties are set
self.fieldSelectionView = gtk.TreeView(None)
self.fieldSelectionView.set_search_column(1) # Sets the search column to 1 (The field name)
self.fieldSelectionView.set_rules_hint(True) # Alternating lines in the tree view will have different background colours
self.fieldSelectionView.set_headers_clickable(True) #The headers can be clicked to re order the model
#The tree view ListStore is created
#This stores [Index, Field Name, Length of X Array, Length of Y Array, Length of Z Array and Length of T Array]
self.fieldSelectionData = gtk.ListStore(int, str, int, int, int, int)
#The tree view's model is set to this list store. If the list store is edited the tree view will automatically change
self.fieldSelectionView.set_model(self.fieldSelectionData)
#The cell renderer is used to display the text in list store.
self.fieldSelectionRenderer = gtk.CellRendererText()
self.fieldSelectionRenderer.set_property('xpad', 25)
#This loop creates a tree view column for each of the items in the list store
for i in range(len(self.fieldSelectionTitles)):
#gtk.TreeViewColumn is initialised with (Title, Renderer, Column in ListStore being displayed)
fieldSelectionColumn = gtk.TreeViewColumn(self.fieldSelectionTitles[i], self.fieldSelectionRenderer, text=i)
#The column can be sortable
fieldSelectionColumn.set_sort_column_id(i)
#Sets the alignment of the title, (set in InitialiseVariables function)
fieldSelectionColumn.set_alignment(self.fieldSelectionTitlesAlignment)
#Each column is a fixed width, dependant on the size of the screen.
fieldSelectionColumn.set_property('sizing', gtk.TREE_VIEW_COLUMN_FIXED)
#i is 1 when the Field Names column is being added.
if i == 1:
fieldSelectionColumn.set_fixed_width(int(self.windowWidth * 0.25))
else:
fieldSelectionColumn.set_fixed_width(int(self.windowWidth * 0.04))
#Adds the column created to the tree view
self.fieldSelectionView.append_column(fieldSelectionColumn)
#Add the tree view to the scrolled window
self.fieldSelectionInputWindow.add(self.fieldSelectionView)
#When the selection is changed the function selectionChanged is called.
self.fieldSelectionChoice = self.fieldSelectionView.get_selection()
self.fieldSelectionChoice.connect("changed", self.selectionChanged)
#########################################################################################################################################
# Region #2 #
# XYZT Selection #
# (self.xyztTreeviewWindow) #
#########################################################################################################################################
#This function is called when the Create Maps page is being created
#It creates the tree view to see the data in the arrays of each variable
def addXYZTSelection(self):
#Creates a cell renderer to display the text
self.valueRenderer = gtk.CellRendererText()
self.valueRenderer.set_property('xalign', 1)
#A scrolled window to contain the tree view is created with a vertical scroll bar only
self.xyztTreeviewWindow = gtk.ScrolledWindow()
self.xyztTreeviewWindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
self.xyztTreeviewWindow.show()
#The tree view is created
self.xyztTreeview = gtk.TreeView(None)
self.xyztTreeview.show()
self.xyztTreeview.set_rules_hint(True) # Alternating lines will have different background colours
#Adds the tree view to the scrolled window.
self.xyztTreeviewWindow.add(self.xyztTreeview)
#Only a single column is needed. Initialised with (Title, Renderer, Column in List Store to Display)
self.treeViewColumn = gtk.TreeViewColumn(self.variablesTitle[self.currentXYZT], self.valueRenderer, text=0)
self.treeViewColumn.set_alignment(0.5)
self.treeViewColumn.set_property('sizing', gtk.TREE_VIEW_COLUMN_FIXED)
self.treeViewColumn.set_fixed_width(150)
self.xyztTreeview.append_column(self.treeViewColumn)
#Set the model of the tree view to be a list store with two columns, float and int.
#The second column is not displayed.
self.xyztTreeview.set_model(gtk.ListStore(float, int))
#########################################################################################################################################
# Region #3 #
# Select Data Page (Variable Display) #
# (self.xyztVBox) #
#########################################################################################################################################
#This function is called when the Create Maps page is being created
#It creates the labels and buttons for the Select Data tab in the notebook on the top right of the page.
def addSelectDataPage(self):
#A container to hold all of the labels and buttons is created
self.xyztVBox = gtk.VBox()
#All labels will be in a table with 4 rows and 5 columns
self.xyztTable = gtk.Table(4, 5, False)
self.xyztTable.set_row_spacings(30)
#The table is put at the start of the container so will be at the top of this page
self.xyztVBox.pack_start(self.xyztTable, False, False, 2)
#The labels are created and placed into the table.
#Labels start with no text
self.xyztLabel0 = gtk.Label('')
self.xyztTable.attach(self.xyztLabel0, 0, 1, 0, 1)
self.xyztLabel1 = gtk.Label('')
self.xyztTable.attach(self.xyztLabel1, 0, 1, 1, 2)
self.xyztLabel2 = gtk.Label('')
self.xyztTable.attach(self.xyztLabel2, 0, 1, 2, 3)
self.xyztLabel3 = gtk.Label('')
self.xyztTable.attach(self.xyztLabel3, 3, 4, 0, 1)
self.xyztLabel4 = gtk.Label('')
self.xyztTable.attach(self.xyztLabel4, 3, 4, 1, 2)
self.xyztLabel5 = gtk.Label('')
self.xyztTable.attach(self.xyztLabel5, 3, 4, 2, 3)
self.xyztLabel6 = gtk.Label('')
self.xyztTable.attach(self.xyztLabel6, 1, 2, 0, 1)
self.xyztLabel7 = gtk.Label('')
self.xyztTable.attach(self.xyztLabel7, 1, 2, 1, 2)
self.xyztLabel8 = gtk.Label('')
self.xyztTable.attach(self.xyztLabel8, 1, 2, 2, 3)