voxcity 0.7.0__py3-none-any.whl → 1.0.2__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.
Files changed (42) hide show
  1. voxcity/__init__.py +14 -14
  2. voxcity/exporter/__init__.py +12 -12
  3. voxcity/exporter/cityles.py +633 -633
  4. voxcity/exporter/envimet.py +733 -728
  5. voxcity/exporter/magicavoxel.py +333 -333
  6. voxcity/exporter/netcdf.py +238 -238
  7. voxcity/exporter/obj.py +1480 -1480
  8. voxcity/generator/__init__.py +47 -44
  9. voxcity/generator/api.py +721 -675
  10. voxcity/generator/grids.py +381 -379
  11. voxcity/generator/io.py +94 -94
  12. voxcity/generator/pipeline.py +282 -282
  13. voxcity/generator/update.py +429 -0
  14. voxcity/generator/voxelizer.py +18 -6
  15. voxcity/geoprocessor/__init__.py +75 -75
  16. voxcity/geoprocessor/draw.py +1488 -1219
  17. voxcity/geoprocessor/merge_utils.py +91 -91
  18. voxcity/geoprocessor/mesh.py +806 -806
  19. voxcity/geoprocessor/network.py +708 -708
  20. voxcity/geoprocessor/raster/buildings.py +435 -428
  21. voxcity/geoprocessor/raster/export.py +93 -93
  22. voxcity/geoprocessor/raster/landcover.py +5 -2
  23. voxcity/geoprocessor/utils.py +824 -824
  24. voxcity/models.py +113 -113
  25. voxcity/simulator/solar/__init__.py +66 -43
  26. voxcity/simulator/solar/integration.py +336 -336
  27. voxcity/simulator/solar/sky.py +668 -0
  28. voxcity/simulator/solar/temporal.py +792 -434
  29. voxcity/utils/__init__.py +11 -0
  30. voxcity/utils/classes.py +194 -0
  31. voxcity/utils/lc.py +80 -39
  32. voxcity/utils/shape.py +230 -0
  33. voxcity/visualizer/__init__.py +24 -24
  34. voxcity/visualizer/builder.py +43 -43
  35. voxcity/visualizer/grids.py +141 -141
  36. voxcity/visualizer/maps.py +187 -187
  37. voxcity/visualizer/renderer.py +1145 -928
  38. {voxcity-0.7.0.dist-info → voxcity-1.0.2.dist-info}/METADATA +90 -49
  39. {voxcity-0.7.0.dist-info → voxcity-1.0.2.dist-info}/RECORD +42 -38
  40. {voxcity-0.7.0.dist-info → voxcity-1.0.2.dist-info}/WHEEL +0 -0
  41. {voxcity-0.7.0.dist-info → voxcity-1.0.2.dist-info}/licenses/AUTHORS.rst +0 -0
  42. {voxcity-0.7.0.dist-info → voxcity-1.0.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,238 +1,238 @@
1
- """
2
- NetCDF export utilities for VoxCity.
3
-
4
- This module provides functions to convert a 3D voxel grid produced by
5
- `voxcity.generator.get_voxcity` into a NetCDF file for portable storage
6
- and downstream analysis.
7
-
8
- The voxel values follow VoxCity conventions (see generator.create_3d_voxel):
9
- - -3: built structures (buildings)
10
- - -2: vegetation canopy
11
- - -1: subsurface/underground
12
- - >= 1: ground-surface land cover code (offset by +1 from source classes)
13
-
14
- Notes
15
- -----
16
- - This writer prefers xarray for NetCDF export. If xarray is not installed,
17
- a clear error is raised with installation hints.
18
- - Coordinates are stored as index-based distances in meters from the grid
19
- origin along the y, x, and z axes. Geographic metadata such as the
20
- `rectangle_vertices` and `meshsize_m` are stored as global attributes to
21
- avoid making assumptions about map projection or geodesic conversions.
22
- """
23
-
24
- from __future__ import annotations
25
-
26
- from pathlib import Path
27
- from typing import Any, Dict, Mapping, MutableMapping, Optional, Sequence, Tuple
28
- import json
29
-
30
- import numpy as np
31
-
32
- try: # xarray is the preferred backend for NetCDF writing
33
- import xarray as xr # type: ignore
34
- XR_AVAILABLE = True
35
- except Exception: # pragma: no cover - optional dependency
36
- XR_AVAILABLE = False
37
-
38
- __all__ = [
39
- "voxel_to_xarray_dataset",
40
- "save_voxel_netcdf",
41
- "NetCDFExporter",
42
- ]
43
-
44
-
45
- def _ensure_parent_dir(path: Path) -> None:
46
- path.parent.mkdir(parents=True, exist_ok=True)
47
-
48
-
49
- def voxel_to_xarray_dataset(
50
- voxcity_grid: np.ndarray,
51
- voxel_size_m: float,
52
- rectangle_vertices: Optional[Sequence[Tuple[float, float]]] = None,
53
- extra_attrs: Optional[Mapping[str, Any]] = None,
54
- ) -> "xr.Dataset":
55
- """Create an xarray Dataset from a VoxCity voxel grid.
56
-
57
- Parameters
58
- ----------
59
- voxcity_grid
60
- 3D numpy array with shape (rows, cols, levels) as returned by
61
- `get_voxcity` (first element of the returned tuple).
62
- voxel_size_m
63
- Voxel size (mesh size) in meters.
64
- rectangle_vertices
65
- Optional polygon vertices defining the area of interest in
66
- longitude/latitude pairs, typically the same list passed to
67
- `get_voxcity`.
68
- extra_attrs
69
- Optional mapping of additional global attributes to store in the
70
- dataset.
71
-
72
- Returns
73
- -------
74
- xr.Dataset
75
- Dataset containing one DataArray named "voxels" with dims (y, x, z)
76
- and coordinate variables in meters from origin.
77
- """
78
- if not XR_AVAILABLE: # pragma: no cover - optional dependency
79
- raise ImportError(
80
- "xarray is required to export NetCDF. Install with: \n"
81
- " pip install xarray netCDF4\n"
82
- "or: \n"
83
- " pip install xarray h5netcdf"
84
- )
85
-
86
- if voxcity_grid.ndim != 3:
87
- raise ValueError(
88
- f"voxcity_grid must be 3D (rows, cols, levels); got shape={voxcity_grid.shape}"
89
- )
90
-
91
- rows, cols, levels = voxcity_grid.shape
92
-
93
- # Coordinate vectors in meters relative to the grid origin
94
- # y increases with row index, x increases with column index
95
- y_m = np.arange(rows, dtype=float) * float(voxel_size_m)
96
- x_m = np.arange(cols, dtype=float) * float(voxel_size_m)
97
- z_m = np.arange(levels, dtype=float) * float(voxel_size_m)
98
-
99
- ds_attrs: MutableMapping[str, Any] = {
100
- "title": "VoxCity voxel grid",
101
- "institution": "VoxCity",
102
- "source": "voxcity.generator.create_3d_voxel",
103
- "Conventions": "CF-1.10 (partial)",
104
- # NetCDF attributes must be basic types; serialize complex structures as strings
105
- "vox_value_meanings": [
106
- "-3: building",
107
- "-2: vegetation_canopy",
108
- "-1: subsurface",
109
- ">=1: surface_land_cover_code (offset +1)",
110
- ],
111
- "meshsize_m": float(voxel_size_m),
112
- # Store vertices as JSON string for portability
113
- "rectangle_vertices_lonlat_json": (
114
- json.dumps([[float(v[0]), float(v[1])] for v in rectangle_vertices])
115
- if rectangle_vertices is not None else ""
116
- ),
117
- "vertical_reference": "z=0 corresponds to min(DEM) as used in voxel construction",
118
- }
119
- if extra_attrs:
120
- ds_attrs.update(dict(extra_attrs))
121
-
122
- da = xr.DataArray(
123
- voxcity_grid,
124
- dims=("y", "x", "z"),
125
- coords={
126
- "y": ("y", y_m, {"units": "m", "long_name": "row_distance_from_origin"}),
127
- "x": ("x", x_m, {"units": "m", "long_name": "col_distance_from_origin"}),
128
- "z": ("z", z_m, {"units": "m", "positive": "up", "long_name": "height_above_vertical_origin"}),
129
- },
130
- name="voxels",
131
- attrs={
132
- "units": "category",
133
- "description": "VoxCity voxel values; see global attribute 'vox_value_meanings'",
134
- },
135
- )
136
-
137
- ds = xr.Dataset({"voxels": da}, attrs=ds_attrs)
138
- return ds
139
-
140
-
141
- def save_voxel_netcdf(
142
- voxcity_grid: np.ndarray,
143
- output_path: str | Path,
144
- voxel_size_m: float,
145
- rectangle_vertices: Optional[Sequence[Tuple[float, float]]] = None,
146
- extra_attrs: Optional[Mapping[str, Any]] = None,
147
- engine: Optional[str] = None,
148
- ) -> str:
149
- """Save a VoxCity voxel grid to a NetCDF file.
150
-
151
- Parameters
152
- ----------
153
- voxcity_grid
154
- 3D numpy array (rows, cols, levels) of voxel values.
155
- output_path
156
- Path to the NetCDF file to be written. Parent directories will be
157
- created as needed.
158
- voxel_size_m
159
- Voxel size in meters.
160
- rectangle_vertices
161
- Optional list of (lon, lat) pairs defining the area of interest.
162
- Stored as dataset metadata only.
163
- extra_attrs
164
- Optional additional global attributes to embed in the dataset.
165
- engine
166
- Optional xarray engine, e.g., "netcdf4" or "h5netcdf". If not provided,
167
- xarray will choose a default; on failure we retry alternate engines.
168
-
169
- Returns
170
- -------
171
- str
172
- The string path to the written NetCDF file.
173
- """
174
- if not XR_AVAILABLE: # pragma: no cover - optional dependency
175
- raise ImportError(
176
- "xarray is required to export NetCDF. Install with: \n"
177
- " pip install xarray netCDF4\n"
178
- "or: \n"
179
- " pip install xarray h5netcdf"
180
- )
181
-
182
- path = Path(output_path)
183
- _ensure_parent_dir(path)
184
-
185
- ds = voxel_to_xarray_dataset(
186
- voxcity_grid=voxcity_grid,
187
- voxel_size_m=voxel_size_m,
188
- rectangle_vertices=rectangle_vertices,
189
- extra_attrs=extra_attrs,
190
- )
191
-
192
- # Attempt to save with the requested or default engine; on failure, try a fallback
193
- tried_engines = []
194
- try:
195
- ds.to_netcdf(path, engine=engine) # type: ignore[call-arg]
196
- except Exception as e_first: # pragma: no cover - I/O backend dependent
197
- tried_engines.append(engine or "default")
198
- for fallback in ("netcdf4", "h5netcdf"):
199
- try:
200
- ds.to_netcdf(path, engine=fallback) # type: ignore[call-arg]
201
- break
202
- except Exception:
203
- tried_engines.append(fallback)
204
- else:
205
- raise RuntimeError(
206
- f"Failed to write NetCDF using engines: {tried_engines}. "
207
- f"Original error: {e_first}"
208
- )
209
-
210
- return str(path)
211
-
212
-
213
- class NetCDFExporter:
214
- """Exporter adapter to write a VoxCity voxel grid to NetCDF."""
215
-
216
- def export(self, obj, output_directory: str, base_filename: str, **kwargs):
217
- from ..models import VoxCity
218
- path = Path(output_directory) / f"{base_filename}.nc"
219
- if not isinstance(obj, VoxCity):
220
- raise TypeError("NetCDFExporter expects a VoxCity instance")
221
- rect = obj.extras.get("rectangle_vertices")
222
- # Merge default attrs with user-provided extras
223
- user_extra = kwargs.get("extra_attrs") or {}
224
- attrs = {
225
- "land_cover_source": obj.extras.get("land_cover_source", ""),
226
- "building_source": obj.extras.get("building_source", ""),
227
- "dem_source": obj.extras.get("dem_source", ""),
228
- }
229
- attrs.update(user_extra)
230
- return save_voxel_netcdf(
231
- voxcity_grid=obj.voxels.classes,
232
- output_path=path,
233
- voxel_size_m=obj.voxels.meta.meshsize,
234
- rectangle_vertices=rect,
235
- extra_attrs=attrs,
236
- engine=kwargs.get("engine"),
237
- )
238
-
1
+ """
2
+ NetCDF export utilities for VoxCity.
3
+
4
+ This module provides functions to convert a 3D voxel grid produced by
5
+ `voxcity.generator.get_voxcity` into a NetCDF file for portable storage
6
+ and downstream analysis.
7
+
8
+ The voxel values follow VoxCity conventions (see generator.create_3d_voxel):
9
+ - -3: built structures (buildings)
10
+ - -2: vegetation canopy
11
+ - -1: subsurface/underground
12
+ - >= 1: ground-surface land cover code (offset by +1 from source classes)
13
+
14
+ Notes
15
+ -----
16
+ - This writer prefers xarray for NetCDF export. If xarray is not installed,
17
+ a clear error is raised with installation hints.
18
+ - Coordinates are stored as index-based distances in meters from the grid
19
+ origin along the y, x, and z axes. Geographic metadata such as the
20
+ `rectangle_vertices` and `meshsize_m` are stored as global attributes to
21
+ avoid making assumptions about map projection or geodesic conversions.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from pathlib import Path
27
+ from typing import Any, Dict, Mapping, MutableMapping, Optional, Sequence, Tuple
28
+ import json
29
+
30
+ import numpy as np
31
+
32
+ try: # xarray is the preferred backend for NetCDF writing
33
+ import xarray as xr # type: ignore
34
+ XR_AVAILABLE = True
35
+ except Exception: # pragma: no cover - optional dependency
36
+ XR_AVAILABLE = False
37
+
38
+ __all__ = [
39
+ "voxel_to_xarray_dataset",
40
+ "save_voxel_netcdf",
41
+ "NetCDFExporter",
42
+ ]
43
+
44
+
45
+ def _ensure_parent_dir(path: Path) -> None:
46
+ path.parent.mkdir(parents=True, exist_ok=True)
47
+
48
+
49
+ def voxel_to_xarray_dataset(
50
+ voxcity_grid: np.ndarray,
51
+ voxel_size_m: float,
52
+ rectangle_vertices: Optional[Sequence[Tuple[float, float]]] = None,
53
+ extra_attrs: Optional[Mapping[str, Any]] = None,
54
+ ) -> "xr.Dataset":
55
+ """Create an xarray Dataset from a VoxCity voxel grid.
56
+
57
+ Parameters
58
+ ----------
59
+ voxcity_grid
60
+ 3D numpy array with shape (rows, cols, levels) as returned by
61
+ `get_voxcity` (first element of the returned tuple).
62
+ voxel_size_m
63
+ Voxel size (mesh size) in meters.
64
+ rectangle_vertices
65
+ Optional polygon vertices defining the area of interest in
66
+ longitude/latitude pairs, typically the same list passed to
67
+ `get_voxcity`.
68
+ extra_attrs
69
+ Optional mapping of additional global attributes to store in the
70
+ dataset.
71
+
72
+ Returns
73
+ -------
74
+ xr.Dataset
75
+ Dataset containing one DataArray named "voxels" with dims (y, x, z)
76
+ and coordinate variables in meters from origin.
77
+ """
78
+ if not XR_AVAILABLE: # pragma: no cover - optional dependency
79
+ raise ImportError(
80
+ "xarray is required to export NetCDF. Install with: \n"
81
+ " pip install xarray netCDF4\n"
82
+ "or: \n"
83
+ " pip install xarray h5netcdf"
84
+ )
85
+
86
+ if voxcity_grid.ndim != 3:
87
+ raise ValueError(
88
+ f"voxcity_grid must be 3D (rows, cols, levels); got shape={voxcity_grid.shape}"
89
+ )
90
+
91
+ rows, cols, levels = voxcity_grid.shape
92
+
93
+ # Coordinate vectors in meters relative to the grid origin
94
+ # y increases with row index, x increases with column index
95
+ y_m = np.arange(rows, dtype=float) * float(voxel_size_m)
96
+ x_m = np.arange(cols, dtype=float) * float(voxel_size_m)
97
+ z_m = np.arange(levels, dtype=float) * float(voxel_size_m)
98
+
99
+ ds_attrs: MutableMapping[str, Any] = {
100
+ "title": "VoxCity voxel grid",
101
+ "institution": "VoxCity",
102
+ "source": "voxcity.generator.create_3d_voxel",
103
+ "Conventions": "CF-1.10 (partial)",
104
+ # NetCDF attributes must be basic types; serialize complex structures as strings
105
+ "vox_value_meanings": [
106
+ "-3: building",
107
+ "-2: vegetation_canopy",
108
+ "-1: subsurface",
109
+ ">=1: surface_land_cover_code (offset +1)",
110
+ ],
111
+ "meshsize_m": float(voxel_size_m),
112
+ # Store vertices as JSON string for portability
113
+ "rectangle_vertices_lonlat_json": (
114
+ json.dumps([[float(v[0]), float(v[1])] for v in rectangle_vertices])
115
+ if rectangle_vertices is not None else ""
116
+ ),
117
+ "vertical_reference": "z=0 corresponds to min(DEM) as used in voxel construction",
118
+ }
119
+ if extra_attrs:
120
+ ds_attrs.update(dict(extra_attrs))
121
+
122
+ da = xr.DataArray(
123
+ voxcity_grid,
124
+ dims=("y", "x", "z"),
125
+ coords={
126
+ "y": ("y", y_m, {"units": "m", "long_name": "row_distance_from_origin"}),
127
+ "x": ("x", x_m, {"units": "m", "long_name": "col_distance_from_origin"}),
128
+ "z": ("z", z_m, {"units": "m", "positive": "up", "long_name": "height_above_vertical_origin"}),
129
+ },
130
+ name="voxels",
131
+ attrs={
132
+ "units": "category",
133
+ "description": "VoxCity voxel values; see global attribute 'vox_value_meanings'",
134
+ },
135
+ )
136
+
137
+ ds = xr.Dataset({"voxels": da}, attrs=ds_attrs)
138
+ return ds
139
+
140
+
141
+ def save_voxel_netcdf(
142
+ voxcity_grid: np.ndarray,
143
+ output_path: str | Path,
144
+ voxel_size_m: float,
145
+ rectangle_vertices: Optional[Sequence[Tuple[float, float]]] = None,
146
+ extra_attrs: Optional[Mapping[str, Any]] = None,
147
+ engine: Optional[str] = None,
148
+ ) -> str:
149
+ """Save a VoxCity voxel grid to a NetCDF file.
150
+
151
+ Parameters
152
+ ----------
153
+ voxcity_grid
154
+ 3D numpy array (rows, cols, levels) of voxel values.
155
+ output_path
156
+ Path to the NetCDF file to be written. Parent directories will be
157
+ created as needed.
158
+ voxel_size_m
159
+ Voxel size in meters.
160
+ rectangle_vertices
161
+ Optional list of (lon, lat) pairs defining the area of interest.
162
+ Stored as dataset metadata only.
163
+ extra_attrs
164
+ Optional additional global attributes to embed in the dataset.
165
+ engine
166
+ Optional xarray engine, e.g., "netcdf4" or "h5netcdf". If not provided,
167
+ xarray will choose a default; on failure we retry alternate engines.
168
+
169
+ Returns
170
+ -------
171
+ str
172
+ The string path to the written NetCDF file.
173
+ """
174
+ if not XR_AVAILABLE: # pragma: no cover - optional dependency
175
+ raise ImportError(
176
+ "xarray is required to export NetCDF. Install with: \n"
177
+ " pip install xarray netCDF4\n"
178
+ "or: \n"
179
+ " pip install xarray h5netcdf"
180
+ )
181
+
182
+ path = Path(output_path)
183
+ _ensure_parent_dir(path)
184
+
185
+ ds = voxel_to_xarray_dataset(
186
+ voxcity_grid=voxcity_grid,
187
+ voxel_size_m=voxel_size_m,
188
+ rectangle_vertices=rectangle_vertices,
189
+ extra_attrs=extra_attrs,
190
+ )
191
+
192
+ # Attempt to save with the requested or default engine; on failure, try a fallback
193
+ tried_engines = []
194
+ try:
195
+ ds.to_netcdf(path, engine=engine) # type: ignore[call-arg]
196
+ except Exception as e_first: # pragma: no cover - I/O backend dependent
197
+ tried_engines.append(engine or "default")
198
+ for fallback in ("netcdf4", "h5netcdf"):
199
+ try:
200
+ ds.to_netcdf(path, engine=fallback) # type: ignore[call-arg]
201
+ break
202
+ except Exception:
203
+ tried_engines.append(fallback)
204
+ else:
205
+ raise RuntimeError(
206
+ f"Failed to write NetCDF using engines: {tried_engines}. "
207
+ f"Original error: {e_first}"
208
+ )
209
+
210
+ return str(path)
211
+
212
+
213
+ class NetCDFExporter:
214
+ """Exporter adapter to write a VoxCity voxel grid to NetCDF."""
215
+
216
+ def export(self, obj, output_directory: str, base_filename: str, **kwargs):
217
+ from ..models import VoxCity
218
+ path = Path(output_directory) / f"{base_filename}.nc"
219
+ if not isinstance(obj, VoxCity):
220
+ raise TypeError("NetCDFExporter expects a VoxCity instance")
221
+ rect = obj.extras.get("rectangle_vertices")
222
+ # Merge default attrs with user-provided extras
223
+ user_extra = kwargs.get("extra_attrs") or {}
224
+ attrs = {
225
+ "land_cover_source": obj.extras.get("land_cover_source", ""),
226
+ "building_source": obj.extras.get("building_source", ""),
227
+ "dem_source": obj.extras.get("dem_source", ""),
228
+ }
229
+ attrs.update(user_extra)
230
+ return save_voxel_netcdf(
231
+ voxcity_grid=obj.voxels.classes,
232
+ output_path=path,
233
+ voxel_size_m=obj.voxels.meta.meshsize,
234
+ rectangle_vertices=rect,
235
+ extra_attrs=attrs,
236
+ engine=kwargs.get("engine"),
237
+ )
238
+