"""
"""
#FIXME: Ensure that all attributes of a georef are treated everywhere
#and unit test
import sys
import copy
from anuga.utilities.numerical_tools import ensure_numeric
from anuga.anuga_exceptions import ANUGAError, TitleError, \
ParsingError, ShapeError
from anuga.config import netcdf_float, netcdf_int, netcdf_float32
import anuga.utilities.log as log
import numpy as num
DEFAULT_ZONE = -1 # This signifies that simulation isn't located within a UTM framework.
# This is the case for hypothetical simulations or something relative
# to an arbitrary origin (e.g. the corner of a wavetank).
DEFAULT_PROJECTION = 'UTM'
DEFAULT_DATUM = 'wgs84'
DEFAULT_UNITS = 'm'
DEFAULT_SOUTHERN_FALSE_EASTING = 500000
DEFAULT_SOUTHERN_FALSE_NORTHING = 10000000
DEFAULT_NORTHERN_FALSE_EASTING = 500000
DEFAULT_NORTHERN_FALSE_NORTHING = 0
DEFAULT_HEMISPHERE = 'undefined'
# WGS84 UTM EPSG code ranges
# Northern hemisphere: 32601 (zone 1N) – 32660 (zone 60N)
# Southern hemisphere: 32701 (zone 1S) – 32760 (zone 60S)
_WGS84_UTM_NORTH_BASE = 32600
_WGS84_UTM_SOUTH_BASE = 32700
TITLE = '#geo reference' + "\n" # this title is referred to in the test format
[docs]
class Geo_reference(object):
"""Coordinate reference system for an ANUGA domain.
Attributes
----------
zone : int
UTM zone (1–60), or -1 (DEFAULT_ZONE) for non-UTM / arbitrary origin
(e.g. a wave-tank simulation).
hemisphere : str
``'southern'``, ``'northern'``, or ``'undefined'``.
xllcorner : float
X coordinate (easting) of the local origin relative to the UTM grid.
yllcorner : float
Y coordinate (northing) of the local origin relative to the UTM grid.
datum : str
Geodetic datum (default ``'wgs84'``).
projection : str
Map projection (default ``'UTM'``).
units : str
Units of measurement (default ``'m'``).
false_easting : int
False easting offset (500 000 m for WGS84 UTM).
false_northing : int
False northing offset (10 000 000 m southern, 0 m northern).
epsg : int or None
EPSG code for the coordinate reference system. For WGS84 UTM this is
auto-computed from *zone* and *hemisphere* (32600 + zone for northern,
32700 + zone for southern). Returns ``None`` when the zone is
DEFAULT_ZONE or the CRS cannot be determined.
"""
def __init__(self,
zone=None,
xllcorner=0.0,
yllcorner=0.0,
datum=DEFAULT_DATUM,
projection=DEFAULT_PROJECTION,
units=DEFAULT_UNITS,
false_easting=None,
false_northing=None,
hemisphere=DEFAULT_HEMISPHERE,
epsg=None,
NetCDFObject=None,
ASCIIFile=None,
read_title=None):
"""
Parameters
----------
zone : int, optional
UTM zone (1–60) or -1 for no UTM framework. Inferred from *epsg*
when possible if not supplied.
xllcorner : float, optional
X (easting) of the local origin in metres.
yllcorner : float, optional
Y (northing) of the local origin in metres.
datum : str, optional
Geodetic datum. Default ``'wgs84'``.
projection : str, optional
Map projection. Default ``'UTM'``.
units : str, optional
Distance units. Default ``'m'``.
false_easting : int, optional
Override the default false easting for the hemisphere.
false_northing : int, optional
Override the default false northing for the hemisphere.
hemisphere : str, optional
``'southern'``, ``'northern'``, or ``'undefined'``. Inferred from
*epsg* when possible if not supplied.
epsg : int, optional
EPSG code. For WGS84 UTM codes (32601–32660 northern,
32701–32760 southern), *zone* and *hemisphere* are inferred
automatically when they have not been set explicitly.
NetCDFObject : file handle, optional
Open NetCDF file to read geo-reference attributes from.
ASCIIFile : file handle, optional
Open text file to read geo-reference attributes from.
read_title : str, optional
Title line already read from *ASCIIFile* (pass if the caller has
already consumed it).
"""
if zone is None:
zone = DEFAULT_ZONE
self._epsg = None # must exist before set_zone / set_hemisphere calls
self.set_zone(zone)
self.set_hemisphere(hemisphere)
self.set_false_easting_northing(false_easting=false_easting, false_northing=false_northing)
self.datum = datum
self.projection = projection
self.units = units
self.xllcorner = float(xllcorner)
self.yllcorner = float(yllcorner)
if epsg is not None:
self._set_epsg(int(epsg))
if NetCDFObject is not None:
self.read_NetCDF(NetCDFObject)
if ASCIIFile is not None:
self.read_ASCII(ASCIIFile, read_title=read_title)
# Set flag for absolute points (used by get_absolute)
# FIXME (Ole): It would be more robust to always use get_absolute()
self.absolute = num.allclose([self.xllcorner, self.yllcorner], 0)
def __eq__(self, other):
# FIXME (Ole): Can this be automatically done for all attributes?
# Anyway, it is arranged like this so one can step through and find out
# why two objects might not be equal
equal = True
if self.false_easting != other.false_easting: equal = False
if self.false_northing != other.false_northing: equal = False
if self.datum != other.datum: equal = False
if self.projection != other.projection: equal = False
if self.zone != other.zone: equal = False
if self.hemisphere != other.hemisphere: equal = False
if self.units != other.units: equal = False
if self.xllcorner != other.xllcorner: equal = False
if self.yllcorner != other.yllcorner: equal = False
if self.absolute != other.absolute: equal = False
return(equal)
[docs]
def get_xllcorner(self):
"""Get the X coordinate of the origin of this georef."""
return self.xllcorner
[docs]
def get_yllcorner(self):
"""Get the Y coordinate of the origin of this georef."""
return self.yllcorner
def set_zone(self, zone):
"""set zone as an integer in [1,60] or -1."""
zone = int(zone)
assert (zone == -1 or (zone >= 1 and zone <= 60)), f'zone {zone} not valid.'
self.zone = zone
[docs]
def get_zone(self):
"""Get the zone of this georef."""
return self.zone
[docs]
def get_hemisphere(self):
"""Check if this georef has a defined hemisphere."""
return self.hemisphere
def set_hemisphere(self, hemisphere):
msg = f"'{hemisphere}' not corresponding to allowed hemisphere values 'southern', 'northern' or 'undefined'"
assert hemisphere in ['southern', 'northern', 'undefined'], msg
self.hemisphere=str(hemisphere)
def set_false_easting_northing(self, false_easting=None, false_northing=None):
if false_easting is None:
if self.hemisphere == 'southern':
false_easting = DEFAULT_SOUTHERN_FALSE_EASTING
elif self.hemisphere == 'northern':
false_easting = DEFAULT_NORTHERN_FALSE_EASTING
else:
false_easting = 0
if false_northing is None:
if self.hemisphere == 'southern':
false_northing = DEFAULT_SOUTHERN_FALSE_NORTHING
elif self.hemisphere == 'northern':
false_northing = DEFAULT_NORTHERN_FALSE_NORTHING
else:
false_northing = 0
self.false_easting = int(false_easting)
self.false_northing = int(false_northing)
def _set_epsg(self, epsg):
"""Store EPSG code and infer zone/hemisphere for WGS84 UTM codes.
For WGS84 UTM EPSG codes (32601–32660 northern, 32701–32760 southern),
*zone* and *hemisphere* are inferred automatically when not already set.
For all other EPSG codes (national grids, geographic CRS, etc.) the code
is stored as-is. If ``pyproj`` is available, *datum* and *projection*
are populated from the CRS definition so the SWW metadata is accurate.
Parameters
----------
epsg : int
EPSG code to store.
"""
self._epsg = epsg
if _WGS84_UTM_NORTH_BASE < epsg <= _WGS84_UTM_NORTH_BASE + 60:
inferred_zone = epsg - _WGS84_UTM_NORTH_BASE
inferred_hemisphere = 'northern'
elif _WGS84_UTM_SOUTH_BASE < epsg <= _WGS84_UTM_SOUTH_BASE + 60:
inferred_zone = epsg - _WGS84_UTM_SOUTH_BASE
inferred_hemisphere = 'southern'
else:
# Non-UTM EPSG (e.g. EPSG:28992 Netherlands RD New,
# EPSG:27700 British National Grid). No zone or hemisphere to infer.
# Populate datum and projection from pyproj if available so that
# the SWW file metadata accurately describes the CRS.
self._populate_from_pyproj(epsg)
return
if self.zone == DEFAULT_ZONE:
self.set_zone(inferred_zone)
elif self.zone != inferred_zone:
log.warning(f'EPSG {epsg} implies zone {inferred_zone} but zone '
f'{self.zone} is already set; zone unchanged.')
if self.hemisphere == DEFAULT_HEMISPHERE:
self.set_hemisphere(inferred_hemisphere)
self.set_false_easting_northing()
elif self.hemisphere != inferred_hemisphere:
log.warning(f'EPSG {epsg} implies {inferred_hemisphere} hemisphere but '
f'{self.hemisphere} is already set; hemisphere unchanged.')
def _populate_from_pyproj(self, epsg):
"""Use pyproj to set CRS metadata from an EPSG code.
Populates *projection*, *datum*, *false_easting*, and *false_northing*
from the EPSG definition. Does nothing if pyproj is not installed or
the lookup fails.
Parameters
----------
epsg : int
EPSG code to look up.
"""
try:
from pyproj import CRS
crs = CRS.from_epsg(epsg)
except Exception:
return # pyproj not available or invalid EPSG
self.projection = crs.name
if crs.datum is not None:
self.datum = crs.datum.name
elif crs.geodetic_crs is not None and crs.geodetic_crs.datum is not None:
self.datum = crs.geodetic_crs.datum.name
if not crs.is_projected:
return
# False easting/northing: try CF params first, fall back to PROJ dict.
# Neither source covers all projections alone:
# to_cf() works for BNG (27700) but not RD New (28992)
# to_dict() works for RD New (28992) but not some UTM variants
fe = fn = None
try:
cf = crs.to_cf()
fe = cf.get('false_easting')
fn = cf.get('false_northing')
except Exception:
pass
if fe is None or fn is None:
try:
import warnings
with warnings.catch_warnings():
warnings.simplefilter('ignore')
d = crs.to_dict()
fe = d.get('x_0', fe)
fn = d.get('y_0', fn)
except Exception:
pass
if fe is not None:
self.false_easting = int(round(float(fe)))
if fn is not None:
self.false_northing = int(round(float(fn)))
@property
def epsg(self):
"""EPSG code for this coordinate reference system.
For WGS84 UTM projections the code is computed automatically from
*zone* and *hemisphere* when it has not been set explicitly:
* Northern hemisphere: ``32600 + zone`` (e.g. zone 55N → EPSG 32655)
* Southern hemisphere: ``32700 + zone`` (e.g. zone 55S → EPSG 32755)
Returns ``None`` when the zone is DEFAULT_ZONE (-1) or the CRS cannot
be determined.
Returns
-------
int or None
"""
if self._epsg is not None:
return self._epsg
if self.zone == DEFAULT_ZONE:
return None
if self.datum.lower() == 'wgs84' and self.projection.upper() == 'UTM':
if self.hemisphere == 'southern':
return _WGS84_UTM_SOUTH_BASE + self.zone
if self.hemisphere == 'northern':
return _WGS84_UTM_NORTH_BASE + self.zone
return None
@epsg.setter
def epsg(self, value):
if value is None:
self._epsg = None
else:
self._set_epsg(int(value))
[docs]
def get_epsg(self):
"""Return the EPSG code for this coordinate reference system.
Returns
-------
int or None
EPSG code, or ``None`` if unknown.
"""
return self.epsg
[docs]
def is_located(self):
"""Return True if this geo-reference describes a real, located CRS.
A geo-reference is *located* when either:
* *zone* is a valid UTM zone (1–60), or
* an EPSG code has been set (covers national grids, geographic CRS, and
any other projected CRS that does not use UTM zones, e.g.
EPSG:28992 Netherlands RD New, EPSG:27700 British National Grid).
A geo-reference with ``zone == DEFAULT_ZONE`` (-1) and no EPSG code is
*unlocated* — this is the case for wavetank or other hypothetical
simulations with an arbitrary local origin.
Returns
-------
bool
"""
return self.zone != DEFAULT_ZONE or self._epsg is not None
[docs]
def write_NetCDF(self, outfile):
"""Write georef attributes to an open NetCDF file.
Parameters
----------
outfile : file handle
Handle to an open NetCDF file.
"""
outfile.xllcorner = self.xllcorner
outfile.yllcorner = self.yllcorner
outfile.zone = self.zone
outfile.hemisphere = self.hemisphere
outfile.false_easting = self.false_easting
outfile.false_northing = self.false_northing
outfile.datum = self.datum
outfile.projection = self.projection
outfile.units = self.units
epsg = self.epsg
if epsg is not None:
outfile.epsg = epsg
[docs]
def read_NetCDF(self, infile):
"""Set georef attributes from open NetCDF file.
Parameters
----------
infile : file handle
Handle to an open NetCDF file.
"""
self.xllcorner = float(infile.xllcorner)
self.yllcorner = float(infile.yllcorner)
self.zone = int(infile.zone)
try:
self.hemisphere = str(infile.hemisphere)
except AttributeError:
self.hemisphere = DEFAULT_HEMISPHERE
self.false_easting = int(infile.false_easting)
self.false_northing = int(infile.false_northing)
self.datum = infile.datum
self.projection = infile.projection
self.units = infile.units
# Read EPSG if present (old SWW files may not have it)
try:
self._epsg = int(infile.epsg)
except AttributeError:
self._epsg = None
# Set flag for absolute points (used by get_absolute)
# FIXME (Ole): It would be more robust to always use get_absolute()
self.absolute = num.allclose([self.xllcorner, self.yllcorner], 0)
if self.hemisphere == 'southern':
if self.false_easting != DEFAULT_SOUTHERN_FALSE_EASTING:
log.critical("WARNING: False easting of %f specified."
% self.false_easting)
log.critical("Default false easting is %f." % DEFAULT_SOUTHERN_FALSE_EASTING)
log.critical("ANUGA does not correct for differences in "
"False Eastings.")
if self.false_northing != DEFAULT_SOUTHERN_FALSE_NORTHING:
log.critical("WARNING: False northing of %f specified."
% self.false_northing)
log.critical("Default false northing is %f."
% DEFAULT_SOUTHERN_FALSE_NORTHING)
log.critical("ANUGA does not correct for differences in "
"False Northings.")
if self.hemisphere == 'northern':
if self.false_easting != DEFAULT_NORTHERN_FALSE_EASTING:
log.critical("WARNING: False easting of %f specified."
% self.false_easting)
log.critical("Default false easting is %f." % DEFAULT_NORTHERN_FALSE_EASTING)
log.critical("ANUGA does not correct for differences in "
"False Eastings.")
if self.false_northing != DEFAULT_NORTHERN_FALSE_NORTHING:
log.critical("WARNING: False northing of %f specified."
% self.false_northing)
log.critical("Default false northing is %f."
% DEFAULT_NORTHERN_FALSE_NORTHING)
log.critical("ANUGA does not correct for differences in "
"False Northings.")
# Suppress datum/projection warnings for non-UTM EPSG codes
# (e.g. EPSG:28992 RD New has datum 'Amersfoort', not 'wgs84').
# The EPSG code is the authoritative CRS identifier in that case.
non_utm_epsg = (self._epsg is not None and
not (_WGS84_UTM_NORTH_BASE < self._epsg <= _WGS84_UTM_NORTH_BASE + 60 or
_WGS84_UTM_SOUTH_BASE < self._epsg <= _WGS84_UTM_SOUTH_BASE + 60))
if not non_utm_epsg:
if self.datum.upper() != DEFAULT_DATUM.upper():
log.critical("WARNING: Datum of %s specified." % self.datum)
log.critical("Default Datum is %s." % DEFAULT_DATUM)
log.critical("ANUGA does not correct for differences in datums.")
if self.projection.upper() != DEFAULT_PROJECTION.upper():
log.critical("WARNING: Projection of %s specified."
% self.projection)
log.critical("Default Projection is %s." % DEFAULT_PROJECTION)
log.critical("ANUGA does not correct for differences in "
"Projection.")
if self.units.upper() != DEFAULT_UNITS.upper():
log.critical("WARNING: Units of %s specified." % self.units)
log.critical("Default units is %s." % DEFAULT_UNITS)
log.critical("ANUGA does not correct for differences in units.")
################################################################################
# ASCII files with geo-refs are currently not used
################################################################################
def write_ASCII(self, fd):
"""Write georef attriutes to an open text file.
fd handle to open text file
"""
fd.write(TITLE)
fd.write(str(self.zone) + "\n")
fd.write(str(self.xllcorner) + "\n")
fd.write(str(self.yllcorner) + "\n")
def read_ASCII(self, fd, read_title=None):
"""Set georef attribtes from open text file.
fd handle to open text file
"""
try:
if read_title is None:
read_title = fd.readline() # remove the title line
if read_title[0:2].upper() != TITLE[0:2].upper():
msg = ('File error. Expecting line: %s. Got this line: %s'
% (TITLE, read_title))
raise TitleError(msg)
self.zone = int(fd.readline())
self.xllcorner = float(fd.readline())
self.yllcorner = float(fd.readline())
except SyntaxError:
msg = 'File error. Got syntax error while parsing geo reference'
raise ParsingError(msg)
# Fix some assertion failures
if isinstance(self.zone, num.ndarray) and self.zone.shape == ():
self.zone = self.zone[0]
if (isinstance(self.xllcorner, num.ndarray) and
self.xllcorner.shape == ()):
self.xllcorner = self.xllcorner[0]
if (isinstance(self.yllcorner, num.ndarray) and
self.yllcorner.shape == ()):
self.yllcorner = self.yllcorner[0]
assert isinstance(self.xllcorner, float)
assert isinstance(self.yllcorner, float)
assert isinstance(self.zone, int)
################################################################################
def change_points_geo_ref(self, points, points_geo_ref=None):
"""Change points to be absolute wrt new georef 'points_geo_ref'.
points the points to change
points_geo_ref the new georef to make points absolute wrt
Returns the changed points data.
If the points do not have a georef, assume 'absolute' values.
"""
import copy
# remember if we got a list
is_list = isinstance(points, list)
points = ensure_numeric(points, float)
# sanity checks
if len(points.shape) == 1:
#One point has been passed
msg = 'Single point must have two elements'
assert len(points) == 2, msg
points = num.reshape(points, (1,2))
msg = 'Points array must be two dimensional.\n'
msg += 'I got %d dimensions' %len(points.shape)
assert len(points.shape) == 2, msg
msg = 'Input must be an N x 2 array or list of (x,y) values. '
msg += 'I got an %d x %d array' %points.shape
assert points.shape[1] == 2, msg
# FIXME (Ole): Could also check if zone, xllcorner, yllcorner
# are identical in the two geo refs.
if points_geo_ref is not self:
# If georeferences are different
points = copy.copy(points) # Don't destroy input
if not points_geo_ref is None:
# Convert points to absolute coordinates
points[:,0] += points_geo_ref.xllcorner
points[:,1] += points_geo_ref.yllcorner
# Make points relative to primary geo reference
points[:,0] -= self.xllcorner
points[:,1] -= self.yllcorner
if is_list:
points = points.tolist()
return points
def is_absolute(self):
"""Test if points in georef are absolute.
Return True if xllcorner==yllcorner==0 indicating that points in
question are absolute.
"""
# FIXME(Ole): It is unfortunate that decision about whether points
# are absolute or not lies with the georeference object. Ross pointed this out.
# Moreover, this little function is responsible for a large fraction of the time
# using in data fitting (something in like 40 - 50%.
# This was due to the repeated calls to allclose.
# With the flag method fitting is much faster (18 Mar 2009).
# FIXME(Ole): HACK to be able to reuse data already cached (18 Mar 2009).
# Remove at some point
if not hasattr(self, 'absolute'):
self.absolute = num.allclose([self.xllcorner, self.yllcorner], 0)
# Return absolute flag
return self.absolute
[docs]
def get_absolute(self, points):
"""Given a set of points geo referenced to this instance, return the
points as absolute values.
"""
# remember if we got a list
is_list = isinstance(points, list)
points = ensure_numeric(points, float)
if len(points.shape) == 1:
# One point has been passed
msg = 'Single point must have two elements'
if not len(points) == 2:
raise ShapeError(msg)
msg = 'Input must be an N x 2 array or list of (x,y) values. '
msg += 'I got an %d x %d array' %points.shape
if not points.shape[1] == 2:
raise ShapeError(msg)
# Add geo ref to points
if not self.is_absolute():
points = copy.copy(points) # Don't destroy input
points[:,0] += self.xllcorner
points[:,1] += self.yllcorner
if is_list:
points = points.tolist()
return points
[docs]
def get_relative(self, points):
"""Convert points to relative measurement.
points Points to convert to relative measurements
Returns a set of points relative to the geo_reference instance.
This is the inverse of get_absolute().
"""
# remember if we got a list
is_list = isinstance(points, list)
points = ensure_numeric(points, float)
if len(points.shape) == 1:
#One point has been passed
msg = 'Single point must have two elements'
if not len(points) == 2:
raise ShapeError(msg)
if not points.shape[1] == 2:
msg = ('Input must be an N x 2 array or list of (x,y) values. '
'I got an %d x %d array' % points.shape)
raise ShapeError(msg)
# Subtract geo ref from points
if not self.is_absolute():
points = copy.copy(points) # Don't destroy input
points[:,0] -= self.xllcorner
points[:,1] -= self.yllcorner
if is_list:
points = points.tolist()
return points
def reconcile_zones(self, other):
if other is None:
# FIXME(Ole): Why would we do this?
other = Geo_reference()
#raise Exception('Expected georeference object, got None')
if (self.zone == other.zone):
pass
elif self.zone == DEFAULT_ZONE:
self.zone = other.zone
elif other.zone == DEFAULT_ZONE:
other.zone = self.zone
else:
msg = ('Geospatial data must be in the same '
'ZONE to allow reconciliation. I got zone %d and %d'
% (self.zone, other.zone))
raise ANUGAError(msg)
# Should also reconcile hemisphere
if (self.hemisphere == other.hemisphere):
pass
elif self.hemisphere == DEFAULT_HEMISPHERE:
self.hemisphere = other.hemisphere
elif other.hemisphere == DEFAULT_HEMISPHERE:
other.hemisphere = self.hemisphere
else:
msg = ('Geospatial data must be in the same '
'HEMISPHERE to allow reconciliation. I got hemisphere %d and %d'
% (self.hemisphere, other.hemisphere))
raise ANUGAError(msg)
# FIXME (Ole): Do we need this back?
#def easting_northing2geo_reffed_point(self, x, y):
# return [x-self.xllcorner, y - self.xllcorner]
#def easting_northing2geo_reffed_points(self, x, y):
# return [x-self.xllcorner, y - self.xllcorner]
[docs]
def get_origin(self):
"""Get origin of this geo_reference."""
return (self.zone, self.xllcorner, self.yllcorner)
def __repr__(self):
epsg = self.epsg
is_utm = (epsg is not None and
(_WGS84_UTM_NORTH_BASE < epsg <= _WGS84_UTM_NORTH_BASE + 60 or
_WGS84_UTM_SOUTH_BASE < epsg <= _WGS84_UTM_SOUTH_BASE + 60))
if epsg is not None and not is_utm:
# Non-UTM CRS: zone/hemisphere are not meaningful; show CRS name instead
return ('(crs=%s, easting=%f, northing=%f, epsg=%i)'
% (self.projection, self.xllcorner, self.yllcorner, epsg))
if epsg is not None:
return ('(zone=%i, easting=%f, northing=%f, hemisphere=%s, epsg=%i)'
% (self.zone, self.xllcorner, self.yllcorner, self.hemisphere, epsg))
return ('(zone=%i, easting=%f, northing=%f, hemisphere=%s)'
% (self.zone, self.xllcorner, self.yllcorner, self.hemisphere))
#def __cmp__(self, other):
# """Compare two geo_reference instances.#
#
# self this geo_reference instance
# other another geo_reference instance to compare against#
#
# Returns 0 if instances have the same attributes, else returns 1.
#
# Note: attributes are: zone, xllcorner, yllcorner.
# """
# # FIXME (DSG) add a tolerence
# if other is None:
# return 1
# cmp = 0
# if not (self.xllcorner == self.xllcorner):
# cmp = 1
# if not (self.yllcorner == self.yllcorner):
# cmp = 1
# if not (self.zone == self.zone):
# cmp = 1
# return cmp
def write_NetCDF_georeference(georef, outfile):
"""Write georeference info to a NetCDF file, usually a SWW file.
georef a georef instance or parameters to create a georef instance
outfile path to file to write
Returns the normalised georef.
"""
geo_ref = ensure_geo_reference(georef)
geo_ref.write_NetCDF(outfile)
return geo_ref
def ensure_geo_reference(origin):
"""Create a georef object from a tuple of attributes.
origin a georef instance or (zone, xllcorner, yllcorner)
If origin is None, return None, so calling this function doesn't
effect code logic.
"""
if isinstance(origin, Geo_reference):
geo_ref = origin
elif origin is None:
geo_ref = None
else:
if len(origin) == 1:
geo_ref = Geo_reference(zone = origin)
elif len(origin) == 2:
geo_ref = Geo_reference(zone = -1, xllcorner=origin[0], yllcorner=origin[1])
elif len(origin) == 3:
geo_ref = Geo_reference(zone = origin[0], xllcorner=origin[1], yllcorner=origin[2])
else:
raise Exception(f'Invalid input {origin}, expected (zone), (xllcorner, yllcorner), or (zone, xllcorner, yllcorner).')
return geo_ref
#-----------------------------------------------------------------------
if __name__ == "__main__":
pass