source: branches/numpy/anuga/coordinate_transforms/geo_reference.py @ 7195

Last change on this file since 7195 was 7193, checked in by rwilson, 16 years ago

Back-merge from Numeric trunk.

File size: 16.7 KB
RevLine 
[2488]1"""
[4451]2
[2488]3"""
4
5
6#FIXME: Ensure that all attributes of a georef are treated everywhere
7#and unit test
8
[2593]9import types, sys
[6441]10import copy
11
[3514]12from anuga.utilities.numerical_tools import ensure_numeric
[6360]13from anuga.utilities.anuga_exceptions import ANUGAError, TitleError, \
14                                             ParsingError, ShapeError
[6304]15from anuga.config import netcdf_float, netcdf_int, netcdf_float32
[2488]16
[6304]17import numpy as num
[2941]18
[6149]19
[2602]20DEFAULT_ZONE = -1
[6360]21TITLE = '#geo reference' + "\n" # this title is referred to in the test format
[2488]22
[4387]23DEFAULT_PROJECTION = 'UTM'
24DEFAULT_DATUM = 'wgs84'
25DEFAULT_UNITS = 'm'
26DEFAULT_FALSE_EASTING = 500000
[6360]27DEFAULT_FALSE_NORTHING = 10000000    # Default for southern hemisphere
[4387]28
[6360]29
30##
31# @brief A class for ...
[2488]32class Geo_reference:
33    """
[6360]34    Attributes of the Geo_reference class:
35        .zone           The UTM zone (default is -1)
36        .false_easting  ??
37        .false_northing ??
38        .datum          The Datum used (default is wgs84)
39        .projection     The projection used (default is 'UTM')
40        .units          The units of measure used (default metres)
41        .xllcorner      The X coord of origin (default is 0.0 wrt UTM grid)
42        .yllcorner      The y coord of origin (default is 0.0 wrt UTM grid)
43        .is_absolute    ??
44
[2488]45    """
46
[6360]47    ##
48    # @brief Instantiate an instance of class Geo_reference.
49    # @param zone The UTM zone.
50    # @param xllcorner X coord of origin of georef.
51    # @param yllcorner Y coord of origin of georef.
52    # @param datum ??
53    # @param projection The projection used (default UTM).
54    # @param units Units used in measuring distance (default m).
55    # @param false_easting ??
56    # @param false_northing ??
57    # @param NetCDFObject NetCDF file *handle* to write to.
58    # @param ASCIIFile ASCII text file *handle* to write to.
59    # @param read_title Title of the georeference text.
[2488]60    def __init__(self,
[6360]61                 zone=DEFAULT_ZONE,
62                 xllcorner=0.0,
63                 yllcorner=0.0,
64                 datum=DEFAULT_DATUM,
65                 projection=DEFAULT_PROJECTION,
66                 units=DEFAULT_UNITS,
67                 false_easting=DEFAULT_FALSE_EASTING,
68                 false_northing=DEFAULT_FALSE_NORTHING,
[2488]69                 NetCDFObject=None,
70                 ASCIIFile=None,
71                 read_title=None):
72        """
73        input:
[6360]74        NetCDFObject - a handle to the netCDF file to be written to
[2488]75        ASCIIFile - a handle to the text file
76        read_title - the title of the georeference text, if it was read in.
[2941]77         If the function that calls this has already read the title line,
78         it can't unread it, so this info has to be passed.
79         If you know of a way to unread this info, then tell us.
[2488]80
[4387]81         Note, the text file only saves a sub set of the info the
82         points file does.  Currently the info not written in text
83         must be the default info, since ANUGA assumes it isn't
84         changing.
[6360]85        """
86
[4451]87        if zone is None:
88            zone = DEFAULT_ZONE
[7061]89        self.false_easting = int(false_easting)
90        self.false_northing = int(false_northing)
[2488]91        self.datum = datum
92        self.projection = projection
[7061]93        self.zone = int(zone)
[2488]94        self.units = units
[7061]95        self.xllcorner = float(xllcorner)
96        self.yllcorner = float(yllcorner)
[6553]97           
[2488]98        if NetCDFObject is not None:
99            self.read_NetCDF(NetCDFObject)
[6360]100
[2488]101        if ASCIIFile is not None:
102            self.read_ASCII(ASCIIFile, read_title=read_title)
[6553]103           
104        # Set flag for absolute points (used by get_absolute)   
105        self.absolute = num.allclose([self.xllcorner, self.yllcorner], 0)
106           
107
[2488]108    def get_xllcorner(self):
109        return self.xllcorner
[6360]110
111    ##
112    # @brief Get the Y coordinate of the origin of this georef.
[2488]113    def get_yllcorner(self):
114        return self.yllcorner
[6360]115
116    ##
117    # @brief Get the zone of this georef.
[2488]118    def get_zone(self):
119        return self.zone
[6360]120
121    ##
122    # @brief Write <something> to an open NetCDF file.
123    # @param outfile Handle to open NetCDF file.
[2488]124    def write_NetCDF(self, outfile):
[6360]125        outfile.xllcorner = self.xllcorner
[2488]126        outfile.yllcorner = self.yllcorner
127        outfile.zone = self.zone
128
129        outfile.false_easting = self.false_easting
130        outfile.false_northing = self.false_northing
131
[6360]132        outfile.datum = self.datum
[2488]133        outfile.projection = self.projection
134        outfile.units = self.units
135
[6360]136    ##
137    # @brief Read data from an open NetCDF file.
138    # @param infile Handle to open NetCDF file.
[2488]139    def read_NetCDF(self, infile):
[7061]140        self.xllcorner = float(infile.xllcorner[0])
141        self.yllcorner = float(infile.yllcorner[0])
142        self.zone = int(infile.zone[0])
[2488]143
[4389]144        try:
[7061]145            self.false_easting = int(infile.false_easting[0])
146            self.false_northing = int(infile.false_northing[0])
[6360]147
148            self.datum = infile.datum
[4389]149            self.projection = infile.projection
150            self.units = infile.units
151        except:
152            pass
[6360]153
154        if self.false_easting != DEFAULT_FALSE_EASTING:
155            print "WARNING: False easting of %f specified." % self.false_easting
156            print "Default false easting is %f." % DEFAULT_FALSE_EASTING
[4451]157            print "ANUGA does not correct for differences in False Eastings."
[6360]158
159        if self.false_northing != DEFAULT_FALSE_NORTHING:
160            print ("WARNING: False northing of %f specified."
161                   % self.false_northing)
162            print "Default false northing is %f." % DEFAULT_FALSE_NORTHING
[4451]163            print "ANUGA does not correct for differences in False Northings."
[6360]164
165        if self.datum.upper() != DEFAULT_DATUM.upper():
166            print "WARNING: Datum of %s specified." % self.datum
167            print "Default Datum is %s." % DEFAULT_DATUM
[4451]168            print "ANUGA does not correct for differences in datums."
[6360]169
170        if self.projection.upper() != DEFAULT_PROJECTION.upper():
171            print "WARNING: Projection of %s specified." % self.projection
172            print "Default Projection is %s." % DEFAULT_PROJECTION
[4451]173            print "ANUGA does not correct for differences in Projection."
[6360]174
175        if self.units.upper() != DEFAULT_UNITS.upper():
176            print "WARNING: Units of %s specified." % self.units
177            print "Default units is %s." % DEFAULT_UNITS
[4451]178            print "ANUGA does not correct for differences in units."
[6360]179
180################################################################################
181# ASCII files with geo-refs are currently not used
182################################################################################
183
184    ##
185    # @brief Write georef data to an open text file.
186    # @param fd Handle to open text file.
[2488]187    def write_ASCII(self, fd):
188        fd.write(TITLE)
[6360]189        fd.write(str(self.zone) + "\n")
190        fd.write(str(self.xllcorner) + "\n")
[2488]191        fd.write(str(self.yllcorner) + "\n")
192
[6360]193    ##
194    # @brief Read georef data from an open text file.
195    # @param fd Handle to open text file.
196    def read_ASCII(self, fd, read_title=None):
[2942]197        try:
198            if read_title == None:
[6360]199                read_title = fd.readline()     # remove the title line
[2942]200            if read_title[0:2].upper() != TITLE[0:2].upper():
[6360]201                msg = ('File error.  Expecting line: %s.  Got this line: %s'
202                       % (TITLE, read_title))
203                raise TitleError, msg
[2942]204            self.zone = int(fd.readline())
205            self.xllcorner = float(fd.readline())
206            self.yllcorner = float(fd.readline())
207        except SyntaxError:
[6360]208            msg = 'File error.  Got syntax error while parsing geo reference'
209            raise ParsingError, msg
210
[2488]211        # Fix some assertion failures
[6304]212        if isinstance(self.zone, num.ndarray) and self.zone.shape == ():
[2488]213            self.zone = self.zone[0]
[6360]214        if (isinstance(self.xllcorner, num.ndarray) and
215                self.xllcorner.shape == ()):
[2488]216            self.xllcorner = self.xllcorner[0]
[6360]217        if (isinstance(self.yllcorner, num.ndarray) and
218                self.yllcorner.shape == ()):
[2488]219            self.yllcorner = self.yllcorner[0]
[6304]220
[6553]221        assert (type(self.xllcorner) == types.FloatType)
222        assert (type(self.yllcorner) == types.FloatType)
223        assert (type(self.zone) == types.IntType)
224
[6360]225################################################################################
[6304]226
[6428]227    ##
228    # @brief Change points to be absolute wrt new georef 'points_geo_ref'.
229    # @param points The points to change.
230    # @param points_geo_ref The new georef to make points absolute wrt.
231    # @return The changed points.
232    # @note If 'points' is a list then a changed list is returned.
[6360]233    def change_points_geo_ref(self, points, points_geo_ref=None):
[7193]234        """Change the geo reference of a list or numeric array of points to
[2488]235        be this reference.(The reference used for this object)
[6553]236        If the points do not have a geo ref, assume 'absolute' values
[2488]237        """
[6553]238        import copy
239       
[6428]240        # remember if we got a list
241        is_list = isinstance(points, list)
[2594]242
[6553]243        points = ensure_numeric(points, num.float)
[6360]244
[6553]245        # sanity checks
[2594]246        if len(points.shape) == 1:
[6553]247            #One point has been passed
[2594]248            msg = 'Single point must have two elements'
249            assert len(points) == 2, msg
[6149]250            points = num.reshape(points, (1,2))
[2594]251
[6553]252        msg = 'Points array must be two dimensional.\n'
253        msg += 'I got %d dimensions' %len(points.shape)
[5665]254        assert len(points.shape) == 2, msg
255
[6553]256        msg = 'Input must be an N x 2 array or list of (x,y) values. '
257        msg += 'I got an %d x %d array' %points.shape   
258        assert points.shape[1] == 2, msg               
[3071]259
[6553]260        # FIXME (Ole): Could also check if zone, xllcorner, yllcorner
261        # are identical in the two geo refs.   
[2594]262        if points_geo_ref is not self:
[5736]263            # If georeferences are different
[6553]264            points = copy.copy(points) # Don't destroy input                   
[2594]265            if not points_geo_ref is None:
[5736]266                # Convert points to absolute coordinates
[6553]267                points[:,0] += points_geo_ref.xllcorner
268                points[:,1] += points_geo_ref.yllcorner
269       
[5736]270            # Make points relative to primary geo reference
[6553]271            points[:,0] -= self.xllcorner
[2594]272            points[:,1] -= self.yllcorner
273
[2488]274        if is_list:
275            points = points.tolist()
[6553]276           
[2488]277        return points
[3129]278
279    def is_absolute(self):
[6553]280        """Return True if xllcorner==yllcorner==0 indicating that points
281        in question are absolute.
282        """
283       
284        # FIXME(Ole): It is unfortunate that decision about whether points
285        # are absolute or not lies with the georeference object. Ross pointed this out.
286        # Moreover, this little function is responsible for a large fraction of the time
287        # using in data fitting (something in like 40 - 50%.
288        # This was due to the repeated calls to allclose.
289        # With the flag method fitting is much faster (18 Mar 2009).
[3129]290
[6553]291        # FIXME(Ole): HACK to be able to reuse data already cached (18 Mar 2009).
292        # Remove at some point
293        if not hasattr(self, 'absolute'):
294            self.absolute = num.allclose([self.xllcorner, self.yllcorner], 0)
295           
296        # Return absolute flag   
297        return self.absolute
[3129]298
[2488]299    def get_absolute(self, points):
[6553]300        """Given a set of points geo referenced to this instance,
[2488]301        return the points as absolute values.
302        """
[3071]303
[7035]304        # remember if we got a list
305        is_list = isinstance(points, list)
[3071]306
[6553]307        points = ensure_numeric(points, num.float)
[3071]308        if len(points.shape) == 1:
[6553]309            # One point has been passed
310            msg = 'Single point must have two elements'
[3134]311            if not len(points) == 2:
[6553]312                raise ShapeError, msg   
[3071]313
[6553]314
315        msg = 'Input must be an N x 2 array or list of (x,y) values. '
316        msg += 'I got an %d x %d array' %points.shape   
[3134]317        if not points.shape[1] == 2:
[6553]318            raise ShapeError, msg   
319           
320       
[3134]321        # Add geo ref to points
322        if not self.is_absolute():
[6553]323            points = copy.copy(points) # Don't destroy input                   
324            points[:,0] += self.xllcorner
[3134]325            points[:,1] += self.yllcorner
[6360]326
[6553]327       
[2488]328        if is_list:
329            points = points.tolist()
[6553]330             
[2488]331        return points
332
[6360]333    ##
334    # @brief Convert points to relative measurement.
335    # @param points Points to convert to relative measurements.
336    # @return A set of points relative to the geo_reference instance.
[5288]337    def get_relative(self, points):
[6360]338        """Given a set of points in absolute UTM coordinates,
[5288]339        make them relative to this geo_reference instance,
340        return the points as relative values.
341
342        This is the inverse of get_absolute.
343        """
344
[6442]345        # remember if we got a list
346        is_list = isinstance(points, list)
[5288]347
[6553]348        points = ensure_numeric(points, num.float)
[5288]349        if len(points.shape) == 1:
350            #One point has been passed
[6553]351            msg = 'Single point must have two elements'
[5288]352            if not len(points) == 2:
[6553]353                raise ShapeError, msg   
[5288]354
[6360]355        if not points.shape[1] == 2:
356            msg = ('Input must be an N x 2 array or list of (x,y) values. '
357                   'I got an %d x %d array' % points.shape)
[6553]358            raise ShapeError, msg   
[5288]359
360        # Subtract geo ref from points
361        if not self.is_absolute():
[6553]362            points = copy.copy(points) # Don't destroy input                           
363            points[:,0] -= self.xllcorner
[5288]364            points[:,1] -= self.yllcorner
365
366        if is_list:
367            points = points.tolist()
[6553]368             
[5288]369        return points
370
[6360]371    ##
372    # @brief ??
373    # @param other ??
[2594]374    def reconcile_zones(self, other):
[4180]375        if other is None:
376            other = Geo_reference()
[6360]377        if (self.zone == other.zone or
378            self.zone == DEFAULT_ZONE and
379            other.zone == DEFAULT_ZONE):
[2594]380            pass
381        elif self.zone == DEFAULT_ZONE:
382            self.zone = other.zone
383        elif other.zone == DEFAULT_ZONE:
[6360]384            other.zone = self.zone
385        else:
386            msg = ('Geospatial data must be in the same '
387                   'ZONE to allow reconciliation. I got zone %d and %d'
388                   % (self.zone, other.zone))
[2608]389            raise ANUGAError, msg
[6360]390
[2488]391    #def easting_northing2geo_reffed_point(self, x, y):
392    #    return [x-self.xllcorner, y - self.xllcorner]
393
394    #def easting_northing2geo_reffed_points(self, x, y):
395    #    return [x-self.xllcorner, y - self.xllcorner]
396
[6360]397    ##
398    # @brief Get origin of this geo_reference.
399    # @return (zone, xllcorner, yllcorner).
[2488]400    def get_origin(self):
401        return (self.zone, self.xllcorner, self.yllcorner)
[6360]402
403    ##
404    # @brief Get a string representation of this geo_reference instance.
[2488]405    def __repr__(self):
[6360]406        return ('(zone=%i easting=%f, northing=%f)'
407                % (self.zone, self.xllcorner, self.yllcorner))
408
409    ##
410    # @brief Compare two geo_reference instances.
411    # @param self This geo_reference instance.
412    # @param other Another geo_reference instance to compare against.
413    # @return 0 if instances have the same attributes, else 1.
414    # @note Attributes are: zone, xllcorner, yllcorner.
415    def __cmp__(self, other):
[2488]416        # FIXME (DSG) add a tolerence
417        if other is None:
418            return 1
419        cmp = 0
420        if not (self.xllcorner == self.xllcorner):
421            cmp = 1
422        if not (self.yllcorner == self.yllcorner):
423            cmp = 1
424        if not (self.zone == self.zone):
425            cmp = 1
426        return cmp
[4387]427
[6360]428
429##
430# @brief Write a geo_reference to a NetCDF file (usually SWW).
431# @param origin A georef instance or parameters to create a georef instance.
432# @param outfile Path to file to write.
433# @return A normalized geo_reference.
[4387]434def write_NetCDF_georeference(origin, outfile):
[6360]435    """Write georeference info to a netcdf file, usually sww.
[4387]436
[6360]437    The origin can be a georef instance or parameters for a geo_ref instance
[4387]438
439    outfile is the name of the file to be written to.
440    """
[6360]441
[4451]442    geo_ref = ensure_geo_reference(origin)
443    geo_ref.write_NetCDF(outfile)
444    return geo_ref
445
[6360]446
447##
448# @brief Convert an object to a georeference instance.
449# @param origin A georef instance or (zone, xllcorner, yllcorner)
450# @return A georef object, or None if 'origin' was None.
[4451]451def ensure_geo_reference(origin):
452    """
453    Given a list/tuple of zone, xllcorner and yllcorner of a geo-ref object,
454    return a geo ref object.
455
456    If the origin is None, return None, so calling this function doesn't
457    effect code logic
458    """
[6360]459
460    if isinstance(origin, Geo_reference):
[4387]461        geo_ref = origin
[4451]462    elif origin is None:
463        geo_ref = None
[4387]464    else:
[6360]465        geo_ref = apply(Geo_reference, origin)
466
[4387]467    return geo_ref
[4451]468
[6360]469
[2488]470#-----------------------------------------------------------------------
471
472if __name__ == "__main__":
473    pass
Note: See TracBrowser for help on using the repository browser.