matplotlib-map-utils 3.1.0__tar.gz → 3.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/PKG-INFO +10 -7
  2. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/README.md +9 -6
  3. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/core/scale_bar.py +68 -11
  4. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/defaults/scale_bar.py +31 -6
  5. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/validation/scale_bar.py +11 -1
  6. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils.egg-info/PKG-INFO +10 -7
  7. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/pyproject.toml +12 -1
  8. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/LICENSE +0 -0
  9. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/__init__.py +0 -0
  10. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/core/__init__.py +0 -0
  11. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/core/inset_map.py +0 -0
  12. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/core/north_arrow.py +0 -0
  13. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/defaults/__init__.py +0 -0
  14. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/defaults/inset_map.py +0 -0
  15. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/defaults/north_arrow.py +0 -0
  16. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/scratch/map_utils.py +0 -0
  17. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/scratch/north_arrow_old_classes.py +0 -0
  18. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/utils/__init__.py +0 -0
  19. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/utils/usa.json +0 -0
  20. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/utils/usa.py +0 -0
  21. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/validation/__init__.py +0 -0
  22. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/validation/functions.py +0 -0
  23. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/validation/inset_map.py +0 -0
  24. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils/validation/north_arrow.py +0 -0
  25. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils.egg-info/SOURCES.txt +0 -0
  26. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils.egg-info/dependency_links.txt +0 -0
  27. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils.egg-info/requires.txt +0 -0
  28. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/matplotlib_map_utils.egg-info/top_level.txt +0 -0
  29. {matplotlib_map_utils-3.1.0 → matplotlib_map_utils-3.1.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matplotlib-map-utils
3
- Version: 3.1.0
3
+ Version: 3.1.2
4
4
  Summary: A suite of tools for creating maps in matplotlib
5
5
  Author-email: David Moss <davidmoss1221@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/moss-xyz/matplotlib-map-utils/
@@ -179,7 +179,9 @@ This will create an output like the following:
179
179
 
180
180
  ![Customized north arrow](matplotlib_map_utils/docs/assets/readme_northarrow_customization.png)
181
181
 
182
- Refer to `docs\howto_north_arrow` for details on how to customize each facet of the north arrow.
182
+ Refer to `docs\howto_north_arrow` for details on how to customize each facet of the north arrow.
183
+
184
+ _Note: only add a north arrow **after** adding all of your geodata and changing your axis limits!_
183
185
 
184
186
  #### Rotation
185
187
 
@@ -260,6 +262,8 @@ This will create an output like the following:
260
262
 
261
263
  Refer to `docs\howto_scale_bar` for details on how to customize each facet of the scale bar.
262
264
 
265
+ _Note: only add a scale bar **after** adding all of your geodata and changing your axis limits!_
266
+
263
267
  #### Specifying Length
264
268
 
265
269
  There are three main ways of specifying the length of a scale bar:
@@ -291,7 +295,6 @@ All of the above cases expect a valid CRS to be supplied to the `projection` par
291
295
  - If `projection` is set to `dx`, `custom`, or `axis`, then values for `max` and `major_mult` are interpreted as being in _the units of the x or y axis_ (so a `max` of 1,000 will result in a bar equal to 1,000 units of the x-axis (if orientated horizontally))
292
296
 
293
297
  The intent of these additional methods is to provide an alternative interface for defining the bar, in the case of non-standard projections, or for non-cartographic use cases (in particular, this is inspired by the `dx` implementation of `matplotlib-scalebar`). However, this puts the onus on the user to know how big their bar should be - you also cannot pass a value to `unit` to convert! Note you can provide custom label text to the bar via the `labels` and `units` arguments (ex. if you need to label "inches" or something).
294
-
295
298
  </details>
296
299
 
297
300
  ---
@@ -351,7 +354,6 @@ This will create an output like the following (extent indicator on the left, det
351
354
  ![Customized scale bar](matplotlib_map_utils/docs/assets/readme_indicators.png)
352
355
 
353
356
  Refer to `docs\howto_inset_map` for details on how to customize the inset map and indicators to your liking.
354
-
355
357
  </details>
356
358
 
357
359
  ---
@@ -383,7 +385,6 @@ usa.filter(region=["South","Midwest"], to_return="name")
383
385
  ```
384
386
 
385
387
  Refer to `docs\howto_utils` for details on how to use this class, including with `pandas.apply()`.
386
-
387
388
  </details>
388
389
 
389
390
  ---
@@ -414,9 +415,7 @@ Two more projects assisted with the creation of this script:
414
415
  - `v2.0.2`: Changed f-string formatting to alternate double and single quotes, so as to maintain compatibility with versions of Python before 3.12 (see [here](https://github.com/moss-xyz/matplotlib-map-utils/issues/3)). However, this did reveal that another aspect of the code, namely concatenating `type` in function arguments, requires 3.10, and so the minimum python version was incremented.
415
416
 
416
417
  - `v2.1.0`: Added a utility class, `USA`, for filtering subsets of US states and territories based on FIPS code, name, abbreviation, region, division, and more. This is considered a beta release, and might be subject to change later on.
417
-
418
418
  </details>
419
- <br>
420
419
 
421
420
  - `v3.0.0`: Release of inset map and extent and detail indicator classes and functions.
422
421
 
@@ -424,6 +423,10 @@ Two more projects assisted with the creation of this script:
424
423
 
425
424
  - `v3.1.0`: Overhauled the functionality for specifying the the length of a scale bar, including support for custom units/projections (similar to `matplotlib-scalebar`'s `dx` argument) and to specify the length of a major division instead of the entire scale bar, as requested [here](https://github.com/moss-xyz/matplotlib-map-utils/issues/10). Added ability to set artist-level `zorder` variables for all elements, with both the function and class method approaches, as requested [here](https://github.com/moss-xyz/matplotlib-map-utils/issues/9) and [here](https://github.com/moss-xyz/matplotlib-map-utils/issues/10). Also fixed a bug related to custom division labels on the scale bar.
426
425
 
426
+ - `v3.1.1`: Fixed a bug that led to errors when creating a `scale_bar` at resolutions below 5km or 1 mile, due to a bug in the backend configuration functions (namely, `_config_bar_dim()`), which was fixed by correctly instantiating the necessary variable `ax_units` in other cases via an `else` statement (see [here](https://github.com/moss-xyz/matplotlib-map-utils/issues/14) for details).
427
+
428
+ - `v3.1.2`: Fixed a compatibility issue with [Ultraplot](https://github.com/Ultraplot/UltraPlot), primarily affecting the `ScaleBar` element, where text would rasterize at a low resolution (see [here](https://github.com/moss-xyz/matplotlib-map-utils/issues/16) and [here](https://github.com/moss-xyz/matplotlib-map-utils/pull/17) for details). A big thank you to cvanelteren on the Ultraplot team for identifying and implementing the necessary fixes, as well as making adjustments to the Ultraplot package to improve compatibility!
429
+
427
430
  #### Future Roadmap
428
431
 
429
432
  With the release of `v3.x`, this project has achieved full coverage of the "main" map elements I think are necessary.
@@ -160,7 +160,9 @@ This will create an output like the following:
160
160
 
161
161
  ![Customized north arrow](matplotlib_map_utils/docs/assets/readme_northarrow_customization.png)
162
162
 
163
- Refer to `docs\howto_north_arrow` for details on how to customize each facet of the north arrow.
163
+ Refer to `docs\howto_north_arrow` for details on how to customize each facet of the north arrow.
164
+
165
+ _Note: only add a north arrow **after** adding all of your geodata and changing your axis limits!_
164
166
 
165
167
  #### Rotation
166
168
 
@@ -241,6 +243,8 @@ This will create an output like the following:
241
243
 
242
244
  Refer to `docs\howto_scale_bar` for details on how to customize each facet of the scale bar.
243
245
 
246
+ _Note: only add a scale bar **after** adding all of your geodata and changing your axis limits!_
247
+
244
248
  #### Specifying Length
245
249
 
246
250
  There are three main ways of specifying the length of a scale bar:
@@ -272,7 +276,6 @@ All of the above cases expect a valid CRS to be supplied to the `projection` par
272
276
  - If `projection` is set to `dx`, `custom`, or `axis`, then values for `max` and `major_mult` are interpreted as being in _the units of the x or y axis_ (so a `max` of 1,000 will result in a bar equal to 1,000 units of the x-axis (if orientated horizontally))
273
277
 
274
278
  The intent of these additional methods is to provide an alternative interface for defining the bar, in the case of non-standard projections, or for non-cartographic use cases (in particular, this is inspired by the `dx` implementation of `matplotlib-scalebar`). However, this puts the onus on the user to know how big their bar should be - you also cannot pass a value to `unit` to convert! Note you can provide custom label text to the bar via the `labels` and `units` arguments (ex. if you need to label "inches" or something).
275
-
276
279
  </details>
277
280
 
278
281
  ---
@@ -332,7 +335,6 @@ This will create an output like the following (extent indicator on the left, det
332
335
  ![Customized scale bar](matplotlib_map_utils/docs/assets/readme_indicators.png)
333
336
 
334
337
  Refer to `docs\howto_inset_map` for details on how to customize the inset map and indicators to your liking.
335
-
336
338
  </details>
337
339
 
338
340
  ---
@@ -364,7 +366,6 @@ usa.filter(region=["South","Midwest"], to_return="name")
364
366
  ```
365
367
 
366
368
  Refer to `docs\howto_utils` for details on how to use this class, including with `pandas.apply()`.
367
-
368
369
  </details>
369
370
 
370
371
  ---
@@ -395,9 +396,7 @@ Two more projects assisted with the creation of this script:
395
396
  - `v2.0.2`: Changed f-string formatting to alternate double and single quotes, so as to maintain compatibility with versions of Python before 3.12 (see [here](https://github.com/moss-xyz/matplotlib-map-utils/issues/3)). However, this did reveal that another aspect of the code, namely concatenating `type` in function arguments, requires 3.10, and so the minimum python version was incremented.
396
397
 
397
398
  - `v2.1.0`: Added a utility class, `USA`, for filtering subsets of US states and territories based on FIPS code, name, abbreviation, region, division, and more. This is considered a beta release, and might be subject to change later on.
398
-
399
399
  </details>
400
- <br>
401
400
 
402
401
  - `v3.0.0`: Release of inset map and extent and detail indicator classes and functions.
403
402
 
@@ -405,6 +404,10 @@ Two more projects assisted with the creation of this script:
405
404
 
406
405
  - `v3.1.0`: Overhauled the functionality for specifying the the length of a scale bar, including support for custom units/projections (similar to `matplotlib-scalebar`'s `dx` argument) and to specify the length of a major division instead of the entire scale bar, as requested [here](https://github.com/moss-xyz/matplotlib-map-utils/issues/10). Added ability to set artist-level `zorder` variables for all elements, with both the function and class method approaches, as requested [here](https://github.com/moss-xyz/matplotlib-map-utils/issues/9) and [here](https://github.com/moss-xyz/matplotlib-map-utils/issues/10). Also fixed a bug related to custom division labels on the scale bar.
407
406
 
407
+ - `v3.1.1`: Fixed a bug that led to errors when creating a `scale_bar` at resolutions below 5km or 1 mile, due to a bug in the backend configuration functions (namely, `_config_bar_dim()`), which was fixed by correctly instantiating the necessary variable `ax_units` in other cases via an `else` statement (see [here](https://github.com/moss-xyz/matplotlib-map-utils/issues/14) for details).
408
+
409
+ - `v3.1.2`: Fixed a compatibility issue with [Ultraplot](https://github.com/Ultraplot/UltraPlot), primarily affecting the `ScaleBar` element, where text would rasterize at a low resolution (see [here](https://github.com/moss-xyz/matplotlib-map-utils/issues/16) and [here](https://github.com/moss-xyz/matplotlib-map-utils/pull/17) for details). A big thank you to cvanelteren on the Ultraplot team for identifying and implementing the necessary fixes, as well as making adjustments to the Ultraplot package to improve compatibility!
410
+
408
411
  #### Future Roadmap
409
412
 
410
413
  With the release of `v3.x`, this project has achieved full coverage of the "main" map elements I think are necessary.
@@ -35,6 +35,7 @@ from ..validation import functions as sbf
35
35
 
36
36
  _DEFAULT_BAR, _DEFAULT_LABELS, _DEFAULT_UNITS, _DEFAULT_TEXT, _DEFAULT_AOB = sbd._DEFAULTS_SB["md"]
37
37
 
38
+
38
39
  ### CLASSES ###
39
40
 
40
41
  class ScaleBar(matplotlib.artist.Artist):
@@ -205,9 +206,17 @@ class ScaleBar(matplotlib.artist.Artist):
205
206
  # THANK YOU to matplotlib-scalebar for figuring this out
206
207
  # Note that we never specify the renderer - the axis takes care of it!
207
208
  def draw(self, renderer, *args, **kwargs):
209
+ # Prefer renderer dpi for class-based artists so exports stay sharp
210
+ # when savefig(dpi=...) differs from the figure construction dpi.
211
+ _bar = copy.deepcopy(self._bar)
212
+ if _bar.get("raster_dpi", None) is None:
213
+ _raster_dpi, _ = _resolve_raster_dpi(
214
+ _bar, self.axes.get_figure(), renderer=renderer
215
+ )
216
+ _bar["raster_dpi"] = _raster_dpi
208
217
  # Can re-use the drawing function we already established, but return the object instead
209
218
  sb_artist = scale_bar(ax=self.axes, style=self._style, location=self._location, draw=False,
210
- bar=self._bar, units=self._units,
219
+ bar=_bar, units=self._units,
211
220
  labels=self._labels, text=self._text, aob=self._aob,
212
221
  zorder=self._zorder)
213
222
  # This handles the actual drawing
@@ -251,6 +260,22 @@ def scale_bar(ax, draw=True, style: Literal["ticks","boxes"]="boxes",
251
260
  aob: None | bool | sbt._TYPE_AOB=None,
252
261
  zorder: int=99,
253
262
  return_aob: bool=True,):
263
+ # For the default function mode, dispatch to the Artist class so final
264
+ # rasterization happens at draw-time with the active renderer dpi.
265
+ if draw == True and return_aob == True:
266
+ _ = ax.add_artist(
267
+ ScaleBar(
268
+ style=style,
269
+ location=location,
270
+ bar=bar,
271
+ units=units,
272
+ labels=labels,
273
+ text=text,
274
+ aob=aob,
275
+ zorder=zorder,
276
+ )
277
+ )
278
+ return
254
279
 
255
280
  ##### VALIDATION #####
256
281
  _style = sbf._validate(sbt._VALIDATE_PRIMARY, "style", style)
@@ -272,6 +297,12 @@ def scale_bar(ax, draw=True, style: Literal["ticks","boxes"]="boxes",
272
297
  _text = sbf._validate_dict(text, copy.deepcopy(_DEFAULT_TEXT), sbt._VALIDATE_TEXT, return_clean=True) # this one has to be a deepcopy due to dictionary immutability
273
298
  _aob = sbf._validate_dict(aob, _DEFAULT_AOB, sbt._VALIDATE_AOB, return_clean=True)
274
299
 
300
+ # Raster controls for the temporary rendered image.
301
+ # These are kept explicit so output quality is not coupled to external rc state.
302
+ _fig = ax.get_figure()
303
+ _raster_dpi, _raster_dpi_scale = _resolve_raster_dpi(_bar, _fig)
304
+ _raster_dpi = _raster_dpi * _raster_dpi_scale
305
+
275
306
  ##### CONFIGURING TEXT #####
276
307
  # First need to convert each string font size (if any) to a point size
277
308
  for d in [_text, _labels, _units]:
@@ -290,7 +321,7 @@ def scale_bar(ax, draw=True, style: Literal["ticks","boxes"]="boxes",
290
321
  # First, ensuring matplotlib knows the correct dimensions for everything
291
322
  # as we need it to be accurate to calculate out the plots!
292
323
  if draw:
293
- ax.get_figure().draw_without_rendering()
324
+ _fig.draw_without_rendering()
294
325
 
295
326
  # Getting the config for the bar (length, text, divs, etc.)
296
327
  bar_max, bar_length, units_label, major_div, minor_div = _config_bar(ax, _bar)
@@ -308,7 +339,7 @@ def scale_bar(ax, draw=True, style: Literal["ticks","boxes"]="boxes",
308
339
  units_label = _units["label"]
309
340
 
310
341
  # Creating a temporary figure and axis for rendering later
311
- fig_temp, ax_temp = _temp_figure(ax)
342
+ fig_temp, ax_temp = _temp_figure(ax, dpi=_raster_dpi)
312
343
 
313
344
  ##### BAR CONSTRUCTION #####
314
345
 
@@ -472,7 +503,14 @@ def scale_bar(ax, draw=True, style: Literal["ticks","boxes"]="boxes",
472
503
 
473
504
  # Placing the image in an OffsetBox, while rotating if desired
474
505
  # We have to set the zoom level to be relative to the DPI as well (image is in pixels)
475
- offset_img = matplotlib.offsetbox.OffsetImage(img_scale_bar, origin="upper", zoom=72/fig_temp.dpi)
506
+ offset_img = matplotlib.offsetbox.OffsetImage(
507
+ img_scale_bar,
508
+ origin="upper",
509
+ zoom=72/fig_temp.dpi,
510
+ interpolation=_bar.get("interpolation", "none"),
511
+ dpi_cor=_bar.get("dpi_cor", True),
512
+ resample=_bar.get("resample", False),
513
+ )
476
514
  # If desired, we can just return the rendered image in the final OffsetImage
477
515
  # This will override any aob or draw selections! Only the OffsetImage is returned!
478
516
  if return_aob==False:
@@ -771,7 +809,6 @@ def _config_bar_dim(ax, bar_vertical, bar_projection, bar_unit):
771
809
  units_user = None
772
810
 
773
811
  # Converting
774
-
775
812
  # First, the case where the user doesn't provide any units
776
813
  # In this instance, we just use the units from the projection
777
814
  if units_user is None:
@@ -785,6 +822,9 @@ def _config_bar_dim(ax, bar_vertical, bar_projection, bar_unit):
785
822
  elif units_proj == "ft" and ax_range > (5280*5):
786
823
  ax_units = ax_range / 5280
787
824
  units_label = "mi"
825
+ # Otherwise, if no scaling is necessary...
826
+ else:
827
+ ax_units = ax_range
788
828
 
789
829
  # Otherwise, if the user supplied a unit of some sort, then handle conversion
790
830
  else:
@@ -793,6 +833,9 @@ def _config_bar_dim(ax, bar_vertical, bar_projection, bar_unit):
793
833
  if units_user != units_proj:
794
834
  # This works by finding the ratios between the two units, using meters as the base
795
835
  ax_units = ax_range * (sbt.convert_dict[units_proj] / sbt.convert_dict[units_user])
836
+ # Otherwise, if the units are the same
837
+ else:
838
+ ax_units = ax_range
796
839
 
797
840
  return ax_inches, ax_units, units_label
798
841
 
@@ -1082,15 +1125,18 @@ def _format_numeric(val, fmt, integer_override=True):
1082
1125
  return f"{val:{fmt}}"
1083
1126
 
1084
1127
  # A small function for creating a temporary figure based on a provided axis
1085
- def _temp_figure(ax, axis=False, visible=False):
1128
+ def _temp_figure(ax, axis=False, visible=False, dpi=None):
1086
1129
  # Getting the figure of the provided axis
1087
1130
  fig = ax.get_figure()
1131
+ # If no dpi is passed, fall back to the figure dpi
1132
+ if dpi is None:
1133
+ dpi = fig.dpi
1088
1134
  # Getting the dimensions of the axis
1089
1135
  ax_bbox = ax.patch.get_window_extent()
1090
1136
  # Converting to inches and rounding up
1091
1137
  ax_dim = math.ceil(max(ax_bbox.height, ax_bbox.width) / fig.dpi)
1092
1138
  # Creating a new temporary figure
1093
- fig_temp, ax_temp = matplotlib.pyplot.subplots(1,1, figsize=(ax_dim*1.5, ax_dim*1.5), dpi=fig.dpi)
1139
+ fig_temp, ax_temp = matplotlib.pyplot.subplots(1,1, figsize=(ax_dim*1.5, ax_dim*1.5), dpi=dpi)
1094
1140
  # Turning off the x and y labels if desired
1095
1141
  if axis == False:
1096
1142
  ax_temp.axis("off")
@@ -1265,9 +1311,9 @@ def _render_as_image(fig, ax, artist, rotation, add=True, remove=True, close=Tru
1265
1311
  # If needed, adding the artist to the axis
1266
1312
  if add == True:
1267
1313
  ax.add_artist(artist)
1268
- # Draw the figure, but without showing it, to place all the elements
1269
- fig.draw_without_rendering()
1270
- # Sets the canvas for the figure to AGG (Anti-Grain Geometry)
1314
+ # Render directly with Agg; a prior draw_without_rendering() can override
1315
+ # temporary figure DPI in some wrappers (e.g., UltraPlot), which makes
1316
+ # raster_dpi ineffective.
1271
1317
  canvas = FigureCanvasAgg(fig)
1272
1318
  # Draws the figure onto the canvas
1273
1319
  canvas.draw()
@@ -1286,4 +1332,15 @@ def _render_as_image(fig, ax, artist, rotation, add=True, remove=True, close=Tru
1286
1332
  if close == True:
1287
1333
  matplotlib.pyplot.close(fig)
1288
1334
  # Returning the image
1289
- return img
1335
+ return img
1336
+
1337
+
1338
+ def _resolve_raster_dpi(bar, fig, renderer=None):
1339
+ """
1340
+ Resolve base raster DPI and raster scale for temporary rendering.
1341
+ """
1342
+ raster_dpi = bar.get("raster_dpi", None)
1343
+ if raster_dpi is None:
1344
+ raster_dpi = renderer.dpi if renderer is not None else fig.dpi
1345
+ raster_dpi_scale = bar.get("raster_dpi_scale", 1)
1346
+ return raster_dpi, raster_dpi_scale
@@ -35,7 +35,12 @@ _BAR_XS = {
35
35
  "tick_loc":"above",
36
36
  "basecolors":["black"],
37
37
  "tickcolors":["black"],
38
- "tickwidth":0.5 # changed
38
+ "tickwidth":0.5, # changed
39
+ "interpolation":"none",
40
+ "dpi_cor":True,
41
+ "resample":False,
42
+ "raster_dpi":None,
43
+ "raster_dpi_scale":1,
39
44
  }
40
45
 
41
46
  # Labels
@@ -107,7 +112,12 @@ _BAR_SM = {
107
112
  "tick_loc":"above",
108
113
  "basecolors":["black"],
109
114
  "tickcolors":["black"],
110
- "tickwidth":0.75 # changed
115
+ "tickwidth":0.75, # changed
116
+ "interpolation":"none",
117
+ "dpi_cor":True,
118
+ "resample":False,
119
+ "raster_dpi":None,
120
+ "raster_dpi_scale":1,
111
121
  }
112
122
 
113
123
  # Labels
@@ -179,7 +189,12 @@ _BAR_MD = {
179
189
  "tick_loc":"above",
180
190
  "basecolors":["black"],
181
191
  "tickcolors":["black"],
182
- "tickwidth":1.5 # changed
192
+ "tickwidth":1.5, # changed
193
+ "interpolation":"none",
194
+ "dpi_cor":True,
195
+ "resample":False,
196
+ "raster_dpi":None,
197
+ "raster_dpi_scale":1,
183
198
  }
184
199
 
185
200
  # Labels
@@ -251,7 +266,12 @@ _BAR_LG = {
251
266
  "tick_loc":"above",
252
267
  "basecolors":["black"],
253
268
  "tickcolors":["black"],
254
- "tickwidth":3 # changed
269
+ "tickwidth":3, # changed
270
+ "interpolation":"none",
271
+ "dpi_cor":True,
272
+ "resample":False,
273
+ "raster_dpi":None,
274
+ "raster_dpi_scale":1,
255
275
  }
256
276
 
257
277
  # Labels
@@ -323,7 +343,12 @@ _BAR_XL = {
323
343
  "tick_loc":"above",
324
344
  "basecolors":["black"],
325
345
  "tickcolors":["black"],
326
- "tickwidth":5 # changed
346
+ "tickwidth":5, # changed
347
+ "interpolation":"none",
348
+ "dpi_cor":True,
349
+ "resample":False,
350
+ "raster_dpi":None,
351
+ "raster_dpi_scale":1,
327
352
  }
328
353
 
329
354
  # Labels
@@ -379,4 +404,4 @@ _DEFAULTS_SB = {
379
404
  "md":[_BAR_MD, _LABELS_MD, _UNITS_MD, _TEXT_MD, _AOB_MD],
380
405
  "lg":[_BAR_LG, _LABELS_LG, _UNITS_LG, _TEXT_LG, _AOB_LG],
381
406
  "xl":[_BAR_XL, _LABELS_XL, _UNITS_XL, _TEXT_XL, _AOB_XL],
382
- }
407
+ }
@@ -99,6 +99,11 @@ class _TYPE_BAR(TypedDict, total=False):
99
99
  basecolors: list | tuple | str # a color or list of colors to use for the bottom bar
100
100
  tickcolors: list | tuple | str # a color or list of colors to use for the ticks
101
101
  tickwidth: float | int # the line thickness of the bottom bar and ticks
102
+ interpolation: str | None # interpolation method used by OffsetImage; e.g. "none", "nearest", "bilinear"
103
+ dpi_cor: bool # whether OffsetImage should be corrected for renderer dpi (matplotlib default behavior)
104
+ resample: bool # whether OffsetImage should use image resampling during scaling
105
+ raster_dpi: float | int | None # explicit dpi for temporary rasterization step, None uses figure/renderer dpi
106
+ raster_dpi_scale: float | int # multiplier applied to raster_dpi for supersampling
102
107
 
103
108
 
104
109
  class _TYPE_LABELS(TypedDict, total=False):
@@ -195,6 +200,11 @@ _VALIDATE_BAR = {
195
200
  "basecolors":{"func":vf._validate_iterable, "kwargs":{"func":matplotlib.rcsetup.validate_color}}, # ticks only: any color value for matplotlib
196
201
  "tickcolors":{"func":vf._validate_iterable, "kwargs":{"func":matplotlib.rcsetup.validate_color}}, # ticks only: any color value for matplotlib
197
202
  "tickwidth":{"func":vf._validate_range, "kwargs":{"min":0, "max":None, "none_ok":True}}, # ticks only: between 0 and inf
203
+ "interpolation":{"func":vf._validate_type, "kwargs":{"match":str, "none_ok":True}},
204
+ "dpi_cor":{"func":vf._validate_type, "kwargs":{"match":bool}},
205
+ "resample":{"func":vf._validate_type, "kwargs":{"match":bool}},
206
+ "raster_dpi":{"func":vf._validate_range, "kwargs":{"min":1, "max":None, "none_ok":True}},
207
+ "raster_dpi_scale":{"func":vf._validate_range, "kwargs":{"min":0.0001, "max":None, "none_ok":True}},
198
208
  }
199
209
 
200
210
  _VALID_LABELS_STYLE = get_args(_TYPE_LABELS.__annotations__["style"])
@@ -272,4 +282,4 @@ _VALIDATE_AOB = {
272
282
  "frameon":{"func":vf._validate_type, "kwargs":{"match":bool}}, # any bool
273
283
  "bbox_to_anchor":{"func":vf._skip_validation}, # NOTE: currently unvalidated, use at your own risk!
274
284
  "bbox_transform":{"func":vf._skip_validation}, # NOTE: currently unvalidated, use at your own risk!
275
- }
285
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matplotlib-map-utils
3
- Version: 3.1.0
3
+ Version: 3.1.2
4
4
  Summary: A suite of tools for creating maps in matplotlib
5
5
  Author-email: David Moss <davidmoss1221@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/moss-xyz/matplotlib-map-utils/
@@ -179,7 +179,9 @@ This will create an output like the following:
179
179
 
180
180
  ![Customized north arrow](matplotlib_map_utils/docs/assets/readme_northarrow_customization.png)
181
181
 
182
- Refer to `docs\howto_north_arrow` for details on how to customize each facet of the north arrow.
182
+ Refer to `docs\howto_north_arrow` for details on how to customize each facet of the north arrow.
183
+
184
+ _Note: only add a north arrow **after** adding all of your geodata and changing your axis limits!_
183
185
 
184
186
  #### Rotation
185
187
 
@@ -260,6 +262,8 @@ This will create an output like the following:
260
262
 
261
263
  Refer to `docs\howto_scale_bar` for details on how to customize each facet of the scale bar.
262
264
 
265
+ _Note: only add a scale bar **after** adding all of your geodata and changing your axis limits!_
266
+
263
267
  #### Specifying Length
264
268
 
265
269
  There are three main ways of specifying the length of a scale bar:
@@ -291,7 +295,6 @@ All of the above cases expect a valid CRS to be supplied to the `projection` par
291
295
  - If `projection` is set to `dx`, `custom`, or `axis`, then values for `max` and `major_mult` are interpreted as being in _the units of the x or y axis_ (so a `max` of 1,000 will result in a bar equal to 1,000 units of the x-axis (if orientated horizontally))
292
296
 
293
297
  The intent of these additional methods is to provide an alternative interface for defining the bar, in the case of non-standard projections, or for non-cartographic use cases (in particular, this is inspired by the `dx` implementation of `matplotlib-scalebar`). However, this puts the onus on the user to know how big their bar should be - you also cannot pass a value to `unit` to convert! Note you can provide custom label text to the bar via the `labels` and `units` arguments (ex. if you need to label "inches" or something).
294
-
295
298
  </details>
296
299
 
297
300
  ---
@@ -351,7 +354,6 @@ This will create an output like the following (extent indicator on the left, det
351
354
  ![Customized scale bar](matplotlib_map_utils/docs/assets/readme_indicators.png)
352
355
 
353
356
  Refer to `docs\howto_inset_map` for details on how to customize the inset map and indicators to your liking.
354
-
355
357
  </details>
356
358
 
357
359
  ---
@@ -383,7 +385,6 @@ usa.filter(region=["South","Midwest"], to_return="name")
383
385
  ```
384
386
 
385
387
  Refer to `docs\howto_utils` for details on how to use this class, including with `pandas.apply()`.
386
-
387
388
  </details>
388
389
 
389
390
  ---
@@ -414,9 +415,7 @@ Two more projects assisted with the creation of this script:
414
415
  - `v2.0.2`: Changed f-string formatting to alternate double and single quotes, so as to maintain compatibility with versions of Python before 3.12 (see [here](https://github.com/moss-xyz/matplotlib-map-utils/issues/3)). However, this did reveal that another aspect of the code, namely concatenating `type` in function arguments, requires 3.10, and so the minimum python version was incremented.
415
416
 
416
417
  - `v2.1.0`: Added a utility class, `USA`, for filtering subsets of US states and territories based on FIPS code, name, abbreviation, region, division, and more. This is considered a beta release, and might be subject to change later on.
417
-
418
418
  </details>
419
- <br>
420
419
 
421
420
  - `v3.0.0`: Release of inset map and extent and detail indicator classes and functions.
422
421
 
@@ -424,6 +423,10 @@ Two more projects assisted with the creation of this script:
424
423
 
425
424
  - `v3.1.0`: Overhauled the functionality for specifying the the length of a scale bar, including support for custom units/projections (similar to `matplotlib-scalebar`'s `dx` argument) and to specify the length of a major division instead of the entire scale bar, as requested [here](https://github.com/moss-xyz/matplotlib-map-utils/issues/10). Added ability to set artist-level `zorder` variables for all elements, with both the function and class method approaches, as requested [here](https://github.com/moss-xyz/matplotlib-map-utils/issues/9) and [here](https://github.com/moss-xyz/matplotlib-map-utils/issues/10). Also fixed a bug related to custom division labels on the scale bar.
426
425
 
426
+ - `v3.1.1`: Fixed a bug that led to errors when creating a `scale_bar` at resolutions below 5km or 1 mile, due to a bug in the backend configuration functions (namely, `_config_bar_dim()`), which was fixed by correctly instantiating the necessary variable `ax_units` in other cases via an `else` statement (see [here](https://github.com/moss-xyz/matplotlib-map-utils/issues/14) for details).
427
+
428
+ - `v3.1.2`: Fixed a compatibility issue with [Ultraplot](https://github.com/Ultraplot/UltraPlot), primarily affecting the `ScaleBar` element, where text would rasterize at a low resolution (see [here](https://github.com/moss-xyz/matplotlib-map-utils/issues/16) and [here](https://github.com/moss-xyz/matplotlib-map-utils/pull/17) for details). A big thank you to cvanelteren on the Ultraplot team for identifying and implementing the necessary fixes, as well as making adjustments to the Ultraplot package to improve compatibility!
429
+
427
430
  #### Future Roadmap
428
431
 
429
432
  With the release of `v3.x`, this project has achieved full coverage of the "main" map elements I think are necessary.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "matplotlib-map-utils"
7
- version = "3.1.0"
7
+ version = "3.1.2"
8
8
  authors = [
9
9
  { name="David Moss", email="davidmoss1221@gmail.com" },
10
10
  ]
@@ -29,6 +29,17 @@ exclude = ["matplotlib_map_utils.scratch*"]
29
29
  [tool.setuptools.package-data]
30
30
  "matplotlib_map_utils.utils" = ["*.json"]
31
31
 
32
+ [dependency-groups]
33
+ dev = [
34
+ "contextily>=1.6.2",
35
+ "geopandas>=1.1.1",
36
+ "ipykernel>=6.30.1",
37
+ "jupyter>=1.1.1",
38
+ "notebook>=7.4.7",
39
+ "pip>=25.2",
40
+ "pygris>=0.2.0",
41
+ ]
42
+
32
43
  [project.urls]
33
44
  "Homepage" = "https://github.com/moss-xyz/matplotlib-map-utils/"
34
45
  "Bug Tracker" = "https://github.com/moss-xyz/matplotlib-map-utils/issues"