lets-plot 4.8.1rc1__cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.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.
Files changed (97) hide show
  1. lets_plot/__init__.py +382 -0
  2. lets_plot/_global_settings.py +192 -0
  3. lets_plot/_kbridge.py +197 -0
  4. lets_plot/_type_utils.py +133 -0
  5. lets_plot/_version.py +6 -0
  6. lets_plot/bistro/__init__.py +16 -0
  7. lets_plot/bistro/_plot2d_common.py +106 -0
  8. lets_plot/bistro/corr.py +448 -0
  9. lets_plot/bistro/im.py +196 -0
  10. lets_plot/bistro/joint.py +192 -0
  11. lets_plot/bistro/qq.py +207 -0
  12. lets_plot/bistro/residual.py +341 -0
  13. lets_plot/bistro/waterfall.py +332 -0
  14. lets_plot/export/__init__.py +6 -0
  15. lets_plot/export/ggsave_.py +172 -0
  16. lets_plot/frontend_context/__init__.py +8 -0
  17. lets_plot/frontend_context/_configuration.py +140 -0
  18. lets_plot/frontend_context/_dynamic_configure_html.py +115 -0
  19. lets_plot/frontend_context/_frontend_ctx.py +16 -0
  20. lets_plot/frontend_context/_html_contexts.py +223 -0
  21. lets_plot/frontend_context/_intellij_python_json_ctx.py +38 -0
  22. lets_plot/frontend_context/_isolated_webview_panel_ctx.py +81 -0
  23. lets_plot/frontend_context/_json_contexts.py +39 -0
  24. lets_plot/frontend_context/_jupyter_notebook_ctx.py +82 -0
  25. lets_plot/frontend_context/_mime_types.py +7 -0
  26. lets_plot/frontend_context/_static_html_page_ctx.py +76 -0
  27. lets_plot/frontend_context/_static_svg_ctx.py +26 -0
  28. lets_plot/frontend_context/_webbr_html_page_ctx.py +29 -0
  29. lets_plot/frontend_context/sandbox.py +5 -0
  30. lets_plot/geo_data/__init__.py +19 -0
  31. lets_plot/geo_data/core.py +335 -0
  32. lets_plot/geo_data/geocoder.py +988 -0
  33. lets_plot/geo_data/geocodes.py +512 -0
  34. lets_plot/geo_data/gis/__init__.py +0 -0
  35. lets_plot/geo_data/gis/fluent_dict.py +201 -0
  36. lets_plot/geo_data/gis/geocoding_service.py +42 -0
  37. lets_plot/geo_data/gis/geometry.py +91 -0
  38. lets_plot/geo_data/gis/json_request.py +232 -0
  39. lets_plot/geo_data/gis/json_response.py +308 -0
  40. lets_plot/geo_data/gis/request.py +492 -0
  41. lets_plot/geo_data/gis/response.py +247 -0
  42. lets_plot/geo_data/livemap_helper.py +65 -0
  43. lets_plot/geo_data/to_geo_data_frame.py +141 -0
  44. lets_plot/geo_data/type_assertion.py +34 -0
  45. lets_plot/geo_data_internals/__init__.py +4 -0
  46. lets_plot/geo_data_internals/constants.py +13 -0
  47. lets_plot/geo_data_internals/utils.py +33 -0
  48. lets_plot/mapping.py +115 -0
  49. lets_plot/package_data/lets-plot.min.js +3 -0
  50. lets_plot/plot/__init__.py +64 -0
  51. lets_plot/plot/_global_theme.py +14 -0
  52. lets_plot/plot/annotation.py +290 -0
  53. lets_plot/plot/coord.py +242 -0
  54. lets_plot/plot/core.py +1071 -0
  55. lets_plot/plot/expand_limits_.py +78 -0
  56. lets_plot/plot/facet.py +210 -0
  57. lets_plot/plot/font_features.py +71 -0
  58. lets_plot/plot/geom.py +9146 -0
  59. lets_plot/plot/geom_extras.py +53 -0
  60. lets_plot/plot/geom_function_.py +219 -0
  61. lets_plot/plot/geom_imshow_.py +393 -0
  62. lets_plot/plot/geom_livemap_.py +343 -0
  63. lets_plot/plot/ggbunch_.py +96 -0
  64. lets_plot/plot/gggrid_.py +139 -0
  65. lets_plot/plot/ggtb_.py +81 -0
  66. lets_plot/plot/guide.py +231 -0
  67. lets_plot/plot/label.py +187 -0
  68. lets_plot/plot/marginal_layer.py +181 -0
  69. lets_plot/plot/plot.py +245 -0
  70. lets_plot/plot/pos.py +344 -0
  71. lets_plot/plot/sampling.py +338 -0
  72. lets_plot/plot/sandbox_.py +26 -0
  73. lets_plot/plot/scale.py +3580 -0
  74. lets_plot/plot/scale_colormap_mpl.py +300 -0
  75. lets_plot/plot/scale_convenience.py +155 -0
  76. lets_plot/plot/scale_identity_.py +653 -0
  77. lets_plot/plot/scale_position.py +1342 -0
  78. lets_plot/plot/series_meta.py +209 -0
  79. lets_plot/plot/stat.py +585 -0
  80. lets_plot/plot/subplots.py +331 -0
  81. lets_plot/plot/subplots_util.py +24 -0
  82. lets_plot/plot/theme_.py +790 -0
  83. lets_plot/plot/theme_set.py +418 -0
  84. lets_plot/plot/tooltip.py +486 -0
  85. lets_plot/plot/util.py +267 -0
  86. lets_plot/settings_utils.py +244 -0
  87. lets_plot/tilesets.py +429 -0
  88. lets_plot-4.8.1rc1.dist-info/METADATA +221 -0
  89. lets_plot-4.8.1rc1.dist-info/RECORD +97 -0
  90. lets_plot-4.8.1rc1.dist-info/WHEEL +6 -0
  91. lets_plot-4.8.1rc1.dist-info/licenses/LICENSE +21 -0
  92. lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.FreeType +166 -0
  93. lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.ImageMagick +106 -0
  94. lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.expat +21 -0
  95. lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.fontconfig +200 -0
  96. lets_plot-4.8.1rc1.dist-info/top_level.txt +2 -0
  97. lets_plot_kotlin_bridge.cpython-311-x86_64-linux-gnu.so +0 -0
@@ -0,0 +1,988 @@
1
+ # Copyright (c) 2020. JetBrains s.r.o.
2
+ # Use of this source code is governed by the MIT license that can be found in the LICENSE file.
3
+ from collections import namedtuple
4
+ from collections.abc import Iterable
5
+ from typing import Union, List, Optional, Dict
6
+
7
+ from pandas import Series
8
+
9
+ from .geocodes import _to_level_kind, request_types, Geocodes, _raise_exception, _ensure_is_list
10
+ from .gis.geocoding_service import GeocodingService
11
+ from .gis.geometry import GeoRect, GeoPoint
12
+ from .gis.request import RequestBuilder, GeocodingRequest, RequestKind, MapRegion, AmbiguityResolver, \
13
+ RegionQuery, LevelKind, IgnoringStrategyKind, PayloadKind, ReverseGeocodingRequest
14
+ from .gis.response import Response, SuccessResponse
15
+ from .type_assertion import assert_list_type
16
+
17
+ NAMESAKE_MAX_COUNT = 10
18
+
19
+ ShapelyPointType = 'shapely.geometry.Point'
20
+ ShapelyPolygonType = 'shapely.geometry.Polygon'
21
+
22
+ QuerySpec = namedtuple('QuerySpec', 'name, county, state, country')
23
+ WhereSpec = namedtuple('WhereSpec', 'scope, ambiguity_resolver')
24
+
25
+ parent_types = Optional[Union[str, Geocodes, 'Geocoder', MapRegion, List]] # list of same types
26
+ scope_types = Optional[Union[str, Geocodes, 'Geocoder', ShapelyPolygonType]]
27
+
28
+
29
+ def _to_scope(location: scope_types) -> Optional[Union[List[MapRegion], MapRegion]]:
30
+ if location is None:
31
+ return None
32
+
33
+ def _make_region(obj: Union[str, Geocodes]) -> Optional[MapRegion]:
34
+ if isinstance(obj, Geocodes):
35
+ return MapRegion.scope(obj.unique_ids())
36
+
37
+ if isinstance(obj, str):
38
+ return MapRegion.with_name(obj)
39
+
40
+ raise ValueError('Unsupported scope type. Expected Geocoder, str or list, but was `{}`'.format(type(obj)))
41
+
42
+ if isinstance(location, list):
43
+ return [_make_region(obj) for obj in location]
44
+
45
+ return _make_region(location)
46
+
47
+
48
+ class LazyShapely:
49
+ @staticmethod
50
+ def is_point(p) -> bool:
51
+ if not LazyShapely._is_shapely_available():
52
+ return False
53
+
54
+ from shapely.geometry import Point
55
+ return isinstance(p, Point)
56
+
57
+ @staticmethod
58
+ def is_polygon(p):
59
+ if not LazyShapely._is_shapely_available():
60
+ return False
61
+
62
+ from shapely.geometry import Polygon
63
+ return isinstance(p, Polygon)
64
+
65
+ @staticmethod
66
+ def _is_shapely_available():
67
+ try:
68
+ import shapely
69
+ return True
70
+ except ImportError:
71
+ return False
72
+
73
+
74
+ def _make_ambiguity_resolver(ignoring_strategy: Optional[IgnoringStrategyKind] = None,
75
+ scope: Optional[ShapelyPolygonType] = None,
76
+ closest_object: Optional[Union[Geocodes, ShapelyPointType]] = None):
77
+ if LazyShapely.is_polygon(scope):
78
+ rect = GeoRect(start_lon=scope.bounds[0], min_lat=scope.bounds[1], end_lon=scope.bounds[2],
79
+ max_lat=scope.bounds[3])
80
+ elif scope is None:
81
+ rect = None
82
+ else:
83
+ assert scope is not None # else for empty scope - existing scope should be already handled
84
+ raise ValueError('Wrong type of parameter `scope` - expected `shapely.geometry.Polygon`, but was `{}`'.format(
85
+ type(scope).__name__))
86
+
87
+ return AmbiguityResolver(
88
+ ignoring_strategy=ignoring_strategy,
89
+ closest_coord=_to_geo_point(closest_object),
90
+ box=rect
91
+ )
92
+
93
+
94
+ def _to_geo_point(closest_place: Optional[Union[Geocodes, ShapelyPointType]]) -> Optional[GeoPoint]:
95
+ if closest_place is None:
96
+ return None
97
+
98
+ if isinstance(closest_place, Geocoder):
99
+ closest_place = closest_place._geocode()
100
+
101
+ if isinstance(closest_place, Geocodes):
102
+ closest_place_id = closest_place.as_list()[0].unique_ids()
103
+ assert len(closest_place_id) == 1
104
+
105
+ request = RequestBuilder() \
106
+ .set_request_kind(RequestKind.explicit) \
107
+ .set_requested_payload([PayloadKind.centroids]) \
108
+ .set_ids(closest_place_id) \
109
+ .build()
110
+
111
+ response: Response = GeocodingService().do_request(request)
112
+ if isinstance(response, SuccessResponse):
113
+ assert len(response.features) == 1
114
+ centroid = response.features[0].centroid
115
+ return GeoPoint(lon=centroid.lon, lat=centroid.lat)
116
+ else:
117
+ raise ValueError("Unexpected geocoding response for id " + str(closest_place_id[0]))
118
+
119
+ if LazyShapely.is_point(closest_place):
120
+ return GeoPoint(lon=closest_place.x, lat=closest_place.y)
121
+
122
+ raise ValueError('Not supported type: {}'.format(type(closest_place)))
123
+
124
+
125
+ def _get_or_none(list, index):
126
+ if index >= len(list):
127
+ return None
128
+ return list[index]
129
+
130
+
131
+ def _ensure_is_parent_list(obj):
132
+ if obj is None:
133
+ return None
134
+
135
+ if isinstance(obj, Geocoder):
136
+ obj = obj._geocode()
137
+
138
+ if isinstance(obj, Geocodes):
139
+ return obj.as_list()
140
+
141
+ if isinstance(obj, Iterable) and not isinstance(obj, str):
142
+ return [v for v in obj]
143
+
144
+ return [obj]
145
+
146
+
147
+ def _make_parents(values: parent_types) -> List[Optional[MapRegion]]:
148
+ values = _ensure_is_parent_list(values)
149
+
150
+ if values is None:
151
+ return []
152
+
153
+ return list(map(lambda v: _make_parent_region(v) if values is not None else None, values))
154
+
155
+
156
+ def _make_parent_region(place: parent_types) -> Optional[MapRegion]:
157
+ if place is None:
158
+ return None
159
+
160
+ if isinstance(place, Geocoder):
161
+ place = place._geocode()
162
+
163
+ if isinstance(place, str):
164
+ return MapRegion.with_name(place)
165
+
166
+ if isinstance(place, Geocodes):
167
+ assert len(place.to_map_regions()) == 1, 'Region object used as parent should contain only single record'
168
+ return place.to_map_regions()[0]
169
+
170
+ raise ValueError('Unsupported parent type: ' + str(type(place)))
171
+
172
+
173
+ class Geocoder:
174
+ """
175
+ Do not use this class explicitly.
176
+
177
+ Instead you should construct its objects with special functions:
178
+ `geocode() <https://lets-plot.org/python/pages/api/lets_plot.geo_data.geocode.html>`__,
179
+ `geocode_cities() <https://lets-plot.org/python/pages/api/lets_plot.geo_data.geocode_cities.html>`__,
180
+ `geocode_counties() <https://lets-plot.org/python/pages/api/lets_plot.geo_data.geocode_counties.html>`__,
181
+ `geocode_states() <https://lets-plot.org/python/pages/api/lets_plot.geo_data.geocode_states.html>`__,
182
+ `geocode_countries() <https://lets-plot.org/python/pages/api/lets_plot.geo_data.geocode_countries.html>`__,
183
+ ``reverse_geocode()``.
184
+ """
185
+
186
+ def __init__(self):
187
+ """Initialize self."""
188
+
189
+ self._inc_res = 0
190
+
191
+ def get_limits(self) -> 'GeoDataFrame':
192
+ """
193
+ Return bboxes (Polygon geometry) for given regions in form of ``GeoDataFrame``.
194
+ For regions intersecting anti-meridian bbox will be divided into two parts
195
+ and stored as two rows.
196
+
197
+ Returns
198
+ -------
199
+ ``GeoDataFrame``
200
+ Table of data.
201
+
202
+ Examples
203
+ --------
204
+ .. jupyter-execute::
205
+ :linenos:
206
+ :emphasize-lines: 5
207
+
208
+ from IPython.display import display
209
+ from lets_plot import *
210
+ from lets_plot.geo_data import *
211
+ LetsPlot.setup_html()
212
+ countries = geocode_countries(['Germany', 'Poland']).get_limits()
213
+ display(countries)
214
+ ggplot() + geom_rect(aes(fill='found name'), data=countries, color='white')
215
+
216
+ """
217
+ return self._geocode().limits()
218
+
219
+ def get_centroids(self) -> 'GeoDataFrame':
220
+ """
221
+ Return centroids (Point geometry) for given regions in form of ``GeoDataFrame``.
222
+
223
+ Returns
224
+ -------
225
+ ``GeoDataFrame``
226
+ Table of data.
227
+
228
+ Examples
229
+ --------
230
+ .. jupyter-execute::
231
+ :linenos:
232
+ :emphasize-lines: 5
233
+
234
+ from IPython.display import display
235
+ from lets_plot import *
236
+ from lets_plot.geo_data import *
237
+ LetsPlot.setup_html()
238
+ countries = geocode_countries(['Germany', 'Poland']).get_centroids()
239
+ display(countries)
240
+ ggplot() + geom_point(aes(color='found name'), data=countries, size=10)
241
+
242
+ """
243
+ return self._geocode().centroids()
244
+
245
+ def get_boundaries(self, resolution=None) -> 'GeoDataFrame':
246
+ """
247
+ Return boundaries for given regions in the form of ``GeoDataFrame``.
248
+
249
+ Parameters
250
+ ----------
251
+ resolution : int or str
252
+ Boundaries resolution.
253
+
254
+ Returns
255
+ -------
256
+ ``GeoDataFrame``
257
+ Table of data.
258
+
259
+ Notes
260
+ -----
261
+ If ``resolution`` has int type, it may take one of the following values:
262
+
263
+ - 1-3 for world scale view,
264
+ - 4-6 for country scale view,
265
+ - 7-9 for state scale view,
266
+ - 10-12 for county scale view,
267
+ - 13-15 for city scale view.
268
+
269
+ Here value 1 corresponds to maximum performance and 15 - to maximum quality.
270
+
271
+ If ``resolution`` is of str type, it may take one of the following values:
272
+
273
+ - 'world' corresponds to int value 2,
274
+ - 'country' corresponds to int value 5,
275
+ - 'state' corresponds to int value 8,
276
+ - 'county' corresponds to int value 11,
277
+ - 'city' corresponds to int value 14.
278
+
279
+ Here value 'world' corresponds to maximum performance and 'city' - to maximum quality.
280
+
281
+ The resolution choice depends on the type of displayed area.
282
+ The number of objects also matters: one state looks good on a 'state' scale
283
+ while 50 states is a 'country' view.
284
+
285
+ It is allowed to use any resolution for all regions.
286
+ For example, 'city' scale can be used for a state to get a more detailed boundary
287
+ when zooming in, or 'world' for a small preview.
288
+
289
+ If ``resolution`` is not specified (or equal to None), it will be auto-detected.
290
+ Auto-detection by level_kind is used for geocoding and the number of objects.
291
+ In this case performance is preferred over quality.
292
+ The pixelated geometries can be obtained.
293
+ Use explicit resolution or ``inc_res()`` function for better quality.
294
+
295
+ If the number of objects is equal to n, then ``resolution`` will be the following:
296
+
297
+ - For countries: if n < 3 then ``resolution=3``, else ``resolution=1``.
298
+ - For states: if n < 3 then ``resolution=7``, if n < 10 then ``resolution=4``, else ``resolution=2``.
299
+ - For counties: if n < 5 then ``resolution=10``, if n < 20 then ``resolution=8``, else ``resolution=3``.
300
+ - For cities: if n < 5 then ``resolution=13``, if n < 50 then ``resolution=4``, else ``resolution=3``.
301
+
302
+ Examples
303
+ --------
304
+ .. jupyter-execute::
305
+ :linenos:
306
+ :emphasize-lines: 5
307
+
308
+ from IPython.display import display
309
+ from lets_plot import *
310
+ from lets_plot.geo_data import *
311
+ LetsPlot.setup_html()
312
+ countries = geocode_countries(['Germany', 'Poland']).inc_res().get_boundaries()
313
+ display(countries)
314
+ ggplot() + geom_map(aes(fill='found name'), data=countries, color='white')
315
+
316
+ """
317
+ return self._geocode().boundaries(resolution, self._inc_res)
318
+
319
+ def get_geocodes(self) -> 'DataFrame':
320
+ """
321
+ Return metadata for given regions.
322
+
323
+ Returns
324
+ -------
325
+ ``DataFrame``
326
+ Table of data.
327
+
328
+ Examples
329
+ --------
330
+ .. jupyter-execute::
331
+ :linenos:
332
+ :emphasize-lines: 2
333
+
334
+ from lets_plot.geo_data import *
335
+ geocode_countries(['Germany', 'Russia']).get_geocodes()
336
+
337
+ """
338
+ return self._geocode().to_data_frame()
339
+
340
+ def inc_res(self, delta=2):
341
+ """
342
+ Increase auto-detected resolution for boundaries.
343
+
344
+ Parameters
345
+ ----------
346
+ delta : int, default=2
347
+ Value that will be added to auto-detected resolution.
348
+
349
+ Returns
350
+ -------
351
+ ``Geocoder``
352
+ Geocoder object specification.
353
+
354
+ Examples
355
+ --------
356
+ .. jupyter-execute::
357
+ :linenos:
358
+ :emphasize-lines: 5
359
+
360
+ from IPython.display import display
361
+ from lets_plot import *
362
+ from lets_plot.geo_data import *
363
+ LetsPlot.setup_html()
364
+ countries = geocode_countries(['Germany', 'Poland']).inc_res().get_boundaries()
365
+ display(countries)
366
+ ggplot() + geom_map(aes(fill='found name'), data=countries, color='white')
367
+
368
+ """
369
+ self._inc_res = delta
370
+ return self
371
+
372
+ def _geocode(self) -> Geocodes:
373
+ raise ValueError('Abstract method')
374
+
375
+
376
+ def _to_coords(lon: Optional[Union[float, Series, List[float]]], lat: Optional[Union[float, Series, List[float]]]) -> \
377
+ List[GeoPoint]:
378
+ if type(lon) != type(lat):
379
+ raise ValueError('lon and lat have different types')
380
+
381
+ if isinstance(lon, float):
382
+ return [GeoPoint(lon, lat)]
383
+
384
+ if isinstance(lon, Series):
385
+ lon = lon.tolist()
386
+ lat = lat.tolist()
387
+
388
+ if isinstance(lon, list):
389
+ assert_list_type(lon, float)
390
+ assert_list_type(lat, float)
391
+ return [GeoPoint(lo, la) for lo, la in zip(lon, lat)]
392
+
393
+
394
+ class ReverseGeocoder(Geocoder):
395
+ """
396
+ Do not use this class explicitly.
397
+
398
+ Instead you should construct its objects with special functions:
399
+ `geocode() <https://lets-plot.org/python/pages/api/lets_plot.geo_data.geocode.html>`__,
400
+ `geocode_cities() <https://lets-plot.org/python/pages/api/lets_plot.geo_data.geocode_cities.html>`__,
401
+ `geocode_counties() <https://lets-plot.org/python/pages/api/lets_plot.geo_data.geocode_counties.html>`__,
402
+ `geocode_states() <https://lets-plot.org/python/pages/api/lets_plot.geo_data.geocode_states.html>`__,
403
+ `geocode_countries() <https://lets-plot.org/python/pages/api/lets_plot.geo_data.geocode_countries.html>`__,
404
+ ``reverse_geocode()``.
405
+ """
406
+
407
+ def __init__(self, lon, lat, level: Optional[Union[str, LevelKind]], scope=None):
408
+ """Initialize self."""
409
+
410
+ Geocoder.__init__(self)
411
+
412
+ self._geocodes: Optional[Geocodes] = None
413
+ self._request: ReverseGeocodingRequest = RequestBuilder() \
414
+ .set_requested_payload([PayloadKind.centroids, PayloadKind.poisitions, PayloadKind.limits]) \
415
+ .set_request_kind(RequestKind.reverse) \
416
+ .set_reverse_coordinates(_to_coords(lon, lat)) \
417
+ .set_level(_to_level_kind(level)) \
418
+ .set_reverse_scope(_to_scope(scope)) \
419
+ .build()
420
+
421
+ def _geocode(self) -> Geocodes:
422
+ if self._geocodes is None:
423
+ response: Response = GeocodingService().do_request(self._request)
424
+ if not isinstance(response, SuccessResponse):
425
+ _raise_exception(response)
426
+ self._geocodes = Geocodes(
427
+ response.level,
428
+ response.answers,
429
+ [RegionQuery(request='[{}, {}]'.format(pt.lon, pt.lat)) for pt in self._request.coordinates],
430
+ highlights=False
431
+ )
432
+
433
+ return self._geocodes
434
+
435
+
436
+ class NamesGeocoder(Geocoder):
437
+ """
438
+ Do not use this class explicitly.
439
+
440
+ Instead you should construct its objects with special functions:
441
+ `geocode() <https://lets-plot.org/python/pages/api/lets_plot.geo_data.geocode.html>`__,
442
+ `geocode_cities() <https://lets-plot.org/python/pages/api/lets_plot.geo_data.geocode_cities.html>`__,
443
+ `geocode_counties() <https://lets-plot.org/python/pages/api/lets_plot.geo_data.geocode_counties.html>`__,
444
+ `geocode_states() <https://lets-plot.org/python/pages/api/lets_plot.geo_data.geocode_states.html>`__,
445
+ `geocode_countries() <https://lets-plot.org/python/pages/api/lets_plot.geo_data.geocode_countries.html>`__.
446
+ """
447
+
448
+ def __init__(
449
+ self,
450
+ level: Optional[Union[str, LevelKind]] = None,
451
+ request: request_types = None
452
+ ):
453
+ """Initialize self."""
454
+
455
+ Geocoder.__init__(self)
456
+
457
+ self._geocodes: Optional[Geocodes] = None
458
+ self._scope: List[Optional[MapRegion]] = []
459
+ self._level: Optional[LevelKind] = _to_level_kind(level)
460
+ self._default_ambiguity_resolver: AmbiguityResolver = AmbiguityResolver.empty() # TODO rename to geohint
461
+ self._highlights: bool = False
462
+ self._allow_ambiguous = False
463
+ self._countries: List[Optional[MapRegion]] = []
464
+ self._states: List[Optional[MapRegion]] = []
465
+ self._counties: List[Optional[MapRegion]] = []
466
+ self._overridings: Dict[QuerySpec, WhereSpec] = {} # query to scope
467
+
468
+ requests: Optional[List[str]] = _ensure_is_list(request)
469
+ if requests is not None:
470
+ self._names: List[Optional[str]] = list(map(lambda name: name if requests is not None else None, requests))
471
+ else:
472
+ self._names = []
473
+
474
+ def scope(self, scope) -> 'NamesGeocoder':
475
+ """
476
+ Limit area of interest to resolve an ambiguity.
477
+
478
+ Parameters
479
+ ----------
480
+ scope : str or ``Geocoder``
481
+ Area of interest.
482
+ If it is of str type then it should be the geo-object name.
483
+ If it is of ``Geocoder`` type then it must contain only one object.
484
+
485
+ Returns
486
+ -------
487
+ ``NamesGeocoder``
488
+ Geocoder object specification.
489
+
490
+ Examples
491
+ --------
492
+ .. jupyter-execute::
493
+ :linenos:
494
+ :emphasize-lines: 6
495
+
496
+ from IPython.display import display
497
+ from lets_plot import *
498
+ from lets_plot.geo_data import *
499
+ LetsPlot.setup_html()
500
+ scope = geocode_states('Kentucky')
501
+ city = geocode_cities('Franklin').scope(scope).get_boundaries()
502
+ display(city)
503
+ ggplot() + geom_map(data=city) + ggtitle('Franklin, Kentucky')
504
+
505
+ """
506
+ self._reset_geocodes()
507
+ self._scope = _prepare_new_scope(scope)
508
+ return self
509
+
510
+ def highlights(self, v: bool):
511
+ """
512
+ Add matched string to geocodes ``DataFrame``. Doesn't affect ``GeoDataFrame``.
513
+
514
+ Parameters
515
+ ----------
516
+ v : bool
517
+ If True geocodes ``DataFrame`` will contain column 'highlights'
518
+ with string that matched the name.
519
+
520
+ Returns
521
+ -------
522
+ ``NamesGeocoder``
523
+ Geocoder object specification.
524
+
525
+ Examples
526
+ --------
527
+ .. jupyter-execute::
528
+ :linenos:
529
+ :emphasize-lines: 2
530
+
531
+ from lets_plot.geo_data import *
532
+ geocode(names='OH').allow_ambiguous().highlights(True).get_geocodes()
533
+
534
+ """
535
+ self._highlights = v
536
+ return self
537
+
538
+ def countries(self, countries):
539
+ """
540
+ Set parents for 'country' level to resolve an ambiguity
541
+ or to join geometry with data via multi-key.
542
+
543
+ Parameters
544
+ ----------
545
+ countries : str or ``Geocoder`` or list
546
+ Parents for 'country' level.
547
+ If it is of str type then it should be the country name.
548
+ If it is of ``Geocoder`` type then it must contain the same number
549
+ of values as the number of names of ``Geocoder``.
550
+ If it is of list type then it must be the same size
551
+ as the number of names of ``Geocoder``.
552
+
553
+ Returns
554
+ -------
555
+ ``NamesGeocoder``
556
+ Geocoder object specification.
557
+
558
+ Examples
559
+ --------
560
+ .. jupyter-execute::
561
+ :linenos:
562
+ :emphasize-lines: 5
563
+
564
+ from IPython.display import display
565
+ from lets_plot import *
566
+ from lets_plot.geo_data import *
567
+ LetsPlot.setup_html()
568
+ cities = geocode_cities(['Boston', 'Boston']).countries(['US', 'UK']).get_centroids()
569
+ display(cities)
570
+ ggplot() + geom_livemap() + geom_point(data=cities, color='red', size=5)
571
+
572
+ """
573
+ self._reset_geocodes()
574
+ self._countries = _make_parents(countries)
575
+ return self
576
+
577
+ def states(self, states) -> 'NamesGeocoder':
578
+ """
579
+ Set parents for 'state' level to resolve an ambiguity
580
+ or to join geometry with data via multi-key.
581
+
582
+ Parameters
583
+ ----------
584
+ states : str or ``Geocoder`` or list
585
+ Parents for 'state' level.
586
+ If it is of str type then it should be the state name.
587
+ If it is of ``Geocoder`` type then it must contain the same number
588
+ of values as the number of names of ``Geocoder``.
589
+ If it is of list type then it must be the same size
590
+ as the number of names of ``Geocoder``.
591
+
592
+ Returns
593
+ -------
594
+ ``NamesGeocoder``
595
+ Geocoder object specification.
596
+
597
+ Examples
598
+ --------
599
+ .. jupyter-execute::
600
+ :linenos:
601
+ :emphasize-lines: 6
602
+
603
+ from IPython.display import display
604
+ from lets_plot import *
605
+ from lets_plot.geo_data import *
606
+ LetsPlot.setup_html()
607
+ states = geocode_states(['Massachusetts', 'New York'])
608
+ cities = geocode_cities(['Boston', 'Boston']).states(states).get_centroids()
609
+ display(cities)
610
+ ggplot() + geom_livemap() + geom_point(data=cities, color='red', size=5)
611
+
612
+ """
613
+ self._reset_geocodes()
614
+ self._states = _make_parents(states)
615
+ return self
616
+
617
+ def counties(self, counties: parent_types) -> 'NamesGeocoder':
618
+ """
619
+ Set parents for 'county' level to resolve an ambiguity
620
+ or to join geometry with data via multi-key.
621
+
622
+ Parameters
623
+ ----------
624
+ counties : str or ``Geocoder`` or list
625
+ Parents for 'county' level.
626
+ If it is of str type then it should be the county name.
627
+ If it is of ``Geocoder`` type then it must contain the same number
628
+ of values as the number of names of ``Geocoder``.
629
+ If it is of list type then it must be the same size
630
+ as the number of names of ``Geocoder``.
631
+
632
+ Returns
633
+ -------
634
+ ``NamesGeocoder``
635
+ Geocoder object specification.
636
+
637
+ Examples
638
+ --------
639
+ .. jupyter-execute::
640
+ :linenos:
641
+ :emphasize-lines: 7
642
+
643
+ from IPython.display import display
644
+ from lets_plot import *
645
+ from lets_plot.geo_data import *
646
+ LetsPlot.setup_html()
647
+ counties = geocode_counties(['Suffolk County', 'Erie County'])\\
648
+ .states(['Massachusetts', 'New York'])
649
+ cities = geocode_cities(['Boston', 'Boston']).counties(counties).get_centroids()
650
+ display(cities)
651
+ ggplot() + geom_livemap() + geom_point(data=cities, color='red', size=5)
652
+
653
+ """
654
+ self._reset_geocodes()
655
+ self._counties = _make_parents(counties)
656
+ return self
657
+
658
+ def ignore_not_found(self) -> 'NamesGeocoder':
659
+ """
660
+ Remove not found objects from the result.
661
+
662
+ Returns
663
+ -------
664
+ ``NamesGeocoder``
665
+ Geocoder object specification.
666
+
667
+ Examples
668
+ --------
669
+ .. jupyter-execute::
670
+ :linenos:
671
+ :emphasize-lines: 6
672
+
673
+ from IPython.display import display
674
+ from lets_plot import *
675
+ from lets_plot.geo_data import *
676
+ LetsPlot.setup_html()
677
+ countries = geocode_countries(['Germany', 'Hungary', 'Czechoslovakia'])\\
678
+ .ignore_not_found().get_boundaries(6)
679
+ display(countries)
680
+ ggplot() + geom_map(aes(fill='found name'), data=countries, color='white')
681
+
682
+ """
683
+ self._reset_geocodes()
684
+ self._default_ambiguity_resolver = AmbiguityResolver(IgnoringStrategyKind.skip_missing)
685
+ return self
686
+
687
+ def ignore_all_errors(self) -> 'NamesGeocoder':
688
+ """
689
+ Remove objects that have multiple matches from the result.
690
+
691
+ Returns
692
+ -------
693
+ ``NamesGeocoder``
694
+ Geocoder object specification.
695
+
696
+ Examples
697
+ --------
698
+ .. jupyter-execute::
699
+ :linenos:
700
+ :emphasize-lines: 6
701
+
702
+ from IPython.display import display
703
+ from lets_plot import *
704
+ from lets_plot.geo_data import *
705
+ LetsPlot.setup_html()
706
+ cities = geocode_cities(['Boston', 'Worcester', 'Barnstable'])\\
707
+ .ignore_all_errors().get_centroids()
708
+ display(cities)
709
+ ggplot() + geom_livemap() + geom_point(data=cities, color='red', size=5)
710
+
711
+ """
712
+ self._reset_geocodes()
713
+ self._default_ambiguity_resolver = AmbiguityResolver(IgnoringStrategyKind.skip_all)
714
+ return self
715
+
716
+ def allow_ambiguous(self) -> 'NamesGeocoder':
717
+ """
718
+ For objects that have multiple matches add all of them to the result.
719
+
720
+ Returns
721
+ -------
722
+ ``NamesGeocoder``
723
+ Geocoder object specification.
724
+
725
+ Examples
726
+ --------
727
+ .. jupyter-execute::
728
+ :linenos:
729
+ :emphasize-lines: 6
730
+
731
+ from IPython.display import display
732
+ from lets_plot import *
733
+ from lets_plot.geo_data import *
734
+ LetsPlot.setup_html()
735
+ cities = geocode_cities('Worcester').scope('US')\\
736
+ .allow_ambiguous().get_centroids()
737
+ display(cities)
738
+ ggplot() + geom_livemap() + geom_point(data=cities, color='red', size=5)
739
+
740
+ """
741
+ self._reset_geocodes()
742
+ self._default_ambiguity_resolver = AmbiguityResolver(IgnoringStrategyKind.take_namesakes)
743
+ self._allow_ambiguous = True
744
+ return self
745
+
746
+ def where(self, name: str,
747
+ county: Optional[parent_types] = None,
748
+ state: Optional[parent_types] = None,
749
+ country: Optional[parent_types] = None,
750
+ scope: scope_types = None,
751
+ closest_to: Optional[Union[Geocodes, ShapelyPointType]] = None
752
+ ) -> 'NamesGeocoder':
753
+ """
754
+ Allows to resolve ambiguity by setting up extra parameters.
755
+ Combination of name, county, state, country identifies a row with an ambiguity.
756
+ If row with given names does not exist error will be generated.
757
+
758
+ Parameters
759
+ ----------
760
+ name : str
761
+ Name in ``Geocoder`` that needs better qualification.
762
+ county : str
763
+ If ``Geocoder`` has parent counties this field must be present to identify a row for the name.
764
+ state : str
765
+ If ``Geocoder`` has parent states this field must be present to identify a row for the name.
766
+ country : str
767
+ If ``Geocoder`` has parent countries this field must be present to identify a row for the name.
768
+ scope : str or ``Geocoder`` or ``shapely.geometry.Polygon``
769
+ Limits area of geocoding. If parent country is set then error will be generated.
770
+ If type is a str - geoobject should have geocoded scope in parents.
771
+ If type is a ``Geocoder`` - geoobject should have geocoded scope in parents.
772
+ Scope should contain only one entry.
773
+ If type is a ``shapely.geometry.Polygon`` -
774
+ geoobject centroid should fall into bbox of the polygon.
775
+ closest_to : ``Geocoder`` or ``shapely.geometry.Point``
776
+ Resolve ambiguity by taking closest geoobject.
777
+
778
+ Returns
779
+ -------
780
+ ``NamesGeocoder``
781
+ Geocoder object specification.
782
+
783
+ Examples
784
+ --------
785
+ .. jupyter-execute::
786
+ :linenos:
787
+ :emphasize-lines: 6
788
+
789
+ from IPython.display import display
790
+ from lets_plot import *
791
+ from lets_plot.geo_data import *
792
+ LetsPlot.setup_html()
793
+ city = geocode_cities('Warwick').countries('US')\\
794
+ .where(name='Warwick', country='US', scope='Massachusetts').get_centroids()
795
+ display(city)
796
+ ggplot() + geom_livemap() + geom_point(data=city, color='red', size=5)
797
+
798
+ |
799
+
800
+ .. jupyter-execute::
801
+ :linenos:
802
+ :emphasize-lines: 7
803
+
804
+ from IPython.display import display
805
+ from lets_plot import *
806
+ from lets_plot.geo_data import *
807
+ LetsPlot.setup_html()
808
+ closest_city = geocode_cities('Birmingham').get_centroids().iloc[0].geometry
809
+ city = geocode_cities('Warwick')\\
810
+ .where(name='Warwick', closest_to=closest_city).get_centroids()
811
+ display(city)
812
+ ggplot() + geom_livemap() + geom_point(data=city, color='red', size=5)
813
+
814
+ """
815
+ self._reset_geocodes()
816
+ query_spec = QuerySpec(
817
+ name,
818
+ _make_parent_region(county),
819
+ _make_parent_region(state),
820
+ _make_parent_region(country)
821
+ )
822
+
823
+ def query_exist(query):
824
+ for i in range(len(self._names)):
825
+ if query.name == self._names[i] and \
826
+ query.country == _get_or_none(self._countries, i) and \
827
+ query.state == _get_or_none(self._states, i) and \
828
+ query.county == _get_or_none(self._counties, i):
829
+ return True
830
+ return False
831
+
832
+ if not query_exist(query_spec):
833
+ parents: List[str] = []
834
+ if query_spec.county is not None:
835
+ parents.append('county={}'.format(str(query_spec.county)))
836
+
837
+ if query_spec.state is not None:
838
+ parents.append('state={}'.format(str(query_spec.state)))
839
+
840
+ if query_spec.country is not None:
841
+ parents.append('country={}'.format(str(query_spec.country)))
842
+
843
+ parents_str = ", ".join(parents)
844
+ if len(parents_str) == 0:
845
+ raise ValueError("{} is not found in names".format(name))
846
+ else:
847
+ raise ValueError("{}({}) is not found in names".format(name, parents_str))
848
+
849
+ if scope is None:
850
+ new_scope = None
851
+ ambiguity_resolver = _make_ambiguity_resolver(scope=None, closest_object=closest_to)
852
+ else:
853
+ if LazyShapely.is_polygon(scope):
854
+ new_scope = None
855
+ ambiguity_resolver = _make_ambiguity_resolver(scope=scope, closest_object=closest_to)
856
+ else:
857
+ new_scope = _prepare_new_scope(scope)[0]
858
+ ambiguity_resolver = _make_ambiguity_resolver(scope=None, closest_object=closest_to)
859
+
860
+ self._overridings[query_spec] = WhereSpec(new_scope, ambiguity_resolver)
861
+ return self
862
+
863
+ def _build_request(self) -> GeocodingRequest:
864
+ if len(self._names) == 0:
865
+ def to_scope(parents):
866
+ if len(parents) == 0:
867
+ return None
868
+ elif len(parents) == 1:
869
+ return parents[0]
870
+ else:
871
+ raise ValueError(
872
+ 'Too many parent objects. Expcted single object instead of {}'.format(len(parents))
873
+ )
874
+
875
+ # all countries/states etc. We need one dummy query
876
+ queries = [
877
+ RegionQuery(
878
+ request=None,
879
+ country=to_scope(self._countries),
880
+ state=to_scope(self._states),
881
+ county=to_scope(self._counties)
882
+ )
883
+ ]
884
+ else:
885
+ def assert_parents_size(parents: List, parents_level: str):
886
+ if len(parents) == 0:
887
+ return
888
+
889
+ if len(parents) != len(self._names):
890
+ raise ValueError(
891
+ 'Invalid request: {} count({}) != names count({})'
892
+ .format(parents_level, len(parents), len(self._names))
893
+ )
894
+
895
+ if len(self._countries) > 0 and len(self._scope) > 0:
896
+ raise ValueError("Invalid request: countries and scope can't be used simultaneously")
897
+
898
+ assert_parents_size(self._countries, 'countries')
899
+ assert_parents_size(self._states, 'states')
900
+ assert_parents_size(self._counties, 'counties')
901
+
902
+ queries = []
903
+ for i in range(len(self._names)):
904
+ name = self._names[i]
905
+ country = _get_or_none(self._countries, i)
906
+ state = _get_or_none(self._states, i)
907
+ county = _get_or_none(self._counties, i)
908
+
909
+ scope, ambiguity_resolver = self._overridings.get(
910
+ QuerySpec(name, county, state, country),
911
+ WhereSpec(None, self._default_ambiguity_resolver)
912
+ )
913
+
914
+ query = RegionQuery(
915
+ request=name,
916
+ country=country,
917
+ state=state,
918
+ county=county,
919
+ scope=scope,
920
+ ambiguity_resolver=ambiguity_resolver
921
+ )
922
+
923
+ queries.append(query)
924
+
925
+ request = RequestBuilder() \
926
+ .set_request_kind(RequestKind.geocoding) \
927
+ .set_queries(queries) \
928
+ .set_scope(self._scope) \
929
+ .set_level(self._level) \
930
+ .set_namesake_limit(NAMESAKE_MAX_COUNT) \
931
+ .set_allow_ambiguous(self._allow_ambiguous)
932
+
933
+ payload = [PayloadKind.limits, PayloadKind.poisitions, PayloadKind.centroids]
934
+ if self._highlights:
935
+ payload.append(PayloadKind.highlights)
936
+
937
+ request.set_requested_payload(payload)
938
+
939
+ return request.build()
940
+
941
+ def _geocode(self) -> Geocodes:
942
+ if self._geocodes is None:
943
+ request: GeocodingRequest = self._build_request()
944
+ response: Response = GeocodingService().do_request(request)
945
+ if not isinstance(response, SuccessResponse):
946
+ _raise_exception(response)
947
+ self._geocodes = Geocodes(response.level, response.answers, request.region_queries, self._highlights)
948
+
949
+ return self._geocodes
950
+
951
+ def _reset_geocodes(self):
952
+ self._geocodes = None
953
+
954
+ def __eq__(self, o):
955
+ return isinstance(o, NamesGeocoder) \
956
+ and self._overridings == o._overridings
957
+
958
+ def __ne__(self, o):
959
+ return not self == o
960
+
961
+
962
+ def _prepare_new_scope(scope: Optional[Union[str, Geocoder, Geocodes, MapRegion]]) -> List[MapRegion]:
963
+ """
964
+ Return list of MapRegions. Every MapRegion object contains only one name or id.
965
+ """
966
+ if scope is None:
967
+ return []
968
+
969
+ def assert_scope_length_(l):
970
+ if l != 1:
971
+ raise ValueError("'scope' has {} entries, but expected to have exactly 1".format(l))
972
+
973
+ if isinstance(scope, MapRegion):
974
+ assert_scope_length_(len(scope.values))
975
+ return [scope]
976
+
977
+ if isinstance(scope, str):
978
+ return [MapRegion.with_name(scope)]
979
+
980
+ if isinstance(scope, Geocoder):
981
+ scope = scope._geocode()
982
+
983
+ if isinstance(scope, Geocodes):
984
+ map_regions = scope.to_map_regions()
985
+ assert_scope_length_(len(map_regions))
986
+ return map_regions
987
+
988
+ raise ValueError("Unsupported 'scope' type. Expected 'str' or 'Geocoder' but was '{}'".format(type(scope).__name__))