source: trunk/anuga_core/source/anuga/coordinate_transforms/geo_reference.py @ 8125

Last change on this file since 8125 was 8125, checked in by wilsonr, 13 years ago

Changes to address ticket 360.

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