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 e1687cd9f797143bfbf125e6694807479183cae8 Author: Antonio Valentino <antonio.valent...@tiscali.it> Date: Fri Sep 15 05:37:47 2017 +0000 New upstream version 1.6.0 --- .bumpversion.cfg | 2 +- .travis.yml | 5 ++- changelog.rst | 45 +++++++++++++++++++++ docs/source/geo_def.rst | 13 ++++-- pyresample/geometry.py | 40 +++++++++++------- pyresample/kd_tree.py | 23 ++++++++--- pyresample/test/test_geometry.py | 9 ++++- pyresample/test/test_kd_tree.py | 10 +++-- pyresample/test/test_utils.py | 87 ++++++++++++++++++++++++++++++---------- pyresample/utils.py | 72 +++++++++++++++++++++++++++++---- pyresample/version.py | 2 +- setup.cfg | 3 +- setup.py | 6 ++- 13 files changed, 251 insertions(+), 66 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 4eee916..eed2dcc 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.5.0 +current_version = 1.6.0 commit = True tag = True diff --git a/.travis.yml b/.travis.yml index 6693de1..f433d0c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,11 +4,12 @@ python: - '3.3' - '3.4' - '3.5' +- '3.6' before_install: - sudo add-apt-repository ppa:ubuntugis/ppa -y - sudo apt-get update -qq - sudo apt-get install libfreetype6-dev -- sudo apt-get install libgeos-3.3.8 libgeos-c1 libgeos-dev +- sudo apt-get install libgeos-dev install: - if [[ $TRAVIS_PYTHON_VERSION == "2.7" ]]; then pip install "matplotlib>=1.5.0"; fi - if [[ $TRAVIS_PYTHON_VERSION == "2.7" ]]; then pip install "sphinx>=1.5.0"; fi @@ -18,6 +19,8 @@ install: - if [[ $TRAVIS_PYTHON_VERSION == "3.4" ]]; then pip install "sphinx>=1.5.0"; fi - if [[ $TRAVIS_PYTHON_VERSION == "3.5" ]]; then pip install "matplotlib>=1.5.0"; fi - if [[ $TRAVIS_PYTHON_VERSION == "3.5" ]]; then pip install "sphinx>=1.5.0"; fi +- if [[ $TRAVIS_PYTHON_VERSION == "3.6" ]]; then pip install "matplotlib>=1.5.0"; fi +- if [[ $TRAVIS_PYTHON_VERSION == "3.6" ]]; then pip install "sphinx>=1.5.0"; fi - pip install -r requirements.txt - pip install -e ".[pykdtree,quicklook]" - pip install coveralls diff --git a/changelog.rst b/changelog.rst index d45d4ca..cd16cc8 100644 --- a/changelog.rst +++ b/changelog.rst @@ -2,6 +2,51 @@ Changelog ========= +v1.6.0 (2017-09-12) +------------------- +- update changelog. [Martin Raspaud] +- Bump version: 1.5.0 → 1.6.0. [Martin Raspaud] +- Make sure x_size and y_size are ints. [Martin Raspaud] +- Merge pull request #69 from pytroll/bugfix-66. [Martin Raspaud] + + Fix write to mask affecting original mask in future versions of numpy + + Fixes #66 +- Add python 3.6 to travis tests. [davidh-ssec] +- Fix write to mask affecting original mask in future versions of numpy. + [davidh-ssec] + + Fix #66 + +- Merge pull request #67 from pytroll/bugfix-13. [Martin Raspaud] + + Rename `proj_x/y_coords` to `projection_x/y_coords` +- Rename `proj_x/y_coords` to `projection_x/y_coords` [davidh-ssec] + + Fix #13 + +- Merge pull request #63 from pytroll/feature-multiple-area-files. + [David Hoese] + + Parse multiple area files +- Fix tests_require in setup.py. [davidh-ssec] +- Use libgeos-dev to depend on the C++ libgeos-X.X.X and libgeos-c1. + [davidh-ssec] +- Add simple tests for parsing multiple yaml area strings. [davidh-ssec] +- Fix indentation in area file parsing functions. [davidh-ssec] +- Add ability to parse multiple area files at once. [davidh-ssec] +- Merge pull request #65 from pytroll/fix-numpy-1.13. [Martin Raspaud] + + Fix numpy 1.13 compatibility +- Fix boolean mask array usage in gaussian resampling. [davidh-ssec] + + In numpy 1.13 it is illegal to index an array with a boolean + array of a different size. + +- Add mock to test dependencies for python <3.3. [davidh-ssec] +- Use prepackaged numexpr in bdist_rpm. [Martin Raspaud] + + v1.5.0 (2017-05-02) ------------------- - update changelog. [Martin Raspaud] diff --git a/docs/source/geo_def.rst b/docs/source/geo_def.rst index 2889824..d5e6f82 100644 --- a/docs/source/geo_def.rst +++ b/docs/source/geo_def.rst @@ -267,13 +267,18 @@ Geographic coordinates and boundaries ------------------------------------- A ***definition** object allows for retrieval of geographic coordinates using array slicing (slice stepping is currently not supported). -All ***definition** objects exposes the coordinates **lons**, **lats** and **cartesian_coords**. +All ***definition** objects expose the coordinates **lons**, **lats** and **cartesian_coords**. AreaDefinition exposes the full set of projection coordinates as **projection_x_coords** and **projection_y_coords**. Note that in the case of projection coordinates expressed in longitude and latitude, **projection_x_coords** will be longitude and **projection_y_coords** will be latitude. +.. versionchanged:: 1.5.1 + + Renamed `proj_x_coords` to `projection_x_coords` and `proj_y_coords` + to `projection_y_coords`. + Get full coordinate set: .. doctest:: @@ -306,8 +311,8 @@ Get slice of coordinate set: ... x_size, y_size, area_extent) >>> cart_subset = area_def.get_cartesian_coords()[100:200, 350:] -If only the 1D range of a projection coordinate is required it can be extraxted -using the **proj_x_coord** or **proj_y_coords** property of a geographic coordinate +If only the 1D range of a projection coordinate is required it can be extracted +using the **projection_x_coord** or **projection_y_coords** property of a geographic coordinate .. doctest:: @@ -321,7 +326,7 @@ using the **proj_x_coord** or **proj_y_coords** property of a geographic coordin >>> area_extent = (-5326849.0625,-5326849.0625,5326849.0625,5326849.0625) >>> area_def = utils.get_area_def(area_id, description, proj_id, projection, ... x_size, y_size, area_extent) - >>> proj_x_range = area_def.proj_x_coords + >>> proj_x_range = area_def.projection_x_coords Spherical geometry operations ----------------------------- diff --git a/pyresample/geometry.py b/pyresample/geometry.py index 7f6809a..c0f02ca 100644 --- a/pyresample/geometry.py +++ b/pyresample/geometry.py @@ -541,8 +541,8 @@ class AreaDefinition(BaseDefinition): self.area_id = area_id self.name = name self.proj_id = proj_id - self.x_size = x_size - self.y_size = y_size + self.x_size = int(x_size) + self.y_size = int(y_size) self.shape = (y_size, x_size) if lons is not None: if lons.shape != self.shape: @@ -575,8 +575,8 @@ class AreaDefinition(BaseDefinition): self.pixel_offset_x = -self.area_extent[0] / self.pixel_size_x self.pixel_offset_y = self.area_extent[3] / self.pixel_size_y - self.projection_x_coords = None - self.projection_y_coords = None + self._projection_x_coords = None + self._projection_y_coords = None self.dtype = dtype @@ -659,8 +659,8 @@ class AreaDefinition(BaseDefinition): (see get_lonlats). """ p = _spatial_mp.Proj(self.proj4_string) - x = self.proj_x_coords - y = self.proj_y_coords + x = self.projection_x_coords + y = self.projection_y_coords return p(y[y.size - cols], x[x.size - rows], inverse=True) def lonlat2colrow(self, lons, lats): @@ -807,12 +807,12 @@ class AreaDefinition(BaseDefinition): else: return val - if self.projection_x_coords is not None and self.projection_y_coords is not None: + if self._projection_x_coords is not None and self._projection_y_coords is not None: # Projection coords are cached if data_slice is None: - return self.projection_x_coords, self.projection_y_coords + return self._projection_x_coords, self._projection_y_coords else: - return self.projection_x_coords[data_slice], self.projection_y_coords[data_slice] + return self._projection_x_coords[data_slice], self._projection_y_coords[data_slice] is_single_value = False is_1d_select = False @@ -893,20 +893,30 @@ class AreaDefinition(BaseDefinition): if cache and data_slice is None: # Cache the result if requested - self.projection_x_coords = target_x - self.projection_y_coords = target_y + self._projection_x_coords = target_x + self._projection_y_coords = target_y return target_x, target_y @property - def proj_x_coords(self): + def projection_x_coords(self): return self.get_proj_coords(data_slice=(0, slice(None)))[0] @property - def proj_y_coords(self): + def projection_y_coords(self): return self.get_proj_coords(data_slice=(slice(None), 0))[1] @property + def proj_x_coords(self): + warnings.warn("Deprecated, use 'projection_x_coords' instead", DeprecationWarning) + return self.projection_x_coords + + @property + def proj_y_coords(self): + warnings.warn("Deprecated, use 'projection_y_coords' instead", DeprecationWarning) + return self.projection_y_coords + + @property def outer_boundary_corners(self): """Returns the lon,lat of the outer edges of the corner points """ @@ -1026,8 +1036,8 @@ def concatenate_area_defs(area1, area2, axis=0): if axis == 0: area_extent = combine_area_extents_vertical(area1, area2) - x_size = area1.x_size - y_size = area1.y_size + area2.y_size + x_size = int(area1.x_size) + y_size = int(area1.y_size + area2.y_size) else: raise NotImplementedError('Only vertical contatenation is supported.') return AreaDefinition(area1.area_id, area1.name, area1.proj_id, diff --git a/pyresample/kd_tree.py b/pyresample/kd_tree.py index e08ca84..4be4322 100644 --- a/pyresample/kd_tree.py +++ b/pyresample/kd_tree.py @@ -716,7 +716,7 @@ def get_sample_from_neighbour_info(resample_type, output_shape, data, # Get nearest neighbour using array indexing index_mask = (index_array == input_size) new_index_array = np.where(index_mask, 0, index_array) - result = new_data[new_index_array] + result = new_data[new_index_array].copy() result[index_mask] = fill_value else: # Calculate result using weighting. @@ -807,11 +807,22 @@ def get_sample_from_neighbour_info(resample_type, output_shape, data, # Calculate final stddev new_valid_index = (count > 1) - v1 = norm[new_valid_index] - v2 = norm_sqr[new_valid_index] - stddev[new_valid_index] = np.sqrt( - (v1 / (v1 ** 2 - v2)) * stddev[new_valid_index]) - stddev[~new_valid_index] = np.NaN + if stddev.ndim >= 2: + # If given more than 1 input data array + new_valid_index = new_valid_index[:, 0] + for i in range(stddev.shape[-1]): + v1 = norm[new_valid_index, i] + v2 = norm_sqr[new_valid_index, i] + stddev[new_valid_index, i] = np.sqrt( + (v1 / (v1 ** 2 - v2)) * stddev[new_valid_index, i]) + stddev[~new_valid_index, i] = np.NaN + else: + # If given single input data array + v1 = norm[new_valid_index] + v2 = norm_sqr[new_valid_index] + stddev[new_valid_index] = np.sqrt( + (v1 / (v1 ** 2 - v2)) * stddev[new_valid_index]) + stddev[~new_valid_index] = np.NaN # Add fill values result[np.invert(result_valid_index)] = fill_value diff --git a/pyresample/test/test_geometry.py b/pyresample/test/test_geometry.py index 470ad52..164d32c 100644 --- a/pyresample/test/test_geometry.py +++ b/pyresample/test/test_geometry.py @@ -4,7 +4,6 @@ import random import sys import numpy as np -from mock import MagicMock, patch from pyresample import geo_filter, geometry from pyresample.geometry import (IncompatibleAreas, @@ -12,6 +11,12 @@ from pyresample.geometry import (IncompatibleAreas, concatenate_area_defs) from pyresample.test.utils import catch_warnings +try: + from unittest.mock import MagicMock, patch +except ImportError: + # separate mock package py<3.3 + from mock import MagicMock, patch + if sys.version_info < (2, 7): import unittest2 as unittest else: @@ -505,7 +510,7 @@ class Test(unittest.TestCase): -909968.64000000001, 1029087.28, 1490031.3600000001]) - proj_x_boundary, proj_y_boundary = area_def.proj_x_coords, area_def.proj_y_coords + proj_x_boundary, proj_y_boundary = area_def.projection_x_coords, area_def.projection_y_coords expected_x = np.array([-1250912.72, -1010912.72, -770912.72, -530912.72, -290912.72, -50912.72, 189087.28, 429087.28, 669087.28, 909087.28]) diff --git a/pyresample/test/test_kd_tree.py b/pyresample/test/test_kd_tree.py index 80dbec0..eafc689 100644 --- a/pyresample/test/test_kd_tree.py +++ b/pyresample/test/test_kd_tree.py @@ -345,17 +345,19 @@ class Test(unittest.TestCase): self.assertTrue(any(['Possible more' in str( x.message) for x in w]), 'Failed to create correct neighbour radius warning') cross_sum = res.sum() - cross_sum_stddev = stddev.sum() cross_sum_counts = counts.sum() expected = 1461.84313918 - expected_stddev = 0.446204424799 + expected_stddev = [0.446193170875, 0.443606880035, 0.438586349519] expected_counts = 4934802.0 self.assertTrue(res.shape == stddev.shape and stddev.shape == counts.shape and counts.shape == (800, 800, 3)) self.assertAlmostEqual(cross_sum, expected, msg='Swath multi channel resampling gauss failed on data') - self.assertAlmostEqual(cross_sum_stddev, expected_stddev, - msg='Swath multi channel resampling gauss failed on stddev') + for i, e_stddev in enumerate(expected_stddev): + cross_sum_stddev = stddev[:, :, i].sum() + print(cross_sum_stddev, e_stddev) + self.assertAlmostEqual(cross_sum_stddev, e_stddev, + msg='Swath multi channel resampling gauss failed on stddev (channel {})'.format(i)) self.assertAlmostEqual(cross_sum_counts, expected_counts, msg='Swath multi channel resampling gauss failed on counts') diff --git a/pyresample/test/test_utils.py b/pyresample/test/test_utils.py index 4162fe0..d79af05 100644 --- a/pyresample/test/test_utils.py +++ b/pyresample/test/test_utils.py @@ -11,8 +11,7 @@ def tmp(f): return f -class Test(unittest.TestCase): - +class TestLegacyAreaParser(unittest.TestCase): def test_area_parser_legacy(self): """Test legacy area parser.""" ease_nh, ease_sh = utils.parse_area_file(os.path.join(os.path.dirname(__file__), @@ -37,6 +36,27 @@ Number of rows: 425 Area extent: (-5326849.0625, -5326849.0625, 5326849.0625, 5326849.0625)""" self.assertEquals(ease_sh.__str__(), sh_str) + def test_load_area(self): + ease_nh = utils.load_area(os.path.join(os.path.dirname(__file__), + 'test_files', + 'areas.cfg'), 'ease_nh') + nh_str = """Area ID: ease_nh +Description: Arctic EASE grid +Projection ID: ease_nh +Projection: {'a': '6371228.0', 'lat_0': '90', 'lon_0': '0', 'proj': 'laea', 'units': 'm'} +Number of columns: 425 +Number of rows: 425 +Area extent: (-5326849.0625, -5326849.0625, 5326849.0625, 5326849.0625)""" + self.assertEquals(nh_str, ease_nh.__str__()) + + def test_not_found_exception(self): + self.assertRaises(utils.AreaNotFound, utils.parse_area_file, + os.path.join( + os.path.dirname(__file__), 'test_files', 'areas.cfg'), + 'no_area') + + +class TestYAMLAreaParser(unittest.TestCase): def test_area_parser_yaml(self): """Test YAML area parser.""" ease_nh, ease_sh = utils.parse_area_file(os.path.join(os.path.dirname(__file__), @@ -60,25 +80,46 @@ Number of rows: 425 Area extent: (-5326849.0625, -5326849.0625, 5326849.0625, 5326849.0625)""" self.assertEquals(ease_sh.__str__(), sh_str) - def test_load_area(self): - ease_nh = utils.load_area(os.path.join(os.path.dirname(__file__), - 'test_files', - 'areas.cfg'), 'ease_nh') - nh_str = """Area ID: ease_nh -Description: Arctic EASE grid -Projection ID: ease_nh -Projection: {'a': '6371228.0', 'lat_0': '90', 'lon_0': '0', 'proj': 'laea', 'units': 'm'} -Number of columns: 425 -Number of rows: 425 -Area extent: (-5326849.0625, -5326849.0625, 5326849.0625, 5326849.0625)""" - self.assertEquals(nh_str, ease_nh.__str__()) - - def test_not_found_exception(self): - self.assertRaises(utils.AreaNotFound, utils.parse_area_file, - os.path.join( - os.path.dirname(__file__), 'test_files', 'areas.cfg'), - 'no_area') - + def test_multiple_file_content(self): + area_list = ["""ease_sh: + description: Antarctic EASE grid + projection: + a: 6371228.0 + units: m + lon_0: 0 + proj: laea + lat_0: -90 + shape: + height: 425 + width: 425 + area_extent: + lower_left_xy: [-5326849.0625, -5326849.0625] + upper_right_xy: [5326849.0625, 5326849.0625] + units: m +""", + """ease_sh2: + description: Antarctic EASE grid + projection: + a: 6371228.0 + units: m + lon_0: 0 + proj: laea + lat_0: -90 + shape: + height: 425 + width: 425 + area_extent: + lower_left_xy: [-5326849.0625, -5326849.0625] + upper_right_xy: [5326849.0625, 5326849.0625] + units: m +"""] + results = utils.parse_area_file(area_list) + self.assertEquals(len(results), 2) + self.assertIn(results[0].area_id, ('ease_sh', 'ease_sh2')) + self.assertIn(results[1].area_id, ('ease_sh', 'ease_sh2')) + + +class TestMisc(unittest.TestCase): def test_wrap_longitudes(self): # test that we indeed wrap to [-180:+180[ step = 60 @@ -102,6 +143,8 @@ def suite(): """ loader = unittest.TestLoader() mysuite = unittest.TestSuite() - mysuite.addTest(loader.loadTestsFromTestCase(Test)) + mysuite.addTest(loader.loadTestsFromTestCase(TestLegacyAreaParser)) + mysuite.addTest(loader.loadTestsFromTestCase(TestYAMLAreaParser)) + mysuite.addTest(loader.loadTestsFromTestCase(TestMisc)) return mysuite diff --git a/pyresample/utils.py b/pyresample/utils.py index 9284752..2bb70ed 100644 --- a/pyresample/utils.py +++ b/pyresample/utils.py @@ -23,10 +23,12 @@ from __future__ import absolute_import +import os import numpy as np import six import yaml from configobj import ConfigObj +from collections import Mapping import pyresample as pr @@ -95,12 +97,34 @@ def parse_area_file(area_file_name, *regions): return _parse_legacy_area_file(area_file_name, *regions) +def _read_yaml_area_file_content(area_file_name): + """Read one or more area files in to a single dict object.""" + if isinstance(area_file_name, (str, six.text_type)): + area_file_name = [area_file_name] + + area_dict = {} + for area_file_obj in area_file_name: + if (isinstance(area_file_obj, (str, six.text_type)) and + os.path.isfile(area_file_obj)): + # filename + area_file_obj = open(area_file_obj) + tmp_dict = yaml.load(area_file_obj) + area_dict = recursive_dict_update(area_dict, tmp_dict) + return area_dict + + def _parse_yaml_area_file(area_file_name, *regions): - """Parse area information from a yaml area file.""" + """Parse area information from a yaml area file. - with open(area_file_name) as fd_: - area_dict = yaml.load(fd_) + Args: + area_file_name: filename, file-like object, yaml string, or list of + these. + The result of loading multiple area files is the combination of all + the files, using the first file as the "base", replacing things after + that. + """ + area_dict = _read_yaml_area_file_content(area_file_name) area_list = regions or area_dict.keys() res = [] @@ -124,10 +148,29 @@ def _parse_yaml_area_file(area_file_name, *regions): return res +def _read_legacy_area_file_lines(area_file_name): + if isinstance(area_file_name, (str, six.text_type)): + area_file_name = [area_file_name] + + for area_file_obj in area_file_name: + if (isinstance(area_file_obj, (str, six.text_type)) and + not os.path.isfile(area_file_obj)): + # file content string + for line in area_file_obj.splitlines(): + yield line + continue + elif isinstance(area_file_obj, (str, six.text_type)): + # filename + area_file_obj = open(area_file_obj, 'r') + + for line in area_file_obj.readlines(): + yield line + + def _parse_legacy_area_file(area_file_name, *regions): """Parse area information from a legacy area file.""" - area_file = open(area_file_name, 'r') + area_file = _read_legacy_area_file_lines(area_file_name) area_list = list(regions) if len(area_list) == 0: select_all_areas = True @@ -138,7 +181,7 @@ def _parse_legacy_area_file(area_file_name, *regions): # Extract area from file in_area = False - for line in area_file.readlines(): + for line in area_file: if not in_area: if 'REGION' in line: area_id = line.replace('REGION:', ''). \ @@ -156,8 +199,6 @@ def _parse_legacy_area_file(area_file_name, *regions): else: area_content += line - area_file.close() - # Check if all specified areas were found if not select_all_areas: for i, area in enumerate(area_defs): @@ -393,3 +434,20 @@ def wrap_longitudes(lons): """ lons_wrap = (lons + 180) % (360) - 180 return lons_wrap.astype(lons.dtype) + + +def recursive_dict_update(d, u): + """Recursive dictionary update using + + Copied from: + + http://stackoverflow.com/questions/3232943/update-value-of-a-nested-dictionary-of-varying-depth + + """ + for k, v in u.items(): + if isinstance(v, Mapping): + r = recursive_dict_update(d.get(k, {}), v) + d[k] = r + else: + d[k] = u[k] + return d diff --git a/pyresample/version.py b/pyresample/version.py index d73a813..ee9fd28 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.5.0' +__version__ = '1.6.0' diff --git a/setup.cfg b/setup.cfg index 97c8d8b..47d15b6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,4 @@ [bdist_rpm] -requires=numpy pykdtree numexpr pyproj python-configobj +requires=numpy pykdtree python2-numexpr pyproj python-configobj release=1 doc_files = docs/Makefile docs/source/*.rst - diff --git a/setup.py b/setup.py index a1a38c1..7b666f7 100644 --- a/setup.py +++ b/setup.py @@ -27,11 +27,14 @@ from setuptools.command.build_ext import build_ext as _build_ext version = imp.load_source('pyresample.version', 'pyresample/version.py') requirements = ['setuptools>=3.2', 'pyproj', 'numpy', 'configobj', - 'pykdtree>=1.1.1', 'pyyaml'] + 'pykdtree>=1.1.1', 'pyyaml', 'six'] extras_require = {'pykdtree': ['pykdtree>=1.1.1'], 'numexpr': ['numexpr'], 'quicklook': ['matplotlib', 'basemap', 'pillow']} +test_requires = [] +if sys.version_info < (3, 3): + test_requires.append('mock') if sys.version_info < (2, 6): # multiprocessing is not in the standard library requirements.append('multiprocessing') @@ -110,6 +113,7 @@ if __name__ == "__main__": setup_requires=['numpy'], install_requires=requirements, extras_require=extras_require, + tests_require=test_requires, cmdclass={'build_ext': build_ext}, ext_modules=cythonize(extensions), test_suite='pyresample.test.suite', -- 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