This is an automated email from the git hooks/post-receive script. a_valentino-guest pushed a commit to branch master in repository pyresample.
commit 34c33ee8172d2f1084f3eafef3fac431d9b8bc79 Author: Antonio Valentino <antonio.valent...@tiscali.it> Date: Sat Sep 23 17:06:01 2017 +0000 New upstream version 1.6.1 --- .bumpversion.cfg | 2 +- changelog.rst | 50 +++++ pyresample/geometry.py | 171 +++++++++++++++++ pyresample/kd_tree.py | 80 ++++---- pyresample/test/test_ewa_ll2cr.py | 23 +-- pyresample/test/test_geometry.py | 385 ++++++++++++++++++++++++++++---------- pyresample/test/test_utils.py | 87 ++++++++- pyresample/test/utils.py | 22 +++ pyresample/utils.py | 80 +++++--- pyresample/version.py | 2 +- 10 files changed, 709 insertions(+), 193 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index eed2dcc..9232afd 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.6.0 +current_version = 1.6.1 commit = True tag = True diff --git a/changelog.rst b/changelog.rst index cd16cc8..820a32d 100644 --- a/changelog.rst +++ b/changelog.rst @@ -2,6 +2,56 @@ Changelog ========= +v1.6.1 (2017-09-18) +------------------- +- update changelog. [Martin Raspaud] +- Bump version: 1.6.0 → 1.6.1. [Martin Raspaud] +- Merge pull request #60 from pytroll/feature-dynamic-area. [David + Hoese] + + Add support for dynamic areas +- Merge branch 'develop' into feature-dynamic-area. [Martin Raspaud] +- Apply assert_allclose to proj dicts for tests. [Martin Raspaud] +- Fix some style issues. [Martin Raspaud] +- Set DynamicArea proj to `omerc` by default. [Martin Raspaud] +- Implement proposed changes in PR review. [Martin Raspaud] +- Use numpy's assert almost equal for area_extent comparisons. [Martin + Raspaud] +- Document the DynamicArea class. [Martin Raspaud] +- Fix optimal projection computation tests. [Martin Raspaud] +- Pep8 cleanup. [Martin Raspaud] +- Valid index computation optimization. [Martin Raspaud] +- Change bb computation api to use the whole proj_dict. [Martin Raspaud] +- Fix unittests for updated omerc computations. [Martin Raspaud] +- Use other azimuth direction for omerc. [Martin Raspaud] +- Flip x and y size in omerc projection. [Martin Raspaud] +- Bugfix typo. [Martin Raspaud] +- Allow lons and lats to be any array in bb computation. [Martin + Raspaud] +- Add SwathDefinition tests to the test suite. [Martin Raspaud] +- Support bounding box area computation from SwathDefintion. [Martin + Raspaud] + + This add support for computing a bounding box area from a swath definition that would fit optimally. The default projection is oblique mercator, with is optimal for locally received imager passes. +- Add support for dynamic areas. [Martin Raspaud] +- Merge pull request #70 from pytroll/feature-radius-parameters. [David + Hoese] + + Add 'proj4_radius_parameters' to calculate 'a' and 'b' from ellps +- Add tests for proj4_radius_parameters. [davidh-ssec] +- Fix typo in function call in radius parameters. [davidh-ssec] +- Add 'proj4_radius_parameters' to calculate 'a' and 'b' from ellps. + [davidh-ssec] +- Merge pull request #68 from pytroll/feature-56. [Martin Raspaud] + + Fix GridDefinition as permitted definition in preprocessing utils +- Add more preprocessing tests. [davidh-ssec] +- Fix preprocessing functions to use duck type on provided areas. + [davidh-ssec] +- Fix GridDefinition as permitted definition in preprocessing utils. + [davidh-ssec] + + v1.6.0 (2017-09-12) ------------------- - update changelog. [Martin Raspaud] diff --git a/pyresample/geometry.py b/pyresample/geometry.py index c0f02ca..31d84ee 100644 --- a/pyresample/geometry.py +++ b/pyresample/geometry.py @@ -24,12 +24,16 @@ import warnings from collections import OrderedDict +from logging import getLogger import numpy as np import yaml +from pyproj import Geod, Proj from pyresample import _spatial_mp, utils +logger = getLogger(__name__) + class DimensionError(Exception): pass @@ -59,6 +63,8 @@ class BaseDefinition(object): if type(lons) != type(lats): raise TypeError('lons and lats must be of same type') elif lons is not None: + lons = np.asanyarray(lons) + lats = np.asanyarray(lats) if lons.shape != lats.shape: raise ValueError('lons and lats must have same shape') @@ -351,6 +357,8 @@ class CoordinateDefinition(BaseDefinition): """Base class for geometry definitions defined by lons and lats only""" def __init__(self, lons, lats, nprocs=1): + lons = np.asanyarray(lons) + lats = np.asanyarray(lats) super(CoordinateDefinition, self).__init__(lons, lats, nprocs) if lons.shape == lats.shape and lons.dtype == lats.dtype: self.shape = lons.shape @@ -449,12 +457,175 @@ class SwathDefinition(CoordinateDefinition): """ def __init__(self, lons, lats, nprocs=1): + lons = np.asanyarray(lons) + lats = np.asanyarray(lats) super(SwathDefinition, self).__init__(lons, lats, nprocs) if lons.shape != lats.shape: raise ValueError('lon and lat arrays must have same shape') elif lons.ndim > 2: raise ValueError('Only 1 and 2 dimensional swaths are allowed') + def _compute_omerc_parameters(self, ellipsoid): + """Compute the oblique mercator projection bouding box parameters.""" + lines, cols = self.lons.shape + lon1, lon2 = np.asanyarray(self.lons[[0, -1], int(cols / 2)]) + lat1, lat, lat2 = np.asanyarray( + self.lats[[0, int(lines / 2), -1], int(cols / 2)]) + + proj_dict2points = {'proj': 'omerc', 'lat_0': lat, 'ellps': ellipsoid, + 'lat_1': lat1, 'lon_1': lon1, + 'lat_2': lat2, 'lon_2': lon2} + + lonc, lat0 = Proj(**proj_dict2points)(0, 0, inverse=True) + az1, az2, dist = Geod(**proj_dict2points).inv(lonc, lat0, lon1, lat1) + del az2, dist + return {'proj': 'omerc', 'alpha': float(az1), + 'lat_0': float(lat0), 'lonc': float(lonc), + 'no_rot': True, 'ellps': ellipsoid} + + def get_edge_lonlats(self): + """Get the concatenated boundary of the current swath.""" + lons, lats = self.get_boundary_lonlats() + blons = np.ma.concatenate([lons.side1, lons.side2, + lons.side3, lons.side4]) + blats = np.ma.concatenate([lats.side1, lats.side2, + lats.side3, lats.side4]) + return blons, blats + + def compute_bb_proj_params(self, proj_dict): + projection = proj_dict['proj'] + ellipsoid = proj_dict.get('ellps', 'WGS84') + if projection == 'omerc': + return self._compute_omerc_parameters(ellipsoid) + else: + raise NotImplementedError('Only omerc supported for now.') + + def compute_optimal_bb_area(self, proj_dict=None): + """Compute the "best" bounding box area for this swath with `proj_dict`. + + By default, the projection is Oblique Mercator (`omerc` in proj.4), in + which case the right projection angle `alpha` is computed from the + swath centerline. For other projections, only the appropriate center of + projection and area extents are computed. + """ + if proj_dict is None: + proj_dict = {} + projection = proj_dict.setdefault('proj', 'omerc') + area_id = projection + '_otf' + description = 'On-the-fly ' + projection + ' area' + lines, cols = self.lons.shape + x_size = int(cols * 1.1) + y_size = int(lines * 1.1) + + proj_dict = self.compute_bb_proj_params(proj_dict) + + if projection == 'omerc': + x_size, y_size = y_size, x_size + + area = DynamicAreaDefinition(area_id, description, proj_dict) + lons, lats = self.get_edge_lonlats() + return area.freeze((lons, lats), size=(x_size, y_size)) + + +class DynamicAreaDefinition(object): + """An AreaDefintion containing just a subset of the needed parameters. + + The purpose of this class is to be able to adapt the area extent and size of + the area to a given set of longitudes and latitudes, such that e.g. polar + satellite granules can be resampled optimaly to a give projection.""" + + def __init__(self, area_id=None, description=None, proj_dict=None, + x_size=None, y_size=None, area_extent=None, + optimize_projection=False): + """Initialize the DynamicAreaDefinition. + + area_id: + The name of the area. + description: + The description of the area. + proj_dict: + The dictionary of projection parameters. Doesn't have to be complete. + x_size, y_size: + The size of the resulting area. + area_extent: + The area extent of the area. + optimize_projection: + Whether the projection parameters have to be optimized. + """ + self.area_id = area_id + self.description = description + self.proj_dict = proj_dict + self.x_size = x_size + self.y_size = y_size + self.area_extent = area_extent + self.optimize_projection = optimize_projection + + def compute_domain(self, corners, resolution=None, size=None): + """Compute size and area_extent from corners and [size or resolution] + info.""" + if resolution is not None and size is not None: + raise ValueError("Both resolution and size can't be provided.") + + if size: + x_size, y_size = size + x_resolution = (corners[2] - corners[0]) * 1.0 / (x_size - 1) + y_resolution = (corners[3] - corners[1]) * 1.0 / (y_size - 1) + + if resolution: + try: + x_resolution, y_resolution = resolution + except TypeError: + x_resolution = y_resolution = resolution + x_size = int(np.rint((corners[2] - corners[0]) * 1.0 / + x_resolution + 1)) + y_size = int(np.rint((corners[3] - corners[1]) * 1.0 / + y_resolution + 1)) + + area_extent = (corners[0] - x_resolution / 2, + corners[1] - y_resolution / 2, + corners[2] + x_resolution / 2, + corners[3] + y_resolution / 2) + return area_extent, x_size, y_size + + def freeze(self, lonslats=None, + resolution=None, size=None, + proj_info=None): + """Create an AreaDefintion from this area with help of some extra info. + + lonlats: + the geographical coordinates to contain in the resulting area. + resolution: + the resolution of the resulting area. + size: + the size of the resulting area. + proj_info: + complementing parameters to the projection info. + + Resolution and Size parameters are ignored if the instance is created + with the `optimize_projection` flag set to True. + """ + if proj_info is not None: + self.proj_dict.update(proj_info) + + if self.optimize_projection: + return lonslats.compute_optimal_bb_area(self.proj_dict) + + if not self.area_extent or not self.x_size or not self.y_size: + proj4 = Proj(**self.proj_dict) + try: + lons, lats = lonslats + except (TypeError, ValueError): + lons, lats = lonslats.get_lonlats() + xarr, yarr = proj4(np.asarray(lons), np.asarray(lats)) + corners = [np.min(xarr), np.min(yarr), np.max(xarr), np.max(yarr)] + + domain = self.compute_domain(corners, resolution, size) + self.area_extent, self.x_size, self.y_size = domain + + return AreaDefinition(self.area_id, self.description, None, + self.proj_dict, self.x_size, self.y_size, + self.area_extent) + class AreaDefinition(BaseDefinition): diff --git a/pyresample/kd_tree.py b/pyresample/kd_tree.py index 4be4322..e8cdcd2 100644 --- a/pyresample/kd_tree.py +++ b/pyresample/kd_tree.py @@ -21,13 +21,13 @@ supported""" from __future__ import absolute_import +import sys import types import warnings -import sys import numpy as np -from pyresample import geometry, data_reduce, _spatial_mp +from pyresample import _spatial_mp, data_reduce, geometry if sys.version < '3': range = xrange @@ -66,20 +66,20 @@ def resample_nearest(source_geo_def, data, target_geo_def, ---------- source_geo_def : object Geometry definition of source - data : numpy array + data : numpy array 1d array of single channel data points or (source_size, k) array of k channels of datapoints target_geo_def : object Geometry definition of target - radius_of_influence : float + radius_of_influence : float Cut off distance in meters epsilon : float, optional Allowed uncertainty in meters. Increasing uncertainty reduces execution time fill_value : int or None, optional Set undetermined pixels to this value. - If fill_value is None a masked array is returned - with undetermined pixels masked + If fill_value is None a masked array is returned + with undetermined pixels masked reduce_data : bool, optional Perform initial coarse reduction of source dataset in order to reduce execution time @@ -91,7 +91,7 @@ def resample_nearest(source_geo_def, data, target_geo_def, Returns ------- - data : numpy array + data : numpy array Source data resampled to target geometry """ @@ -110,26 +110,26 @@ def resample_gauss(source_geo_def, data, target_geo_def, ---------- source_geo_def : object Geometry definition of source - data : numpy array + data : numpy array Array of single channel data points or (source_geo_def.shape, k) array of k channels of datapoints target_geo_def : object Geometry definition of target - radius_of_influence : float + radius_of_influence : float Cut off distance in meters - sigmas : list of floats or float - List of sigmas to use for the gauss weighting of each + sigmas : list of floats or float + List of sigmas to use for the gauss weighting of each channel 1 to k, w_k = exp(-dist^2/sigma_k^2). If only one channel is resampled sigmas is a single float value. - neighbours : int, optional + neighbours : int, optional The number of neigbours to consider for each grid point epsilon : float, optional Allowed uncertainty in meters. Increasing uncertainty reduces execution time - fill_value : {int, None}, optional + fill_value : {int, None}, optional Set undetermined pixels to this value. - If fill_value is None a masked array is returned - with undetermined pixels masked + If fill_value is None a masked array is returned + with undetermined pixels masked reduce_data : bool, optional Perform initial coarse reduction of source dataset in order to reduce execution time @@ -148,7 +148,7 @@ def resample_gauss(source_geo_def, data, target_geo_def, data, stddev, counts : numpy array, numpy array, numpy array (if with_uncert == True) Source data resampled to target geometry. Weighted standard devaition for all pixels having more than one source value - Counts of number of source values used in weighting per pixel + Counts of number of source values used in weighting per pixel """ def gauss(sigma): @@ -190,27 +190,27 @@ def resample_custom(source_geo_def, data, target_geo_def, ---------- source_geo_def : object Geometry definition of source - data : numpy array + data : numpy array Array of single channel data points or (source_geo_def.shape, k) array of k channels of datapoints target_geo_def : object Geometry definition of target - radius_of_influence : float + radius_of_influence : float Cut off distance in meters - weight_funcs : list of function objects or function object - List of weight functions f(dist) to use for the weighting + weight_funcs : list of function objects or function object + List of weight functions f(dist) to use for the weighting of each channel 1 to k. If only one channel is resampled weight_funcs is a single function object. - neighbours : int, optional + neighbours : int, optional The number of neigbours to consider for each grid point epsilon : float, optional Allowed uncertainty in meters. Increasing uncertainty reduces execution time - fill_value : {int, None}, optional + fill_value : {int, None}, optional Set undetermined pixels to this value. - If fill_value is None a masked array is returned - with undetermined pixels masked + If fill_value is None a masked array is returned + with undetermined pixels masked reduce_data : bool, optional Perform initial coarse reduction of source dataset in order to reduce execution time @@ -282,9 +282,9 @@ def get_neighbour_info(source_geo_def, target_geo_def, radius_of_influence, Geometry definition of source target_geo_def : object Geometry definition of target - radius_of_influence : float + radius_of_influence : float Cut off distance in meters - neighbours : int, optional + neighbours : int, optional The number of neigbours to consider for each grid point epsilon : float, optional Allowed uncertainty in meters. Increasing uncertainty @@ -300,7 +300,7 @@ def get_neighbour_info(source_geo_def, target_geo_def, radius_of_influence, Returns ------- - (valid_input_index, valid_output_index, + (valid_input_index, valid_output_index, index_array, distance_array) : tuple of numpy arrays Neighbour resampling info """ @@ -390,8 +390,8 @@ def _get_valid_input_index(source_geo_def, target_geo_def, reduce_data, """Find indices of reduced inputput data""" source_lons, source_lats = source_geo_def.get_lonlats(nprocs=nprocs) - source_lons = source_lons.ravel() - source_lats = source_lats.ravel() + source_lons = np.asanyarray(source_lons).ravel() + source_lats = np.asanyarray(source_lats).ravel() if source_lons.size == 0 or source_lats.size == 0: raise ValueError('Cannot resample empty data set') @@ -400,9 +400,8 @@ def _get_valid_input_index(source_geo_def, target_geo_def, reduce_data, raise ValueError('Mismatch between lons and lats') # Remove illegal values - valid_data = ((source_lons >= -180) & (source_lons <= 180) & - (source_lats <= 90) & (source_lats >= -90)) - valid_input_index = np.ones(source_geo_def.size, dtype=np.bool) + valid_input_index = ((source_lons >= -180) & (source_lons <= 180) & + (source_lats <= 90) & (source_lats >= -90)) if reduce_data: # Reduce dataset @@ -415,16 +414,15 @@ def _get_valid_input_index(source_geo_def, target_geo_def, reduce_data, geometry.AreaDefinition))): # Resampling from swath to grid or from grid to grid lonlat_boundary = target_geo_def.get_boundary_lonlats() - valid_input_index = \ + + # Combine reduced and legal values + valid_input_index &= \ data_reduce.get_valid_index_from_lonlat_boundaries( lonlat_boundary[0], lonlat_boundary[1], source_lons, source_lats, radius_of_influence) - # Combine reduced and legal values - valid_input_index = (valid_data & valid_input_index) - if(isinstance(valid_input_index, np.ma.core.MaskedArray)): # Make sure valid_input_index is not a masked array valid_input_index = valid_input_index.filled(False) @@ -469,7 +467,7 @@ def _create_resample_kdtree(source_lons, source_lats, valid_input_index, nprocs= """ if not isinstance(source_geo_def, geometry.BaseDefinition): raise TypeError('source_geo_def must be of geometry type') - + #Get reduced cartesian coordinates and flatten them source_cartesian_coords = source_geo_def.get_cartesian_coords(nprocs=nprocs) input_coords = geometry._flatten_cartesian_coords(source_cartesian_coords) @@ -599,20 +597,20 @@ def get_sample_from_neighbour_info(resample_type, output_shape, data, distance_array : numpy array, optional distance_array from get_neighbour_info Not needed for 'nn' resample type - weight_funcs : list of function objects or function object, optional - List of weight functions f(dist) to use for the weighting + weight_funcs : list of function objects or function object, optional + List of weight functions f(dist) to use for the weighting of each channel 1 to k. If only one channel is resampled weight_funcs is a single function object. Must be supplied when using 'custom' resample type fill_value : int or None, optional Set undetermined pixels to this value. - If fill_value is None a masked array is returned + If fill_value is None a masked array is returned with undetermined pixels masked Returns ------- - result : numpy array + result : numpy array Source data resampled to target geometry """ diff --git a/pyresample/test/test_ewa_ll2cr.py b/pyresample/test/test_ewa_ll2cr.py index 99caaaa..018b0e3 100644 --- a/pyresample/test/test_ewa_ll2cr.py +++ b/pyresample/test/test_ewa_ll2cr.py @@ -24,6 +24,7 @@ import sys import logging import numpy as np +from pyresample.test.utils import create_test_longitude, create_test_latitude if sys.version_info < (2, 7): import unittest2 as unittest else: @@ -32,28 +33,6 @@ else: LOG = logging.getLogger(__name__) -def create_test_longitude(start, stop, shape, twist_factor=0.0, dtype=np.float32): - if start > stop: - stop += 360.0 - - lon_row = np.linspace(start, stop, num=shape[1]).astype(dtype) - twist_array = np.arange(shape[0]).reshape((shape[0], 1)) * twist_factor - lon_array = np.repeat([lon_row], shape[0], axis=0) - lon_array += twist_array - - if stop > 360.0: - lon_array[lon_array > 360.0] -= 360 - return lon_array - - -def create_test_latitude(start, stop, shape, twist_factor=0.0, dtype=np.float32): - lat_col = np.linspace(start, stop, num=shape[0]).astype(dtype).reshape((shape[0], 1)) - twist_array = np.arange(shape[1]) * twist_factor - lat_array = np.repeat(lat_col, shape[1], axis=1) - lat_array += twist_array - return lat_array - - dynamic_wgs84 = { "grid_name": "test_wgs84_fit", "origin_x": None, diff --git a/pyresample/test/test_geometry.py b/pyresample/test/test_geometry.py index 164d32c..73a3050 100644 --- a/pyresample/test/test_geometry.py +++ b/pyresample/test/test_geometry.py @@ -165,39 +165,6 @@ class Test(unittest.TestCase): "BaseDefinition did not maintain dtype of longitudes (in:%s out:%s)" % (lons2_ints.dtype, lons.dtype,)) - def test_swath(self): - lons1 = np.fromfunction(lambda y, x: 3 + (10.0 / 100) * x, (5000, 100)) - lats1 = np.fromfunction( - lambda y, x: 75 - (50.0 / 5000) * y, (5000, 100)) - - swath_def = geometry.SwathDefinition(lons1, lats1) - - lons2, lats2 = swath_def.get_lonlats() - - self.assertFalse(id(lons1) != id(lons2) or id(lats1) != id(lats2), - msg='Caching of swath coordinates failed') - - def test_swath_wrap(self): - lons1 = np.fromfunction(lambda y, x: 3 + (10.0 / 100) * x, (5000, 100)) - lats1 = np.fromfunction( - lambda y, x: 75 - (50.0 / 5000) * y, (5000, 100)) - - lons1 += 180. - with catch_warnings() as w1: - swath_def = geometry.BaseDefinition(lons1, lats1) - self.assertFalse( - len(w1) != 1, 'Failed to trigger a warning on longitude wrapping') - self.assertFalse(('-180:+180' not in str(w1[0].message)), - 'Failed to trigger correct warning about longitude wrapping') - - lons2, lats2 = swath_def.get_lonlats() - - self.assertTrue(id(lons1) != id(lons2), - msg='Caching of swath coordinates failed with longitude wrapping') - - self.assertTrue(lons2.min() > -180 and lons2.max() < 180, - 'Wrapping of longitudes failed for SwathDefinition') - def test_area_equal(self): area_def = geometry.AreaDefinition('areaD', 'Europe (3km, HRV, VTC)', 'areaD', {'a': '6378144.0', @@ -260,23 +227,6 @@ class Test(unittest.TestCase): self.assertFalse( area_def == msg_area, 'area_defs are not expected to be equal') - def test_swath_equal(self): - lons = np.array([1.2, 1.3, 1.4, 1.5]) - lats = np.array([65.9, 65.86, 65.82, 65.78]) - swath_def = geometry.SwathDefinition(lons, lats) - swath_def2 = geometry.SwathDefinition(lons, lats) - self.assertFalse( - swath_def != swath_def2, 'swath_defs are not equal as expected') - - def test_swath_not_equal(self): - lats1 = np.array([65.9, 65.86, 65.82, 65.78]) - lons = np.array([1.2, 1.3, 1.4, 1.5]) - lats2 = np.array([65.91, 65.85, 65.80, 65.75]) - swath_def = geometry.SwathDefinition(lons, lats1) - swath_def2 = geometry.SwathDefinition(lons, lats2) - self.assertFalse( - swath_def == swath_def2, 'swath_defs are not expected to be equal') - def test_swath_equal_area(self): area_def = geometry.AreaDefinition('areaD', 'Europe (3km, HRV, VTC)', 'areaD', {'a': '6378144.0', @@ -353,60 +303,6 @@ class Test(unittest.TestCase): self.assertFalse( area_def == swath_def, "swath_def and area_def should be different") - def test_concat_1d(self): - lons1 = np.array([1, 2, 3]) - lats1 = np.array([1, 2, 3]) - lons2 = np.array([4, 5, 6]) - lats2 = np.array([4, 5, 6]) - swath_def1 = geometry.SwathDefinition(lons1, lats1) - swath_def2 = geometry.SwathDefinition(lons2, lats2) - swath_def_concat = swath_def1.concatenate(swath_def2) - expected = np.array([1, 2, 3, 4, 5, 6]) - self.assertTrue(np.array_equal(swath_def_concat.lons, expected) and - np.array_equal(swath_def_concat.lons, expected), - 'Failed to concatenate 1D swaths') - - def test_concat_2d(self): - lons1 = np.array([[1, 2, 3], [3, 4, 5], [5, 6, 7]]) - lats1 = np.array([[1, 2, 3], [3, 4, 5], [5, 6, 7]]) - lons2 = np.array([[4, 5, 6], [6, 7, 8]]) - lats2 = np.array([[4, 5, 6], [6, 7, 8]]) - swath_def1 = geometry.SwathDefinition(lons1, lats1) - swath_def2 = geometry.SwathDefinition(lons2, lats2) - swath_def_concat = swath_def1.concatenate(swath_def2) - expected = np.array( - [[1, 2, 3], [3, 4, 5], [5, 6, 7], [4, 5, 6], [6, 7, 8]]) - self.assertTrue(np.array_equal(swath_def_concat.lons, expected) and - np.array_equal(swath_def_concat.lons, expected), - 'Failed to concatenate 2D swaths') - - def test_append_1d(self): - lons1 = np.array([1, 2, 3]) - lats1 = np.array([1, 2, 3]) - lons2 = np.array([4, 5, 6]) - lats2 = np.array([4, 5, 6]) - swath_def1 = geometry.SwathDefinition(lons1, lats1) - swath_def2 = geometry.SwathDefinition(lons2, lats2) - swath_def1.append(swath_def2) - expected = np.array([1, 2, 3, 4, 5, 6]) - self.assertTrue(np.array_equal(swath_def1.lons, expected) and - np.array_equal(swath_def1.lons, expected), - 'Failed to append 1D swaths') - - def test_append_2d(self): - lons1 = np.array([[1, 2, 3], [3, 4, 5], [5, 6, 7]]) - lats1 = np.array([[1, 2, 3], [3, 4, 5], [5, 6, 7]]) - lons2 = np.array([[4, 5, 6], [6, 7, 8]]) - lats2 = np.array([[4, 5, 6], [6, 7, 8]]) - swath_def1 = geometry.SwathDefinition(lons1, lats1) - swath_def2 = geometry.SwathDefinition(lons2, lats2) - swath_def1.append(swath_def2) - expected = np.array( - [[1, 2, 3], [3, 4, 5], [5, 6, 7], [4, 5, 6], [6, 7, 8]]) - self.assertTrue(np.array_equal(swath_def1.lons, expected) and - np.array_equal(swath_def1.lons, expected), - 'Failed to append 2D swaths') - def test_grid_filter_valid(self): lons = np.array([-170, -30, 30, 170]) lats = np.array([20, -40, 50, -80]) @@ -706,6 +602,205 @@ class Test(unittest.TestCase): self.assertTrue((y__.data == y_expects).all()) +def assert_np_dict_allclose(dict1, dict2): + + assert set(dict1.keys()) == set(dict2.keys()) + for key, val in dict1.items(): + try: + np.testing.assert_allclose(val, dict2[key]) + except TypeError: + assert(val == dict2[key]) + + +class TestSwathDefinition(unittest.TestCase): + """Test the SwathDefinition.""" + + def test_swath(self): + lons1 = np.fromfunction(lambda y, x: 3 + (10.0 / 100) * x, (5000, 100)) + lats1 = np.fromfunction( + lambda y, x: 75 - (50.0 / 5000) * y, (5000, 100)) + + swath_def = geometry.SwathDefinition(lons1, lats1) + + lons2, lats2 = swath_def.get_lonlats() + + self.assertFalse(id(lons1) != id(lons2) or id(lats1) != id(lats2), + msg='Caching of swath coordinates failed') + + def test_swath_wrap(self): + lons1 = np.fromfunction(lambda y, x: 3 + (10.0 / 100) * x, (5000, 100)) + lats1 = np.fromfunction( + lambda y, x: 75 - (50.0 / 5000) * y, (5000, 100)) + + lons1 += 180. + with catch_warnings() as w1: + swath_def = geometry.BaseDefinition(lons1, lats1) + self.assertFalse( + len(w1) != 1, 'Failed to trigger a warning on longitude wrapping') + self.assertFalse(('-180:+180' not in str(w1[0].message)), + 'Failed to trigger correct warning about longitude wrapping') + + lons2, lats2 = swath_def.get_lonlats() + + self.assertTrue(id(lons1) != id(lons2), + msg='Caching of swath coordinates failed with longitude wrapping') + + self.assertTrue(lons2.min() > -180 and lons2.max() < 180, + 'Wrapping of longitudes failed for SwathDefinition') + + def test_concat_1d(self): + lons1 = np.array([1, 2, 3]) + lats1 = np.array([1, 2, 3]) + lons2 = np.array([4, 5, 6]) + lats2 = np.array([4, 5, 6]) + swath_def1 = geometry.SwathDefinition(lons1, lats1) + swath_def2 = geometry.SwathDefinition(lons2, lats2) + swath_def_concat = swath_def1.concatenate(swath_def2) + expected = np.array([1, 2, 3, 4, 5, 6]) + self.assertTrue(np.array_equal(swath_def_concat.lons, expected) and + np.array_equal(swath_def_concat.lons, expected), + 'Failed to concatenate 1D swaths') + + def test_concat_2d(self): + lons1 = np.array([[1, 2, 3], [3, 4, 5], [5, 6, 7]]) + lats1 = np.array([[1, 2, 3], [3, 4, 5], [5, 6, 7]]) + lons2 = np.array([[4, 5, 6], [6, 7, 8]]) + lats2 = np.array([[4, 5, 6], [6, 7, 8]]) + swath_def1 = geometry.SwathDefinition(lons1, lats1) + swath_def2 = geometry.SwathDefinition(lons2, lats2) + swath_def_concat = swath_def1.concatenate(swath_def2) + expected = np.array( + [[1, 2, 3], [3, 4, 5], [5, 6, 7], [4, 5, 6], [6, 7, 8]]) + self.assertTrue(np.array_equal(swath_def_concat.lons, expected) and + np.array_equal(swath_def_concat.lons, expected), + 'Failed to concatenate 2D swaths') + + def test_append_1d(self): + lons1 = np.array([1, 2, 3]) + lats1 = np.array([1, 2, 3]) + lons2 = np.array([4, 5, 6]) + lats2 = np.array([4, 5, 6]) + swath_def1 = geometry.SwathDefinition(lons1, lats1) + swath_def2 = geometry.SwathDefinition(lons2, lats2) + swath_def1.append(swath_def2) + expected = np.array([1, 2, 3, 4, 5, 6]) + self.assertTrue(np.array_equal(swath_def1.lons, expected) and + np.array_equal(swath_def1.lons, expected), + 'Failed to append 1D swaths') + + def test_append_2d(self): + lons1 = np.array([[1, 2, 3], [3, 4, 5], [5, 6, 7]]) + lats1 = np.array([[1, 2, 3], [3, 4, 5], [5, 6, 7]]) + lons2 = np.array([[4, 5, 6], [6, 7, 8]]) + lats2 = np.array([[4, 5, 6], [6, 7, 8]]) + swath_def1 = geometry.SwathDefinition(lons1, lats1) + swath_def2 = geometry.SwathDefinition(lons2, lats2) + swath_def1.append(swath_def2) + expected = np.array( + [[1, 2, 3], [3, 4, 5], [5, 6, 7], [4, 5, 6], [6, 7, 8]]) + self.assertTrue(np.array_equal(swath_def1.lons, expected) and + np.array_equal(swath_def1.lons, expected), + 'Failed to append 2D swaths') + + def test_swath_equal(self): + """Test swath equality.""" + lons = np.array([1.2, 1.3, 1.4, 1.5]) + lats = np.array([65.9, 65.86, 65.82, 65.78]) + swath_def = geometry.SwathDefinition(lons, lats) + swath_def2 = geometry.SwathDefinition(lons, lats) + self.assertFalse( + swath_def != swath_def2, 'swath_defs are not equal as expected') + + def test_swath_not_equal(self): + """Test swath inequality.""" + lats1 = np.array([65.9, 65.86, 65.82, 65.78]) + lons = np.array([1.2, 1.3, 1.4, 1.5]) + lats2 = np.array([65.91, 65.85, 65.80, 65.75]) + swath_def = geometry.SwathDefinition(lons, lats1) + swath_def2 = geometry.SwathDefinition(lons, lats2) + self.assertFalse( + swath_def == swath_def2, 'swath_defs are not expected to be equal') + + def test_compute_omerc_params(self): + """Test omerc parameters computation.""" + lats = np.array([[85.23900604248047, 62.256004333496094, 35.58000183105469], + [80.84000396728516, 60.74200439453125, 34.08500289916992], + [67.07600402832031, 54.147003173828125, 30.547000885009766]]).T + + lons = np.array([[-90.67900085449219, -21.565000534057617, -21.525001525878906], + [79.11000061035156, 7.284000396728516, -5.107000350952148], + [81.26400756835938, 29.672000885009766, 10.260000228881836]]).T + + area = geometry.SwathDefinition(lons, lats) + proj_dict = {'no_rot': True, 'lonc': 5.340645620216994, + 'ellps': 'WGS84', 'proj': 'omerc', + 'alpha': 19.022450179020247, 'lat_0': 60.7420043944989} + assert_np_dict_allclose(area._compute_omerc_parameters('WGS84'), + proj_dict) + + def test_get_edge_lonlats(self): + """Test the `get_edge_lonlats` functionality.""" + lats = np.array([[85.23900604248047, 62.256004333496094, 35.58000183105469], + [80.84000396728516, 60.74200439453125, 34.08500289916992], + [67.07600402832031, 54.147003173828125, 30.547000885009766]]).T + + lons = np.array([[-90.67900085449219, -21.565000534057617, -21.525001525878906], + [79.11000061035156, 7.284000396728516, -5.107000350952148], + [81.26400756835938, 29.672000885009766, 10.260000228881836]]).T + + area = geometry.SwathDefinition(lons, lats) + lons, lats = area.get_edge_lonlats() + + np.testing.assert_allclose(lons, [-90.67900085, 79.11000061, 81.26400757, + 81.26400757, 29.67200089, 10.26000023, + 10.26000023, -5.10700035, -21.52500153, + -21.52500153, -21.56500053, -90.67900085]) + np.testing.assert_allclose(lats, [85.23900604, 80.84000397, 67.07600403, + 67.07600403, 54.14700317, 30.54700089, + 30.54700089, 34.0850029, 35.58000183, + 35.58000183, 62.25600433, 85.23900604]) + + lats = np.array([[80., 80., 80.], + [80., 90., 80], + [80., 80., 80.]]).T + + lons = np.array([[-45., 0., 45.], + [-90, 0., 90.], + [-135., 180., 135.]]).T + + area = geometry.SwathDefinition(lons, lats) + lons, lats = area.get_edge_lonlats() + + np.testing.assert_allclose(lons, [-45., -90., -135., -135., -180., 135., + 135., 90., 45., 45., 0., -45.]) + np.testing.assert_allclose(lats, [80., 80., 80., 80., 80., 80., 80., + 80., 80., 80., 80., 80.]) + + def test_compute_optimal_bb(self): + """Test computing the bb area.""" + lats = np.array([[85.23900604248047, 62.256004333496094, 35.58000183105469], + [80.84000396728516, 60.74200439453125, 34.08500289916992], + [67.07600402832031, 54.147003173828125, 30.547000885009766]]).T + + lons = np.array([[-90.67900085449219, -21.565000534057617, -21.525001525878906], + [79.11000061035156, 7.284000396728516, -5.107000350952148], + [81.26400756835938, 29.672000885009766, 10.260000228881836]]).T + + area = geometry.SwathDefinition(lons, lats) + + res = area.compute_optimal_bb_area({'proj': 'omerc', 'ellps': 'WGS84'}) + + np.testing.assert_allclose(res.area_extent, (2286629.731529, + -2359693.817959, + 11729881.856072, + 2437001.523925)) + proj_dict = {'no_rot': True, 'lonc': 5.340645620216994, + 'ellps': 'WGS84', 'proj': 'omerc', + 'alpha': 19.022450179020247, 'lat_0': 60.7420043944989} + assert_np_dict_allclose(res.proj_dict, proj_dict) + self.assertEqual(res.shape, (3, 3)) + + class TestStackedAreaDefinition(unittest.TestCase): """Test the StackedAreaDefition.""" @@ -864,6 +959,86 @@ class TestStackedAreaDefinition(unittest.TestCase): area_extent) +class TestDynamicAreaDefinition(unittest.TestCase): + """Test the DynamicAreaDefinition class.""" + + def test_freeze(self): + """Test freezing the area.""" + area = geometry.DynamicAreaDefinition('test_area', 'A test area', + {'proj': 'laea'}) + lons = [10, 10, 22, 22] + lats = [50, 66, 66, 50] + result = area.freeze((lons, lats), + resolution=3000, + proj_info={'lon0': 16, 'lat0': 58}) + + np.testing.assert_allclose(result.area_extent, (538546.7274949469, + 5380808.879250369, + 1724415.6519203288, + 6998895.701001488)) + self.assertEqual(result.proj_dict['lon0'], 16) + self.assertEqual(result.proj_dict['lat0'], 58) + self.assertEqual(result.x_size, 395) + self.assertEqual(result.y_size, 539) + + def test_freeze_with_bb(self): + """Test freezing the area with bounding box computation.""" + # area = geometry.DynamicAreaDefinition('test_area', 'A test area', + # {'proj': 'omerc'}, + # optimize_projection=False) + # lons = [[10, 12.1, 14.2, 16.3], + # [10, 12, 14, 16], + # [10, 11.9, 13.8, 15.7]] + # lats = [[66, 67, 68, 69.], + # [58, 59, 60, 61], + # [50, 51, 52, 53]] + # sdef = geometry.SwathDefinition(lons, lats) + # result = area.freeze(sdef, + # resolution=1000) + # self.assertTupleEqual(result.area_extent, (5578795.1654752363, + # -270848.61872542271, + # 7694893.3964453982, + # 126974.877141819)) + # self.assertEqual(result.x_size, 2116) + # self.assertEqual(result.y_size, 398) + + area = geometry.DynamicAreaDefinition('test_area', 'A test area', + {'proj': 'omerc'}, + optimize_projection=True) + lons = [[10, 12.1, 14.2, 16.3], + [10, 12, 14, 16], + [10, 11.9, 13.8, 15.7]] + lats = [[66, 67, 68, 69.], + [58, 59, 60, 61], + [50, 51, 52, 53]] + sdef = geometry.SwathDefinition(lons, lats) + result = area.freeze(sdef, + resolution=1000) + np.testing.assert_allclose(result.area_extent, (5050520.6077326955, + -336485.86803662963, + 8223167.9541879389, + 192612.12645302597)) + self.assertEqual(result.x_size, 3) + self.assertEqual(result.y_size, 4) + + def test_compute_domain(self): + """Test computing size and area extent.""" + area = geometry.DynamicAreaDefinition('test_area', 'A test area', + {'proj': 'laea'}) + corners = [1, 1, 9, 9] + self.assertRaises(ValueError, area.compute_domain, corners, 1, 1) + + area_extent, x_size, y_size = area.compute_domain(corners, size=(5, 5)) + self.assertTupleEqual(area_extent, (0, 0, 10, 10)) + self.assertEqual(x_size, 5) + self.assertEqual(y_size, 5) + + area_extent, x_size, y_size = area.compute_domain(corners, resolution=2) + self.assertTupleEqual(area_extent, (0, 0, 10, 10)) + self.assertEqual(x_size, 5) + self.assertEqual(y_size, 5) + + def suite(): """The test suite. """ @@ -871,6 +1046,8 @@ def suite(): mysuite = unittest.TestSuite() mysuite.addTest(loader.loadTestsFromTestCase(Test)) mysuite.addTest(loader.loadTestsFromTestCase(TestStackedAreaDefinition)) + mysuite.addTest(loader.loadTestsFromTestCase(TestDynamicAreaDefinition)) + mysuite.addTest(loader.loadTestsFromTestCase(TestSwathDefinition)) return mysuite diff --git a/pyresample/test/test_utils.py b/pyresample/test/test_utils.py index d79af05..7f0409e 100644 --- a/pyresample/test/test_utils.py +++ b/pyresample/test/test_utils.py @@ -3,7 +3,7 @@ import unittest import numpy as np -from pyresample import utils +from pyresample.test.utils import create_test_longitude, create_test_latitude def tmp(f): @@ -14,6 +14,7 @@ def tmp(f): class TestLegacyAreaParser(unittest.TestCase): def test_area_parser_legacy(self): """Test legacy area parser.""" + from pyresample import utils ease_nh, ease_sh = utils.parse_area_file(os.path.join(os.path.dirname(__file__), 'test_files', 'areas.cfg'), 'ease_nh', 'ease_sh') @@ -37,6 +38,7 @@ Area extent: (-5326849.0625, -5326849.0625, 5326849.0625, 5326849.0625)""" self.assertEquals(ease_sh.__str__(), sh_str) def test_load_area(self): + from pyresample import utils ease_nh = utils.load_area(os.path.join(os.path.dirname(__file__), 'test_files', 'areas.cfg'), 'ease_nh') @@ -50,6 +52,7 @@ Area extent: (-5326849.0625, -5326849.0625, 5326849.0625, 5326849.0625)""" self.assertEquals(nh_str, ease_nh.__str__()) def test_not_found_exception(self): + from pyresample import utils self.assertRaises(utils.AreaNotFound, utils.parse_area_file, os.path.join( os.path.dirname(__file__), 'test_files', 'areas.cfg'), @@ -59,6 +62,7 @@ Area extent: (-5326849.0625, -5326849.0625, 5326849.0625, 5326849.0625)""" class TestYAMLAreaParser(unittest.TestCase): def test_area_parser_yaml(self): """Test YAML area parser.""" + from pyresample import utils ease_nh, ease_sh = utils.parse_area_file(os.path.join(os.path.dirname(__file__), 'test_files', 'areas.yaml'), @@ -81,6 +85,7 @@ Area extent: (-5326849.0625, -5326849.0625, 5326849.0625, 5326849.0625)""" self.assertEquals(ease_sh.__str__(), sh_str) def test_multiple_file_content(self): + from pyresample import utils area_list = ["""ease_sh: description: Antarctic EASE grid projection: @@ -119,9 +124,62 @@ Area extent: (-5326849.0625, -5326849.0625, 5326849.0625, 5326849.0625)""" self.assertIn(results[1].area_id, ('ease_sh', 'ease_sh2')) +class TestPreprocessing(unittest.TestCase): + def test_nearest_neighbor_area_area(self): + from pyresample import utils, geometry + proj_str = "+proj=lcc +datum=WGS84 +ellps=WGS84 +lat_0=25 +lat_1=25 +lon_0=-95 +units=m +no_defs" + proj_dict = utils.proj4_str_to_dict(proj_str) + extents = [0, 0, 1000. * 5000, 1000. * 5000] + area_def = geometry.AreaDefinition('CONUS', 'CONUS', 'CONUS', + proj_dict, 400, 500, extents) + + extents2 = [-1000, -1000, 1000. * 4000, 1000. * 4000] + area_def2 = geometry.AreaDefinition('CONUS', 'CONUS', 'CONUS', + proj_dict, 600, 700, extents2) + rows, cols = utils.generate_nearest_neighbour_linesample_arrays(area_def, area_def2, 12000.) + + def test_nearest_neighbor_area_grid(self): + from pyresample import utils, geometry + lon_arr = create_test_longitude(-94.9, -90.0, (50, 100), dtype=np.float64) + lat_arr = create_test_latitude(25.1, 30.0, (50, 100), dtype=np.float64) + grid = geometry.GridDefinition(lons=lon_arr, lats=lat_arr) + + proj_str = "+proj=lcc +datum=WGS84 +ellps=WGS84 +lat_0=25 +lat_1=25 +lon_0=-95 +units=m +no_defs" + proj_dict = utils.proj4_str_to_dict(proj_str) + extents = [0, 0, 1000. * 5000, 1000. * 5000] + area_def = geometry.AreaDefinition('CONUS', 'CONUS', 'CONUS', + proj_dict, 400, 500, extents) + rows, cols = utils.generate_nearest_neighbour_linesample_arrays(area_def, grid, 12000.) + + def test_nearest_neighbor_grid_area(self): + from pyresample import utils, geometry + proj_str = "+proj=lcc +datum=WGS84 +ellps=WGS84 +lat_0=25 +lat_1=25 +lon_0=-95 +units=m +no_defs" + proj_dict = utils.proj4_str_to_dict(proj_str) + extents = [0, 0, 1000. * 2500., 1000. * 2000.] + area_def = geometry.AreaDefinition('CONUS', 'CONUS', 'CONUS', + proj_dict, 40, 50, extents) + + lon_arr = create_test_longitude(-100.0, -60.0, (550, 500), dtype=np.float64) + lat_arr = create_test_latitude(20.0, 45.0, (550, 500), dtype=np.float64) + grid = geometry.GridDefinition(lons=lon_arr, lats=lat_arr) + rows, cols = utils.generate_nearest_neighbour_linesample_arrays(grid, area_def, 12000.) + + def test_nearest_neighbor_grid_grid(self): + from pyresample import utils, geometry + lon_arr = create_test_longitude(-95.0, -85.0, (40, 50), dtype=np.float64) + lat_arr = create_test_latitude(25.0, 35.0, (40, 50), dtype=np.float64) + grid_dst = geometry.GridDefinition(lons=lon_arr, lats=lat_arr) + + lon_arr = create_test_longitude(-100.0, -80.0, (400, 500), dtype=np.float64) + lat_arr = create_test_latitude(20.0, 40.0, (400, 500), dtype=np.float64) + grid = geometry.GridDefinition(lons=lon_arr, lats=lat_arr) + rows, cols = utils.generate_nearest_neighbour_linesample_arrays(grid, grid_dst, 12000.) + + class TestMisc(unittest.TestCase): def test_wrap_longitudes(self): # test that we indeed wrap to [-180:+180[ + from pyresample import utils step = 60 lons = np.arange(-360, 360 + step, step) self.assertTrue( @@ -133,10 +191,36 @@ class TestMisc(unittest.TestCase): def test_unicode_proj4_string(self): """Test that unicode is accepted for area creation. """ + from pyresample import utils utils.get_area_def(u"eurol", u"eurol", u"bla", u'+proj=stere +a=6378273 +b=6356889.44891 +lat_0=90 +lat_ts=70 +lon_0=-45', 1000, 1000, (-1000, -1000, 1000, 1000)) + def test_proj4_radius_parameters_provided(self): + from pyresample import utils + a, b = utils.proj4_radius_parameters( + '+proj=stere +a=6378273 +b=6356889.44891', + ) + np.testing.assert_almost_equal(a, 6378273) + np.testing.assert_almost_equal(b, 6356889.44891) + + def test_proj4_radius_parameters_ellps(self): + from pyresample import utils + a, b = utils.proj4_radius_parameters( + '+proj=stere +ellps=WGS84', + ) + np.testing.assert_almost_equal(a, 6378137.) + np.testing.assert_almost_equal(b, 6356752.314245, decimal=6) + + def test_proj4_radius_parameters_default(self): + from pyresample import utils + a, b = utils.proj4_radius_parameters( + '+proj=lcc', + ) + # WGS84 + np.testing.assert_almost_equal(a, 6378137.) + np.testing.assert_almost_equal(b, 6356752.314245, decimal=6) + def suite(): """The test suite. @@ -145,6 +229,7 @@ def suite(): mysuite = unittest.TestSuite() mysuite.addTest(loader.loadTestsFromTestCase(TestLegacyAreaParser)) mysuite.addTest(loader.loadTestsFromTestCase(TestYAMLAreaParser)) + mysuite.addTest(loader.loadTestsFromTestCase(TestPreprocessing)) mysuite.addTest(loader.loadTestsFromTestCase(TestMisc)) return mysuite diff --git a/pyresample/test/utils.py b/pyresample/test/utils.py index 39033e4..1dbbcf9 100644 --- a/pyresample/test/utils.py +++ b/pyresample/test/utils.py @@ -25,6 +25,7 @@ import six import types import warnings +import numpy as np _deprecations_as_exceptions = False _include_astropy_deprecations = False @@ -151,3 +152,24 @@ class catch_warnings(warnings.catch_warnings): treat_deprecations_as_exceptions() +def create_test_longitude(start, stop, shape, twist_factor=0.0, dtype=np.float32): + if start > stop: + stop += 360.0 + + lon_row = np.linspace(start, stop, num=shape[1]).astype(dtype) + twist_array = np.arange(shape[0]).reshape((shape[0], 1)) * twist_factor + lon_array = np.repeat([lon_row], shape[0], axis=0) + lon_array += twist_array + + if stop > 360.0: + lon_array[lon_array > 360.0] -= 360 + return lon_array + + +def create_test_latitude(start, stop, shape, twist_factor=0.0, dtype=np.float32): + lat_col = np.linspace(start, stop, num=shape[0]).astype(dtype).reshape((shape[0], 1)) + twist_array = np.arange(shape[1]) * twist_factor + lat_array = np.repeat(lat_col, shape[1], axis=1) + lat_array += twist_array + return lat_array + diff --git a/pyresample/utils.py b/pyresample/utils.py index 2bb70ed..3351b54 100644 --- a/pyresample/utils.py +++ b/pyresample/utils.py @@ -137,14 +137,27 @@ def _parse_yaml_area_file(area_file_name, *regions): area_name, area_file_name)) description = params['description'] projection = params['projection'] - xsize = params['shape']['width'] - ysize = params['shape']['height'] - area_extent = (params['area_extent']['lower_left_xy'] + - params['area_extent']['upper_right_xy']) - res.append(pr.geometry.AreaDefinition(area_name, description, - None, projection, - xsize, ysize, - area_extent)) + optimize_projection = params.get('optimize_projection', False) + try: + xsize = params['shape']['width'] + ysize = params['shape']['height'] + except KeyError: + xsize, ysize = None, None + try: + area_extent = (params['area_extent']['lower_left_xy'] + + params['area_extent']['upper_right_xy']) + except KeyError: + area_extent = None + area = pr.geometry.DynamicAreaDefinition(area_name, description, + projection, xsize, ysize, + area_extent, + optimize_projection) + try: + area = area.freeze() + except (TypeError, AttributeError): + pass + + res.append(area) return res @@ -278,9 +291,9 @@ def generate_quick_linesample_arrays(source_area_def, target_area_def, nprocs=1) Parameters ----------- source_area_def : object - Source area definition as AreaDefinition object + Source area definition as geometry definition object target_area_def : object - Target area definition as AreaDefinition object + Target area definition as geometry definition object nprocs : int, optional Number of processor cores to be used @@ -288,11 +301,6 @@ def generate_quick_linesample_arrays(source_area_def, target_area_def, nprocs=1) ------- (row_indices, col_indices) : tuple of numpy arrays """ - if not (isinstance(source_area_def, pr.geometry.AreaDefinition) and - isinstance(target_area_def, pr.geometry.AreaDefinition)): - raise TypeError('source_area_def and target_area_def must be of type ' - 'geometry.AreaDefinition') - lons, lats = target_area_def.get_lonlats(nprocs) source_pixel_y, source_pixel_x = pr.grid.get_linesample(lons, lats, @@ -314,9 +322,9 @@ def generate_nearest_neighbour_linesample_arrays(source_area_def, target_area_de Parameters ----------- source_area_def : object - Source area definition as AreaDefinition object + Source area definition as geometry definition object target_area_def : object - Target area definition as AreaDefinition object + Target area definition as geometry definition object radius_of_influence : float Cut off distance in meters nprocs : int, optional @@ -327,11 +335,6 @@ def generate_nearest_neighbour_linesample_arrays(source_area_def, target_area_de (row_indices, col_indices) : tuple of numpy arrays """ - if not (isinstance(source_area_def, pr.geometry.AreaDefinition) and - isinstance(target_area_def, pr.geometry.AreaDefinition)): - raise TypeError('source_area_def and target_area_def must be of type ' - 'geometry.AreaDefinition') - valid_input_index, valid_output_index, index_array, distance_array = \ pr.kd_tree.get_neighbour_info(source_area_def, target_area_def, @@ -400,13 +403,44 @@ def _get_proj4_args(proj4_args): def proj4_str_to_dict(proj4_str): """Convert PROJ.4 compatible string definition to dict - + Note: Key only parameters will be assigned a value of `True`. """ pairs = (x.split('=', 1) for x in proj4_str.split(" ")) return dict((x[0], (x[1] if len(x) == 2 else True)) for x in pairs) +def proj4_radius_parameters(proj4_dict): + """Calculate 'a' and 'b' radius parameters. + + Arguments: + proj4_dict (str or dict): PROJ.4 parameters + + Returns: + a (float), b (float): equatorial and polar radius + """ + if isinstance(proj4_dict, str): + new_info = proj4_str_to_dict(proj4_dict) + else: + new_info = proj4_dict.copy() + + # load information from PROJ.4 about the ellipsis if possible + if '+a' not in new_info or '+b' not in new_info: + import pyproj + ellps = pyproj.pj_ellps[new_info.get('+ellps', 'WGS84')] + new_info['+a'] = ellps['a'] + if 'b' not in ellps and 'rf' in ellps: + new_info['+f'] = 1. / ellps['rf'] + else: + new_info['+b'] = ellps['b'] + + if '+a' in new_info and '+f' in new_info and '+b' not in new_info: + # add a 'b' attribute back in if they used 'f' instead + new_info['+b'] = new_info['+a'] * (1 - new_info['+f']) + + return float(new_info['+a']), float(new_info['+b']) + + def _downcast_index_array(index_array, size): """Try to downcast array to uint16 """ diff --git a/pyresample/version.py b/pyresample/version.py index ee9fd28..6f605a3 100644 --- a/pyresample/version.py +++ b/pyresample/version.py @@ -15,4 +15,4 @@ # You should have received a copy of the GNU Lesser General Public License along # with this program. If not, see <http://www.gnu.org/licenses/>. -__version__ = '1.6.0' +__version__ = '1.6.1' -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/pyresample.git _______________________________________________ Pkg-grass-devel mailing list Pkg-grass-devel@lists.alioth.debian.org http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-grass-devel