nextmv 0.18.0__py3-none-any.whl → 1.0.0.dev2__py3-none-any.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.
- nextmv/__about__.py +1 -1
- nextmv/__entrypoint__.py +8 -13
- nextmv/__init__.py +53 -0
- nextmv/_serialization.py +96 -0
- nextmv/base_model.py +54 -9
- nextmv/cli/CONTRIBUTING.md +511 -0
- nextmv/cli/__init__.py +0 -0
- nextmv/cli/cloud/__init__.py +47 -0
- nextmv/cli/cloud/acceptance/__init__.py +27 -0
- nextmv/cli/cloud/acceptance/create.py +393 -0
- nextmv/cli/cloud/acceptance/delete.py +68 -0
- nextmv/cli/cloud/acceptance/get.py +104 -0
- nextmv/cli/cloud/acceptance/list.py +62 -0
- nextmv/cli/cloud/acceptance/update.py +95 -0
- nextmv/cli/cloud/account/__init__.py +28 -0
- nextmv/cli/cloud/account/create.py +83 -0
- nextmv/cli/cloud/account/delete.py +60 -0
- nextmv/cli/cloud/account/get.py +66 -0
- nextmv/cli/cloud/account/update.py +70 -0
- nextmv/cli/cloud/app/__init__.py +35 -0
- nextmv/cli/cloud/app/create.py +141 -0
- nextmv/cli/cloud/app/delete.py +58 -0
- nextmv/cli/cloud/app/exists.py +44 -0
- nextmv/cli/cloud/app/get.py +66 -0
- nextmv/cli/cloud/app/list.py +61 -0
- nextmv/cli/cloud/app/push.py +137 -0
- nextmv/cli/cloud/app/update.py +124 -0
- nextmv/cli/cloud/batch/__init__.py +29 -0
- nextmv/cli/cloud/batch/create.py +454 -0
- nextmv/cli/cloud/batch/delete.py +68 -0
- nextmv/cli/cloud/batch/get.py +104 -0
- nextmv/cli/cloud/batch/list.py +63 -0
- nextmv/cli/cloud/batch/metadata.py +66 -0
- nextmv/cli/cloud/batch/update.py +95 -0
- nextmv/cli/cloud/data/__init__.py +26 -0
- nextmv/cli/cloud/data/upload.py +162 -0
- nextmv/cli/cloud/ensemble/__init__.py +31 -0
- nextmv/cli/cloud/ensemble/create.py +414 -0
- nextmv/cli/cloud/ensemble/delete.py +67 -0
- nextmv/cli/cloud/ensemble/get.py +65 -0
- nextmv/cli/cloud/ensemble/update.py +103 -0
- nextmv/cli/cloud/input_set/__init__.py +30 -0
- nextmv/cli/cloud/input_set/create.py +170 -0
- nextmv/cli/cloud/input_set/get.py +63 -0
- nextmv/cli/cloud/input_set/list.py +63 -0
- nextmv/cli/cloud/input_set/update.py +123 -0
- nextmv/cli/cloud/instance/__init__.py +35 -0
- nextmv/cli/cloud/instance/create.py +290 -0
- nextmv/cli/cloud/instance/delete.py +62 -0
- nextmv/cli/cloud/instance/exists.py +39 -0
- nextmv/cli/cloud/instance/get.py +62 -0
- nextmv/cli/cloud/instance/list.py +60 -0
- nextmv/cli/cloud/instance/update.py +216 -0
- nextmv/cli/cloud/managed_input/__init__.py +31 -0
- nextmv/cli/cloud/managed_input/create.py +146 -0
- nextmv/cli/cloud/managed_input/delete.py +65 -0
- nextmv/cli/cloud/managed_input/get.py +63 -0
- nextmv/cli/cloud/managed_input/list.py +60 -0
- nextmv/cli/cloud/managed_input/update.py +97 -0
- nextmv/cli/cloud/run/__init__.py +37 -0
- nextmv/cli/cloud/run/cancel.py +37 -0
- nextmv/cli/cloud/run/create.py +530 -0
- nextmv/cli/cloud/run/get.py +199 -0
- nextmv/cli/cloud/run/input.py +86 -0
- nextmv/cli/cloud/run/list.py +80 -0
- nextmv/cli/cloud/run/logs.py +167 -0
- nextmv/cli/cloud/run/metadata.py +67 -0
- nextmv/cli/cloud/run/track.py +501 -0
- nextmv/cli/cloud/scenario/__init__.py +29 -0
- nextmv/cli/cloud/scenario/create.py +451 -0
- nextmv/cli/cloud/scenario/delete.py +65 -0
- nextmv/cli/cloud/scenario/get.py +102 -0
- nextmv/cli/cloud/scenario/list.py +63 -0
- nextmv/cli/cloud/scenario/metadata.py +67 -0
- nextmv/cli/cloud/scenario/update.py +93 -0
- nextmv/cli/cloud/secrets/__init__.py +33 -0
- nextmv/cli/cloud/secrets/create.py +206 -0
- nextmv/cli/cloud/secrets/delete.py +67 -0
- nextmv/cli/cloud/secrets/get.py +66 -0
- nextmv/cli/cloud/secrets/list.py +60 -0
- nextmv/cli/cloud/secrets/update.py +147 -0
- nextmv/cli/cloud/shadow/__init__.py +33 -0
- nextmv/cli/cloud/shadow/create.py +184 -0
- nextmv/cli/cloud/shadow/delete.py +68 -0
- nextmv/cli/cloud/shadow/get.py +61 -0
- nextmv/cli/cloud/shadow/list.py +63 -0
- nextmv/cli/cloud/shadow/metadata.py +66 -0
- nextmv/cli/cloud/shadow/start.py +43 -0
- nextmv/cli/cloud/shadow/stop.py +43 -0
- nextmv/cli/cloud/shadow/update.py +95 -0
- nextmv/cli/cloud/upload/__init__.py +22 -0
- nextmv/cli/cloud/upload/create.py +39 -0
- nextmv/cli/cloud/version/__init__.py +33 -0
- nextmv/cli/cloud/version/create.py +97 -0
- nextmv/cli/cloud/version/delete.py +62 -0
- nextmv/cli/cloud/version/exists.py +39 -0
- nextmv/cli/cloud/version/get.py +62 -0
- nextmv/cli/cloud/version/list.py +60 -0
- nextmv/cli/cloud/version/update.py +92 -0
- nextmv/cli/community/__init__.py +24 -0
- nextmv/cli/community/clone.py +270 -0
- nextmv/cli/community/list.py +265 -0
- nextmv/cli/configuration/__init__.py +23 -0
- nextmv/cli/configuration/config.py +195 -0
- nextmv/cli/configuration/create.py +94 -0
- nextmv/cli/configuration/delete.py +67 -0
- nextmv/cli/configuration/list.py +77 -0
- nextmv/cli/main.py +188 -0
- nextmv/cli/message.py +153 -0
- nextmv/cli/options.py +206 -0
- nextmv/cli/version.py +38 -0
- nextmv/cloud/__init__.py +71 -17
- nextmv/cloud/acceptance_test.py +757 -51
- nextmv/cloud/account.py +406 -17
- nextmv/cloud/application/__init__.py +957 -0
- nextmv/cloud/application/_acceptance.py +419 -0
- nextmv/cloud/application/_batch_scenario.py +860 -0
- nextmv/cloud/application/_ensemble.py +251 -0
- nextmv/cloud/application/_input_set.py +227 -0
- nextmv/cloud/application/_instance.py +289 -0
- nextmv/cloud/application/_managed_input.py +227 -0
- nextmv/cloud/application/_run.py +1393 -0
- nextmv/cloud/application/_secrets.py +294 -0
- nextmv/cloud/application/_shadow.py +314 -0
- nextmv/cloud/application/_utils.py +54 -0
- nextmv/cloud/application/_version.py +303 -0
- nextmv/cloud/assets.py +48 -0
- nextmv/cloud/batch_experiment.py +294 -33
- nextmv/cloud/client.py +307 -66
- nextmv/cloud/ensemble.py +247 -0
- nextmv/cloud/input_set.py +120 -2
- nextmv/cloud/instance.py +133 -8
- nextmv/cloud/integration.py +533 -0
- nextmv/cloud/package.py +168 -53
- nextmv/cloud/scenario.py +410 -0
- nextmv/cloud/secrets.py +234 -0
- nextmv/cloud/shadow.py +190 -0
- nextmv/cloud/url.py +73 -0
- nextmv/cloud/version.py +132 -4
- nextmv/default_app/.gitignore +1 -0
- nextmv/default_app/README.md +32 -0
- nextmv/default_app/app.yaml +12 -0
- nextmv/default_app/input.json +5 -0
- nextmv/default_app/main.py +37 -0
- nextmv/default_app/requirements.txt +2 -0
- nextmv/default_app/src/__init__.py +0 -0
- nextmv/default_app/src/visuals.py +36 -0
- nextmv/deprecated.py +47 -0
- nextmv/input.py +861 -90
- nextmv/local/__init__.py +5 -0
- nextmv/local/application.py +1251 -0
- nextmv/local/executor.py +1042 -0
- nextmv/local/geojson_handler.py +323 -0
- nextmv/local/local.py +97 -0
- nextmv/local/plotly_handler.py +61 -0
- nextmv/local/runner.py +274 -0
- nextmv/logger.py +80 -9
- nextmv/manifest.py +1466 -0
- nextmv/model.py +241 -66
- nextmv/options.py +708 -115
- nextmv/output.py +1301 -274
- nextmv/polling.py +325 -0
- nextmv/run.py +1702 -0
- nextmv/safe.py +145 -0
- nextmv/status.py +122 -0
- nextmv-1.0.0.dev2.dist-info/METADATA +311 -0
- nextmv-1.0.0.dev2.dist-info/RECORD +170 -0
- {nextmv-0.18.0.dist-info → nextmv-1.0.0.dev2.dist-info}/WHEEL +1 -1
- nextmv-1.0.0.dev2.dist-info/entry_points.txt +2 -0
- nextmv/cloud/application.py +0 -1405
- nextmv/cloud/manifest.py +0 -234
- nextmv/cloud/status.py +0 -29
- nextmv-0.18.0.dist-info/METADATA +0 -770
- nextmv-0.18.0.dist-info/RECORD +0 -25
- {nextmv-0.18.0.dist-info → nextmv-1.0.0.dev2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GeoJSON visualization handler module.
|
|
3
|
+
|
|
4
|
+
This module provides functionality to handle GeoJSON visualizations by converting
|
|
5
|
+
them to interactive HTML maps using Folium. It supports various GeoJSON formats
|
|
6
|
+
and automatically calculates optimal map positioning and zoom levels.
|
|
7
|
+
|
|
8
|
+
Functions
|
|
9
|
+
---------
|
|
10
|
+
handle_geojson_visual
|
|
11
|
+
Handle and write GeoJSON visuals to HTML files.
|
|
12
|
+
extract_coordinates
|
|
13
|
+
Recursively extract coordinates from nested coordinate structures.
|
|
14
|
+
calculate_map_center_and_zoom
|
|
15
|
+
Calculate the optimal center and zoom level for a GeoJSON map.
|
|
16
|
+
extract_geojson_fields
|
|
17
|
+
Extract available fields for tooltip and popup from GeoJSON data.
|
|
18
|
+
create_geojson_map
|
|
19
|
+
Create a folium map with GeoJSON data.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
|
|
25
|
+
import folium
|
|
26
|
+
|
|
27
|
+
from nextmv.logger import log
|
|
28
|
+
from nextmv.output import Asset
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def handle_geojson_visual(asset: Asset, visuals_dir: str) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Handle and write GeoJSON visuals to HTML files.
|
|
34
|
+
|
|
35
|
+
This function processes GeoJSON visualization assets and converts them to
|
|
36
|
+
interactive HTML maps using Folium. It handles multiple content formats
|
|
37
|
+
including dictionaries, lists, and JSON strings. Each visualization is
|
|
38
|
+
converted to a map with appropriate positioning and saved as an HTML file.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
asset : Asset
|
|
43
|
+
The asset containing the GeoJSON visualization data. The content can be
|
|
44
|
+
a dictionary (single GeoJSON), a list (multiple GeoJSONs), or a JSON
|
|
45
|
+
string representation.
|
|
46
|
+
visuals_dir : str
|
|
47
|
+
The directory path where the HTML files will be written.
|
|
48
|
+
|
|
49
|
+
Notes
|
|
50
|
+
-----
|
|
51
|
+
- For list content, each GeoJSON is saved with an index suffix
|
|
52
|
+
(e.g., "map_0.html", "map_1.html")
|
|
53
|
+
- For dict content, the GeoJSON is saved with the asset label
|
|
54
|
+
(e.g., "map.html")
|
|
55
|
+
- String content is parsed as JSON before processing
|
|
56
|
+
- Invalid JSON strings or unsupported content types are ignored with
|
|
57
|
+
appropriate logging
|
|
58
|
+
"""
|
|
59
|
+
if isinstance(asset.content, list):
|
|
60
|
+
for ix, content in enumerate(asset.content):
|
|
61
|
+
if isinstance(content, dict):
|
|
62
|
+
layer_name = f"{asset.visual.label} Layer {ix + 1}"
|
|
63
|
+
m = create_geojson_map(content, layer_name)
|
|
64
|
+
m.save(os.path.join(visuals_dir, f"{asset.visual.label}_{ix}.html"))
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
if isinstance(asset.content, dict):
|
|
68
|
+
layer_name = f"{asset.visual.label} Layer"
|
|
69
|
+
m = create_geojson_map(asset.content, layer_name)
|
|
70
|
+
m.save(os.path.join(visuals_dir, f"{asset.visual.label}.html"))
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
if isinstance(asset.content, str):
|
|
74
|
+
try:
|
|
75
|
+
geojson_data = json.loads(asset.content)
|
|
76
|
+
layer_name = f"{asset.visual.label} Layer"
|
|
77
|
+
m = create_geojson_map(geojson_data, layer_name)
|
|
78
|
+
m.save(os.path.join(visuals_dir, f"{asset.visual.label}.html"))
|
|
79
|
+
except json.JSONDecodeError:
|
|
80
|
+
log(f"Warning: Could not parse GeoJSON string content for {asset.visual.label}")
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
# If there is a different content type for geojson visuals, we ignore it for now
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def extract_coordinates(coords, all_coords) -> None:
|
|
87
|
+
"""
|
|
88
|
+
Recursively extract coordinates from nested coordinate structures.
|
|
89
|
+
|
|
90
|
+
This function traverses nested coordinate structures commonly found in
|
|
91
|
+
GeoJSON geometries and extracts all coordinate pairs. It handles various
|
|
92
|
+
geometry types by recursively processing nested arrays until it finds
|
|
93
|
+
coordinate pairs in [longitude, latitude] format.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
coords : list or tuple
|
|
98
|
+
The coordinate structure to extract from. Can be a nested list/tuple
|
|
99
|
+
containing coordinate pairs or other nested structures.
|
|
100
|
+
all_coords : list
|
|
101
|
+
A list to accumulate all extracted coordinate pairs. This list is
|
|
102
|
+
modified in-place to store [longitude, latitude] pairs.
|
|
103
|
+
|
|
104
|
+
Notes
|
|
105
|
+
-----
|
|
106
|
+
- Coordinate pairs are identified as lists/tuples with exactly 2 numeric
|
|
107
|
+
elements
|
|
108
|
+
- The function expects coordinates in [longitude, latitude] format as per
|
|
109
|
+
GeoJSON specification
|
|
110
|
+
- Nested structures are recursively processed to handle complex geometries
|
|
111
|
+
like Polygons and MultiPolygons
|
|
112
|
+
"""
|
|
113
|
+
if isinstance(coords, list):
|
|
114
|
+
if len(coords) == 2 and isinstance(coords[0], (int, float)) and isinstance(coords[1], (int, float)):
|
|
115
|
+
# This is a coordinate pair [lon, lat]
|
|
116
|
+
all_coords.append(coords)
|
|
117
|
+
else:
|
|
118
|
+
# This is a nested structure, recurse
|
|
119
|
+
for coord in coords:
|
|
120
|
+
extract_coordinates(coord, all_coords)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def calculate_map_center_and_zoom(geojson_data: dict) -> tuple[float, float, int]:
|
|
124
|
+
"""
|
|
125
|
+
Calculate the optimal center and zoom level for a GeoJSON map.
|
|
126
|
+
|
|
127
|
+
This function analyzes the geographic extent of GeoJSON features to
|
|
128
|
+
determine the best map center point and zoom level for visualization.
|
|
129
|
+
It extracts all coordinates from the features, calculates the centroid,
|
|
130
|
+
and determines an appropriate zoom level based on the data's geographic
|
|
131
|
+
spread.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
geojson_data : dict
|
|
136
|
+
A GeoJSON object containing features with geometric data. Should
|
|
137
|
+
follow the GeoJSON specification with a "features" key containing
|
|
138
|
+
an array of feature objects.
|
|
139
|
+
|
|
140
|
+
Returns
|
|
141
|
+
-------
|
|
142
|
+
tuple[float, float, int]
|
|
143
|
+
A tuple containing (center_latitude, center_longitude, zoom_level).
|
|
144
|
+
- center_latitude : float
|
|
145
|
+
The latitude coordinate for the map center
|
|
146
|
+
- center_longitude : float
|
|
147
|
+
The longitude coordinate for the map center
|
|
148
|
+
- zoom_level : int
|
|
149
|
+
The recommended zoom level (typically 4-12)
|
|
150
|
+
|
|
151
|
+
Notes
|
|
152
|
+
-----
|
|
153
|
+
- Default center is New York City (40.7128, -74.0060) with zoom level 12
|
|
154
|
+
- Zoom levels are calculated based on coordinate range:
|
|
155
|
+
- Range > 10 degrees: zoom level 4 (continental view)
|
|
156
|
+
- Range > 1 degree: zoom level 8 (regional view)
|
|
157
|
+
- Range > 0.1 degree: zoom level 10 (city view)
|
|
158
|
+
- Smaller ranges: zoom level 12 (neighborhood view)
|
|
159
|
+
- Falls back to defaults if no valid coordinates are found or errors occur
|
|
160
|
+
"""
|
|
161
|
+
default_lat, default_lon, default_zoom = 40.7128, -74.0060, 12
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
if "features" not in geojson_data or not geojson_data["features"]:
|
|
165
|
+
return default_lat, default_lon, default_zoom
|
|
166
|
+
|
|
167
|
+
# Calculate bounds from all features
|
|
168
|
+
all_coords = []
|
|
169
|
+
for feature in geojson_data["features"]:
|
|
170
|
+
if feature.get("geometry", {}).get("coordinates"):
|
|
171
|
+
coords = feature["geometry"]["coordinates"]
|
|
172
|
+
extract_coordinates(coords, all_coords)
|
|
173
|
+
|
|
174
|
+
if not all_coords:
|
|
175
|
+
return default_lat, default_lon, default_zoom
|
|
176
|
+
|
|
177
|
+
lats = [coord[1] for coord in all_coords]
|
|
178
|
+
lons = [coord[0] for coord in all_coords]
|
|
179
|
+
center_lat = sum(lats) / len(lats)
|
|
180
|
+
center_lon = sum(lons) / len(lons)
|
|
181
|
+
|
|
182
|
+
# Adjust zoom based on coordinate spread
|
|
183
|
+
lat_range = max(lats) - min(lats)
|
|
184
|
+
lon_range = max(lons) - min(lons)
|
|
185
|
+
max_range = max(lat_range, lon_range)
|
|
186
|
+
|
|
187
|
+
if max_range > 10:
|
|
188
|
+
zoom_level = 4
|
|
189
|
+
elif max_range > 1:
|
|
190
|
+
zoom_level = 8
|
|
191
|
+
elif max_range > 0.1:
|
|
192
|
+
zoom_level = 10
|
|
193
|
+
else:
|
|
194
|
+
zoom_level = default_zoom
|
|
195
|
+
|
|
196
|
+
return center_lat, center_lon, zoom_level
|
|
197
|
+
|
|
198
|
+
except (KeyError, TypeError, ValueError, IndexError) as e:
|
|
199
|
+
log(f"Warning: Error calculating map center and zoom from GeoJSON data: {e}")
|
|
200
|
+
return default_lat, default_lon, default_zoom
|
|
201
|
+
except Exception as e:
|
|
202
|
+
log(f"Warning: Unexpected error calculating map center and zoom: {e}")
|
|
203
|
+
return default_lat, default_lon, default_zoom
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def extract_geojson_fields(geojson_data: dict) -> tuple[list[str], list[str]]:
|
|
207
|
+
"""
|
|
208
|
+
Extract available fields for tooltip and popup from GeoJSON data.
|
|
209
|
+
|
|
210
|
+
This function analyzes the properties of GeoJSON features to identify
|
|
211
|
+
suitable fields for displaying in map tooltips and popups. It prioritizes
|
|
212
|
+
common field names and limits the number of fields to maintain usability.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
geojson_data : dict
|
|
217
|
+
A GeoJSON object containing features with properties. Should follow
|
|
218
|
+
the GeoJSON specification with features containing properties objects.
|
|
219
|
+
|
|
220
|
+
Returns
|
|
221
|
+
-------
|
|
222
|
+
tuple[list[str], list[str]]
|
|
223
|
+
A tuple containing (tooltip_fields, popup_fields).
|
|
224
|
+
- tooltip_fields : list[str]
|
|
225
|
+
List of field names suitable for tooltips (max 3 fields)
|
|
226
|
+
- popup_fields : list[str]
|
|
227
|
+
List of field names suitable for popups (max 5 fields)
|
|
228
|
+
|
|
229
|
+
Notes
|
|
230
|
+
-----
|
|
231
|
+
- Prioritizes common field names: "name", "title", "label", "id",
|
|
232
|
+
"popupContent", "description"
|
|
233
|
+
- Tooltip fields are limited to 3 to prevent overcrowding
|
|
234
|
+
- Popup fields are limited to 5 to maintain readability
|
|
235
|
+
- Returns empty lists if no features or properties are found
|
|
236
|
+
- Gracefully handles malformed GeoJSON data by returning empty lists
|
|
237
|
+
"""
|
|
238
|
+
tooltip_fields = []
|
|
239
|
+
popup_fields = []
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
if "features" not in geojson_data or not geojson_data["features"]:
|
|
243
|
+
return tooltip_fields, popup_fields
|
|
244
|
+
|
|
245
|
+
# Get fields from the first feature's properties
|
|
246
|
+
first_feature = geojson_data["features"][0]
|
|
247
|
+
if "properties" not in first_feature or not first_feature["properties"]:
|
|
248
|
+
return tooltip_fields, popup_fields
|
|
249
|
+
|
|
250
|
+
available_fields = list(first_feature["properties"].keys())
|
|
251
|
+
# Prioritize common field names for tooltip/popup
|
|
252
|
+
priority_fields = ["name", "title", "label", "id", "popupContent", "description"]
|
|
253
|
+
|
|
254
|
+
for field in priority_fields:
|
|
255
|
+
if field in available_fields:
|
|
256
|
+
tooltip_fields.append(field)
|
|
257
|
+
popup_fields.append(field)
|
|
258
|
+
|
|
259
|
+
# Add remaining fields up to a reasonable limit
|
|
260
|
+
for field in available_fields:
|
|
261
|
+
if field not in tooltip_fields and len(tooltip_fields) < 3:
|
|
262
|
+
tooltip_fields.append(field)
|
|
263
|
+
if field not in popup_fields and len(popup_fields) < 5:
|
|
264
|
+
popup_fields.append(field)
|
|
265
|
+
|
|
266
|
+
except (KeyError, TypeError, IndexError) as e:
|
|
267
|
+
log(f"Warning: Error extracting GeoJSON fields: {e}")
|
|
268
|
+
except Exception as e:
|
|
269
|
+
log(f"Warning: Unexpected error extracting GeoJSON fields: {e}")
|
|
270
|
+
|
|
271
|
+
return tooltip_fields, popup_fields
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def create_geojson_map(geojson_data: dict, layer_name: str = "GeoJSON Layer") -> folium.Map:
|
|
275
|
+
"""
|
|
276
|
+
Create a folium map with GeoJSON data.
|
|
277
|
+
|
|
278
|
+
This function creates an interactive map using Folium with the provided
|
|
279
|
+
GeoJSON data. It automatically calculates the optimal center point and
|
|
280
|
+
zoom level, extracts relevant fields for tooltips and popups, and
|
|
281
|
+
configures the map with appropriate interactive features.
|
|
282
|
+
|
|
283
|
+
Parameters
|
|
284
|
+
----------
|
|
285
|
+
geojson_data : dict
|
|
286
|
+
A GeoJSON object containing the geographic data to display. Should
|
|
287
|
+
follow the GeoJSON specification with features and geometries.
|
|
288
|
+
layer_name : str, optional
|
|
289
|
+
The name to assign to the GeoJSON layer in the map, by default
|
|
290
|
+
"GeoJSON Layer". This name appears in the layer control widget.
|
|
291
|
+
|
|
292
|
+
Returns
|
|
293
|
+
-------
|
|
294
|
+
folium.Map
|
|
295
|
+
A configured Folium map object with the GeoJSON data added as a layer.
|
|
296
|
+
The map includes tooltips, popups, and layer controls when applicable.
|
|
297
|
+
|
|
298
|
+
Notes
|
|
299
|
+
-----
|
|
300
|
+
- Map center and zoom are automatically calculated based on the data extent
|
|
301
|
+
- Tooltips are added if suitable fields are found in feature properties
|
|
302
|
+
- Popups are added if suitable fields are found in feature properties
|
|
303
|
+
- Layer control is always added to allow toggling of the GeoJSON layer
|
|
304
|
+
- The map uses default Folium styling and can be further customized
|
|
305
|
+
"""
|
|
306
|
+
center_lat, center_lon, zoom_level = calculate_map_center_and_zoom(geojson_data)
|
|
307
|
+
tooltip_fields, popup_fields = extract_geojson_fields(geojson_data)
|
|
308
|
+
|
|
309
|
+
m = folium.Map(location=[center_lat, center_lon], zoom_start=zoom_level)
|
|
310
|
+
|
|
311
|
+
# Create GeoJson layer with dynamic tooltip and popup configuration
|
|
312
|
+
geojson_kwargs = {"name": layer_name}
|
|
313
|
+
|
|
314
|
+
if tooltip_fields:
|
|
315
|
+
geojson_kwargs["tooltip"] = folium.GeoJsonTooltip(fields=tooltip_fields)
|
|
316
|
+
|
|
317
|
+
if popup_fields:
|
|
318
|
+
geojson_kwargs["popup"] = folium.GeoJsonPopup(fields=popup_fields)
|
|
319
|
+
|
|
320
|
+
folium.GeoJson(geojson_data, **geojson_kwargs).add_to(m)
|
|
321
|
+
folium.LayerControl().add_to(m)
|
|
322
|
+
|
|
323
|
+
return m
|
nextmv/local/local.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Local module to hold convenience functions used in the `local` package.
|
|
3
|
+
|
|
4
|
+
Functions
|
|
5
|
+
----------
|
|
6
|
+
calculate_files_size
|
|
7
|
+
Function to calculate the total size of files in a directory.
|
|
8
|
+
|
|
9
|
+
Attributes
|
|
10
|
+
----------
|
|
11
|
+
OUTPUT_KEY : str
|
|
12
|
+
Output key constant used for identifying output in the run output.
|
|
13
|
+
LOGS_KEY : str
|
|
14
|
+
Logs key constant used for identifying logs in the run output.
|
|
15
|
+
LOGS_FILE : str
|
|
16
|
+
Constant used for identifying the file used for logging.
|
|
17
|
+
DEFAULT_OUTPUT_JSON_FILE : str
|
|
18
|
+
Constant for the default output JSON file name.
|
|
19
|
+
RUNS_KEY : str
|
|
20
|
+
Runs key constant used for identifying the runs directory in the nextmv
|
|
21
|
+
location.
|
|
22
|
+
NEXTMV_DIR : str
|
|
23
|
+
Constant for the Nextmv directory name.
|
|
24
|
+
DEFAULT_INPUT_JSON_FILE : str
|
|
25
|
+
Constant for the default input JSON file name.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import json
|
|
29
|
+
import os
|
|
30
|
+
|
|
31
|
+
OUTPUT_KEY = "output"
|
|
32
|
+
"""
|
|
33
|
+
Output key constant used for identifying output in the run output.
|
|
34
|
+
"""
|
|
35
|
+
LOGS_KEY = "logs"
|
|
36
|
+
"""
|
|
37
|
+
Logs key constant used for identifying logs in the run output.
|
|
38
|
+
"""
|
|
39
|
+
LOGS_FILE = "logs.log"
|
|
40
|
+
"""
|
|
41
|
+
Constant used for identifying the file used for logging.
|
|
42
|
+
"""
|
|
43
|
+
DEFAULT_OUTPUT_JSON_FILE = "solution.json"
|
|
44
|
+
"""
|
|
45
|
+
Constant for the default output JSON file name.
|
|
46
|
+
"""
|
|
47
|
+
RUNS_KEY = "runs"
|
|
48
|
+
"""
|
|
49
|
+
Runs key constant used for identifying the runs directory in the nextmv
|
|
50
|
+
location.
|
|
51
|
+
"""
|
|
52
|
+
NEXTMV_DIR = ".nextmv"
|
|
53
|
+
"""
|
|
54
|
+
Constant for the Nextmv directory name.
|
|
55
|
+
"""
|
|
56
|
+
DEFAULT_INPUT_JSON_FILE = "input.json"
|
|
57
|
+
"""
|
|
58
|
+
Constant for the default input JSON file name.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def calculate_files_size(run_dir: str, run_id: str, dir_path: str, metadata_key: str) -> None:
|
|
63
|
+
"""
|
|
64
|
+
Calculates the total size of the files in a directory, in bytes.
|
|
65
|
+
|
|
66
|
+
The calculated size is stored in the run information metadata under the
|
|
67
|
+
specified key.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
run_dir : str
|
|
72
|
+
The path to the run directory.
|
|
73
|
+
run_id : str
|
|
74
|
+
The ID of the run.
|
|
75
|
+
dir_path : str
|
|
76
|
+
The path to the directory whose size is to be calculated.
|
|
77
|
+
metadata_key : str
|
|
78
|
+
The key under which to store the calculated size in the run information
|
|
79
|
+
metadata.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
total_size = 0
|
|
83
|
+
for dirpath, _, filenames in os.walk(dir_path):
|
|
84
|
+
for f in filenames:
|
|
85
|
+
fp = os.path.join(dirpath, f)
|
|
86
|
+
# Skip if it is a symbolic link
|
|
87
|
+
if os.path.islink(fp):
|
|
88
|
+
continue
|
|
89
|
+
total_size += os.path.getsize(fp)
|
|
90
|
+
|
|
91
|
+
info_file = os.path.join(run_dir, f"{run_id}.json")
|
|
92
|
+
with open(info_file, "r+") as f:
|
|
93
|
+
info = json.load(f)
|
|
94
|
+
info["metadata"][metadata_key] = total_size
|
|
95
|
+
f.seek(0)
|
|
96
|
+
json.dump(info, f, indent=2)
|
|
97
|
+
f.truncate()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plotly visualization handler module.
|
|
3
|
+
|
|
4
|
+
This module provides functionality to handle Plotly visualizations by converting
|
|
5
|
+
them to HTML files for local run processing.
|
|
6
|
+
|
|
7
|
+
Functions
|
|
8
|
+
---------
|
|
9
|
+
handle_plotly_visual
|
|
10
|
+
Handle and write Plotly visuals to HTML files.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
import plotly.io as pio
|
|
17
|
+
|
|
18
|
+
from nextmv.output import Asset
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def handle_plotly_visual(asset: Asset, visuals_dir: str) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Handle and write Plotly visuals to HTML files.
|
|
24
|
+
|
|
25
|
+
This function processes Plotly visualization assets and converts them to
|
|
26
|
+
HTML files. It handles both single visualizations (dict content) and
|
|
27
|
+
multiple visualizations (list content). Each visualization is converted
|
|
28
|
+
from JSON format to a Plotly figure and then saved as an HTML file.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
asset : Asset
|
|
33
|
+
The asset containing the Plotly visualization data. The content can be
|
|
34
|
+
either a dictionary (single visualization) or a list (multiple
|
|
35
|
+
visualizations).
|
|
36
|
+
visuals_dir : str
|
|
37
|
+
The directory path where the HTML files will be written.
|
|
38
|
+
|
|
39
|
+
Notes
|
|
40
|
+
-----
|
|
41
|
+
- For list content, each visualization is saved with an index suffix
|
|
42
|
+
(e.g., "chart_0.html", "chart_1.html")
|
|
43
|
+
- For dict content, the visualization is saved with the asset label
|
|
44
|
+
(e.g., "chart.html")
|
|
45
|
+
- Content types other than dict or list are currently ignored
|
|
46
|
+
"""
|
|
47
|
+
if isinstance(asset.content, list):
|
|
48
|
+
for ix, content in enumerate(asset.content):
|
|
49
|
+
fig = pio.from_json(json.dumps(content))
|
|
50
|
+
fig.write_html(os.path.join(visuals_dir, f"{asset.visual.label}_{ix}.html"))
|
|
51
|
+
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
if isinstance(asset.content, dict):
|
|
55
|
+
fig = pio.from_json(json.dumps(asset.content))
|
|
56
|
+
fig.write_html(os.path.join(visuals_dir, f"{asset.visual.label}.html"))
|
|
57
|
+
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
# If there is a different content type for plotly visuals, we ignore it for
|
|
61
|
+
# now.
|