diff --git a/docs/source/whats_new.rst b/docs/source/whats_new.rst index a9d56f993..9fe7641a3 100644 --- a/docs/source/whats_new.rst +++ b/docs/source/whats_new.rst @@ -1,3 +1,12 @@ +What's new in Cartopy 0.8 +************************* + +:Release: 0.8.0 +:Data: ?? ??? 2013 + +* Bill Little added support for the OSNI projection and enhanced the image nest capability. (:pull:`263`) + + What's new in Cartopy 0.7 ************************* diff --git a/lib/cartopy/crs.py b/lib/cartopy/crs.py index e8dcf811e..60161c0d7 100644 --- a/lib/cartopy/crs.py +++ b/lib/cartopy/crs.py @@ -552,6 +552,36 @@ def y_limits(self): return (0, 13e5) +class OSNI(Projection): + def __init__(self): + proj4_params = {'proj': 'tmerc', 'lat_0': 53.5, 'lon_0': -8, + 'k': 1.000035, 'x_0': 200000, 'y_0': 250000, + 'a': 6377340.189, 'b': 6356034.447938534, + 'units': 'm', 'no_defs': ''} + super(OSNI, self).__init__(proj4_params) + + @property + def threshold(self): + return 1e4 + + @property + def boundary(self): + x0, x1 = self.x_limits + w = x1 - x0 + y0, y1 = self.y_limits + h = y1 - y0 + # XXX Should this be a LinearRing? + return sgeom.LineString([(0, 0), (0, h), (w, h), (w, 0), (0, 0)]) + + @property + def x_limits(self): + return (18814.9667, 386062.3293) + + @property + def y_limits(self): + return (11764.8481, 464720.9559) + + class EuroPP(Projection): """ UTM Zone 32 projection for EuroPP domain. diff --git a/lib/cartopy/io/img_nest.py b/lib/cartopy/io/img_nest.py index ab0687174..2c0e35db1 100755 --- a/lib/cartopy/io/img_nest.py +++ b/lib/cartopy/io/img_nest.py @@ -17,9 +17,9 @@ import collections -import os -import itertools import glob +import itertools +import os.path import numpy as np from PIL import Image @@ -30,12 +30,6 @@ class Img(collections.namedtuple('Img', _img_class_attrs)): - """ - Represents a simple geolocated image. - - Note: API is likely to change in the future to include a CRS. - - """ def __new__(cls, *args, **kwargs): # ensure any lists given as args or kwargs are turned into tuples. new_args = [] @@ -51,6 +45,29 @@ def __new__(cls, *args, **kwargs): return super(Img, cls).__new__(cls, *new_args, **new_kwargs) def __init__(self, *args, **kwargs): + """ + Represents a simple geo-located image. + + Args: + + * filename: + Filename of the image tile. + + * extent: + The (x_lower, x_upper, y_lower, y_upper) extent of the image + in units of the native projection. + + * origin: + Name of the origin. + + * pixel_size: + The (x_scale, y_scale) pixel width, in units of the native + projection per pixel. + + .. note:: + API is likely to change in the future to include a CRS. + + """ self._bbox = None def bbox(self): @@ -59,52 +76,99 @@ def bbox(self): self._bbox = box(x0, y0, x1, y1) return self._bbox + @staticmethod + def world_files(fname): + """ + Determine world filename combinations. + + For example, a '*.tif' file may have one of the following + popular conventions for world file extensions "*.tifw' or + '*.tfw'. + + Args: + + * fname: + Name of the file to world file combinations + + Returns: + A list of world filename combinations. + + """ + froot, fext = os.path.splitext(fname) + if froot == fname: + result = ['{}.{}'.format(fname, 'w'), + '{}.{}'.format(fname, 'W')] + else: + fext = fext[1::].lower() + if len(fext) < 3: + result = ['{}.{}'.format(fname, 'w'), + '{}.{}'.format(fname, 'W')] + else: + fext_types = [fext + 'w', fext[0] + fext[-1] + 'w'] + fext_types.extend([ext.upper() for ext in fext_types]) + result = ['{}.{}'.format(froot, ext) for ext in fext_types] + return result + class ImageCollection(object): - """ - Represents a collection of images at the same logical level - (typically zoom level). - """ def __init__(self, name, crs, images=None): + """ + Represents a collection of images at the same logical level. + + Typically these are images at the same zoom level or resolution. + + Args: + + * name: + The name of the image collection. + + * crs: + The :class:`~cartopy.crs.Projection` instance. + + Kwargs: + + * images: + A list of one or more :class:`~cartopy.io.img_nest.Img` instances. + + """ self.name = name self.crs = crs self.images = images or [] - def find_images(self, target_domain): + def scan_dir_for_imgs(self, directory, glob_pattern='*.tif'): """ - Find the images which exist in this collection which intersect with - the target domain. + Search the given directory for the associated world files + of the image files. - Target domain is a shapely polygon in native coordinates. + Args: - """ - for fname, extent, domain, origin in self.images: - if target_domain.intersects(domain): - yield fname, extent, domain, crs + * directory: + The directory path to search for image files. - def scan_dir_for_imgs(self, directory, glob_pattern='*.tif'): - """ - Scan the given directory for images with associated tfw files. - Append any found results to self.images + Kwargs: - .. note:: Does not recursively look through directories. + * glob_pattern: + The image filename glob pattern to search with. + Defaults to '*.tif'. + + .. note:: + Does not recursively search sub-directories. """ imgs = glob.glob(os.path.join(directory, glob_pattern)) - # maps tilename to [bbox_poly, [children]] - tiles = {} for img in imgs: dirname, fname = os.path.split(img) - orig_tfw = fname[:-3] + 'tfw' - for tfw in [orig_tfw, orig_tfw.upper()]: - tfw = os.path.join(dirname, tfw) - if os.path.exists(tfw): + worlds = Img.world_files(fname) + for fworld in worlds: + fworld = os.path.join(dirname, fworld) + if os.path.exists(fworld): break else: - raise ValueError('Image %s has no tfw' % img) + msg = 'Image file {!r} has no associated world file' + raise ValueError(msg.format(img)) - lines = open(tfw).readlines() + lines = open(fworld).readlines() pix_size = [float(lines[0]), float(lines[3])] pix_rotation = [float(lines[1]), float(lines[2])] assert pix_rotation == [0., 0.], ('Rotated pixels not currently ' @@ -124,36 +188,53 @@ def scan_dir_for_imgs(self, directory, glob_pattern='*.tif'): extent, img), 'lower', tuple(pix_size))) def _extent_finalize(self, extent, filename): - """The final extent of an image is passed through this method. This - is a good place to implement rounding or some other processing.""" + """ + The final extent of an image is passed through this method. This + is a good place to implement rounding or some other processing. + + """ return extent class NestedImageCollection(object): - """ - Represents a complex nest of ImageCollections. + def __init__(self, name, crs, collections, _ancestry=None): + """ + Represents a complex nest of ImageCollections. - On construction, the image collections are scanned for ancestry, leading - to fast image finding capabilities. + On construction, the image collections are scanned for ancestry, + leading to fast image finding capabilities. - A complex (and time consuming to create) NestedImageCollection instance can - be saved as a pickle file and subsequently be (quickly) restored. + A complex (and time consuming to create) NestedImageCollection instance + can be saved as a pickle file and subsequently be (quickly) restored. - There is a simplified creation interface for NestedImageCollection - ``from_configuration`` for more detail. + There is a simplified creation interface for NestedImageCollection + ``from_configuration`` for more detail. - """ - def __init__(self, name, crs, collections, _ancestry=None): + Args: + + * name: + The name of the nested image collection. + + * crs: + The native :class:`~cartopy.crs.Projection` of all the image + collections. + + * collections: + A list of one or more :class:`~cartopy.io.img_nest.ImageCollection` + instances. + + """ # NOTE: all collections must have the same crs. - _collection_names = [collection.name for collection in collections] - assert len(_collection_names) == len(collections), \ + _names = set([collection.name for collection in collections]) + assert len(_names) == len(collections), \ 'The collections must have unique names.' self.name = name self.crs = crs self._collections_by_name = {collection.name: collection for collection in collections} - self._collections = collections + sort_func = lambda c: np.max([image.bbox().area for image in c.images]) + self._collections = sorted(collections, key=sort_func, reverse=True) self._ancestry = {} """ maps (collection name, image) to a list of children @@ -165,7 +246,8 @@ def __init__(self, name, crs, collections, _ancestry=None): if _ancestry is not None: self._ancestry = _ancestry else: - parent_wth_children = itertools.izip(collections, collections[1:]) + parent_wth_children = itertools.izip(self._collections, + self._collections[1:]) for parent_collection, collection in parent_wth_children: for parent_image in parent_collection.images: for image in collection.images: @@ -179,20 +261,50 @@ def __init__(self, name, crs, collections, _ancestry=None): # collection has child images) @staticmethod - def _is_parent(potential_parent_image, image): + def _is_parent(parent, child): """ Returns whether the given Image is the parent of image. Used by __init__. """ - return potential_parent_image.bbox().contains(image.bbox()) + result = False + pbox = parent.bbox() + cbox = child.bbox() + if pbox.area > cbox.area: + result = pbox.intersects(cbox) and not pbox.touches(cbox) + return result def image_for_domain(self, target_domain, target_z): + """ + Determine the image that provides complete coverage of target location. + + The composed image is merged from one or more image tiles that overlay + the target location and provide complete image coverage of the target + location. + + Args: + + * target_domain: + A :class:`~shapely.geometry.linestring.LineString` instance that + specifies the target location requiring image coverage. + + * target_z: + The name of the target + :class`~cartopy.io.img_nest.ImageCollection` which specifies the + target zoom level (resolution) of the required images. + + Returns: + A tuple containing three items, consisting of the target + location :class:`numpy.ndarray` image data, the + (x-lower, x-upper, y-lower, y-upper) extent of the image, and the + origin for the target location. + + """ # XXX Copied from cartopy.io.img_tiles if target_z not in self._collections_by_name: # TODO: Handle integer depths also? - raise ValueError('%s is not one of the possible ' - 'collections.' % target_z) + msg = '{!r} is not one of the possible collections.' + raise ValueError(msg.format(target_z)) tiles = [] for tile in self.find_images(target_domain, target_z): @@ -214,11 +326,40 @@ def image_for_domain(self, target_domain, target_z): return img, extent, origin def find_images(self, target_domain, target_z, start_tiles=None): + """ + A generator that finds all images that overlap the bounded + target location. + + Args: + + * target_domain: + A :class:`~shapely.geometry.linestring.LineString` instance that + specifies the target location requiring image coverage. + + * target_z: + The name of the target + :class:`~cartopy.io.img_nest.ImageCollection` which specifies + the target zoom level (resolution) of the required images. + + Kwargs: + + * start_tiles: + A list of one or more tuple pairs, composed of a + :class:`~cartopy.io.img_nest.ImageCollection` name and an + :class:`~cartopy.io.img_nest.Img` instance, from which to search + for the target images. + + Returns: + A generator tuple pair composed of a + :class:`~cartopy.io.img_nest.ImageCollection` name and an + :class:`~cartopy.io.img_nest.Img` instance. + + """ # XXX Copied from cartopy.io.img_tiles if target_z not in self._collections_by_name: # TODO: Handle integer depths also? - raise ValueError('%s is not one of the possible ' - 'collections.' % target_z) + msg = '{!r} is not one of the possible collections.' + raise ValueError(msg.format(target_z)) if start_tiles is None: start_tiles = ((self._collections[0].name, img) @@ -227,7 +368,8 @@ def find_images(self, target_domain, target_z, start_tiles=None): for start_tile in start_tiles: # recursively drill down to the images at the target zoom domain = start_tile[1].bbox() - if domain.intersects(target_domain): + if target_domain.intersects(domain) and \ + not target_domain.touches(domain): if start_tile[0] == target_z: yield start_tile else: @@ -238,11 +380,49 @@ def find_images(self, target_domain, target_z, start_tiles=None): yield result def subtiles(self, collection_image): + """ + Find the higher resolution image tiles that compose this parent + image tile. + + Args: + + * collection_image: + A tuple pair containing the parent + :class:`~cartopy.io.img_nest.ImageCollection` name and + :class:`~cartopy.io.img_nest.Img` instance. + + Returns: + An iterator of tuple pairs containing the higher resolution child + :class:`~cartopy.io.img_nest.ImageCollection` name and + :class:`~cartopy.io.img_nest.Img` instance that compose the parent. + + """ return iter(self._ancestry.get(collection_image, [])) desired_tile_form = 'RGB' def get_image(self, collection_image): + """ + Retrieve the data of the target image from file. + + .. note:: + The format of the retrieved image file data is controlled by + :attr:`~cartopy.io.img_nest.NestedImageCollection.desired_tile_form`, + which defaults to 'RGB' format. + + Args: + + * collection_image: + A tuple pair containing the target + :class:`~cartopy.io.img_nest.ImageCollection` name and + :class:`~cartopy.io.img_nest.Img` instance. + + Returns: + A tuple containing three items, consisting of the associated image + file data, the (x_lower, x_upper, y_lower, y_upper) extent of the + image, and the image origin. + + """ img = collection_image[1] img_data = Image.open(img.filename) img_data = img_data.convert(self.desired_tile_form) @@ -253,9 +433,11 @@ def from_configuration(cls, name, crs, name_dir_pairs, glob_pattern='*.tif', img_collection_cls=ImageCollection): """ - Creates a NestedImageCollection given the [collection name, directory] - pairs. This is very convenient functionality for simple configuration - level creation of this complex object. + Creates a :class:`~cartopy.io.img_nest.NestedImageCollection` instance + given the list of image collection name and directory path pairs. + + This is very convenient functionality for simple configuration level + creation of this complex object. For example, to produce a nested collection of OS map tiles:: @@ -268,6 +450,36 @@ def from_configuration(cls, name, crs, name_dir_pairs, files, ) + .. important:: + The list of image collection name and directory path pairs must be + given in increasing resolution order i.e. from low resolution to + high resolution. + + Args: + + * name: + The name for the + :class:`~cartopy.io.img_nest.NestedImageCollection` instance. + + * crs: + The :class:`~cartopy.crs.Projection` of the image collection. + + * name_dir_pairs: + A list of image collection name and directory path pairs. + + Kwargs: + + * glob_pattern: + The image collection filename glob pattern. + Defaults to '*.tif'. + + * img_collection_cls: + The class of image collection to nest. + Defaults to :class:`~cartopy.io.img_nest.ImageCollection`. + + Returns: + A :class:`~cartopy.io.img_nest.NestedImageCollection` instance. + """ collections = [] for collection_name, collection_dir in name_dir_pairs: diff --git a/lib/cartopy/tests/__init__.py b/lib/cartopy/tests/__init__.py index 0eaa37efb..c698e84f5 100644 --- a/lib/cartopy/tests/__init__.py +++ b/lib/cartopy/tests/__init__.py @@ -15,13 +15,27 @@ # You should have received a copy of the GNU Lesser General Public License # along with cartopy. If not, see . +import contextlib import functools +import tempfile +import shutil import types import matplotlib.patches as mpatches import matplotlib.pyplot as plt +@contextlib.contextmanager +def temp_dir(suffix=None): + if suffix is None: + suffix = '' + dname = tempfile.mkdtemp(suffix=suffix) + try: + yield dname + finally: + shutil.rmtree(dname) + + def not_a_nose_fixture(function): """ Provides a decorator to mark a function as not a nose fixture. diff --git a/lib/cartopy/tests/test_crs.py b/lib/cartopy/tests/test_crs.py index f0f5dd4e3..b7d3384ba 100644 --- a/lib/cartopy/tests/test_crs.py +++ b/lib/cartopy/tests/test_crs.py @@ -37,6 +37,22 @@ def test_hash(self): self.assertEqual(ccrs.Geodetic(), ccrs.Geodetic()) + def test_osni(self): + osni = ccrs.OSNI() + ll = ccrs.Geodetic() + + # results obtained by nearby.org.uk. + lat, lon = np.array([54.5622169298669, -5.54159863617957], + dtype=np.double) + east, north = np.array([359000, 371000], dtype=np.double) + + assert_arr_almost_eq(osni.transform_point(lon, lat, ll), + np.array([east, north]), + -1) + assert_arr_almost_eq(ll.transform_point(east, north, osni), + np.array([lon, lat]), + 3) + def test_osgb(self): osgb = ccrs.OSGB() ll = ccrs.Geodetic() diff --git a/lib/cartopy/tests/test_img_nest.py b/lib/cartopy/tests/test_img_nest.py index 612e46a8e..421469fdb 100644 --- a/lib/cartopy/tests/test_img_nest.py +++ b/lib/cartopy/tests/test_img_nest.py @@ -14,14 +14,14 @@ # # You should have received a copy of the GNU Lesser General Public License # along with cartopy. If not, see . - from __future__ import division import io import cPickle as pickle import os +from PIL import Image -from nose.tools import assert_equal, assert_raises +from nose.tools import assert_equal, assert_raises, assert_true import numpy as np import shapely.geometry @@ -29,24 +29,148 @@ import cartopy.crs as ccrs import cartopy.io.img_tiles as cimgt import cartopy.io.img_nest as cimg_nest +import cartopy.tests as tests _TEST_DATA_DIR = os.path.join(config["repo_data_dir"], 'wmts', 'aerial') +def _save_world(fname, args): + _world = ('{x_pix_size}\n' + '{y_rotation}\n' + '{x_rotation}\n' + '{y_pix_size}\n' + '{x_center}\n' + '{y_center}\n') + with open(fname, 'w') as fh: + fh.write(_world.format(**args)) + + +def test_intersect(): + with tests.temp_dir() as base_dir: + # Zoom level zero. + # File 1: Parent space of all images. + z_0_dir = os.path.join(base_dir, 'z_0') + os.mkdir(z_0_dir) + world = dict(x_pix_size=2, y_rotation=0, x_rotation=0, + y_pix_size=2, x_center=1, y_center=1) + im = Image.new('RGB', (50, 50)) + fname = os.path.join(z_0_dir, 'p0.tfw') + _save_world(fname, world) + fname = os.path.join(z_0_dir, 'p0.tif') + im.save(fname) + + # Zoom level one. + # File 1: complete containment within p0. + z_1_dir = os.path.join(base_dir, 'z_1') + os.mkdir(z_1_dir) + world = dict(x_pix_size=2, y_rotation=0, x_rotation=0, + y_pix_size=2, x_center=21, y_center=21) + im = Image.new('RGB', (30, 30)) + fname = os.path.join(z_1_dir, 'p1.tfw') + _save_world(fname, world) + fname = os.path.join(z_1_dir, 'p1.tif') + im.save(fname) + + # Zoom level two. + # File 1: intersect right edge with p1 left edge. + z_2_dir = os.path.join(base_dir, 'z_2') + os.mkdir(z_2_dir) + world = dict(x_pix_size=2, y_rotation=0, x_rotation=0, + y_pix_size=2, x_center=6, y_center=21) + im = Image.new('RGB', (5, 5)) + fname = os.path.join(z_2_dir, 'p2-1.tfw') + _save_world(fname, world) + fname = os.path.join(z_2_dir, 'p2-1.tif') + im.save(fname) + # File 2: intersect upper right corner with p1 + # lower left corner. + world = dict(x_pix_size=2, y_rotation=0, x_rotation=0, + y_pix_size=2, x_center=6, y_center=6) + im = Image.new('RGB', (5, 5)) + fname = os.path.join(z_2_dir, 'p2-2.tfw') + _save_world(fname, world) + fname = os.path.join(z_2_dir, 'p2-2.tif') + im.save(fname) + # File 3: complete containment within p1. + world = dict(x_pix_size=2, y_rotation=0, x_rotation=0, + y_pix_size=2, x_center=41, y_center=41) + im = Image.new('RGB', (5, 5)) + fname = os.path.join(z_2_dir, 'p2-3.tfw') + _save_world(fname, world) + fname = os.path.join(z_2_dir, 'p2-3.tif') + im.save(fname) + # File 4: overlap with p1 right edge. + world = dict(x_pix_size=2, y_rotation=0, x_rotation=0, + y_pix_size=2, x_center=76, y_center=61) + im = Image.new('RGB', (5, 5)) + fname = os.path.join(z_2_dir, 'p2-4.tfw') + _save_world(fname, world) + fname = os.path.join(z_2_dir, 'p2-4.tif') + im.save(fname) + # File 5: overlap with p1 bottom right corner. + world = dict(x_pix_size=2, y_rotation=0, x_rotation=0, + y_pix_size=2, x_center=76, y_center=76) + im = Image.new('RGB', (5, 5)) + fname = os.path.join(z_2_dir, 'p2-5.tfw') + _save_world(fname, world) + fname = os.path.join(z_2_dir, 'p2-5.tif') + im.save(fname) + + # Provided in reverse order in order to test the area sorting. + items = [('dummy-z-2', z_2_dir), + ('dummy-z-1', z_1_dir), + ('dummy-z-0', z_0_dir)] + nic = cimg_nest.NestedImageCollection.from_configuration('dummy', + None, + items) + + names = [collection.name for collection in nic._collections] + zoom_levels = ['dummy-z-0', 'dummy-z-1', 'dummy-z-2'] + assert_true(names, zoom_levels) + + # Check all images are loaded. + for zoom, expected_image_count in zip(zoom_levels, [1, 1, 5]): + images = nic._collections_by_name[zoom].images + assert_equal(len(images), expected_image_count) + + # Check the image ancestry. + zoom_levels = ['dummy-z-0', 'dummy-z-1'] + assert_equal(sorted(k[0] for k in nic._ancestry.keys()), + zoom_levels) + + expected = [('dummy-z-0', ['p1.tif']), + ('dummy-z-1', ['p2-3.tif', 'p2-4.tif', 'p2-5.tif'])] + for zoom, image_names in expected: + key = [k for k in nic._ancestry.keys() if k[0] == zoom][0] + ancestry = nic._ancestry[key] + fnames = sorted([os.path.basename(item[1].filename) + for item in ancestry]) + assert_equal(image_names, fnames) + + # Check image retrieval for specific domain. + items = [(shapely.geometry.box(20, 20, 80, 80), 3), + (shapely.geometry.box(20, 20, 75, 75), 1), + (shapely.geometry.box(40, 40, 85, 85), 3)] + for domain, expected in items: + result = [image for image in nic.find_images(domain, + 'dummy-z-2')] + assert_equal(len(result), expected) + + def _tile_from_img(img): - """ - Turns an img into the appropriate x, y, z tile based on it filename. + """ + Turns an img into the appropriate x, y, z tile based on its filename. - Imgs have a filename attribute which is something - like "lib/cartopy/data/wmts/aerial/z_0/x_0_y0.png" + Imgs have a filename attribute which is something + like "lib/cartopy/data/wmts/aerial/z_0/x_0_y0.png" - """ - _, z = os.path.basename(os.path.dirname(img.filename)).split('_') - xy, _ = os.path.splitext(os.path.basename(img.filename)) - _, x, _, y = xy.split('_') - return int(x), int(y), int(z) + """ + _, z = os.path.basename(os.path.dirname(img.filename)).split('_') + xy, _ = os.path.splitext(os.path.basename(img.filename)) + _, x, _, y = xy.split('_') + return int(x), int(y), int(z) class RoundedImageCollection(cimg_nest.ImageCollection): @@ -90,8 +214,8 @@ def test_nest(): glob_pattern='*.png') # make sure all the images from z1 are contained by the z0 image. The - # only reason this might occur is if the tfw files are handling floating - # point values badly + # only reason this might occur is if the tfw files are handling + # floating point values badly for img in z1.images: if not z0.images[0].bbox().contains(img.bbox()): raise IOError('The test images aren\'t all "contained" by the ' @@ -109,14 +233,14 @@ def test_nest(): z0_key = ('aerial z0 test', z0.images[0]) - assert z0_key in nest_z0_z1._ancestry.keys() + assert_true(z0_key in nest_z0_z1._ancestry.keys()) assert_equal(len(nest_z0_z1._ancestry), 1) # check that it has figured out that all the z1 images are children of # the only z0 image for img in z1.images: key = ('aerial z0 test', z0.images[0]) - assert ('aerial z1 test', img) in nest_z0_z1._ancestry[key] + assert_true('aerial z1 test', img) in nest_z0_z1._ancestry[key] x1_y0_z1, = [img for img in z1.images if img.filename.endswith('z_1/x_1_y_0.png')] @@ -128,11 +252,12 @@ def test_nest(): nest.subtiles(('aerial z1 test', x1_y0_z1))])) nest_from_config = gen_nest() - # check that the the images in the nest from configuration are the same as - # those created by hand. + # check that the the images in the nest from configuration are the + # same as those created by hand. for name in nest_z0_z1._collections_by_name.keys(): for img in nest_z0_z1._collections_by_name[name].images: - assert img in nest_from_config._collections_by_name[name].images + collection = nest_from_config._collections_by_name[name] + assert_true(img in collection.images) assert_equal(nest_z0_z1._ancestry, nest_from_config._ancestry) @@ -142,7 +267,8 @@ def test_nest(): s.seek(0) nest_z0_z1_from_pickle = pickle.load(s) - assert_equal(nest_z0_z1._ancestry, nest_z0_z1_from_pickle._ancestry) + assert_equal(nest_z0_z1._ancestry, + nest_z0_z1_from_pickle._ancestry) def gen_test_data(): @@ -176,29 +302,18 @@ def gen_test_data(): upper_left_center = (extent[0] + pix_size_x / 2, extent[2] + pix_size_y / 2) - tfw_fname = fname[:-4] + '.tfw' - - tfw_str_fmt = ('{x_pix_size}\n' - '{y_rotation}\n' - '{x_rotation}\n' - '{y_pix_size}\n' - '{x_center}\n' - '{y_center}\n') - - tfw_keys = {'x_pix_size': np.float32(pix_size_x), + pgw_fname = fname[:-4] + '.pgw' + pgw_keys = {'x_pix_size': np.float32(pix_size_x), 'y_rotation': 0, 'x_rotation': 0, 'y_pix_size': np.float32(pix_size_y), 'x_center': np.float32(upper_left_center[0]), 'y_center': np.float32(upper_left_center[1]), } - - with open(tfw_fname, 'w') as fh: - fh.write(tfw_str_fmt.format(**tfw_keys)) + _save_world(pgw_fname, pgw_keys) img.save(fname) if __name__ == '__main__': -# import nose -# nose.runmodule(argv=['-s', '--with-doctest'], exit=False) - test_nest() + import nose + nose.runmodule(argv=['-s', '--with-doctest'], exit=False)