cave-utils 2.2.0b4__tar.gz → 2.2.1__tar.gz
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.
- {cave_utils-2.2.0b4/cave_utils.egg-info → cave_utils-2.2.1}/PKG-INFO +1 -1
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/__init__.py +2 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/api/__init__.py +2 -1
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/api/appBar.py +1 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/api/extraKwargs.py +1 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/api/globalOutputs.py +1 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/api/groupedOutputs.py +18 -4
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/api/mapFeatures.py +22 -7
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/api/maps.py +1 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/api/pages.py +2 -1
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/api/panes.py +1 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/api/settings.py +2 -1
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/api_utils/general.py +9 -8
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/api_utils/validator.py +1 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/api_utils/validator_utils.py +22 -17
- cave_utils-2.2.1/cave_utils/geo_utils.py +115 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/socket.py +0 -1
- {cave_utils-2.2.0b4 → cave_utils-2.2.1/cave_utils.egg-info}/PKG-INFO +1 -1
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils.egg-info/SOURCES.txt +2 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/pyproject.toml +1 -1
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/setup.cfg +1 -1
- cave_utils-2.2.1/test/test_geo_utils.py +17 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/LICENSE +0 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/NOTICE.md +0 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/README.md +0 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/api_utils/__init__.py +0 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/arguments.py +0 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/builders/__init__.py +0 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/builders/groups.py +0 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils/log.py +0 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils.egg-info/dependency_links.txt +0 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils.egg-info/requires.txt +0 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/cave_utils.egg-info/top_level.txt +0 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/test/test_arguments.py +0 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/test/test_builders_groups.py +0 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/test/test_import.py +0 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/test/test_log.py +0 -0
- {cave_utils-2.2.0b4 → cave_utils-2.2.1}/test/test_validator.py +0 -0
|
@@ -5,7 +5,9 @@ On the left hand side, you will find all available submodules, and their associa
|
|
|
5
5
|
|
|
6
6
|
Click on a submodule to see the documentation for that submodule.
|
|
7
7
|
"""
|
|
8
|
+
|
|
8
9
|
from .log import LogObject, LogHelper
|
|
9
10
|
from .socket import Socket
|
|
10
11
|
from .api_utils.validator import Validator
|
|
11
12
|
from .arguments import Arguments
|
|
13
|
+
from .geo_utils import GeoUtils
|
|
@@ -11,6 +11,7 @@ Submodules (and their classes) are used to define the api at each level. You can
|
|
|
11
11
|
|
|
12
12
|
See the left hand side for all available submodules.
|
|
13
13
|
"""
|
|
14
|
+
|
|
14
15
|
from cave_utils.api_utils.validator_utils import *
|
|
15
16
|
from cave_utils.api.extraKwargs import extraKwargs
|
|
16
17
|
from cave_utils.api.settings import settings
|
|
@@ -97,7 +98,7 @@ class Root(ApiValidator):
|
|
|
97
98
|
)
|
|
98
99
|
# Special logic to add timeLength to kwargs
|
|
99
100
|
# This is used to validate timeValues across the app
|
|
100
|
-
kwargs["timeLength"] = pamda.path(["settings","time","timeLength"], self.data)
|
|
101
|
+
kwargs["timeLength"] = pamda.path(["settings", "time", "timeLength"], self.data)
|
|
101
102
|
# Validate panes
|
|
102
103
|
panes_data = self.data.get("panes")
|
|
103
104
|
pane_validPaneIds = []
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
These outputs should be general to the entire application and they can be compared across sessions.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from cave_utils.api_utils.validator_utils import ApiValidator, CustomKeyValidator
|
|
6
7
|
from cave_utils.api_utils.general import props, values, layout
|
|
7
8
|
import type_enforced
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Create grouped outputs for building generalized charts and tables.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from cave_utils.api_utils.validator_utils import ApiValidator, CustomKeyValidator
|
|
5
6
|
import type_enforced
|
|
6
7
|
from pamda import pamda
|
|
@@ -348,7 +349,14 @@ class groupedOutputs_groupings_star_levels_star(ApiValidator):
|
|
|
348
349
|
"""
|
|
349
350
|
|
|
350
351
|
@staticmethod
|
|
351
|
-
def spec(
|
|
352
|
+
def spec(
|
|
353
|
+
name: str,
|
|
354
|
+
parent: [str, None] = None,
|
|
355
|
+
ordering: [list, None] = None,
|
|
356
|
+
orderWithParent: bool = True,
|
|
357
|
+
coloring: [dict, None] = None,
|
|
358
|
+
**kwargs,
|
|
359
|
+
):
|
|
352
360
|
"""
|
|
353
361
|
Arguments:
|
|
354
362
|
|
|
@@ -387,15 +395,21 @@ class groupedOutputs_groupings_star_levels_star(ApiValidator):
|
|
|
387
395
|
ordering = self.data.get("ordering")
|
|
388
396
|
if ordering is not None:
|
|
389
397
|
self.__check_subset_valid__(
|
|
390
|
-
subset=ordering,
|
|
398
|
+
subset=ordering,
|
|
399
|
+
valid_values=kwargs.get("acceptable_data_levels", {}).get(
|
|
400
|
+
kwargs.get("CustomKeyValidatorFieldId"), []
|
|
401
|
+
),
|
|
402
|
+
prepend_path=["ordering"],
|
|
391
403
|
)
|
|
392
404
|
|
|
393
405
|
coloring = self.data.get("coloring")
|
|
394
406
|
if coloring is not None:
|
|
395
407
|
self.__check_subset_valid__(
|
|
396
408
|
subset=list(coloring.keys()),
|
|
397
|
-
valid_values=kwargs.get("acceptable_data_levels", {}).get(
|
|
409
|
+
valid_values=kwargs.get("acceptable_data_levels", {}).get(
|
|
410
|
+
kwargs.get("CustomKeyValidatorFieldId"), []
|
|
411
|
+
),
|
|
398
412
|
prepend_path=["coloring"],
|
|
399
413
|
)
|
|
400
414
|
for key, value in coloring.items():
|
|
401
|
-
self.__check_rgba_string_valid__(rgba_string=value, prepend_path=[
|
|
415
|
+
self.__check_rgba_string_valid__(rgba_string=value, prepend_path=["coloring", key])
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Create visualizations for your map, including `arc`s, `node`s, and `geo`s, and customize their appearance.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from cave_utils.api_utils.validator_utils import ApiValidator, CustomKeyValidator
|
|
5
6
|
from cave_utils.api_utils.general import props, valueLists, layout
|
|
6
7
|
import type_enforced
|
|
@@ -155,10 +156,8 @@ class mapFeatures_data_star_data(ApiValidator):
|
|
|
155
156
|
**kwargs,
|
|
156
157
|
)
|
|
157
158
|
# Validate that all lengths are the same
|
|
158
|
-
lengths = [
|
|
159
|
-
len(v) for k, v in
|
|
160
|
-
] + [
|
|
161
|
-
len(v) for k, v in valueLists_data.items() if k not in ['timeValues', 'order']
|
|
159
|
+
lengths = [len(v) for k, v in location_data.items() if k not in ["timeValues", "order"]] + [
|
|
160
|
+
len(v) for k, v in valueLists_data.items() if k not in ["timeValues", "order"]
|
|
162
161
|
]
|
|
163
162
|
if len(set(lengths)) > 1:
|
|
164
163
|
self.__error__(msg=f"location and valueLists keys must have the same length.", path=[])
|
|
@@ -193,10 +192,26 @@ class mapFeatures_data_star_data_location(ApiValidator):
|
|
|
193
192
|
elif layer_type == "arc":
|
|
194
193
|
if layer_geoJson is not None:
|
|
195
194
|
required_keys = ["geoJsonValue"]
|
|
196
|
-
optional_keys += [
|
|
195
|
+
optional_keys += [
|
|
196
|
+
"path",
|
|
197
|
+
"startLatitude",
|
|
198
|
+
"startLongitude",
|
|
199
|
+
"endLatitude",
|
|
200
|
+
"endLongitude",
|
|
201
|
+
"startAltitude",
|
|
202
|
+
"endAltitude",
|
|
203
|
+
]
|
|
197
204
|
elif "path" in passed_keys:
|
|
198
205
|
required_keys = ["path"]
|
|
199
|
-
optional_keys += [
|
|
206
|
+
optional_keys += [
|
|
207
|
+
"startLatitude",
|
|
208
|
+
"startLongitude",
|
|
209
|
+
"endLatitude",
|
|
210
|
+
"endLongitude",
|
|
211
|
+
"geoJsonValue",
|
|
212
|
+
"startAltitude",
|
|
213
|
+
"endAltitude",
|
|
214
|
+
]
|
|
200
215
|
else:
|
|
201
216
|
required_keys = ["startLatitude", "startLongitude", "endLatitude", "endLongitude"]
|
|
202
217
|
optional_keys += ["startAltitude", "endAltitude", "geoJsonValue", "path"]
|
|
@@ -215,7 +230,7 @@ class mapFeatures_data_star_data_location(ApiValidator):
|
|
|
215
230
|
)
|
|
216
231
|
continue
|
|
217
232
|
if key == "geoJsonValue":
|
|
218
|
-
if len(value_list)!=len(set(value_list)):
|
|
233
|
+
if len(value_list) != len(set(value_list)):
|
|
219
234
|
self.__warn__(
|
|
220
235
|
msg=f"`geoJsonValue` should be a list of unique values. Otherwise, the corresponding map feature may not render correctly.",
|
|
221
236
|
path=[key],
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Configure your application's pages.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from cave_utils.api_utils.validator_utils import ApiValidator, CustomKeyValidator
|
|
5
6
|
import type_enforced
|
|
6
7
|
|
|
@@ -137,7 +138,7 @@ class pages_data_star_pageLayout(ApiValidator):
|
|
|
137
138
|
* **`maximized`**: `[bool]` = `False` → Whether or not the layout should be maximized.
|
|
138
139
|
* **Note**: If more than one chart belonging to the same page layout is set to `True`, the first one found in the list will take precedence.
|
|
139
140
|
* **`defaultToZero`**: `[bool]` = `False` → Whether or not the chart should default missing values to zero.
|
|
140
|
-
|
|
141
|
+
|
|
141
142
|
[area chart]: https://en.wikipedia.org/wiki/Area_chart
|
|
142
143
|
[bar chart]: https://en.wikipedia.org/wiki/Bar_chart
|
|
143
144
|
[stacked bar chart]: https://en.wikipedia.org/wiki/Bar_chart
|
|
@@ -3,6 +3,7 @@ Panes serve as main containers for UI controls such as toggles, text
|
|
|
3
3
|
and number fields, sliders, etc. They can also contain buttons that
|
|
4
4
|
facilitate interaction with actionable data.
|
|
5
5
|
"""
|
|
6
|
+
|
|
6
7
|
from cave_utils.api_utils.validator_utils import ApiValidator, CustomKeyValidator
|
|
7
8
|
from cave_utils.api_utils.general import props, values, layout
|
|
8
9
|
import type_enforced
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Configure general settings for your application like the icons to use, how to sync data with the server, and more.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from cave_utils.api_utils.validator_utils import *
|
|
5
6
|
import type_enforced
|
|
6
7
|
|
|
@@ -291,7 +292,7 @@ class settings_time(ApiValidator):
|
|
|
291
292
|
* **Example**: `"Decade"`.
|
|
292
293
|
"""
|
|
293
294
|
return {"kwargs": kwargs, "accepted_values": {}}
|
|
294
|
-
|
|
295
|
+
|
|
295
296
|
def __extend_spec__(self, **kwargs):
|
|
296
297
|
timeLength = self.data.get("timeLength")
|
|
297
298
|
if timeLength < 1:
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""
|
|
2
2
|
General API Spec items that are found in multiple places. This is not a key that should be passed as part of your `session_data`.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from pamda import pamda
|
|
5
6
|
import type_enforced
|
|
6
7
|
from cave_utils.api_utils.validator_utils import ApiValidator, CustomKeyValidator
|
|
7
|
-
from typing import Literal
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
@type_enforced.Enforcer
|
|
@@ -323,13 +323,13 @@ class props(ApiValidator):
|
|
|
323
323
|
if k not in required_fields + optional_fields:
|
|
324
324
|
kwargs[k] = v
|
|
325
325
|
notationDisplay_options_dict = {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
326
|
+
"compact": ["short", "long"],
|
|
327
|
+
"scientific": ["e", "e+", "E", "E+", "x10^", "x10^+"],
|
|
328
|
+
"engineering": ["e", "e+", "E", "E+", "x10^", "x10^+"],
|
|
329
|
+
"standard": [],
|
|
330
330
|
}
|
|
331
|
-
notation = passed_values.get(
|
|
332
|
-
legendNotation = passed_values.get(
|
|
331
|
+
notation = passed_values.get("notation", "standard")
|
|
332
|
+
legendNotation = passed_values.get("legendNotation", "standard")
|
|
333
333
|
view_options_dict = {
|
|
334
334
|
"date": ["year", "month", "day"],
|
|
335
335
|
"time": ["hours", "minutes", "seconds"],
|
|
@@ -348,7 +348,7 @@ class props(ApiValidator):
|
|
|
348
348
|
"legendNotationDisplay": notationDisplay_options_dict.get(legendNotation, []),
|
|
349
349
|
"variant": {
|
|
350
350
|
"head": ["column", "row", "icon", "iconRow"],
|
|
351
|
-
"text": ["textarea"],
|
|
351
|
+
"text": ["single", "textarea"],
|
|
352
352
|
"num": ["field", "slider", "icon", "iconCompact"],
|
|
353
353
|
"selector": [
|
|
354
354
|
"dropdown",
|
|
@@ -377,6 +377,7 @@ class props(ApiValidator):
|
|
|
377
377
|
**kwargs,
|
|
378
378
|
)
|
|
379
379
|
|
|
380
|
+
|
|
380
381
|
@type_enforced.Enforcer
|
|
381
382
|
class props_options(ApiValidator):
|
|
382
383
|
@staticmethod
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Special utility functions to help in validating your data against the CAVE API. This is not a key that should be passed as part of your `session_data`.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from pamda import pamda
|
|
5
6
|
import type_enforced
|
|
6
7
|
import re, datetime
|
|
@@ -24,7 +25,7 @@ class ApiValidator:
|
|
|
24
25
|
"accepted_values": {},
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
def __validate__(self, data:dict, log: LogObject, prepend_path: list[str] = list(), **kwargs):
|
|
28
|
+
def __validate__(self, data: dict, log: LogObject, prepend_path: list[str] = list(), **kwargs):
|
|
28
29
|
"""
|
|
29
30
|
Run the API validation process for the passed data.
|
|
30
31
|
"""
|
|
@@ -39,7 +40,7 @@ class ApiValidator:
|
|
|
39
40
|
if extra_kwargs != {}:
|
|
40
41
|
self.__warn__(
|
|
41
42
|
msg=f"Unknown Fields: {str(list(extra_kwargs.keys()))}",
|
|
42
|
-
)
|
|
43
|
+
)
|
|
43
44
|
|
|
44
45
|
except Exception as e:
|
|
45
46
|
self.__error__(
|
|
@@ -77,7 +78,7 @@ class ApiValidator:
|
|
|
77
78
|
|
|
78
79
|
# Additional core validations for generic terms like `order` and `timeValues`
|
|
79
80
|
def __genericKeyValidation__(self, **kwargs):
|
|
80
|
-
# Remove `timeValues` out prior to each level validation
|
|
81
|
+
# Remove `timeValues` out prior to each level validation
|
|
81
82
|
data_timeValues = self.data.pop("timeValues", None)
|
|
82
83
|
# Remove `order` out prior to each level validation
|
|
83
84
|
data_order = self.data.pop("order", None)
|
|
@@ -85,20 +86,22 @@ class ApiValidator:
|
|
|
85
86
|
timeLength = kwargs.get("timeLength")
|
|
86
87
|
if timeLength is None:
|
|
87
88
|
self.__error__(
|
|
88
|
-
path=[
|
|
89
|
+
path=["timeValues"],
|
|
89
90
|
msg="`settings.time.timeLength` must be specified to validate `timeValues`",
|
|
90
91
|
)
|
|
91
92
|
else:
|
|
92
93
|
self.__timeValues_validation__(timeValues=data_timeValues, timeLength=timeLength)
|
|
93
94
|
if data_order is not None:
|
|
94
95
|
self.__order_validation__(order=data_order)
|
|
95
|
-
|
|
96
|
+
|
|
96
97
|
@type_enforced.Enforcer
|
|
97
|
-
def __order_validation__(self, order:dict[list[str,int]]):
|
|
98
|
+
def __order_validation__(self, order: dict[list[str, int]]):
|
|
98
99
|
"""
|
|
99
100
|
Check that the ordering options are valid
|
|
100
101
|
"""
|
|
101
|
-
orderable_data_keys = {
|
|
102
|
+
orderable_data_keys = {
|
|
103
|
+
key: list(value.keys()) for key, value in self.data.items() if isinstance(value, dict)
|
|
104
|
+
}
|
|
102
105
|
if self.__check_subset_valid__(
|
|
103
106
|
subset=list(order.keys()),
|
|
104
107
|
valid_values=list(orderable_data_keys.keys()),
|
|
@@ -110,15 +113,15 @@ class ApiValidator:
|
|
|
110
113
|
valid_values=orderable_data_keys[order_key],
|
|
111
114
|
prepend_path=["order", order_key],
|
|
112
115
|
)
|
|
113
|
-
|
|
116
|
+
|
|
114
117
|
@type_enforced.Enforcer
|
|
115
|
-
def __timeValues_validation__(self, timeValues: [dict[dict],list[dict]], timeLength:int):
|
|
118
|
+
def __timeValues_validation__(self, timeValues: [dict[dict], list[dict]], timeLength: int):
|
|
116
119
|
if len(timeValues) == 0:
|
|
117
120
|
return
|
|
118
121
|
if isinstance(timeValues, list):
|
|
119
122
|
if len(timeValues) != timeLength:
|
|
120
123
|
self.__error__(
|
|
121
|
-
path=[
|
|
124
|
+
path=["timeValues"],
|
|
122
125
|
msg=f"The length of `timeValues` (as a list) must be equal to `settings.time.timeLength` ({timeLength})",
|
|
123
126
|
)
|
|
124
127
|
return
|
|
@@ -126,22 +129,22 @@ class ApiValidator:
|
|
|
126
129
|
keys = list(timeValues.keys())
|
|
127
130
|
if not all(isinstance(key, int) for key in keys):
|
|
128
131
|
self.__error__(
|
|
129
|
-
path=[
|
|
132
|
+
path=["timeValues"],
|
|
130
133
|
msg="`timeValues` (as a dict) keys must be integers",
|
|
131
134
|
)
|
|
132
135
|
return
|
|
133
|
-
if not all(key >= 0 and key<timeLength for key in keys):
|
|
136
|
+
if not all(key >= 0 and key < timeLength for key in keys):
|
|
134
137
|
self.__error__(
|
|
135
|
-
path=[
|
|
138
|
+
path=["timeValues"],
|
|
136
139
|
msg=f"`timeValues` (as a dict) keys must be integers between 0 and {timeLength-1} inclusive (1 minus the value at `settings.time.timeLength`)",
|
|
137
140
|
)
|
|
138
141
|
return
|
|
139
142
|
timeValues = list(timeValues.values())
|
|
140
|
-
timeValueTypes = {k:type(v) for k,v in timeValues[0].items()}
|
|
143
|
+
timeValueTypes = {k: type(v) for k, v in timeValues[0].items()}
|
|
141
144
|
for timeValue in timeValues:
|
|
142
|
-
if timeValueTypes != {k:type(v) for k,v in timeValue.items()}:
|
|
145
|
+
if timeValueTypes != {k: type(v) for k, v in timeValue.items()}:
|
|
143
146
|
self.__error__(
|
|
144
|
-
path=[
|
|
147
|
+
path=["timeValues"],
|
|
145
148
|
msg="All timeValues must have the same keys and each key must have the same type",
|
|
146
149
|
)
|
|
147
150
|
return
|
|
@@ -285,7 +288,9 @@ class ApiValidator:
|
|
|
285
288
|
return False
|
|
286
289
|
return True
|
|
287
290
|
|
|
288
|
-
def __check_coord_path_valid__(
|
|
291
|
+
def __check_coord_path_valid__(
|
|
292
|
+
self, coord_path: list[list[int, float]], prepend_path: list[str] = list()
|
|
293
|
+
):
|
|
289
294
|
"""
|
|
290
295
|
Validate a coordinate path and if an issue is present, log an error
|
|
291
296
|
"""
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from pamda import pamda
|
|
2
|
+
import type_enforced
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class GeoUtils:
|
|
6
|
+
@type_enforced.Enforcer
|
|
7
|
+
@staticmethod
|
|
8
|
+
def create_shortest_paths_geojson(
|
|
9
|
+
geoGraph,
|
|
10
|
+
ids: list[int, str],
|
|
11
|
+
origin_latitudes: list[int, float],
|
|
12
|
+
origin_longitudes: list[int, float],
|
|
13
|
+
destination_latitudes: list[int, float],
|
|
14
|
+
destination_longitudes: list[int, float],
|
|
15
|
+
additional_properties: [list[dict], None] = None,
|
|
16
|
+
show_progress: bool = False,
|
|
17
|
+
filename: [str, None] = None,
|
|
18
|
+
**kwargs,
|
|
19
|
+
):
|
|
20
|
+
"""
|
|
21
|
+
Creates a geoJson output with the shortest paths between a list of
|
|
22
|
+
origin and destination points.
|
|
23
|
+
|
|
24
|
+
Arguments:
|
|
25
|
+
|
|
26
|
+
* **`geoGraph`**: `[geoGraph]` → A geoGraph object from scgraph.
|
|
27
|
+
* **`ids`**: `[list[int, str]]` → A list of identifiers for each path.
|
|
28
|
+
* Note: These are imputed into the output GeoJSON as an id property.
|
|
29
|
+
* **`origin_latitudes`**: `[list[int, float]]` →
|
|
30
|
+
* A list of latitudes for the origin points.
|
|
31
|
+
* **`origin_longitudes`**: `[list[int, float]]` →
|
|
32
|
+
* A list of longitudes for the origin points.
|
|
33
|
+
* **`destination_latitudes`**: `[list[int, float]]` →
|
|
34
|
+
* A list of latitudes for the destination points.
|
|
35
|
+
* **`destination_longitudes`**: `[list[int, float]]` →
|
|
36
|
+
* A list of longitudes for the destination points.
|
|
37
|
+
* **`additional_properties`**: `[list[dict], None]` →
|
|
38
|
+
* A list of dictionaries with additional properties for each path.
|
|
39
|
+
* Note: The dictionaries must have the same length as the input lists.
|
|
40
|
+
* Note: The dictionaries are imputed into the output GeoJSON as properties.
|
|
41
|
+
|
|
42
|
+
Optional Arguments:
|
|
43
|
+
|
|
44
|
+
* **`show_progress`**: `[bool]` →
|
|
45
|
+
* If True, shows the progress of the calculations.
|
|
46
|
+
* Default: False
|
|
47
|
+
* **`filename`**: `[str, None]` →
|
|
48
|
+
* If provided, saves the output GeoJSON to the specified filename.
|
|
49
|
+
* Default: None
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
|
|
53
|
+
* **`output`**: `[dict]` → A GeoJSON dictionary with the shortest paths given the input data.
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
if not hasattr(geoGraph, "get_shortest_path"):
|
|
57
|
+
raise ValueError("`geoGraph` must be a geoGraph object from scgraph")
|
|
58
|
+
len_items = len(ids)
|
|
59
|
+
if additional_properties is None:
|
|
60
|
+
additional_properties = [{} for i in range(len_items)]
|
|
61
|
+
data = {
|
|
62
|
+
"ids": ids,
|
|
63
|
+
"origin_latitudes": origin_latitudes,
|
|
64
|
+
"origin_longitudes": origin_longitudes,
|
|
65
|
+
"destination_latitudes": destination_latitudes,
|
|
66
|
+
"destination_longitudes": destination_longitudes,
|
|
67
|
+
"additional_properties": additional_properties,
|
|
68
|
+
}
|
|
69
|
+
# Check that all the lists have at least one element
|
|
70
|
+
if len_items == 0:
|
|
71
|
+
raise ValueError("All input lists must have at least one element")
|
|
72
|
+
# Check that all the lists have the same length
|
|
73
|
+
if len(set(map(len, data.values()))) != 1:
|
|
74
|
+
raise ValueError("All input lists must have the same length")
|
|
75
|
+
features = []
|
|
76
|
+
# Iterate over the data and calculate the shortest path for
|
|
77
|
+
# each origin and destination pair
|
|
78
|
+
for idx, item in enumerate(pamda.pivot(data)):
|
|
79
|
+
item = dict(item)
|
|
80
|
+
shortest_path_output = geoGraph.get_shortest_path(
|
|
81
|
+
origin_node={
|
|
82
|
+
"latitude": item["origin_latitudes"],
|
|
83
|
+
"longitude": item["origin_longitudes"],
|
|
84
|
+
},
|
|
85
|
+
destination_node={
|
|
86
|
+
"latitude": item["destination_latitudes"],
|
|
87
|
+
"longitude": item["destination_longitudes"],
|
|
88
|
+
},
|
|
89
|
+
output_coordinate_path="list_of_lists_long_first",
|
|
90
|
+
**kwargs,
|
|
91
|
+
)
|
|
92
|
+
# Append the calculated path to the features list in GeoJSON format
|
|
93
|
+
features.append(
|
|
94
|
+
{
|
|
95
|
+
"type": "Feature",
|
|
96
|
+
"geometry": {
|
|
97
|
+
"type": "LineString",
|
|
98
|
+
"coordinates": shortest_path_output["coordinate_path"],
|
|
99
|
+
},
|
|
100
|
+
"properties": {
|
|
101
|
+
"id": item["ids"],
|
|
102
|
+
"length": shortest_path_output["length"],
|
|
103
|
+
**item["additional_properties"],
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
if show_progress:
|
|
108
|
+
print(f"Paths Calculated: {idx}/{len_items}", end="\r")
|
|
109
|
+
if show_progress:
|
|
110
|
+
print(f"Paths Calculated: {len_items}/{len_items}")
|
|
111
|
+
# Create the GeoJSON output
|
|
112
|
+
output = {"type": "FeatureCollection", "features": features}
|
|
113
|
+
if filename is not None:
|
|
114
|
+
pamda.write_json(data=output, filename=filename)
|
|
115
|
+
return output
|
|
@@ -5,6 +5,7 @@ pyproject.toml
|
|
|
5
5
|
setup.cfg
|
|
6
6
|
cave_utils/__init__.py
|
|
7
7
|
cave_utils/arguments.py
|
|
8
|
+
cave_utils/geo_utils.py
|
|
8
9
|
cave_utils/log.py
|
|
9
10
|
cave_utils/socket.py
|
|
10
11
|
cave_utils.egg-info/PKG-INFO
|
|
@@ -30,6 +31,7 @@ cave_utils/builders/__init__.py
|
|
|
30
31
|
cave_utils/builders/groups.py
|
|
31
32
|
test/test_arguments.py
|
|
32
33
|
test/test_builders_groups.py
|
|
34
|
+
test/test_geo_utils.py
|
|
33
35
|
test/test_import.py
|
|
34
36
|
test/test_log.py
|
|
35
37
|
test/test_validator.py
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from scgraph.geographs.marnet import marnet_geograph
|
|
2
|
+
|
|
3
|
+
from cave_utils import GeoUtils
|
|
4
|
+
|
|
5
|
+
count = 100
|
|
6
|
+
print("Creating 100 shortest paths geojson...")
|
|
7
|
+
out = GeoUtils.create_shortest_paths_geojson(
|
|
8
|
+
geoGraph=marnet_geograph,
|
|
9
|
+
ids=[str(i) for i in range(count)],
|
|
10
|
+
origin_latitudes=[0 + i / 4 for i in range(count)],
|
|
11
|
+
origin_longitudes=[0 + i / 4 for i in range(count)],
|
|
12
|
+
destination_latitudes=[0 - i / 4 for i in range(count)],
|
|
13
|
+
destination_longitudes=[0 - i / 4 for i in range(count)],
|
|
14
|
+
show_progress=True,
|
|
15
|
+
# filename="test.geojson"
|
|
16
|
+
)
|
|
17
|
+
print("Done!")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|