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,247 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
from typing import List, Optional, Dict, Union
|
|
3
|
+
|
|
4
|
+
from .geometry import GeometryBase, GeoPoint, GeoRect, Polygon, Multipolygon
|
|
5
|
+
from .request import LevelKind
|
|
6
|
+
from ..type_assertion import assert_list_type, assert_optional_type, assert_type, assert_optional_list_type
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Status(enum.Enum):
|
|
10
|
+
success = 'success'
|
|
11
|
+
ambiguous = 'ambiguous'
|
|
12
|
+
error = 'error'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class NamesakeParent:
|
|
16
|
+
def __init__(self, name: str, level: LevelKind):
|
|
17
|
+
assert_type(name, str)
|
|
18
|
+
assert_type(level, LevelKind)
|
|
19
|
+
|
|
20
|
+
self.name: str = name
|
|
21
|
+
self.level: LevelKind = level
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Namesake:
|
|
25
|
+
def __init__(self, name: str, parents: List[NamesakeParent]):
|
|
26
|
+
assert_type(name, str)
|
|
27
|
+
assert_list_type(parents, NamesakeParent)
|
|
28
|
+
|
|
29
|
+
self.name: str = name
|
|
30
|
+
self.parents: List[NamesakeParent] = parents
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Boundary(GeometryBase):
|
|
34
|
+
def __init__(self, geometry: Union[Multipolygon, Polygon, GeoPoint]):
|
|
35
|
+
self.geometry: Union[Multipolygon, Polygon, GeoPoint] = geometry
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class GeocodedFeature:
|
|
39
|
+
def __init__(self,
|
|
40
|
+
id: str, name: str,
|
|
41
|
+
highlights: Optional[List[str]] = None,
|
|
42
|
+
boundary: Optional[Boundary] = None,
|
|
43
|
+
centroid: Optional[GeoPoint] = None,
|
|
44
|
+
limit: Optional[GeoRect] = None,
|
|
45
|
+
position: Optional[GeoRect] = None):
|
|
46
|
+
assert_type(id, str)
|
|
47
|
+
assert_type(name, str)
|
|
48
|
+
assert_optional_list_type(highlights, str)
|
|
49
|
+
assert_optional_type(boundary, Boundary)
|
|
50
|
+
assert_optional_type(centroid, GeoPoint)
|
|
51
|
+
assert_optional_type(limit, GeoRect)
|
|
52
|
+
assert_optional_type(position, GeoRect)
|
|
53
|
+
|
|
54
|
+
self.id: str = id
|
|
55
|
+
self.name: str = name
|
|
56
|
+
self.highlights: Optional[List[str]] = highlights
|
|
57
|
+
self.boundary: Optional[Boundary] = boundary
|
|
58
|
+
self.centroid: Optional[GeoPoint] = centroid
|
|
59
|
+
self.limit: Optional[GeoRect] = limit
|
|
60
|
+
self.position: Optional[GeoRect] = position
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class AmbiguousFeature:
|
|
64
|
+
def __init__(self, query: str, total_namesake_count: int, namesake_examples: List[Namesake]):
|
|
65
|
+
assert_type(query, str)
|
|
66
|
+
assert_type(total_namesake_count, int)
|
|
67
|
+
assert_list_type(namesake_examples, Namesake)
|
|
68
|
+
|
|
69
|
+
self.query: str = query
|
|
70
|
+
self.total_namesake_count: int = total_namesake_count
|
|
71
|
+
self.namesake_examples: List[Namesake] = namesake_examples
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Response:
|
|
75
|
+
def __init__(self, message: str):
|
|
76
|
+
assert_type(message, str)
|
|
77
|
+
self.message: str = message
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class Answer:
|
|
81
|
+
def __init__(self, features: List[GeocodedFeature]):
|
|
82
|
+
assert_list_type(features, GeocodedFeature)
|
|
83
|
+
self.features: List[GeocodedFeature] = features
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class SuccessResponse(Response):
|
|
87
|
+
def __init__(self, message: str, level: LevelKind, answers: List[Answer]):
|
|
88
|
+
super().__init__(message)
|
|
89
|
+
|
|
90
|
+
assert_type(message, str)
|
|
91
|
+
assert_optional_type(level, LevelKind)
|
|
92
|
+
assert_list_type(answers, Answer)
|
|
93
|
+
|
|
94
|
+
self.level: LevelKind = level
|
|
95
|
+
self.answers: List[Answer] = answers
|
|
96
|
+
|
|
97
|
+
features = []
|
|
98
|
+
for answer in answers:
|
|
99
|
+
features.extend(answer.features)
|
|
100
|
+
|
|
101
|
+
self.features: List[GeocodedFeature] = features
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class AmbiguousResponse(Response):
|
|
105
|
+
def __init__(self, message: str, level: LevelKind, features: List[AmbiguousFeature]):
|
|
106
|
+
super().__init__(message)
|
|
107
|
+
|
|
108
|
+
assert_type(message, str)
|
|
109
|
+
assert_optional_type(level, LevelKind)
|
|
110
|
+
assert_list_type(features, AmbiguousFeature)
|
|
111
|
+
|
|
112
|
+
self.level: LevelKind = level
|
|
113
|
+
self.features: List[AmbiguousFeature] = features
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class ErrorResponse(Response):
|
|
117
|
+
def __init__(self, message: str):
|
|
118
|
+
super().__init__(message)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class FeatureBuilder:
|
|
122
|
+
def __init__(self):
|
|
123
|
+
self.query: Optional[str] = None
|
|
124
|
+
self.id: Optional[str] = None
|
|
125
|
+
self.name: Optional[str] = None
|
|
126
|
+
self.highlights: Optional[List[str]] = None
|
|
127
|
+
self.boundary: Optional[Boundary] = None
|
|
128
|
+
self.centroid: Optional[GeoPoint] = None
|
|
129
|
+
self.limit: Optional[GeoRect] = None
|
|
130
|
+
self.position: Optional[GeoRect] = None
|
|
131
|
+
self.total_namesake_count: Optional[int] = None
|
|
132
|
+
self.namesake_examples: List[Namesake] = []
|
|
133
|
+
|
|
134
|
+
def set_query(self, v: Optional[str]) -> 'FeatureBuilder':
|
|
135
|
+
assert_optional_type(v, str)
|
|
136
|
+
self.query = v
|
|
137
|
+
return self
|
|
138
|
+
|
|
139
|
+
def set_id(self, v: str) -> 'FeatureBuilder':
|
|
140
|
+
assert_type(v, str)
|
|
141
|
+
self.id = v
|
|
142
|
+
return self
|
|
143
|
+
|
|
144
|
+
def set_name(self, v: str) -> 'FeatureBuilder':
|
|
145
|
+
assert_type(v, str)
|
|
146
|
+
self.name = v
|
|
147
|
+
return self
|
|
148
|
+
|
|
149
|
+
def set_highlights(self, v: List[str]) -> 'FeatureBuilder':
|
|
150
|
+
assert_list_type(v, str)
|
|
151
|
+
self.highlights = v
|
|
152
|
+
return self
|
|
153
|
+
|
|
154
|
+
def set_boundary(self, v: Union[Multipolygon, Polygon, GeoPoint]) -> 'FeatureBuilder':
|
|
155
|
+
assert_type(v, (Multipolygon, Polygon, GeoPoint))
|
|
156
|
+
self.boundary = Boundary(v)
|
|
157
|
+
return self
|
|
158
|
+
|
|
159
|
+
def set_centroid(self, v: GeoPoint) -> 'FeatureBuilder':
|
|
160
|
+
assert_type(v, GeoPoint)
|
|
161
|
+
self.centroid = v
|
|
162
|
+
return self
|
|
163
|
+
|
|
164
|
+
def set_limit(self, v: GeoRect) -> 'FeatureBuilder':
|
|
165
|
+
assert_type(v, GeoRect)
|
|
166
|
+
self.limit = v
|
|
167
|
+
return self
|
|
168
|
+
|
|
169
|
+
def set_position(self, v: GeoRect) -> 'FeatureBuilder':
|
|
170
|
+
assert_type(v, GeoRect)
|
|
171
|
+
self.position = v
|
|
172
|
+
return self
|
|
173
|
+
|
|
174
|
+
def set_total_namesake_count(self, v: int) -> 'FeatureBuilder':
|
|
175
|
+
assert_type(v, int)
|
|
176
|
+
self.total_namesake_count = v
|
|
177
|
+
return self
|
|
178
|
+
|
|
179
|
+
def set_namesake_examples(self, v: List[Namesake]) -> 'FeatureBuilder':
|
|
180
|
+
assert_list_type(v, Namesake)
|
|
181
|
+
self.namesake_examples = v
|
|
182
|
+
return self
|
|
183
|
+
|
|
184
|
+
def add_namesake(self, namesake: Namesake) -> 'FeatureBuilder':
|
|
185
|
+
assert_type(namesake, Namesake)
|
|
186
|
+
self.namesake_examples.append(namesake)
|
|
187
|
+
return self
|
|
188
|
+
|
|
189
|
+
def build_ambiguous(self) -> AmbiguousFeature:
|
|
190
|
+
return AmbiguousFeature(self.query, self.total_namesake_count, self.namesake_examples)
|
|
191
|
+
|
|
192
|
+
def build_geocoded(self) -> GeocodedFeature:
|
|
193
|
+
return GeocodedFeature(self.id, self.name, self.highlights, self.boundary, self.centroid, self.limit,
|
|
194
|
+
self.position)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class ResponseBuilder:
|
|
198
|
+
def __init__(self):
|
|
199
|
+
self.status: Status = None
|
|
200
|
+
self.level: LevelKind = None
|
|
201
|
+
self.message: str = None
|
|
202
|
+
self.answers: List[Answer] = None
|
|
203
|
+
self.ambiguous_features: List[AmbiguousFeature] = None
|
|
204
|
+
self.data: Dict = None
|
|
205
|
+
|
|
206
|
+
def set_status(self, v: Status) -> 'ResponseBuilder':
|
|
207
|
+
assert_type(v, Status)
|
|
208
|
+
self.status = v
|
|
209
|
+
return self
|
|
210
|
+
|
|
211
|
+
def set_level(self, v: LevelKind) -> 'ResponseBuilder':
|
|
212
|
+
assert_type(v, LevelKind)
|
|
213
|
+
self.level = v
|
|
214
|
+
return self
|
|
215
|
+
|
|
216
|
+
def set_message(self, v: str) -> 'ResponseBuilder':
|
|
217
|
+
assert_type(v, str)
|
|
218
|
+
self.message = v
|
|
219
|
+
return self
|
|
220
|
+
|
|
221
|
+
def set_ambiguous_features(self, v: List[AmbiguousFeature]) -> 'ResponseBuilder':
|
|
222
|
+
assert_list_type(v, AmbiguousFeature)
|
|
223
|
+
self.ambiguous_features = v
|
|
224
|
+
return self
|
|
225
|
+
|
|
226
|
+
def set_answers(self, v: List[Answer]) -> 'ResponseBuilder':
|
|
227
|
+
assert_list_type(v, Answer)
|
|
228
|
+
self.answers = v
|
|
229
|
+
return self
|
|
230
|
+
|
|
231
|
+
def set_geocoded_features(self, v: List[GeocodedFeature]):
|
|
232
|
+
'''
|
|
233
|
+
Exactly matching non-exploding features, i.e. one feature per answer
|
|
234
|
+
'''
|
|
235
|
+
assert_list_type(v, GeocodedFeature)
|
|
236
|
+
self.answers = [Answer([f]) for f in v]
|
|
237
|
+
return self
|
|
238
|
+
|
|
239
|
+
def build(self) -> Response:
|
|
240
|
+
if self.status == Status.error:
|
|
241
|
+
return ErrorResponse(self.message)
|
|
242
|
+
elif self.status == Status.success:
|
|
243
|
+
return SuccessResponse(self.message, self.level, self.answers)
|
|
244
|
+
elif self.status == Status.ambiguous:
|
|
245
|
+
return AmbiguousResponse(self.message, self.level, self.ambiguous_features)
|
|
246
|
+
else:
|
|
247
|
+
raise ValueError('Unknown status: ' + str(self.status))
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Union, Optional, List
|
|
3
|
+
|
|
4
|
+
from pandas import DataFrame
|
|
5
|
+
|
|
6
|
+
from .geocodes import Geocodes
|
|
7
|
+
|
|
8
|
+
LOCATION_COORDINATE_COLUMNS = {'lon', 'lat'}
|
|
9
|
+
LOCATION_RECTANGLE_COLUMNS = {'lonmin', 'latmin', 'lonmax', 'latmax'}
|
|
10
|
+
LOCATION_LIST_ERROR_MESSAGE = "Expected: location = [double lon1, double lat1, ... , double lonN, double latN]"
|
|
11
|
+
LOCATION_DATAFRAME_ERROR_MESSAGE = "Expected: location = DataFrame with [{}] or [{}] columns" \
|
|
12
|
+
.format(', '.join(LOCATION_COORDINATE_COLUMNS), ', '.join(LOCATION_RECTANGLE_COLUMNS))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RegionKind(Enum):
|
|
16
|
+
region_ids = 'region_ids'
|
|
17
|
+
region_name = 'region_name'
|
|
18
|
+
coordinates = 'coordinates'
|
|
19
|
+
data_frame = 'data_frame'
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _prepare_parent(parent: Union[str, Geocodes]) -> Optional[dict]:
|
|
23
|
+
if not parent:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
if isinstance(parent, Geocodes):
|
|
27
|
+
kind = RegionKind.region_ids
|
|
28
|
+
value = parent.unique_ids()
|
|
29
|
+
|
|
30
|
+
elif isinstance(parent, str):
|
|
31
|
+
kind = RegionKind.region_name
|
|
32
|
+
value = parent
|
|
33
|
+
|
|
34
|
+
else:
|
|
35
|
+
raise ValueError('Wrong parent type: ' + parent.__str__())
|
|
36
|
+
|
|
37
|
+
return {'type': kind.value, 'data': value}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _prepare_location(location: Union[str, Geocodes, List[float], DataFrame]) -> Optional[dict]:
|
|
41
|
+
if location is None:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
value = location
|
|
45
|
+
if isinstance(location, Geocodes):
|
|
46
|
+
kind = RegionKind.region_ids
|
|
47
|
+
value = location.unique_ids()
|
|
48
|
+
|
|
49
|
+
elif isinstance(location, str):
|
|
50
|
+
kind = RegionKind.region_name
|
|
51
|
+
|
|
52
|
+
elif isinstance(location, list):
|
|
53
|
+
if len(location) == 0 or len(location) % 2 != 0:
|
|
54
|
+
raise ValueError(LOCATION_LIST_ERROR_MESSAGE)
|
|
55
|
+
kind = RegionKind.coordinates
|
|
56
|
+
|
|
57
|
+
elif isinstance(location, DataFrame):
|
|
58
|
+
if not LOCATION_COORDINATE_COLUMNS.issubset(location.columns) and not LOCATION_RECTANGLE_COLUMNS.issubset(location.columns):
|
|
59
|
+
raise ValueError(LOCATION_DATAFRAME_ERROR_MESSAGE)
|
|
60
|
+
kind = RegionKind.data_frame
|
|
61
|
+
|
|
62
|
+
else:
|
|
63
|
+
raise ValueError('Wrong location type: ' + location.__str__())
|
|
64
|
+
|
|
65
|
+
return {'type': kind.value, 'data': value}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
import shapely
|
|
4
|
+
from geopandas import GeoDataFrame
|
|
5
|
+
from pandas import DataFrame
|
|
6
|
+
from shapely.geometry import box
|
|
7
|
+
|
|
8
|
+
from lets_plot.geo_data import PlacesDataFrameBuilder, abstractmethod
|
|
9
|
+
from lets_plot.geo_data.geocodes import _zip_answers
|
|
10
|
+
from lets_plot.geo_data.gis.request import RegionQuery, LevelKind
|
|
11
|
+
from lets_plot.geo_data.gis.response import Answer, GeocodedFeature, GeoRect, Boundary, Multipolygon, Polygon, GeoPoint
|
|
12
|
+
|
|
13
|
+
ShapelyPoint = shapely.geometry.Point
|
|
14
|
+
ShapelyLinearRing = shapely.geometry.LinearRing
|
|
15
|
+
ShapelyPolygon = shapely.geometry.Polygon
|
|
16
|
+
ShapelyMultiPolygon = shapely.geometry.MultiPolygon
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _create_geo_data_frame(data, geometry) -> DataFrame:
|
|
20
|
+
return GeoDataFrame(
|
|
21
|
+
data,
|
|
22
|
+
crs='EPSG:4326',
|
|
23
|
+
geometry=geometry
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class RectGeoDataFrame:
|
|
28
|
+
def __init__(self):
|
|
29
|
+
super().__init__()
|
|
30
|
+
self._lonmin: List[float] = []
|
|
31
|
+
self._latmin: List[float] = []
|
|
32
|
+
self._lonmax: List[float] = []
|
|
33
|
+
self._latmax: List[float] = []
|
|
34
|
+
|
|
35
|
+
def to_data_frame(self, answers: List[Answer], queries: List[RegionQuery], level_kind: LevelKind) -> DataFrame:
|
|
36
|
+
assert len(answers) == len(queries)
|
|
37
|
+
places = PlacesDataFrameBuilder(level_kind)
|
|
38
|
+
|
|
39
|
+
for query, answer in _zip_answers(queries, answers):
|
|
40
|
+
for feature in answer.features:
|
|
41
|
+
rects: List[GeoRect] = self._read_rect(feature)
|
|
42
|
+
for rect in rects:
|
|
43
|
+
places.append_row(query, feature)
|
|
44
|
+
self._lonmin.append(rect.start_lon)
|
|
45
|
+
self._latmin.append(rect.min_lat)
|
|
46
|
+
self._lonmax.append(rect.end_lon)
|
|
47
|
+
self._latmax.append(rect.max_lat)
|
|
48
|
+
|
|
49
|
+
geometry = [
|
|
50
|
+
box(lmt[0], lmt[1], lmt[2], lmt[3]) for lmt in zip(self._lonmin, self._latmin, self._lonmax, self._latmax)
|
|
51
|
+
]
|
|
52
|
+
return _create_geo_data_frame(places.build_dict(), geometry=geometry)
|
|
53
|
+
|
|
54
|
+
def _read_rect(self, feature: GeocodedFeature) -> List[GeoRect]:
|
|
55
|
+
rect: GeoRect = self._select_rect(feature)
|
|
56
|
+
if rect.crosses_antimeridian():
|
|
57
|
+
return [
|
|
58
|
+
GeoRect(start_lon=rect.start_lon, end_lon=180., min_lat=rect.min_lat, max_lat=rect.max_lat),
|
|
59
|
+
GeoRect(start_lon=-180., end_lon=rect.end_lon, min_lat=rect.min_lat, max_lat=rect.max_lat)
|
|
60
|
+
]
|
|
61
|
+
else:
|
|
62
|
+
return [rect]
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def _select_rect(self, feature: GeocodedFeature) -> GeoRect:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class CentroidsGeoDataFrame:
|
|
70
|
+
def __init__(self):
|
|
71
|
+
super().__init__()
|
|
72
|
+
self._lons: List[float] = []
|
|
73
|
+
self._lats: List[float] = []
|
|
74
|
+
|
|
75
|
+
def to_data_frame(self, answers: List[Answer], queries: List[RegionQuery], level_kind: LevelKind) -> DataFrame:
|
|
76
|
+
places = PlacesDataFrameBuilder(level_kind)
|
|
77
|
+
|
|
78
|
+
for query, answer in _zip_answers(queries, answers):
|
|
79
|
+
for feature in answer.features:
|
|
80
|
+
places.append_row(query, feature)
|
|
81
|
+
self._lons.append(feature.centroid.lon)
|
|
82
|
+
self._lats.append(feature.centroid.lat)
|
|
83
|
+
|
|
84
|
+
geometry = [ShapelyPoint(pnt[0], pnt[1]) for pnt in zip(self._lons, self._lats)]
|
|
85
|
+
return _create_geo_data_frame(places.build_dict(), geometry)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class BoundariesGeoDataFrame:
|
|
89
|
+
def __init__(self):
|
|
90
|
+
super().__init__()
|
|
91
|
+
|
|
92
|
+
def to_data_frame(self, answers: List[Answer], queries: List[RegionQuery], level_kind: LevelKind) -> DataFrame:
|
|
93
|
+
places = PlacesDataFrameBuilder(level_kind)
|
|
94
|
+
|
|
95
|
+
geometry = []
|
|
96
|
+
for query, answer in _zip_answers(queries, answers):
|
|
97
|
+
for feature in answer.features:
|
|
98
|
+
places.append_row(query, feature)
|
|
99
|
+
geometry.append(self._geo_parse_geometry(feature.boundary))
|
|
100
|
+
|
|
101
|
+
return _create_geo_data_frame(places.build_dict(), geometry=geometry)
|
|
102
|
+
|
|
103
|
+
def _geo_parse_geometry(self, boundary: Boundary):
|
|
104
|
+
geometry = boundary.geometry
|
|
105
|
+
if isinstance(geometry, GeoPoint):
|
|
106
|
+
return self._geo_parse_point(geometry)
|
|
107
|
+
|
|
108
|
+
if isinstance(geometry, Polygon):
|
|
109
|
+
return self._geo_parse_polygon(geometry)
|
|
110
|
+
|
|
111
|
+
if isinstance(geometry, Multipolygon):
|
|
112
|
+
return self._geo_parse_multipolygon(geometry)
|
|
113
|
+
|
|
114
|
+
raise ValueError('Invalid geometry type')
|
|
115
|
+
|
|
116
|
+
def _geo_parse_multipolygon(self, geometry: Multipolygon) -> ShapelyMultiPolygon:
|
|
117
|
+
geo_polygons: List[ShapelyPolygon] = [self._geo_parse_polygon(polygon) for polygon in geometry.polygons]
|
|
118
|
+
return ShapelyMultiPolygon(geo_polygons)
|
|
119
|
+
|
|
120
|
+
def _geo_parse_polygon(self, polygon: Polygon) -> ShapelyPolygon:
|
|
121
|
+
geo_rings: List[ShapelyLinearRing] = [
|
|
122
|
+
ShapelyLinearRing([(p.lon, p.lat) for p in ring.points]) for ring in polygon.rings
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
if len(geo_rings) == 0:
|
|
126
|
+
return ShapelyPolygon()
|
|
127
|
+
else:
|
|
128
|
+
return ShapelyPolygon(shell=geo_rings[0], holes=geo_rings[1:])
|
|
129
|
+
|
|
130
|
+
def _geo_parse_point(self, geometry_data: GeoPoint) -> ShapelyPoint:
|
|
131
|
+
return ShapelyPoint((geometry_data.lon, geometry_data.lat))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class LimitsGeoDataFrame(RectGeoDataFrame):
|
|
135
|
+
def _select_rect(self, feature: GeocodedFeature) -> GeoRect:
|
|
136
|
+
return feature.limit
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class PositionsGeoDataFrame(RectGeoDataFrame):
|
|
140
|
+
def _select_rect(self, feature: GeocodedFeature) -> GeoRect:
|
|
141
|
+
return feature.position
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
def assert_type(obj, *types):
|
|
2
|
+
assert isinstance(obj, types), 'Invalid type: \nActual: ' + str(type(obj)) + ' (' + str(obj) + ')\nExpected: ' + str(types)
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def assert_optional_type(obj, *types):
|
|
6
|
+
if obj is None:
|
|
7
|
+
return
|
|
8
|
+
assert_type(obj, *types)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def assert_list_type(obj, *types):
|
|
12
|
+
assert_type(obj, list, tuple)
|
|
13
|
+
for v in obj:
|
|
14
|
+
assert_type(v, *types)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def assert_optional_list_type(obj, *types):
|
|
18
|
+
if obj is None:
|
|
19
|
+
return
|
|
20
|
+
assert_list_type(obj, *types)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def assert_optional_tuple_type(obj, *types):
|
|
24
|
+
if obj is None:
|
|
25
|
+
return
|
|
26
|
+
assert_tuple_type(obj, *types)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def assert_tuple_type(obj, *types):
|
|
30
|
+
assert_type(obj, tuple)
|
|
31
|
+
assert len(obj) == len(types)
|
|
32
|
+
|
|
33
|
+
for v, t in zip(obj, types):
|
|
34
|
+
assert_type(v, t)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Copyright (c) 2021. 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
|
+
|
|
4
|
+
DF_COLUMN_ID = 'id'
|
|
5
|
+
DF_COLUMN_POSITION = 'position'
|
|
6
|
+
DF_COLUMN_LIMIT = 'limit'
|
|
7
|
+
DF_COLUMN_CENTROID = 'centroid'
|
|
8
|
+
DF_COLUMN_FOUND_NAME = 'found name'
|
|
9
|
+
DF_COLUMN_HIGHLIGHTS = 'highlights'
|
|
10
|
+
DF_COLUMN_CITY = 'city'
|
|
11
|
+
DF_COLUMN_COUNTRY = 'country'
|
|
12
|
+
DF_COLUMN_STATE = 'state'
|
|
13
|
+
DF_COLUMN_COUNTY = 'county'
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Copyright (c) 2021. 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 typing import List, Any
|
|
4
|
+
|
|
5
|
+
from lets_plot.geo_data_internals.constants import DF_COLUMN_CITY, DF_COLUMN_COUNTY, DF_COLUMN_STATE, DF_COLUMN_COUNTRY
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def find_geo_names(obj) -> List[str]:
|
|
9
|
+
if is_geocoder(obj):
|
|
10
|
+
data = obj.get_geocodes()
|
|
11
|
+
else:
|
|
12
|
+
data = obj
|
|
13
|
+
|
|
14
|
+
names = []
|
|
15
|
+
if DF_COLUMN_CITY in data:
|
|
16
|
+
names.append(DF_COLUMN_CITY)
|
|
17
|
+
if DF_COLUMN_COUNTY in data:
|
|
18
|
+
names.append(DF_COLUMN_COUNTY)
|
|
19
|
+
if DF_COLUMN_STATE in data:
|
|
20
|
+
names.append(DF_COLUMN_STATE)
|
|
21
|
+
if DF_COLUMN_COUNTRY in data:
|
|
22
|
+
names.append(DF_COLUMN_COUNTRY)
|
|
23
|
+
|
|
24
|
+
return names
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def is_geocoder(v: Any) -> bool:
|
|
28
|
+
# do not import Geocoder directly to suppress OSM attribution from geo_data package
|
|
29
|
+
if v is None:
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
return any(base.__name__ == 'Geocoder' for base in type(v).mro())
|
|
33
|
+
|
lets_plot/mapping.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
|
|
4
|
+
__all__ = ['as_discrete']
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MappingMeta:
|
|
8
|
+
def __init__(self, variable, annotation, levels, **parameters):
|
|
9
|
+
if variable is None:
|
|
10
|
+
raise ValueError("variable can't be none")
|
|
11
|
+
|
|
12
|
+
if annotation is None:
|
|
13
|
+
raise ValueError("annotation can't be none")
|
|
14
|
+
|
|
15
|
+
self.variable = variable
|
|
16
|
+
self.annotation = annotation
|
|
17
|
+
self.levels = levels
|
|
18
|
+
self.parameters = parameters
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def as_discrete(variable, label=None, order_by=None, order=None, levels=None):
|
|
22
|
+
"""
|
|
23
|
+
The function converts a column to a discrete scale and allows you to specify the order of its values.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
variable : str
|
|
28
|
+
The name of the variable.
|
|
29
|
+
label : str
|
|
30
|
+
The name of the scale - used as the axis label or the legend title.
|
|
31
|
+
order_by : str
|
|
32
|
+
The variable name to order by.
|
|
33
|
+
order : int
|
|
34
|
+
The ordering direction. 1 for ascending, -1 for descending.
|
|
35
|
+
levels : list
|
|
36
|
+
The list of values that defines a specific order of categories.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
``MappingMeta`` or list
|
|
41
|
+
Variable meta information.
|
|
42
|
+
|
|
43
|
+
Notes
|
|
44
|
+
-----
|
|
45
|
+
The plot will use a discrete scale for the aesthetic mapping.
|
|
46
|
+
It is similar to the ``factor()`` function from R but works differently - there is no data transformation.
|
|
47
|
+
|
|
48
|
+
To enable ordering mode, at least one ordering parameter (``order_by`` or ``order``) should be specified.
|
|
49
|
+
By the default, it will use descending direction and ordering by eigenvalues.
|
|
50
|
+
You cannot specify different order settings for the same variable.
|
|
51
|
+
But if these settings don't contradict each other, they will be combined.
|
|
52
|
+
|
|
53
|
+
Examples
|
|
54
|
+
--------
|
|
55
|
+
.. jupyter-execute::
|
|
56
|
+
:linenos:
|
|
57
|
+
:emphasize-lines: 12
|
|
58
|
+
|
|
59
|
+
import numpy as np
|
|
60
|
+
from lets_plot import *
|
|
61
|
+
LetsPlot.setup_html()
|
|
62
|
+
n = 100
|
|
63
|
+
np.random.seed(42)
|
|
64
|
+
data = {
|
|
65
|
+
'x': np.random.normal(size=n),
|
|
66
|
+
'y': np.random.normal(size=n),
|
|
67
|
+
'c': np.random.randint(5, size=n),
|
|
68
|
+
}
|
|
69
|
+
ggplot(data, aes('x', 'y')) + \\
|
|
70
|
+
geom_point(aes(color=as_discrete('c')))
|
|
71
|
+
|
|
72
|
+
|
|
|
73
|
+
|
|
74
|
+
.. jupyter-execute::
|
|
75
|
+
:linenos:
|
|
76
|
+
:emphasize-lines: 11
|
|
77
|
+
|
|
78
|
+
import numpy as np
|
|
79
|
+
from lets_plot import *
|
|
80
|
+
LetsPlot.setup_html()
|
|
81
|
+
n = 100
|
|
82
|
+
np.random.seed(42)
|
|
83
|
+
data = {
|
|
84
|
+
'x': np.random.uniform(size=100),
|
|
85
|
+
'c': np.random.choice(list('abcde'), size=100),
|
|
86
|
+
}
|
|
87
|
+
ggplot(data) + \\
|
|
88
|
+
geom_boxplot(aes(as_discrete('c', label='class', order=1), 'x'))
|
|
89
|
+
|
|
90
|
+
|
|
|
91
|
+
|
|
92
|
+
.. jupyter-execute::
|
|
93
|
+
:linenos:
|
|
94
|
+
:emphasize-lines: 12-13
|
|
95
|
+
|
|
96
|
+
import numpy as np
|
|
97
|
+
from lets_plot import *
|
|
98
|
+
LetsPlot.setup_html()
|
|
99
|
+
n = 100
|
|
100
|
+
np.random.seed(42)
|
|
101
|
+
data = {
|
|
102
|
+
'x': np.random.normal(size=n),
|
|
103
|
+
'c': np.random.choice(list('abcde'), size=n),
|
|
104
|
+
'i': np.random.randint(3, size=n),
|
|
105
|
+
}
|
|
106
|
+
ggplot(data) + \\
|
|
107
|
+
geom_bar(aes(as_discrete('c', order=1, order_by='..count..'), 'x', \\
|
|
108
|
+
fill=as_discrete('i', order=1, order_by='..count..')))
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
if isinstance(variable, str):
|
|
112
|
+
label = variable if label is None else label
|
|
113
|
+
return MappingMeta(variable, 'as_discrete', levels=levels, label=label, order_by=order_by, order=order)
|
|
114
|
+
# aes(x=as_discrete([1, 2, 3])) - pass as is
|
|
115
|
+
return variable
|