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)