Skip to content

Commit 9a4269f

Browse files
authored
Merge pull request #50 from jorisvandenbossche/merge-tiles
Re-implementation of tile merging based on mercantile
2 parents cb7a4a0 + 57214b4 commit 9a4269f

File tree

4 files changed

+66
-23
lines changed

4 files changed

+66
-23
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ the internet into geospatial raster files. Bounding boxes can be passed in both
2121

2222
## Dependencies
2323

24-
* `cartopy`
2524
* `mercantile`
2625
* `numpy`
2726
* `pandas`
27+
- `matplotlib`
2828
* `pillow`
2929
* `rasterio`
3030
* `requests`

contextily/tile.py

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import pandas as pd
1010
import rasterio as rio
1111
from PIL import Image
12-
from cartopy.io.img_tiles import _merge_tiles as merge_tiles
1312
from rasterio.transform import from_origin
1413
from . import tile_providers as sources
1514

@@ -146,6 +145,7 @@ def bounds2img(w, s, e, n, zoom='auto',
146145
if zoom == 'auto':
147146
zoom = _calculate_zoom(w, e, s, n)
148147
tiles = []
148+
arrays = []
149149
for t in mt.tiles(w, s, e, n, [zoom]):
150150
x, y, z = t.x, t.y, t.z
151151
tile_url = url.replace('tileX', str(x)).replace('tileY', str(y)).replace('tileZ', str(z))
@@ -155,17 +155,15 @@ def bounds2img(w, s, e, n, zoom='auto',
155155
image = Image.open(image_stream).convert('RGB')
156156
image = np.asarray(image)
157157
# ---
158-
wt, st, et, nt = mt.bounds(t)
159-
xr = np.linspace(wt, et, image.shape[0])
160-
yr = np.linspace(st, nt, image.shape[1])
161-
tiles.append([image, xr, yr, 'lower'])
162-
merged, extent = merge_tiles(tiles)[:2]
158+
tiles.append(t)
159+
arrays.append(image)
160+
merged, extent = _merge_tiles(tiles, arrays)
163161
# lon/lat extent --> Spheric Mercator
164-
minX, maxX, minY, maxY = extent
165-
w, s = mt.xy(minX, minY)
166-
e, n = mt.xy(maxX, maxY)
167-
extent = w, e, s, n
168-
return merged[::-1], extent
162+
west, south, east, north = extent
163+
left, bottom = mt.xy(west, south)
164+
right, top = mt.xy(east, north)
165+
extent = left, right, bottom, top
166+
return merged, extent
169167

170168

171169
def _retryer(tile_url, wait, max_retries):
@@ -332,3 +330,50 @@ def _calculate_zoom(w, s, e, n):
332330
zoom_lat = np.ceil(np.log2(360 * 2. / lat_length))
333331
zoom = np.max([zoom_lon, zoom_lat])
334332
return int(zoom)
333+
334+
335+
def _merge_tiles(tiles, arrays):
336+
"""
337+
Merge a set of tiles into a single array.
338+
339+
Parameters
340+
---------
341+
tiles : list of mercantile.Tile objects
342+
The tiles to merge.
343+
arrays : list of numpy arrays
344+
The corresponding arrays (image pixels) of the tiles. This list
345+
has the same length and order as the `tiles` argument.
346+
347+
Returns
348+
-------
349+
img : np.ndarray
350+
Merged arrays.
351+
extent : tuple
352+
Bounding box [west, south, east, north] of the returned image
353+
in long/lat.
354+
"""
355+
# create (n_tiles x 2) array with column for x and y coordinates
356+
tile_xys = np.array([(t.x, t.y) for t in tiles])
357+
358+
# get indices starting at zero
359+
indices = tile_xys - tile_xys.min(axis=0)
360+
361+
# the shape of individual tile images
362+
h, w, d = arrays[0].shape
363+
364+
# number of rows and columns in the merged tile
365+
n_x, n_y = (indices+1).max(axis=0)
366+
367+
# empty merged tiles array to be filled in
368+
img = np.zeros((h * n_y, w * n_x, d), dtype=np.uint8)
369+
370+
for ind, arr in zip(indices, arrays):
371+
x, y = ind
372+
img[y*h:(y+1)*h, x*w:(x+1)*w, :] = arr
373+
374+
bounds = np.array([mt.bounds(t) for t in tiles])
375+
west, south, east, north = (
376+
min(bounds[:, 0]), min(bounds[:, 1]),
377+
max(bounds[:, 2]), max(bounds[:, 3]))
378+
379+
return img, (west, south, east, north)

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
cartopy
21
geopy
2+
matplotlib
33
mercantile
44
pandas
55
pillow

tests/test_ctx.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,8 @@ def test_bounds2raster():
4343
assert_array_almost_equal(rimg.mean(), img.mean())
4444
assert_array_almost_equal(ext, (0.0, 939258.2035682457,
4545
6261721.35712164, 6887893.492833804))
46-
assert_array_almost_equal(ext, (0.0, 939258.2035682457,
47-
6261721.35712164, 6887893.492833804))
48-
rtr_bounds = [-613.0928221724841, 6262334.050013727,
49-
938645.1107460733, 6888506.185725891]
46+
rtr_bounds = [-611.49622628141, 6262332.853347922,
47+
938646.7073419644, 6888504.989060086]
5048
assert_array_almost_equal(list(rtr.bounds), rtr_bounds)
5149

5250
def test_bounds2img():
@@ -159,10 +157,10 @@ def test_add_basemap():
159157
ax_extent = (-11740727.544603072, -11662456.027639052,
160158
4852834.0517692715, 4891969.810251278)
161159
assert_array_almost_equal(ax_extent, ax.images[0].get_extent())
162-
assert ax.images[0].get_array().sum() == 75687792
163-
assert ax.images[0].get_array().shape == (256, 511, 3)
160+
assert ax.images[0].get_array().sum() == 75853866
161+
assert ax.images[0].get_array().shape == (256, 512, 3)
164162
assert_array_almost_equal(ax.images[0].get_array().mean(),
165-
192.86068982387476)
163+
192.90635681152344)
166164

167165
# Test local source
168166
f, ax = matplotlib.pyplot.subplots(1)
@@ -188,10 +186,10 @@ def test_add_basemap():
188186
ax_extent = (-11740727.544603072, -11691807.846500559,
189187
4852834.0517692715, 4891969.810251278)
190188
assert_array_almost_equal(ax_extent, ax.images[0].get_extent())
191-
assert ax.images[0].get_array().sum() == 719543527
192-
assert ax.images[0].get_array().shape == (1021, 1276, 3)
189+
assert ax.images[0].get_array().sum() == 723918764
190+
assert ax.images[0].get_array().shape == (1024, 1280, 3)
193191
assert_array_almost_equal(ax.images[0].get_array().mean(),
194-
184.10237852536648)
192+
184.10206197102863)
195193

196194
def test_attribution():
197195
f, ax = matplotlib.pyplot.subplots(1)

0 commit comments

Comments
 (0)