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,201 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Union, Callable, Any, Dict, Optional, Type, List, Generic, TypeVar
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class FluentDict:
|
|
6
|
+
|
|
7
|
+
def __init__(self, dict: Optional[Dict] = None):
|
|
8
|
+
if dict is not None:
|
|
9
|
+
self._dict = dict
|
|
10
|
+
else:
|
|
11
|
+
self._dict = {}
|
|
12
|
+
|
|
13
|
+
def contains(self, key: Union[str, Enum]) -> bool:
|
|
14
|
+
str_key: str = self._key_to_str(key)
|
|
15
|
+
return str_key in self._dict and self.get(str_key) is not None
|
|
16
|
+
|
|
17
|
+
def put(self, key: Union[str, Enum], value: Any) -> 'FluentDict':
|
|
18
|
+
if value is None:
|
|
19
|
+
pass
|
|
20
|
+
elif isinstance(value, (int, float, str, dict)):
|
|
21
|
+
pass
|
|
22
|
+
elif isinstance(value, FluentDict):
|
|
23
|
+
value = value.to_dict()
|
|
24
|
+
elif isinstance(value, Enum):
|
|
25
|
+
value = value.value
|
|
26
|
+
elif isinstance(value, (list, tuple)):
|
|
27
|
+
if len(value) > 0:
|
|
28
|
+
if isinstance(value[0], Enum):
|
|
29
|
+
value = list(map(lambda enum: enum.value, value))
|
|
30
|
+
else:
|
|
31
|
+
raise ValueError('Not supported type: ' + str(value.__class__))
|
|
32
|
+
|
|
33
|
+
self._dict[self._key_to_str(key)] = value
|
|
34
|
+
return self
|
|
35
|
+
|
|
36
|
+
def visit(self, key: Union[str, Enum], consumer: Callable[[Any], Any]) -> 'FluentDict':
|
|
37
|
+
consumer(self._dict[self._key_to_str(key)])
|
|
38
|
+
return self
|
|
39
|
+
|
|
40
|
+
def visit_str(self, key: Union[str, Enum], consumer: Callable[[str], Any]) -> 'FluentDict':
|
|
41
|
+
consumer(str(self.get(key)))
|
|
42
|
+
return self
|
|
43
|
+
|
|
44
|
+
def visit_str_existing(self, key: Union[str, Enum], consumer: Callable[[str], Any]) -> 'FluentDict':
|
|
45
|
+
if self._is_not_none(key):
|
|
46
|
+
consumer(self.get(key))
|
|
47
|
+
return self
|
|
48
|
+
|
|
49
|
+
def visit_int(self, key: Union[str, Enum], consumer: Callable[[int], Any]) -> 'FluentDict':
|
|
50
|
+
consumer(int(self.get(key)))
|
|
51
|
+
return self
|
|
52
|
+
|
|
53
|
+
def get_float(self, key: Union[str, Enum]) -> float:
|
|
54
|
+
return float(self.get(key))
|
|
55
|
+
|
|
56
|
+
def visit_float(self, key: Union[str, Enum], consumer: Callable[[float], Any]) -> 'FluentDict':
|
|
57
|
+
consumer(self.get_float(key))
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
def visit_float_optional(self, key: Union[str, Enum], consumer: Callable[[float], Any]) -> 'FluentDict':
|
|
61
|
+
if self._is_not_none(key):
|
|
62
|
+
self.visit_float(key, consumer)
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
def visit_int_optional(self, key: Union[str, Enum], consumer: Callable[[int], Any]) -> 'FluentDict':
|
|
66
|
+
if self._is_not_none(key):
|
|
67
|
+
self.visit_int(key, consumer)
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
def visit_dict(self, key: Union[str, Enum], consumer: Callable[['FluentDict'], Any]) -> 'FluentDict':
|
|
71
|
+
consumer(self.get_object(key))
|
|
72
|
+
return self
|
|
73
|
+
|
|
74
|
+
def visit_object_optional(self, key: Union[str, Enum], consumer: Callable[['FluentDict'], Any]) -> 'FluentDict':
|
|
75
|
+
if self._is_not_none(key):
|
|
76
|
+
self.visit_dict(key, consumer)
|
|
77
|
+
return self
|
|
78
|
+
|
|
79
|
+
def visit_list_optional(self, key: Union[str, Enum], consumer: Callable[['FluentList'], Any]) -> 'FluentDict':
|
|
80
|
+
if self._is_not_none(key):
|
|
81
|
+
self.visit_list(key, consumer)
|
|
82
|
+
return self
|
|
83
|
+
|
|
84
|
+
def get_enum(self, key: Union[str, Enum], enum_type: Type[Enum]):
|
|
85
|
+
return self._to_enum(self.get(key), enum_type)
|
|
86
|
+
|
|
87
|
+
def visit_enum(self, key: Union[str, Enum], enum_type: Type[Enum], consumer: Callable[[Enum], Any]) -> 'FluentDict':
|
|
88
|
+
consumer(self.get_enum(key, enum_type))
|
|
89
|
+
return self
|
|
90
|
+
|
|
91
|
+
def visit_enums(self, key: Union[str, Enum], enum_type: Type[Enum], consumer: Callable[[List[Enum]], Any]) -> 'FluentDict':
|
|
92
|
+
consumer(
|
|
93
|
+
self.get_fluent_list(key)
|
|
94
|
+
.map(lambda enum_str: self._to_enum(enum_str, enum_type))
|
|
95
|
+
.list()
|
|
96
|
+
)
|
|
97
|
+
return self
|
|
98
|
+
|
|
99
|
+
def visit_enum_existing(self, key: Union[str, Enum], enum_type: Type[Enum], consumer: Callable[[Enum], Any]) -> 'FluentDict':
|
|
100
|
+
if self._is_not_none(key):
|
|
101
|
+
consumer(self.get_enum(key, enum_type))
|
|
102
|
+
|
|
103
|
+
return self
|
|
104
|
+
|
|
105
|
+
def visit_objects(self, key: Union[str, Enum], consumer: Callable[['FluentDict'], Any]) -> 'FluentDict':
|
|
106
|
+
for d in self.get(key):
|
|
107
|
+
consumer(FluentDict(d))
|
|
108
|
+
return self
|
|
109
|
+
|
|
110
|
+
def visit_object(self, key: Union[str, Enum], consumer: Callable[['FluentDict'], Any]) -> 'FluentDict':
|
|
111
|
+
consumer(FluentDict(self.get(key)))
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
def visit_list(self, key: Union[str, Enum], consumer: Callable[['FluentList'], Any]) -> 'FluentDict':
|
|
115
|
+
consumer(self.get_fluent_list(key))
|
|
116
|
+
return self
|
|
117
|
+
|
|
118
|
+
def get(self, key: Union[str, Enum]) -> Any:
|
|
119
|
+
return self._dict[self._key_to_str(key)]
|
|
120
|
+
|
|
121
|
+
def get_bool(self, key: Union[str, Enum]) -> bool:
|
|
122
|
+
return bool(self.get(key))
|
|
123
|
+
|
|
124
|
+
def visit_bool(self, key: Union[str, Enum], consumer: Callable[[bool], Any]) -> 'FluentDict':
|
|
125
|
+
consumer(self.get_bool(key))
|
|
126
|
+
return self
|
|
127
|
+
|
|
128
|
+
def get_fluent_list(self, key: Union[str, Enum]) -> 'FluentList':
|
|
129
|
+
return FluentList(self.get(key))
|
|
130
|
+
|
|
131
|
+
def get_list(self, key: Union[str, Enum]) -> list:
|
|
132
|
+
value = self.get(key)
|
|
133
|
+
assert isinstance(value, list)
|
|
134
|
+
return value
|
|
135
|
+
|
|
136
|
+
def get_object(self, key: Union[str, Enum]) -> 'FluentDict':
|
|
137
|
+
return FluentDict(self.get(key))
|
|
138
|
+
|
|
139
|
+
def get_objects(self, key: Union[str, Enum]) -> 'FluentList[FluentDict]':
|
|
140
|
+
return FluentList(self._dict[self._key_to_str(key)]).map(lambda v: FluentDict(v))
|
|
141
|
+
|
|
142
|
+
def to_dict(self) -> Dict:
|
|
143
|
+
return self._dict
|
|
144
|
+
|
|
145
|
+
def _is_not_none(self, key: Union[str, Enum]):
|
|
146
|
+
return self._key_to_str(key) in self._dict and self.get(key) is not None
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def _key_to_str(key: Union[str, Enum]) -> str:
|
|
150
|
+
if isinstance(key, str):
|
|
151
|
+
return key
|
|
152
|
+
elif isinstance(key, Enum):
|
|
153
|
+
return key.value
|
|
154
|
+
else:
|
|
155
|
+
raise ValueError('Unknown key type: ' + str(key.__class__))
|
|
156
|
+
|
|
157
|
+
@staticmethod
|
|
158
|
+
def _to_enum(enum_str: str, enum_type: Type[Enum]):
|
|
159
|
+
for e in enum_type:
|
|
160
|
+
if e.value == enum_str:
|
|
161
|
+
return e
|
|
162
|
+
raise ValueError('Unknown emum value: ' + enum_str)
|
|
163
|
+
|
|
164
|
+
def visit_str_list(self, key: Union[str, Enum], consumer: Callable[[List[str]], Any]) -> 'FluentDict':
|
|
165
|
+
consumer(self.get(key))
|
|
166
|
+
return self
|
|
167
|
+
|
|
168
|
+
def visit_str_list_optional(self, key: Union[str, Enum], consumer: Callable[[List[str]], Any]) -> 'FluentDict':
|
|
169
|
+
if self._is_not_none(key):
|
|
170
|
+
consumer(self.get(key))
|
|
171
|
+
|
|
172
|
+
return self
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
T = TypeVar('T')
|
|
176
|
+
TOut = TypeVar('TOut')
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class FluentList(Generic[T]):
|
|
180
|
+
|
|
181
|
+
def __init__(self, list: List[T] = None):
|
|
182
|
+
if list is None:
|
|
183
|
+
self._list: List[T] = []
|
|
184
|
+
else:
|
|
185
|
+
self._list: List[T] = list
|
|
186
|
+
|
|
187
|
+
def add(self, obj: T) -> 'FluentList[T]':
|
|
188
|
+
self._list.append(obj)
|
|
189
|
+
return self
|
|
190
|
+
|
|
191
|
+
def list(self) -> List[T]:
|
|
192
|
+
return self._list
|
|
193
|
+
|
|
194
|
+
TOut = TypeVar('TOut')
|
|
195
|
+
|
|
196
|
+
def map(self, func: Callable[[T], TOut]) -> 'FluentList[TOut]':
|
|
197
|
+
out = []
|
|
198
|
+
for v in self._list:
|
|
199
|
+
out.append(func(v))
|
|
200
|
+
|
|
201
|
+
return FluentList(out)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import gzip
|
|
2
|
+
import json
|
|
3
|
+
import urllib.parse
|
|
4
|
+
import urllib.request
|
|
5
|
+
from urllib.error import HTTPError
|
|
6
|
+
|
|
7
|
+
from .json_request import RequestFormatter
|
|
8
|
+
from .json_response import ResponseParser
|
|
9
|
+
from .request import Request
|
|
10
|
+
from .response import Response
|
|
11
|
+
from lets_plot._global_settings import has_global_value, get_global_str, GEOCODING_ROUTE
|
|
12
|
+
from lets_plot.settings_utils import GEOCODING_PROVIDER_URL
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GeocodingService:
|
|
16
|
+
def do_request(self, request: Request) -> Response:
|
|
17
|
+
if not has_global_value(GEOCODING_PROVIDER_URL):
|
|
18
|
+
raise ValueError('Geocoding server url is not defined')
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
request_json = RequestFormatter().format(request).to_dict()
|
|
22
|
+
request_str = json.dumps(request_json)
|
|
23
|
+
|
|
24
|
+
request = urllib.request.Request(
|
|
25
|
+
url=get_global_str(GEOCODING_PROVIDER_URL) + GEOCODING_ROUTE,
|
|
26
|
+
headers={'Content-Type': 'application/json', 'Accept-Encoding': 'gzip'},
|
|
27
|
+
method='POST',
|
|
28
|
+
data=bytearray(request_str, 'utf-8')
|
|
29
|
+
)
|
|
30
|
+
response = urllib.request.urlopen(request)
|
|
31
|
+
if response.info().get('Content-Encoding') == 'gzip':
|
|
32
|
+
content = response.read()
|
|
33
|
+
response_str = gzip.decompress(content).decode('utf-8')
|
|
34
|
+
else:
|
|
35
|
+
response_str = response.read().decode('utf-8')
|
|
36
|
+
|
|
37
|
+
response_json = json.loads(response_str)
|
|
38
|
+
return ResponseParser().parse(response_json)
|
|
39
|
+
|
|
40
|
+
except HTTPError as e:
|
|
41
|
+
raise ValueError(
|
|
42
|
+
'Geocoding server connection failure: {} {} ({})'.format(e.code, e.msg, e.filename)) from None
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from ..type_assertion import assert_list_type
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GeometryBase:
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GeoPoint(GeometryBase):
|
|
11
|
+
def __init__(self, lon: float, lat: float):
|
|
12
|
+
self.lon: float = lon
|
|
13
|
+
self.lat: float = lat
|
|
14
|
+
|
|
15
|
+
def __eq__(self, other):
|
|
16
|
+
return isinstance(other, GeoPoint) \
|
|
17
|
+
and self.lon == other.lon \
|
|
18
|
+
and self.lat == other.lat
|
|
19
|
+
|
|
20
|
+
def __ne__(self, other):
|
|
21
|
+
return not self == other
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GeoRect(GeometryBase):
|
|
25
|
+
def __init__(self, start_lon: float, min_lat: float, end_lon: float, max_lat: float):
|
|
26
|
+
self.start_lon: float = start_lon
|
|
27
|
+
self.min_lat: float = min_lat
|
|
28
|
+
self.end_lon: float = end_lon
|
|
29
|
+
self.max_lat: float = max_lat
|
|
30
|
+
|
|
31
|
+
def crosses_antimeridian(self):
|
|
32
|
+
return self.start_lon > self.end_lon
|
|
33
|
+
|
|
34
|
+
def __eq__(self, other):
|
|
35
|
+
return isinstance(other, GeoRect) \
|
|
36
|
+
and self.start_lon == other.start_lon \
|
|
37
|
+
and self.min_lat == other.min_lat \
|
|
38
|
+
and self.end_lon == other.end_lon \
|
|
39
|
+
and self.max_lat == other.max_lat
|
|
40
|
+
|
|
41
|
+
def __ne__(self, other):
|
|
42
|
+
return not self == other
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Ring(GeometryBase):
|
|
46
|
+
def __init__(self, points: List[GeoPoint]):
|
|
47
|
+
assert_list_type(points, GeoPoint)
|
|
48
|
+
self.points: List[GeoPoint] = points
|
|
49
|
+
|
|
50
|
+
def __eq__(self, other):
|
|
51
|
+
return isinstance(other, Ring) \
|
|
52
|
+
and self.points == other.points
|
|
53
|
+
|
|
54
|
+
def __ne__(self, other):
|
|
55
|
+
return not self == other
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Polygon(GeometryBase):
|
|
59
|
+
def __init__(self, rings: List[Ring]):
|
|
60
|
+
assert_list_type(rings, Ring)
|
|
61
|
+
self.rings: List[Ring] = rings
|
|
62
|
+
|
|
63
|
+
def __eq__(self, other):
|
|
64
|
+
if not isinstance(other, Polygon):
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
for r, o in zip(self.rings, other.rings):
|
|
68
|
+
if r != o:
|
|
69
|
+
return False
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
def __ne__(self, other):
|
|
73
|
+
return not self == other
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Multipolygon(GeometryBase):
|
|
77
|
+
def __init__(self, polygons: List[Polygon]):
|
|
78
|
+
assert_list_type(polygons, Polygon)
|
|
79
|
+
self.polygons: List[Polygon] = polygons
|
|
80
|
+
|
|
81
|
+
def __eq__(self, other):
|
|
82
|
+
if not isinstance(other, Multipolygon):
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
for p, o in zip(self.polygons, other.polygons):
|
|
86
|
+
if p != o:
|
|
87
|
+
return False
|
|
88
|
+
return True
|
|
89
|
+
|
|
90
|
+
def __ne__(self, other):
|
|
91
|
+
return not self == other
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
from typing import Dict, List, Optional, Tuple
|
|
3
|
+
|
|
4
|
+
from .fluent_dict import FluentDict, FluentList
|
|
5
|
+
from .geometry import GeoPoint
|
|
6
|
+
from .request import RegionQuery, MapRegion, MapRegionKind, PayloadKind, RegionQueryBuilder, IgnoringStrategyKind, \
|
|
7
|
+
MapRegionBuilder
|
|
8
|
+
from .request import Request, GeocodingRequest, ExplicitRequest, RequestBuilder, RequestKind, ReverseGeocodingRequest
|
|
9
|
+
from .response import LevelKind, GeoRect
|
|
10
|
+
|
|
11
|
+
PROTOCOL_VERSION = 3
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Field(enum.Enum):
|
|
15
|
+
version = 'version'
|
|
16
|
+
mode = 'mode'
|
|
17
|
+
requested_payload = 'feature_options'
|
|
18
|
+
resolution = 'resolution'
|
|
19
|
+
view_box = 'view_box'
|
|
20
|
+
fetched_ids = 'fetched_ids'
|
|
21
|
+
option_kind = 'kind'
|
|
22
|
+
geo_object_list = 'ids'
|
|
23
|
+
region_queries = 'region_queries'
|
|
24
|
+
region_query_names = 'region_query_names'
|
|
25
|
+
region_query_countries = 'region_query_countries'
|
|
26
|
+
region_query_states = 'region_query_states'
|
|
27
|
+
region_query_counties = 'region_query_counties'
|
|
28
|
+
region_query_parent = 'region_query_parent'
|
|
29
|
+
scope = 'scope'
|
|
30
|
+
level = 'level'
|
|
31
|
+
map_region_kind = 'kind'
|
|
32
|
+
map_region_values = 'values'
|
|
33
|
+
match = 'match'
|
|
34
|
+
namesake_example_limit = 'namesake_example_limit'
|
|
35
|
+
allow_ambiguous = 'allow_ambiguous'
|
|
36
|
+
ambiguity_resolver = 'ambiguity_resolver'
|
|
37
|
+
ambiguity_ignoring_strategy = 'ambiguity_resolver_ignoring_strategy'
|
|
38
|
+
ambiguity_closest_coord = 'ambiguity_resolver_closest_coord'
|
|
39
|
+
ambiguity_box = 'ambiguity_resolver_box'
|
|
40
|
+
|
|
41
|
+
reverse_level = "level"
|
|
42
|
+
reverse_coordinates = "reverse_coordinates"
|
|
43
|
+
reverse_parent = "reverse_parent"
|
|
44
|
+
|
|
45
|
+
coord_lon = 'lon'
|
|
46
|
+
coord_lat = 'lat'
|
|
47
|
+
|
|
48
|
+
min_lon = 'min_lon'
|
|
49
|
+
min_lat = 'min_lat'
|
|
50
|
+
max_lon = 'max_lon'
|
|
51
|
+
max_lat = 'max_lat'
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class RequestFormatter:
|
|
55
|
+
@staticmethod
|
|
56
|
+
def format(request: Request) -> FluentDict:
|
|
57
|
+
if isinstance(request, GeocodingRequest):
|
|
58
|
+
return RequestFormatter._format_geocoding_request(request)
|
|
59
|
+
elif isinstance(request, ExplicitRequest):
|
|
60
|
+
return RequestFormatter._format_explicit_request(request)
|
|
61
|
+
elif isinstance(request, ReverseGeocodingRequest):
|
|
62
|
+
return RequestFormatter._format_reverse_geocoding_request(request)
|
|
63
|
+
else:
|
|
64
|
+
raise ValueError('Unknown request kind: ' + str(request))
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def _format_geocoding_request(request: 'GeocodingRequest') -> FluentDict:
|
|
68
|
+
return RequestFormatter \
|
|
69
|
+
._common(RequestKind.geocoding, request) \
|
|
70
|
+
.put(Field.region_queries, RequestFormatter._format_region_queries(request.region_queries)) \
|
|
71
|
+
.put(Field.scope, RequestFormatter._format_scope(request.scope)) \
|
|
72
|
+
.put(Field.level, request.level) \
|
|
73
|
+
.put(Field.namesake_example_limit, request.namesake_example_limit) \
|
|
74
|
+
.put(Field.allow_ambiguous, request.allow_ambiguous)
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def _format_explicit_request(request: 'ExplicitRequest') -> FluentDict:
|
|
78
|
+
return RequestFormatter \
|
|
79
|
+
._common(RequestKind.explicit, request) \
|
|
80
|
+
.put(Field.geo_object_list, request.geo_object_list)
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def _format_reverse_geocoding_request(request: 'ReverseGeocodingRequest'):
|
|
84
|
+
return RequestFormatter \
|
|
85
|
+
._common(RequestKind.reverse, request) \
|
|
86
|
+
.put(Field.reverse_coordinates, [RequestFormatter._format_coord(coord) for coord in request.coordinates]) \
|
|
87
|
+
.put(Field.reverse_level, request.level) \
|
|
88
|
+
.put(Field.reverse_parent, RequestFormatter._format_map_region(request.scope))
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def _common(request_kind: RequestKind, request: Request) -> FluentDict:
|
|
92
|
+
return FluentDict() \
|
|
93
|
+
.put(Field.version, PROTOCOL_VERSION) \
|
|
94
|
+
.put(Field.mode, request_kind.value) \
|
|
95
|
+
.put(Field.requested_payload, request.requested_payload) \
|
|
96
|
+
.put(Field.resolution, request.resolution) \
|
|
97
|
+
.put(Field.view_box, None) \
|
|
98
|
+
.put(Field.fetched_ids, None)
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def _format_region_queries(region_queires: List[RegionQuery]) -> List[Dict]:
|
|
102
|
+
result = []
|
|
103
|
+
for query in region_queires:
|
|
104
|
+
result.append(
|
|
105
|
+
FluentDict()
|
|
106
|
+
.put(Field.region_query_names, [] if query.request is None else [query.request])
|
|
107
|
+
.put(Field.region_query_countries, RequestFormatter._format_map_region(query.country))
|
|
108
|
+
.put(Field.region_query_states, RequestFormatter._format_map_region(query.state))
|
|
109
|
+
.put(Field.region_query_counties, RequestFormatter._format_map_region(query.county))
|
|
110
|
+
.put(Field.ambiguity_resolver, None if query.ambiguity_resolver is None else FluentDict()
|
|
111
|
+
.put(Field.ambiguity_ignoring_strategy, query.ambiguity_resolver.ignoring_strategy)
|
|
112
|
+
.put(Field.ambiguity_box, RequestFormatter._format_box(query.ambiguity_resolver.box))
|
|
113
|
+
.put(Field.ambiguity_closest_coord, RequestFormatter._format_coord(query.ambiguity_resolver.closest_coord))) \
|
|
114
|
+
.put(Field.region_query_parent, RequestFormatter._format_map_region(query.scope))
|
|
115
|
+
.to_dict()
|
|
116
|
+
)
|
|
117
|
+
return result
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
def _format_scope(scope: List[MapRegion]) -> List[Dict]:
|
|
121
|
+
return [RequestFormatter._format_map_region(s) for s in scope]
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def _format_map_region(parent: Optional[MapRegion]) -> Optional[Dict]:
|
|
125
|
+
if parent is None:
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
# special case - place is just a geocoded object with id and extra information, used by client
|
|
129
|
+
# server doesn't need this extra information
|
|
130
|
+
if parent.kind.value == 'place':
|
|
131
|
+
return FluentDict() \
|
|
132
|
+
.put(Field.map_region_kind, MapRegionKind.id.value) \
|
|
133
|
+
.put(Field.map_region_values, parent.values) \
|
|
134
|
+
.to_dict()
|
|
135
|
+
|
|
136
|
+
return FluentDict() \
|
|
137
|
+
.put(Field.map_region_kind, parent.kind.value) \
|
|
138
|
+
.put(Field.map_region_values, parent.values) \
|
|
139
|
+
.to_dict()
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def _format_coord(closest_coord: GeoPoint) -> Optional[Tuple[float, float]]:
|
|
143
|
+
if closest_coord is None:
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
return closest_coord.lon, closest_coord.lat
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def _format_box(rect: GeoRect) -> Optional[Dict]:
|
|
150
|
+
if rect is None:
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
return FluentDict() \
|
|
154
|
+
.put(Field.min_lon, rect.start_lon) \
|
|
155
|
+
.put(Field.min_lat, rect.min_lat) \
|
|
156
|
+
.put(Field.max_lon, rect.end_lon) \
|
|
157
|
+
.put(Field.max_lat, rect.max_lat) \
|
|
158
|
+
.to_dict()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class RequestParser:
|
|
162
|
+
@staticmethod
|
|
163
|
+
def parse(request_json: Dict) -> Request:
|
|
164
|
+
request = RequestBuilder()
|
|
165
|
+
request_dict = FluentDict(request_json) \
|
|
166
|
+
.visit_enum(Field.mode, RequestKind, request.set_request_kind) \
|
|
167
|
+
.visit_enums(Field.requested_payload, PayloadKind, request.set_requested_payload) \
|
|
168
|
+
.visit_int_optional(Field.resolution, request.set_resolution)
|
|
169
|
+
|
|
170
|
+
if request.request_kind == RequestKind.explicit:
|
|
171
|
+
request_dict.visit_str_list(Field.geo_object_list, request.set_ids)
|
|
172
|
+
elif request.request_kind == RequestKind.geocoding:
|
|
173
|
+
request_dict \
|
|
174
|
+
.visit_enum_existing(Field.level, LevelKind, request.set_level) \
|
|
175
|
+
.visit_int(Field.namesake_example_limit, request.set_namesake_limit) \
|
|
176
|
+
.visit_bool(Field.allow_ambiguous, request.set_allow_ambiguous) \
|
|
177
|
+
.visit_list(Field.region_queries, lambda regions: request.set_queries(regions.map(RequestParser._parse_region_query).list()))
|
|
178
|
+
elif request.request_kind == RequestKind.reverse:
|
|
179
|
+
request_dict \
|
|
180
|
+
.visit_enum_existing(Field.reverse_level, LevelKind, request.set_level) \
|
|
181
|
+
.visit_list(Field.reverse_coordinates, lambda coords: request.set_reverse_coordinates(RequestParser._parse_coordinates(coords))) \
|
|
182
|
+
.visit_object_optional(Field.reverse_parent,
|
|
183
|
+
lambda parent: request.set_reverse_scope(RequestParser._parse_map_region(parent)))
|
|
184
|
+
else:
|
|
185
|
+
raise ValueError('Unknown request kind: ' + str(request))
|
|
186
|
+
|
|
187
|
+
return request.build()
|
|
188
|
+
|
|
189
|
+
@staticmethod
|
|
190
|
+
def _parse_region_query(region_query: dict) -> RegionQuery:
|
|
191
|
+
region_q = FluentDict(region_query)
|
|
192
|
+
assert len(region_q.get_list(Field.region_query_names)) in [0, 1], 'Multirequests are not supported'
|
|
193
|
+
|
|
194
|
+
builder = RegionQueryBuilder()
|
|
195
|
+
|
|
196
|
+
FluentDict(region_query) \
|
|
197
|
+
.visit_str_list(Field.region_query_names, lambda names: builder.set_request(names[0] if len(names) == 1 else None)) \
|
|
198
|
+
.visit_object_optional(Field.ambiguity_resolver, lambda resolver: resolver
|
|
199
|
+
.visit_list_optional(Field.ambiguity_closest_coord,
|
|
200
|
+
lambda coord: builder.set_closest_coord(RequestParser._parse_coord(coord)))
|
|
201
|
+
.visit_object_optional(Field.ambiguity_box,
|
|
202
|
+
lambda jsonBox: builder.set_box(RequestParser._parse_geo_rect(jsonBox)))
|
|
203
|
+
.visit_enum_existing(Field.ambiguity_ignoring_strategy, IgnoringStrategyKind, builder.set_ignoring_strategy)) \
|
|
204
|
+
.visit_object_optional(Field.region_query_parent, lambda parent: builder.set_scope(RequestParser._parse_map_region(parent)))
|
|
205
|
+
|
|
206
|
+
return builder.build()
|
|
207
|
+
|
|
208
|
+
@staticmethod
|
|
209
|
+
def _parse_map_region(json: FluentDict) -> MapRegion:
|
|
210
|
+
builder = MapRegionBuilder()
|
|
211
|
+
json \
|
|
212
|
+
.visit_str_list(Field.map_region_values, builder.set_parent_values) \
|
|
213
|
+
.visit_bool(Field.map_region_kind, builder.set_parent_kind)
|
|
214
|
+
|
|
215
|
+
return builder.build()
|
|
216
|
+
|
|
217
|
+
@staticmethod
|
|
218
|
+
def _parse_coord(jsonCoord: FluentList) -> GeoPoint:
|
|
219
|
+
return GeoPoint(jsonCoord.list()[0], jsonCoord.list()[1])
|
|
220
|
+
|
|
221
|
+
@staticmethod
|
|
222
|
+
def _parse_geo_rect(jsonBox: FluentDict) -> GeoRect:
|
|
223
|
+
return GeoRect(
|
|
224
|
+
start_lon=jsonBox.get_float(Field.min_lon),
|
|
225
|
+
min_lat=jsonBox.get_float(Field.min_lat),
|
|
226
|
+
end_lon=jsonBox.get_float(Field.max_lon),
|
|
227
|
+
max_lat=jsonBox.get_float(Field.max_lat),
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
@staticmethod
|
|
231
|
+
def _parse_coordinates(coordinates: FluentList) -> List[GeoPoint]:
|
|
232
|
+
return [GeoPoint(coord[0], coord[1]) for coord in coordinates.list()]
|