From cd9461044922ec4546fe63b493829c41ce7a3d47 Mon Sep 17 00:00:00 2001 From: Martin Fleischmann Date: Mon, 24 Nov 2025 17:02:48 +0100 Subject: [PATCH 1/2] DEP: remove long deprecated plot_map --- contextily/__init__.py | 2 +- contextily/place.py | 66 ++---------------------------------------- docs/reference.rst | 2 -- examples/plot_map.py | 52 --------------------------------- tests/test_cx.py | 18 ------------ 5 files changed, 3 insertions(+), 137 deletions(-) delete mode 100644 examples/plot_map.py diff --git a/contextily/__init__.py b/contextily/__init__.py index a64a2130..bca91d99 100644 --- a/contextily/__init__.py +++ b/contextily/__init__.py @@ -3,7 +3,7 @@ """ import xyzservices.providers as providers -from .place import Place, plot_map +from .place import Place from .tile import * from .plotting import add_basemap, add_attribution diff --git a/contextily/place.py b/contextily/place.py index f94870aa..89ec5ed3 100644 --- a/contextily/place.py +++ b/contextily/place.py @@ -1,10 +1,10 @@ """Tools for generating maps from a text search.""" + import geopy as gp import numpy as np import matplotlib.pyplot as plt -import warnings -from .tile import howmany, bounds2raster, bounds2img, _sm2ll, _calculate_zoom +from .tile import howmany, bounds2raster, bounds2img, _calculate_zoom from .plotting import INTERPOLATION, ZOOM, add_attribution from . import providers from xyzservices import TileProvider @@ -214,65 +214,3 @@ def __repr__(self): self.place, self.n_tiles, self.zoom, self.im.shape[:2] ) return s - - -def plot_map( - place, bbox=None, title=None, ax=None, axis_off=True, latlon=True, attribution=None -): - """Plot a map of the given place. - - Parameters - ---------- - place : instance of Place or ndarray - The map to plot. If an ndarray, this must be an image corresponding - to a map. If an instance of ``Place``, the extent of the image and name - will be inferred from the bounding box. - ax : instance of matplotlib Axes object or None - The axis on which to plot. If None, one will be created. - axis_off : bool - Whether to turn off the axis border and ticks before plotting. - attribution : str - [Optional. Default to standard `ATTRIBUTION`] Text to be added at the - bottom of the axis. - - Returns - ------- - ax : instance of matplotlib Axes object or None - The axis on the map is plotted. - """ - warnings.warn( - ( - "The method `plot_map` is deprecated and will be removed from the" - " library in future versions. Please use either `add_basemap` or" - " the internal method `Place.plot`" - ), - DeprecationWarning, - ) - if not isinstance(place, Place): - im = place - bbox = bbox - title = title - else: - im = place.im - if bbox is None: - bbox = place.bbox_map - if latlon is True: - # Convert w, s, e, n into lon/lat - w, e, s, n = bbox - w, s = _sm2ll(w, s) - e, n = _sm2ll(e, n) - bbox = [w, e, s, n] - - title = place.place if title is None else title - - if ax is None: - fig, ax = plt.subplots(figsize=(15, 15)) - ax.imshow(im, extent=bbox) - ax.set(xlabel="X", ylabel="Y") - if title is not None: - ax.set(title=title) - if attribution: - add_attribution(ax, attribution) - if axis_off is True: - ax.set_axis_off() - return ax diff --git a/docs/reference.rst b/docs/reference.rst index 331f818e..3985b02b 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -32,5 +32,3 @@ Geocoding and plotting places .. automethod:: contextily.Place.plot -.. autofunction:: contextily.plot_map - diff --git a/examples/plot_map.py b/examples/plot_map.py deleted file mode 100644 index aee0254c..00000000 --- a/examples/plot_map.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -Downloading and Plotting Maps ------------------------------ - -Plotting maps with Contextily. - -Contextily is designed to pull map tile information from the web. In many -cases we want to go from a location to a map of that location as quickly -as possible. There are two main ways to do this with Contextily. - -Searching for places with text -============================== - -The simplest approach is to search for a location with text. You can do -this with the ``Place`` class. This will return an object that contains -metadata about the place, such as its bounding box. It will also contain an -image of the place. -""" -import numpy as np -import matplotlib.pyplot as plt -import contextily as cx - -loc = cx.Place("boulder", zoom_adjust=0) # zoom_adjust modifies the auto-zoom - -# Print some metadata -for attr in ["w", "s", "e", "n", "place", "zoom", "n_tiles"]: - print("{}: {}".format(attr, getattr(loc, attr))) - -# Show the map -im1 = loc.im - -fig, axs = plt.subplots(1, 3, figsize=(15, 5)) -cx.plot_map(loc, ax=axs[0]) - -############################################################################### -# The zoom level will be chosen for you by default, though you can specify -# this manually as well: - -loc2 = cx.Place("boulder", zoom=11) -cx.plot_map(loc2, ax=axs[1]) - -############################################################################### -# Downloading tiles from bounds -# ============================= -# -# You can also grab tile information directly from a bounding box + zoom level. -# This is demoed below: - -im2, bbox = cx.bounds2img(loc.w, loc.s, loc.e, loc.n, zoom=loc.zoom, ll=True) -cx.plot_map(im2, bbox, ax=axs[2], title="Boulder, CO") - -plt.show() diff --git a/tests/test_cx.py b/tests/test_cx.py index 12183e7a..d03e2070 100644 --- a/tests/test_cx.py +++ b/tests/test_cx.py @@ -639,24 +639,6 @@ def test_place(): ax = loc.plot(ax=ax) assert_array_almost_equal(loc.bbox_map, ax.images[0].get_extent()) - -@pytest.mark.network -def test_plot_map(): - # Place as a search - loc = cx.Place(SEARCH, zoom_adjust=ADJUST) - w, e, s, n = loc.bbox_map - ax = cx.plot_map(loc) - - assert ax.get_title() == loc.place - ax = cx.plot_map(loc.im, loc.bbox) - assert_array_almost_equal(loc.bbox, ax.images[0].get_extent()) - - # Place as an image - img, ext = cx.bounds2img(w, s, e, n, zoom=10) - ax = cx.plot_map(img, ext) - assert_array_almost_equal(ext, ax.images[0].get_extent()) - - # Plotting From e28c2a308761b0ac52d0411a5bf471b86eef8429 Mon Sep 17 00:00:00 2001 From: Martin Fleischmann Date: Mon, 24 Nov 2025 17:02:54 +0100 Subject: [PATCH 2/2] lint --- tests/test_cx.py | 116 +++++++++++++++++++++++------------------------ 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/tests/test_cx.py b/tests/test_cx.py index d03e2070..f1c50773 100644 --- a/tests/test_cx.py +++ b/tests/test_cx.py @@ -128,27 +128,27 @@ def test_custom_headers(): -93.50721740722656, 36.49387741088867, ) - + # Create a mock image to return img_array = np.random.randint(0, 255, (256, 256, 4), dtype=np.uint8) img = Image.fromarray(img_array, mode='RGBA') img_bytes = io.BytesIO() img.save(img_bytes, format='PNG') img_bytes.seek(0) - + # Create mock response mock_response = MagicMock() mock_response.status_code = 200 mock_response.content = img_bytes.read() - + custom_headers = { "Authorization": "Bearer test-token-123", "X-Custom-Header": "test-value" } - + with patch('contextily.tile.requests.get', return_value=mock_response) as mock_get: mock_get.return_value.raise_for_status = MagicMock() - + # Test bounds2img with custom headers # Disable cache to ensure requests.get is actually called img, ext = cx.bounds2img( @@ -159,21 +159,21 @@ def test_custom_headers(): use_cache=False, source=cx.providers.CartoDB.Positron ) - + # Verify requests.get was called assert mock_get.called, "requests.get should have been called" - + # Verify that the headers were passed correctly # The actual call should merge custom headers with the default user-agent call_args = mock_get.call_args headers_used = call_args.kwargs.get('headers', call_args[1].get('headers')) - + # Check that custom headers are present assert "Authorization" in headers_used assert headers_used["Authorization"] == "Bearer test-token-123" assert "X-Custom-Header" in headers_used assert headers_used["X-Custom-Header"] == "test-value" - + # Check that the default user-agent is also present assert "user-agent" in headers_used assert headers_used["user-agent"].startswith("contextily-") @@ -187,28 +187,28 @@ def test_custom_headers_bounds2raster(tmpdir): -93.50721740722656, 36.49387741088867, ) - + # Create a mock image to return img_array = np.random.randint(0, 255, (256, 256, 4), dtype=np.uint8) img = Image.fromarray(img_array, mode='RGBA') img_bytes = io.BytesIO() img.save(img_bytes, format='PNG') img_bytes.seek(0) - + # Create mock response mock_response = MagicMock() mock_response.status_code = 200 mock_response.content = img_bytes.read() - + custom_headers = { "Authorization": "Bearer test-token-456", } - + output_path = str(tmpdir.join("test_headers.tif")) - + with patch('contextily.tile.requests.get', return_value=mock_response) as mock_get: mock_get.return_value.raise_for_status = MagicMock() - + # Test bounds2raster with custom headers # Disable cache to ensure requests.get is actually called _ = cx.bounds2raster( @@ -220,12 +220,12 @@ def test_custom_headers_bounds2raster(tmpdir): use_cache=False, source=cx.providers.CartoDB.Positron ) - + # Verify requests.get was called with correct headers assert mock_get.called call_args = mock_get.call_args headers_used = call_args.kwargs.get('headers', call_args[1].get('headers')) - + assert "Authorization" in headers_used assert headers_used["Authorization"] == "Bearer test-token-456" assert "user-agent" in headers_used @@ -239,22 +239,22 @@ def test_no_custom_headers(): -93.50721740722656, 36.49387741088867, ) - + # Create a mock image to return img_array = np.random.randint(0, 255, (256, 256, 4), dtype=np.uint8) img = Image.fromarray(img_array, mode='RGBA') img_bytes = io.BytesIO() img.save(img_bytes, format='PNG') img_bytes.seek(0) - + # Create mock response mock_response = MagicMock() mock_response.status_code = 200 mock_response.content = img_bytes.read() - + with patch('contextily.tile.requests.get', return_value=mock_response) as mock_get: mock_get.return_value.raise_for_status = MagicMock() - + # Test bounds2img without custom headers (default behavior) # Disable cache to ensure requests.get is actually called img, ext = cx.bounds2img( @@ -264,14 +264,14 @@ def test_no_custom_headers(): use_cache=False, source=cx.providers.CartoDB.Positron ) - + # Verify requests.get was called assert mock_get.called - + # Verify that only the default user-agent header is present call_args = mock_get.call_args headers_used = call_args.kwargs.get('headers', call_args[1].get('headers')) - + # Should only have the user-agent header assert "user-agent" in headers_used assert headers_used["user-agent"].startswith("contextily-") @@ -287,27 +287,27 @@ def test_custom_user_agent_override(): -93.50721740722656, 36.49387741088867, ) - + # Create a mock image to return img_array = np.random.randint(0, 255, (256, 256, 4), dtype=np.uint8) img = Image.fromarray(img_array, mode='RGBA') img_bytes = io.BytesIO() img.save(img_bytes, format='PNG') img_bytes.seek(0) - + # Create mock response mock_response = MagicMock() mock_response.status_code = 200 mock_response.content = img_bytes.read() - + custom_user_agent = "MyCustomAgent/1.0" custom_headers = { "user-agent": custom_user_agent } - + with patch('contextily.tile.requests.get', return_value=mock_response) as mock_get: mock_get.return_value.raise_for_status = MagicMock() - + # Test bounds2img with custom user-agent header # Disable cache to ensure requests.get is actually called img, ext = cx.bounds2img( @@ -318,14 +318,14 @@ def test_custom_user_agent_override(): use_cache=False, source=cx.providers.CartoDB.Positron ) - + # Verify requests.get was called assert mock_get.called, "requests.get should have been called" - + # Verify that the custom user-agent was used, not the default call_args = mock_get.call_args headers_used = call_args.kwargs.get('headers', call_args[1].get('headers')) - + # Check that custom user-agent is present assert "user-agent" in headers_used assert headers_used["user-agent"] == custom_user_agent @@ -341,31 +341,31 @@ def test_place_with_custom_headers(): img_bytes = io.BytesIO() img.save(img_bytes, format='PNG') img_bytes.seek(0) - + # Create mock response mock_response = MagicMock() mock_response.status_code = 200 mock_response.content = img_bytes.read() - + custom_headers = { "X-API-Key": "test-api-key-789", } - + with patch('contextily.tile.requests.get', return_value=mock_response) as mock_get: mock_get.return_value.raise_for_status = MagicMock() - + # Create a Place with custom headers loc = cx.Place( SEARCH, zoom_adjust=ADJUST, headers=custom_headers, ) - + # Verify requests.get was called with correct headers assert mock_get.called call_args = mock_get.call_args headers_used = call_args.kwargs.get('headers', call_args[1].get('headers')) - + assert "X-API-Key" in headers_used assert headers_used["X-API-Key"] == "test-api-key-789" assert "user-agent" in headers_used @@ -379,19 +379,19 @@ def test_add_basemap_with_custom_headers(): img_bytes = io.BytesIO() img.save(img_bytes, format='PNG') img_bytes.seek(0) - + # Create mock response mock_response = MagicMock() mock_response.status_code = 200 mock_response.content = img_bytes.read() - + custom_headers = { "X-Custom-Auth": "custom-token", } - + with patch('contextily.tile.requests.get', return_value=mock_response) as mock_get: mock_get.return_value.raise_for_status = MagicMock() - + # Create a simple plot and add basemap with custom headers x1, x2, y1, y2 = [ -11740727.544603072, @@ -399,22 +399,22 @@ def test_add_basemap_with_custom_headers(): 4852834.0517692715, 4891969.810251278, ] - + fig, ax = matplotlib.pyplot.subplots(1) ax.set_xlim(x1, x2) ax.set_ylim(y1, y2) - + cx.add_basemap(ax, zoom=10, headers=custom_headers) - + # Verify requests.get was called with correct headers assert mock_get.called call_args = mock_get.call_args headers_used = call_args.kwargs.get('headers', call_args[1].get('headers')) - + assert "X-Custom-Auth" in headers_used assert headers_used["X-Custom-Auth"] == "custom-token" assert "user-agent" in headers_used - + matplotlib.pyplot.close(fig) @@ -422,30 +422,30 @@ def test_retryer_error_handling(): """Test error handling and retry logic in _retryer function.""" from contextily.tile import _retryer import requests - + # Test 404 error mock_response = MagicMock() mock_response.status_code = 404 mock_response.raise_for_status.side_effect = requests.HTTPError("404 Not Found") - + with patch('contextily.tile.requests.get', return_value=mock_response): with pytest.raises(requests.HTTPError) as exc_info: _retryer("http://example.com/tile.png", wait=0, max_retries=0, headers={}) - + assert "404 error" in str(exc_info.value) assert "http://example.com/tile.png" in str(exc_info.value) - + # Test retry exhaustion with non-404 error mock_response = MagicMock() mock_response.status_code = 503 mock_response.reason = "Service Unavailable" mock_response.url = "http://example.com/tile.png" mock_response.raise_for_status.side_effect = requests.HTTPError("503 Service Unavailable") - + with patch('contextily.tile.requests.get', return_value=mock_response): with pytest.raises(requests.HTTPError) as exc_info: _retryer("http://example.com/tile.png", wait=0, max_retries=0, headers={}) - + assert "Connection reset by peer too many times" in str(exc_info.value) assert "503" in str(exc_info.value) @@ -454,22 +454,22 @@ def test_retryer_with_retries(): """Test that _retryer actually retries when max_retries > 0 and passes headers.""" from contextily.tile import _retryer import requests - + # Test that retry logic is executed with proper headers mock_response = MagicMock() mock_response.status_code = 503 mock_response.reason = "Service Unavailable" mock_response.url = "http://example.com/tile.png" mock_response.raise_for_status.side_effect = requests.HTTPError("503") - + custom_headers = {"X-API-Key": "test-key"} - + with patch('contextily.tile.requests.get', return_value=mock_response) as mock_get: with patch('contextily.tile.time.sleep') as mock_sleep: # Should exhaust retries and raise exception with pytest.raises(requests.HTTPError) as exc_info: _retryer("http://example.com/tile.png", wait=1, max_retries=2, headers=custom_headers) - + # Verify sleep was called (indicating retry logic executed) assert mock_sleep.call_count == 2 # Verify each call to requests.get included the custom headers @@ -477,7 +477,7 @@ def test_retryer_with_retries(): headers_used = call.kwargs.get('headers', call[1].get('headers')) assert "X-API-Key" in headers_used assert headers_used["X-API-Key"] == "test-key" - + assert "Connection reset by peer too many times" in str(exc_info.value)