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.
- lets_plot/__init__.py +382 -0
- lets_plot/_global_settings.py +192 -0
- lets_plot/_kbridge.py +197 -0
- lets_plot/_type_utils.py +133 -0
- lets_plot/_version.py +6 -0
- lets_plot/bistro/__init__.py +16 -0
- lets_plot/bistro/_plot2d_common.py +106 -0
- lets_plot/bistro/corr.py +448 -0
- lets_plot/bistro/im.py +196 -0
- lets_plot/bistro/joint.py +192 -0
- lets_plot/bistro/qq.py +207 -0
- lets_plot/bistro/residual.py +341 -0
- lets_plot/bistro/waterfall.py +332 -0
- lets_plot/export/__init__.py +6 -0
- lets_plot/export/ggsave_.py +172 -0
- lets_plot/frontend_context/__init__.py +8 -0
- lets_plot/frontend_context/_configuration.py +140 -0
- lets_plot/frontend_context/_dynamic_configure_html.py +115 -0
- lets_plot/frontend_context/_frontend_ctx.py +16 -0
- lets_plot/frontend_context/_html_contexts.py +223 -0
- lets_plot/frontend_context/_intellij_python_json_ctx.py +38 -0
- lets_plot/frontend_context/_isolated_webview_panel_ctx.py +81 -0
- lets_plot/frontend_context/_json_contexts.py +39 -0
- lets_plot/frontend_context/_jupyter_notebook_ctx.py +82 -0
- lets_plot/frontend_context/_mime_types.py +7 -0
- lets_plot/frontend_context/_static_html_page_ctx.py +76 -0
- lets_plot/frontend_context/_static_svg_ctx.py +26 -0
- lets_plot/frontend_context/_webbr_html_page_ctx.py +29 -0
- lets_plot/frontend_context/sandbox.py +5 -0
- lets_plot/geo_data/__init__.py +19 -0
- lets_plot/geo_data/core.py +335 -0
- lets_plot/geo_data/geocoder.py +988 -0
- lets_plot/geo_data/geocodes.py +512 -0
- lets_plot/geo_data/gis/__init__.py +0 -0
- lets_plot/geo_data/gis/fluent_dict.py +201 -0
- lets_plot/geo_data/gis/geocoding_service.py +42 -0
- lets_plot/geo_data/gis/geometry.py +91 -0
- lets_plot/geo_data/gis/json_request.py +232 -0
- lets_plot/geo_data/gis/json_response.py +308 -0
- lets_plot/geo_data/gis/request.py +492 -0
- lets_plot/geo_data/gis/response.py +247 -0
- lets_plot/geo_data/livemap_helper.py +65 -0
- lets_plot/geo_data/to_geo_data_frame.py +141 -0
- lets_plot/geo_data/type_assertion.py +34 -0
- lets_plot/geo_data_internals/__init__.py +4 -0
- lets_plot/geo_data_internals/constants.py +13 -0
- lets_plot/geo_data_internals/utils.py +33 -0
- lets_plot/mapping.py +115 -0
- lets_plot/package_data/lets-plot.min.js +3 -0
- lets_plot/plot/__init__.py +64 -0
- lets_plot/plot/_global_theme.py +14 -0
- lets_plot/plot/annotation.py +290 -0
- lets_plot/plot/coord.py +242 -0
- lets_plot/plot/core.py +1071 -0
- lets_plot/plot/expand_limits_.py +78 -0
- lets_plot/plot/facet.py +210 -0
- lets_plot/plot/font_features.py +71 -0
- lets_plot/plot/geom.py +9146 -0
- lets_plot/plot/geom_extras.py +53 -0
- lets_plot/plot/geom_function_.py +219 -0
- lets_plot/plot/geom_imshow_.py +393 -0
- lets_plot/plot/geom_livemap_.py +343 -0
- lets_plot/plot/ggbunch_.py +96 -0
- lets_plot/plot/gggrid_.py +139 -0
- lets_plot/plot/ggtb_.py +81 -0
- lets_plot/plot/guide.py +231 -0
- lets_plot/plot/label.py +187 -0
- lets_plot/plot/marginal_layer.py +181 -0
- lets_plot/plot/plot.py +245 -0
- lets_plot/plot/pos.py +344 -0
- lets_plot/plot/sampling.py +338 -0
- lets_plot/plot/sandbox_.py +26 -0
- lets_plot/plot/scale.py +3580 -0
- lets_plot/plot/scale_colormap_mpl.py +300 -0
- lets_plot/plot/scale_convenience.py +155 -0
- lets_plot/plot/scale_identity_.py +653 -0
- lets_plot/plot/scale_position.py +1342 -0
- lets_plot/plot/series_meta.py +209 -0
- lets_plot/plot/stat.py +585 -0
- lets_plot/plot/subplots.py +331 -0
- lets_plot/plot/subplots_util.py +24 -0
- lets_plot/plot/theme_.py +790 -0
- lets_plot/plot/theme_set.py +418 -0
- lets_plot/plot/tooltip.py +486 -0
- lets_plot/plot/util.py +267 -0
- lets_plot/settings_utils.py +244 -0
- lets_plot/tilesets.py +429 -0
- lets_plot-4.8.1rc1.dist-info/METADATA +221 -0
- lets_plot-4.8.1rc1.dist-info/RECORD +97 -0
- lets_plot-4.8.1rc1.dist-info/WHEEL +6 -0
- lets_plot-4.8.1rc1.dist-info/licenses/LICENSE +21 -0
- lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.FreeType +166 -0
- lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.ImageMagick +106 -0
- lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.expat +21 -0
- lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.fontconfig +200 -0
- lets_plot-4.8.1rc1.dist-info/top_level.txt +2 -0
- 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__))
|