source: anuga_core/source/anuga/shallow_water/data_manager.py @ 4301

Last change on this file since 4301 was 4301, checked in by duncan, 18 years ago

starting to add urs2txt stuff

File size: 154.8 KB
Line 
1"""datamanager.py - input output for AnuGA
2
3
4This module takes care of reading and writing datafiles such as topograhies,
5model output, etc
6
7
8Formats used within AnuGA:
9
10.sww: Netcdf format for storing model output f(t,x,y)
11.tms: Netcdf format for storing time series f(t)
12
13.xya: ASCII format for storing arbitrary points and associated attributes
14.pts: NetCDF format for storing arbitrary points and associated attributes
15
16.asc: ASCII format of regular DEMs as output from ArcView
17.prj: Associated ArcView file giving more meta data for asc format
18.ers: ERMapper header format of regular DEMs for ArcView
19
20.dem: NetCDF representation of regular DEM data
21
22.tsh: ASCII format for storing meshes and associated boundary and region info
23.msh: NetCDF format for storing meshes and associated boundary and region info
24
25.nc: Native ferret NetCDF format
26.geo: Houdinis ascii geometry format (?)
27
28
29A typical dataflow can be described as follows
30
31Manually created files:
32ASC, PRJ:     Digital elevation models (gridded)
33TSH:          Triangular meshes (e.g. created from anuga.pmesh)
34NC            Model outputs for use as boundary conditions (e.g from MOST)
35
36
37AUTOMATICALLY CREATED FILES:
38
39ASC, PRJ  ->  DEM  ->  PTS: Conversion of DEM's to native pts file
40
41NC -> SWW: Conversion of MOST bundary files to boundary sww
42
43PTS + TSH -> TSH with elevation: Least squares fit
44
45TSH -> SWW:  Conversion of TSH to sww viewable using Swollen
46
47TSH + Boundary SWW -> SWW: Simluation using abstract_2d_finite_volumes
48
49"""
50
51import exceptions
52class TitleValueError(exceptions.Exception): pass
53class DataMissingValuesError(exceptions.Exception): pass
54class DataFileNotOpenError(exceptions.Exception): pass
55class DataTimeError(exceptions.Exception): pass
56class DataDomainError(exceptions.Exception): pass
57
58
59
60import csv
61import os
62from struct import unpack
63import array as p_array
64#import time, os
65from os import sep, path
66
67from Numeric import concatenate, array, Float, Int, Int32, resize, sometrue, \
68     searchsorted, zeros, allclose, around, reshape, transpose, sort
69from Scientific.IO.NetCDF import NetCDFFile
70
71
72from anuga.coordinate_transforms.redfearn import redfearn, \
73     convert_from_latlon_to_utm
74from anuga.coordinate_transforms.geo_reference import Geo_reference
75from anuga.geospatial_data.geospatial_data import Geospatial_data
76from anuga.config import minimum_storable_height as default_minimum_storable_height
77from anuga.utilities.numerical_tools import ensure_numeric,  mean
78from anuga.caching.caching import myhash
79from anuga.utilities.anuga_exceptions import ANUGAError
80from anuga.shallow_water import Domain
81from anuga.abstract_2d_finite_volumes.pmesh2domain import \
82     pmesh_to_domain_instance
83   
84def make_filename(s):
85    """Transform argument string into a suitable filename
86    """
87
88    s = s.strip()
89    s = s.replace(' ', '_')
90    s = s.replace('(', '')
91    s = s.replace(')', '')
92    s = s.replace('__', '_')
93
94    return s
95
96
97def check_dir(path, verbose=None):
98    """Check that specified path exists.
99    If path does not exist it will be created if possible
100
101    USAGE:
102       checkdir(path, verbose):
103
104    ARGUMENTS:
105        path -- Directory
106        verbose -- Flag verbose output (default: None)
107
108    RETURN VALUE:
109        Verified path including trailing separator
110
111    """
112
113    import os, sys
114    import os.path
115
116    if sys.platform in ['nt', 'dos', 'win32', 'what else?']:
117        unix = 0
118    else:
119        unix = 1
120
121
122    if path[-1] != os.sep:
123        path = path + os.sep  # Add separator for directories
124
125    path = os.path.expanduser(path) # Expand ~ or ~user in pathname
126    if not (os.access(path,os.R_OK and os.W_OK) or path == ''):
127        try:
128            exitcode=os.mkdir(path)
129
130            # Change access rights if possible
131            #
132            if unix:
133                exitcode=os.system('chmod 775 '+path)
134            else:
135                pass  # FIXME: What about acces rights under Windows?
136
137            if verbose: print 'MESSAGE: Directory', path, 'created.'
138
139        except:
140            print 'WARNING: Directory', path, 'could not be created.'
141            if unix:
142                path = '/tmp/'
143            else:
144                path = 'C:'
145
146            print 'Using directory %s instead' %path
147
148    return(path)
149
150
151
152def del_dir(path):
153    """Recursively delete directory path and all its contents
154    """
155
156    import os
157
158    if os.path.isdir(path):
159        for file in os.listdir(path):
160            X = os.path.join(path, file)
161
162
163            if os.path.isdir(X) and not os.path.islink(X):
164                del_dir(X)
165            else:
166                try:
167                    os.remove(X)
168                except:
169                    print "Could not remove file %s" %X
170
171        os.rmdir(path)
172
173
174
175def create_filename(datadir, filename, format, size=None, time=None):
176
177    import os
178    #from anuga.config import data_dir
179
180    FN = check_dir(datadir) + filename
181
182    if size is not None:
183        FN += '_size%d' %size
184
185    if time is not None:
186        FN += '_time%.2f' %time
187
188    FN += '.' + format
189    return FN
190
191
192def get_files(datadir, filename, format, size):
193    """Get all file (names) with gven name, size and format
194    """
195
196    import glob
197
198    import os
199    #from anuga.config import data_dir
200
201    dir = check_dir(datadir)
202
203    pattern = dir + os.sep + filename + '_size=%d*.%s' %(size, format)
204    return glob.glob(pattern)
205
206
207
208#Generic class for storing output to e.g. visualisation or checkpointing
209class Data_format:
210    """Generic interface to data formats
211    """
212
213
214    def __init__(self, domain, extension, mode = 'w'):
215        assert mode in ['r', 'w', 'a'], '''Mode %s must be either:''' %mode +\
216                                        '''   'w' (write)'''+\
217                                        '''   'r' (read)''' +\
218                                        '''   'a' (append)'''
219
220        #Create filename
221        self.filename = create_filename(domain.get_datadir(),
222                                        domain.get_name(), extension)
223
224        #print 'F', self.filename
225        self.timestep = 0
226        self.domain = domain
227       
228
229
230        # Exclude ghosts in case this is a parallel domain
231        self.number_of_nodes = domain.number_of_full_nodes       
232        self.number_of_volumes = domain.number_of_full_triangles
233        #self.number_of_volumes = len(domain)       
234
235
236
237
238        #FIXME: Should we have a general set_precision function?
239
240
241
242#Class for storing output to e.g. visualisation
243class Data_format_sww(Data_format):
244    """Interface to native NetCDF format (.sww) for storing model output
245
246    There are two kinds of data
247
248    1: Constant data: Vertex coordinates and field values. Stored once
249    2: Variable data: Conserved quantities. Stored once per timestep.
250
251    All data is assumed to reside at vertex locations.
252    """
253
254
255    def __init__(self, domain, mode = 'w',\
256                 max_size = 2000000000,
257                 recursion = False):
258        from Scientific.IO.NetCDF import NetCDFFile
259        from Numeric import Int, Float, Float32
260
261        self.precision = Float32 #Use single precision for quantities
262        if hasattr(domain, 'max_size'):
263            self.max_size = domain.max_size #file size max is 2Gig
264        else:
265            self.max_size = max_size
266        self.recursion = recursion
267        self.mode = mode
268
269        Data_format.__init__(self, domain, 'sww', mode)
270
271        if hasattr(domain, 'minimum_storable_height'):
272            self.minimum_storable_height =  domain.minimum_storable_height
273        else:
274            self.minimum_storable_height = default_minimum_storable_height
275
276        # NetCDF file definition
277        fid = NetCDFFile(self.filename, mode)
278
279        if mode == 'w':
280
281            #Create new file
282            fid.institution = 'Geoscience Australia'
283            fid.description = 'Output from anuga.abstract_2d_finite_volumes suitable for plotting'
284
285            if domain.smooth:
286                fid.smoothing = 'Yes'
287            else:
288                fid.smoothing = 'No'
289
290            fid.order = domain.default_order
291
292            if hasattr(domain, 'texture'):
293                fid.texture = domain.texture
294            #else:
295            #    fid.texture = 'None'
296
297            #Reference point
298            #Start time in seconds since the epoch (midnight 1/1/1970)
299            #FIXME: Use Georef
300            fid.starttime = domain.starttime
301
302            # dimension definitions
303            fid.createDimension('number_of_volumes', self.number_of_volumes)
304            fid.createDimension('number_of_vertices', 3)
305
306            if domain.smooth is True:
307                #fid.createDimension('number_of_points', len(domain.vertexlist))
308                fid.createDimension('number_of_points', self.domain.number_of_full_nodes)
309
310                # FIXME(Ole): This will cause sww files for paralle domains to
311                # have ghost nodes stored (but not used by triangles).
312                # To clean this up, we have to change get_vertex_values and friends in
313                # quantity.py (but I can't be bothered right now)
314            else:
315                fid.createDimension('number_of_points', 3*self.number_of_volumes)
316
317            fid.createDimension('number_of_timesteps', None) #extensible
318
319            # variable definitions
320            fid.createVariable('x', self.precision, ('number_of_points',))
321            fid.createVariable('y', self.precision, ('number_of_points',))
322            fid.createVariable('elevation', self.precision, ('number_of_points',))
323            if domain.geo_reference is not None:
324                domain.geo_reference.write_NetCDF(fid)
325
326            #FIXME: Backwards compatibility
327            fid.createVariable('z', self.precision, ('number_of_points',))
328            #################################
329
330            fid.createVariable('volumes', Int, ('number_of_volumes',
331                                                'number_of_vertices'))
332
333            fid.createVariable('time', Float,  # Always use full precision lest two timesteps
334                                               # close to each other may appear as the same step
335                               ('number_of_timesteps',))
336
337            fid.createVariable('stage', self.precision,
338                               ('number_of_timesteps',
339                                'number_of_points'))
340
341            fid.createVariable('xmomentum', self.precision,
342                               ('number_of_timesteps',
343                                'number_of_points'))
344
345            fid.createVariable('ymomentum', self.precision,
346                               ('number_of_timesteps',
347                                'number_of_points'))
348
349        #Close
350        fid.close()
351
352
353    def store_connectivity(self):
354        """Specialisation of store_connectivity for net CDF format
355
356        Writes x,y,z coordinates of triangles constituting
357        the bed elevation.
358        """
359
360        from Scientific.IO.NetCDF import NetCDFFile
361
362        from Numeric import concatenate, Int
363
364        domain = self.domain
365
366        #Get NetCDF
367        fid = NetCDFFile(self.filename, 'a')  #Open existing file for append
368
369        # Get the variables
370        x = fid.variables['x']
371        y = fid.variables['y']
372        z = fid.variables['elevation']
373
374        volumes = fid.variables['volumes']
375
376        # Get X, Y and bed elevation Z
377        Q = domain.quantities['elevation']
378        X,Y,Z,V = Q.get_vertex_values(xy=True,
379                                      precision=self.precision)
380
381        volumes[:] = V.astype(volumes.typecode())
382        x[:] = X
383        y[:] = Y
384        z[:] = Z
385
386        #FIXME: Backwards compatibility
387        z = fid.variables['z']
388        z[:] = Z
389        ################################
390
391
392
393        #Close
394        fid.close()
395
396    def store_timestep(self, names):
397        """Store time and named quantities to file
398        """
399        from Scientific.IO.NetCDF import NetCDFFile
400        import types
401        from time import sleep
402        from os import stat
403
404        from Numeric import choose
405
406        #Get NetCDF
407        retries = 0
408        file_open = False
409        while not file_open and retries < 10:
410            try:
411                fid = NetCDFFile(self.filename, 'a') #Open existing file
412            except IOError:
413                #This could happen if someone was reading the file.
414                #In that case, wait a while and try again
415                msg = 'Warning (store_timestep): File %s could not be opened'\
416                      %self.filename
417                msg += ' - trying step %s again' %self.domain.time
418                print msg
419                retries += 1
420                sleep(1)
421            else:
422                file_open = True
423
424        if not file_open:
425            msg = 'File %s could not be opened for append' %self.filename
426            raise DataFileNotOpenError, msg
427
428
429
430        #Check to see if the file is already too big:
431        time = fid.variables['time']
432        i = len(time)+1
433        file_size = stat(self.filename)[6]
434        file_size_increase =  file_size/i
435        if file_size + file_size_increase > self.max_size*(2**self.recursion):
436            #in order to get the file name and start time correct,
437            #I change the domian.filename and domain.starttime.
438            #This is the only way to do this without changing
439            #other modules (I think).
440
441            #write a filename addon that won't break swollens reader
442            #(10.sww is bad)
443            filename_ext = '_time_%s'%self.domain.time
444            filename_ext = filename_ext.replace('.', '_')
445            #remember the old filename, then give domain a
446            #name with the extension
447            old_domain_filename = self.domain.get_name()
448            if not self.recursion:
449                self.domain.set_name(old_domain_filename+filename_ext)
450
451
452            #change the domain starttime to the current time
453            old_domain_starttime = self.domain.starttime
454            self.domain.starttime = self.domain.time
455
456            #build a new data_structure.
457            next_data_structure=\
458                Data_format_sww(self.domain, mode=self.mode,\
459                                max_size = self.max_size,\
460                                recursion = self.recursion+1)
461            if not self.recursion:
462                print '    file_size = %s'%file_size
463                print '    saving file to %s'%next_data_structure.filename
464            #set up the new data_structure
465            self.domain.writer = next_data_structure
466
467            #FIXME - could be cleaner to use domain.store_timestep etc.
468            next_data_structure.store_connectivity()
469            next_data_structure.store_timestep(names)
470            fid.sync()
471            fid.close()
472
473            #restore the old starttime and filename
474            self.domain.starttime = old_domain_starttime
475            self.domain.set_name(old_domain_filename)           
476        else:
477            self.recursion = False
478            domain = self.domain
479
480            # Get the variables
481            time = fid.variables['time']
482            stage = fid.variables['stage']
483            xmomentum = fid.variables['xmomentum']
484            ymomentum = fid.variables['ymomentum']
485            i = len(time)
486
487            #Store time
488            time[i] = self.domain.time
489
490
491            if type(names) not in [types.ListType, types.TupleType]:
492                names = [names]
493
494            for name in names:
495                # Get quantity
496                Q = domain.quantities[name]
497                A,V = Q.get_vertex_values(xy = False,
498                                          precision = self.precision)
499
500
501                #FIXME: Make this general (see below)
502                if name == 'stage':
503                    z = fid.variables['elevation']
504                    #print z[:]
505                    #print A-z[:]
506
507
508                    A = choose(A-z[:] >= self.minimum_storable_height,
509                               (z[:], A))
510                    stage[i,:] = A.astype(self.precision)
511                elif name == 'xmomentum':
512                    xmomentum[i,:] = A.astype(self.precision)
513                elif name == 'ymomentum':
514                    ymomentum[i,:] = A.astype(self.precision)
515
516                #As in....
517            #eval( name + '[i,:] = A.astype(self.precision)' )
518            #FIXME: But we need a UNIT test for that before refactoring
519
520
521
522            #Flush and close
523            fid.sync()
524            fid.close()
525
526
527
528#Class for handling checkpoints data
529class Data_format_cpt(Data_format):
530    """Interface to native NetCDF format (.cpt)
531    """
532
533
534    def __init__(self, domain, mode = 'w'):
535        from Scientific.IO.NetCDF import NetCDFFile
536        from Numeric import Int, Float, Float
537
538        self.precision = Float #Use full precision
539
540        Data_format.__init__(self, domain, 'sww', mode)
541
542
543        # NetCDF file definition
544        fid = NetCDFFile(self.filename, mode)
545
546        if mode == 'w':
547            #Create new file
548            fid.institution = 'Geoscience Australia'
549            fid.description = 'Checkpoint data'
550            #fid.smooth = domain.smooth
551            fid.order = domain.default_order
552
553            # dimension definitions
554            fid.createDimension('number_of_volumes', self.number_of_volumes)
555            fid.createDimension('number_of_vertices', 3)
556
557            #Store info at all vertices (no smoothing)
558            fid.createDimension('number_of_points', 3*self.number_of_volumes)
559            fid.createDimension('number_of_timesteps', None) #extensible
560
561            # variable definitions
562
563            #Mesh
564            fid.createVariable('x', self.precision, ('number_of_points',))
565            fid.createVariable('y', self.precision, ('number_of_points',))
566
567
568            fid.createVariable('volumes', Int, ('number_of_volumes',
569                                                'number_of_vertices'))
570
571            fid.createVariable('time', self.precision,
572                               ('number_of_timesteps',))
573
574            #Allocate space for all quantities
575            for name in domain.quantities.keys():
576                fid.createVariable(name, self.precision,
577                                   ('number_of_timesteps',
578                                    'number_of_points'))
579
580        #Close
581        fid.close()
582
583
584    def store_checkpoint(self):
585        """
586        Write x,y coordinates of triangles.
587        Write connectivity (
588        constituting
589        the bed elevation.
590        """
591
592        from Scientific.IO.NetCDF import NetCDFFile
593
594        from Numeric import concatenate
595
596        domain = self.domain
597
598        #Get NetCDF
599        fid = NetCDFFile(self.filename, 'a')  #Open existing file for append
600
601        # Get the variables
602        x = fid.variables['x']
603        y = fid.variables['y']
604
605        volumes = fid.variables['volumes']
606
607        # Get X, Y and bed elevation Z
608        Q = domain.quantities['elevation']
609        X,Y,Z,V = Q.get_vertex_values(xy=True,
610                      precision = self.precision)
611
612
613
614        x[:] = X.astype(self.precision)
615        y[:] = Y.astype(self.precision)
616        z[:] = Z.astype(self.precision)
617
618        volumes[:] = V
619
620        #Close
621        fid.close()
622
623
624    def store_timestep(self, name):
625        """Store time and named quantity to file
626        """
627        from Scientific.IO.NetCDF import NetCDFFile
628        from time import sleep
629
630        #Get NetCDF
631        retries = 0
632        file_open = False
633        while not file_open and retries < 10:
634            try:
635                fid = NetCDFFile(self.filename, 'a') #Open existing file
636            except IOError:
637                #This could happen if someone was reading the file.
638                #In that case, wait a while and try again
639                msg = 'Warning (store_timestep): File %s could not be opened'\
640                  %self.filename
641                msg += ' - trying again'
642                print msg
643                retries += 1
644                sleep(1)
645            else:
646                file_open = True
647
648        if not file_open:
649            msg = 'File %s could not be opened for append' %self.filename
650            raise DataFileNotOPenError, msg
651
652
653        domain = self.domain
654
655        # Get the variables
656        time = fid.variables['time']
657        stage = fid.variables['stage']
658        i = len(time)
659
660        #Store stage
661        time[i] = self.domain.time
662
663        # Get quantity
664        Q = domain.quantities[name]
665        A,V = Q.get_vertex_values(xy=False,
666                  precision = self.precision)
667
668        stage[i,:] = A.astype(self.precision)
669
670        #Flush and close
671        fid.sync()
672        fid.close()
673
674
675
676
677
678#Function for storing xya output
679#FIXME Not done yet for this version
680#This is obsolete.  Use geo_spatial_data instead
681class Data_format_xya(Data_format):
682    """Generic interface to data formats
683    """
684
685
686    def __init__(self, domain, mode = 'w'):
687        from Scientific.IO.NetCDF import NetCDFFile
688        from Numeric import Int, Float, Float32
689
690        self.precision = Float32 #Use single precision
691
692        Data_format.__init__(self, domain, 'xya', mode)
693
694
695
696    #FIXME -This is the old xya format
697    def store_all(self):
698        """Specialisation of store all for xya format
699
700        Writes x,y,z coordinates of triangles constituting
701        the bed elevation.
702        """
703
704        from Numeric import concatenate
705
706        domain = self.domain
707
708        fd = open(self.filename, 'w')
709
710
711        if domain.smooth is True:
712            number_of_points =  len(domain.vertexlist)
713        else:
714            number_of_points = 3*self.number_of_volumes
715
716        numVertAttrib = 3 #Three attributes is what is assumed by the xya format
717
718        fd.write(str(number_of_points) + " " + str(numVertAttrib) +\
719                 " # <vertex #> <x> <y> [attributes]" + "\n")
720
721
722        # Get X, Y, bed elevation and friction (index=0,1)
723        X,Y,A,V = domain.get_vertex_values(xy=True, value_array='field_values',
724                                           indices = (0,1), precision = self.precision)
725
726        bed_eles = A[:,0]
727        fricts = A[:,1]
728
729        # Get stage (index=0)
730        B,V = domain.get_vertex_values(xy=False, value_array='conserved_quantities',
731                                       indices = (0,), precision = self.precision)
732
733        stages = B[:,0]
734
735        #<vertex #> <x> <y> [attributes]
736        for x, y, bed_ele, stage, frict in map(None, X, Y, bed_eles,
737                                               stages, fricts):
738
739            s = '%.6f %.6f %.6f %.6f %.6f\n' %(x, y, bed_ele, stage, frict)
740            fd.write(s)
741
742        #close
743        fd.close()
744
745
746    def store_timestep(self, t, V0, V1, V2):
747        """Store time, water heights (and momentums) to file
748        """
749        pass
750
751
752#### NED is national exposure database
753   
754LAT_TITLE = 'LATITUDE'
755LONG_TITLE = 'LONGITUDE'
756X_TITLE = 'x'
757Y_TITLE = 'y'
758class Exposure_csv:
759    def __init__(self,file_name, latitude_title=LAT_TITLE,
760                 longitude_title=LONG_TITLE, is_x_y_locations=None,
761                 x_title=X_TITLE, y_title=Y_TITLE,
762                 refine_polygon=None):
763        """
764        This class is for handling the exposure csv file.
765        It reads the file in and converts the lats and longs to a geospatial
766        data object.
767        Use the methods to read and write columns.
768
769        The format of the csv files it reads is;
770           The first row is a title row.
771           comma's are the delimiters
772           each column is a 'set' of data
773
774        Feel free to use/expand it to read other csv files.
775           
776           
777        It is not for adding and deleting rows
778       
779        Can geospatial handle string attributes? It's not made for them.
780        Currently it can't load and save string att's.
781
782        So just use geospatial to hold the x, y and georef? Bad, since
783        different att's are in diferent structures.  Not so bad, the info
784        to write if the .csv file is saved is in attribute_dic
785
786        The location info is in the geospatial attribute.
787       
788       
789        """
790        self._file_name = file_name
791        self._geospatial = None #
792
793        # self._attribute_dic is a dictionary.
794        #The keys are the column titles.
795        #The values are lists of column data
796       
797        # self._title_index_dic is a dictionary.
798        #The keys are the column titles.
799        #The values are the index positions of file columns.
800        self._attribute_dic, self._title_index_dic = \
801        self._load_exposure_csv(self._file_name)
802        try:
803            #Have code here that handles caps or lower
804            lats = self._attribute_dic[latitude_title]
805            longs = self._attribute_dic[longitude_title]
806           
807        except KeyError:
808            # maybe a warning..
809            #Let's see if this works..
810            if False != is_x_y_locations:
811                is_x_y_locations = True
812            pass
813        else:
814            self._geospatial = Geospatial_data(latitudes = lats,
815                 longitudes = longs)
816
817        if is_x_y_locations is True:
818            if self._geospatial is not None:
819                pass #fixme throw an error
820            try:
821                xs = self._attribute_dic[x_title]
822                ys = self._attribute_dic[y_title]
823                points = [[float(i),float(j)] for i,j in map(None,xs,ys)]
824            except KeyError:
825                # maybe a warning..
826                msg = "Could not find location information."
827                raise TitleValueError, msg
828            else:
829                self._geospatial = Geospatial_data(data_points=points)
830           
831        # create a list of points that are in the refining_polygon
832        # described by a list of indexes representing the points
833
834    def __cmp__(self, other):
835        #print "self._attribute_dic",self._attribute_dic
836        #print "other._attribute_dic",other._attribute_dic
837        #print "self._title_index_dic", self._title_index_dic
838        #print "other._title_index_dic", other._title_index_dic
839       
840        #check that a is an instance of this class
841        if isinstance(self, type(other)):
842            result = cmp(self._attribute_dic, other._attribute_dic)
843            if result <>0:
844                return result
845            # The order of the columns is important. Therefore..
846            result = cmp(self._title_index_dic, other._title_index_dic)
847            if result <>0:
848                return result
849            for self_ls, other_ls in map(None,self._attribute_dic, \
850                    other._attribute_dic):
851                result = cmp(self._attribute_dic[self_ls],
852                             other._attribute_dic[other_ls])
853                if result <>0:
854                    return result
855            return 0
856        else:
857            return 1
858   
859    def _load_exposure_csv(self, file_name):
860        """
861        Load in the csv as a dic, title as key and column info as value, .
862        Also, create a dic, title as key and column index as value,
863        to keep track of the column order.
864        """
865        #
866        attribute_dic = {}
867        title_index_dic = {}
868        titles_stripped = [] # list of titles
869       
870        reader = csv.reader(file(file_name))
871
872        # Read in and manipulate the title info
873        titles = reader.next()
874        for i,title in enumerate(titles):
875            titles_stripped.append(title.strip())
876            title_index_dic[title.strip()] = i
877        title_count = len(titles_stripped)       
878        #print "title_index_dic",title_index_dic
879
880       
881        #create a dic of colum values, indexed by column title
882        for line in reader:
883            if len(line) <> title_count:
884                raise IOError #FIXME make this nicer
885            for i, value in enumerate(line):
886                attribute_dic.setdefault(titles_stripped[i],[]).append(value)
887           
888        return attribute_dic, title_index_dic
889
890    def get_column(self, column_name, use_refind_polygon=False):
891        """
892        Given a column name return a list of the column values
893
894        Note, the type of the values will be String!
895        do this to change a list of strings to a list of floats
896        time = [float(x) for x in time]
897       
898        Not implemented:
899        if use_refind_polygon is True, only return values in the
900        refined polygon
901        """
902        if not self._attribute_dic.has_key(column_name):
903            msg = 'Therer is  no column called %s!' %column_name
904            raise TitleValueError, msg
905        return self._attribute_dic[column_name]
906
907
908    def get_value(self, value_column_name,
909                  known_column_name,
910                  known_values,
911                  use_refind_polygon=False):
912        """
913        Do linear interpolation on the known_colum, using the known_value,
914        to return a value of the column_value_name.
915        """
916        pass
917
918
919    def get_location(self, use_refind_polygon=False):
920        """
921        Return a geospatial object which describes the
922        locations of the location file.
923
924        Note, if there is not location info, this returns None.
925       
926        Not implemented:
927        if use_refind_polygon is True, only return values in the
928        refined polygon
929        """
930        return self._geospatial
931
932    def set_column(self, column_name, column_values, overwrite=False):
933        """
934        Add a column to the 'end' (with the right most column being the end)
935        of the csv file.
936
937        Set overwrite to True if you want to overwrite a column.
938       
939        Note, in column_name white space is removed and case is not checked.
940        Precondition
941        The column_name and column_values cannot have comma's in it.
942        """
943       
944        value_row_count = \
945            len(self._attribute_dic[self._title_index_dic.keys()[0]])
946        if len(column_values) <> value_row_count: 
947            msg = 'The number of column values must equal the number of rows.'
948            raise DataMissingValuesError, msg
949       
950        if self._attribute_dic.has_key(column_name):
951            if not overwrite:
952                msg = 'Column name %s already in use!' %column_name
953                raise TitleValueError, msg
954        else:
955            # New title.  Add it to the title index.
956            self._title_index_dic[column_name] = len(self._title_index_dic)
957        self._attribute_dic[column_name] = column_values
958        #print "self._title_index_dic[column_name]",self._title_index_dic
959
960    def save(self, file_name=None):
961        """
962        Save the exposure csv file
963        """
964        if file_name is None:
965            file_name = self._file_name
966       
967        fd = open(file_name,'wb')
968        writer = csv.writer(fd)
969       
970        #Write the title to a cvs file
971        line = [None]* len(self._title_index_dic)
972        for title in self._title_index_dic.iterkeys():
973            line[self._title_index_dic[title]]= title
974        writer.writerow(line)
975       
976        # Write the values to a cvs file
977        value_row_count = \
978            len(self._attribute_dic[self._title_index_dic.keys()[0]])
979        for row_i in range(value_row_count):
980            line = [None]* len(self._title_index_dic)
981            for title in self._title_index_dic.iterkeys():
982                line[self._title_index_dic[title]]= \
983                     self._attribute_dic[title][row_i]
984            writer.writerow(line)
985
986
987#Auxiliary
988def write_obj(filename,x,y,z):
989    """Store x,y,z vectors into filename (obj format)
990       Vectors are assumed to have dimension (M,3) where
991       M corresponds to the number elements.
992       triangles are assumed to be disconnected
993
994       The three numbers in each vector correspond to three vertices,
995
996       e.g. the x coordinate of vertex 1 of element i is in x[i,1]
997
998    """
999    #print 'Writing obj to %s' % filename
1000
1001    import os.path
1002
1003    root, ext = os.path.splitext(filename)
1004    if ext == '.obj':
1005        FN = filename
1006    else:
1007        FN = filename + '.obj'
1008
1009
1010    outfile = open(FN, 'wb')
1011    outfile.write("# Triangulation as an obj file\n")
1012
1013    M, N = x.shape
1014    assert N==3  #Assuming three vertices per element
1015
1016    for i in range(M):
1017        for j in range(N):
1018            outfile.write("v %f %f %f\n" % (x[i,j],y[i,j],z[i,j]))
1019
1020    for i in range(M):
1021        base = i*N
1022        outfile.write("f %d %d %d\n" % (base+1,base+2,base+3))
1023
1024    outfile.close()
1025
1026
1027#########################################################
1028#Conversion routines
1029########################################################
1030
1031def sww2obj(basefilename, size):
1032    """Convert netcdf based data output to obj
1033    """
1034    from Scientific.IO.NetCDF import NetCDFFile
1035
1036    from Numeric import Float, zeros
1037
1038    #Get NetCDF
1039    FN = create_filename('.', basefilename, 'sww', size)
1040    print 'Reading from ', FN
1041    fid = NetCDFFile(FN, 'r')  #Open existing file for read
1042
1043
1044    # Get the variables
1045    x = fid.variables['x']
1046    y = fid.variables['y']
1047    z = fid.variables['elevation']
1048    time = fid.variables['time']
1049    stage = fid.variables['stage']
1050
1051    M = size  #Number of lines
1052    xx = zeros((M,3), Float)
1053    yy = zeros((M,3), Float)
1054    zz = zeros((M,3), Float)
1055
1056    for i in range(M):
1057        for j in range(3):
1058            xx[i,j] = x[i+j*M]
1059            yy[i,j] = y[i+j*M]
1060            zz[i,j] = z[i+j*M]
1061
1062    #Write obj for bathymetry
1063    FN = create_filename('.', basefilename, 'obj', size)
1064    write_obj(FN,xx,yy,zz)
1065
1066
1067    #Now read all the data with variable information, combine with
1068    #x,y info and store as obj
1069
1070    for k in range(len(time)):
1071        t = time[k]
1072        print 'Processing timestep %f' %t
1073
1074        for i in range(M):
1075            for j in range(3):
1076                zz[i,j] = stage[k,i+j*M]
1077
1078
1079        #Write obj for variable data
1080        #FN = create_filename(basefilename, 'obj', size, time=t)
1081        FN = create_filename('.', basefilename[:5], 'obj', size, time=t)
1082        write_obj(FN,xx,yy,zz)
1083
1084
1085def dat2obj(basefilename):
1086    """Convert line based data output to obj
1087    FIXME: Obsolete?
1088    """
1089
1090    import glob, os
1091    from anuga.config import data_dir
1092
1093
1094    #Get bathymetry and x,y's
1095    lines = open(data_dir+os.sep+basefilename+'_geometry.dat', 'r').readlines()
1096
1097    from Numeric import zeros, Float
1098
1099    M = len(lines)  #Number of lines
1100    x = zeros((M,3), Float)
1101    y = zeros((M,3), Float)
1102    z = zeros((M,3), Float)
1103
1104    ##i = 0
1105    for i, line in enumerate(lines):
1106        tokens = line.split()
1107        values = map(float,tokens)
1108
1109        for j in range(3):
1110            x[i,j] = values[j*3]
1111            y[i,j] = values[j*3+1]
1112            z[i,j] = values[j*3+2]
1113
1114        ##i += 1
1115
1116
1117    #Write obj for bathymetry
1118    write_obj(data_dir+os.sep+basefilename+'_geometry',x,y,z)
1119
1120
1121    #Now read all the data files with variable information, combine with
1122    #x,y info
1123    #and store as obj
1124
1125    files = glob.glob(data_dir+os.sep+basefilename+'*.dat')
1126
1127    for filename in files:
1128        print 'Processing %s' % filename
1129
1130        lines = open(data_dir+os.sep+filename,'r').readlines()
1131        assert len(lines) == M
1132        root, ext = os.path.splitext(filename)
1133
1134        #Get time from filename
1135        i0 = filename.find('_time=')
1136        if i0 == -1:
1137            #Skip bathymetry file
1138            continue
1139
1140        i0 += 6  #Position where time starts
1141        i1 = filename.find('.dat')
1142
1143        if i1 > i0:
1144            t = float(filename[i0:i1])
1145        else:
1146            raise DataTimeError, 'Hmmmm'
1147
1148
1149
1150        ##i = 0
1151        for i, line in enumerate(lines):
1152            tokens = line.split()
1153            values = map(float,tokens)
1154
1155            for j in range(3):
1156                z[i,j] = values[j]
1157
1158            ##i += 1
1159
1160        #Write obj for variable data
1161        write_obj(data_dir+os.sep+basefilename+'_time=%.4f' %t,x,y,z)
1162
1163
1164def filter_netcdf(filename1, filename2, first=0, last=None, step = 1):
1165    """Read netcdf filename1, pick timesteps first:step:last and save to
1166    nettcdf file filename2
1167    """
1168    from Scientific.IO.NetCDF import NetCDFFile
1169
1170    #Get NetCDF
1171    infile = NetCDFFile(filename1, 'r')  #Open existing file for read
1172    outfile = NetCDFFile(filename2, 'w')  #Open new file
1173
1174
1175    #Copy dimensions
1176    for d in infile.dimensions:
1177        outfile.createDimension(d, infile.dimensions[d])
1178
1179    for name in infile.variables:
1180        var = infile.variables[name]
1181        outfile.createVariable(name, var.typecode(), var.dimensions)
1182
1183
1184    #Copy the static variables
1185    for name in infile.variables:
1186        if name == 'time' or name == 'stage':
1187            pass
1188        else:
1189            #Copy
1190            outfile.variables[name][:] = infile.variables[name][:]
1191
1192    #Copy selected timesteps
1193    time = infile.variables['time']
1194    stage = infile.variables['stage']
1195
1196    newtime = outfile.variables['time']
1197    newstage = outfile.variables['stage']
1198
1199    if last is None:
1200        last = len(time)
1201
1202    selection = range(first, last, step)
1203    for i, j in enumerate(selection):
1204        print 'Copying timestep %d of %d (%f)' %(j, last-first, time[j])
1205        newtime[i] = time[j]
1206        newstage[i,:] = stage[j,:]
1207
1208    #Close
1209    infile.close()
1210    outfile.close()
1211
1212
1213#Get data objects
1214def get_dataobject(domain, mode='w'):
1215    """Return instance of class of given format using filename
1216    """
1217
1218    cls = eval('Data_format_%s' %domain.format)
1219    return cls(domain, mode)
1220
1221#FIXME move into geospatial.  There should only be one method that
1222# reads xya, and writes pts.
1223def xya2pts(basename_in, basename_out=None, verbose=False,
1224            #easting_min=None, easting_max=None,
1225            #northing_min=None, northing_max=None,
1226            stride = 1,
1227            attribute_name = 'elevation',
1228            z_func = None):
1229    """Read points data from ascii (.xya)
1230
1231    Example:
1232
1233              x(m)        y(m)        z(m)
1234         0.00000e+00  0.00000e+00  1.3535000e-01
1235         0.00000e+00  1.40000e-02  1.3535000e-01
1236
1237
1238
1239    Convert to NetCDF pts format which is
1240
1241    points:  (Nx2) Float array
1242    elevation: N Float array
1243
1244    Only lines that contain three numeric values are processed
1245
1246    If z_func is specified, it will be applied to the third field
1247    """
1248
1249    import os
1250    #from Scientific.IO.NetCDF import NetCDFFile
1251    from Numeric import Float, arrayrange, concatenate
1252
1253    root, ext = os.path.splitext(basename_in)
1254
1255    if ext == '': ext = '.xya'
1256
1257    #Get NetCDF
1258    infile = open(root + ext, 'r')  #Open existing xya file for read
1259
1260    if verbose: print 'Reading xya points from %s' %(root + ext)
1261
1262    points = []
1263    attribute = []
1264    for i, line in enumerate(infile.readlines()):
1265
1266        if i % stride != 0: continue
1267
1268        fields = line.split()
1269
1270        try:
1271            assert len(fields) == 3
1272        except:
1273            print 'WARNING: Line %d doesn\'t have 3 elements: %s' %(i, line)
1274
1275        try:
1276            x = float( fields[0] )
1277            y = float( fields[1] )
1278            z = float( fields[2] )
1279        except:
1280            continue
1281
1282        points.append( [x, y] )
1283
1284        if callable(z_func):
1285            attribute.append(z_func(z))
1286        else:
1287            attribute.append(z)
1288
1289
1290    #Get output file
1291    if basename_out == None:
1292        ptsname = root + '.pts'
1293    else:
1294        ptsname = basename_out + '.pts'
1295
1296    if verbose: print 'Store to NetCDF file %s' %ptsname
1297    write_ptsfile(ptsname, points, attribute, attribute_name)
1298
1299
1300
1301######Obsoleted by export_points in load_mesh
1302def write_ptsfile(ptsname, points, attribute, attribute_name = None,
1303                  zone=None, xllcorner=None, yllcorner=None):
1304    """Write points and associated attribute to pts (NetCDF) format
1305    """
1306
1307    print 'WARNING: write_ptsfile is obsolete. Use export_points from load_mesh.loadASCII instead.'
1308
1309    from Numeric import Float
1310
1311    if attribute_name is None:
1312        attribute_name = 'attribute'
1313
1314
1315    from Scientific.IO.NetCDF import NetCDFFile
1316
1317    # NetCDF file definition
1318    outfile = NetCDFFile(ptsname, 'w')
1319
1320
1321    #Create new file
1322    outfile.institution = 'Geoscience Australia'
1323    outfile.description = 'NetCDF pts format for compact and '\
1324                          'portable storage of spatial point data'
1325
1326
1327    #Georeferencing
1328    from anuga.coordinate_transforms.geo_reference import Geo_reference
1329    if zone is None:
1330        assert xllcorner is None, 'xllcorner must be None'
1331        assert yllcorner is None, 'yllcorner must be None'
1332        Geo_reference().write_NetCDF(outfile)
1333    else:
1334        Geo_reference(zone, xllcorner, yllcorner).write_NetCDF(outfile)
1335
1336
1337
1338    outfile.createDimension('number_of_points', len(points))
1339    outfile.createDimension('number_of_dimensions', 2) #This is 2d data
1340
1341    # variable definitions
1342    outfile.createVariable('points', Float, ('number_of_points',
1343                                             'number_of_dimensions'))
1344    outfile.createVariable(attribute_name, Float, ('number_of_points',))
1345
1346    # Get handles to the variables
1347    nc_points = outfile.variables['points']
1348    nc_attribute = outfile.variables[attribute_name]
1349
1350    #Store data
1351    nc_points[:, :] = points
1352    nc_attribute[:] = attribute
1353
1354    outfile.close()
1355
1356
1357def dem2pts(basename_in, basename_out=None,
1358            easting_min=None, easting_max=None,
1359            northing_min=None, northing_max=None,
1360            use_cache=False, verbose=False,):
1361    """Read Digitial Elevation model from the following NetCDF format (.dem)
1362
1363    Example:
1364
1365    ncols         3121
1366    nrows         1800
1367    xllcorner     722000
1368    yllcorner     5893000
1369    cellsize      25
1370    NODATA_value  -9999
1371    138.3698 137.4194 136.5062 135.5558 ..........
1372
1373    Convert to NetCDF pts format which is
1374
1375    points:  (Nx2) Float array
1376    elevation: N Float array
1377    """
1378
1379
1380
1381    kwargs = {'basename_out': basename_out,
1382              'easting_min': easting_min,
1383              'easting_max': easting_max,
1384              'northing_min': northing_min,
1385              'northing_max': northing_max,
1386              'verbose': verbose}
1387
1388    if use_cache is True:
1389        from caching import cache
1390        result = cache(_dem2pts, basename_in, kwargs,
1391                       dependencies = [basename_in + '.dem'],
1392                       verbose = verbose)
1393
1394    else:
1395        result = apply(_dem2pts, [basename_in], kwargs)
1396
1397    return result
1398
1399
1400def _dem2pts(basename_in, basename_out=None, verbose=False,
1401            easting_min=None, easting_max=None,
1402            northing_min=None, northing_max=None):
1403    """Read Digitial Elevation model from the following NetCDF format (.dem)
1404
1405    Internal function. See public function dem2pts for details.
1406    """
1407
1408    #FIXME: Can this be written feasibly using write_pts?
1409
1410    import os
1411    from Scientific.IO.NetCDF import NetCDFFile
1412    from Numeric import Float, zeros, reshape, sum
1413
1414    root = basename_in
1415
1416    #Get NetCDF
1417    infile = NetCDFFile(root + '.dem', 'r')  #Open existing netcdf file for read
1418
1419    if verbose: print 'Reading DEM from %s' %(root + '.dem')
1420
1421    ncols = infile.ncols[0]
1422    nrows = infile.nrows[0]
1423    xllcorner = infile.xllcorner[0]  #Easting of lower left corner
1424    yllcorner = infile.yllcorner[0]  #Northing of lower left corner
1425    cellsize = infile.cellsize[0]
1426    NODATA_value = infile.NODATA_value[0]
1427    dem_elevation = infile.variables['elevation']
1428
1429    zone = infile.zone[0]
1430    false_easting = infile.false_easting[0]
1431    false_northing = infile.false_northing[0]
1432
1433    #Text strings
1434    projection = infile.projection
1435    datum = infile.datum
1436    units = infile.units
1437
1438
1439    #Get output file
1440    if basename_out == None:
1441        ptsname = root + '.pts'
1442    else:
1443        ptsname = basename_out + '.pts'
1444
1445    if verbose: print 'Store to NetCDF file %s' %ptsname
1446    # NetCDF file definition
1447    outfile = NetCDFFile(ptsname, 'w')
1448
1449    #Create new file
1450    outfile.institution = 'Geoscience Australia'
1451    outfile.description = 'NetCDF pts format for compact and portable storage ' +\
1452                      'of spatial point data'
1453    #assign default values
1454    if easting_min is None: easting_min = xllcorner
1455    if easting_max is None: easting_max = xllcorner + ncols*cellsize
1456    if northing_min is None: northing_min = yllcorner
1457    if northing_max is None: northing_max = yllcorner + nrows*cellsize
1458
1459    #compute offsets to update georeferencing
1460    easting_offset = xllcorner - easting_min
1461    northing_offset = yllcorner - northing_min
1462
1463    #Georeferencing
1464    outfile.zone = zone
1465    outfile.xllcorner = easting_min #Easting of lower left corner
1466    outfile.yllcorner = northing_min #Northing of lower left corner
1467    outfile.false_easting = false_easting
1468    outfile.false_northing = false_northing
1469
1470    outfile.projection = projection
1471    outfile.datum = datum
1472    outfile.units = units
1473
1474
1475    #Grid info (FIXME: probably not going to be used, but heck)
1476    outfile.ncols = ncols
1477    outfile.nrows = nrows
1478
1479    dem_elevation_r = reshape(dem_elevation, (nrows, ncols))
1480    totalnopoints = nrows*ncols
1481
1482    # calculating number of NODATA_values for each row in clipped region
1483    #FIXME: use array operations to do faster
1484    nn = 0
1485    k = 0
1486    i1_0 = 0
1487    j1_0 = 0
1488    thisj = 0
1489    thisi = 0
1490    for i in range(nrows):
1491        y = (nrows-i-1)*cellsize + yllcorner
1492        for j in range(ncols):
1493            x = j*cellsize + xllcorner
1494            if easting_min <= x <= easting_max and \
1495               northing_min <= y <= northing_max:
1496                thisj = j
1497                thisi = i
1498                if dem_elevation_r[i,j] == NODATA_value: nn += 1
1499
1500                if k == 0:
1501                    i1_0 = i
1502                    j1_0 = j
1503                k += 1
1504
1505    index1 = j1_0
1506    index2 = thisj
1507
1508    # dimension definitions
1509    nrows_in_bounding_box = int(round((northing_max-northing_min)/cellsize))
1510    ncols_in_bounding_box = int(round((easting_max-easting_min)/cellsize))
1511
1512    clippednopoints = (thisi+1-i1_0)*(thisj+1-j1_0)
1513    nopoints = clippednopoints-nn
1514
1515    clipped_dem_elev = dem_elevation_r[i1_0:thisi+1,j1_0:thisj+1]
1516
1517    if verbose:
1518        print 'There are %d values in the elevation' %totalnopoints
1519        print 'There are %d values in the clipped elevation' %clippednopoints
1520        print 'There are %d NODATA_values in the clipped elevation' %nn
1521
1522    outfile.createDimension('number_of_points', nopoints)
1523    outfile.createDimension('number_of_dimensions', 2) #This is 2d data
1524
1525    # variable definitions
1526    outfile.createVariable('points', Float, ('number_of_points',
1527                                             'number_of_dimensions'))
1528    outfile.createVariable('elevation', Float, ('number_of_points',))
1529
1530    # Get handles to the variables
1531    points = outfile.variables['points']
1532    elevation = outfile.variables['elevation']
1533
1534    lenv = index2-index1+1
1535    #Store data
1536    global_index = 0
1537    #for i in range(nrows):
1538    for i in range(i1_0,thisi+1,1):
1539        if verbose and i%((nrows+10)/10)==0:
1540            print 'Processing row %d of %d' %(i, nrows)
1541
1542        lower_index = global_index
1543
1544        v = dem_elevation_r[i,index1:index2+1]
1545        no_NODATA = sum(v == NODATA_value)
1546        if no_NODATA > 0:
1547            newcols = lenv - no_NODATA #ncols_in_bounding_box - no_NODATA
1548        else:
1549            newcols = lenv #ncols_in_bounding_box
1550
1551        telev = zeros(newcols, Float)
1552        tpoints = zeros((newcols, 2), Float)
1553
1554        local_index = 0
1555
1556        y = (nrows-i-1)*cellsize + yllcorner
1557        #for j in range(ncols):
1558        for j in range(j1_0,index2+1,1):
1559
1560            x = j*cellsize + xllcorner
1561            if easting_min <= x <= easting_max and \
1562               northing_min <= y <= northing_max and \
1563               dem_elevation_r[i,j] <> NODATA_value:
1564                tpoints[local_index, :] = [x-easting_min,y-northing_min]
1565                telev[local_index] = dem_elevation_r[i, j]
1566                global_index += 1
1567                local_index += 1
1568
1569        upper_index = global_index
1570
1571        if upper_index == lower_index + newcols:
1572            points[lower_index:upper_index, :] = tpoints
1573            elevation[lower_index:upper_index] = telev
1574
1575    assert global_index == nopoints, 'index not equal to number of points'
1576
1577    infile.close()
1578    outfile.close()
1579
1580
1581
1582def _read_hecras_cross_sections(lines):
1583    """Return block of surface lines for each cross section
1584    Starts with SURFACE LINE,
1585    Ends with END CROSS-SECTION
1586    """
1587
1588    points = []
1589
1590    reading_surface = False
1591    for i, line in enumerate(lines):
1592
1593        if len(line.strip()) == 0:    #Ignore blanks
1594            continue
1595
1596        if lines[i].strip().startswith('SURFACE LINE'):
1597            reading_surface = True
1598            continue
1599
1600        if lines[i].strip().startswith('END') and reading_surface:
1601            yield points
1602            reading_surface = False
1603            points = []
1604
1605        if reading_surface:
1606            fields = line.strip().split(',')
1607            easting = float(fields[0])
1608            northing = float(fields[1])
1609            elevation = float(fields[2])
1610            points.append([easting, northing, elevation])
1611
1612
1613
1614
1615def hecras_cross_sections2pts(basename_in,
1616                              basename_out=None,
1617                              verbose=False):
1618    """Read HEC-RAS Elevation datal from the following ASCII format (.sdf)
1619
1620    Example:
1621
1622
1623# RAS export file created on Mon 15Aug2005 11:42
1624# by HEC-RAS Version 3.1.1
1625
1626BEGIN HEADER:
1627  UNITS: METRIC
1628  DTM TYPE: TIN
1629  DTM: v:\1\cit\perth_topo\river_tin
1630  STREAM LAYER: c:\local\hecras\21_02_03\up_canning_cent3d.shp
1631  CROSS-SECTION LAYER: c:\local\hecras\21_02_03\up_can_xs3d.shp
1632  MAP PROJECTION: UTM
1633  PROJECTION ZONE: 50
1634  DATUM: AGD66
1635  VERTICAL DATUM:
1636  NUMBER OF REACHES:  19
1637  NUMBER OF CROSS-SECTIONS:  14206
1638END HEADER:
1639
1640
1641Only the SURFACE LINE data of the following form will be utilised
1642
1643  CROSS-SECTION:
1644    STREAM ID:Southern-Wungong
1645    REACH ID:Southern-Wungong
1646    STATION:19040.*
1647    CUT LINE:
1648      405548.671603161 , 6438142.7594925
1649      405734.536092045 , 6438326.10404912
1650      405745.130459356 , 6438331.48627354
1651      405813.89633823 , 6438368.6272789
1652    SURFACE LINE:
1653     405548.67,   6438142.76,   35.37
1654     405552.24,   6438146.28,   35.41
1655     405554.78,   6438148.78,   35.44
1656     405555.80,   6438149.79,   35.44
1657     405559.37,   6438153.31,   35.45
1658     405560.88,   6438154.81,   35.44
1659     405562.93,   6438156.83,   35.42
1660     405566.50,   6438160.35,   35.38
1661     405566.99,   6438160.83,   35.37
1662     ...
1663   END CROSS-SECTION
1664
1665    Convert to NetCDF pts format which is
1666
1667    points:  (Nx2) Float array
1668    elevation: N Float array
1669    """
1670
1671    #FIXME: Can this be written feasibly using write_pts?
1672
1673    import os
1674    from Scientific.IO.NetCDF import NetCDFFile
1675    from Numeric import Float, zeros, reshape
1676
1677    root = basename_in
1678
1679    #Get ASCII file
1680    infile = open(root + '.sdf', 'r')  #Open SDF file for read
1681
1682    if verbose: print 'Reading DEM from %s' %(root + '.sdf')
1683
1684    lines = infile.readlines()
1685    infile.close()
1686
1687    if verbose: print 'Converting to pts format'
1688
1689    i = 0
1690    while lines[i].strip() == '' or lines[i].strip().startswith('#'):
1691        i += 1
1692
1693    assert lines[i].strip().upper() == 'BEGIN HEADER:'
1694    i += 1
1695
1696    assert lines[i].strip().upper().startswith('UNITS:')
1697    units = lines[i].strip().split()[1]
1698    i += 1
1699
1700    assert lines[i].strip().upper().startswith('DTM TYPE:')
1701    i += 1
1702
1703    assert lines[i].strip().upper().startswith('DTM:')
1704    i += 1
1705
1706    assert lines[i].strip().upper().startswith('STREAM')
1707    i += 1
1708
1709    assert lines[i].strip().upper().startswith('CROSS')
1710    i += 1
1711
1712    assert lines[i].strip().upper().startswith('MAP PROJECTION:')
1713    projection = lines[i].strip().split(':')[1]
1714    i += 1
1715
1716    assert lines[i].strip().upper().startswith('PROJECTION ZONE:')
1717    zone = int(lines[i].strip().split(':')[1])
1718    i += 1
1719
1720    assert lines[i].strip().upper().startswith('DATUM:')
1721    datum = lines[i].strip().split(':')[1]
1722    i += 1
1723
1724    assert lines[i].strip().upper().startswith('VERTICAL DATUM:')
1725    i += 1
1726
1727    assert lines[i].strip().upper().startswith('NUMBER OF REACHES:')
1728    i += 1
1729
1730    assert lines[i].strip().upper().startswith('NUMBER OF CROSS-SECTIONS:')
1731    number_of_cross_sections = int(lines[i].strip().split(':')[1])
1732    i += 1
1733
1734
1735    #Now read all points
1736    points = []
1737    elevation = []
1738    for j, entries in enumerate(_read_hecras_cross_sections(lines[i:])):
1739        for k, entry in enumerate(entries):
1740            points.append(entry[:2])
1741            elevation.append(entry[2])
1742
1743
1744    msg = 'Actual #number_of_cross_sections == %d, Reported as %d'\
1745          %(j+1, number_of_cross_sections)
1746    assert j+1 == number_of_cross_sections, msg
1747
1748    #Get output file
1749    if basename_out == None:
1750        ptsname = root + '.pts'
1751    else:
1752        ptsname = basename_out + '.pts'
1753
1754    #FIXME (DSG-ON): use loadASCII export_points_file
1755    if verbose: print 'Store to NetCDF file %s' %ptsname
1756    # NetCDF file definition
1757    outfile = NetCDFFile(ptsname, 'w')
1758
1759    #Create new file
1760    outfile.institution = 'Geoscience Australia'
1761    outfile.description = 'NetCDF pts format for compact and portable ' +\
1762                          'storage of spatial point data derived from HEC-RAS'
1763
1764    #Georeferencing
1765    outfile.zone = zone
1766    outfile.xllcorner = 0.0
1767    outfile.yllcorner = 0.0
1768    outfile.false_easting = 500000    #FIXME: Use defaults from e.g. config.py
1769    outfile.false_northing = 1000000
1770
1771    outfile.projection = projection
1772    outfile.datum = datum
1773    outfile.units = units
1774
1775
1776    outfile.createDimension('number_of_points', len(points))
1777    outfile.createDimension('number_of_dimensions', 2) #This is 2d data
1778
1779    # variable definitions
1780    outfile.createVariable('points', Float, ('number_of_points',
1781                                             'number_of_dimensions'))
1782    outfile.createVariable('elevation', Float, ('number_of_points',))
1783
1784    # Get handles to the variables
1785    outfile.variables['points'][:] = points
1786    outfile.variables['elevation'][:] = elevation
1787
1788    outfile.close()
1789
1790
1791
1792def sww2dem(basename_in, basename_out = None,
1793            quantity = None,
1794            timestep = None,
1795            reduction = None,
1796            cellsize = 10,
1797            NODATA_value = -9999,
1798            easting_min = None,
1799            easting_max = None,
1800            northing_min = None,
1801            northing_max = None,
1802            verbose = False,
1803            origin = None,
1804            datum = 'WGS84',
1805        format = 'ers'):
1806
1807    """Read SWW file and convert to Digitial Elevation model format (.asc or .ers)
1808
1809    Example (ASC):
1810
1811    ncols         3121
1812    nrows         1800
1813    xllcorner     722000
1814    yllcorner     5893000
1815    cellsize      25
1816    NODATA_value  -9999
1817    138.3698 137.4194 136.5062 135.5558 ..........
1818
1819    Also write accompanying file with same basename_in but extension .prj
1820    used to fix the UTM zone, datum, false northings and eastings.
1821
1822    The prj format is assumed to be as
1823
1824    Projection    UTM
1825    Zone          56
1826    Datum         WGS84
1827    Zunits        NO
1828    Units         METERS
1829    Spheroid      WGS84
1830    Xshift        0.0000000000
1831    Yshift        10000000.0000000000
1832    Parameters
1833
1834
1835    The parameter quantity must be the name of an existing quantity or
1836    an expression involving existing quantities. The default is
1837    'elevation'.
1838
1839    if timestep (an index) is given, output quantity at that timestep
1840
1841    if reduction is given use that to reduce quantity over all timesteps.
1842
1843    datum
1844
1845    format can be either 'asc' or 'ers'
1846    """
1847
1848    import sys
1849    from Numeric import array, Float, concatenate, NewAxis, zeros, reshape, sometrue
1850    from Numeric import array2string
1851
1852    from anuga.utilities.polygon import inside_polygon, outside_polygon, separate_points_by_polygon
1853    from anuga.abstract_2d_finite_volumes.util import apply_expression_to_dictionary
1854
1855    msg = 'Format must be either asc or ers'
1856    assert format.lower() in ['asc', 'ers'], msg
1857
1858
1859    false_easting = 500000
1860    false_northing = 10000000
1861
1862    if quantity is None:
1863        quantity = 'elevation'
1864
1865    if reduction is None:
1866        reduction = max
1867
1868    if basename_out is None:
1869        basename_out = basename_in + '_%s' %quantity
1870
1871    swwfile = basename_in + '.sww'
1872    demfile = basename_out + '.' + format
1873    # Note the use of a .ers extension is optional (write_ermapper_grid will
1874    # deal with either option
1875
1876    #Read sww file
1877    if verbose: print 'Reading from %s' %swwfile
1878    from Scientific.IO.NetCDF import NetCDFFile
1879    fid = NetCDFFile(swwfile)
1880
1881    #Get extent and reference
1882    x = fid.variables['x'][:]
1883    y = fid.variables['y'][:]
1884    volumes = fid.variables['volumes'][:]
1885    if timestep is not None:
1886        times = fid.variables['time'][timestep]
1887    else:
1888        times = fid.variables['time'][:]
1889
1890    number_of_timesteps = fid.dimensions['number_of_timesteps']
1891    number_of_points = fid.dimensions['number_of_points']
1892   
1893    if origin is None:
1894
1895        #Get geo_reference
1896        #sww files don't have to have a geo_ref
1897        try:
1898            geo_reference = Geo_reference(NetCDFObject=fid)
1899        except AttributeError, e:
1900            geo_reference = Geo_reference() #Default georef object
1901
1902        xllcorner = geo_reference.get_xllcorner()
1903        yllcorner = geo_reference.get_yllcorner()
1904        zone = geo_reference.get_zone()
1905    else:
1906        zone = origin[0]
1907        xllcorner = origin[1]
1908        yllcorner = origin[2]
1909
1910
1911
1912    #FIXME: Refactor using code from file_function.statistics
1913    #Something like print swwstats(swwname)
1914    if verbose:
1915        print '------------------------------------------------'
1916        print 'Statistics of SWW file:'
1917        print '  Name: %s' %swwfile
1918        print '  Reference:'
1919        print '    Lower left corner: [%f, %f]'\
1920              %(xllcorner, yllcorner)
1921        if timestep is not None:
1922            print '    Time: %f' %(times)
1923        else:
1924            print '    Start time: %f' %fid.starttime[0]
1925        print '  Extent:'
1926        print '    x [m] in [%f, %f], len(x) == %d'\
1927              %(min(x.flat), max(x.flat), len(x.flat))
1928        print '    y [m] in [%f, %f], len(y) == %d'\
1929              %(min(y.flat), max(y.flat), len(y.flat))
1930        if timestep is not None:
1931            print '    t [s] = %f, len(t) == %d' %(times, 1)
1932        else:
1933            print '    t [s] in [%f, %f], len(t) == %d'\
1934              %(min(times), max(times), len(times))
1935        print '  Quantities [SI units]:'
1936        for name in ['stage', 'xmomentum', 'ymomentum']:
1937            q = fid.variables[name][:].flat
1938            if timestep is not None:
1939                q = q[timestep*len(x):(timestep+1)*len(x)]
1940            if verbose: print '    %s in [%f, %f]' %(name, min(q), max(q))
1941        for name in ['elevation']:
1942            q = fid.variables[name][:].flat
1943            if verbose: print '    %s in [%f, %f]' %(name, min(q), max(q))
1944           
1945    # Get quantity and reduce if applicable
1946    if verbose: print 'Processing quantity %s' %quantity
1947
1948    # Turn NetCDF objects into Numeric arrays
1949    quantity_dict = {}
1950    for name in fid.variables.keys():
1951        quantity_dict[name] = fid.variables[name][:] 
1952
1953
1954    # Convert quantity expression to quantities found in sww file   
1955    q = apply_expression_to_dictionary(quantity, quantity_dict)
1956
1957    if len(q.shape) == 2:
1958        #q has a time component and needs to be reduced along
1959        #the temporal dimension
1960        if verbose: print 'Reducing quantity %s' %quantity
1961        q_reduced = zeros( number_of_points, Float )
1962       
1963        if timestep is not None:
1964            for k in range(number_of_points):
1965                q_reduced[k] = q[timestep,k] 
1966        else:
1967            for k in range(number_of_points):
1968                q_reduced[k] = reduction( q[:,k] )
1969
1970        q = q_reduced
1971
1972    #Post condition: Now q has dimension: number_of_points
1973    assert len(q.shape) == 1
1974    assert q.shape[0] == number_of_points
1975
1976
1977    if verbose:
1978        print 'Processed values for %s are in [%f, %f]' %(quantity, min(q), max(q))
1979
1980
1981    #Create grid and update xll/yll corner and x,y
1982
1983    #Relative extent
1984    if easting_min is None:
1985        xmin = min(x)
1986    else:
1987        xmin = easting_min - xllcorner
1988
1989    if easting_max is None:
1990        xmax = max(x)
1991    else:
1992        xmax = easting_max - xllcorner
1993
1994    if northing_min is None:
1995        ymin = min(y)
1996    else:
1997        ymin = northing_min - yllcorner
1998
1999    if northing_max is None:
2000        ymax = max(y)
2001    else:
2002        ymax = northing_max - yllcorner
2003
2004
2005
2006    if verbose: print 'Creating grid'
2007    ncols = int((xmax-xmin)/cellsize)+1
2008    nrows = int((ymax-ymin)/cellsize)+1
2009
2010
2011    #New absolute reference and coordinates
2012    newxllcorner = xmin+xllcorner
2013    newyllcorner = ymin+yllcorner
2014
2015    x = x+xllcorner-newxllcorner
2016    y = y+yllcorner-newyllcorner
2017   
2018    vertex_points = concatenate ((x[:, NewAxis] ,y[:, NewAxis]), axis = 1)
2019    assert len(vertex_points.shape) == 2
2020
2021    grid_points = zeros ( (ncols*nrows, 2), Float )
2022
2023
2024    for i in xrange(nrows):
2025        if format.lower() == 'asc':
2026            yg = i*cellsize
2027        else:
2028        #this will flip the order of the y values for ers
2029            yg = (nrows-i)*cellsize
2030
2031        for j in xrange(ncols):
2032            xg = j*cellsize
2033            k = i*ncols + j
2034
2035            grid_points[k,0] = xg
2036            grid_points[k,1] = yg
2037
2038    #Interpolate
2039    #from least_squares import Interpolation
2040    from anuga.fit_interpolate.interpolate import Interpolate
2041
2042    interp = Interpolate(vertex_points, volumes, verbose = verbose)
2043
2044    #Interpolate using quantity values
2045    if verbose: print 'Interpolating'
2046    grid_values = interp.interpolate(q, grid_points).flat
2047
2048
2049    if verbose:
2050        print 'Interpolated values are in [%f, %f]' %(min(grid_values),
2051                                                      max(grid_values))
2052
2053    #Assign NODATA_value to all points outside bounding polygon (from interpolation mesh)
2054    P = interp.mesh.get_boundary_polygon()
2055    outside_indices = outside_polygon(grid_points, P, closed=True)
2056
2057    for i in outside_indices:
2058        grid_values[i] = NODATA_value
2059
2060
2061
2062
2063    if format.lower() == 'ers':
2064        # setup ERS header information
2065        grid_values = reshape(grid_values,(nrows, ncols))
2066        header = {}
2067        header['datum'] = '"' + datum + '"'
2068        # FIXME The use of hardwired UTM and zone number needs to be made optional
2069        # FIXME Also need an automatic test for coordinate type (i.e. EN or LL)
2070        header['projection'] = '"UTM-' + str(zone) + '"'
2071        header['coordinatetype'] = 'EN'
2072        if header['coordinatetype'] == 'LL':
2073            header['longitude'] = str(newxllcorner)
2074            header['latitude'] = str(newyllcorner)
2075        elif header['coordinatetype'] == 'EN':
2076            header['eastings'] = str(newxllcorner)
2077            header['northings'] = str(newyllcorner)
2078        header['nullcellvalue'] = str(NODATA_value)
2079        header['xdimension'] = str(cellsize)
2080        header['ydimension'] = str(cellsize)
2081        header['value'] = '"' + quantity + '"'
2082        #header['celltype'] = 'IEEE8ByteReal'  #FIXME: Breaks unit test
2083
2084
2085        #Write
2086        if verbose: print 'Writing %s' %demfile
2087        import ermapper_grids
2088        ermapper_grids.write_ermapper_grid(demfile, grid_values, header)
2089
2090        fid.close()
2091    else:
2092        #Write to Ascii format
2093
2094        #Write prj file
2095        prjfile = basename_out + '.prj'
2096
2097        if verbose: print 'Writing %s' %prjfile
2098        prjid = open(prjfile, 'w')
2099        prjid.write('Projection    %s\n' %'UTM')
2100        prjid.write('Zone          %d\n' %zone)
2101        prjid.write('Datum         %s\n' %datum)
2102        prjid.write('Zunits        NO\n')
2103        prjid.write('Units         METERS\n')
2104        prjid.write('Spheroid      %s\n' %datum)
2105        prjid.write('Xshift        %d\n' %false_easting)
2106        prjid.write('Yshift        %d\n' %false_northing)
2107        prjid.write('Parameters\n')
2108        prjid.close()
2109
2110
2111
2112        if verbose: print 'Writing %s' %demfile
2113
2114        ascid = open(demfile, 'w')
2115
2116        ascid.write('ncols         %d\n' %ncols)
2117        ascid.write('nrows         %d\n' %nrows)
2118        ascid.write('xllcorner     %d\n' %newxllcorner)
2119        ascid.write('yllcorner     %d\n' %newyllcorner)
2120        ascid.write('cellsize      %f\n' %cellsize)
2121        ascid.write('NODATA_value  %d\n' %NODATA_value)
2122
2123
2124        #Get bounding polygon from mesh
2125        #P = interp.mesh.get_boundary_polygon()
2126        #inside_indices = inside_polygon(grid_points, P)
2127
2128        for i in range(nrows):
2129            if verbose and i%((nrows+10)/10)==0:
2130                print 'Doing row %d of %d' %(i, nrows)
2131
2132            base_index = (nrows-i-1)*ncols
2133
2134            slice = grid_values[base_index:base_index+ncols]
2135            s = array2string(slice, max_line_width=sys.maxint)
2136            ascid.write(s[1:-1] + '\n')
2137
2138
2139            #print
2140            #for j in range(ncols):
2141            #    index = base_index+j#
2142            #    print grid_values[index],
2143            #    ascid.write('%f ' %grid_values[index])
2144            #ascid.write('\n')
2145
2146        #Close
2147        ascid.close()
2148        fid.close()
2149
2150#Backwards compatibility
2151def sww2asc(basename_in, basename_out = None,
2152            quantity = None,
2153            timestep = None,
2154            reduction = None,
2155            cellsize = 10,
2156            verbose = False,
2157            origin = None):
2158    print 'sww2asc will soon be obsoleted - please use sww2dem'
2159    sww2dem(basename_in,
2160            basename_out = basename_out,
2161            quantity = quantity,
2162            timestep = timestep,
2163            reduction = reduction,
2164            cellsize = cellsize,
2165            verbose = verbose,
2166            origin = origin,
2167        datum = 'WGS84',
2168        format = 'asc')
2169
2170def sww2ers(basename_in, basename_out = None,
2171            quantity = None,
2172            timestep = None,
2173            reduction = None,
2174            cellsize = 10,
2175            verbose = False,
2176            origin = None,
2177            datum = 'WGS84'):
2178    print 'sww2ers will soon be obsoleted - please use sww2dem'
2179    sww2dem(basename_in,
2180            basename_out = basename_out,
2181            quantity = quantity,
2182            timestep = timestep,
2183            reduction = reduction,
2184            cellsize = cellsize,
2185            verbose = verbose,
2186            origin = origin,
2187            datum = datum,
2188            format = 'ers')
2189################################# END COMPATIBILITY ##############
2190
2191
2192
2193def sww2pts(basename_in, basename_out=None,
2194            data_points=None,
2195            quantity=None,
2196            timestep=None,
2197            reduction=None,
2198            NODATA_value=-9999,
2199            verbose=False,
2200            origin=None):
2201            #datum = 'WGS84')
2202
2203
2204    """Read SWW file and convert to interpolated values at selected points
2205
2206    The parameter quantity' must be the name of an existing quantity or
2207    an expression involving existing quantities. The default is
2208    'elevation'.
2209
2210    if timestep (an index) is given, output quantity at that timestep
2211
2212    if reduction is given use that to reduce quantity over all timesteps.
2213
2214    data_points (Nx2 array) give locations of points where quantity is to be computed.
2215   
2216    """
2217
2218    import sys
2219    from Numeric import array, Float, concatenate, NewAxis, zeros, reshape, sometrue
2220    from Numeric import array2string
2221
2222    from anuga.utilities.polygon import inside_polygon, outside_polygon, separate_points_by_polygon
2223    from anuga.abstract_2d_finite_volumes.util import apply_expression_to_dictionary
2224
2225    from anuga.geospatial_data.geospatial_data import Geospatial_data
2226
2227    if quantity is None:
2228        quantity = 'elevation'
2229
2230    if reduction is None:
2231        reduction = max
2232
2233    if basename_out is None:
2234        basename_out = basename_in + '_%s' %quantity
2235
2236    swwfile = basename_in + '.sww'
2237    ptsfile = basename_out + '.pts'
2238
2239    # Read sww file
2240    if verbose: print 'Reading from %s' %swwfile
2241    from Scientific.IO.NetCDF import NetCDFFile
2242    fid = NetCDFFile(swwfile)
2243
2244    # Get extent and reference
2245    x = fid.variables['x'][:]
2246    y = fid.variables['y'][:]
2247    volumes = fid.variables['volumes'][:]
2248
2249    number_of_timesteps = fid.dimensions['number_of_timesteps']
2250    number_of_points = fid.dimensions['number_of_points']
2251    if origin is None:
2252
2253        # Get geo_reference
2254        # sww files don't have to have a geo_ref
2255        try:
2256            geo_reference = Geo_reference(NetCDFObject=fid)
2257        except AttributeError, e:
2258            geo_reference = Geo_reference() #Default georef object
2259
2260        xllcorner = geo_reference.get_xllcorner()
2261        yllcorner = geo_reference.get_yllcorner()
2262        zone = geo_reference.get_zone()
2263    else:
2264        zone = origin[0]
2265        xllcorner = origin[1]
2266        yllcorner = origin[2]
2267
2268
2269
2270    # FIXME: Refactor using code from file_function.statistics
2271    # Something like print swwstats(swwname)
2272    if verbose:
2273        x = fid.variables['x'][:]
2274        y = fid.variables['y'][:]
2275        times = fid.variables['time'][:]
2276        print '------------------------------------------------'
2277        print 'Statistics of SWW file:'
2278        print '  Name: %s' %swwfile
2279        print '  Reference:'
2280        print '    Lower left corner: [%f, %f]'\
2281              %(xllcorner, yllcorner)
2282        print '    Start time: %f' %fid.starttime[0]
2283        print '  Extent:'
2284        print '    x [m] in [%f, %f], len(x) == %d'\
2285              %(min(x.flat), max(x.flat), len(x.flat))
2286        print '    y [m] in [%f, %f], len(y) == %d'\
2287              %(min(y.flat), max(y.flat), len(y.flat))
2288        print '    t [s] in [%f, %f], len(t) == %d'\
2289              %(min(times), max(times), len(times))
2290        print '  Quantities [SI units]:'
2291        for name in ['stage', 'xmomentum', 'ymomentum', 'elevation']:
2292            q = fid.variables[name][:].flat
2293            print '    %s in [%f, %f]' %(name, min(q), max(q))
2294
2295
2296
2297    # Get quantity and reduce if applicable
2298    if verbose: print 'Processing quantity %s' %quantity
2299
2300    # Turn NetCDF objects into Numeric arrays
2301    quantity_dict = {}
2302    for name in fid.variables.keys():
2303        quantity_dict[name] = fid.variables[name][:]
2304
2305
2306
2307    # Convert quantity expression to quantities found in sww file   
2308    q = apply_expression_to_dictionary(quantity, quantity_dict)
2309
2310
2311
2312    if len(q.shape) == 2:
2313        # q has a time component and needs to be reduced along
2314        # the temporal dimension
2315        if verbose: print 'Reducing quantity %s' %quantity
2316        q_reduced = zeros( number_of_points, Float )
2317
2318        for k in range(number_of_points):
2319            q_reduced[k] = reduction( q[:,k] )
2320
2321        q = q_reduced
2322
2323    # Post condition: Now q has dimension: number_of_points
2324    assert len(q.shape) == 1
2325    assert q.shape[0] == number_of_points
2326
2327
2328    if verbose:
2329        print 'Processed values for %s are in [%f, %f]' %(quantity, min(q), max(q))
2330
2331
2332    # Create grid and update xll/yll corner and x,y
2333    vertex_points = concatenate ((x[:, NewAxis] ,y[:, NewAxis]), axis = 1)
2334    assert len(vertex_points.shape) == 2
2335
2336    # Interpolate
2337    from anuga.fit_interpolate.interpolate import Interpolate
2338    interp = Interpolate(vertex_points, volumes, verbose = verbose)
2339
2340    # Interpolate using quantity values
2341    if verbose: print 'Interpolating'
2342    interpolated_values = interp.interpolate(q, data_points).flat
2343
2344
2345    if verbose:
2346        print 'Interpolated values are in [%f, %f]' %(min(interpolated_values),
2347                                                      max(interpolated_values))
2348
2349    # Assign NODATA_value to all points outside bounding polygon (from interpolation mesh)
2350    P = interp.mesh.get_boundary_polygon()
2351    outside_indices = outside_polygon(data_points, P, closed=True)
2352
2353    for i in outside_indices:
2354        interpolated_values[i] = NODATA_value
2355
2356
2357    # Store results   
2358    G = Geospatial_data(data_points=data_points,
2359                        attributes=interpolated_values)
2360
2361    G.export_points_file(ptsfile, absolute = True)
2362
2363    fid.close()
2364
2365
2366def convert_dem_from_ascii2netcdf(basename_in, basename_out = None,
2367                                  use_cache = False,
2368                                  verbose = False):
2369    """Read Digitial Elevation model from the following ASCII format (.asc)
2370
2371    Example:
2372
2373    ncols         3121
2374    nrows         1800
2375    xllcorner     722000
2376    yllcorner     5893000
2377    cellsize      25
2378    NODATA_value  -9999
2379    138.3698 137.4194 136.5062 135.5558 ..........
2380
2381    Convert basename_in + '.asc' to NetCDF format (.dem)
2382    mimicking the ASCII format closely.
2383
2384
2385    An accompanying file with same basename_in but extension .prj must exist
2386    and is used to fix the UTM zone, datum, false northings and eastings.
2387
2388    The prj format is assumed to be as
2389
2390    Projection    UTM
2391    Zone          56
2392    Datum         WGS84
2393    Zunits        NO
2394    Units         METERS
2395    Spheroid      WGS84
2396    Xshift        0.0000000000
2397    Yshift        10000000.0000000000
2398    Parameters
2399    """
2400
2401
2402
2403    kwargs = {'basename_out': basename_out, 'verbose': verbose}
2404
2405    if use_cache is True:
2406        from caching import cache
2407        result = cache(_convert_dem_from_ascii2netcdf, basename_in, kwargs,
2408                       dependencies = [basename_in + '.asc'],
2409                       verbose = verbose)
2410
2411    else:
2412        result = apply(_convert_dem_from_ascii2netcdf, [basename_in], kwargs)
2413
2414    return result
2415
2416
2417
2418
2419
2420
2421def _convert_dem_from_ascii2netcdf(basename_in, basename_out = None,
2422                                  verbose = False):
2423    """Read Digitial Elevation model from the following ASCII format (.asc)
2424
2425    Internal function. See public function convert_dem_from_ascii2netcdf for details.
2426    """
2427
2428    import os
2429    from Scientific.IO.NetCDF import NetCDFFile
2430    from Numeric import Float, array
2431
2432    #root, ext = os.path.splitext(basename_in)
2433    root = basename_in
2434
2435    ###########################################
2436    # Read Meta data
2437    if verbose: print 'Reading METADATA from %s' %root + '.prj'
2438    metadatafile = open(root + '.prj')
2439    metalines = metadatafile.readlines()
2440    metadatafile.close()
2441
2442    L = metalines[0].strip().split()
2443    assert L[0].strip().lower() == 'projection'
2444    projection = L[1].strip()                   #TEXT
2445
2446    L = metalines[1].strip().split()
2447    assert L[0].strip().lower() == 'zone'
2448    zone = int(L[1].strip())
2449
2450    L = metalines[2].strip().split()
2451    assert L[0].strip().lower() == 'datum'
2452    datum = L[1].strip()                        #TEXT
2453
2454    L = metalines[3].strip().split()
2455    assert L[0].strip().lower() == 'zunits'     #IGNORE
2456    zunits = L[1].strip()                       #TEXT
2457
2458    L = metalines[4].strip().split()
2459    assert L[0].strip().lower() == 'units'
2460    units = L[1].strip()                        #TEXT
2461
2462    L = metalines[5].strip().split()
2463    assert L[0].strip().lower() == 'spheroid'   #IGNORE
2464    spheroid = L[1].strip()                     #TEXT
2465
2466    L = metalines[6].strip().split()
2467    assert L[0].strip().lower() == 'xshift'
2468    false_easting = float(L[1].strip())
2469
2470    L = metalines[7].strip().split()
2471    assert L[0].strip().lower() == 'yshift'
2472    false_northing = float(L[1].strip())
2473
2474    #print false_easting, false_northing, zone, datum
2475
2476
2477    ###########################################
2478    #Read DEM data
2479
2480    datafile = open(basename_in + '.asc')
2481
2482    if verbose: print 'Reading DEM from %s' %(basename_in + '.asc')
2483    lines = datafile.readlines()
2484    datafile.close()
2485
2486    if verbose: print 'Got', len(lines), ' lines'
2487
2488    ncols = int(lines[0].split()[1].strip())
2489    nrows = int(lines[1].split()[1].strip())
2490    xllcorner = float(lines[2].split()[1].strip())
2491    yllcorner = float(lines[3].split()[1].strip())
2492    cellsize = float(lines[4].split()[1].strip())
2493    NODATA_value = int(lines[5].split()[1].strip())
2494
2495    assert len(lines) == nrows + 6
2496
2497
2498    ##########################################
2499
2500
2501    if basename_out == None:
2502        netcdfname = root + '.dem'
2503    else:
2504        netcdfname = basename_out + '.dem'
2505
2506    if verbose: print 'Store to NetCDF file %s' %netcdfname
2507    # NetCDF file definition
2508    fid = NetCDFFile(netcdfname, 'w')
2509
2510    #Create new file
2511    fid.institution = 'Geoscience Australia'
2512    fid.description = 'NetCDF DEM format for compact and portable storage ' +\
2513                      'of spatial point data'
2514
2515    fid.ncols = ncols
2516    fid.nrows = nrows
2517    fid.xllcorner = xllcorner
2518    fid.yllcorner = yllcorner
2519    fid.cellsize = cellsize
2520    fid.NODATA_value = NODATA_value
2521
2522    fid.zone = zone
2523    fid.false_easting = false_easting
2524    fid.false_northing = false_northing
2525    fid.projection = projection
2526    fid.datum = datum
2527    fid.units = units
2528
2529
2530    # dimension definitions
2531    fid.createDimension('number_of_rows', nrows)
2532    fid.createDimension('number_of_columns', ncols)
2533
2534    # variable definitions
2535    fid.createVariable('elevation', Float, ('number_of_rows',
2536                                            'number_of_columns'))
2537
2538    # Get handles to the variables
2539    elevation = fid.variables['elevation']
2540
2541    #Store data
2542    n = len(lines[6:])
2543    for i, line in enumerate(lines[6:]):
2544        fields = line.split()
2545        if verbose and i%((n+10)/10)==0:
2546            print 'Processing row %d of %d' %(i, nrows)
2547
2548        elevation[i, :] = array([float(x) for x in fields])
2549
2550    fid.close()
2551
2552
2553
2554
2555
2556def ferret2sww(basename_in, basename_out = None,
2557               verbose = False,
2558               minlat = None, maxlat = None,
2559               minlon = None, maxlon = None,
2560               mint = None, maxt = None, mean_stage = 0,
2561               origin = None, zscale = 1,
2562               fail_on_NaN = True,
2563               NaN_filler = 0,
2564               elevation = None,
2565               inverted_bathymetry = True
2566               ): #FIXME: Bathymetry should be obtained
2567                                  #from MOST somehow.
2568                                  #Alternatively from elsewhere
2569                                  #or, as a last resort,
2570                                  #specified here.
2571                                  #The value of -100 will work
2572                                  #for the Wollongong tsunami
2573                                  #scenario but is very hacky
2574    """Convert MOST and 'Ferret' NetCDF format for wave propagation to
2575    sww format native to abstract_2d_finite_volumes.
2576
2577    Specify only basename_in and read files of the form
2578    basefilename_ha.nc, basefilename_ua.nc, basefilename_va.nc containing
2579    relative height, x-velocity and y-velocity, respectively.
2580
2581    Also convert latitude and longitude to UTM. All coordinates are
2582    assumed to be given in the GDA94 datum.
2583
2584    min's and max's: If omitted - full extend is used.
2585    To include a value min may equal it, while max must exceed it.
2586    Lat and lon are assuemd to be in decimal degrees
2587
2588    origin is a 3-tuple with geo referenced
2589    UTM coordinates (zone, easting, northing)
2590
2591    nc format has values organised as HA[TIME, LATITUDE, LONGITUDE]
2592    which means that longitude is the fastest
2593    varying dimension (row major order, so to speak)
2594
2595    ferret2sww uses grid points as vertices in a triangular grid
2596    counting vertices from lower left corner upwards, then right
2597    """
2598
2599    import os
2600    from Scientific.IO.NetCDF import NetCDFFile
2601    from Numeric import Float, Int, Int32, searchsorted, zeros, array
2602    from Numeric import allclose, around
2603
2604    precision = Float
2605
2606    msg = 'Must use latitudes and longitudes for minlat, maxlon etc'
2607
2608    if minlat != None:
2609        assert -90 < minlat < 90 , msg
2610    if maxlat != None:
2611        assert -90 < maxlat < 90 , msg
2612        if minlat != None:
2613            assert maxlat > minlat
2614    if minlon != None:
2615        assert -180 < minlon < 180 , msg
2616    if maxlon != None:
2617        assert -180 < maxlon < 180 , msg
2618        if minlon != None:
2619            assert maxlon > minlon
2620       
2621
2622
2623    #Get NetCDF data
2624    if verbose: print 'Reading files %s_*.nc' %basename_in
2625    #print "basename_in + '_ha.nc'",basename_in + '_ha.nc'
2626    file_h = NetCDFFile(basename_in + '_ha.nc', 'r') #Wave amplitude (cm)
2627    file_u = NetCDFFile(basename_in + '_ua.nc', 'r') #Velocity (x) (cm/s)
2628    file_v = NetCDFFile(basename_in + '_va.nc', 'r') #Velocity (y) (cm/s)
2629    file_e = NetCDFFile(basename_in + '_e.nc', 'r')  #Elevation (z) (m)
2630
2631    if basename_out is None:
2632        swwname = basename_in + '.sww'
2633    else:
2634        swwname = basename_out + '.sww'
2635
2636#    Get dimensions of file_h
2637    for dimension in file_h.dimensions.keys():
2638        if dimension[:3] == 'LON':
2639            dim_h_longitude = dimension
2640        if dimension[:3] == 'LAT':
2641            dim_h_latitude = dimension
2642        if dimension[:4] == 'TIME':
2643            dim_h_time = dimension
2644
2645#    print 'long:', dim_h_longitude
2646#    print 'lats:', dim_h_latitude
2647#    print 'times:', dim_h_time
2648
2649    times = file_h.variables[dim_h_time]
2650    latitudes = file_h.variables[dim_h_latitude]
2651    longitudes = file_h.variables[dim_h_longitude]
2652   
2653# get dimensions for file_e
2654    for dimension in file_e.dimensions.keys():
2655        if dimension[:3] == 'LON':
2656            dim_e_longitude = dimension
2657        if dimension[:3] == 'LAT':
2658            dim_e_latitude = dimension
2659
2660# get dimensions for file_u
2661    for dimension in file_u.dimensions.keys():
2662        if dimension[:3] == 'LON':
2663            dim_u_longitude = dimension
2664        if dimension[:3] == 'LAT':
2665            dim_u_latitude = dimension
2666        if dimension[:4] == 'TIME':
2667            dim_u_time = dimension
2668           
2669# get dimensions for file_v
2670    for dimension in file_v.dimensions.keys():
2671        if dimension[:3] == 'LON':
2672            dim_v_longitude = dimension
2673        if dimension[:3] == 'LAT':
2674            dim_v_latitude = dimension
2675        if dimension[:4] == 'TIME':
2676            dim_v_time = dimension
2677
2678
2679    #Precision used by most for lat/lon is 4 or 5 decimals
2680    e_lat = around(file_e.variables[dim_e_latitude][:], 5)
2681    e_lon = around(file_e.variables[dim_e_longitude][:], 5)
2682
2683    #Check that files are compatible
2684    assert allclose(latitudes, file_u.variables[dim_u_latitude])
2685    assert allclose(latitudes, file_v.variables[dim_v_latitude])
2686    assert allclose(latitudes, e_lat)
2687
2688    assert allclose(longitudes, file_u.variables[dim_u_longitude])
2689    assert allclose(longitudes, file_v.variables[dim_v_longitude])
2690    assert allclose(longitudes, e_lon)
2691
2692    if mint == None:
2693        jmin = 0
2694    else:
2695        jmin = searchsorted(times, mint)
2696       
2697    if maxt == None:
2698        jmax=len(times)
2699    else:
2700        jmax = searchsorted(times, maxt)
2701
2702    #print "latitudes[:]",latitudes[:]
2703    #print "longitudes[:]",longitudes [:]
2704    kmin, kmax, lmin, lmax = _get_min_max_indexes(latitudes[:],
2705                                                  longitudes[:],
2706                                                 minlat, maxlat,
2707                                                 minlon, maxlon)
2708
2709
2710#    print' j', jmin, jmax
2711    times = times[jmin:jmax]
2712    latitudes = latitudes[kmin:kmax]
2713    longitudes = longitudes[lmin:lmax]
2714
2715    #print "latitudes[:]",latitudes[:]
2716    #print "longitudes[:]",longitudes [:]
2717
2718    if verbose: print 'cropping'
2719    zname = 'ELEVATION'
2720
2721    amplitudes = file_h.variables['HA'][jmin:jmax, kmin:kmax, lmin:lmax]
2722    uspeed = file_u.variables['UA'][jmin:jmax, kmin:kmax, lmin:lmax] #Lon
2723    vspeed = file_v.variables['VA'][jmin:jmax, kmin:kmax, lmin:lmax] #Lat
2724    elevations = file_e.variables[zname][kmin:kmax, lmin:lmax]
2725
2726    #    if latitudes2[0]==latitudes[0] and latitudes2[-1]==latitudes[-1]:
2727    #        elevations = file_e.variables['ELEVATION'][kmin:kmax, lmin:lmax]
2728    #    elif latitudes2[0]==latitudes[-1] and latitudes2[-1]==latitudes[0]:
2729    #        from Numeric import asarray
2730    #        elevations=elevations.tolist()
2731    #        elevations.reverse()
2732    #        elevations=asarray(elevations)
2733    #    else:
2734    #        from Numeric import asarray
2735    #        elevations=elevations.tolist()
2736    #        elevations.reverse()
2737    #        elevations=asarray(elevations)
2738    #        'print hmmm'
2739
2740
2741
2742    #Get missing values
2743    nan_ha = file_h.variables['HA'].missing_value[0]
2744    nan_ua = file_u.variables['UA'].missing_value[0]
2745    nan_va = file_v.variables['VA'].missing_value[0]
2746    if hasattr(file_e.variables[zname],'missing_value'):
2747        nan_e  = file_e.variables[zname].missing_value[0]
2748    else:
2749        nan_e = None
2750
2751    #Cleanup
2752    from Numeric import sometrue
2753
2754    missing = (amplitudes == nan_ha)
2755    if sometrue (missing):
2756        if fail_on_NaN:
2757            msg = 'NetCDFFile %s contains missing values'\
2758                  %(basename_in+'_ha.nc')
2759            raise DataMissingValuesError, msg
2760        else:
2761            amplitudes = amplitudes*(missing==0) + missing*NaN_filler
2762
2763    missing = (uspeed == nan_ua)
2764    if sometrue (missing):
2765        if fail_on_NaN:
2766            msg = 'NetCDFFile %s contains missing values'\
2767                  %(basename_in+'_ua.nc')
2768            raise DataMissingValuesError, msg
2769        else:
2770            uspeed = uspeed*(missing==0) + missing*NaN_filler
2771
2772    missing = (vspeed == nan_va)
2773    if sometrue (missing):
2774        if fail_on_NaN:
2775            msg = 'NetCDFFile %s contains missing values'\
2776                  %(basename_in+'_va.nc')
2777            raise DataMissingValuesError, msg
2778        else:
2779            vspeed = vspeed*(missing==0) + missing*NaN_filler
2780
2781
2782    missing = (elevations == nan_e)
2783    if sometrue (missing):
2784        if fail_on_NaN:
2785            msg = 'NetCDFFile %s contains missing values'\
2786                  %(basename_in+'_e.nc')
2787            raise DataMissingValuesError, msg
2788        else:
2789            elevations = elevations*(missing==0) + missing*NaN_filler
2790
2791    #######
2792
2793
2794
2795    number_of_times = times.shape[0]
2796    number_of_latitudes = latitudes.shape[0]
2797    number_of_longitudes = longitudes.shape[0]
2798
2799    assert amplitudes.shape[0] == number_of_times
2800    assert amplitudes.shape[1] == number_of_latitudes
2801    assert amplitudes.shape[2] == number_of_longitudes
2802
2803    if verbose:
2804        print '------------------------------------------------'
2805        print 'Statistics:'
2806        print '  Extent (lat/lon):'
2807        print '    lat in [%f, %f], len(lat) == %d'\
2808              %(min(latitudes.flat), max(latitudes.flat),
2809                len(latitudes.flat))
2810        print '    lon in [%f, %f], len(lon) == %d'\
2811              %(min(longitudes.flat), max(longitudes.flat),
2812                len(longitudes.flat))
2813        print '    t in [%f, %f], len(t) == %d'\
2814              %(min(times.flat), max(times.flat), len(times.flat))
2815
2816        q = amplitudes.flat
2817        name = 'Amplitudes (ha) [cm]'
2818        print %s in [%f, %f]' %(name, min(q), max(q))
2819
2820        q = uspeed.flat
2821        name = 'Speeds (ua) [cm/s]'
2822        print %s in [%f, %f]' %(name, min(q), max(q))
2823
2824        q = vspeed.flat
2825        name = 'Speeds (va) [cm/s]'
2826        print %s in [%f, %f]' %(name, min(q), max(q))
2827
2828        q = elevations.flat
2829        name = 'Elevations (e) [m]'
2830        print %s in [%f, %f]' %(name, min(q), max(q))
2831
2832
2833    #print number_of_latitudes, number_of_longitudes
2834    number_of_points = number_of_latitudes*number_of_longitudes
2835    number_of_volumes = (number_of_latitudes-1)*(number_of_longitudes-1)*2
2836
2837
2838    file_h.close()
2839    file_u.close()
2840    file_v.close()
2841    file_e.close()
2842
2843
2844    # NetCDF file definition
2845    outfile = NetCDFFile(swwname, 'w')
2846
2847    #Create new file
2848    outfile.institution = 'Geoscience Australia'
2849    outfile.description = 'Converted from Ferret files: %s, %s, %s, %s'\
2850                          %(basename_in + '_ha.nc',
2851                            basename_in + '_ua.nc',
2852                            basename_in + '_va.nc',
2853                            basename_in + '_e.nc')
2854
2855
2856    #For sww compatibility
2857    outfile.smoothing = 'Yes'
2858    outfile.order = 1
2859
2860    #Start time in seconds since the epoch (midnight 1/1/1970)
2861    outfile.starttime = starttime = times[0]
2862    times = times - starttime  #Store relative times
2863
2864    # dimension definitions
2865    outfile.createDimension('number_of_volumes', number_of_volumes)
2866
2867    outfile.createDimension('number_of_vertices', 3)
2868    outfile.createDimension('number_of_points', number_of_points)
2869
2870
2871    #outfile.createDimension('number_of_timesteps', len(times))
2872    outfile.createDimension('number_of_timesteps', len(times))
2873
2874    # variable definitions
2875    outfile.createVariable('x', precision, ('number_of_points',))
2876    outfile.createVariable('y', precision, ('number_of_points',))
2877    outfile.createVariable('elevation', precision, ('number_of_points',))
2878
2879    #FIXME: Backwards compatibility
2880    outfile.createVariable('z', precision, ('number_of_points',))
2881    #################################
2882
2883    outfile.createVariable('volumes', Int, ('number_of_volumes',
2884                                            'number_of_vertices'))
2885
2886    outfile.createVariable('time', precision,
2887                           ('number_of_timesteps',))
2888
2889    outfile.createVariable('stage', precision,
2890                           ('number_of_timesteps',
2891                            'number_of_points'))
2892
2893    outfile.createVariable('xmomentum', precision,
2894                           ('number_of_timesteps',
2895                            'number_of_points'))
2896
2897    outfile.createVariable('ymomentum', precision,
2898                           ('number_of_timesteps',
2899                            'number_of_points'))
2900
2901
2902    #Store
2903    from anuga.coordinate_transforms.redfearn import redfearn
2904    x = zeros(number_of_points, Float)  #Easting
2905    y = zeros(number_of_points, Float)  #Northing
2906
2907
2908    if verbose: print 'Making triangular grid'
2909    #Check zone boundaries
2910    refzone, _, _ = redfearn(latitudes[0],longitudes[0])
2911
2912    vertices = {}
2913    i = 0
2914    for k, lat in enumerate(latitudes):       #Y direction
2915        for l, lon in enumerate(longitudes):  #X direction
2916
2917            vertices[l,k] = i
2918
2919            zone, easting, northing = redfearn(lat,lon)
2920
2921            msg = 'Zone boundary crossed at longitude =', lon
2922            #assert zone == refzone, msg
2923            #print '%7.2f %7.2f %8.2f %8.2f' %(lon, lat, easting, northing)
2924            x[i] = easting
2925            y[i] = northing
2926            i += 1
2927
2928
2929    #Construct 2 triangles per 'rectangular' element
2930    volumes = []
2931    for l in range(number_of_longitudes-1):    #X direction
2932        for k in range(number_of_latitudes-1): #Y direction
2933            v1 = vertices[l,k+1]
2934            v2 = vertices[l,k]
2935            v3 = vertices[l+1,k+1]
2936            v4 = vertices[l+1,k]
2937
2938            volumes.append([v1,v2,v3]) #Upper element
2939            volumes.append([v4,v3,v2]) #Lower element
2940
2941    volumes = array(volumes)
2942
2943    if origin == None:
2944        zone = refzone
2945        xllcorner = min(x)
2946        yllcorner = min(y)
2947    else:
2948        zone = origin[0]
2949        xllcorner = origin[1]
2950        yllcorner = origin[2]
2951
2952
2953    outfile.xllcorner = xllcorner
2954    outfile.yllcorner = yllcorner
2955    outfile.zone = zone
2956
2957
2958    if elevation is not None:
2959        z = elevation
2960    else:
2961        if inverted_bathymetry:
2962            z = -1*elevations
2963        else:
2964            z = elevations
2965    #FIXME: z should be obtained from MOST and passed in here
2966
2967    from Numeric import resize
2968    z = resize(z,outfile.variables['z'][:].shape)
2969    outfile.variables['x'][:] = x - xllcorner
2970    outfile.variables['y'][:] = y - yllcorner
2971    outfile.variables['z'][:] = z             #FIXME HACK for bacwards compat.
2972    outfile.variables['elevation'][:] = z
2973    outfile.variables['time'][:] = times   #Store time relative
2974    outfile.variables['volumes'][:] = volumes.astype(Int32) #For Opteron 64
2975
2976
2977
2978    #Time stepping
2979    stage = outfile.variables['stage']
2980    xmomentum = outfile.variables['xmomentum']
2981    ymomentum = outfile.variables['ymomentum']
2982
2983    if verbose: print 'Converting quantities'
2984    n = len(times)
2985    for j in range(n):
2986        if verbose and j%((n+10)/10)==0: print '  Doing %d of %d' %(j, n)
2987        i = 0
2988        for k in range(number_of_latitudes):      #Y direction
2989            for l in range(number_of_longitudes): #X direction
2990                w = zscale*amplitudes[j,k,l]/100 + mean_stage
2991                stage[j,i] = w
2992                h = w - z[i]
2993                xmomentum[j,i] = uspeed[j,k,l]/100*h
2994                ymomentum[j,i] = vspeed[j,k,l]/100*h
2995                i += 1
2996
2997    #outfile.close()
2998
2999    #FIXME: Refactor using code from file_function.statistics
3000    #Something like print swwstats(swwname)
3001    if verbose:
3002        x = outfile.variables['x'][:]
3003        y = outfile.variables['y'][:]
3004        times = outfile.variables['time'][:]
3005        print '------------------------------------------------'
3006        print 'Statistics of output file:'
3007        print '  Name: %s' %swwname
3008        print '  Reference:'
3009        print '    Lower left corner: [%f, %f]'\
3010              %(xllcorner, yllcorner)
3011        print '    Start time: %f' %starttime
3012        print '  Extent:'
3013        print '    x [m] in [%f, %f], len(x) == %d'\
3014              %(min(x.flat), max(x.flat), len(x.flat))
3015        print '    y [m] in [%f, %f], len(y) == %d'\
3016              %(min(y.flat), max(y.flat), len(y.flat))
3017        print '    t [s] in [%f, %f], len(t) == %d'\
3018              %(min(times), max(times), len(times))
3019        print '  Quantities [SI units]:'
3020        for name in ['stage', 'xmomentum', 'ymomentum', 'elevation']:
3021            q = outfile.variables[name][:].flat
3022            print '    %s in [%f, %f]' %(name, min(q), max(q))
3023
3024
3025
3026    outfile.close()
3027
3028
3029
3030
3031
3032def timefile2netcdf(filename, quantity_names = None):
3033    """Template for converting typical text files with time series to
3034    NetCDF tms file.
3035
3036
3037    The file format is assumed to be either two fields separated by a comma:
3038
3039        time [DD/MM/YY hh:mm:ss], value0 value1 value2 ...
3040
3041    E.g
3042
3043      31/08/04 00:00:00, 1.328223 0 0
3044      31/08/04 00:15:00, 1.292912 0 0
3045
3046    will provide a time dependent function f(t) with three attributes
3047
3048    filename is assumed to be the rootname with extenisons .txt and .sww
3049    """
3050
3051    import time, calendar
3052    from Numeric import array
3053    from anuga.config import time_format
3054    from anuga.utilities.numerical_tools import ensure_numeric
3055
3056
3057
3058    fid = open(filename + '.txt')
3059    line = fid.readline()
3060    fid.close()
3061
3062    fields = line.split(',')
3063    msg = 'File %s must have the format date, value0 value1 value2 ...'
3064    assert len(fields) == 2, msg
3065
3066    try:
3067        starttime = calendar.timegm(time.strptime(fields[0], time_format))
3068    except ValueError:
3069        msg = 'First field in file %s must be' %filename
3070        msg += ' date-time with format %s.\n' %time_format
3071        msg += 'I got %s instead.' %fields[0]
3072        raise DataTimeError, msg
3073
3074
3075    #Split values
3076    values = []
3077    for value in fields[1].split():
3078        values.append(float(value))
3079
3080    q = ensure_numeric(values)
3081
3082    msg = 'ERROR: File must contain at least one independent value'
3083    assert len(q.shape) == 1, msg
3084
3085
3086
3087    #Read times proper
3088    from Numeric import zeros, Float, alltrue
3089    from anuga.config import time_format
3090    import time, calendar
3091
3092    fid = open(filename + '.txt')
3093    lines = fid.readlines()
3094    fid.close()
3095
3096    N = len(lines)
3097    d = len(q)
3098
3099    T = zeros(N, Float)       #Time
3100    Q = zeros((N, d), Float)  #Values
3101
3102    for i, line in enumerate(lines):
3103        fields = line.split(',')
3104        realtime = calendar.timegm(time.strptime(fields[0], time_format))
3105        T[i] = realtime - starttime
3106
3107        for j, value in enumerate(fields[1].split()):
3108            Q[i, j] = float(value)
3109
3110    msg = 'File %s must list time as a monotonuosly ' %filename
3111    msg += 'increasing sequence'
3112    assert alltrue( T[1:] - T[:-1] > 0 ), msg
3113
3114    #Create NetCDF file
3115    from Scientific.IO.NetCDF import NetCDFFile
3116
3117    fid = NetCDFFile(filename + '.tms', 'w')
3118
3119
3120    fid.institution = 'Geoscience Australia'
3121    fid.description = 'Time series'
3122
3123
3124    #Reference point
3125    #Start time in seconds since the epoch (midnight 1/1/1970)
3126    #FIXME: Use Georef
3127    fid.starttime = starttime
3128
3129
3130    # dimension definitions
3131    #fid.createDimension('number_of_volumes', self.number_of_volumes)
3132    #fid.createDimension('number_of_vertices', 3)
3133
3134
3135    fid.createDimension('number_of_timesteps', len(T))
3136
3137    fid.createVariable('time', Float, ('number_of_timesteps',))
3138
3139    fid.variables['time'][:] = T
3140
3141    for i in range(Q.shape[1]):
3142        try:
3143            name = quantity_names[i]
3144        except:
3145            name = 'Attribute%d'%i
3146
3147        fid.createVariable(name, Float, ('number_of_timesteps',))
3148        fid.variables[name][:] = Q[:,i]
3149
3150    fid.close()
3151
3152
3153def extent_sww(file_name):
3154    """
3155    Read in an sww file.
3156
3157    Input;
3158    file_name - the sww file
3159
3160    Output;
3161    z - Vector of bed elevation
3162    volumes - Array.  Each row has 3 values, representing
3163    the vertices that define the volume
3164    time - Vector of the times where there is stage information
3165    stage - array with respect to time and vertices (x,y)
3166    """
3167
3168
3169    from Scientific.IO.NetCDF import NetCDFFile
3170
3171    #Check contents
3172    #Get NetCDF
3173    fid = NetCDFFile(file_name, 'r')
3174
3175    # Get the variables
3176    x = fid.variables['x'][:]
3177    y = fid.variables['y'][:]
3178    stage = fid.variables['stage'][:]
3179    #print "stage",stage
3180    #print "stage.shap",stage.shape
3181    #print "min(stage.flat), mpythonax(stage.flat)",min(stage.flat), max(stage.flat)
3182    #print "min(stage)",min(stage)
3183
3184    fid.close()
3185
3186    return [min(x),max(x),min(y),max(y),min(stage.flat),max(stage.flat)]
3187
3188
3189def sww2domain(filename,boundary=None,t=None,\
3190               fail_if_NaN=True,NaN_filler=0\
3191               ,verbose = False,very_verbose = False):
3192    """
3193    Usage: domain = sww2domain('file.sww',t=time (default = last time in file))
3194
3195    Boundary is not recommended if domain.smooth is not selected, as it
3196    uses unique coordinates, but not unique boundaries. This means that
3197    the boundary file will not be compatable with the coordinates, and will
3198    give a different final boundary, or crash.
3199    """
3200    NaN=9.969209968386869e+036
3201    #initialise NaN.
3202
3203    from Scientific.IO.NetCDF import NetCDFFile
3204    from shallow_water import Domain
3205    from Numeric import asarray, transpose, resize
3206
3207    if verbose: print 'Reading from ', filename
3208    fid = NetCDFFile(filename, 'r')    #Open existing file for read
3209    time = fid.variables['time']       #Timesteps
3210    if t is None:
3211        t = time[-1]
3212    time_interp = get_time_interp(time,t)
3213
3214    # Get the variables as Numeric arrays
3215    x = fid.variables['x'][:]             #x-coordinates of vertices
3216    y = fid.variables['y'][:]             #y-coordinates of vertices
3217    elevation = fid.variables['elevation']     #Elevation
3218    stage = fid.variables['stage']     #Water level
3219    xmomentum = fid.variables['xmomentum']   #Momentum in the x-direction
3220    ymomentum = fid.variables['ymomentum']   #Momentum in the y-direction
3221
3222    starttime = fid.starttime[0]
3223    volumes = fid.variables['volumes'][:] #Connectivity
3224    coordinates=transpose(asarray([x.tolist(),y.tolist()]))
3225
3226    conserved_quantities = []
3227    interpolated_quantities = {}
3228    other_quantities = []
3229
3230    # get geo_reference
3231    #sww files don't have to have a geo_ref
3232    try:
3233        geo_reference = Geo_reference(NetCDFObject=fid)
3234    except: #AttributeError, e:
3235        geo_reference = None
3236
3237    if verbose: print '    getting quantities'
3238    for quantity in fid.variables.keys():
3239        dimensions = fid.variables[quantity].dimensions
3240        if 'number_of_timesteps' in dimensions:
3241            conserved_quantities.append(quantity)
3242            interpolated_quantities[quantity]=\
3243                  interpolated_quantity(fid.variables[quantity][:],time_interp)
3244        else: other_quantities.append(quantity)
3245
3246    other_quantities.remove('x')
3247    other_quantities.remove('y')
3248    other_quantities.remove('z')
3249    other_quantities.remove('volumes')
3250
3251    conserved_quantities.remove('time')
3252
3253    if verbose: print '    building domain'
3254    #    From domain.Domain:
3255    #    domain = Domain(coordinates, volumes,\
3256    #                    conserved_quantities = conserved_quantities,\
3257    #                    other_quantities = other_quantities,zone=zone,\
3258    #                    xllcorner=xllcorner, yllcorner=yllcorner)
3259
3260    #   From shallow_water.Domain:
3261    coordinates=coordinates.tolist()
3262    volumes=volumes.tolist()
3263    #FIXME:should this be in mesh?(peter row)
3264    if fid.smoothing == 'Yes': unique = False
3265    else: unique = True
3266    if unique:
3267        coordinates,volumes,boundary=weed(coordinates,volumes,boundary)
3268
3269
3270    try:
3271        domain = Domain(coordinates, volumes, boundary)
3272    except AssertionError, e:
3273        fid.close()
3274        msg = 'Domain could not be created: %s. Perhaps use "fail_if_NaN=False and NaN_filler = ..."' %e
3275        raise DataDomainError, msg
3276
3277    if not boundary is None:
3278        domain.boundary = boundary
3279
3280    domain.geo_reference = geo_reference
3281
3282    domain.starttime=float(starttime)+float(t)
3283    domain.time=0.0
3284
3285    for quantity in other_quantities:
3286        try:
3287            NaN = fid.variables[quantity].missing_value
3288        except:
3289            pass #quantity has no missing_value number
3290        X = fid.variables[quantity][:]
3291        if very_verbose:
3292            print '       ',quantity
3293            print '        NaN =',NaN
3294            print '        max(X)'
3295            print '       ',max(X)
3296            print '        max(X)==NaN'
3297            print '       ',max(X)==NaN
3298            print ''
3299        if (max(X)==NaN) or (min(X)==NaN):
3300            if fail_if_NaN:
3301                msg = 'quantity "%s" contains no_data entry'%quantity
3302                raise DataMissingValuesError, msg
3303            else:
3304                data = (X<>NaN)
3305                X = (X*data)+(data==0)*NaN_filler
3306        if unique:
3307            X = resize(X,(len(X)/3,3))
3308        domain.set_quantity(quantity,X)
3309    #
3310    for quantity in conserved_quantities:
3311        try:
3312            NaN = fid.variables[quantity].missing_value
3313        except:
3314            pass #quantity has no missing_value number
3315        X = interpolated_quantities[quantity]
3316        if very_verbose:
3317            print '       ',quantity
3318            print '        NaN =',NaN
3319            print '        max(X)'
3320            print '       ',max(X)
3321            print '        max(X)==NaN'
3322            print '       ',max(X)==NaN
3323            print ''
3324        if (max(X)==NaN) or (min(X)==NaN):
3325            if fail_if_NaN:
3326                msg = 'quantity "%s" contains no_data entry'%quantity
3327                raise DataMissingValuesError, msg
3328            else:
3329                data = (X<>NaN)
3330                X = (X*data)+(data==0)*NaN_filler
3331        if unique:
3332            X = resize(X,(X.shape[0]/3,3))
3333        domain.set_quantity(quantity,X)
3334
3335    fid.close()
3336    return domain
3337
3338def interpolated_quantity(saved_quantity,time_interp):
3339
3340    #given an index and ratio, interpolate quantity with respect to time.
3341    index,ratio = time_interp
3342    Q = saved_quantity
3343    if ratio > 0:
3344        q = (1-ratio)*Q[index]+ ratio*Q[index+1]
3345    else:
3346        q = Q[index]
3347    #Return vector of interpolated values
3348    return q
3349
3350def get_time_interp(time,t=None):
3351    #Finds the ratio and index for time interpolation.
3352    #It is borrowed from previous abstract_2d_finite_volumes code.
3353    if t is None:
3354        t=time[-1]
3355        index = -1
3356        ratio = 0.
3357    else:
3358        T = time
3359        tau = t
3360        index=0
3361        msg = 'Time interval derived from file %s [%s:%s]'\
3362            %('FIXMEfilename', T[0], T[-1])
3363        msg += ' does not match model time: %s' %tau
3364        if tau < time[0]: raise DataTimeError, msg
3365        if tau > time[-1]: raise DataTimeError, msg
3366        while tau > time[index]: index += 1
3367        while tau < time[index]: index -= 1
3368        if tau == time[index]:
3369            #Protect against case where tau == time[-1] (last time)
3370            # - also works in general when tau == time[i]
3371            ratio = 0
3372        else:
3373            #t is now between index and index+1
3374            ratio = (tau - time[index])/(time[index+1] - time[index])
3375    return (index,ratio)
3376
3377
3378def weed(coordinates,volumes,boundary = None):
3379    if type(coordinates)=='array':
3380        coordinates = coordinates.tolist()
3381    if type(volumes)=='array':
3382        volumes = volumes.tolist()
3383
3384    unique = False
3385    point_dict = {}
3386    same_point = {}
3387    for i in range(len(coordinates)):
3388        point = tuple(coordinates[i])
3389        if point_dict.has_key(point):
3390            unique = True
3391            same_point[i]=point
3392            #to change all point i references to point j
3393        else:
3394            point_dict[point]=i
3395            same_point[i]=point
3396
3397    coordinates = []
3398    i = 0
3399    for point in point_dict.keys():
3400        point = tuple(point)
3401        coordinates.append(list(point))
3402        point_dict[point]=i
3403        i+=1
3404
3405
3406    for volume in volumes:
3407        for i in range(len(volume)):
3408            index = volume[i]
3409            if index>-1:
3410                volume[i]=point_dict[same_point[index]]
3411
3412    new_boundary = {}
3413    if not boundary is None:
3414        for segment in boundary.keys():
3415            point0 = point_dict[same_point[segment[0]]]
3416            point1 = point_dict[same_point[segment[1]]]
3417            label = boundary[segment]
3418            #FIXME should the bounday attributes be concaterated
3419            #('exterior, pond') or replaced ('pond')(peter row)
3420
3421            if new_boundary.has_key((point0,point1)):
3422                new_boundary[(point0,point1)]=new_boundary[(point0,point1)]#\
3423                                              #+','+label
3424
3425            elif new_boundary.has_key((point1,point0)):
3426                new_boundary[(point1,point0)]=new_boundary[(point1,point0)]#\
3427                                              #+','+label
3428            else: new_boundary[(point0,point1)]=label
3429
3430        boundary = new_boundary
3431
3432    return coordinates,volumes,boundary
3433
3434
3435def decimate_dem(basename_in, stencil, cellsize_new, basename_out=None,
3436                 verbose=False):
3437    """Read Digitial Elevation model from the following NetCDF format (.dem)
3438
3439    Example:
3440
3441    ncols         3121
3442    nrows         1800
3443    xllcorner     722000
3444    yllcorner     5893000
3445    cellsize      25
3446    NODATA_value  -9999
3447    138.3698 137.4194 136.5062 135.5558 ..........
3448
3449    Decimate data to cellsize_new using stencil and write to NetCDF dem format.
3450    """
3451
3452    import os
3453    from Scientific.IO.NetCDF import NetCDFFile
3454    from Numeric import Float, zeros, sum, reshape, equal
3455
3456    root = basename_in
3457    inname = root + '.dem'
3458
3459    #Open existing netcdf file to read
3460    infile = NetCDFFile(inname, 'r')
3461    if verbose: print 'Reading DEM from %s' %inname
3462
3463    #Read metadata
3464    ncols = infile.ncols[0]
3465    nrows = infile.nrows[0]
3466    xllcorner = infile.xllcorner[0]
3467    yllcorner = infile.yllcorner[0]
3468    cellsize = infile.cellsize[0]
3469    NODATA_value = infile.NODATA_value[0]
3470    zone = infile.zone[0]
3471    false_easting = infile.false_easting[0]
3472    false_northing = infile.false_northing[0]
3473    projection = infile.projection
3474    datum = infile.datum
3475    units = infile.units
3476
3477    dem_elevation = infile.variables['elevation']
3478
3479    #Get output file name
3480    if basename_out == None:
3481        outname = root + '_' + repr(cellsize_new) + '.dem'
3482    else:
3483        outname = basename_out + '.dem'
3484
3485    if verbose: print 'Write decimated NetCDF file to %s' %outname
3486
3487    #Determine some dimensions for decimated grid
3488    (nrows_stencil, ncols_stencil) = stencil.shape
3489    x_offset = ncols_stencil / 2
3490    y_offset = nrows_stencil / 2
3491    cellsize_ratio = int(cellsize_new / cellsize)
3492    ncols_new = 1 + (ncols - ncols_stencil) / cellsize_ratio
3493    nrows_new = 1 + (nrows - nrows_stencil) / cellsize_ratio
3494
3495    #Open netcdf file for output
3496    outfile = NetCDFFile(outname, 'w')
3497
3498    #Create new file
3499    outfile.institution = 'Geoscience Australia'
3500    outfile.description = 'NetCDF DEM format for compact and portable storage ' +\
3501                           'of spatial point data'
3502    #Georeferencing
3503    outfile.zone = zone
3504    outfile.projection = projection
3505    outfile.datum = datum
3506    outfile.units = units
3507
3508    outfile.cellsize = cellsize_new
3509    outfile.NODATA_value = NODATA_value
3510    outfile.false_easting = false_easting
3511    outfile.false_northing = false_northing
3512
3513    outfile.xllcorner = xllcorner + (x_offset * cellsize)
3514    outfile.yllcorner = yllcorner + (y_offset * cellsize)
3515    outfile.ncols = ncols_new
3516    outfile.nrows = nrows_new
3517
3518    # dimension definition
3519    outfile.createDimension('number_of_points', nrows_new*ncols_new)
3520
3521    # variable definition
3522    outfile.createVariable('elevation', Float, ('number_of_points',))
3523
3524    # Get handle to the variable
3525    elevation = outfile.variables['elevation']
3526
3527    dem_elevation_r = reshape(dem_elevation, (nrows, ncols))
3528
3529    #Store data
3530    global_index = 0
3531    for i in range(nrows_new):
3532        if verbose: print 'Processing row %d of %d' %(i, nrows_new)
3533        lower_index = global_index
3534        telev =  zeros(ncols_new, Float)
3535        local_index = 0
3536        trow = i * cellsize_ratio
3537
3538        for j in range(ncols_new):
3539            tcol = j * cellsize_ratio
3540            tmp = dem_elevation_r[trow:trow+nrows_stencil, tcol:tcol+ncols_stencil]
3541
3542            #if dem contains 1 or more NODATA_values set value in
3543            #decimated dem to NODATA_value, else compute decimated
3544            #value using stencil
3545            if sum(sum(equal(tmp, NODATA_value))) > 0:
3546                telev[local_index] = NODATA_value
3547            else:
3548                telev[local_index] = sum(sum(tmp * stencil))
3549
3550            global_index += 1
3551            local_index += 1
3552
3553        upper_index = global_index
3554
3555        elevation[lower_index:upper_index] = telev
3556
3557    assert global_index == nrows_new*ncols_new, 'index not equal to number of points'
3558
3559    infile.close()
3560    outfile.close()
3561
3562
3563
3564
3565def tsh2sww(filename, verbose=False): #test_tsh2sww
3566    """
3567    to check if a tsh/msh file 'looks' good.
3568    """
3569
3570
3571    if verbose == True:print 'Creating domain from', filename
3572    domain = pmesh_to_domain_instance(filename, Domain)
3573    if verbose == True:print "Number of triangles = ", len(domain)
3574
3575    domain.smooth = True
3576    domain.format = 'sww'   #Native netcdf visualisation format
3577    file_path, filename = path.split(filename)
3578    filename, ext = path.splitext(filename)
3579    domain.set_name(filename)   
3580    domain.reduction = mean
3581    if verbose == True:print "file_path",file_path
3582    if file_path == "":file_path = "."
3583    domain.set_datadir(file_path)
3584
3585    if verbose == True:
3586        print "Output written to " + domain.get_datadir() + sep + \
3587              domain.get_name() + "." + domain.format
3588    sww = get_dataobject(domain)
3589    sww.store_connectivity()
3590    sww.store_timestep('stage')
3591
3592
3593def asc_csiro2sww(bath_dir,
3594                  elevation_dir,
3595                  ucur_dir,
3596                  vcur_dir,
3597                  sww_file,
3598                  minlat = None, maxlat = None,
3599                  minlon = None, maxlon = None,
3600                  zscale=1,
3601                  mean_stage = 0,
3602                  fail_on_NaN = True,
3603                  elevation_NaN_filler = 0,
3604                  bath_prefix='ba',
3605                  elevation_prefix='el',
3606                  verbose=False):
3607    """
3608    Produce an sww boundary file, from esri ascii data from CSIRO.
3609
3610    Also convert latitude and longitude to UTM. All coordinates are
3611    assumed to be given in the GDA94 datum.
3612
3613    assume:
3614    All files are in esri ascii format
3615
3616    4 types of information
3617    bathymetry
3618    elevation
3619    u velocity
3620    v velocity
3621
3622    Assumptions
3623    The metadata of all the files is the same
3624    Each type is in a seperate directory
3625    One bath file with extention .000
3626    The time period is less than 24hrs and uniform.
3627    """
3628    from Scientific.IO.NetCDF import NetCDFFile
3629
3630    from anuga.coordinate_transforms.redfearn import redfearn
3631
3632    precision = Float # So if we want to change the precision its done here
3633
3634    # go in to the bath dir and load the only file,
3635    bath_files = os.listdir(bath_dir)
3636
3637    bath_file = bath_files[0]
3638    bath_dir_file =  bath_dir + os.sep + bath_file
3639    bath_metadata,bath_grid =  _read_asc(bath_dir_file)
3640
3641    #Use the date.time of the bath file as a basis for
3642    #the start time for other files
3643    base_start = bath_file[-12:]
3644
3645    #go into the elevation dir and load the 000 file
3646    elevation_dir_file = elevation_dir  + os.sep + elevation_prefix \
3647                         + base_start
3648
3649    elevation_files = os.listdir(elevation_dir)
3650    ucur_files = os.listdir(ucur_dir)
3651    vcur_files = os.listdir(vcur_dir)
3652    elevation_files.sort()
3653    # the first elevation file should be the
3654    # file with the same base name as the bath data
3655    assert elevation_files[0] == 'el' + base_start
3656
3657    number_of_latitudes = bath_grid.shape[0]
3658    number_of_longitudes = bath_grid.shape[1]
3659    number_of_volumes = (number_of_latitudes-1)*(number_of_longitudes-1)*2
3660
3661    longitudes = [bath_metadata['xllcorner']+x*bath_metadata['cellsize'] \
3662                  for x in range(number_of_longitudes)]
3663    latitudes = [bath_metadata['yllcorner']+y*bath_metadata['cellsize'] \
3664                 for y in range(number_of_latitudes)]
3665
3666     # reverse order of lat, so the fist lat represents the first grid row
3667    latitudes.reverse()
3668
3669    kmin, kmax, lmin, lmax = _get_min_max_indexes(latitudes[:],longitudes[:],
3670                                                 minlat=minlat, maxlat=maxlat,
3671                                                 minlon=minlon, maxlon=maxlon)
3672
3673
3674    bath_grid = bath_grid[kmin:kmax,lmin:lmax]
3675    latitudes = latitudes[kmin:kmax]
3676    longitudes = longitudes[lmin:lmax]
3677    number_of_latitudes = len(latitudes)
3678    number_of_longitudes = len(longitudes)
3679    number_of_times = len(os.listdir(elevation_dir))
3680    number_of_points = number_of_latitudes*number_of_longitudes
3681    number_of_volumes = (number_of_latitudes-1)*(number_of_longitudes-1)*2
3682
3683    #Work out the times
3684    if len(elevation_files) > 1:
3685        # Assume: The time period is less than 24hrs.
3686        time_period = (int(elevation_files[1][-3:]) - \
3687                      int(elevation_files[0][-3:]))*60*60
3688        times = [x*time_period for x in range(len(elevation_files))]
3689    else:
3690        times = [0.0]
3691
3692
3693    if verbose:
3694        print '------------------------------------------------'
3695        print 'Statistics:'
3696        print '  Extent (lat/lon):'
3697        print '    lat in [%f, %f], len(lat) == %d'\
3698              %(min(latitudes), max(latitudes),
3699                len(latitudes))
3700        print '    lon in [%f, %f], len(lon) == %d'\
3701              %(min(longitudes), max(longitudes),
3702                len(longitudes))
3703        print '    t in [%f, %f], len(t) == %d'\
3704              %(min(times), max(times), len(times))
3705
3706    ######### WRITE THE SWW FILE #############
3707    # NetCDF file definition
3708    outfile = NetCDFFile(sww_file, 'w')
3709
3710    #Create new file
3711    outfile.institution = 'Geoscience Australia'
3712    outfile.description = 'Converted from XXX'
3713
3714
3715    #For sww compatibility
3716    outfile.smoothing = 'Yes'
3717    outfile.order = 1
3718
3719    #Start time in seconds since the epoch (midnight 1/1/1970)
3720    outfile.starttime = starttime = times[0]
3721
3722
3723    # dimension definitions
3724    outfile.createDimension('number_of_volumes', number_of_volumes)
3725
3726    outfile.createDimension('number_of_vertices', 3)
3727    outfile.createDimension('number_of_points', number_of_points)
3728    outfile.createDimension('number_of_timesteps', number_of_times)
3729
3730    # variable definitions
3731    outfile.createVariable('x', precision, ('number_of_points',))
3732    outfile.createVariable('y', precision, ('number_of_points',))
3733    outfile.createVariable('elevation', precision, ('number_of_points',))
3734
3735    #FIXME: Backwards compatibility
3736    outfile.createVariable('z', precision, ('number_of_points',))
3737    #################################
3738
3739    outfile.createVariable('volumes', Int, ('number_of_volumes',
3740                                            'number_of_vertices'))
3741
3742    outfile.createVariable('time', precision,
3743                           ('number_of_timesteps',))
3744
3745    outfile.createVariable('stage', precision,
3746                           ('number_of_timesteps',
3747                            'number_of_points'))
3748
3749    outfile.createVariable('xmomentum', precision,
3750                           ('number_of_timesteps',
3751                            'number_of_points'))
3752
3753    outfile.createVariable('ymomentum', precision,
3754                           ('number_of_timesteps',
3755                            'number_of_points'))
3756
3757    #Store
3758    from anuga.coordinate_transforms.redfearn import redfearn
3759    x = zeros(number_of_points, Float)  #Easting
3760    y = zeros(number_of_points, Float)  #Northing
3761
3762    if verbose: print 'Making triangular grid'
3763    #Get zone of 1st point.
3764    refzone, _, _ = redfearn(latitudes[0],longitudes[0])
3765
3766    vertices = {}
3767    i = 0
3768    for k, lat in enumerate(latitudes):
3769        for l, lon in enumerate(longitudes):
3770
3771            vertices[l,k] = i
3772
3773            zone, easting, northing = redfearn(lat,lon)
3774
3775            msg = 'Zone boundary crossed at longitude =', lon
3776            #assert zone == refzone, msg
3777            #print '%7.2f %7.2f %8.2f %8.2f' %(lon, lat, easting, northing)
3778            x[i] = easting
3779            y[i] = northing
3780            i += 1
3781
3782
3783    #Construct 2 triangles per 'rectangular' element
3784    volumes = []
3785    for l in range(number_of_longitudes-1):    #X direction
3786        for k in range(number_of_latitudes-1): #Y direction
3787            v1 = vertices[l,k+1]
3788            v2 = vertices[l,k]
3789            v3 = vertices[l+1,k+1]
3790            v4 = vertices[l+1,k]
3791
3792            #Note, this is different to the ferrit2sww code
3793            #since the order of the lats is reversed.
3794            volumes.append([v1,v3,v2]) #Upper element
3795            volumes.append([v4,v2,v3]) #Lower element
3796
3797    volumes = array(volumes)
3798
3799    geo_ref = Geo_reference(refzone,min(x),min(y))
3800    geo_ref.write_NetCDF(outfile)
3801
3802    # This will put the geo ref in the middle
3803    #geo_ref = Geo_reference(refzone,(max(x)+min(x))/2.0,(max(x)+min(y))/2.)
3804
3805
3806    if verbose:
3807        print '------------------------------------------------'
3808        print 'More Statistics:'
3809        print '  Extent (/lon):'
3810        print '    x in [%f, %f], len(lat) == %d'\
3811              %(min(x), max(x),
3812                len(x))
3813        print '    y in [%f, %f], len(lon) == %d'\
3814              %(min(y), max(y),
3815                len(y))
3816        print 'geo_ref: ',geo_ref
3817
3818    z = resize(bath_grid,outfile.variables['z'][:].shape)
3819    outfile.variables['x'][:] = x - geo_ref.get_xllcorner()
3820    outfile.variables['y'][:] = y - geo_ref.get_yllcorner()
3821    outfile.variables['z'][:] = z
3822    outfile.variables['elevation'][:] = z  #FIXME HACK
3823    outfile.variables['volumes'][:] = volumes.astype(Int32) #On Opteron 64
3824
3825    stage = outfile.variables['stage']
3826    xmomentum = outfile.variables['xmomentum']
3827    ymomentum = outfile.variables['ymomentum']
3828
3829    outfile.variables['time'][:] = times   #Store time relative
3830
3831    if verbose: print 'Converting quantities'
3832    n = number_of_times
3833    for j in range(number_of_times):
3834        # load in files
3835        elevation_meta, elevation_grid = \
3836           _read_asc(elevation_dir + os.sep + elevation_files[j])
3837
3838        _, u_momentum_grid =  _read_asc(ucur_dir + os.sep + ucur_files[j])
3839        _, v_momentum_grid =  _read_asc(vcur_dir + os.sep + vcur_files[j])
3840
3841        #cut matrix to desired size
3842        elevation_grid = elevation_grid[kmin:kmax,lmin:lmax]
3843        u_momentum_grid = u_momentum_grid[kmin:kmax,lmin:lmax]
3844        v_momentum_grid = v_momentum_grid[kmin:kmax,lmin:lmax]
3845       
3846        # handle missing values
3847        missing = (elevation_grid == elevation_meta['NODATA_value'])
3848        if sometrue (missing):
3849            if fail_on_NaN:
3850                msg = 'File %s contains missing values'\
3851                      %(elevation_files[j])
3852                raise DataMissingValuesError, msg
3853            else:
3854                elevation_grid = elevation_grid*(missing==0) + \
3855                                 missing*elevation_NaN_filler
3856
3857
3858        if verbose and j%((n+10)/10)==0: print '  Doing %d of %d' %(j, n)
3859        i = 0
3860        for k in range(number_of_latitudes):      #Y direction
3861            for l in range(number_of_longitudes): #X direction
3862                w = zscale*elevation_grid[k,l] + mean_stage
3863                stage[j,i] = w
3864                h = w - z[i]
3865                xmomentum[j,i] = u_momentum_grid[k,l]*h
3866                ymomentum[j,i] = v_momentum_grid[k,l]*h
3867                i += 1
3868    outfile.close()
3869
3870def _get_min_max_indexes(latitudes_ref,longitudes_ref,
3871                        minlat=None, maxlat=None,
3872                        minlon=None, maxlon=None):
3873    """
3874    return max, min indexes (for slicing) of the lat and long arrays to cover the area
3875    specified with min/max lat/long
3876
3877    Think of the latitudes and longitudes describing a 2d surface.
3878    The area returned is, if possible, just big enough to cover the
3879    inputed max/min area. (This will not be possible if the max/min area
3880    has a section outside of the latitudes/longitudes area.)
3881
3882    asset  longitudes are sorted,
3883    long - from low to high (west to east, eg 148 - 151)
3884    assert latitudes are sorted, ascending or decending
3885    """
3886    latitudes = latitudes_ref[:]
3887    longitudes = longitudes_ref[:]
3888
3889    latitudes = ensure_numeric(latitudes)
3890    longitudes = ensure_numeric(longitudes)
3891   
3892    assert allclose(sort(longitudes), longitudes)
3893   
3894    lat_ascending = True
3895    if not allclose(sort(latitudes), latitudes):
3896        lat_ascending = False
3897        # reverse order of lat, so it's in ascending order         
3898        latitudes = latitudes[::-1]
3899        assert allclose(sort(latitudes), latitudes)
3900    #print "latitudes  in funct", latitudes
3901   
3902    largest_lat_index = len(latitudes)-1
3903    #Cut out a smaller extent.
3904    if minlat == None:
3905        lat_min_index = 0
3906    else:
3907        lat_min_index = searchsorted(latitudes, minlat)-1
3908        if lat_min_index <0:
3909            lat_min_index = 0
3910
3911
3912    if maxlat == None:
3913        lat_max_index = largest_lat_index #len(latitudes)
3914    else:
3915        lat_max_index = searchsorted(latitudes, maxlat)
3916        if lat_max_index > largest_lat_index:
3917            lat_max_index = largest_lat_index
3918
3919    if minlon == None:
3920        lon_min_index = 0
3921    else:
3922        lon_min_index = searchsorted(longitudes, minlon)-1
3923        if lon_min_index <0:
3924            lon_min_index = 0
3925
3926    if maxlon == None:
3927        lon_max_index = len(longitudes)
3928    else:
3929        lon_max_index = searchsorted(longitudes, maxlon)
3930
3931    # Reversing the indexes, if the lat array is decending
3932    if lat_ascending is False:
3933        lat_min_index, lat_max_index = largest_lat_index - lat_max_index , \
3934                                       largest_lat_index - lat_min_index
3935    lat_max_index = lat_max_index + 1 # taking into account how slicing works
3936    lon_max_index = lon_max_index + 1 # taking into account how slicing works
3937
3938    return lat_min_index, lat_max_index, lon_min_index, lon_max_index
3939
3940
3941def _read_asc(filename, verbose=False):
3942    """Read esri file from the following ASCII format (.asc)
3943
3944    Example:
3945
3946    ncols         3121
3947    nrows         1800
3948    xllcorner     722000
3949    yllcorner     5893000
3950    cellsize      25
3951    NODATA_value  -9999
3952    138.3698 137.4194 136.5062 135.5558 ..........
3953
3954    """
3955
3956    datafile = open(filename)
3957
3958    if verbose: print 'Reading DEM from %s' %(filename)
3959    lines = datafile.readlines()
3960    datafile.close()
3961
3962    if verbose: print 'Got', len(lines), ' lines'
3963
3964    ncols = int(lines.pop(0).split()[1].strip())
3965    nrows = int(lines.pop(0).split()[1].strip())
3966    xllcorner = float(lines.pop(0).split()[1].strip())
3967    yllcorner = float(lines.pop(0).split()[1].strip())
3968    cellsize = float(lines.pop(0).split()[1].strip())
3969    NODATA_value = float(lines.pop(0).split()[1].strip())
3970
3971    assert len(lines) == nrows
3972
3973    #Store data
3974    grid = []
3975
3976    n = len(lines)
3977    for i, line in enumerate(lines):
3978        cells = line.split()
3979        assert len(cells) == ncols
3980        grid.append(array([float(x) for x in cells]))
3981    grid = array(grid)
3982
3983    return {'xllcorner':xllcorner,
3984            'yllcorner':yllcorner,
3985            'cellsize':cellsize,
3986            'NODATA_value':NODATA_value}, grid
3987
3988
3989
3990    ####  URS 2 SWW  ###
3991
3992lon_name = 'LON'
3993lat_name = 'LAT'
3994time_name = 'TIME'
3995precision = Float # So if we want to change the precision its done here       
3996class Write_nc:
3997    """
3998    Write an nc file.
3999   
4000    Note, this should be checked to meet cdc netcdf conventions for gridded
4001    data. http://www.cdc.noaa.gov/cdc/conventions/cdc_netcdf_standard.shtml
4002   
4003    """
4004    def __init__(self,
4005                 quantity_name,
4006                 file_name,
4007                 time_step_count,
4008                 time_step,
4009                 lon,
4010                 lat):
4011        """
4012        time_step_count is the number of time steps.
4013        time_step is the time step size
4014       
4015        pre-condition: quantity_name must be 'HA' 'UA'or 'VA'.
4016        """
4017        self.quantity_name = quantity_name
4018        quantity_units= {'HA':'METERS',
4019                              'UA':'METERS/SECOND',
4020                              'VA':'METERS/SECOND'}
4021       
4022        #self.file_name = file_name
4023        self.time_step_count = time_step_count
4024        self.time_step = time_step
4025
4026        # NetCDF file definition
4027        self.outfile = NetCDFFile(file_name, 'w')
4028        outfile = self.outfile       
4029
4030        #Create new file
4031        nc_lon_lat_header(outfile, lon, lat)
4032   
4033        # TIME
4034        outfile.createDimension(time_name, None)
4035        outfile.createVariable(time_name, precision, (time_name,))
4036
4037        #QUANTITY
4038        outfile.createVariable(self.quantity_name, precision,
4039                               (time_name, lat_name, lon_name))
4040        outfile.variables[self.quantity_name].missing_value=-1.e+034
4041        outfile.variables[self.quantity_name].units= \
4042                                 quantity_units[self.quantity_name]
4043        outfile.variables[lon_name][:]= ensure_numeric(lon)
4044        outfile.variables[lat_name][:]= ensure_numeric(lat)
4045
4046        #Assume no one will be wanting to read this, while we are writing
4047        #outfile.close()
4048       
4049    def store_timestep(self, quantity_slice):
4050        """
4051        quantity_slice is the data to be stored at this time step
4052        """
4053       
4054        outfile = self.outfile
4055       
4056        # Get the variables
4057        time = outfile.variables[time_name]
4058        quantity = outfile.variables[self.quantity_name]
4059           
4060        i = len(time)
4061
4062        #Store time
4063        time[i] = i*self.time_step #self.domain.time
4064        quantity[i,:] = quantity_slice*100 # To convert from m to cm
4065                                           # And m/s to cm/sec
4066       
4067    def close(self):
4068        self.outfile.close()
4069
4070def urs2sww(basename_in='o', basename_out=None, verbose=False,
4071            remove_nc_files=True,
4072            minlat=None, maxlat=None,
4073            minlon= None, maxlon=None,
4074            mint=None, maxt=None,
4075            mean_stage=0,
4076            origin = None,
4077            zscale=1,
4078            fail_on_NaN=True,
4079            NaN_filler=0,
4080            elevation=None,
4081            gridded=True):
4082    """
4083    Convert URS C binary format for wave propagation to
4084    sww format native to abstract_2d_finite_volumes.
4085
4086    Specify only basename_in and read files of the form
4087    basefilename-z-mux, basefilename-e-mux and basefilename-n-mux containing
4088    relative height, x-velocity and y-velocity, respectively.
4089
4090    Also convert latitude and longitude to UTM. All coordinates are
4091    assumed to be given in the GDA94 datum. The latitude and longitude
4092    information is for  a grid.
4093
4094    min's and max's: If omitted - full extend is used.
4095    To include a value min may equal it, while max must exceed it.
4096    Lat and lon are assumed to be in decimal degrees.
4097    NOTE: minlon is the most east boundary.
4098   
4099    origin is a 3-tuple with geo referenced
4100    UTM coordinates (zone, easting, northing)
4101    It will be the origin of the sww file. This shouldn't be used,
4102    since all of anuga should be able to handle an arbitary origin.
4103
4104
4105    URS C binary format has data orgainised as TIME, LONGITUDE, LATITUDE
4106    which means that latitude is the fastest
4107    varying dimension (row major order, so to speak)
4108
4109    In URS C binary the latitudes and longitudes are in assending order.
4110    """
4111    if gridded is False:
4112        pass
4113        #urs_ungridded2sww()
4114       
4115    if basename_out == None:
4116        basename_out = basename_in
4117    files_out = urs2nc(basename_in, basename_out)
4118    ferret2sww(basename_out,
4119               minlat=minlat,
4120               maxlat=maxlat,
4121               minlon=minlon,
4122               maxlon=maxlon,
4123               mint=mint,
4124               maxt=maxt,
4125               mean_stage=mean_stage,
4126               origin=origin,
4127               zscale=zscale,
4128               fail_on_NaN=fail_on_NaN,
4129               NaN_filler=NaN_filler,
4130               inverted_bathymetry=True,
4131               verbose=verbose)
4132    #print "files_out",files_out
4133    if remove_nc_files:
4134        for file_out in files_out:
4135            os.remove(file_out)
4136   
4137def urs2nc(basename_in = 'o', basename_out = 'urs'):
4138    """
4139    Convert the 3 urs files to 4 nc files.
4140
4141    The name of the urs file names must be;
4142    [basename_in]-z-mux
4143    [basename_in]-e-mux
4144    [basename_in]-n-mux
4145   
4146    """
4147    files_in = [basename_in+'-z-mux',
4148                basename_in+'-e-mux',
4149                basename_in+'-n-mux']
4150    files_out = [basename_out+'_ha.nc',
4151                 basename_out+'_ua.nc',
4152                 basename_out+'_va.nc']
4153    quantities = ['HA','UA','VA']
4154
4155    for file_name in files_in:
4156        if os.access(file_name, os.F_OK) == 0 :
4157            msg = 'File %s does not exist or is not accessible' %file_name
4158            raise IOError, msg
4159       
4160    hashed_elevation = None
4161    for file_in, file_out, quantity in map(None, files_in,
4162                                           files_out,
4163                                           quantities):
4164        lonlatdep, lon, lat, depth = _binary_c2nc(file_in,
4165                                         file_out,
4166                                         quantity)
4167        #print "lonlatdep", lonlatdep
4168        if hashed_elevation == None:
4169            elevation_file = basename_out+'_e.nc'
4170            write_elevation_nc(elevation_file,
4171                                lon,
4172                                lat,
4173                                depth)
4174            hashed_elevation = myhash(lonlatdep)
4175        else:
4176            msg = "The elevation information in the mux files is inconsistent"
4177            assert hashed_elevation == myhash(lonlatdep), msg
4178    files_out.append(elevation_file)
4179    return files_out
4180   
4181def _binary_c2nc(file_in, file_out, quantity):
4182    """
4183    Reads in a quantity urs file and writes a quantity nc file.
4184    additionally, returns the depth and lat, long info,
4185    so it can be written to a file.
4186    """
4187    columns = 3 # long, lat , depth
4188    mux_file = open(file_in, 'rb')
4189   
4190    # Number of points/stations
4191    (points_num,)= unpack('i',mux_file.read(4))
4192
4193    # nt, int - Number of time steps
4194    (time_step_count,)= unpack('i',mux_file.read(4))
4195
4196    #dt, float - time step, seconds
4197    (time_step,) = unpack('f', mux_file.read(4))
4198   
4199    msg = "Bad data in the mux file."
4200    if points_num < 0:
4201        mux_file.close()
4202        raise ANUGAError, msg
4203    if time_step_count < 0:
4204        mux_file.close()
4205        raise ANUGAError, msg
4206    if time_step < 0:
4207        mux_file.close()
4208        raise ANUGAError, msg
4209   
4210    lonlatdep = p_array.array('f')
4211    lonlatdep.read(mux_file, columns * points_num)
4212    lonlatdep = array(lonlatdep, typecode=Float)   
4213    lonlatdep = reshape(lonlatdep, (points_num, columns))
4214   
4215    lon, lat, depth = lon_lat2grid(lonlatdep)
4216    lon_sorted = list(lon)
4217    lon_sorted.sort()
4218
4219    if not lon == lon_sorted:
4220        msg = "Longitudes in mux file are not in ascending order"
4221        raise IOError, msg
4222    lat_sorted = list(lat)
4223    lat_sorted.sort()
4224
4225    if not lat == lat_sorted:
4226        msg = "Latitudes in mux file are not in ascending order"
4227   
4228    nc_file = Write_nc(quantity,
4229                       file_out,
4230                       time_step_count,
4231                       time_step,
4232                       lon,
4233                       lat)
4234
4235    for i in range(time_step_count):
4236        #Read in a time slice  from mux file 
4237        hz_p_array = p_array.array('f')
4238        hz_p_array.read(mux_file, points_num)
4239        hz_p = array(hz_p_array, typecode=Float)
4240        hz_p = reshape(hz_p, (len(lon), len(lat)))
4241        hz_p = transpose(hz_p) #mux has lat varying fastest, nc has long v.f.
4242
4243        #write time slice to nc file
4244        nc_file.store_timestep(hz_p)
4245    mux_file.close()
4246    nc_file.close()
4247
4248    return lonlatdep, lon, lat, depth
4249   
4250
4251def write_elevation_nc(file_out, lon, lat, depth_vector):
4252    """
4253    Write an nc elevation file.
4254    """
4255   
4256    # NetCDF file definition
4257    outfile = NetCDFFile(file_out, 'w')
4258
4259    #Create new file
4260    nc_lon_lat_header(outfile, lon, lat)
4261   
4262    # ELEVATION
4263    zname = 'ELEVATION'
4264    outfile.createVariable(zname, precision, (lat_name, lon_name))
4265    outfile.variables[zname].units='CENTIMETERS'
4266    outfile.variables[zname].missing_value=-1.e+034
4267
4268    outfile.variables[lon_name][:]= ensure_numeric(lon)
4269    outfile.variables[lat_name][:]= ensure_numeric(lat)
4270
4271    depth = reshape(depth_vector, ( len(lat), len(lon)))
4272    outfile.variables[zname][:]= depth
4273   
4274    outfile.close()
4275   
4276def nc_lon_lat_header(outfile, lon, lat):
4277    """
4278    outfile is the netcdf file handle.
4279    lon - a list/array of the longitudes
4280    lat - a list/array of the latitudes
4281    """
4282   
4283    outfile.institution = 'Geoscience Australia'
4284    outfile.description = 'Converted from URS binary C'
4285   
4286    # Longitude
4287    outfile.createDimension(lon_name, len(lon))
4288    outfile.createVariable(lon_name, precision, (lon_name,))
4289    outfile.variables[lon_name].point_spacing='uneven'
4290    outfile.variables[lon_name].units='degrees_east'
4291    outfile.variables[lon_name].assignValue(lon)
4292
4293
4294    # Latitude
4295    outfile.createDimension(lat_name, len(lat))
4296    outfile.createVariable(lat_name, precision, (lat_name,))
4297    outfile.variables[lat_name].point_spacing='uneven'
4298    outfile.variables[lat_name].units='degrees_north'
4299    outfile.variables[lat_name].assignValue(lat)
4300
4301
4302   
4303def lon_lat2grid(long_lat_dep):
4304    """
4305    given a list of points that are assumed to be an a grid,
4306    return the long's and lat's of the grid.
4307    long_lat_dep is an array where each row is a position.
4308    The first column is longitudes.
4309    The second column is latitudes.
4310
4311    The latitude is the fastest varying dimension - in mux files
4312    """
4313    LONG = 0
4314    LAT = 1
4315    QUANTITY = 2
4316
4317    long_lat_dep = ensure_numeric(long_lat_dep, Float)
4318   
4319    num_points = long_lat_dep.shape[0]
4320    this_rows_long = long_lat_dep[0,LONG]
4321
4322    # Count the length of unique latitudes
4323    i = 0
4324    while long_lat_dep[i,LONG] == this_rows_long and i < num_points:
4325        i += 1
4326    # determine the lats and longsfrom the grid
4327    lat = long_lat_dep[:i, LAT]       
4328    long = long_lat_dep[::i, LONG]
4329   
4330    lenlong = len(long)
4331    lenlat = len(lat)
4332    #print 'len lat', lat, len(lat)
4333    #print 'len long', long, len(long)
4334         
4335    msg = 'Input data is not gridded'     
4336    assert num_points % lenlat == 0, msg
4337    assert num_points % lenlong == 0, msg
4338         
4339    # Test that data is gridded       
4340    for i in range(lenlong):
4341        msg = 'Data is not gridded.  It must be for this operation'
4342        first = i*lenlat
4343        last = first + lenlat
4344               
4345        assert allclose(long_lat_dep[first:last,LAT], lat), msg
4346        assert allclose(long_lat_dep[first:last,LONG], long[i]), msg
4347   
4348   
4349#    print 'range long', min(long), max(long)
4350#    print 'range lat', min(lat), max(lat)
4351#    print 'ref long', min(long_lat_dep[:,0]), max(long_lat_dep[:,0])
4352#    print 'ref lat', min(long_lat_dep[:,1]), max(long_lat_dep[:,1])
4353   
4354   
4355   
4356    msg = 'Out of range latitudes/longitudes'
4357    for l in lat:assert -90 < l < 90 , msg
4358    for l in long:assert -180 < l < 180 , msg
4359
4360    # Changing quantity from lat being the fastest varying dimension to
4361    # long being the fastest varying dimension
4362    # FIXME - make this faster/do this a better way
4363    # use numeric transpose, after reshaping the quantity vector
4364#    quantity = zeros(len(long_lat_dep), Float)
4365    quantity = zeros(num_points, Float)
4366   
4367#    print 'num',num_points
4368    for lat_i, _ in enumerate(lat):
4369        for long_i, _ in enumerate(long):
4370            q_index = lat_i*lenlong+long_i
4371            lld_index = long_i*lenlat+lat_i
4372#            print 'lat_i', lat_i, 'long_i',long_i, 'q_index', q_index, 'lld_index', lld_index
4373            temp = long_lat_dep[lld_index, QUANTITY]
4374            quantity[q_index] = temp
4375           
4376    return long, lat, quantity
4377
4378    ####  END URS 2 SWW  ###
4379
4380    #### URS UNGRIDDED 2 SWW ###
4381
4382    ### PRODUCING THE POINTS NEEDED FILE ###
4383LL_LAT = -50.0
4384LL_LONG = 80.0
4385GRID_SPACING = 1.0/60.0
4386LAT_AMOUNT = 4800
4387LONG_AMOUNT = 3600
4388def URS_points_needed_to_file(file_name, boundary_polygon,
4389                              ll_lat=LL_LAT, ll_long=LL_LONG,
4390                              grid_spacing=GRID_SPACING, 
4391                              lat_amount=LAT_AMOUNT, long_amount=LONG_AMOUNT,
4392                              zone=None, export_csv=False, use_cache=False,
4393                              verbose=False):
4394    """
4395    file_name - name of the urs file produced for David.
4396    boundary_polygon - a list of points that describes a polygon.
4397                      The last point is assumed ot join the first point.
4398                      This is in UTM (lat long would be better though)
4399
4400    ll_lat - lower left latitude, in decimal degrees
4401    ll-long - lower left longitude, in decimal degrees
4402    grid_spacing - in deciamal degrees
4403
4404
4405    Don't add the file extension.  It will be added.
4406    """
4407    geo = URS_points_needed(boundary_polygon, ll_lat, ll_long, grid_spacing, 
4408                      lat_amount, long_amount, zone, use_cache, verbose)
4409    if not file_name[-4:] == ".urs":
4410        file_name += ".urs"
4411    geo.export_points_file(file_name)
4412    if export_csv:
4413        if file_name[-4:] == ".urs":
4414            file_name = file_name[:-4] + ".csv"
4415        geo.export_points_file(file_name)
4416
4417def URS_points_needed(boundary_polygon, ll_lat=LL_LAT,
4418                      ll_long=LL_LONG, grid_spacing=GRID_SPACING, 
4419                      lat_amount=LAT_AMOUNT, long_amount=LONG_AMOUNT,
4420                      zone=None, use_cache=False, verbose=False):
4421    args = (boundary_polygon)
4422    kwargs = {'ll_lat': ll_lat,
4423              'll_long': ll_long,
4424              'grid_spacing': grid_spacing,
4425              'lat_amount': lat_amount,
4426              'long_amount': long_amount,
4427              'zone': zone} 
4428    if use_cache is True:
4429        try:
4430            from anuga.caching import cache
4431        except:
4432            msg = 'Caching was requested, but caching module'+\
4433                  'could not be imported'
4434            raise msg
4435
4436
4437        geo = cache(_URS_points_needed,
4438                  args, kwargs,
4439                  verbose=verbose,
4440                  compression=False)
4441    else:
4442        #I was getting 'got multiple values for keyword argument' errors
4443        #geo = apply(_URS_points_needed, args, kwargs)
4444        geo = _URS_points_needed(boundary_polygon, ll_lat,
4445                      ll_long, grid_spacing, 
4446                      lat_amount, long_amount,
4447                      zone)
4448
4449    return geo   
4450def _URS_points_needed(boundary_polygon, ll_lat=LL_LAT,
4451                      ll_long=LL_LONG, grid_spacing=GRID_SPACING, 
4452                      lat_amount=LAT_AMOUNT, long_amount=LONG_AMOUNT,
4453                      zone=None):
4454    """
4455
4456    boundary_polygon - a list of points that describes a polygon.
4457                      The last point is assumed ot join the first point.
4458                      This is in UTM (lat long would be better though)
4459
4460    ll_lat - lower left latitude, in decimal degrees
4461    ll-long - lower left longitude, in decimal degrees
4462    grid_spacing - in deciamal degrees
4463
4464    """
4465    #FIXME cache this function!
4466   
4467    from sets import ImmutableSet
4468   
4469    msg = "grid_spacing can not be zero"
4470    assert not grid_spacing ==0, msg
4471    a = boundary_polygon
4472    # List of segments.  Each segment is two points.
4473    segs = [i and [a[i-1], a[i]] or [a[len(a)-1], a[0]] for i in range(len(a))]
4474
4475    # convert the segs to Lat's and longs.
4476    if zone is None:
4477        # Assume the zone of the segments is the same as the lower left
4478        # corner of the lat long data
4479        zone, _, _ = redfearn(ll_lat, ll_long)
4480    lat_long_set = ImmutableSet()
4481    for seg in segs:
4482        points_lat_long = points_needed(seg, ll_lat, ll_long, grid_spacing, 
4483                      lat_amount, long_amount, zone)
4484        lat_long_set |= ImmutableSet(points_lat_long)
4485    #print "lat_long_set",lat_long_set
4486    geo = Geospatial_data(data_points=list(lat_long_set),
4487                              points_are_lats_longs=True)
4488    return geo
4489   
4490def points_needed(seg, ll_lat, ll_long, grid_spacing, 
4491                  lat_amount, long_amount, zone):
4492    """
4493    return a list of the points, in lats and longs that are needed to
4494    interpolate any point on the segment.
4495    """
4496    from math import sqrt
4497    #print "zone",zone
4498    geo_reference = Geo_reference(zone=zone)
4499    #print "seg",seg
4500    geo = Geospatial_data(seg,geo_reference=geo_reference)
4501    seg_lat_long = geo.get_data_points(as_lat_long=True)
4502    #print "seg_lat_long", seg_lat_long
4503    # 1.415 = 2^0.5, rounded up....
4504    sqrt_2_rounded_up = 1.415
4505    buffer = sqrt_2_rounded_up * grid_spacing
4506   
4507    #
4508   
4509    max_lat = max(seg_lat_long[0][0], seg_lat_long[1][0]) + buffer
4510    max_long = max(seg_lat_long[0][1], seg_lat_long[1][1]) + buffer
4511    min_lat = min(seg_lat_long[0][0], seg_lat_long[1][0]) - buffer
4512    min_long = min(seg_lat_long[0][1], seg_lat_long[1][1]) - buffer
4513
4514    first_row = (min_long - ll_long)/grid_spacing
4515    # To round up
4516    first_row_long = int(round(first_row + 0.5))
4517    #print "first_row", first_row_long
4518
4519    last_row = (max_long - ll_long)/grid_spacing # round down
4520    last_row_long = int(round(last_row))
4521    #print "last_row",last_row _long
4522   
4523    first_row = (min_lat - ll_lat)/grid_spacing
4524    # To round up
4525    first_row_lat = int(round(first_row + 0.5))
4526    #print "first_row", first_row_lat
4527
4528    last_row = (max_lat - ll_lat)/grid_spacing # round down
4529    last_row_lat = int(round(last_row))
4530    #print "last_row",last_row_lat
4531
4532    # to work out the max distance -
4533    # 111120 - horizontal distance between 1 deg latitude.
4534    #max_distance = sqrt_2_rounded_up * 111120 * grid_spacing
4535    max_distance = 157147.4112 * grid_spacing
4536    #print "max_distance", max_distance #2619.12 m for 1 minute
4537    points_lat_long = []
4538    # Create a list of the lat long points to include.
4539    for index_lat in range(first_row_lat, last_row_lat + 1):
4540        for index_long in range(first_row_long, last_row_long + 1):
4541            lat = ll_lat + index_lat*grid_spacing
4542            long = ll_long + index_long*grid_spacing
4543
4544            #filter here to keep good points
4545            if keep_point(lat, long, seg, max_distance):
4546                points_lat_long.append((lat, long)) #must be hashable
4547    #print "points_lat_long", points_lat_long
4548
4549    # Now that we have these points, lets throw ones out that are too far away
4550    return points_lat_long
4551
4552def keep_point(lat, long, seg, max_distance):
4553    """
4554    seg is two points, UTM
4555    """
4556    from math import sqrt
4557    _ , x0, y0 = redfearn(lat, long)
4558    x1 = seg[0][0]
4559    y1 = seg[0][1]
4560    x2 = seg[1][0]
4561    y2 = seg[1][1]
4562
4563    x2_1 = x2-x1
4564    y2_1 = y2-y1
4565    d = abs((x2_1)*(y1-y0)-(x1-x0)*(y2_1))/sqrt((x2_1)*(x2_1)+(y2_1)*(y2_1))
4566    if d <= max_distance:
4567        return True
4568    else:
4569        return False
4570   
4571    #### CONVERTING UNGRIDDED URS DATA TO AN SWW FILE ####
4572def urs_ungridded2sww_link(basename_in='o', basename_out=None, verbose=False,
4573            remove_nc_files=True,
4574            minlat=None, maxlat=None,
4575            minlon= None, maxlon=None,
4576            mint=None, maxt=None,
4577            mean_stage=0,
4578            origin = None,
4579            zscale=1,
4580            fail_on_NaN=True,
4581            NaN_filler=0,
4582            elevation=None):
4583    """
4584    validation stuff on parrameters not used
4585    """
4586    pass
4587   
4588def urs_ungridded2sww(basename_in='o', basename_out=None, verbose=False,
4589            mint=None, maxt=None,
4590            mean_stage=0,
4591            origin = None,
4592            zscale=1):
4593    """   
4594    Convert URS C binary format for wave propagation to
4595    sww format native to abstract_2d_finite_volumes.
4596
4597    Specify only basename_in and read files of the form
4598    basefilename-z-mux, basefilename-e-mux and basefilename-n-mux containing
4599    relative height, x-velocity and y-velocity, respectively.
4600
4601    Also convert latitude and longitude to UTM. All coordinates are
4602    assumed to be given in the GDA94 datum. The latitude and longitude
4603    information is assumed ungridded grid.
4604
4605    min's and max's: If omitted - full extend is used.
4606    To include a value min ans max may equal it.
4607    Lat and lon are assumed to be in decimal degrees.
4608   
4609    origin is a 3-tuple with geo referenced
4610    UTM coordinates (zone, easting, northing)
4611    It will be the origin of the sww file. This shouldn't be used,
4612    since all of anuga should be able to handle an arbitary origin.
4613
4614
4615    URS C binary format has data orgainised as TIME, LONGITUDE, LATITUDE
4616    which means that latitude is the fastest
4617    varying dimension (row major order, so to speak)
4618
4619    In URS C binary the latitudes and longitudes are in assending order.
4620    """ 
4621    from anuga.pmesh.mesh import Mesh
4622
4623    files_in = [basename_in+'-z-mux',
4624                basename_in+'-e-mux',
4625                basename_in+'-n-mux']
4626    quantities = ['HA','UA','VA']
4627
4628    # instanciate urs_points of the three mux files.
4629    mux = {}
4630    for quantity, file in map(None, quantities, files_in):
4631        mux[quantity] = Urs_points(file)
4632       
4633    # Could check that the depth is the same. (hashing)
4634
4635    # handle to a mux file to do depth stuff
4636    a_mux = mux[quantities[0]]
4637   
4638    # Convert to utm
4639    lat = a_mux.lonlatdep[:,1]
4640    long = a_mux.lonlatdep[:,0]
4641    points_utm, zone = convert_from_latlon_to_utm( \
4642        latitudes=lat, longitudes=long)
4643    #print "points_utm", points_utm
4644    #print "zone", zone
4645
4646    elevation = a_mux.lonlatdep[:,2] * -1 #
4647   
4648    # grid ( create a mesh from the selected points)
4649    # This mesh has a problem.  Triangles are streched over ungridded areas.
4650    #  If these areas could be described as holes in pmesh, that would be great
4651   
4652    mesh = Mesh()
4653    mesh.add_vertices(points_utm)
4654    mesh.auto_segment()
4655    mesh.generate_mesh(minimum_triangle_angle=0.0, verbose=False)
4656    mesh_dic = mesh.Mesh2MeshList()
4657
4658    #mesh.export_mesh_file(basename_in + '.tsh')
4659    # These are the times of the mux file
4660    mux_times = []
4661    for i in range(a_mux.time_step_count):
4662        mux_times.append(a_mux.time_step * i) 
4663    mux_times_start_i, mux_times_fin_i = mux2sww_time(mux_times, mint, maxt)
4664    times = mux_times[mux_times_start_i:mux_times_fin_i]
4665   
4666    if mux_times_start_i == mux_times_fin_i:
4667        # Close the mux files
4668        for quantity, file in map(None, quantities, files_in):
4669            mux[quantity].close()
4670        msg="Due to mint and maxt there's no time info in the boundary SWW."
4671        assert 1==0 , msg #Happy to know a better way of doing this..
4672       
4673    # If this raise is removed there is currently no downstream errors
4674           
4675    points_utm=ensure_numeric(points_utm)
4676    #print "mesh_dic['generatedpointlist']", mesh_dic['generatedpointlist']
4677    #print "points_utm", points_utm
4678    assert ensure_numeric(mesh_dic['generatedpointlist']) == \
4679           ensure_numeric(points_utm)
4680   
4681    volumes = mesh_dic['generatedtrianglelist']
4682   
4683    # write sww intro and grid stuff.   
4684    if basename_out is None:
4685        swwname = basename_in + '.sww'
4686    else:
4687        swwname = basename_out + '.sww'
4688
4689    outfile = NetCDFFile(swwname, 'w')
4690    # For a different way of doing this, check out tsh2sww
4691    # work out sww_times and the index range this covers
4692    write_sww_header(outfile, times, len(volumes), len(points_utm))
4693    outfile.mean_stage = mean_stage
4694    outfile.zscale = zscale
4695    write_sww_triangulation(outfile, points_utm, volumes,
4696                            elevation, zone,  origin=origin, verbose=verbose)
4697   
4698    if verbose: print 'Converting quantities'
4699    j = 0
4700    # Read in a time slice from each mux file and write it to the sww file
4701    for HA, UA, VA in map(None, mux['HA'], mux['UA'], mux['VA']):
4702        if j >= mux_times_start_i and j < mux_times_fin_i:
4703            write_sww_time_slice(outfile, HA, UA, VA,  elevation,
4704                                 j - mux_times_start_i,
4705                                 mean_stage=mean_stage, zscale=zscale,
4706                                 verbose=verbose)
4707        j += 1
4708    outfile.close()
4709    #
4710    # Do some conversions while writing the sww file
4711def mux2sww_time(mux_times, mint, maxt):
4712    """
4713    """
4714
4715    if mint == None:
4716        mux_times_start_i = 0
4717    else:
4718        mux_times_start_i = searchsorted(mux_times, mint)
4719       
4720    if maxt == None:
4721        mux_times_fin_i = len(mux_times)
4722    else:
4723        maxt += 0.5 # so if you specify a time where there is
4724                    # data that time will be included
4725        mux_times_fin_i = searchsorted(mux_times, maxt)
4726
4727    return mux_times_start_i, mux_times_fin_i
4728
4729def write_sww_header(outfile, times, number_of_volumes, number_of_points ):
4730    """
4731    outfile - the name of the file that will be written
4732    times - A list of the time slice times
4733    number_of_volumes - the number of triangles
4734    """
4735    number_of_times = len(times)
4736   
4737    outfile.institution = 'Geoscience Australia'
4738    outfile.description = 'Converted from XXX'
4739
4740
4741    #For sww compatibility
4742    outfile.smoothing = 'Yes'
4743    outfile.order = 1
4744
4745    #Start time in seconds since the epoch (midnight 1/1/1970)
4746    if len(times) == 0:
4747        outfile.starttime = starttime = 0
4748    else:
4749        outfile.starttime = starttime = times[0]
4750
4751
4752    # dimension definitions
4753    outfile.createDimension('number_of_volumes', number_of_volumes)
4754
4755    outfile.createDimension('number_of_vertices', 3)
4756    outfile.createDimension('number_of_points', number_of_points)
4757    outfile.createDimension('number_of_timesteps', number_of_times)
4758
4759    # variable definitions
4760    outfile.createVariable('x', precision, ('number_of_points',))
4761    outfile.createVariable('y', precision, ('number_of_points',))
4762    outfile.createVariable('elevation', precision, ('number_of_points',))
4763
4764    #FIXME: Backwards compatibility
4765    outfile.createVariable('z', precision, ('number_of_points',))
4766    #################################
4767
4768    outfile.createVariable('volumes', Int, ('number_of_volumes',
4769                                            'number_of_vertices'))
4770
4771    outfile.createVariable('time', precision,
4772                           ('number_of_timesteps',))
4773
4774    outfile.createVariable('stage', precision,
4775                           ('number_of_timesteps',
4776                            'number_of_points'))
4777
4778    outfile.createVariable('xmomentum', precision,
4779                           ('number_of_timesteps',
4780                            'number_of_points'))
4781
4782    outfile.createVariable('ymomentum', precision,
4783                           ('number_of_timesteps',
4784                            'number_of_points'))
4785   
4786    outfile.variables['time'][:] = times   
4787
4788
4789def write_sww_triangulation(outfile, points_utm, volumes,
4790                            elevation, zone, origin=None, verbose=False):
4791
4792    number_of_points = len(points_utm)   
4793    volumes = array(volumes)
4794
4795    if origin is not None:
4796        if isinstance(origin, Geo_reference): 
4797            geo_ref = origin
4798        else:
4799            geo_ref = apply(Geo_reference,origin)
4800    else:
4801        geo_ref = Geo_reference(zone,min(points_utm[:,0]),min(points_utm[:,1]))
4802    #geo_ref = Geo_reference(zone,0,0)
4803    geo_ref.write_NetCDF(outfile)
4804
4805    # This will put the geo ref in the middle
4806    #geo_ref = Geo_reference(refzone,(max(x)+min(x))/2.0,(max(x)+min(y))/2.)
4807    x =  points_utm[:,0]
4808    y =  points_utm[:,1]
4809    z = outfile.variables['z'][:]
4810    #FIXME the w.r.t time is lost now..
4811    if verbose:
4812        print '------------------------------------------------'
4813        print 'More Statistics:'
4814        print '  Extent (/lon):'
4815        print '    x in [%f, %f], len(lat) == %d'\
4816              %(min(x), max(x),
4817                len(x))
4818        print '    y in [%f, %f], len(lon) == %d'\
4819              %(min(y), max(y),
4820                len(y))
4821        print '    z in [%f, %f], len(z) == %d'\
4822              %(min(elevation), max(elevation),
4823                len(elevation))
4824        print 'geo_ref: ',geo_ref
4825        print '------------------------------------------------'
4826
4827    #z = resize(bath_grid,outfile.variables['z'][:].shape)
4828    outfile.variables['x'][:] = points_utm[:,0] - geo_ref.get_xllcorner()
4829    outfile.variables['y'][:] = points_utm[:,1] - geo_ref.get_yllcorner()
4830    outfile.variables['z'][:] = elevation
4831    outfile.variables['elevation'][:] = elevation  #FIXME HACK
4832    outfile.variables['volumes'][:] = volumes.astype(Int32) #On Opteron 64
4833
4834
4835def write_sww_time_slices(outfile, has, uas, vas, elevation,
4836                         mean_stage=0, zscale=1,
4837                         verbose=False):   
4838    #Time stepping
4839    stage = outfile.variables['stage']
4840    xmomentum = outfile.variables['xmomentum']
4841    ymomentum = outfile.variables['ymomentum']
4842
4843    n = len(has)
4844    j=0
4845    for ha, ua, va in map(None, has, uas, vas):
4846        if verbose and j%((n+10)/10)==0: print '  Doing %d of %d' %(j, n)
4847        w = zscale*ha + mean_stage
4848        stage[j] = w
4849        h = w - elevation
4850        xmomentum[j] = ua*h
4851        ymomentum[j] = va*h
4852        j += 1
4853
4854def write_sww_time_slice(outfile, ha, ua, va, elevation, slice_index,
4855                         mean_stage=0, zscale=1,
4856                         verbose=False):   
4857    #Time stepping
4858    stage = outfile.variables['stage']
4859    xmomentum = outfile.variables['xmomentum']
4860    ymomentum = outfile.variables['ymomentum']
4861   
4862    w = zscale*ha + mean_stage
4863    stage[slice_index] = w
4864    h = w - elevation
4865    xmomentum[slice_index] = ua*h
4866    ymomentum[slice_index] = va*h
4867
4868def URS2txt(basename_in):
4869    """
4870    Not finished or tested
4871    """
4872    files_in = [basename_in+'-z-mux',
4873                basename_in+'-e-mux',
4874                basename_in+'-n-mux']
4875    quantities = ['HA','UA','VA']
4876
4877    # instanciate urs_points of the three mux files.
4878    mux = {}
4879    for quantity, file in map(None, quantities, files_in):
4880        mux[quantity] = Urs_points(file)
4881       
4882    # Could check that the depth is the same. (hashing)
4883
4884    # handle to a mux file to do depth stuff
4885    a_mux = mux[quantities[0]]
4886   
4887    # Convert to utm
4888    lat = a_mux.lonlatdep[:,1]
4889    long = a_mux.lonlatdep[:,0]
4890    points_utm, zone = convert_from_latlon_to_utm( \
4891        latitudes=lat, longitudes=long)
4892    #print "points_utm", points_utm
4893    #print "zone", zone
4894    elevations = a_mux.lonlatdep[:,2] * -1 #
4895   
4896    fid = open(basename_in + '.txt', 'w')
4897
4898    fid.write("zone" + str(zone) + "\n")
4899
4900    for elevation, point_utm in map(None, elevations, points_utm):
4901        pass
4902   
4903class Urs_points:
4904    """
4905    Read the info in URS mux files.
4906
4907    for the quantities heres a correlation between the file names and
4908    what they mean;
4909    z-mux is height above sea level, m
4910    e-mux is velocity is Eastern direction, m/s
4911    n-mux is velocity is Northern direction, m/s   
4912    """
4913    def __init__(self,urs_file):
4914        self.iterated = False
4915        columns = 3 # long, lat , depth
4916        mux_file = open(urs_file, 'rb')
4917       
4918        # Number of points/stations
4919        (self.points_num,)= unpack('i',mux_file.read(4))
4920       
4921        # nt, int - Number of time steps
4922        (self.time_step_count,)= unpack('i',mux_file.read(4))
4923        #print "self.time_step_count", self.time_step_count
4924        #dt, float - time step, seconds
4925        (self.time_step,) = unpack('f', mux_file.read(4))
4926        #print "self.time_step", self.time_step
4927        msg = "Bad data in the urs file."
4928        if self.points_num < 0:
4929            mux_file.close()
4930            raise ANUGAError, msg
4931        if self.time_step_count < 0:
4932            mux_file.close()
4933            raise ANUGAError, msg
4934        if self.time_step < 0:
4935            mux_file.close()
4936            raise ANUGAError, msg
4937
4938        # the depth is in meters, and it is the distance from the ocean
4939        # to the sea bottom.
4940        lonlatdep = p_array.array('f')
4941        lonlatdep.read(mux_file, columns * self.points_num)
4942        lonlatdep = array(lonlatdep, typecode=Float)   
4943        lonlatdep = reshape(lonlatdep, (self.points_num, columns))
4944        #print 'lonlatdep',lonlatdep
4945        self.lonlatdep = lonlatdep
4946       
4947        self.mux_file = mux_file
4948        # check this array
4949
4950    def __iter__(self):
4951        """
4952        iterate over quantity data which is with respect to time.
4953
4954        Note: You can only interate once over an object
4955       
4956        returns quantity infomation for each time slice
4957        """
4958        msg =  "You can only interate once over a urs file."
4959        assert not self.iterated, msg
4960        self.iter_time_step = 0
4961        self.iterated = True
4962        return self
4963   
4964    def next(self):
4965        if self.time_step_count == self.iter_time_step:
4966            self.close()
4967            raise StopIteration
4968        #Read in a time slice  from mux file 
4969        hz_p_array = p_array.array('f')
4970        hz_p_array.read(self.mux_file, self.points_num)
4971        hz_p = array(hz_p_array, typecode=Float)
4972        self.iter_time_step += 1
4973       
4974        return hz_p
4975
4976    def close(self):
4977        self.mux_file.close()
4978
4979    #### END URS UNGRIDDED 2 SWW ###
4980
4981#-------------------------------------------------------------
4982if __name__ == "__main__":
4983    points=Urs_points('urs2sww_test/o_velocity-e-mux')
4984    for i in points:
4985        print 'i',i
4986        pass
4987
Note: See TracBrowser for help on using the repository browser.