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

Last change on this file since 6364 was 6364, checked in by rwilson, 15 years ago

Changed version to 1.0. Better error messsage if no wxpython found.

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