# Copyright (c) 2013,Vienna University of Technology, Department of Geodesy and Geoinformation
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Vienna University of Technology, Department of Geodesy and Geoinformation nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL VIENNA UNIVERSITY OF TECHNOLOGY,
# DEPARTMENT OF GEODESY AND GEOINFORMATION BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
Created on Jan 21, 2014
Module for saving grid to netCDF
@author: Christoph Paulik christoph.paulik@geo.tuwien.ac.at
'''
from netCDF4 import Dataset
import numpy as np
import os
from datetime import datetime
from pytesmo.grid.grids import CellGrid, BasicGrid
[docs]def save_lonlat(filename, arrlon, arrlat, arrcell=None,
gpis=None, subset_points=None, subset_name='land_points',
subset_meaning="water land",
global_attrs=None):
"""
saves grid information to netCDF file
Parameters
----------
filename : string
name of file
arrlon : numpy.array
array of longitudes
arrlat : numpy.array
array of latitudes
arrcell : numpy.array, optional
array of cell numbers
gpis : numpy.array, optional
gpi numbers if not index of arrlon, arrlat
subset_points : numpy.array, optional
and indication if the given grid point is over land
must be an indices into arrlon, arrlat
subset_name : string, optional
long_name of the variable 'subset_flag'
if the subset symbolises something other than a land/sea mask
subset_meaning : string, optional
will be written into flag_meanings metadata of variable 'subset_flag'
global_attrs : dict, optional
if given will be written as global attributs into netCDF file
"""
nc_name = filename
with Dataset(nc_name, 'w', format='NETCDF4') as ncfile:
if (global_attrs is not None and 'shape' in global_attrs and
type(global_attrs['shape']) is not int):
latsize = global_attrs['shape'][0]
lonsize = global_attrs['shape'][1]
ncfile.createDimension("lat", latsize)
ncfile.createDimension("lon", lonsize)
arrlat = np.unique(arrlat)[::-1] # sorts arrlat descending
arrlon = np.unique(arrlon)
gpisize = global_attrs['shape'][0] * global_attrs['shape'][1]
if gpis is None:
gpivalues = np.arange(gpisize,
dtype=np.int32).reshape(latsize,
lonsize)
gpivalues = gpivalues[::-1]
else:
gpivalues = gpis.reshape(latsize, lonsize)
else:
ncfile.createDimension("gp", arrlon.size)
gpisize = arrlon.size
if gpis is None:
gpivalues = np.arange(arrlon.size, dtype=np.int32)
else:
gpivalues = gpis
dim = ncfile.dimensions.keys()
gpi = ncfile.createVariable('gpi', np.dtype('int32').char, dim)
if gpis is None:
gpi[:] = gpivalues
setattr(gpi, 'long_name', 'Grid point index')
setattr(gpi, 'units', '')
setattr(gpi, 'valid_range', [0, gpisize])
gpidirect = 0x1b
else:
gpi[:] = gpivalues
setattr(gpi, 'long_name', 'Grid point index')
setattr(gpi, 'units', '')
setattr(gpi, 'valid_range', [np.min(gpivalues), np.max(gpivalues)])
gpidirect = 0x0b
latitude = ncfile.createVariable('lat', np.dtype('float32').char,
dim[0])
latitude[:] = arrlat
setattr(latitude, 'long_name', 'Latitude')
setattr(latitude, 'units', 'degree_north')
setattr(latitude, 'standard_name', 'latitude')
setattr(latitude, 'valid_range', [-90.0, 90.0])
if len(dim) == 2:
londim = dim[1]
else:
londim = dim[0]
longitude = ncfile.createVariable('lon', np.dtype('float32').char,
londim)
longitude[:] = arrlon
setattr(longitude, 'long_name', 'Longitude')
setattr(longitude, 'units', 'degree_east')
setattr(longitude, 'standard_name', 'longitude')
setattr(longitude, 'valid_range', [-180.0, 180.0])
if arrcell is not None:
cell = ncfile.createVariable('cell', np.dtype('int16').char, dim)
cell[:] = arrcell
setattr(cell, 'long_name', 'Cell')
setattr(cell, 'units', '')
setattr(cell, 'valid_range', [np.min(arrcell), np.max(arrcell)])
if subset_points is not None:
land_flag = ncfile.createVariable('subset_flag',
np.dtype('int8').char,
dim)
# create landflag array based on shape of data
lf = np.zeros_like(gpivalues)
if len(dim) == 2:
lf = lf.flatten()
lf[subset_points] = 1
if len(dim) == 2:
lf = lf.reshape(latsize, lonsize)
land_flag[:] = lf
setattr(land_flag, 'long_name', subset_name)
setattr(land_flag, 'units', '')
setattr(land_flag, 'coordinates', 'lat lon')
setattr(land_flag, 'flag_values', np.arange(2, dtype=np.int8))
setattr(land_flag, 'flag_meanings', 'water land')
setattr(land_flag, 'valid_range', [0, 1])
s = "%Y-%m-%d %H:%M:%S"
date_created = datetime.now().strftime(s)
attr = {'Conventions': 'CF-1.6',
'id': os.path.split(filename)[1], # file name
'date_created': date_created,
'geospatial_lat_min': np.round(np.min(arrlat), 4),
'geospatial_lat_max': np.round(np.max(arrlat), 4),
'geospatial_lon_min': np.round(np.min(arrlon), 4),
'geospatial_lon_max': np.round(np.max(arrlon), 4),
'gpidirect': gpidirect
}
ncfile.setncatts(attr)
if global_attrs is not None and type(global_attrs) is dict:
ncfile.setncatts(global_attrs)
[docs]def save_grid(filename, grid, subset_name='land_points',
subset_meaning="water land", global_attrs=None):
"""
save a BasicGrid or CellGrid to netCDF
it is assumed that a subset should be used as land_points
Parameters
----------
filename : string
name of file
grid : BasicGrid or CellGrid object
grid whose definition to save to netCDF
subset_name : string, optional
long_name of the variable 'subset_flag'
if the subset symbolises something other than a land/sea mask
subset_meaning : string, optional
will be written into flag_meanings metadata of variable 'subset_flag'
global_attrs : dict, optional
if given will be written as global attributs into netCDF file
"""
try:
arrcell = grid.arrcell
except AttributeError:
arrcell = None
if grid.gpidirect is True:
gpis = None
else:
gpis = grid.gpis
if grid.shape is not None:
if global_attrs is None:
global_attrs = {}
global_attrs['shape'] = grid.shape
save_lonlat(filename, grid.arrlon, grid.arrlat, arrcell=arrcell,
gpis=gpis, subset_points=grid.subset, subset_name=subset_name,
subset_meaning=subset_meaning,
global_attrs=global_attrs)
[docs]def load_grid(filename):
"""
load a grid from netCDF file
Parameters
----------
filename : string
filename
Returns
-------
grid : BasicGrid or CellGrid instance
grid instance initialized with the loaded data
"""
with Dataset(filename, 'r') as nc_data:
# determine if it is a cell grid or a basic grid
arrcell = None
if 'cell' in nc_data.variables.keys():
arrcell = nc_data.variables['cell'][:]
# determine if gpis are in order or custom order
if nc_data.gpidirect == 0x1b:
gpis = None # gpis can be calculated through np.arange..
else:
gpis = nc_data.variables['gpi'][:]
shape = None
if hasattr(nc_data, 'shape'):
try:
shape = tuple(nc_data.shape)
except TypeError as e:
try:
length = len(nc_data.shape)
except TypeError:
length = nc_data.shape.size
if length == 1:
shape = tuple([nc_data.shape])
else:
raise e
subset = None
# some old grid do not have a shape attribute
# this meant that they had shape of len 1
if shape is None:
shape = tuple([len(nc_data.variables['lon'][:])])
# check if grid has regular shape
if len(shape) == 2:
lons, lats = np.meshgrid(nc_data.variables['lon'][:],
nc_data.variables['lat'][::-1])
lons = lons.flatten('F')
lats = lats.flatten('F')
if 'subset_flag' in nc_data.variables.keys():
subset = np.where(nc_data.variables['subset_flag'][:].flatten() == 1)[0]
elif len(shape) == 1:
lons = nc_data.variables['lon'][:]
lats = nc_data.variables['lat'][:]
# determine if it has a subset
if 'subset_flag' in nc_data.variables.keys():
subset = np.where(nc_data.variables['subset_flag'][:] == 1)[0]
if arrcell is None:
# BasicGrid
return BasicGrid(lons,
lats,
gpis=gpis,
subset=subset,
shape=shape)
else:
# CellGrid
return CellGrid(nc_data.variables['lon'][:],
nc_data.variables['lat'][:],
arrcell,
gpis=gpis,
subset=subset,
shape=shape)