matplotlib-map-utils 2.1.0__py3-none-any.whl → 3.0.0__py3-none-any.whl

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.
@@ -15,14 +15,10 @@ import numpy
15
15
  import cartopy
16
16
  import pyproj
17
17
  # Graphical packages
18
- import matplotlib
19
18
  import matplotlib.artist
20
- import matplotlib.pyplot
21
19
  import matplotlib.patches
22
20
  import matplotlib.patheffects
23
21
  import matplotlib.offsetbox
24
- # matplotlib's useful validation functions
25
- import matplotlib.rcsetup
26
22
  # The types we use in this script
27
23
  from typing import Literal
28
24
  # The information contained in our helper scripts (validation and defaults)
@@ -9,27 +9,21 @@
9
9
  import warnings
10
10
  import math
11
11
  import copy
12
- import re
13
12
  # Math packages
14
13
  import numpy
15
14
  # Geo packages
16
- import cartopy
17
15
  import pyproj
18
16
  from great_circle_calculator.great_circle_calculator import distance_between_points
19
17
  # Graphical packages
20
18
  import PIL.Image
21
- import matplotlib
22
19
  import matplotlib.artist
23
20
  import matplotlib.lines
24
21
  import matplotlib.pyplot
25
22
  import matplotlib.patches
26
23
  import matplotlib.patheffects
27
24
  import matplotlib.offsetbox
28
- import matplotlib.transforms
29
25
  import matplotlib.font_manager
30
26
  from matplotlib.backends.backend_agg import FigureCanvasAgg
31
- # matplotlib's useful validation functions
32
- import matplotlib.rcsetup
33
27
  # The types we use in this script
34
28
  from typing import Literal
35
29
  # The information contained in our helper scripts (validation and defaults)
@@ -0,0 +1,67 @@
1
+ #################################################################
2
+ # defaults/inset_map.py contains default values for the InsetMaps
3
+ # at difference plot sizes (xsmall to xlarge)
4
+ # see their corresponding sizes under each default heading
5
+ #################################################################
6
+
7
+ # The main variables that update are the following:
8
+ # inset map: size, pad
9
+ # labels: sep, style
10
+ # units: sep
11
+ # text: fontsize, stroke_width (also changes labels and units)
12
+ # aob: pad, borderpad
13
+
14
+ ## X-SMALL DEFAULTS
15
+ # Should work well for ~A8ish paper (2 to 3 inches, or 5 to 8 cm)
16
+
17
+ # Map
18
+ _INSET_MAP_XS = {
19
+ "size":0.5,
20
+ "pad":0.05,
21
+ }
22
+
23
+ ## SMALL DEFAULTS
24
+ # Should work well for ~A6 paper (4 to 6 inches, or 11 to 15 cm)
25
+
26
+ # Map
27
+ _INSET_MAP_SM = {
28
+ "size":1,
29
+ "pad":0.1,
30
+ }
31
+
32
+ ## MEDIUM DEFAULTS
33
+ # Should work well for ~A4/Letter paper (8 to 12 inches, or 21 to 30 cm)
34
+
35
+ # Map
36
+ _INSET_MAP_MD = {
37
+ "size":2,
38
+ "pad":0.25,
39
+ }
40
+
41
+ ## LARGE DEFAULTS
42
+ # Should work well for ~A2 paper (16 to 24 inches, or 42 to 60 cm)
43
+
44
+ # Map
45
+ _INSET_MAP_LG = {
46
+ "size":4,
47
+ "pad":0.5,
48
+ }
49
+
50
+ ## X-LARGE DEFAULTS
51
+ # Should work well for ~A0/Poster paper (33 to 47 inches, or 85 to 120 cm)
52
+
53
+ # Map
54
+ _INSET_MAP_XL = {
55
+ "size":8,
56
+ "pad":1,
57
+ }
58
+
59
+ ## CONTAINER
60
+ # This makes an easy-to-call dictionary of all the defaults we've set, for easy unpacking by the set_size function
61
+ _DEFAULTS_IM = {
62
+ "xs":[_INSET_MAP_XS],
63
+ "sm":[_INSET_MAP_SM],
64
+ "md":[_INSET_MAP_MD],
65
+ "lg":[_INSET_MAP_LG],
66
+ "xl":[_INSET_MAP_XL],
67
+ }
@@ -20,7 +20,7 @@ def _validate_list(prop, val, list, none_ok=False):
20
20
  raise ValueError(f"'{val}' is not a valid value for {prop}, please provide a value in this list: {list}")
21
21
  return val
22
22
 
23
- def _validate_range(prop, val, min, max, none_ok=False):
23
+ def _validate_range(prop, val, min, max=None, none_ok=False):
24
24
  if none_ok==False and val is None:
25
25
  raise ValueError(f"None is not a valid value for {prop}, please provide a value between {min} and {max}")
26
26
  elif none_ok==True and val is None:
@@ -69,7 +69,7 @@ def _validate_tuple(prop, val, length, types, none_ok=False):
69
69
  raise ValueError(f"None is not a valid value for {prop}, please provide a tuple of length {length} instead")
70
70
  elif none_ok==True and val is None:
71
71
  return val
72
- elif type(val)!=tuple:
72
+ elif not isinstance(val, (tuple, list)):
73
73
  raise ValueError(f"{val} is not a valid value for {prop}, please provide a tuple of length {length} instead")
74
74
  elif len(val)!=length:
75
75
  raise ValueError(f"{val} is not a valid value for {prop}, please provide a tuple of length {length} instead")
@@ -117,7 +117,7 @@ def _validate_crs(prop, val, rotation_dict, none_ok=False):
117
117
  try:
118
118
  val = pyproj.CRS.from_user_input(val)
119
119
  except:
120
- raise Exception(f"Invalid CRS supplied ({val}), please provide a valid CRS input for PyProj instead")
120
+ raise Exception(f"Invalid CRS supplied ({val}), please provide a valid CRS input that PyProj can use instead")
121
121
  return val
122
122
 
123
123
  # A simpler validation function for CRSs
@@ -128,14 +128,14 @@ def _validate_projection(prop, val, none_ok=False):
128
128
  try:
129
129
  val = pyproj.CRS.from_user_input(val)
130
130
  except:
131
- raise Exception(f"Invalid CRS supplied ({val}), please provide a valid CRS input for PyProj instead")
131
+ raise Exception(f"Invalid CRS supplied ({val}) for {prop}, please provide a valid CRS input that PyProj can use instead")
132
132
  return val
133
133
 
134
- # It is specifically to apply another validation function to the items in a list
134
+ # This is specifically to apply another validation function to the items in a list
135
135
  # Ex. if we want to validate a LIST of colors instead of a single color
136
136
  def _validate_iterable(prop, val, func, kwargs=None):
137
137
  # Making sure we wrap everything in a list
138
- if type(val) not in [tuple, list]:
138
+ if not isinstance(val, (tuple, list)):
139
139
  val = [val]
140
140
  # Then, we apply our validation func with optional kwargs to each item in said list, relying on it to return an error value
141
141
  if kwargs is not None:
@@ -148,21 +148,57 @@ def _validate_iterable(prop, val, func, kwargs=None):
148
148
  v = func(v)
149
149
  return val
150
150
 
151
- # This is specifically to apply multiple validation functions to a value, if needed
151
+ # This is to check for the structure of a dictionary-like object
152
+ def _validate_keys(prop, val, keys, none_ok=False):
153
+ if none_ok==False and val is None:
154
+ raise ValueError(f"None is not a valid value for {prop}, please provide a dictionary with keys {keys} instead")
155
+ elif none_ok==True and val is None:
156
+ return val
157
+ elif not isinstance(val, (dict)):
158
+ raise ValueError(f"{val} is not a valid value for {prop}, please provide a dictionary with keys {keys} instead")
159
+ else:
160
+ for k in val.keys():
161
+ if k not in keys:
162
+ raise ValueError(f"{k} is not a valid key for the items in {prop}, please provide a dictionary with keys {keys} instead")
163
+ return val
164
+
165
+ # This is to apply multiple validation functions to a value, if needed - only one needs to pass
152
166
  # Ex. If an item can be a string OR a list of strings, we can use this to validate it
153
- # "labels":{"func":_validate_multiple, "kwargs":{"funcs":[_validate_type, _validate_list], "kwargs":[{"match":str, "none_ok":True}, {"list":["major","first_last","minor_all","minor_first"], "none_ok":True}]}}, # any string or any item in the list
154
- def _validate_multiple(prop, val, funcs, kwargs):
167
+ def _validate_or(prop, val, funcs, kwargs):
168
+ success = False
155
169
  # Simply iterate through each func and kwarg
156
170
  for f,k in zip(funcs,kwargs):
157
171
  # We wrap the attempts in a try block to suppress the errors
158
172
  try:
159
- v = f(prop=prop, val=v, **k)
173
+ val = f(prop=prop, val=val, **k)
160
174
  # If we pass, we can stop here and return the value
161
- return val
175
+ success = True
176
+ break
162
177
  except:
163
- continue
164
- # If we didn't return a value and exit the loop yet, then the passed value is incorrect, as we raise an error
165
- raise ValueError(f"{val} is not a valid value for {prop}, please provide check the documentation")
178
+ pass
179
+ if success == False:
180
+ # If we didn't return a value and exit the loop yet, then the passed value is incorrect, as we raise an error
181
+ raise ValueError(f"{val} is not a valid value for {prop}, please check the documentation")
182
+ else:
183
+ return val
184
+
185
+ # This is the same, but ALL need to pass
186
+ def _validate_and(prop, val, funcs, kwargs):
187
+ success = True
188
+ # Simply iterate through each func and kwarg
189
+ for f,k in zip(funcs,kwargs):
190
+ # We wrap the attempts in a try block to suppress the errors
191
+ try:
192
+ val = f(prop=prop, val=val, **k)
193
+ except:
194
+ # If we fail, we can stop here and return the value
195
+ success = False
196
+ break
197
+ if success == False:
198
+ # If we didn't return a value and exit the loop yet, then the passed value is incorrect, as we raise an error
199
+ raise ValueError(f"{val} is not a valid value for {prop}, please check the documentation")
200
+ else:
201
+ return val
166
202
 
167
203
  # This final one is used for keys that are not validated
168
204
  def _skip_validation(val, none_ok=False):
@@ -221,7 +257,7 @@ def _validate_dict(input_dict, default_dict, functions, to_validate=None, return
221
257
  def _validate(validate_dict, prop, val, return_val=True, kwargs={}):
222
258
  fd = validate_dict[prop]
223
259
  func = fd["func"]
224
- # Our custom functions always have this dictionary key in them, so we know what form they take
260
+ # Most of our custom functions always have this dictionary key in them, so we know what form they take
225
261
  if "kwargs" in fd:
226
262
  val = func(prop=prop, val=val, **(fd["kwargs"] | kwargs))
227
263
  # The matplotlib built-in functions DON'T have that, and only ever take the one value
@@ -0,0 +1,88 @@
1
+ ############################################################
2
+ # validation/inset_map.py contains all the main objects
3
+ # for checking inputs passed to class definitions
4
+ ############################################################
5
+
6
+ ### IMPORTING PACKAGES ###
7
+
8
+ # Geo packages
9
+ import matplotlib.axes
10
+ import pyproj
11
+ # Graphical packages
12
+ import matplotlib
13
+ # matplotlib's useful validation functions
14
+ import matplotlib.rcsetup
15
+ # The types we use in this script
16
+ from typing import TypedDict, Literal
17
+ # Finally, the validation functions
18
+ from . import functions as vf
19
+
20
+ ### ALL ###
21
+ # This code tells other packages what to import if not explicitly stated
22
+ __all__ = [
23
+ "_TYPE_INSET", "_VALIDATE_INSET",
24
+ "_TYPE_EXTENT", "_VALIDATE_EXTENT",
25
+ "_TYPE_DETAIL", "_VALIDATE_DETAIL",
26
+ ]
27
+
28
+ ### TYPE HINTS ###
29
+ # This section of the code is for defining structured dictionaries and lists
30
+ # for the inputs necessary for object creation we've created (such as the style dictionaries)
31
+ # so that intellisense can help with autocompletion
32
+
33
+ class _TYPE_INSET(TypedDict, total=False):
34
+ size: int | float | tuple[int | float, int | float] | list[int | float, int | float] # each int or float should be between 0 and inf
35
+ pad: int | float | tuple[int | float, int | float] | list[int | float, int | float] # each int or float should be between 0 and inf
36
+ coords: tuple[int | float, int | float] | list[int | float, int | float] # each int or float should be between -inf and inf
37
+
38
+ class _TYPE_EXTENT(TypedDict, total=False):
39
+ pax: matplotlib.axes.Axes # any Matplotlib Axes
40
+ bax: matplotlib.axes.Axes # any Matplotlib Axes
41
+ pcrs: str | int | pyproj.CRS # should be a valid cartopy or pyproj crs, or a string or int that can be converted to that
42
+ bcrs: str | int | pyproj.CRS # should be a valid cartopy or pyproj crs, or a string or int that can be converted to that
43
+ straighten: bool # either true or false
44
+ pad: float | int # any positive float or integer
45
+ plot: bool # either true or false
46
+ to_return: Literal["shape","patch","fig","ax"] | None # any item in the list, or None if nothing should be returned
47
+ facecolor: str # a color to use for the face of the box
48
+ linecolor: str # a color to use for the edge of the box
49
+ alpha: float | int # any positive float or integer
50
+ linewidth: float | int # any positive float or integer
51
+
52
+ class _TYPE_DETAIL(TypedDict, total=False):
53
+ to_return: Literal["connectors", "lines"] | None # any item in the list, or None if nothing should be returned
54
+ connector_color: str # a color to use for the face of the box
55
+ connector_width: float | int # any positive float or integer
56
+
57
+ ### VALIDITY DICTS ###
58
+ # These compile the functions in validation/functions, as well as matplotlib's built-in validity functions
59
+ # into dictionaries that can be used to validate all the inputs to a dictionary at once
60
+
61
+ _VALIDATE_INSET = {
62
+ "location":{"func":vf._validate_list, "kwargs":{"list":["upper right", "upper left", "lower left", "lower right", "center left", "center right", "lower center", "upper center", "center"]}},
63
+ "size":{"func":vf._validate_or, "kwargs":{"funcs":[vf._validate_range, vf._validate_and], "kwargs":[{"min":0, "none_ok":True}, {"funcs":[vf._validate_tuple, vf._validate_iterable], "kwargs":[{"length":2, "types":[float, int]}, {"func":vf._validate_range, "kwargs":{"min":0}}]}]}}, # between 0 and inf, or a two-tuple of (x,y) size, each between 0 and inf
64
+ "pad":{"func":vf._validate_or, "kwargs":{"funcs":[vf._validate_range, vf._validate_and], "kwargs":[{"min":0, "none_ok":True}, {"funcs":[vf._validate_tuple, vf._validate_iterable], "kwargs":[{"length":2, "types":[float, int]}, {"func":vf._validate_range, "kwargs":{"min":0}}]}]}}, # between 0 and inf, or a two-tuple of (x,y) size, each between 0 and inf
65
+ "coords":{"func":vf._validate_tuple, "kwargs":{"length":2, "types":[float, int], "none_ok":True}}, # a two-tuple of coordinates where you want to place the inset map
66
+ "to_plot":{"func":vf._validate_iterable, "kwargs":{"func":vf._validate_keys, "kwargs":{"keys":["data","kwargs"], "none_ok":True}}}, # a list of dictionaries, where each contains "data" and "kwargs" keys
67
+ }
68
+
69
+ _VALIDATE_EXTENT = {
70
+ "pax":{"func":vf._validate_type, "kwargs":{"match":matplotlib.axes.Axes}}, # any Matplotlib Axes
71
+ "bax":{"func":vf._validate_type, "kwargs":{"match":matplotlib.axes.Axes}}, # any Matplotlib Axes
72
+ "pcrs":{"func":vf._validate_projection, "kwargs":{"none_ok":False}}, # any valid projection input for PyProj
73
+ "bcrs":{"func":vf._validate_projection, "kwargs":{"none_ok":False}}, # any valid projection input for PyProj
74
+ "straighten":{"func":vf._validate_type, "kwargs":{"match":bool}}, # true or false
75
+ "pad":{"func":vf._validate_range, "kwargs":{"min":0}}, # any positive number
76
+ "plot":{"func":vf._validate_type, "kwargs":{"match":bool}}, # true or false
77
+ "facecolor":{"func":matplotlib.rcsetup.validate_color}, # any color value for matplotlib
78
+ "linecolor":{"func":matplotlib.rcsetup.validate_color}, # any color value for matplotlib
79
+ "alpha":{"func":vf._validate_range, "kwargs":{"min":0}}, # any positive number
80
+ "linewidth":{"func":vf._validate_range, "kwargs":{"min":0}}, # any positive number
81
+ "to_return":{"func":vf._validate_list, "kwargs":{"list":["shape", "patch", "fig", "ax"], "none_ok":True}}, # any value in this list
82
+ }
83
+
84
+ _VALIDATE_DETAIL = {
85
+ "to_return":{"func":vf._validate_list, "kwargs":{"list":["connectors", "lines"], "none_ok":True}}, # any value in this list
86
+ "connector_color":{"func":matplotlib.rcsetup.validate_color}, # any color value for matplotlib
87
+ "connector_width":{"func":vf._validate_range, "kwargs":{"min":0}}, # any positive number
88
+ }
@@ -90,7 +90,7 @@ class _TYPE_ROTATION(TypedDict, total=False):
90
90
  coords: Tuple[float | int, float | int] # only required if degrees is None: should be a tuple of coordinates in the relevant reference window
91
91
 
92
92
  ### VALIDITY DICTS ###
93
- # These compile the functions above^, as well as matplotlib's built-in validity functions
93
+ # These compile the functions in validation/functions, as well as matplotlib's built-in validity functions
94
94
  # into dictionaries that can be used to validate all the inputs to a dictionary at once
95
95
 
96
96
  _VALIDATE_PRIMARY = {
@@ -7,12 +7,10 @@
7
7
 
8
8
  # Geo packages
9
9
  import pyproj
10
- # Graphical packages
11
- import matplotlib
12
10
  # matplotlib's useful validation functions
13
11
  import matplotlib.rcsetup
14
12
  # The types we use in this script
15
- from typing import Tuple, TypedDict, Literal, get_args
13
+ from typing import TypedDict, Literal, get_args
16
14
  # Finally, the validation functions
17
15
  from . import functions as vf
18
16
 
@@ -161,7 +159,7 @@ class _TYPE_AOB(TypedDict, total=False):
161
159
  # bbox_transform: None # NOTE: currently unvalidated, use at your own risk!
162
160
 
163
161
  ### VALIDITY DICTS ###
164
- # These compile the functions above^, as well as matplotlib's built-in validity functions
162
+ # These compile the functions in validation/functions, as well as matplotlib's built-in validity functions
165
163
  # into dictionaries that can be used to validate all the inputs to a dictionary at once
166
164
 
167
165
  _VALIDATE_PRIMARY = {
@@ -173,7 +171,7 @@ _VALID_BAR_TICK_LOC = get_args(_TYPE_BAR.__annotations__["tick_loc"])
173
171
  _VALID_BAR_MINOR_TYPE = get_args(_TYPE_BAR.__annotations__["minor_type"])
174
172
 
175
173
  _VALIDATE_BAR = {
176
- "projection":{"func":vf._validate_projection}, # must be a valid CRS
174
+ "projection":{"func":vf._validate_projection, "kwargs":{"none_ok":False}}, # must be a valid CRS
177
175
  "unit":{"func":vf._validate_list, "kwargs":{"list":list(units_standard.keys()), "none_ok":True}}, # any of the listed unit values are accepted
178
176
  "rotation":{"func":vf._validate_range, "kwargs":{"min":-360, "max":360, "none_ok":True}}, # between -360 and 360 degrees
179
177
  "max":{"func":vf._validate_range, "kwargs":{"min":0, "max":None, "none_ok":True}}, # between 0 and inf
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matplotlib-map-utils
3
- Version: 2.1.0
3
+ Version: 3.0.0
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/
@@ -8,6 +8,7 @@ Project-URL: Bug Tracker, https://github.com/moss-xyz/matplotlib-map-utils/issue
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: License :: OSI Approved :: GNU General Public License (GPL)
10
10
  Classifier: Operating System :: OS Independent
11
+ Classifier: Framework :: Matplotlib
11
12
  Requires-Python: >=3.10
12
13
  Description-Content-Type: text/markdown
13
14
  License-File: LICENSE
@@ -16,13 +17,15 @@ Requires-Dist: cartopy>=0.23.0
16
17
  Requires-Dist: great-circle-calculator>=1.3.1
17
18
  Dynamic: license-file
18
19
 
19
- # matplotlib-map-utils
20
+ ![matplotlib_map_utils logo](matplotlib_map_utils/docs/assets/mmu_logo_w_elements.png)
20
21
 
21
22
  ---
22
23
 
23
- **Documentation**: See `docs` folder
24
+ **Documentation:** See `docs` folder
24
25
 
25
- **Source Code**: [Available on GitHub](https://github.com/moss-xyz/matplotlib-map-utils)
26
+ **Source Code:** [Available on GitHub](https://github.com/moss-xyz/matplotlib-map-utils)
27
+
28
+ **Feedback:** I welcome any and all feedback! See the *Development Notes* below for more details.
26
29
 
27
30
  ---
28
31
 
@@ -30,15 +33,23 @@ Dynamic: license-file
30
33
 
31
34
  `matplotlib_map_utils` is intended to be a package that provides various functions and objects that assist with the the creation of maps using [`matplotlib`](https://matplotlib.org/stable/).
32
35
 
33
- As of `v2.x` (the current version), this includes two tools and one utility:
36
+ As of `v3.x` (the current version), this includes three-ish elements:
37
+
38
+ * `north_arrow.py`, for adding a north arrow to a given plot.
39
+
40
+ * `scale_bar.py`, for adding a scale bar to a given plot.
41
+
42
+ * `inset_map.py`, for adding inset maps and detail/extent indicators to a given plot.
34
43
 
35
- * `north_arrow.py`, which generates a high quality, context-aware north arrow for a given plot.
44
+ The three elements listed above are all intended to be high-resolution, easily modifiable, and context-aware, relative to your specific plot.
36
45
 
37
- * `scale_bar.py`, which generates a high quality, context-aware scale bar to a given plot.
46
+ This package also contains a single utility object:
38
47
 
39
48
  * `usa.py`, which contains a class that helps filter for states and territories within the USA based on given characteristics.
40
49
 
41
- Future releases (if the project is continued) might provide a similar tool inset maps, or other functions that I have created myself that give more control in the formatting of maps.
50
+ Together, these allow for the easy creation of a map such as the following:
51
+
52
+ ![Map with all common elements added](matplotlib_map_utils/docs/assets/readme_bigmap.png)
42
53
 
43
54
  ---
44
55
 
@@ -62,7 +73,8 @@ The requirements for this package are:
62
73
 
63
74
  ### Package Structure
64
75
 
65
- The package is arrayed in the following way:
76
+ <details>
77
+ <summary><i>The package is arrayed in the following way:</i></summary>
66
78
 
67
79
  ```bash
68
80
  package_name/
@@ -70,17 +82,20 @@ package_name/
70
82
 
71
83
  ├── core/
72
84
  │ ├── __init__.py
85
+ │ ├── inset_map.py
73
86
  │ ├── north_arrow.py
74
87
  │ ├── scale_bar.py
75
88
  ├── validation/
76
89
  │ ├── __init__.py
77
90
  │ ├── functions.py
91
+ │ └── inset_map.py
78
92
  │ ├── north_arrow.py
79
93
  │ └── scale_bar.py
80
94
  ├── defaults/
81
95
  │ ├── __init__.py
82
96
  │ ├── north_arrow.py
83
97
  │ └── scale_bar.py
98
+ │ └── inset_map.py
84
99
  ├── utils/
85
100
  │ ├── __init__.py
86
101
  │ ├── usa.py
@@ -95,9 +110,13 @@ Where:
95
110
 
96
111
  * `defaults` contains default settings for each object at different paper sizes
97
112
 
113
+ * `utils` contains utility functions and objects
114
+
115
+ </details>
116
+
98
117
  ---
99
118
 
100
- ### North Arrow
119
+ ### 🧭 North Arrow
101
120
 
102
121
  <details>
103
122
  <summary><i>Expand instructions</i></summary>
@@ -108,6 +127,8 @@ Importing the North Arrow functions and classes can be done like so:
108
127
 
109
128
  ```py
110
129
  from matplotlib_map_utils.core.north_arrow import NorthArrow, north_arrow
130
+ from matplotlib_map_utils.core import NorthArrow, north_arrow # also valid
131
+ from matplotlib_map_utils import NorthArrow, north_arrow # also valid
111
132
  ```
112
133
 
113
134
  The quickest way to add a single north arrow to a single plot is to use the `north_arrow` function:
@@ -171,7 +192,7 @@ Instructions for how to do so can be found in `docs\howto_north_arrow`.
171
192
 
172
193
  ---
173
194
 
174
- ### Scale Bar
195
+ ### 📏 Scale Bar
175
196
 
176
197
  <details>
177
198
  <summary><i>Expand instructions</i></summary>
@@ -182,6 +203,8 @@ Importing the Scale Bar functions and classes can be done like so:
182
203
 
183
204
  ```py
184
205
  from matplotlib_map_utils.core.scale_bar import ScaleBar, scale_bar
206
+ from matplotlib_map_utils.core import ScaleBar, scale_bar # also valid
207
+ from matplotlib_map_utils import ScaleBar, scale_bar # also valid
185
208
  ```
186
209
 
187
210
  There are two available styles for the scale bars: `boxes` and `ticks`. The quickest way to add one to a single plot is to use the `scale_bar` function:
@@ -201,7 +224,7 @@ An object-oriented approach is also supported:
201
224
  fig, ax = matplotlib.pyplot.subplots(1,1, figsize=(5,5), dpi=150)
202
225
  # Adding a scale bar to the upper-right corner of the axis, in the same projection as whatever geodata you plotted
203
226
  # Here, we change the boxes to "ticks"
204
- sb = ScaleBar(location="upper right", style="boxes", bar={"projection":3857})
227
+ sb = ScaleBar(location="upper right", style="ticks", bar={"projection":3857})
205
228
  # Adding the artist to the plot
206
229
  ax.add_artist(sb)
207
230
  ```
@@ -237,7 +260,67 @@ Refer to `docs\howto_scale_bar` for details on how to customize each facet of th
237
260
 
238
261
  ---
239
262
 
240
- ### Utilities
263
+ ### 🗺️ Inset Map
264
+
265
+ <details>
266
+ <summary><i>Expand instructions</i></summary>
267
+
268
+ #### Quick Start
269
+
270
+ Importing the Inset Map functions and classes can be done like so:
271
+
272
+ ```py
273
+ from matplotlib_map_utils.core.inset_map import InsetMap, inset_map, ExtentIndicator, indicate_extent, DetailIndicator, indicate_detail
274
+ from matplotlib_map_utils.core import InsetMap, inset_map, ExtentIndicator, indicate_extent, DetailIndicator, indicate_detail # also valid
275
+ from matplotlib_map_utils import InsetMap, inset_map, ExtentIndicator, indicate_extent, DetailIndicator, indicate_detail # also valid
276
+ ```
277
+
278
+ The quickest way to add a single inset map to an existing plot is the `inset_map` function:
279
+
280
+ ```python
281
+ # Setting up a plot
282
+ fig, ax = matplotlib.pyplot.subplots(1,1, figsize=(5,5), dpi=150)
283
+ # Adding an inset map to the upper-right corner of the axis
284
+ iax = inset_map(ax=ax, location="upper right", size=0.75, pad=0, xticks=[], yticks=[])
285
+ # You can now plot additional data to iax as desired
286
+ ```
287
+
288
+ An object-oriented approach is also supported:
289
+
290
+ ```python
291
+ # Setting up a plot
292
+ fig, ax = matplotlib.pyplot.subplots(1,1, figsize=(5,5), dpi=150)
293
+ # Creating an object for the inset map
294
+ im = InsetMap(location="upper right", size=0.75, pad=0, xticks=[], yticks=[])
295
+ # Adding the inset map template to the plot
296
+ iax = im.create(ax=ax)
297
+ # You can now plot additional data to iax as desired
298
+ ```
299
+
300
+ Both of these will create an output like the following:
301
+
302
+ ![Example inset map](matplotlib_map_utils/docs/assets/readme_insetmap.png)
303
+
304
+ #### Extent and Detail Indicators
305
+
306
+ Inset maps can be paired with either an extent or detail indicator, to provide additional geographic context to the inset map
307
+
308
+ ```python
309
+ indicate_extent(inset_axis, parent_axis, inset_crs, parent_crs, ...)
310
+ indicate_detail(parent_axis, inset_axis, parent_crs, inset_crs, ...)
311
+ ```
312
+
313
+ This will create an output like the following (extent indicator on the left, detail indicator on the right):
314
+
315
+ ![Customized scale bar](matplotlib_map_utils/docs/assets/readme_indicators.png)
316
+
317
+ Refer to `docs\howto_inset_map` for details on how to customize the inset map and indicators to your liking.
318
+
319
+ </details>
320
+
321
+ ---
322
+
323
+ ### 🛠️ Utilities
241
324
 
242
325
  <details>
243
326
  <summary><i>Expand instructions</i></summary>
@@ -283,23 +366,31 @@ Two more projects assisted with the creation of this script:
283
366
 
284
367
  #### Releases
285
368
 
286
- - `v2.0.1`: Fixed a bug in the `dual_bars()` function that prevented empty dictionaries to be passed. Also added a warning when auto-calculated bar widths appear to be exceeding the dimension of the axis (usually occurs when the axis is <2 kilometeres or miles long, depending on the units selected).
369
+ - `v1.0.x`: Initial releases featuring the North Arrow element, along with some minor bug fixes.
370
+
371
+ - `v2.0.0`: Initial release of the Scale Bar element.
372
+
373
+ - `v2.0.1`: Fixed a bug in the `dual_bars()` function that prevented empty dictionaries to be passed. Also added a warning when auto-calculated bar widths appear to be exceeding the dimension of the axis (usually occurs when the axis is <2 kilometers or miles long, depending on the units selected).
287
374
 
288
375
  - `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.
289
376
 
290
377
  - `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.
291
378
 
379
+ - `v3.0.0`: Release of inset map and extent and detail indicator classes and functions.
380
+
292
381
  #### Future Roadmap
293
382
 
294
- With the release of `v2.x`, and the addition of **Scale Bar** tools, this project has achieved the two main objectives that I set out to.
383
+ With the release of `v3.x`, this project has achieved full coverage of the "main" map elements I think are necessary.
295
384
 
296
385
  If I continue development of this project, I will be looking to add or fix the following features:
297
386
 
387
+ * For all: switch to a system based on Pydantic for easier type validation
388
+
298
389
  * **North Arrow:**
299
390
 
300
391
  * Copy the image-rendering functionality of the Scale Bar to allow for rotation of the entire object, label and arrow together
301
392
 
302
- * Create more styles for the arrow, potentiallly including a compass rose and a line-only arrow
393
+ * Create more styles for the arrow, potentially including a compass rose and a line-only arrow
303
394
 
304
395
  * **Scale Bar:**
305
396
 
@@ -309,7 +400,13 @@ If I continue development of this project, I will be looking to add or fix the f
309
400
 
310
401
  * Clean up the variable naming scheme (consistency on `loc` vs `position`, `style` vs `type`, etc.)
311
402
 
312
- * Create more styles for the bar, potentiallly including dual boxes and a sawtooth bar
403
+ * Create more styles for the bar, potentially including dual boxes and a sawtooth bar
404
+
405
+ * **Inset Map:**
406
+
407
+ * Clean up the way that connectors are drawn for detail indicators
408
+
409
+ * New functionality for placing multiple inset maps at once (with context-aware positioning to prevent overlap with each other)
313
410
 
314
411
  * **Utils:**
315
412
 
@@ -319,9 +416,7 @@ If I continue development of this project, I will be looking to add or fix the f
319
416
 
320
417
  * (USA): Stronger typing options, so you don't have to recall which `region` or `division` types are available, etc.
321
418
 
322
- If that goes well, `v3` can then either create a tool for generating inset maps (which `matplotlib` has *some* support for), or the various functions that I have created in the past that assist with formatting a map "properly", such as centering on a given object.
323
-
324
- I am also open to ideas for other extensions to create!
419
+ Future releases (if the project is continued) will probably focus on other functions that I have created myself that give more control in the formatting of maps. I am also open to ideas for other extensions to create!
325
420
 
326
421
  #### Support and Contributions
327
422
 
@@ -0,0 +1,24 @@
1
+ matplotlib_map_utils/__init__.py,sha256=2mL2sZOjfxC--EthFMTMuYh1uvozz-9PzM4EDprDicU,949
2
+ matplotlib_map_utils/core/__init__.py,sha256=mIn7x-LZlvNaYMcmjZXwnKNTirv3Vv2lAJodwRg_AdU,471
3
+ matplotlib_map_utils/core/inset_map.py,sha256=-lZxiORndgYQAZzAerPaMlH95zrRsBspdwxIzHcn_2Q,40880
4
+ matplotlib_map_utils/core/north_arrow.py,sha256=BXagxPdy0uCWpGTFvw-qnaZZKZlXsINTha0sqaBRK-M,22415
5
+ matplotlib_map_utils/core/scale_bar.py,sha256=qVJYUGnajEfW6FeQ37V_6tEHPbu4V-8B0K4XfU7c8YQ,62101
6
+ matplotlib_map_utils/defaults/__init__.py,sha256=_pegE5kv_sb0ansSF4XpWBRwboaP4zUjWY1KIGbK-TE,119
7
+ matplotlib_map_utils/defaults/inset_map.py,sha256=RNwaZqWjDjdNwPgmqx_cN9lQQ6DW_Db61peaeMRCPlc,1569
8
+ matplotlib_map_utils/defaults/north_arrow.py,sha256=uZb1RsUWxFTHywm8HATj_9iPF_GjCs_Z2HOn0JchjTY,8571
9
+ matplotlib_map_utils/defaults/scale_bar.py,sha256=GpXiWUHcOsv43G1HOfpqw-dzDPQQzQB7RNdtIf0e7Bc,8225
10
+ matplotlib_map_utils/scratch/map_utils.py,sha256=j8dOX9uuotl9rRCAXapFLHycUwVE4nzIrqWYOGG2Lgg,19653
11
+ matplotlib_map_utils/scratch/north_arrow_old_classes.py,sha256=1xKQ6yUghX4BWzIv8GsGBHDDPJ8B0Na7ixdw2jgtTqw,50993
12
+ matplotlib_map_utils/utils/__init__.py,sha256=uUy0kUMMGrDpvo88J_OLk2dQI-UwCXclccaEyk8x5R0,41
13
+ matplotlib_map_utils/utils/usa.json,sha256=kLB9JXNSWf8VU-9XwXuMRAPKO-zA4aluQUEln7Ktc_s,26563
14
+ matplotlib_map_utils/utils/usa.py,sha256=7SlUdxtCan5PFNIoLe-HfOC5r2cxJAF-9QKhNIK71EI,16853
15
+ matplotlib_map_utils/validation/__init__.py,sha256=0fL3N63jxjRwTU44b7-6ZYZJfOT_0ac7dx7M6Gpu_5M,52
16
+ matplotlib_map_utils/validation/functions.py,sha256=gh3d6Gj1klL8jb0nOwavIe3b9KFpOOCgQx1dYv-XMTw,13173
17
+ matplotlib_map_utils/validation/inset_map.py,sha256=g5YQByVnQBtRb_PI8xqghkJ0IGdBtYvwNwQz_-ddQ4U,5791
18
+ matplotlib_map_utils/validation/north_arrow.py,sha256=Vs9ljD0PbxBI7-4J8PkzC8SRI-LCgZDnIrL1xA6h77E,10366
19
+ matplotlib_map_utils/validation/scale_bar.py,sha256=XrnGoAXwJFUTOnvoei1pqVJKu-BxcAaih27H6a4CXgk,17625
20
+ matplotlib_map_utils-3.0.0.dist-info/licenses/LICENSE,sha256=aFLFZg6LEJFpTlNQ8su3__jw4GfV-xWBmC1cePkKZVw,35802
21
+ matplotlib_map_utils-3.0.0.dist-info/METADATA,sha256=OqqR0p5BphIlwUHJWH8iEP9mQlVfjtY_iTE6jBxlZSE,16354
22
+ matplotlib_map_utils-3.0.0.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
23
+ matplotlib_map_utils-3.0.0.dist-info/top_level.txt,sha256=6UyDpxsnMhSOd9a-abQe0lLJveybJyYtUHMdX7zXgKA,21
24
+ matplotlib_map_utils-3.0.0.dist-info/RECORD,,