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.
- matplotlib_map_utils/__init__.py +16 -1
- matplotlib_map_utils/core/__init__.py +5 -1
- matplotlib_map_utils/core/inset_map.py +952 -0
- matplotlib_map_utils/core/north_arrow.py +0 -4
- matplotlib_map_utils/core/scale_bar.py +0 -6
- matplotlib_map_utils/defaults/inset_map.py +67 -0
- matplotlib_map_utils/validation/functions.py +51 -15
- matplotlib_map_utils/validation/inset_map.py +88 -0
- matplotlib_map_utils/validation/north_arrow.py +1 -1
- matplotlib_map_utils/validation/scale_bar.py +3 -5
- {matplotlib_map_utils-2.1.0.dist-info → matplotlib_map_utils-3.0.0.dist-info}/METADATA +115 -20
- matplotlib_map_utils-3.0.0.dist-info/RECORD +24 -0
- {matplotlib_map_utils-2.1.0.dist-info → matplotlib_map_utils-3.0.0.dist-info}/WHEEL +1 -1
- matplotlib_map_utils-2.1.0.dist-info/RECORD +0 -21
- {matplotlib_map_utils-2.1.0.dist-info → matplotlib_map_utils-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {matplotlib_map_utils-2.1.0.dist-info → matplotlib_map_utils-3.0.0.dist-info}/top_level.txt +0 -0
@@ -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
|
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
|
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
|
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
|
-
#
|
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
|
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
|
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
|
-
|
154
|
-
|
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
|
-
|
173
|
+
val = f(prop=prop, val=val, **k)
|
160
174
|
# If we pass, we can stop here and return the value
|
161
|
-
|
175
|
+
success = True
|
176
|
+
break
|
162
177
|
except:
|
163
|
-
|
164
|
-
|
165
|
-
|
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
|
-
#
|
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
|
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
|
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
|
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:
|
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
|
-
|
20
|
+

|
20
21
|
|
21
22
|
---
|
22
23
|
|
23
|
-
**Documentation
|
24
|
+
**Documentation:** See `docs` folder
|
24
25
|
|
25
|
-
**Source Code
|
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 `
|
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
|
-
|
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
|
-
|
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
|
-
|
50
|
+
Together, these allow for the easy creation of a map such as the following:
|
51
|
+
|
52
|
+

|
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
|
-
|
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="
|
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
|
-
###
|
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
|
+

|
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
|
+

|
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
|
-
- `
|
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 `
|
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,
|
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,
|
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
|
-
|
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,,
|