source: inundation/geospatial_data/geospatial_data.py @ 2570

Last change on this file since 2570 was 2570, checked in by nick, 18 years ago

Added functionality to geospatial_data.py including import and exporting pts and xya files

File size: 20.5 KB
Line 
1"""Class Geospatial_data - Manipulation of locations on the planet and
2associated attributes.
3
4"""
5
6
7from utilities.numerical_tools import ensure_numeric
8
9from Numeric import concatenate, array, Float, shape
10
11from coordinate_transforms.geo_reference import Geo_reference
12
13from os import access, F_OK, R_OK
14       
15class Geospatial_data:
16
17    def __init__(self,
18                 data_points = None,
19                 attributes = None,
20                 geo_reference = None,
21                 default_attribute_name = None,
22                 file_name = None):
23         
24
25        """Create instance from data points and associated attributes
26
27
28        data_points: x,y coordinates in meters. Type must be either a
29        sequence of 2-tuples or an Mx2 Numeric array of floats.
30
31        attributes: Associated values for each data point. The type
32        must be either a list or an array of length M or a dictionary
33        of lists (or arrays) of length M. In the latter case the keys
34        in the dictionary represent the attribute names, in the former
35        the attribute will get the default name 'attribute'.
36       
37        geo_reference: Object representing the origin of the data
38        points. It contains UTM zone, easting and northing and data
39        points are assumed to be relative to this origin.
40        If geo_reference is None, the default geo ref object is used
41
42        default_attribute_name: Name of default attribute to be used with
43        get_attribute_values. The idea is that the dataset can be
44        equipped with information about which attribute to return.
45        If None, the default is the 'first'
46       
47        file_name: Name of input file.....
48       
49        """
50
51        if file_name is None:
52            self.file_name = None
53            self.check_data_points(data_points)
54
55            self.set_attributes(attributes)
56            self.set_geo_reference(geo_reference)
57            self.set_default_attribute_name(default_attribute_name)
58           
59       
60        else:
61#            print 'from init file name:', file_name
62            if access(file_name, F_OK) == 0 :
63                msg = 'File %s does not exist or is not accessable' %file_name
64                raise msg
65            else:
66                data = {}   
67# watch for case where file name and points, attributes etc are provided!!
68# if file name then all provided info will be removed!
69                self.file_name = file_name
70                data = import_points_file(self.file_name)
71               
72#                print 'data: point in init', data['pointlist']
73#                print 'attrib: point in init', data['attributelist']
74#                print 'geo_ref: point in init', data['geo_reference']
75               
76                data_points = data['pointlist']
77                attributes = data['attributelist']
78                get_reference = data['geo_reference']
79               
80                self.check_data_points(data_points)
81                self.set_attributes(attributes)
82                self.set_geo_reference(geo_reference)
83                self.set_default_attribute_name(default_attribute_name)
84
85       
86    def check_data_points(self, data_points):
87        """Checks data points
88        """
89       
90        if data_points is None:
91            self.data_points = None
92#           FIXME: should throw an error if bad file name and no data!
93            print 'There is no data or files to read for data!!'
94            msg = 'file name %s does not exist in data set and the ' %self.file_name
95#           some assert
96           
97        else:
98            self.data_points = ensure_numeric(data_points)
99
100    def set_attributes(self, attributes):
101        """Check and assign attributes dictionary
102        """
103       
104        from types import DictType
105       
106        if attributes is None:
107            self.attributes = None
108            return
109       
110        if not isinstance(attributes, DictType):
111            #Convert single attribute into dictionary
112            attributes = {'attribute': attributes}
113
114        #Check input attributes   
115        for key in attributes.keys():
116            try:
117                attributes[key] = ensure_numeric(attributes[key])
118            except:
119                msg = 'Attribute %s could not be converted' %key
120                msg += 'to a numeric vector'
121                raise msg
122
123        self.attributes = attributes   
124
125
126    def set_geo_reference(self, geo_reference):
127
128        from coordinate_transforms.geo_reference import Geo_reference
129
130
131        if geo_reference is None:
132            self.geo_reference = Geo_reference() # Use default
133        elif isinstance(geo_reference, Geo_reference):
134            self.geo_reference = geo_reference
135        else:
136            msg = 'Argument geo_reference must be a valid Geo_reference \n'
137            msg += 'object or None.'
138            raise msg
139
140
141    def set_default_attribute_name(self, default_attribute_name):
142        self.default_attribute_name = default_attribute_name
143
144    def set_file_name(self, file_name = None):
145        if file_name is None:
146            self.file_name = None
147        else:
148            if access(file_name, F_OK) == 0 :
149                msg = 'Geospatial_data object needs have either data_points \n'
150                msg += ' or a file with data points'
151                raise msg
152            else:   
153                self.file_name = file_name
154                print 'file name from set', self.file_name
155
156    def set_self_from_file(self, file_name = None):
157        # check if file_name is a string and also that the file exists
158        if file_name is None:
159            self.file_name = None
160#        else:
161#            if access(self.file_name, F_OK) == 0 :
162#                msg = 'Geospatial_data object needs have either data_points \n'
163#                msg += ' or a file with data points'
164#                raise msg
165        else:   
166            self.file_name = file_name
167            self = self.import_points_file(file_name)
168#            print 'data points from set self file name', self.data_points
169#            print 'attributes from set self file name', self.attributes
170#            print 'geo_ref from set self file name', self.geo_reference
171        return self
172           
173
174    def get_geo_reference(self):
175        return self.geo_reference
176       
177    def get_data_points(self, absolute = True):
178        if absolute is True:
179            xll = self.geo_reference.xllcorner
180            yll = self.geo_reference.yllcorner
181            return self.data_points + [xll, yll]
182           
183        else: 
184            return self.data_points
185   
186    def get_attributes(self, attribute_name = None):
187        """Return values for one named attribute.
188
189        If attribute_name is None, default_attribute_name is used
190        """
191
192        if attribute_name is None:
193            if self.default_attribute_name is not None:
194                attribute_name = self.default_attribute_name
195            else:
196                attribute_name = self.attributes.keys()[0] 
197                # above line takes the first one from keys
198               
199
200        msg = 'Attribute name %s does not exist in data set' %attribute_name
201        assert self.attributes.has_key(attribute_name), msg
202
203        return self.attributes[attribute_name]
204
205    def __add__(self,other):
206        """
207        returns the addition of 2 geospatical objects,
208        objects are concatencated to the end of each other
209           
210        NOTE: doesn't add if objects contain different
211        attributes 
212        """
213
214        # sets xll and yll as the smallest from self and other
215        if self.geo_reference.xllcorner <= other.geo_reference.xllcorner:
216            xll = self.geo_reference.xllcorner
217        else:
218            xll = other.geo_reference.xllcorner
219
220        if self.geo_reference.yllcorner <= other.geo_reference.yllcorner:
221            yll = self.geo_reference.yllcorner
222        else:
223            yll = other.geo_reference.yllcorner
224
225        # find objects zone and checks if the same
226        geo_ref1 = self.get_geo_reference()
227        zone1 = geo_ref1.get_zone()
228       
229        geo_ref2 = other.get_geo_reference()
230        zone2 = geo_ref2.get_zone()
231       
232        geo_ref = Geo_reference(zone1, xll, yll)
233       
234        if zone1 == zone2:
235            a_rel_points = self.get_data_points()
236            b_rel_points = other.get_data_points()
237           
238            # subtracts xll and yll from self's and other's
239            # reletive data point and concatenates
240            c_points = concatenate((a_rel_points-[xll, yll],
241                                    b_rel_points-[xll, yll]), axis = 0)
242
243            new_dictionary = {}
244            for x in self.attributes.keys():
245                if other.attributes.has_key(x):
246
247                    a_attrib = self.attributes[x]
248                    b_attrib = other.attributes[x]
249                    new_dictionary[x] = concatenate((a_attrib, b_attrib))
250
251                else:
252                    msg = 'Both geospatial_data objects must have the same \n'
253                    msg += 'attributes to allow addition.'
254                    raise msg
255 
256            return Geospatial_data(c_points, new_dictionary, geo_ref)
257           
258        else:
259            msg = 'Both geospatial_data objects must be in the same \
260            ZONE to allow addition.'
261            raise msg
262
263     
264   
265    ###
266    #  IMPORT/EXPORT POINTS FILES
267    ###
268    '''
269    def export_points_file(ofile, point_dict):
270        """
271        write a points file, ofile, as a binary (.xya) or text (.pts) file
272
273        ofile is the file name, including the extension
274
275        The point_dict is defined at the top of this file.
276        """
277        #this was done for all keys in the mesh file.
278        #if not mesh_dict.has_key('points'):
279        #    mesh_dict['points'] = []
280        if (ofile[-4:] == ".xya"):
281            _write_xya_file(ofile, point_dict)
282        elif (ofile[-4:] == ".pts"):
283            _write_pts_file(ofile, point_dict)
284        else:
285            msg = 'Unknown file type %s ' %ofile
286        raise IOError, msg
287
288    '''       
289def import_points_file( ofile, delimiter = None, verbose = False):
290    """ load an .xya or .pts file
291    Note: will throw an IOError if it can't load the file.
292    Catch these!
293    """
294   
295    all_data = {}
296    if ofile[-4:]== ".xya":
297        try:
298            if delimiter == None:
299                try:
300                    fd = open(ofile)
301                    all_data = _read_xya_file(fd, ',')
302                except SyntaxError:
303                    fd.close()
304                    fd = open(ofile)
305                    all_data = _read_xya_file(fd, ' ')
306            else:
307                fd = open(ofile)
308                all_data = _read_xya_file(fd, delimiter)
309            fd.close()
310        except (IndexError,ValueError,SyntaxError):
311            fd.close()   
312            msg = 'Could not open file %s ' %ofile
313            raise IOError, msg
314        except IOError:
315            # Catch this to add an error message
316            msg = 'Could not open file %s ' %ofile
317            raise IOError, msg
318           
319    elif ofile[-4:]== ".pts":
320        try:
321#            print 'hi from import_points_file'
322            all_data = _read_pts_file(ofile, verbose)
323#            print 'hi1 from import_points_file', all_data
324        except IOError, e:   
325            msg = 'Could not open file %s ' %ofile
326            raise IOError, msg       
327    else:     
328        msg = 'Extension %s is unknown' %ofile[-4:]
329        raise IOError, msg
330
331    return all_data
332
333def export_points_file(ofile, point_dict):
334    """
335    write a points file, ofile, as a text (.xya) or binary (.pts) file
336
337    ofile is the file name, including the extension
338
339    The point_dict is defined at the top of this file.
340    """
341    #this was done for all keys in the mesh file.
342    #if not mesh_dict.has_key('points'):
343    #    mesh_dict['points'] = []
344    if (ofile[-4:] == ".xya"):
345        _write_xya_file(ofile, point_dict)
346    elif (ofile[-4:] == ".pts"):
347        _write_pts_file(ofile, point_dict)
348    else:
349        msg = 'Unknown file type %s ' %ofile
350        raise IOError, msg
351
352def _read_pts_file(file_name, verbose = False):
353    """Read .pts NetCDF file
354   
355    Return a dic of array of points, and dic of array of attribute
356    eg
357    dic['points'] = [[1.0,2.0],[3.0,5.0]]
358    dic['attributelist']['elevation'] = [[7.0,5.0]
359    """   
360    #FIXME: (DSG) This format has issues.
361    # There can't be an attribute called points
362    # consider format change
363
364#    print 'hi for read points file'
365    from Scientific.IO.NetCDF import NetCDFFile
366   
367    if verbose: print 'Reading ', file_name
368   
369       
370    # see if the file is there.  Throw a QUIET IO error if it isn't
371    fd = open(file_name,'r')
372    fd.close()
373   
374    #throws prints to screen if file not present
375    fid = NetCDFFile(file_name, 'r') 
376   
377    point_atts = {} 
378        # Get the variables
379    point_atts['pointlist'] = array(fid.variables['points'])
380    keys = fid.variables.keys()
381    if verbose: print 'Got %d variables: %s' %(len(keys), keys)
382    try:
383        keys.remove('points')
384    except IOError, e:       
385        fid.close()   
386        msg = 'Expected keyword "points" but could not find it'
387        raise IOError, msg
388   
389    attributes = {}
390    for key in keys:
391        if verbose: print "reading attribute '%s'" %key
392           
393        attributes[key] = array(fid.variables[key])
394   
395    point_atts['attributelist'] = attributes
396   
397    try:
398        geo_reference = Geo_reference(NetCDFObject=fid)
399        point_atts['geo_reference'] = geo_reference
400    except AttributeError, e:
401        #geo_ref not compulsory
402        point_atts['geo_reference'] = None
403   
404    fid.close()
405    return point_atts
406
407
408def _read_xya_file( fd, delimiter):
409#    print 'hello from read xya data'
410    points = []
411    pointattributes = []
412    title = fd.readline()
413    att_names = clean_line(title,delimiter)
414   
415    att_dict = {}
416    line = fd.readline()
417    numbers = clean_line(line,delimiter)
418    while len(numbers) > 1:
419        if numbers != []:
420            try:
421                x = float(numbers[0])
422                y = float(numbers[1])
423                points.append([x,y])
424                numbers.pop(0)
425                numbers.pop(0)
426                #attributes = []
427                #print "att_names",att_names
428                #print "numbers",numbers
429                if len(att_names) != len(numbers):
430                    fd.close()
431                    # It might not be a problem with the title
432                    #raise TitleAmountError
433                    raise IOError
434                for i,num in enumerate(numbers):
435                    num.strip()
436                    if num != '\n' and num != '':
437                        #attributes.append(float(num))
438                        att_dict.setdefault(att_names[i],[]).append(float(num))
439                       
440            except ValueError:
441                raise SyntaxError
442        line = fd.readline()
443        numbers = clean_line(line,delimiter)
444   
445    if line == '':
446            # end of file
447        geo_reference = None
448    else:
449        geo_reference = Geo_reference(ASCIIFile=fd,read_title=line)
450   
451    xya_dict = {}
452    xya_dict['pointlist'] = array(points).astype(Float)
453   
454    for key in att_dict.keys():
455        att_dict[key] = array(att_dict[key]).astype(Float)
456    xya_dict['attributelist'] = att_dict
457   
458    xya_dict['geo_reference'] = geo_reference
459    #print "xya_dict",xya_dict
460    return xya_dict
461
462
463
464def _write_pts_file(file_name, point_atts):
465    """Write .pts NetCDF file   
466
467    WARNING: This function mangles the point_atts data structure
468    """
469    #FIXME: (DSG)This format has issues.
470    # There can't be an attribute called points
471    # consider format change
472
473
474    legal_keys = ['pointlist', 'attributelist', 'geo_reference']
475    for key in point_atts.keys():
476        msg = 'Key %s is illegal. Valid keys are %s' %(key, legal_keys) 
477        assert key in legal_keys, msg
478   
479    from Scientific.IO.NetCDF import NetCDFFile
480    _point_atts2array(point_atts)
481    # NetCDF file definition
482    outfile = NetCDFFile(file_name, 'w')
483   
484    #Create new file
485    outfile.institution = 'Geoscience Australia'
486    outfile.description = 'NetCDF format for compact and portable storage ' +\
487                      'of spatial point data'
488   
489    # dimension definitions
490    shape = point_atts['pointlist'].shape[0]
491    outfile.createDimension('number_of_points', shape) 
492    outfile.createDimension('number_of_dimensions', 2) #This is 2d data
493   
494    # variable definition
495    outfile.createVariable('points', Float, ('number_of_points',
496                                             'number_of_dimensions'))
497
498    #create variables 
499    outfile.variables['points'][:] = point_atts['pointlist'] #.astype(Float32)
500    for key in point_atts['attributelist'].keys():
501        outfile.createVariable(key, Float, ('number_of_points',))
502        outfile.variables[key][:] = point_atts['attributelist'][key] #.astype(Float32)
503       
504    if point_atts.has_key('geo_reference') and not point_atts['geo_reference'] == None:
505        point_atts['geo_reference'].write_NetCDF(outfile)
506       
507    outfile.close() 
508 
509
510
511def _write_xya_file( file_name, xya_dict, delimiter = ','):
512    """
513    export a file, ofile, with the xya format
514   
515    """
516    points = xya_dict['pointlist'] 
517    pointattributes = xya_dict['attributelist']
518   
519    fd = open(file_name,'w')
520 
521    titlelist = ""
522    for title in pointattributes.keys():
523        titlelist = titlelist + title + delimiter
524    titlelist = titlelist[0:-len(delimiter)] # remove the last delimiter
525    fd.write(titlelist+"\n")
526    #<vertex #> <x> <y> [attributes]
527    for i,vert in enumerate( points):
528       
529        attlist = ","
530        for att in pointattributes.keys():
531            attlist = attlist + str(pointattributes[att][i])+ delimiter
532        attlist = attlist[0:-len(delimiter)] # remove the last delimiter
533        attlist.strip()
534        fd.write( str(vert[0]) + delimiter
535                  + str(vert[1])
536                  + attlist + "\n")
537   
538    # geo_reference info
539    if xya_dict.has_key('geo_reference') and \
540           not xya_dict['geo_reference'] is None:
541        xya_dict['geo_reference'].write_ASCII(fd)
542    fd.close()
543   
544def _point_atts2array(point_atts):
545    point_atts['pointlist'] = array(point_atts['pointlist']).astype(Float)
546   
547    for key in point_atts['attributelist'].keys():
548        point_atts['attributelist'][key]= array(point_atts['attributelist'][key]).astype(Float)
549    return point_atts
550
551   
552
553
554def geospatial_data2points_dictionary(geospatial_data):
555    """Convert geospatial data to points_dictionary
556    """
557
558    points_dictionary = {}
559    points_dictionary['pointlist'] = geospatial_data.data_points
560
561    points_dictionary['attributelist'] = {}
562
563    for attribute_name in geospatial_data.attributes.keys():
564        val = geospatial_data.attributes[attribute_name]
565        points_dictionary['attributelist'][attribute_name] = val
566
567    points_dictionary['geo_reference'] = geospatial_data.geo_reference
568
569    return points_dictionary
570
571   
572def points_dictionary2geospatial_data(points_dictionary):
573    """Convert points_dictionary to geospatial data object
574    """
575
576    msg = 'Points dictionary must have key pointlist' 
577    assert points_dictionary.has_key('pointlist'), msg
578
579    msg = 'Points dictionary must have key attributelist'     
580    assert points_dictionary.has_key('attributelist'), msg       
581
582    if points_dictionary.has_key('geo_reference'):
583        geo = points_dictionary['geo_reference']
584    else:
585        geo = None
586   
587    return Geospatial_data(points_dictionary['pointlist'],
588                           points_dictionary['attributelist'],
589                           geo_reference = geo)
590
591def clean_line(line,delimiter):     
592    """Remove whitespace
593    """
594    #print ">%s" %line
595    line = line.strip()
596    #print "stripped>%s" %line
597    numbers = line.split(delimiter)
598    i = len(numbers) - 1
599    while i >= 0:
600        if numbers[i] == '':
601            numbers.pop(i)
602        else:
603            numbers[i] = numbers[i].strip()
604       
605        i += -1
606    #for num in numbers:
607    #    print "num>%s<" %num
608    return numbers
609           
610#def add_points_files(file):           
611       
Note: See TracBrowser for help on using the repository browser.