simplestmaps 1.4.0__tar.gz → 1.5.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: simplestmaps
3
- Version: 1.4.0
3
+ Version: 1.5.0
4
4
  Summary: Super simple on-liner maps in python.
5
5
  Home-page: http://github.com/fisadev/simplestmaps
6
6
  Author: Juan Pedro Fisanotti
@@ -49,7 +49,7 @@ Description: # Simplest Maps
49
49
 
50
50
  Here are a good set of examples that showcase (and document) all of the supported features:
51
51
 
52
- [Take a look at everything that's possible with so little code!](https://nbviewer.org/github/fisadev/simplestmaps/blob/f9ca71ff43f39ae201837a824d648d276bb1dd74/demo_and_docs.ipynb)
52
+ ## [Take a look at everything that's possible with so little code!](https://nbviewer.org/github/fisadev/simplestmaps/blob/59c84384463eb31eb827e2ec42cb40f2f502ff58/demo_and_docs.ipynb)
53
53
 
54
54
  Platform: UNKNOWN
55
55
  Classifier: Intended Audience :: Developers
@@ -41,4 +41,4 @@ And every element plotted into the map is also customizable (colors, fonts, size
41
41
 
42
42
  Here are a good set of examples that showcase (and document) all of the supported features:
43
43
 
44
- [Take a look at everything that's possible with so little code!](https://nbviewer.org/github/fisadev/simplestmaps/blob/f9ca71ff43f39ae201837a824d648d276bb1dd74/demo_and_docs.ipynb)
44
+ ## [Take a look at everything that's possible with so little code!](https://nbviewer.org/github/fisadev/simplestmaps/blob/59c84384463eb31eb827e2ec42cb40f2f502ff58/demo_and_docs.ipynb)
@@ -3,7 +3,7 @@ from setuptools import setup
3
3
 
4
4
  setup(
5
5
  name="simplestmaps",
6
- version="1.4.0",
6
+ version="1.5.0",
7
7
  description="Super simple on-liner maps in python.",
8
8
  long_description=open("README.md").read(),
9
9
  long_description_content_type="text/markdown",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: simplestmaps
3
- Version: 1.4.0
3
+ Version: 1.5.0
4
4
  Summary: Super simple on-liner maps in python.
5
5
  Home-page: http://github.com/fisadev/simplestmaps
6
6
  Author: Juan Pedro Fisanotti
@@ -49,7 +49,7 @@ Description: # Simplest Maps
49
49
 
50
50
  Here are a good set of examples that showcase (and document) all of the supported features:
51
51
 
52
- [Take a look at everything that's possible with so little code!](https://nbviewer.org/github/fisadev/simplestmaps/blob/f9ca71ff43f39ae201837a824d648d276bb1dd74/demo_and_docs.ipynb)
52
+ ## [Take a look at everything that's possible with so little code!](https://nbviewer.org/github/fisadev/simplestmaps/blob/59c84384463eb31eb827e2ec42cb40f2f502ff58/demo_and_docs.ipynb)
53
53
 
54
54
  Platform: UNKNOWN
55
55
  Classifier: Intended Audience :: Developers
@@ -7,55 +7,66 @@ import json
7
7
  import folium
8
8
 
9
9
 
10
- Coords = namedtuple("Coords", "lat lon")
10
+ Point = namedtuple("Point", "lat lon")
11
11
 
12
12
  # type -> converter function that receives instance of type and returns either a single (lat,lon)
13
13
  # tuple, or a sequence of (lat,lon) tuples
14
- coords_converters = {}
14
+ converters = {}
15
15
 
16
16
 
17
- def extract_coords(coords_source):
17
+ def all_numbers(sequence):
18
18
  """
19
- Extract lat lon coordinates from any possible known thing.
19
+ Is a sequence composed of just numbers?
20
20
  """
21
- if coords_source.__class__ in coords_converters:
22
- # a custom type for which we have a converter registered, use it
23
- converter = coords_converters[coords_source.__class__]
24
- return extract_coords(converter(coords_source))
25
- if hasattr(coords_source, "lat") and hasattr(coords_source, "lon"):
21
+ return all(
22
+ isinstance(x, (int, float)) and not isinstance(x, bool)
23
+ for x in sequence
24
+ )
25
+
26
+
27
+ def to_points(source):
28
+ """
29
+ Convert any possible thing to lat lon points and/or sequences of points.
30
+ """
31
+ while source.__class__ in converters:
32
+ # a custom type for which we have a converter registered, use it before trying to extract
33
+ # points from it
34
+ source = converters[source.__class__](source)
35
+
36
+ if isinstance(source, GeneratorType):
37
+ # traverse the generator and build a tuple with its components, that we can inspect and
38
+ # parse (so we can do things like decide how to parse it based on its length and the types
39
+ # of its items)
40
+ return to_points(tuple(source))
41
+ elif hasattr(source, "lat") and hasattr(source, "lon"):
26
42
  # a few known classes that have lat long attributes
27
- lat, lon = coords_source.lat, coords_source.lon
28
- elif hasattr(coords_source, "latitude") and hasattr(coords_source, "longitude"):
43
+ lat, lon = source.lat, source.lon
44
+ elif hasattr(source, "latitude") and hasattr(source, "longitude"):
29
45
  # a few known classes that have latitude longitude attributes, like some common geo
30
46
  # libs
31
- lat, lon = coords_source.latitude, coords_source.longitude
32
- elif hasattr(coords_source, "latitude_deg") and hasattr(coords_source, "longitude_deg"):
47
+ lat, lon = source.latitude, source.longitude
48
+ elif hasattr(source, "latitude_deg") and hasattr(source, "longitude_deg"):
33
49
  # a few known classes that have latitude_deg longitude_deg attributes, like the
34
50
  # locations from the orbit-predictor lib
35
- lat, lon = coords_source.latitude_deg, coords_source.longitude_deg
36
- elif isinstance(coords_source, (tuple, list)) and len(coords_source) == 1:
37
- return extract_coords(coords_source[0])
38
- elif isinstance(coords_source, (tuple, list)) and len(coords_source) in (2, 3):
39
- lat, lon = coords_source[:2]
51
+ lat, lon = source.latitude_deg, source.longitude_deg
52
+ elif isinstance(source, (tuple, list)):
53
+ if len(source) == 1:
54
+ # a sequence with a single object inside, so extract a point from it as if we just got
55
+ # that object alone
56
+ return to_points(source[0])
57
+ elif len(source) in (2, 3) and all_numbers(source):
58
+ # a sequence of 2 or 3 numbers, these must be coordinates, finally!
59
+ lat, lon = source[:2]
60
+ else:
61
+ # a sequence that doesn't look like coordinates but maybe a collection of points, try
62
+ # to extract all of them into a list
63
+ return [to_points(item) for item in source]
40
64
  else:
41
65
  raise ValueError(
42
- f"Can't guess the latitude and longitude from this object: {repr(coords_source)}"
66
+ f"Can't guess the latitude and longitude from this object: {repr(source)}"
43
67
  )
44
68
 
45
- return Coords(lat, lon)
46
-
47
-
48
- def extract_coords_sequence(coords_sequence):
49
- """
50
- Extract a sequence of lat lon coordinates from any possible known thing.
51
- """
52
- while coords_sequence.__class__ in coords_converters:
53
- # a custom type from which we need to extract the coordinates sequence
54
- converter = coords_converters[coords_sequence.__class__]
55
- coords_sequence = converter(coords_sequence)
56
-
57
- # a sequence from which we need to extract each coordinate
58
- return [extract_coords(coords) for coords in coords_sequence]
69
+ return Point(lat, lon)
59
70
 
60
71
 
61
72
  def auto_convert(custom_type, converter_function):
@@ -63,34 +74,106 @@ def auto_convert(custom_type, converter_function):
63
74
  Register a custom type to be automatically converted to single coordinates or sequences of
64
75
  coordinates, to be able to use them in the helper functions for map elements.
65
76
  """
66
- coords_converters[custom_type] = converter_function
77
+ converters[custom_type] = converter_function
67
78
 
68
79
 
69
80
  # types of things we can display on a map, with their attributes
70
- Marker = namedtuple("Marker", "coords popup")
71
- Dot = namedtuple("Dot", "coords color radius opacity border_color border_width popup")
72
- Label = namedtuple("Label", "coords text color size font opacity popup")
73
- Html = namedtuple("Html", "coords code popup")
74
- Line = namedtuple("Line", "coords_sequence color width opacity popup")
75
- Area = namedtuple("Area", "coords_sequence color opacity border_color border_width popup")
81
+ Marker = namedtuple("Marker", "point popup")
82
+ Dot = namedtuple("Dot", "point color radius opacity border_color border_width popup")
83
+ Label = namedtuple("Label", "point text color size font opacity popup")
84
+ Html = namedtuple("Html", "point code popup")
85
+ Line = namedtuple("Line", "points_sequence color width opacity popup")
86
+ Area = namedtuple("Area", "points_sequence color opacity border_color border_width popup")
76
87
  Geojson = namedtuple("Geojson", "path points_as lines_as areas_as")
77
88
 
78
89
 
79
- def marker(*coords, popup=None):
90
+ def extract_single_points(points_source):
91
+ """
92
+ Extract and iterate over single points that might be part of point sequences, or even trees of
93
+ points.
94
+
95
+ This function assumes that points_source can contain only two types of things: Point instances,
96
+ or sequences (lists/tuples/generators) containing any of both.
97
+
98
+ Something like [[point_a], [point_b, point_c, point_d], [[[point_e]], point_f]]
99
+ Will yield all of the Point instances, one by one, in order.
100
+ """
101
+ pending = [points_source]
102
+ while pending:
103
+ current = pending.pop(0)
104
+
105
+ if isinstance(current, Point):
106
+ yield current
107
+ elif isinstance(current, (list, tuple, GeneratorType)):
108
+ pending.extend(current)
109
+
110
+
111
+ def extract_points_sequences(points_source):
112
+ """
113
+ Extract and iterate over point sequences that might be themselves part of higher order
114
+ sequences, or even trees of sequences.
115
+
116
+ This function assumes that points_source can contain only two types of things: Point instances,
117
+ or sequences (lists/tuples/generators) containing any of both.
118
+
119
+ The only condition is that any sequence (list/tuple/generator) contains either all Point
120
+ instances, or all sequences. No mixing of the two element types inside the same sequence.
121
+
122
+ Something like [[point_a], [point_b, point_c, point_d], [[[point_e]]]]
123
+ Will yield three sequences:
124
+ [point_a]
125
+ [point_b, point_c, point_d]
126
+ [point_e]
127
+
128
+ While something like [point_a, [point_b, point_c]]
129
+ Will yield an error, as there's a sequence (the top level one) mixing types inside it.
130
+ """
131
+ pending = [points_source]
132
+ while pending:
133
+ current = pending.pop(0)
134
+
135
+ if isinstance(current, Point):
136
+ # we received a single point, not even in a sequence, just yield it as a sequence of
137
+ # one element
138
+ yield [current]
139
+ elif isinstance(current, (list, tuple, GeneratorType)):
140
+ # we have a sequence, but is it a sequence of points? or a sequence of other sequences?
141
+ if all(isinstance(item, Point) for item in current):
142
+ # current is a sequence of coords!
143
+ yield current
144
+ elif any(isinstance(item, Point) for item in current):
145
+ raise ValueError(
146
+ f"There's a sequence mixing points and other kinds of objects: {repr(current)}"
147
+ )
148
+ else:
149
+ # a sequence of sequences, or a sequence of broken things. Add each item to the
150
+ # pending queue and deal with them when popped
151
+ pending.extend(current)
152
+ else:
153
+ raise ValueError(
154
+ f"Can't guess a sequence of coordinates from this object: {repr(current)}"
155
+ )
156
+
157
+
158
+ def marker(*points_sources, popup=None):
80
159
  """
81
160
  Helper to easily build a Marker instance.
82
161
  """
83
- if not coords:
162
+ if not points_sources:
84
163
  return partial(marker, popup=popup)
85
164
 
86
- return Marker(extract_coords(coords), popup)
165
+ return [
166
+ Marker(point, popup)
167
+ for point in extract_single_points(to_points(points_sources))
168
+ ]
87
169
 
88
170
 
89
- def dot(*coords, color="blue", radius=3, opacity=1, border_color=None, border_width=0, popup=None):
171
+ def dot(*points_sources, color="blue", radius=3, opacity=1, border_color=None, border_width=0,
172
+ popup=None):
90
173
  """
91
174
  Helper to easily build a Dot instance.
92
175
  """
93
- if not coords:
176
+ if not points_sources:
94
177
  return partial(dot, color=color, radius=radius, opacity=opacity, border_color=border_color,
95
178
  border_width=border_width, popup=popup)
96
179
 
@@ -99,46 +182,58 @@ def dot(*coords, color="blue", radius=3, opacity=1, border_color=None, border_wi
99
182
  elif border_width == 0:
100
183
  border_width = 2
101
184
 
102
- return Dot(extract_coords(coords), color, radius, opacity, border_color, border_width, popup)
185
+ return [
186
+ Dot(point, color, radius, opacity, border_color, border_width, popup)
187
+ for point in extract_single_points(to_points(points_sources))
188
+ ]
103
189
 
104
190
 
105
- def label(*coords, text, color="blue", size=12, font="arial", opacity=1, popup=None):
191
+ def label(*points_sources, text, color="blue", size=12, font="arial", opacity=1, popup=None):
106
192
  """
107
193
  Helper to easily build a Label instance.
108
194
  """
109
- if not coords:
195
+ if not points_sources:
110
196
  return partial(label, text=text, color=color, size=size, font=font, opacity=opacity,
111
197
  popup=popup)
112
198
 
113
- return Label(extract_coords(coords), text, color, size, font, opacity, popup)
199
+ return [
200
+ Label(point, text, color, size, font, opacity, popup)
201
+ for point in extract_single_points(to_points(points_sources))
202
+ ]
114
203
 
115
204
 
116
- def html(*coords, code, popup=None):
205
+ def html(*points_sources, code, popup=None):
117
206
  """
118
207
  Helper to easily build a Html instance.
119
208
  """
120
- if not coords:
209
+ if not points_sources:
121
210
  return partial(html, code=code, popup=popup)
122
211
 
123
- return Html(extract_coords(coords), code, popup)
212
+ return [
213
+ Html(point, code, popup)
214
+ for point in extract_single_points(to_points(points_sources))
215
+ ]
124
216
 
125
217
 
126
- def line(coords_sequence=None, color="blue", width=2, opacity=1, popup=None):
218
+ def line(*points_sequences_sources, color="blue", width=2, opacity=1, popup=None):
127
219
  """
128
220
  Helper to easily build a Line instance.
129
221
  """
130
- if coords_sequence is None:
222
+ if not points_sequences_sources:
131
223
  return partial(line, color=color, width=width, opacity=opacity, popup=popup)
132
224
 
133
- return Line(extract_coords_sequence(coords_sequence), color, width, opacity, popup)
225
+ return [
226
+ Line(points_sequence, color, width, opacity, popup)
227
+ for points_sequence in extract_points_sequences(to_points(points_sequences_sources))
228
+ ]
134
229
 
135
230
 
136
- def area(coords_sequence=None, color="blue", opacity=0.5, border_color=None, border_width=0,
231
+ def area(*points_sequences_sources, color="blue", opacity=0.5, border_color=None, border_width=0,
137
232
  popup=None):
138
233
  """
139
234
  Helper to easily build an Area instance.
140
235
  """
141
- if coords_sequence is None:
236
+ if not points_sequences_sources:
142
237
  return partial(area, color=color, opacity=opacity, border_color=border_color,
143
238
  border_width=border_width, popup=popup)
144
239
 
@@ -147,8 +242,10 @@ def area(coords_sequence=None, color="blue", opacity=0.5, border_color=None, bor
147
242
  elif border_width == 0:
148
243
  border_width = 2
149
244
 
150
- return Area(extract_coords_sequence(coords_sequence), color, opacity, border_color,
151
- border_width, popup)
245
+ return [
246
+ Area(points_sequence, color, opacity, border_color, border_width, popup)
247
+ for points_sequence in extract_points_sequences(to_points(points_sequences_sources))
248
+ ]
152
249
 
153
250
 
154
251
  def geojson(path_or_data, points_as=marker, lines_as=line, areas_as=area):
@@ -204,35 +301,6 @@ def geojson(path_or_data, points_as=marker, lines_as=line, areas_as=area):
204
301
  )
205
302
 
206
303
 
207
- def pluralize(helper):
208
- """
209
- Make a helper able to work with multiple sources instead of just one.
210
- Example: use the 'label' helper to create a new 'labels' helper, that works with multiple
211
- points coordinates instead of just one point.
212
- """
213
- @wraps(helper)
214
- def plural_helper(sources, **kwargs):
215
- while sources.__class__ in coords_converters:
216
- # a custom type from which we need to extract the coordinates sequence
217
- converter = coords_converters[sources.__class__]
218
- sources = converter(sources)
219
-
220
- # a sequence from which we need to extract each coordinate
221
- for source in sources:
222
- yield helper(source, **kwargs)
223
-
224
- return plural_helper
225
-
226
-
227
- marker_set = pluralize(marker)
228
- dot_set = pluralize(dot)
229
- label_set = pluralize(label)
230
- html_set = pluralize(html)
231
- line_set = pluralize(line)
232
- area_set = pluralize(area)
233
- geojson_set = pluralize(geojson)
234
-
235
-
236
304
  def draw_map(*things, center=(0, 0), zoom=1.5, tiles="cartodbpositron"):
237
305
  """
238
306
  Draw a simple map with elements on it. Good known working tiles with folium 0.14:
@@ -242,7 +310,7 @@ def draw_map(*things, center=(0, 0), zoom=1.5, tiles="cartodbpositron"):
242
310
  More info on tiles:
243
311
  https://python-visualization.github.io/folium/latest/user_guide/raster_layers/tiles.html
244
312
  """
245
- center = extract_coords(center)
313
+ center = to_points(center)
246
314
 
247
315
  map_ = folium.Map(location=(center.lat, center.lon), zoom_start=zoom, attr=".", tiles=tiles)
248
316
 
@@ -260,13 +328,13 @@ def draw_map(*things, center=(0, 0), zoom=1.5, tiles="cartodbpositron"):
260
328
 
261
329
  if isinstance(thing, Marker):
262
330
  folium.Marker(
263
- location=[thing.coords.lat, thing.coords.lon],
331
+ location=[thing.point.lat, thing.point.lon],
264
332
  popup=thing.popup,
265
333
  ).add_to(map_)
266
334
 
267
335
  elif isinstance(thing, Dot):
268
336
  folium.CircleMarker(
269
- location=[thing.coords.lat, thing.coords.lon],
337
+ location=[thing.point.lat, thing.point.lon],
270
338
  radius=thing.radius,
271
339
  color=thing.border_color,
272
340
  weight=thing.border_width,
@@ -277,7 +345,7 @@ def draw_map(*things, center=(0, 0), zoom=1.5, tiles="cartodbpositron"):
277
345
 
278
346
  elif isinstance(thing, Label):
279
347
  folium.Marker(
280
- location=[thing.coords.lat, thing.coords.lon],
348
+ location=[thing.point.lat, thing.point.lon],
281
349
  icon=folium.DivIcon(html=f"""
282
350
  <div style="font-family: {thing.font};
283
351
  font-size: {thing.size}px;
@@ -291,15 +359,15 @@ def draw_map(*things, center=(0, 0), zoom=1.5, tiles="cartodbpositron"):
291
359
 
292
360
  elif isinstance(thing, Html):
293
361
  folium.Marker(
294
- location=[thing.coords.lat, thing.coords.lon],
362
+ location=[thing.point.lat, thing.point.lon],
295
363
  icon=folium.DivIcon(html=thing.code),
296
364
  popup=thing.popup,
297
365
  ).add_to(map_)
298
366
 
299
367
  elif isinstance(thing, Line):
300
368
  folium.PolyLine(
301
- locations=[(coords.lat, coords.lon)
302
- for coords in thing.coords_sequence],
369
+ locations=[(point.lat, point.lon)
370
+ for point in thing.points_sequence],
303
371
  color=thing.color,
304
372
  weight=thing.width,
305
373
  opacity=thing.opacity,
@@ -308,8 +376,8 @@ def draw_map(*things, center=(0, 0), zoom=1.5, tiles="cartodbpositron"):
308
376
 
309
377
  elif isinstance(thing, Area):
310
378
  folium.Polygon(
311
- locations=[(coords.lat, coords.lon)
312
- for coords in thing.coords_sequence],
379
+ locations=[(point.lat, point.lon)
380
+ for point in thing.points_sequence],
313
381
  color=thing.border_color,
314
382
  weight=thing.border_width,
315
383
  fill_color=thing.color,
File without changes
File without changes