From dc87639b3f6c082a4f9fb035318abffeffa42922 Mon Sep 17 00:00:00 2001 From: nitram96 Date: Thu, 8 Feb 2024 14:51:12 +0000 Subject: [PATCH 1/7] fixed bugs with using cachedir in arcgisimage. cachedir is no longer ignored if verbose is false, if a cached image is found now correctly shows it using self.imshow(), now uses os.path.join to join cachedir and filename, so a backslash is no longer required at the end of cachedir --- packages/basemap/src/mpl_toolkits/basemap/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/basemap/src/mpl_toolkits/basemap/__init__.py b/packages/basemap/src/mpl_toolkits/basemap/__init__.py index 75bcecf9e..88963acff 100644 --- a/packages/basemap/src/mpl_toolkits/basemap/__init__.py +++ b/packages/basemap/src/mpl_toolkits/basemap/__init__.py @@ -4351,12 +4351,13 @@ def arcgisimage(self,server='http://server.arcgisonline.com/ArcGIS',\ os.makedirs(cachedir) # Check if the image is already in the cachedir folder. - cache_path = cachedir + filename + cache_path = os.path.join(cachedir, filename) - if os.path.isfile(cache_path) and verbose: - print('Image already in cache') + if os.path.isfile(cache_path): + if verbose: + print('Image already in cache') img = Image.open(cache_path) - return basemap.imshow(img, ax=ax, origin='upper') + return self.imshow(img, ax=ax, origin='upper') # Retrieve image from remote server. import contextlib From d5ba71fd38f5bcb2551c7dab09a55f6f417ac763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Wed, 14 Feb 2024 10:56:53 +0100 Subject: [PATCH 2/7] Apply basic PyLint corrections to Basemap.arcgisimage method --- .../src/mpl_toolkits/basemap/__init__.py | 61 ++++++++++--------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/packages/basemap/src/mpl_toolkits/basemap/__init__.py b/packages/basemap/src/mpl_toolkits/basemap/__init__.py index 88963acff..e13cbc37f 100644 --- a/packages/basemap/src/mpl_toolkits/basemap/__init__.py +++ b/packages/basemap/src/mpl_toolkits/basemap/__init__.py @@ -4257,9 +4257,9 @@ def pil_to_array(*args, **kwargs): im,c = self._cliplimb(ax,im) return im - def arcgisimage(self,server='http://server.arcgisonline.com/ArcGIS',\ - service='World_Imagery',xpixels=400,ypixels=None,\ - dpi=96,cachedir=None,verbose=False,**kwargs): + def arcgisimage(self, server="http://server.arcgisonline.com/ArcGIS", + service="World_Imagery", xpixels=400, ypixels=None, + dpi=96, cachedir=None, verbose=False, **kwargs): """ Retrieve an image using the ArcGIS Server REST API and display it on the map. In order to use this method, the Basemap instance must be @@ -4285,7 +4285,8 @@ def arcgisimage(self,server='http://server.arcgisonline.com/ArcGIS',\ map projection region. dpi The device resolution of the exported image (dots per inch, default 96). - cachedir An optional directory to use as cache folder for the retrieved images. + cachedir An optional directory to use as cache folder for the + retrieved images. verbose if True, print URL used to retrieve image (default False). ============== ==================================================== @@ -4295,7 +4296,7 @@ def arcgisimage(self,server='http://server.arcgisonline.com/ArcGIS',\ returns a matplotlib.image.AxesImage instance. """ - # fix PIL import on some versions of OSX and scipy + # Fix PIL import on some versions of OSX and scipy. try: from PIL import Image except ImportError: @@ -4305,28 +4306,30 @@ def arcgisimage(self,server='http://server.arcgisonline.com/ArcGIS',\ raise ImportError("arcgisimage method requires PIL " "(http://pillow.readthedocs.io)") - if not hasattr(self,'epsg'): + if not hasattr(self, "epsg"): raise ValueError("the Basemap instance must be created using " "an EPSG code (http://spatialreference.org) " "in order to use the wmsmap method") - ax = kwargs.pop('ax', None) or self._check_ax() - # find the x,y values at the corner points. + + ax = kwargs.pop("ax", None) or self._check_ax() + + # Find the (x, y) values at the corner points. with warnings.catch_warnings(): warnings.simplefilter("ignore", category=FutureWarning) p = pyproj.Proj(init="epsg:%s" % self.epsg, preserve_units=True) - xmin,ymin = p(self.llcrnrlon,self.llcrnrlat) - xmax,ymax = p(self.urcrnrlon,self.urcrnrlat) + xmin, ymin = p(self.llcrnrlon, self.llcrnrlat) + xmax, ymax = p(self.urcrnrlon, self.urcrnrlat) if self.projection in _cylproj: - Dateline =\ - _geoslib.Point(self(180.,0.5*(self.llcrnrlat+self.urcrnrlat))) - hasDateline = Dateline.within(self._boundarypolyxy) - if hasDateline: + dateline = _geoslib.Point(self(180., 0.5 * (self.llcrnrlat + self.urcrnrlat))) + if dateline.within(self._boundarypolyxy): raise ValueError("arcgisimage cannot handle images that cross " "the dateline for cylindrical projections") - # ypixels not given, find by scaling xpixels by the map aspect ratio. + + # If ypixels is not given, compute it with xpixels and aspect ratio. if ypixels is None: - ypixels = int(self.aspect*xpixels) - # construct a URL using the ArcGIS Server REST API. + ypixels = int(self.aspect * xpixels) + + # Construct a URL using the ArcGIS Server REST API. basemap_url = \ "%s/rest/services/%s/MapServer/export?\ bbox=%s,%s,%s,%s&\ @@ -4337,27 +4340,29 @@ def arcgisimage(self,server='http://server.arcgisonline.com/ArcGIS',\ format=png32&\ transparent=true&\ f=image" %\ -(server,service,xmin,ymin,xmax,ymax,self.epsg,self.epsg,xpixels,ypixels,dpi) - # print URL? - if verbose: print(basemap_url) +(server, service, xmin, ymin, xmax, ymax, self.epsg, self.epsg, xpixels, ypixels, dpi) + + # Print URL in verbose mode. + if verbose: + print(basemap_url) + + if cachedir is not None: - if cachedir != None: # Generate a filename for the cached file. filename = "%s-bbox-%s-%s-%s-%s-bboxsr%s-imagesr%s-size-%s-%s-dpi%s.png" %\ - (service,xmin,ymin,xmax,ymax,self.epsg,self.epsg,xpixels,ypixels,dpi) + (service, xmin, ymin, xmax, ymax, self.epsg, self.epsg, xpixels, ypixels, dpi) - # Check if the cache directory exists, if not create it. + # Check if the cache directory exists, if not create it. if not os.path.exists(cachedir): os.makedirs(cachedir) # Check if the image is already in the cachedir folder. cache_path = os.path.join(cachedir, filename) - if os.path.isfile(cache_path): if verbose: - print('Image already in cache') + print("Image already in cache") img = Image.open(cache_path) - return self.imshow(img, ax=ax, origin='upper') + return self.imshow(img, ax=ax, origin="upper") # Retrieve image from remote server. import contextlib @@ -4365,11 +4370,11 @@ def arcgisimage(self,server='http://server.arcgisonline.com/ArcGIS',\ with contextlib.closing(conn): img = Image.open(conn) # Save to cache if requested. - if cachedir != None: + if cachedir is not None: img.save(cache_path) # Return AxesImage instance. - return self.imshow(img, ax=ax, origin='upper') + return self.imshow(img, ax=ax, origin="upper") def wmsimage(self,server,\ xpixels=400,ypixels=None,\ From d4eae27751352f260f9d5c9d271a5ecd592c92cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Wed, 14 Feb 2024 11:19:38 +0100 Subject: [PATCH 3/7] Apply basic Flake8 corrections to Basemap.arcgisimage method --- .../src/mpl_toolkits/basemap/__init__.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/basemap/src/mpl_toolkits/basemap/__init__.py b/packages/basemap/src/mpl_toolkits/basemap/__init__.py index e13cbc37f..7562500cd 100644 --- a/packages/basemap/src/mpl_toolkits/basemap/__init__.py +++ b/packages/basemap/src/mpl_toolkits/basemap/__init__.py @@ -4330,17 +4330,17 @@ def arcgisimage(self, server="http://server.arcgisonline.com/ArcGIS", ypixels = int(self.aspect * xpixels) # Construct a URL using the ArcGIS Server REST API. - basemap_url = \ -"%s/rest/services/%s/MapServer/export?\ -bbox=%s,%s,%s,%s&\ -bboxSR=%s&\ -imageSR=%s&\ -size=%s,%s&\ -dpi=%s&\ -format=png32&\ -transparent=true&\ -f=image" %\ -(server, service, xmin, ymin, xmax, ymax, self.epsg, self.epsg, xpixels, ypixels, dpi) + basemap_url = "".join([ + "%s/rest/services/%s/MapServer/export?", + "bbox=%s,%s,%s,%s&", + "bboxSR=%s&", + "imageSR=%s&", + "size=%s,%s&", + "dpi=%s&", + "format=png32&", + "transparent=true&", + "f=image", + ]) % (server, service, xmin, ymin, xmax, ymax, self.epsg, self.epsg, xpixels, ypixels, dpi) # Print URL in verbose mode. if verbose: @@ -4349,8 +4349,8 @@ def arcgisimage(self, server="http://server.arcgisonline.com/ArcGIS", if cachedir is not None: # Generate a filename for the cached file. - filename = "%s-bbox-%s-%s-%s-%s-bboxsr%s-imagesr%s-size-%s-%s-dpi%s.png" %\ - (service, xmin, ymin, xmax, ymax, self.epsg, self.epsg, xpixels, ypixels, dpi) + filename = "%s-bbox-%s-%s-%s-%s-bboxsr%s-imagesr%s-size-%s-%s-dpi%s.png" % \ + (service, xmin, ymin, xmax, ymax, self.epsg, self.epsg, xpixels, ypixels, dpi) # Check if the cache directory exists, if not create it. if not os.path.exists(cachedir): From 27ffdb875bb5196758ffc98546242162aa33e5f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Wed, 14 Feb 2024 11:53:50 +0100 Subject: [PATCH 4/7] Clean Basemap.arcgisimage docstring and comments --- .../src/mpl_toolkits/basemap/__init__.py | 82 ++++++++++--------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/packages/basemap/src/mpl_toolkits/basemap/__init__.py b/packages/basemap/src/mpl_toolkits/basemap/__init__.py index 7562500cd..bec68ab2e 100644 --- a/packages/basemap/src/mpl_toolkits/basemap/__init__.py +++ b/packages/basemap/src/mpl_toolkits/basemap/__init__.py @@ -4260,40 +4260,50 @@ def pil_to_array(*args, **kwargs): def arcgisimage(self, server="http://server.arcgisonline.com/ArcGIS", service="World_Imagery", xpixels=400, ypixels=None, dpi=96, cachedir=None, verbose=False, **kwargs): - """ - Retrieve an image using the ArcGIS Server REST API and display it on - the map. In order to use this method, the Basemap instance must be - created using the ``epsg`` keyword to define the map projection, unless - the ``cyl`` projection is used (in which case the epsg code 4326 is - assumed). + r"""Display background image using ArcGIS Server REST API. - .. tabularcolumns:: |l|L| + In order to use this method, the :class:`Basemap` instance + must be created using the ``epsg`` keyword to define the + map projection, unless the "cyl" projection is used (in + which case the EPSG code 4326 is assumed). - ============== ==================================================== - Keywords Description - ============== ==================================================== - server web map server URL (default - http://server.arcgisonline.com/ArcGIS). - service service (image type) hosted on server (default - 'World_Imagery', which is NASA 'Blue Marble' - image). - xpixels requested number of image pixels in x-direction - (default 400). - ypixels requested number of image pixels in y-direction. - Default (None) is to infer the number from - from xpixels and the aspect ratio of the - map projection region. - dpi The device resolution of the exported image (dots per - inch, default 96). - cachedir An optional directory to use as cache folder for the - retrieved images. - verbose if True, print URL used to retrieve image (default - False). - ============== ==================================================== + Parameters + ---------- - Extra keyword ``ax`` can be used to override the default axis instance. + server : str, optional + base URL of the web map server - returns a matplotlib.image.AxesImage instance. + service : str, optional + service (image type) hosted by the server + + xpixels : int, optional + requested number of image pixels in the `x`-direction + + ypixels : int, optional + requested number of image pixels in the `y`-direction; + if not given, it is inferred from ``xpixels`` and the + aspect ratio of the map projection region + + dpi : int, optional + device resolution of the exported image + + cachedir : str, optional + if given, directory to use as cache folder for the images + retrieved from the server + + verbose : bool, optional + if True, print debugging information + + \**kwargs : dict, optional + keyword-only arguments; currently, only ``ax`` is supported + to override the default :class:`matplotlib.axes.Axes` + instance + + Returns + ------- + + aximg : matplotlib.image.AxesImage + image axes instance """ # Fix PIL import on some versions of OSX and scipy. @@ -4313,7 +4323,7 @@ def arcgisimage(self, server="http://server.arcgisonline.com/ArcGIS", ax = kwargs.pop("ax", None) or self._check_ax() - # Find the (x, y) values at the corner points. + # Find the `(x, y)` values at the corner points. with warnings.catch_warnings(): warnings.simplefilter("ignore", category=FutureWarning) p = pyproj.Proj(init="epsg:%s" % self.epsg, preserve_units=True) @@ -4346,17 +4356,15 @@ def arcgisimage(self, server="http://server.arcgisonline.com/ArcGIS", if verbose: print(basemap_url) + # Try to return fast if cache is enabled. if cachedir is not None: - # Generate a filename for the cached file. filename = "%s-bbox-%s-%s-%s-%s-bboxsr%s-imagesr%s-size-%s-%s-dpi%s.png" % \ (service, xmin, ymin, xmax, ymax, self.epsg, self.epsg, xpixels, ypixels, dpi) - # Check if the cache directory exists, if not create it. if not os.path.exists(cachedir): os.makedirs(cachedir) - - # Check if the image is already in the cachedir folder. + # Return fast if the image is already in the cache. cache_path = os.path.join(cachedir, filename) if os.path.isfile(cache_path): if verbose: @@ -4364,7 +4372,7 @@ def arcgisimage(self, server="http://server.arcgisonline.com/ArcGIS", img = Image.open(cache_path) return self.imshow(img, ax=ax, origin="upper") - # Retrieve image from remote server. + # Retrieve image from the remote server. import contextlib conn = urlopen(basemap_url) with contextlib.closing(conn): @@ -4372,8 +4380,6 @@ def arcgisimage(self, server="http://server.arcgisonline.com/ArcGIS", # Save to cache if requested. if cachedir is not None: img.save(cache_path) - - # Return AxesImage instance. return self.imshow(img, ax=ax, origin="upper") def wmsimage(self,server,\ From d9c9ee74cbe5c38e5ecb4a166f4012c9f65acc22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Wed, 14 Feb 2024 12:39:13 +0100 Subject: [PATCH 5/7] Add basic test for Basemap.arcgisimage with cache directory --- .../src/mpl_toolkits/basemap/__init__.py | 4 +-- .../test/mpl_toolkits/basemap/test_Basemap.py | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/packages/basemap/src/mpl_toolkits/basemap/__init__.py b/packages/basemap/src/mpl_toolkits/basemap/__init__.py index bec68ab2e..efcc6f9c8 100644 --- a/packages/basemap/src/mpl_toolkits/basemap/__init__.py +++ b/packages/basemap/src/mpl_toolkits/basemap/__init__.py @@ -4353,7 +4353,7 @@ def arcgisimage(self, server="http://server.arcgisonline.com/ArcGIS", ]) % (server, service, xmin, ymin, xmax, ymax, self.epsg, self.epsg, xpixels, ypixels, dpi) # Print URL in verbose mode. - if verbose: + if verbose: # pragma: no cover print(basemap_url) # Try to return fast if cache is enabled. @@ -4367,7 +4367,7 @@ def arcgisimage(self, server="http://server.arcgisonline.com/ArcGIS", # Return fast if the image is already in the cache. cache_path = os.path.join(cachedir, filename) if os.path.isfile(cache_path): - if verbose: + if verbose: # pragma: no cover print("Image already in cache") img = Image.open(cache_path) return self.imshow(img, ax=ax, origin="upper") diff --git a/packages/basemap/test/mpl_toolkits/basemap/test_Basemap.py b/packages/basemap/test/mpl_toolkits/basemap/test_Basemap.py index 2565c1569..1579cfb5d 100644 --- a/packages/basemap/test/mpl_toolkits/basemap/test_Basemap.py +++ b/packages/basemap/test/mpl_toolkits/basemap/test_Basemap.py @@ -1,5 +1,8 @@ """Import test for the :mod:`mpl_toolkits.basemap.Basemap` class.""" +import os +import shutil +import tempfile import datetime as dt try: import unittest2 as unittest @@ -145,6 +148,39 @@ def test_arcgisimage_with_cyl(self, axs=None, axslen0=10): axs_children = axs_obj.get_children() self.assertEqual(len(axs_children), axslen0 + 1) + @unittest.skipIf(PIL is None, reason="pillow unavailable") + def test_arcgisimage_with_cyl_using_cache(self, axs=None, axslen0=10): + """Test showing an ArcGIS image as background.""" + + axs_obj = plt.gca() if axs is None else axs + axs_children = axs_obj.get_children() + self.assertEqual(len(axs_children), axslen0) + + bmap = Basemap(ax=axs, projection="cyl", resolution=None, + llcrnrlon=-90, llcrnrlat=30, + urcrnrlon=-60, urcrnrlat=60) + + cachedir = tempfile.mkdtemp(prefix="tmp-basemap-cachedir-") + try: + # Check that the cache is initially empty. + self.assertEqual(len(os.listdir(cachedir)), 0) + # Check that the first call populates the cache. + img = bmap.arcgisimage(verbose=False, cachedir=cachedir) + self.assertEqual(len(os.listdir(cachedir)), 1) + # Check output properties after the first call. + self.assertIsInstance(img, AxesImage) + axs_children = axs_obj.get_children() + self.assertEqual(len(axs_children), axslen0 + 1) + # Check that the second call does not update the cache. + img = bmap.arcgisimage(verbose=False, cachedir=cachedir) + self.assertEqual(len(os.listdir(cachedir)), 1) + # Check output properties after the second call. + self.assertIsInstance(img, AxesImage) + axs_children = axs_obj.get_children() + self.assertEqual(len(axs_children), axslen0 + 2) + finally: + shutil.rmtree(cachedir) + def _test_basemap_data_warpimage(self, method, axs=None, axslen0=10): """Test drawing a map background from :mod:`basemap_data`.""" From 0131763aa7d71de0c3b189f489228e9b55f767c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Wed, 14 Feb 2024 12:49:20 +0100 Subject: [PATCH 6/7] Fix block location Basemap.arcgisimage for cachedir creation --- packages/basemap/src/mpl_toolkits/basemap/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/basemap/src/mpl_toolkits/basemap/__init__.py b/packages/basemap/src/mpl_toolkits/basemap/__init__.py index efcc6f9c8..a7050beed 100644 --- a/packages/basemap/src/mpl_toolkits/basemap/__init__.py +++ b/packages/basemap/src/mpl_toolkits/basemap/__init__.py @@ -4361,9 +4361,6 @@ def arcgisimage(self, server="http://server.arcgisonline.com/ArcGIS", # Generate a filename for the cached file. filename = "%s-bbox-%s-%s-%s-%s-bboxsr%s-imagesr%s-size-%s-%s-dpi%s.png" % \ (service, xmin, ymin, xmax, ymax, self.epsg, self.epsg, xpixels, ypixels, dpi) - # Check if the cache directory exists, if not create it. - if not os.path.exists(cachedir): - os.makedirs(cachedir) # Return fast if the image is already in the cache. cache_path = os.path.join(cachedir, filename) if os.path.isfile(cache_path): @@ -4379,6 +4376,9 @@ def arcgisimage(self, server="http://server.arcgisonline.com/ArcGIS", img = Image.open(conn) # Save to cache if requested. if cachedir is not None: + # Check if the cache directory exists, if not create it. + if not os.path.exists(cachedir): + os.makedirs(cachedir) img.save(cache_path) return self.imshow(img, ax=ax, origin="upper") From ec15505506a9570d74abbb28e0c76a7529d16bcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Wed, 14 Feb 2024 12:49:46 +0100 Subject: [PATCH 7/7] Add test for Basemap.arcgisimage with on-the-fly cachedir creation --- .../test/mpl_toolkits/basemap/test_Basemap.py | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/basemap/test/mpl_toolkits/basemap/test_Basemap.py b/packages/basemap/test/mpl_toolkits/basemap/test_Basemap.py index 1579cfb5d..6a94d291f 100644 --- a/packages/basemap/test/mpl_toolkits/basemap/test_Basemap.py +++ b/packages/basemap/test/mpl_toolkits/basemap/test_Basemap.py @@ -149,7 +149,7 @@ def test_arcgisimage_with_cyl(self, axs=None, axslen0=10): self.assertEqual(len(axs_children), axslen0 + 1) @unittest.skipIf(PIL is None, reason="pillow unavailable") - def test_arcgisimage_with_cyl_using_cache(self, axs=None, axslen0=10): + def test_arcgisimage_with_cyl_using_cache(self, existing=False, axs=None, axslen0=10): """Test showing an ArcGIS image as background.""" axs_obj = plt.gca() if axs is None else axs @@ -160,10 +160,13 @@ def test_arcgisimage_with_cyl_using_cache(self, axs=None, axslen0=10): llcrnrlon=-90, llcrnrlat=30, urcrnrlon=-60, urcrnrlat=60) - cachedir = tempfile.mkdtemp(prefix="tmp-basemap-cachedir-") - try: - # Check that the cache is initially empty. + # Create cache directory string and check it is empty. + tmpdir = tempfile.mkdtemp(prefix="tmp-basemap-cachedir-") + cachedir = tmpdir if existing else os.path.join(tmpdir, "cachedir") + if os.path.isdir(cachedir): self.assertEqual(len(os.listdir(cachedir)), 0) + + try: # Check that the first call populates the cache. img = bmap.arcgisimage(verbose=False, cachedir=cachedir) self.assertEqual(len(os.listdir(cachedir)), 1) @@ -179,7 +182,14 @@ def test_arcgisimage_with_cyl_using_cache(self, axs=None, axslen0=10): axs_children = axs_obj.get_children() self.assertEqual(len(axs_children), axslen0 + 2) finally: - shutil.rmtree(cachedir) + if os.path.isdir(tmpdir): + shutil.rmtree(tmpdir) + + @unittest.skipIf(PIL is None, reason="pillow unavailable") + def test_arcgisimage_with_cyl_using_cache_already_existing(self): + """Test showing an ArcGIS image as background.""" + + self.test_arcgisimage_with_cyl_using_cache(existing=True) def _test_basemap_data_warpimage(self, method, axs=None, axslen0=10): """Test drawing a map background from :mod:`basemap_data`."""