source: misc/tools/event_selection/EventSelection.py @ 6348

Last change on this file since 6348 was 6333, checked in by rwilson, 16 years ago

Added better error handling on failure to import WxPython?.

File size: 40.0 KB
Line 
1#!/usr/bin/env python
2################################################################################
3# Earthquake Event Selection.
4################################################################################
5
6
7import os
8import os.path
9import sys
10import re
11import shutil
12import time
13
14import tempfile
15try:
16    import cpickle
17except:
18    import pickle
19
20try:
21    import wx
22except ImportError:
23    import tkinter_error
24    msg = ('Sorry, you have to install WxPython.\n'
25           'Get it from either:\n'
26           r'   N:\georisk\downloads\event_selection, or' '\n'
27           '   [http://www.wxpython.org/download.php#binaries].')
28    tkinter_error.tkinter_error(msg)
29    print msg
30    sys.exit(1)
31
32   
33Imported_PyEmbeddedImage = True
34try:
35    from wx.lib.embeddedimage import PyEmbeddedImage
36except ImportError:
37    Imported_PyEmbeddedImage = False
38
39
40######
41# Various constants
42######
43
44# where the data lives
45if sys.platform == 'win32':
46    BaseDirectory = r'N:\georisk_models\tsunami\models\PTHA'
47else:
48    BaseDirectory = '/nas/gemd/georisk_models/tsunami/models/PTHA'
49
50# name of the fault name file (in multimux directory)
51FaultNameFilename = 'fault_list.txt'
52
53# program name and version
54APP_NAME = 'EventSelection'
55APP_VERSION = '0.7'
56
57# name of the configuration filename
58# GUI values are saved in this file for next time
59ConfigFilename = 'EventSelection.cfg'
60
61# name of the 'fault XY' output file
62FaultXYFilename = 'fault.xy'
63
64# name of the quake probability output file
65QuakeProbFilename = 'quake_prob.txt'
66
67# name of the output URS data module
68# note: the %d is where the event number goes
69URSDataFilename = 'urs_data_%6.6d.py'
70
71# name of the output bash params file
72# note: the %d is where the event number goes
73GridBashScriptName = 'faults_%6.6d.params'
74
75# map display name to directory name
76Regions = [ ('Australia',  'Australia'),
77            ('India',      'Indian'),
78            ('SW Pacific', 'SW_Pacific')
79          ]
80
81# pattern string used to split multimax data
82SpacesPatternString = ' +'
83
84# generate 're' pattern for 'any number of spaces'
85SpacesPattern = re.compile(SpacesPatternString)
86
87# Global holding current results sub-directory
88ResultSubdir = ''
89
90# a small value to perturb the user limits
91Epsilon = 1.0E-6
92
93# GUI definitions
94BOX_WIDTH = 400
95BOX_HEIGHT = 360
96BOX2_HEIGHT = 285
97
98
99FORM_WIDTH = BOX_WIDTH + 15
100FORM_HEIGHT = 755
101
102START_YOFFSET = 7
103OUT_TEXT_XOFFSET = 8
104OUT_TEXT_YOFFSET = 11
105OUTPUT_BOX_HEIGHT = 45
106
107GEN_YOFFSET = 15
108GEN_DELTAY = 25
109GEN_LABELXOFFSET = 5
110GEN_TEXTXOFFSET = BOX_WIDTH/2 + GEN_LABELXOFFSET + 10
111
112TEXTLIST_WIDTH = BOX_WIDTH - 10
113TEXTLIST_HEIGHT = 200
114
115CTL_WIDTH = BOX_WIDTH / 2
116CTL_HEIGHT = 22
117
118BUTTON_WIDTH = 100
119BUTTON_HEIGHT = 30
120
121DOUBLE_BUTTON_OFFSET = 8
122
123COMBO_FUDGE = 2
124
125# Copies of various parameters
126Region = ''
127GaugeNumber = ''
128MinimumHeight = ''
129MaximumHeight = ''
130
131
132################################################################################
133# This code was generated by img2py.py
134# Embed the ICON image here, so we don't need an external *.ico file.
135# Icon image inspired by Hokusai's print "The Great Wave off Kanagawa"
136# (Kanagawa okinami ura) from the "Thirty-Six Views of Mount Fuji" series.
137# http://commons.wikimedia.org/wiki/Image:The_Great_Wave_off_Kanagawa.jpg
138################################################################################
139
140if Imported_PyEmbeddedImage:
141    def getIcon():
142        return PyEmbeddedImage(
143          "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAABDBJ"
144          "REFUWIXtlmtsFFUUx393dnbbbh+hlPQVKEEBKSLyEEEhqFQSNYAQNahAIJJUidEEtUQIRgk+"
145          "GkSMJBJjUEADCVghtipSjeWhWIoh5VXoBqgU2aWvLbt0H52dmesH6La1U7OVbvCD59PcmTPn"
146          "/M69/3vuFYDkFppyK5P/D/CfAFD//kIgcGbkkpk/GYczjat/umiqrUIaBgA2RwKG1h71lxZP"
147          "/xJAkJiazn3LNnD7g8+gqI5o0Iaawxza+CJ3zlnGiIKF+C6f5+iWVTicaaRkDSXY4uHCwa/Q"
148          "20N9BhGAdA7IIjVnGNNf3Uz60NEghYWr7PJLz3GguZ6Tuz/C8/teQoFrDBoxEefAbPyeOtzV"
149          "FZhGxBpg9voDMnvMVIRi6xN57xVJJF0LkDScreSHVbNob/P28Fdyxk7vt+TX0wmuz46k/si3"
150          "uMq3kTVqCmPmvWTp30OE/WWu8q1UbV5J2N9C7rgZvS5BXACklFTveI9BIycweNKj1FeWcWrP"
151          "RojOTqfdVB8QvSheILEnp6EF/OgBPwNvG8v8LS6e3lrLhAWrUZTOukVhuYzLYSRNo4u2ZLfi"
152          "qza/zvFd7yOR8euEXZNfOFjCzsV3sOu5fLwXTzP84UXRuYt7K24+d4yK4oUEvW600DXqDpTQ"
153          "1ngxulHjtgs67OLhUgxDx56cxl1zX0ZKk0MbCqPf46aBDpPSIBJqI3y1Gb/7HJ4T+6nd+xlB"
154          "X1NXAAlYtV/LkN18TSNCqLUR58DsmBtaqNXD9ysfwVt3skMDsSa/7msaEa6c/pWg14PeHmTn"
155          "khGUvTKda1cuWAI3nj3CH7/soaHmNwy9naT0bKYUrgdE7yIUwmplJH73OUqXT+OnNU9wfNc6"
156          "bPZEFNVO45lKKtYtBiRhfzOGFqZj39WUbuLHtU9SunwquwvHEWhxkzF8AopN7R1AWpyIquZn"
157          "3xuz8F2qZdKSNeTdM5MzO9/BnpBE7riHULU2VAWqd7zL1y+Mp7W+BhDcPX8FAsHo2cuItAcJ"
158          "eT00uaqQht43Edbu+5wDHyzt8T5j2BimLV2Lt+EyWijAsS/ewjQN0vPymfvxUYRQ2P7sEOyJ"
159          "yeTPep6w30tN2Sa0gK9v2/D8z9u7z4iqous6LXWn+Gb1PBwOB5qmkZiaQcGKbZS/+Tju6goG"
160          "T5yJPSkV32UXlZ8WdYsRcyMSSPRQoHMsBKZpdvPRNA2AlMwhaG1eMkdNJiUrj5CviUDTJcu4"
161          "MQNIIDlraOdYyh4AHdZSd4JIOMhjxfsAyf7iRTdEaVFYrBqwKZLqkg+p/OQ1ZAz3PiEENjUB"
162          "U9cwpTUogBLrJVJKGD3jKewJSTH6S/RI+B+T3wCIrQmZUmAbMJh7F6xAtfXfEdLH01Awcl4R"
163          "eeMfwOFw3AoAUBOc3F/0JZnDx5OUFNty9CsAgDM9h4K3vyNvyhxSUlJvCuAvPDCzruPdn6UA"
164          "AAAASUVORK5CYII=")
165
166
167################################################################################
168# An object that behaves like a dictionary but is pickled/unpickled from a file.
169################################################################################
170
171class Config(object):
172    """
173    An object that behaves like a dictionary but is persistent:
174
175        cfg = Config('filename')
176        cfg['save'] = 'A saved value'
177        cfg[123] = 'value 123'
178        print cfg['save']
179
180    The values stored in the file 'filename' will be available to any
181    application using that config file.
182    """
183
184    def __init__(self, configfile=None):
185        """
186        __init__(self, String filename=None) -> Config
187        """
188
189        self.delconf = False
190        self.cfgdict = {}
191        self.changed = False
192        if not configfile:
193            self.delconf = True
194            configfile =tempfile.mktemp()
195        self.configfile = os.path.abspath(configfile)
196        if os.path.exists(self.configfile):
197            try:
198                f = open(configfile, "r")
199                u = pickle.Unpickler(f)
200                self.cfgdict = u.load()
201            except pickle.UnpicklingError, e:
202                print e
203            except:
204                pass
205            else:
206                f.close()
207
208    def __setitem__(self, key, value):
209        """
210        Override to allow: cfg[<key>] = <value>
211        """
212        self.cfgdict[key] = value
213        self.changed = True
214
215    def __getitem__(self, key):
216        """
217        Override to allow: <var> = cfg[<key>]
218        """
219        return self.cfgdict.get(key, None)
220
221    def __str__(self):
222        """
223        __str__(self) -> String
224        """
225        return "<config object at %s>" % hex(id(self))
226
227    def getfilename(self):
228        """
229        getfilename(self) -> String filename
230        """
231        return self.configfile
232
233    def setdeleted(self):
234        """
235        setdeleted(self)
236        """
237        self.delconf = True
238
239    def save(self):
240        """
241        save(self)
242        """
243        try:
244            f = open(self.configfile, "w")
245            p = pickle.Pickler(f)
246            p.dump(self.cfgdict)
247        except pickle.PicklingError, e:
248            print e
249        else:
250            f.close()
251        self.changed = False
252
253    def close(self):
254        """
255        close(self)
256        """
257        if self.changed:
258            self.save()
259        if self.delconf:
260            if os.path.exists(self.configfile):
261                os.remove(self.configfile)
262
263    def __del__(self):
264        """
265        __del__(self)
266        """
267        self.close()
268
269################################################################################
270# This code implements the logic of David Burbidge's 'list_quakes' program.
271# Except the two output files are now in CSV format.
272#################################################################################
273
274##
275# @brief Function to do all the work - list the quakes selected.
276def list_quakes(event_num, min_height, max_height, InvallFilename,
277                TStarFilename, FaultXYFilename, QuakeProbFilename):
278    ##
279    # @brief Class to hold i_invall data
280    class Inval(object):
281        def __init__(self, lon, lat):
282            self.lon = lon
283            self.lat = lat
284
285        def __str__(self):
286            return '.lon=%g, .lat=%g' % (self.lon, self.lat)
287
288
289    ##
290    # @brief Class to hold T-**** data
291    class TStar(object):
292        def __init__(self, ipt, zquake, zprob, mag, slip, ng, ng_data):
293            self.ipt = ipt
294            self.zquake = zquake
295            self.zprob = zprob
296            self.mag = mag
297            self.slip = slip
298            self.ng_data = [int(i) for i in SpacesPattern.split(ng_data)]
299            if len(self.ng_data) != ng:
300                raise RuntimeError(1, "Error parsing T-***** data: %s" % msg)
301
302        def __str__(self):
303            return ('.ipt=%d, .zquake=%g, .zprob=%g, .mag=%g, .slip=%g, .ng_data=%s' %
304                    (self.ipt, self.zquake, self.zprob, self.mag,
305                     self.slip, str(self.ng_data)))
306
307
308    # read i_invall file
309    try:
310        fd = open(InvallFilename, "r")
311        invall_lines = fd.readlines()
312        fd.close()
313    except IOError, msg:
314        raise RuntimeError(1, "Error reading file '%s': %s" % 
315                           (InvallFilename, msg))
316
317    # trash the first three lines
318    invall_lines = invall_lines[3:]
319
320    # split i_invall data into fields
321    invall_data = []
322    for line in invall_lines:
323        l = line.strip()
324        (lon, lat, _) = SpacesPattern.split(l, maxsplit=2)
325        invall_data.append(Inval(float(lon), float(lat)))
326
327    del invall_lines
328
329    # now read T-**** file, creating filename from event #
330    try:
331        fd = open(TStarFilename, "r")
332        tstar_lines = fd.readlines()
333        fd.close()
334    except IOError, msg:
335        raise RuntimeError(1, "Error reading file: %s" % msg)
336
337    # trash the first line of T-**** data
338    tstar_lines = tstar_lines[1:]
339
340    # get the data from tstar_lines
341    tstar_data = []
342    min_wave = min_height - Epsilon
343    max_wave = max_height + Epsilon
344    for (ipt, line) in enumerate(tstar_lines):
345        l = line.strip()
346        (zquake, zprob, mag, slip, ng, ng_data) = SpacesPattern.split(l, maxsplit=5)
347        zquake = float(zquake)
348        zprob = float(zprob)
349        mag = float(mag)
350        slip = float(slip)
351        ng = int(ng)
352        ng_data = ng_data.strip()
353        # only remember the data if zquake in wave height range and zprob > 0.0
354        if zprob > 0.0 and (min_wave <= zquake <= max_wave):
355            tstar_data.append(TStar(ipt+1, zquake, zprob, mag, slip, ng, ng_data))
356    del tstar_lines
357
358    # write out lines joining centroids
359    try:
360        outfd = open(FaultXYFilename, "w")
361    except IOError, msg:
362        raise RuntimeError(1, "Error opening output file: %s" % msg)
363
364    outfd.write('Lon,Lat,Quake_ID,Subfault_ID\n')
365    for t in tstar_data:
366        for n in t.ng_data:
367            outfd.write('%.4f,%.4f,%d,%d\n' %
368                        (invall_data[n].lon, invall_data[n].lat, t.ipt, n))
369    outfd.close()
370
371    # write out quake probabilities
372    try:
373        outfd = open(QuakeProbFilename, "w")
374    except IOError, msg:
375        raise RuntimeError(1, "Error opening output file: %s" % msg)
376
377    outfd.write('Quake_ID,Ann_Prob,z_max(m),Mag\n')
378    for t in tstar_data:
379        outfd.write('%d,%.5G,%.5f,%.2f\n' % (t.ipt, t.zprob, t.zquake, t.mag))
380    outfd.close()
381
382################################################################################
383# This code does exactly what David Burbidge's 'get_multimux' program does.
384################################################################################
385
386def get_multimux(quake_ID, multimux_dir, fault_event_filename):
387    # get the fault name data
388    filename = os.path.join(multimux_dir, FaultNameFilename)
389    try:
390        fd = open(filename, "r")
391        fault_names = [ fn.strip() for fn in fd.readlines() ]
392        fd.close()
393    except IOError, msg:
394        raise RuntimeError(1, "Error reading file: %s" % msg)
395
396    # open the output file
397    try:
398        outfd = open(fault_event_filename, "w")
399    except IOError, msg:
400        raise RuntimeError(1, "Error opening output file: %s" % msg)
401
402    # handle each fault
403    nquake = 0
404    for fn in fault_names:
405        # create the filename for the multimux data file
406        mmx_filename = 'i_multimux-%s' % fn
407        mmx_filename = os.path.join(multimux_dir, mmx_filename)
408
409        # Read all data in file, checking as we go
410        try:
411            infd = open(mmx_filename, "r")
412        except IOError, msg:
413            raise RuntimeError(1, "Error opening file: %s" % msg)
414
415        # check fault name in file is as expected
416        mux_faultname = infd.readline().strip()
417        if mux_faultname != fn:
418            raise RuntimeError(1, "Error reading file")
419
420        # read data
421        while True:
422            # get number of subfaults, EOF means finished
423            try:
424                nsubfault = infd.readline()
425            except IOError:
426                raise RuntimeError(1, "Error reading file")
427
428            if not nsubfault:
429                break
430            nsubfault = int(nsubfault)
431
432            nquake += 1
433            if nquake == quake_ID:
434                outfd.write(' %d\n' % nsubfault)
435                for i in range(nsubfault):
436                    line = infd.readline()
437                    (subfaultname, slip, prob, mag, _) = \
438                                   SpacesPattern.split(line, maxsplit=4)
439                    subfaultname = subfaultname.strip()
440                    slip = float(slip)
441                    outfd.write(" %s %g\n" % (subfaultname, slip))
442            else:
443                for i in range(nsubfault):
444                    try:
445                        infd.readline()
446                    except IOError:
447                        raise RuntimeError(1,
448                                           "Something wrong at bottom of file %s" %
449                                           mux_faultname)
450
451        infd.close()
452    outfd.close()
453
454
455################################################################################
456# Code to do the GUI
457#
458# The Frame code is buried under the "if __name__ ..." test since we are going
459# to import this file into verify.py to test the list_quake() and get_multimux()
460# functions.
461################################################################################
462
463if __name__ == '__main__':
464   
465    class MyFrame(wx.Frame):
466        def __init__(self, parent, id, title, pos=wx.DefaultPosition,
467                        size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE):
468            # Make the frame
469            wx.Frame.__init__(self, parent, id, title, pos=(50, 50),
470                                size=(FORM_WIDTH, FORM_HEIGHT),
471                                style=(wx.DEFAULT_FRAME_STYLE &
472                                       ~ (wx.RESIZE_BOX | wx.MAXIMIZE_BOX |
473                                          wx.RESIZE_BORDER)))
474
475            p = self.panel = wx.Panel(self, -1)
476
477            if Imported_PyEmbeddedImage:
478                tsunami = getIcon()
479                icon = tsunami.GetIcon()
480                self.SetIcon(icon)
481           
482            self.Center()
483            self.Show(True)
484
485            # place the 'output dir' textbox
486            Y_OFFSET = START_YOFFSET
487            wx.StaticBox(p, -1, 'output base directory', (3, 1),
488                         size=(BOX_WIDTH, OUTPUT_BOX_HEIGHT))
489            self.txtOutputDir = wx.TextCtrl(p, -1, size=(BOX_WIDTH-10, 20),
490                                            pos=(OUT_TEXT_XOFFSET,
491                                                 Y_OFFSET+OUT_TEXT_YOFFSET))
492            self.txtOutputDir.SetToolTip(wx.ToolTip("Where your data files will be written"))
493            self.txtOutputDir.Bind(wx.EVT_LEFT_DOWN, self.OnOutputDirSelect)
494            Y_OFFSET += 50
495           
496            # start laying out list_quake controls
497            wx.StaticBox(p, -1, 'list_quakes', (3, Y_OFFSET),
498                         size=(BOX_WIDTH, BOX_HEIGHT))
499            sampleList = [ x for (x, y) in Regions ]
500            Y_OFFSET += GEN_YOFFSET
501            wx.StaticText(p, -1, 'Region', pos=(GEN_LABELXOFFSET, Y_OFFSET),
502                          size=(CTL_WIDTH, CTL_HEIGHT), style=wx.ALIGN_RIGHT)
503            self.cbRegion = wx.ComboBox(p, 500, 
504                                        pos=(GEN_TEXTXOFFSET, Y_OFFSET-COMBO_FUDGE),
505                                        size=(100,20),
506                                        choices=sampleList,
507                                        style=wx.CB_DROPDOWN)
508            self.cbRegion.SetEditable(False)
509            Y_OFFSET += GEN_DELTAY
510            wx.StaticText(p, -1, 'Hazard index',
511                          pos=(GEN_LABELXOFFSET, Y_OFFSET),
512                          size=(CTL_WIDTH, CTL_HEIGHT), style=wx.ALIGN_RIGHT)
513            self.txtTGN = wx.TextCtrl(p, -1, size=(100, 20),
514                                      pos=(GEN_TEXTXOFFSET, Y_OFFSET))
515            Y_OFFSET += GEN_DELTAY
516            wx.StaticText(p, -1, 'Minimum wave height',
517                          pos=(GEN_LABELXOFFSET, Y_OFFSET),
518                          size=(CTL_WIDTH, CTL_HEIGHT), style=wx.ALIGN_RIGHT)
519            self.txtMinH = wx.TextCtrl(p, -1, size=(100, 20),
520                                       pos=(GEN_TEXTXOFFSET, Y_OFFSET))
521            Y_OFFSET += GEN_DELTAY
522            wx.StaticText(p, -1, 'Maximum wave height',
523                          pos=(GEN_LABELXOFFSET, Y_OFFSET),
524                          size=(CTL_WIDTH, CTL_HEIGHT), style=wx.ALIGN_RIGHT)
525            self.txtMaxH = wx.TextCtrl(p, -1, size=(100, 20),
526                                       pos=(GEN_TEXTXOFFSET, Y_OFFSET))
527            Y_OFFSET += GEN_DELTAY
528            x = FORM_WIDTH/2 - BUTTON_WIDTH/2
529            self.btnGenerate = wx.Button(p, label="List",
530                                         pos=(x, Y_OFFSET),
531                                         size=(100, BUTTON_HEIGHT))
532            Y_OFFSET += GEN_DELTAY + BUTTON_HEIGHT/2
533
534            self.lstGenerate = wx.ListCtrl(p, -1, pos=(8, Y_OFFSET),
535                                           size=(TEXTLIST_WIDTH,TEXTLIST_HEIGHT),
536                                           style=wx.LC_REPORT|wx.LC_SINGLE_SEL)
537           
538            Y_OFFSET += TEXTLIST_HEIGHT + 20
539            self.Bind(wx.EVT_BUTTON, self.GenerateClick, self.btnGenerate)
540            self.lstGenerate.Bind(wx.EVT_LIST_ITEM_SELECTED,
541                                  self.OnItemSelected, self.lstGenerate)
542
543            # start laying out get_multimux controls
544            wx.StaticBox(p, -1, 'multimux_grid', (3, Y_OFFSET),
545                         size=(BOX_WIDTH, BOX2_HEIGHT))
546            Y_OFFSET += 18
547            wx.StaticText(p, -1, 'Quake ID', pos=(GEN_LABELXOFFSET, Y_OFFSET),
548                          size=(CTL_WIDTH, CTL_HEIGHT), style=wx.ALIGN_RIGHT)
549            self.txtQuakeID = wx.TextCtrl(p, -1, size=(100, 20),
550                                          pos=(GEN_TEXTXOFFSET, Y_OFFSET))
551            Y_OFFSET += GEN_DELTAY
552            x = FORM_WIDTH/2 - BUTTON_WIDTH - DOUBLE_BUTTON_OFFSET
553            self.btnMultimux = wx.Button(p, label="Multimux", pos=(x, Y_OFFSET),
554                                         size=(100, BUTTON_HEIGHT))
555            x = FORM_WIDTH/2 + DOUBLE_BUTTON_OFFSET
556            self.btnGrid = wx.Button(p, label="Grid", pos=(x, Y_OFFSET),
557                                         size=(100, BUTTON_HEIGHT))
558            Y_OFFSET += GEN_DELTAY + BUTTON_HEIGHT/2
559            self.lstMultimux = wx.ListCtrl(p, -1, pos=(8, Y_OFFSET),
560                                           size=(TEXTLIST_WIDTH,TEXTLIST_HEIGHT),
561                                           style=wx.LC_REPORT|wx.LC_SINGLE_SEL)
562            Y_OFFSET += TEXTLIST_HEIGHT + 20
563            self.Bind(wx.EVT_BUTTON, self.MultimuxClick, self.btnMultimux)
564            self.Bind(wx.EVT_BUTTON, self.GridClick, self.btnGrid)
565
566            self.Bind(wx.EVT_CLOSE, self.doStateSave)
567            self.doStateRestore()
568
569        def OnOutputDirSelect(self, event):
570            dlg = wx.DirDialog(self, "Choose an output directory:",
571                  style=wx.DD_DEFAULT_STYLE
572                   #| wx.DD_DIR_MUST_EXIST
573                   #| wx.DD_CHANGE_DIR
574                   )
575
576            # If the user selects OK, then we process the dialog's data.
577            # This is done by getting the path data from the dialog - BEFORE
578            # we destroy it.
579            if dlg.ShowModal() == wx.ID_OK:
580                self.txtOutputDir.Clear()
581                self.txtOutputDir.WriteText(dlg.GetPath())
582            self.cbRegion.SetFocus()
583            # Only destroy a dialog after you're done with it.
584            dlg.Destroy()
585
586
587        def doStateSave(self, event):
588            """
589            Save state here.
590            """
591            self.cfg['OutputDirectory'] = self.txtOutputDir.GetValue()
592            self.cfg['Region'] = self.cbRegion.GetValue()
593            self.cfg['GaugeNumber'] = self.txtTGN.GetValue()
594            self.cfg['MinimumHeight'] = self.txtMinH.GetValue()
595            self.cfg['MaximumHeight'] = self.txtMaxH.GetValue()
596            self.cfg.save()
597           
598            event.Skip(True)
599
600        def doStateRestore(self):
601            """
602            Restore state here - globals have been set and tested sane
603            """
604            global Region, GaugeNumber, MinimumHeight, MaximumHeight
605
606            cfg = Config(ConfigFilename)
607            output_dir = cfg['OutputDirectory']
608            Region = cfg['Region']
609            GaugeNumber = cfg['GaugeNumber']
610            if GaugeNumber:
611                GaugeNumber = int(GaugeNumber)
612            MinimumHeight = cfg['MinimumHeight']
613            if MinimumHeight:
614                MinimumHeight = float(MinimumHeight)
615            MaximumHeight = cfg['MaximumHeight']
616            if MaximumHeight:
617                MaximumHeight = float(MaximumHeight)
618
619            self.cfg = cfg
620
621            region_name = None
622            for (name, dir) in Regions:
623                if name == Region:
624                    region_name = name
625            if not region_name:
626                Region = ''
627
628            if output_dir:
629                self.txtOutputDir.WriteText(output_dir)
630            self.cbRegion.SetStringSelection(Region)
631            if GaugeNumber:
632                self.txtTGN.WriteText(str(GaugeNumber))
633            if MinimumHeight:
634                self.txtMinH.WriteText(str(MinimumHeight))
635            if MaximumHeight:
636                self.txtMaxH.WriteText(str(MaximumHeight))
637           
638
639        def OnItemSelected(self, event):
640            item = self.lstGenerate.GetItem(event.m_itemIndex, 0)
641            self.txtQuakeID.SetValue(item.GetText())
642
643        def error(self, msg):
644            dlg = wx.MessageDialog(self, msg, 'Error',
645                                   wx.OK | wx.ICON_INFORMATION)
646            dlg.ShowModal()
647            dlg.Destroy()
648
649        ######
650        # Call the list_quakes function.
651        ######
652        def GenerateClick(self, event):
653            global ResultSubdir, Region, GaugeNumber, MinimumHeight, MaximumHeight
654           
655            # check that everything has been entered correctly
656            output_base_dir = self.txtOutputDir.GetValue()
657            if len(output_base_dir) == 0:
658                self.error('You must select an output base directory first')
659                return
660
661            region_select = self.cbRegion.GetValue()
662            if len(region_select) == 0:
663                self.error('You must select a region first')
664                return
665
666            try:
667                gauge_number = int(self.txtTGN.GetValue())
668            except:
669                self.error("You must enter a Hazard Index Number")
670                return
671
672            try:
673                min_height = float(self.txtMinH .GetValue())
674            except:
675                self.error("You must enter a minimum wave height in metres (eg, 1.3)")
676                return
677
678            try:
679                max_height = float(self.txtMaxH.GetValue())
680            except:
681                self.error("You must enter a maximum wave height in metres (eg, 1.8)")
682                return
683            if min_height > max_height:
684                self.error("Minimum wave height must be less than maximum height")
685                return
686
687            # set directory names
688            basename = None
689            region_name = None
690            for (name, dir) in Regions:
691                if name == region_select:
692                    region_name = name
693                    basename = dir
694            base_directory = os.path.join(BaseDirectory, basename)
695            if not os.path.exists(base_directory):
696                self.error("Sorry, region %s doesn't exist yet (%s)" %
697                           (region_name, base_directory))
698                return
699            tFilesDir = os.path.join(base_directory, 'Tfiles')
700            multimuxDir = os.path.join(base_directory, 'multimux')
701
702            # create a sub-directory for the result files
703            ResultSubdir = os.path.join(output_base_dir,
704                                        'Results_%s_%d_%.2f_%.2f' %
705                                        (region_select, gauge_number,
706                                         min_height, max_height))
707            try:
708                shutil.rmtree(ResultSubdir)
709            except:
710                pass
711            try:
712                os.mkdir(ResultSubdir)
713            except WindowsError, msg:
714                self.error("Sorry, don't seem to be able to make "
715                           "the result directory '%s':\n%s" %
716                           (ResultSubdir, str(msg)))
717                return
718            except:
719                self.error("Some sort of error making the result directory '%s'" %
720                           ResultSubdir)
721                return
722
723            # set names of the output files (in dir '<wherever>/Results*')
724            faultxy_filename = os.path.join(ResultSubdir, FaultXYFilename)
725            quakeprob_filename = os.path.join(ResultSubdir, QuakeProbFilename)
726
727            # now check that gauge number is valid
728            gaugeFile = os.path.join(tFilesDir, 'T-%05d' % gauge_number)
729            if not os.path.exists(gaugeFile):
730                self.error("T file for hazard %d doesn't exist" % gauge_number)
731                return
732
733            # check we have an i_invall file
734            invallFile = os.path.join(multimuxDir, 'i_invall')
735            if not os.path.exists(invallFile):
736                self.error("'i_invall' file doesn't exist: %s" % invallFile)
737                return
738
739            # save region, hazard #, min and max heights to globals
740            Region = region_select
741            GaugeNumber = gauge_number
742            MinimumHeight = min_height
743            MaximumHeight = max_height
744
745            # clear the Quake ID textbox
746            self.txtQuakeID.SetValue('')
747           
748            # now process the data we have
749            # clear output list
750            self.lstGenerate.ClearAll()
751            # delete output files
752            try:
753                os.unlink(FaultXYFilename)
754            except:
755                pass
756            try:
757                os.unlink(QuakeProbFilename)
758            except:
759                pass
760
761            ####################################################################
762            # reset button label, hourglass the cursor
763            self.btnGenerate.SetLabel('Listing ...')
764            self.panel.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
765           
766            wx.Yield()          # allow GUI refresh
767
768            # generate output files
769            try:
770                list_quakes(gauge_number, min_height, max_height, invallFile,
771                            gaugeFile, faultxy_filename, quakeprob_filename)       
772            except RuntimeError, msg:
773                self.error("Error in list_quakes module: %s" % msg)
774                self.panel.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
775                self.btnGenerate.SetLabel('List')
776                return
777            # put cursor back to normal, button label back to normal
778            self.panel.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
779            self.btnGenerate.SetLabel('List')
780            ####################################################################
781
782            # list result file, if any
783            if not os.path.exists(quakeprob_filename):
784                self.error("Error generating data: file '%s' not found" %
785                           quakeprob_filename)
786                return
787            in_fd = open(quakeprob_filename, 'r')
788            lines = in_fd.readlines()
789            in_fd.close()
790            hdr = lines[0].split(',')
791            for (i, h) in enumerate(hdr):
792                self.lstGenerate.InsertColumn(i, h.strip())
793            for (i, l) in enumerate(lines[1:]):
794                l = l.split(',')
795                index = self.lstGenerate.InsertStringItem(sys.maxint, l[0].strip())
796                for (ii, ll) in enumerate(l[1:]):
797                    self.lstGenerate.SetStringItem(index, ii+1, ll.strip())
798
799            column_width = FORM_WIDTH / len(hdr) - 20
800            for i in range(len(hdr)):
801                self.lstGenerate.SetColumnWidth(i, column_width)
802
803        ######
804        # Call the get_multimux function.
805        ######
806        def MultimuxClick(self, event):
807            # check we have a quake ID
808            quake_ID = self.txtQuakeID.GetValue()
809            if len(quake_ID) == 0:
810                self.error('You must enter a quake ID first')
811                return
812            try:
813                quake_ID = int(quake_ID)
814            except:
815                self.error("You must enter a Quake ID")
816                return
817
818            region_select = self.cbRegion.GetValue()
819            if len(region_select) == 0:
820                self.error('You must select a region first')
821                return
822
823            region_dir_name = None
824            for (disp, real) in Regions:
825                if disp == region_select:
826                    region_dir_name = real
827                    break
828            if region_dir_name is None:
829                self.error('INTERNAL ERROR: region_dir_name is None?!')
830                return
831
832            multimux_dir = os.path.join(BaseDirectory, region_dir_name, 'multimux')
833
834            ####################################################################
835            # clear list, set button label, hourglass the cursor
836            self.lstMultimux.ClearAll()
837            self.lstMultimux.InsertColumn(0, '')
838            self.btnMultimux.SetLabel('Multimuxing ...')
839            self.panel.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
840
841            fault_event_filename = ('%s/event_%6.6d.list' %
842                                    (ResultSubdir, quake_ID))
843            wx.Yield()          # allow GUI refresh
844
845            try:
846                get_multimux(quake_ID, multimux_dir, fault_event_filename)
847            except RuntimeError, msg:
848                self.error("Error in get_multimux module: %s" % msg)
849                self.panel.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
850                self.btnMultimux.SetLabel('Multimux')
851                return
852            # cursor and button label to normal
853            self.panel.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
854            self.btnMultimux.SetLabel('Multimux')
855            ####################################################################
856
857            # list resultant files, if any
858            if not os.path.exists(fault_event_filename):
859                self.error("Error generating data: file %s not found" %
860                           fault_event_filename)
861                return
862           
863            in_fd = open(fault_event_filename, 'r')
864            lines = in_fd.readlines()
865            in_fd.close()
866            hdr = lines[0].strip()
867            hdr = SpacesPattern.split(hdr)
868            for (i, h) in enumerate(hdr):
869                self.lstMultimux.InsertColumn(i, h)
870            for (i, l) in enumerate(lines[1:]):
871                l = l.strip()
872                l = SpacesPattern.split(l)
873                index = self.lstMultimux.InsertStringItem(sys.maxint, l[0])
874                for (ii, ll) in enumerate(l[1:]):
875                    self.lstMultimux.SetStringItem(index, ii+1, ll)
876            column_width = FORM_WIDTH / 2 - 30
877            for i in range(2):
878                self.lstMultimux.SetColumnWidth(i, column_width)
879
880            # generate the urs_filenames.py module from 'lines' data
881            # get data from lines we read previously
882            last_weight = None
883            filenames = []
884            for line in lines[1:]:
885                (_, filename, weight) = SpacesPattern.split(line)
886                weight = float(weight)
887                if last_weight:
888                    if last_weight != weight:
889                        self.error("In file '%s', inconsistent weight, "
890                                   "was %.4f, expected %.4f" % (weight, last_weight))
891                        return
892                last_weight = weight
893                filenames.append(filename)
894            # write data to the file
895            urs_filename = '%s/%s' % (ResultSubdir, URSDataFilename % quake_ID)
896            try:
897                out_fd = open(urs_filename, "w")
898            except IOError, msg:
899                self.error("Error opening output file %s': %s" %
900                           (urs_filename, msg))
901                return
902            if last_weight and filenames:
903                out_fd.write('# Data for region %s,\n'
904                             '#          hazard %d,\n'
905                             '#          min height %.2f,\n'
906                             '#          max height %.2f,\n'
907                             '#          quake ID %d\n' %
908                             (Region, GaugeNumber, MinimumHeight,
909                              MaximumHeight, quake_ID))
910                out_fd.write('# Generated on %s.\n' % time.asctime(time.localtime()))
911                out_fd.write('\n')
912                out_fd.write('import os.path\n')
913                out_fd.write('\n')
914                out_fd.write('def getInfo(base_directory):\n')
915                out_fd.write('    """\n')
916                out_fd.write('    Return the weight factor and list of URS data filenames\n')
917                out_fd.write('\n')
918                out_fd.write('        (weight_factor, [filenames]) <- get_urs_data(base_directory)\n')
919                out_fd.write('\n')
920                out_fd.write('    where base_directory is the path to the mux files.\n')
921                out_fd.write('    """\n')
922                out_fd.write('\n')
923                out_fd.write('    weight_factor = %f\n' % last_weight)
924                out_fd.write('    mux_filenames = [')
925                if filenames:
926                    for fn in filenames[:-1]:
927                        out_fd.write('"%%s" %% os.path.join(base_directory, "%s"),\n                     ' % fn)
928                    out_fd.write('"%%s" %% os.path.join(base_directory, "%s")\n                    ' % filenames[-1])
929                out_fd.write(']\n')
930                out_fd.write('\n')
931                out_fd.write('    return (weight_factor, mux_filenames)\n')
932            out_fd.close() 
933           
934        ######
935        # Call the get_grid function.
936        ######
937        def GridClick(self, event):
938            # check we have a quake ID
939            quake_ID = self.txtQuakeID.GetValue()
940            if len(quake_ID) == 0:
941                self.error('You must enter a quake ID first')
942                return
943            try:
944                quake_ID = int(quake_ID)
945            except:
946                self.error("You must enter a Quake ID")
947                return
948
949            region_select = self.cbRegion.GetValue()
950            if len(region_select) == 0:
951                self.error('You must select a region first')
952                return
953
954            region_dir_name = None
955            for (disp, real) in Regions:
956                if disp == region_select:
957                    region_dir_name = real
958                    break
959            if region_dir_name is None:
960                self.error('INTERNAL ERROR: region_dir_name is None?!')
961                return
962
963            multimux_dir = os.path.join(BaseDirectory, region_dir_name, 'multimux')
964            grid_dir = os.path.join(BaseDirectory, region_dir_name, 'static_2m')
965
966            ####################################################################
967            # clear list, set button label, hourglass the cursor
968            self.lstMultimux.ClearAll()
969            self.lstMultimux.InsertColumn(0, '')
970            self.btnGrid.SetLabel('Gridding ...')
971            self.panel.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
972
973            fault_event_filename = ('%s/event_%6.6d.list' %
974                                    (ResultSubdir, quake_ID))
975            wx.Yield()          # allow GUI refresh
976
977            try:
978                get_multimux(quake_ID, multimux_dir, fault_event_filename)
979            except RuntimeError, msg:
980                self.error("Error in get_multimux module: %s" % msg)
981                self.panel.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
982                self.btnGrid.SetLabel('Grid')
983                return
984            # cursor and button label to normal
985            self.panel.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
986            self.btnGrid.SetLabel('Grid')
987            ####################################################################
988
989            # list resultant files, if any
990            if not os.path.exists(fault_event_filename):
991                self.error("Error generating data: file %s not found" %
992                           fault_event_filename)
993                return
994
995            # read output data file
996            in_fd = open(fault_event_filename, 'r')
997            lines = in_fd.readlines()
998            in_fd.close()
999            # get header items, display them
1000            hdr = lines[0].strip()
1001            hdr = SpacesPattern.split(hdr)
1002            for (i, h) in enumerate(hdr):
1003                self.lstMultimux.InsertColumn(i, h)
1004            # display resultant data, collect filenames and weight_factor               
1005            weight_factor = None
1006            wf_error = False        # True if there was a weight_factor error
1007            filenames = []
1008            for (i, l) in enumerate(lines[1:]):
1009                l = l.strip()
1010                l = SpacesPattern.split(l)
1011                fname = l[0].replace('.grd-z-mux2', '.grd')
1012                filenames.append(fname)
1013                index = self.lstMultimux.InsertStringItem(sys.maxint, fname)
1014                for (ii, ll) in enumerate(l[1:]):
1015                    self.lstMultimux.SetStringItem(index, ii+1, ll)
1016                if weight_factor:
1017                    if weight_factor != float(l[1]):
1018                        wf_error = True
1019                else:
1020                    weight_factor = float(l[1])
1021            column_width = FORM_WIDTH / 2 - 30
1022            for i in range(2):
1023                self.lstMultimux.SetColumnWidth(i, column_width)
1024
1025            if wf_error:
1026                self.error('Weight factor is not the same for all files!')
1027                return
1028
1029            if not filenames:
1030                self.error('No grid files found!')
1031                return
1032
1033            # generate the bash script param_file.
1034            out_name = GridBashScriptName % quake_ID
1035            grid_filename = '%s/%s' % (ResultSubdir, out_name)
1036           
1037            try:
1038                out_fd = open(grid_filename, "w")
1039            except IOError, msg:
1040                self.error("Error opening output file %s': %s" %
1041                           (grid_filename, msg))
1042                return
1043
1044            out_fd.write('GENERATED=%s\n' % time.asctime(time.localtime()))
1045            out_fd.write('REGION=%s\n' % Region)
1046            out_fd.write('HAZARD_INDEX=%d\n' % GaugeNumber)
1047            out_fd.write('WAVE_HEIGHTS=[%.2f,%.2f]\n' % (MinimumHeight, MaximumHeight))
1048            out_fd.write('QUAKE_ID=%d\n' % quake_ID)
1049            out_fd.write('WEIGHT_FACTOR=%f\n' % weight_factor)
1050            out_fd.write('DATA_PATH=%s\n' % grid_dir)
1051            for fn in filenames[1:]:
1052                owning_dir = filenames[0].split('-')[0]
1053                out_fd.write('GRID=%s\n' % (os.path.join(owning_dir, fn)))
1054            out_fd.write('FAULT=%s\n' % owning_dir)
1055   
1056    app = wx.App()
1057    frame = MyFrame(None, -1, '%s %s' % (APP_NAME, APP_VERSION),
1058                    size=(FORM_WIDTH, FORM_HEIGHT))
1059    app.SetTopWindow(frame)
1060    app.MainLoop()
1061
Note: See TracBrowser for help on using the repository browser.