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,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()]