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

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

Made geo_ref attributes be forced to python types, not numpy, added test case.

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