Skip to content

Commit 40db9b3

Browse files
authoredFeb 14, 2024
Merge pull request #598 from nitram96/develop
Bug fixes to arcgisimage
2 parents a3112f1 + ec15505 commit 40db9b3

File tree

2 files changed

+136
-78
lines changed

2 files changed

+136
-78
lines changed
 

‎packages/basemap/src/mpl_toolkits/basemap/__init__.py

+90-78
Original file line numberDiff line numberDiff line change
@@ -4257,45 +4257,56 @@ def pil_to_array(*args, **kwargs):
42574257
im,c = self._cliplimb(ax,im)
42584258
return im
42594259

4260-
def arcgisimage(self,server='http://server.arcgisonline.com/ArcGIS',\
4261-
service='World_Imagery',xpixels=400,ypixels=None,\
4262-
dpi=96,cachedir=None,verbose=False,**kwargs):
4263-
"""
4264-
Retrieve an image using the ArcGIS Server REST API and display it on
4265-
the map. In order to use this method, the Basemap instance must be
4266-
created using the ``epsg`` keyword to define the map projection, unless
4267-
the ``cyl`` projection is used (in which case the epsg code 4326 is
4268-
assumed).
4260+
def arcgisimage(self, server="http://server.arcgisonline.com/ArcGIS",
4261+
service="World_Imagery", xpixels=400, ypixels=None,
4262+
dpi=96, cachedir=None, verbose=False, **kwargs):
4263+
r"""Display background image using ArcGIS Server REST API.
42694264
4270-
.. tabularcolumns:: |l|L|
4265+
In order to use this method, the :class:`Basemap` instance
4266+
must be created using the ``epsg`` keyword to define the
4267+
map projection, unless the "cyl" projection is used (in
4268+
which case the EPSG code 4326 is assumed).
42714269
4272-
============== ====================================================
4273-
Keywords Description
4274-
============== ====================================================
4275-
server web map server URL (default
4276-
http://server.arcgisonline.com/ArcGIS).
4277-
service service (image type) hosted on server (default
4278-
'World_Imagery', which is NASA 'Blue Marble'
4279-
image).
4280-
xpixels requested number of image pixels in x-direction
4281-
(default 400).
4282-
ypixels requested number of image pixels in y-direction.
4283-
Default (None) is to infer the number from
4284-
from xpixels and the aspect ratio of the
4285-
map projection region.
4286-
dpi The device resolution of the exported image (dots per
4287-
inch, default 96).
4288-
cachedir An optional directory to use as cache folder for the retrieved images.
4289-
verbose if True, print URL used to retrieve image (default
4290-
False).
4291-
============== ====================================================
4270+
Parameters
4271+
----------
42924272
4293-
Extra keyword ``ax`` can be used to override the default axis instance.
4273+
server : str, optional
4274+
base URL of the web map server
42944275
4295-
returns a matplotlib.image.AxesImage instance.
4276+
service : str, optional
4277+
service (image type) hosted by the server
4278+
4279+
xpixels : int, optional
4280+
requested number of image pixels in the `x`-direction
4281+
4282+
ypixels : int, optional
4283+
requested number of image pixels in the `y`-direction;
4284+
if not given, it is inferred from ``xpixels`` and the
4285+
aspect ratio of the map projection region
4286+
4287+
dpi : int, optional
4288+
device resolution of the exported image
4289+
4290+
cachedir : str, optional
4291+
if given, directory to use as cache folder for the images
4292+
retrieved from the server
4293+
4294+
verbose : bool, optional
4295+
if True, print debugging information
4296+
4297+
\**kwargs : dict, optional
4298+
keyword-only arguments; currently, only ``ax`` is supported
4299+
to override the default :class:`matplotlib.axes.Axes`
4300+
instance
4301+
4302+
Returns
4303+
-------
4304+
4305+
aximg : matplotlib.image.AxesImage
4306+
image axes instance
42964307
"""
42974308

4298-
# fix PIL import on some versions of OSX and scipy
4309+
# Fix PIL import on some versions of OSX and scipy.
42994310
try:
43004311
from PIL import Image
43014312
except ImportError:
@@ -4305,70 +4316,71 @@ def arcgisimage(self,server='http://server.arcgisonline.com/ArcGIS',\
43054316
raise ImportError("arcgisimage method requires PIL "
43064317
"(http://pillow.readthedocs.io)")
43074318

4308-
if not hasattr(self,'epsg'):
4319+
if not hasattr(self, "epsg"):
43094320
raise ValueError("the Basemap instance must be created using "
43104321
"an EPSG code (http://spatialreference.org) "
43114322
"in order to use the wmsmap method")
4312-
ax = kwargs.pop('ax', None) or self._check_ax()
4313-
# find the x,y values at the corner points.
4323+
4324+
ax = kwargs.pop("ax", None) or self._check_ax()
4325+
4326+
# Find the `(x, y)` values at the corner points.
43144327
with warnings.catch_warnings():
43154328
warnings.simplefilter("ignore", category=FutureWarning)
43164329
p = pyproj.Proj(init="epsg:%s" % self.epsg, preserve_units=True)
4317-
xmin,ymin = p(self.llcrnrlon,self.llcrnrlat)
4318-
xmax,ymax = p(self.urcrnrlon,self.urcrnrlat)
4330+
xmin, ymin = p(self.llcrnrlon, self.llcrnrlat)
4331+
xmax, ymax = p(self.urcrnrlon, self.urcrnrlat)
43194332
if self.projection in _cylproj:
4320-
Dateline =\
4321-
_geoslib.Point(self(180.,0.5*(self.llcrnrlat+self.urcrnrlat)))
4322-
hasDateline = Dateline.within(self._boundarypolyxy)
4323-
if hasDateline:
4333+
dateline = _geoslib.Point(self(180., 0.5 * (self.llcrnrlat + self.urcrnrlat)))
4334+
if dateline.within(self._boundarypolyxy):
43244335
raise ValueError("arcgisimage cannot handle images that cross "
43254336
"the dateline for cylindrical projections")
4326-
# ypixels not given, find by scaling xpixels by the map aspect ratio.
4337+
4338+
# If ypixels is not given, compute it with xpixels and aspect ratio.
43274339
if ypixels is None:
4328-
ypixels = int(self.aspect*xpixels)
4329-
# construct a URL using the ArcGIS Server REST API.
4330-
basemap_url = \
4331-
"%s/rest/services/%s/MapServer/export?\
4332-
bbox=%s,%s,%s,%s&\
4333-
bboxSR=%s&\
4334-
imageSR=%s&\
4335-
size=%s,%s&\
4336-
dpi=%s&\
4337-
format=png32&\
4338-
transparent=true&\
4339-
f=image" %\
4340-
(server,service,xmin,ymin,xmax,ymax,self.epsg,self.epsg,xpixels,ypixels,dpi)
4341-
# print URL?
4342-
if verbose: print(basemap_url)
4343-
4344-
if cachedir != None:
4340+
ypixels = int(self.aspect * xpixels)
4341+
4342+
# Construct a URL using the ArcGIS Server REST API.
4343+
basemap_url = "".join([
4344+
"%s/rest/services/%s/MapServer/export?",
4345+
"bbox=%s,%s,%s,%s&",
4346+
"bboxSR=%s&",
4347+
"imageSR=%s&",
4348+
"size=%s,%s&",
4349+
"dpi=%s&",
4350+
"format=png32&",
4351+
"transparent=true&",
4352+
"f=image",
4353+
]) % (server, service, xmin, ymin, xmax, ymax, self.epsg, self.epsg, xpixels, ypixels, dpi)
4354+
4355+
# Print URL in verbose mode.
4356+
if verbose: # pragma: no cover
4357+
print(basemap_url)
4358+
4359+
# Try to return fast if cache is enabled.
4360+
if cachedir is not None:
43454361
# Generate a filename for the cached file.
4346-
filename = "%s-bbox-%s-%s-%s-%s-bboxsr%s-imagesr%s-size-%s-%s-dpi%s.png" %\
4347-
(service,xmin,ymin,xmax,ymax,self.epsg,self.epsg,xpixels,ypixels,dpi)
4348-
4349-
# Check if the cache directory exists, if not create it.
4350-
if not os.path.exists(cachedir):
4351-
os.makedirs(cachedir)
4352-
4353-
# Check if the image is already in the cachedir folder.
4354-
cache_path = cachedir + filename
4355-
4356-
if os.path.isfile(cache_path) and verbose:
4357-
print('Image already in cache')
4362+
filename = "%s-bbox-%s-%s-%s-%s-bboxsr%s-imagesr%s-size-%s-%s-dpi%s.png" % \
4363+
(service, xmin, ymin, xmax, ymax, self.epsg, self.epsg, xpixels, ypixels, dpi)
4364+
# Return fast if the image is already in the cache.
4365+
cache_path = os.path.join(cachedir, filename)
4366+
if os.path.isfile(cache_path):
4367+
if verbose: # pragma: no cover
4368+
print("Image already in cache")
43584369
img = Image.open(cache_path)
4359-
return basemap.imshow(img, ax=ax, origin='upper')
4370+
return self.imshow(img, ax=ax, origin="upper")
43604371

4361-
# Retrieve image from remote server.
4372+
# Retrieve image from the remote server.
43624373
import contextlib
43634374
conn = urlopen(basemap_url)
43644375
with contextlib.closing(conn):
43654376
img = Image.open(conn)
43664377
# Save to cache if requested.
4367-
if cachedir != None:
4378+
if cachedir is not None:
4379+
# Check if the cache directory exists, if not create it.
4380+
if not os.path.exists(cachedir):
4381+
os.makedirs(cachedir)
43684382
img.save(cache_path)
4369-
4370-
# Return AxesImage instance.
4371-
return self.imshow(img, ax=ax, origin='upper')
4383+
return self.imshow(img, ax=ax, origin="upper")
43724384

43734385
def wmsimage(self,server,\
43744386
xpixels=400,ypixels=None,\

‎packages/basemap/test/mpl_toolkits/basemap/test_Basemap.py

+46
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
"""Import test for the :mod:`mpl_toolkits.basemap.Basemap` class."""
22

3+
import os
4+
import shutil
5+
import tempfile
36
import datetime as dt
47
try:
58
import unittest2 as unittest
@@ -145,6 +148,49 @@ def test_arcgisimage_with_cyl(self, axs=None, axslen0=10):
145148
axs_children = axs_obj.get_children()
146149
self.assertEqual(len(axs_children), axslen0 + 1)
147150

151+
@unittest.skipIf(PIL is None, reason="pillow unavailable")
152+
def test_arcgisimage_with_cyl_using_cache(self, existing=False, axs=None, axslen0=10):
153+
"""Test showing an ArcGIS image as background."""
154+
155+
axs_obj = plt.gca() if axs is None else axs
156+
axs_children = axs_obj.get_children()
157+
self.assertEqual(len(axs_children), axslen0)
158+
159+
bmap = Basemap(ax=axs, projection="cyl", resolution=None,
160+
llcrnrlon=-90, llcrnrlat=30,
161+
urcrnrlon=-60, urcrnrlat=60)
162+
163+
# Create cache directory string and check it is empty.
164+
tmpdir = tempfile.mkdtemp(prefix="tmp-basemap-cachedir-")
165+
cachedir = tmpdir if existing else os.path.join(tmpdir, "cachedir")
166+
if os.path.isdir(cachedir):
167+
self.assertEqual(len(os.listdir(cachedir)), 0)
168+
169+
try:
170+
# Check that the first call populates the cache.
171+
img = bmap.arcgisimage(verbose=False, cachedir=cachedir)
172+
self.assertEqual(len(os.listdir(cachedir)), 1)
173+
# Check output properties after the first call.
174+
self.assertIsInstance(img, AxesImage)
175+
axs_children = axs_obj.get_children()
176+
self.assertEqual(len(axs_children), axslen0 + 1)
177+
# Check that the second call does not update the cache.
178+
img = bmap.arcgisimage(verbose=False, cachedir=cachedir)
179+
self.assertEqual(len(os.listdir(cachedir)), 1)
180+
# Check output properties after the second call.
181+
self.assertIsInstance(img, AxesImage)
182+
axs_children = axs_obj.get_children()
183+
self.assertEqual(len(axs_children), axslen0 + 2)
184+
finally:
185+
if os.path.isdir(tmpdir):
186+
shutil.rmtree(tmpdir)
187+
188+
@unittest.skipIf(PIL is None, reason="pillow unavailable")
189+
def test_arcgisimage_with_cyl_using_cache_already_existing(self):
190+
"""Test showing an ArcGIS image as background."""
191+
192+
self.test_arcgisimage_with_cyl_using_cache(existing=True)
193+
148194
def _test_basemap_data_warpimage(self, method, axs=None, axslen0=10):
149195
"""Test drawing a map background from :mod:`basemap_data`."""
150196

0 commit comments

Comments
 (0)
Please sign in to comment.