voxcity 0.6.15__py3-none-any.whl → 0.7.0__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.
- voxcity/__init__.py +14 -8
- voxcity/downloader/__init__.py +2 -1
- voxcity/downloader/citygml.py +32 -18
- voxcity/downloader/gba.py +210 -0
- voxcity/downloader/gee.py +5 -1
- voxcity/downloader/mbfp.py +1 -1
- voxcity/downloader/oemj.py +80 -8
- voxcity/downloader/osm.py +23 -7
- voxcity/downloader/overture.py +26 -1
- voxcity/downloader/utils.py +73 -73
- voxcity/errors.py +30 -0
- voxcity/exporter/__init__.py +13 -4
- voxcity/exporter/cityles.py +633 -535
- voxcity/exporter/envimet.py +728 -708
- voxcity/exporter/magicavoxel.py +334 -297
- voxcity/exporter/netcdf.py +238 -0
- voxcity/exporter/obj.py +1481 -655
- voxcity/generator/__init__.py +44 -0
- voxcity/generator/api.py +675 -0
- voxcity/generator/grids.py +379 -0
- voxcity/generator/io.py +94 -0
- voxcity/generator/pipeline.py +282 -0
- voxcity/generator/voxelizer.py +380 -0
- voxcity/geoprocessor/__init__.py +75 -6
- voxcity/geoprocessor/conversion.py +153 -0
- voxcity/geoprocessor/draw.py +62 -12
- voxcity/geoprocessor/heights.py +199 -0
- voxcity/geoprocessor/io.py +101 -0
- voxcity/geoprocessor/merge_utils.py +91 -0
- voxcity/geoprocessor/mesh.py +806 -790
- voxcity/geoprocessor/network.py +708 -679
- voxcity/geoprocessor/overlap.py +84 -0
- voxcity/geoprocessor/raster/__init__.py +82 -0
- voxcity/geoprocessor/raster/buildings.py +428 -0
- voxcity/geoprocessor/raster/canopy.py +258 -0
- voxcity/geoprocessor/raster/core.py +150 -0
- voxcity/geoprocessor/raster/export.py +93 -0
- voxcity/geoprocessor/raster/landcover.py +156 -0
- voxcity/geoprocessor/raster/raster.py +110 -0
- voxcity/geoprocessor/selection.py +85 -0
- voxcity/geoprocessor/utils.py +18 -14
- voxcity/models.py +113 -0
- voxcity/simulator/common/__init__.py +22 -0
- voxcity/simulator/common/geometry.py +98 -0
- voxcity/simulator/common/raytracing.py +450 -0
- voxcity/simulator/solar/__init__.py +43 -0
- voxcity/simulator/solar/integration.py +336 -0
- voxcity/simulator/solar/kernels.py +62 -0
- voxcity/simulator/solar/radiation.py +648 -0
- voxcity/simulator/solar/temporal.py +434 -0
- voxcity/simulator/view.py +36 -2286
- voxcity/simulator/visibility/__init__.py +29 -0
- voxcity/simulator/visibility/landmark.py +392 -0
- voxcity/simulator/visibility/view.py +508 -0
- voxcity/utils/logging.py +61 -0
- voxcity/utils/orientation.py +51 -0
- voxcity/utils/weather/__init__.py +26 -0
- voxcity/utils/weather/epw.py +146 -0
- voxcity/utils/weather/files.py +36 -0
- voxcity/utils/weather/onebuilding.py +486 -0
- voxcity/visualizer/__init__.py +24 -0
- voxcity/visualizer/builder.py +43 -0
- voxcity/visualizer/grids.py +141 -0
- voxcity/visualizer/maps.py +187 -0
- voxcity/visualizer/palette.py +228 -0
- voxcity/visualizer/renderer.py +928 -0
- {voxcity-0.6.15.dist-info → voxcity-0.7.0.dist-info}/METADATA +113 -36
- voxcity-0.7.0.dist-info/RECORD +77 -0
- {voxcity-0.6.15.dist-info → voxcity-0.7.0.dist-info}/WHEEL +1 -1
- voxcity/generator.py +0 -1137
- voxcity/geoprocessor/grid.py +0 -1568
- voxcity/geoprocessor/polygon.py +0 -1344
- voxcity/simulator/solar.py +0 -2329
- voxcity/utils/visualization.py +0 -2660
- voxcity/utils/weather.py +0 -817
- voxcity-0.6.15.dist-info/RECORD +0 -37
- {voxcity-0.6.15.dist-info → voxcity-0.7.0.dist-info/licenses}/AUTHORS.rst +0 -0
- {voxcity-0.6.15.dist-info → voxcity-0.7.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from .builder import MeshBuilder
|
|
2
|
+
from .renderer import PyVistaRenderer, create_multi_view_scene, visualize_voxcity_plotly, visualize_voxcity
|
|
3
|
+
from .palette import get_voxel_color_map
|
|
4
|
+
from .grids import visualize_landcover_grid_on_basemap, visualize_numerical_grid_on_basemap, visualize_numerical_gdf_on_basemap, visualize_point_gdf_on_basemap
|
|
5
|
+
from .maps import plot_grid, visualize_land_cover_grid_on_map, visualize_building_height_grid_on_map, visualize_numerical_grid_on_map
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"MeshBuilder",
|
|
9
|
+
"PyVistaRenderer",
|
|
10
|
+
"create_multi_view_scene",
|
|
11
|
+
"visualize_voxcity_plotly",
|
|
12
|
+
"visualize_voxcity",
|
|
13
|
+
"get_voxel_color_map",
|
|
14
|
+
"visualize_landcover_grid_on_basemap",
|
|
15
|
+
"visualize_numerical_grid_on_basemap",
|
|
16
|
+
"visualize_numerical_gdf_on_basemap",
|
|
17
|
+
"visualize_point_gdf_on_basemap",
|
|
18
|
+
"plot_grid",
|
|
19
|
+
"visualize_land_cover_grid_on_map",
|
|
20
|
+
"visualize_building_height_grid_on_map",
|
|
21
|
+
"visualize_numerical_grid_on_map",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import trimesh
|
|
5
|
+
|
|
6
|
+
from ..models import MeshModel, MeshCollection, VoxelGrid
|
|
7
|
+
from ..geoprocessor.mesh import create_city_meshes
|
|
8
|
+
from .palette import get_voxel_color_map
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MeshBuilder:
|
|
12
|
+
"""Build mesh collections from voxel grids for rendering/export."""
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def from_voxel_grid(voxel_grid: VoxelGrid, meshsize: float, voxel_color_map: "str|dict" = "default",
|
|
16
|
+
include_classes=None, exclude_classes=None) -> MeshCollection:
|
|
17
|
+
if isinstance(voxel_color_map, dict):
|
|
18
|
+
vox_dict = voxel_color_map
|
|
19
|
+
else:
|
|
20
|
+
vox_dict = get_voxel_color_map(voxel_color_map)
|
|
21
|
+
|
|
22
|
+
meshes = create_city_meshes(
|
|
23
|
+
voxel_grid.classes,
|
|
24
|
+
vox_dict,
|
|
25
|
+
meshsize=meshsize,
|
|
26
|
+
include_classes=include_classes,
|
|
27
|
+
exclude_classes=exclude_classes,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
collection = MeshCollection()
|
|
31
|
+
for key, m in meshes.items():
|
|
32
|
+
if m is None:
|
|
33
|
+
continue
|
|
34
|
+
colors = getattr(m.visual, 'face_colors', None)
|
|
35
|
+
collection.add(str(key), MeshModel(
|
|
36
|
+
vertices=m.vertices.copy(),
|
|
37
|
+
faces=m.faces.copy(),
|
|
38
|
+
colors=colors.copy() if colors is not None else None,
|
|
39
|
+
name=str(key)
|
|
40
|
+
))
|
|
41
|
+
return collection
|
|
42
|
+
|
|
43
|
+
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
import matplotlib.colors as mcolors
|
|
6
|
+
from matplotlib.colors import ListedColormap, BoundaryNorm
|
|
7
|
+
import contextily as ctx
|
|
8
|
+
|
|
9
|
+
from ..geoprocessor.raster import grid_to_geodataframe
|
|
10
|
+
from ..utils.lc import get_land_cover_classes
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def visualize_landcover_grid_on_basemap(landcover_grid, rectangle_vertices, meshsize, source='Standard', alpha=0.6, figsize=(12, 8), basemap='CartoDB light', show_edge=False, edge_color='black', edge_width=0.5):
|
|
14
|
+
land_cover_classes = get_land_cover_classes(source)
|
|
15
|
+
gdf = grid_to_geodataframe(landcover_grid, rectangle_vertices, meshsize)
|
|
16
|
+
colors = [(r/255, g/255, b/255) for (r,g,b) in land_cover_classes.keys()]
|
|
17
|
+
cmap = ListedColormap(colors)
|
|
18
|
+
bounds = np.arange(len(colors) + 1)
|
|
19
|
+
norm = BoundaryNorm(bounds, cmap.N)
|
|
20
|
+
gdf_web = gdf.to_crs(epsg=3857)
|
|
21
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
22
|
+
gdf_web.plot(column='value', ax=ax, alpha=alpha, cmap=cmap, norm=norm, legend=True,
|
|
23
|
+
legend_kwds={'label': 'Land Cover Class', 'ticks': bounds[:-1] + 0.5, 'boundaries': bounds,
|
|
24
|
+
'format': lambda x, p: list(land_cover_classes.values())[int(x)]},
|
|
25
|
+
edgecolor=edge_color if show_edge else 'none', linewidth=edge_width if show_edge else 0)
|
|
26
|
+
basemaps = {
|
|
27
|
+
'CartoDB dark': ctx.providers.CartoDB.DarkMatter,
|
|
28
|
+
'CartoDB light': ctx.providers.CartoDB.Positron,
|
|
29
|
+
'CartoDB voyager': ctx.providers.CartoDB.Voyager,
|
|
30
|
+
'CartoDB light no labels': ctx.providers.CartoDB.PositronNoLabels,
|
|
31
|
+
'CartoDB dark no labels': ctx.providers.CartoDB.DarkMatterNoLabels,
|
|
32
|
+
}
|
|
33
|
+
ctx.add_basemap(ax, source=basemaps[basemap])
|
|
34
|
+
ax.set_axis_off()
|
|
35
|
+
plt.tight_layout(); plt.show()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def visualize_numerical_grid_on_basemap(grid, rectangle_vertices, meshsize, value_name="value", cmap='viridis', vmin=None, vmax=None,
|
|
39
|
+
alpha=0.6, figsize=(12, 8), basemap='CartoDB light', show_edge=False, edge_color='black', edge_width=0.5):
|
|
40
|
+
gdf = grid_to_geodataframe(grid, rectangle_vertices, meshsize)
|
|
41
|
+
gdf_web = gdf.to_crs(epsg=3857)
|
|
42
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
43
|
+
gdf_web.plot(column='value', ax=ax, alpha=alpha, cmap=cmap, vmin=vmin, vmax=vmax, legend=True,
|
|
44
|
+
legend_kwds={'label': value_name}, edgecolor=edge_color if show_edge else 'none', linewidth=edge_width if show_edge else 0)
|
|
45
|
+
basemaps = {
|
|
46
|
+
'CartoDB dark': ctx.providers.CartoDB.DarkMatter,
|
|
47
|
+
'CartoDB light': ctx.providers.CartoDB.Positron,
|
|
48
|
+
'CartoDB voyager': ctx.providers.CartoDB.Voyager,
|
|
49
|
+
'CartoDB light no labels': ctx.providers.CartoDB.PositronNoLabels,
|
|
50
|
+
'CartoDB dark no labels': ctx.providers.CartoDB.DarkMatterNoLabels,
|
|
51
|
+
}
|
|
52
|
+
ctx.add_basemap(ax, source=basemaps[basemap])
|
|
53
|
+
ax.set_axis_off()
|
|
54
|
+
plt.tight_layout(); plt.show()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def visualize_numerical_gdf_on_basemap(gdf, value_name="value", cmap='viridis', vmin=None, vmax=None,
|
|
58
|
+
alpha=0.6, figsize=(12, 8), basemap='CartoDB light',
|
|
59
|
+
show_edge=False, edge_color='black', edge_width=0.5, input_crs=None):
|
|
60
|
+
if gdf.crs is None:
|
|
61
|
+
if input_crs is not None:
|
|
62
|
+
gdf = gdf.set_crs(input_crs, allow_override=True)
|
|
63
|
+
else:
|
|
64
|
+
try:
|
|
65
|
+
minx, miny, maxx, maxy = gdf.total_bounds
|
|
66
|
+
looks_like_lonlat = (-180.0 <= minx <= 180.0 and -180.0 <= maxx <= 180.0 and -90.0 <= miny <= 90.0 and -90.0 <= maxy <= 90.0)
|
|
67
|
+
except Exception:
|
|
68
|
+
looks_like_lonlat = False
|
|
69
|
+
if looks_like_lonlat:
|
|
70
|
+
gdf = gdf.set_crs("EPSG:4326", allow_override=True)
|
|
71
|
+
else:
|
|
72
|
+
raise ValueError("Input GeoDataFrame has no CRS. Provide 'input_crs' or set gdf.crs.")
|
|
73
|
+
|
|
74
|
+
gdf_web = gdf.to_crs(epsg=3857) if str(gdf.crs) != 'EPSG:3857' else gdf
|
|
75
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
76
|
+
gdf_web.plot(column=value_name, ax=ax, alpha=alpha, cmap=cmap, vmin=vmin, vmax=vmax, legend=True,
|
|
77
|
+
legend_kwds={'label': value_name}, edgecolor=edge_color if show_edge else 'none', linewidth=edge_width if show_edge else 0)
|
|
78
|
+
basemaps = {
|
|
79
|
+
'CartoDB dark': ctx.providers.CartoDB.DarkMatter,
|
|
80
|
+
'CartoDB light': ctx.providers.CartoDB.Positron,
|
|
81
|
+
'CartoDB voyager': ctx.providers.CartoDB.Voyager,
|
|
82
|
+
'CartoDB light no labels': ctx.providers.CartoDB.PositronNoLabels,
|
|
83
|
+
'CartoDB dark no labels': ctx.providers.CartoDB.DarkMatterNoLabels,
|
|
84
|
+
}
|
|
85
|
+
ctx.add_basemap(ax, source=basemaps[basemap])
|
|
86
|
+
ax.set_axis_off()
|
|
87
|
+
plt.tight_layout(); plt.show()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def visualize_point_gdf_on_basemap(point_gdf, value_name='value', **kwargs):
|
|
91
|
+
import contextily as ctx
|
|
92
|
+
defaults = {
|
|
93
|
+
'figsize': (12, 8),
|
|
94
|
+
'colormap': 'viridis',
|
|
95
|
+
'markersize': 20,
|
|
96
|
+
'alpha': 0.7,
|
|
97
|
+
'vmin': None,
|
|
98
|
+
'vmax': None,
|
|
99
|
+
'title': None,
|
|
100
|
+
'basemap_style': ctx.providers.CartoDB.Positron,
|
|
101
|
+
'zoom': 15
|
|
102
|
+
}
|
|
103
|
+
settings = {**defaults, **kwargs}
|
|
104
|
+
fig, ax = plt.subplots(figsize=settings['figsize'])
|
|
105
|
+
point_gdf_web = point_gdf.to_crs(epsg=3857)
|
|
106
|
+
point_gdf_web.plot(column=value_name, ax=ax, cmap=settings['colormap'], markersize=settings['markersize'], alpha=settings['alpha'], vmin=settings['vmin'], vmax=settings['vmax'], legend=True, legend_kwds={'label': value_name})
|
|
107
|
+
ctx.add_basemap(ax, source=settings['basemap_style'], zoom=settings['zoom'])
|
|
108
|
+
if settings['title']:
|
|
109
|
+
plt.title(settings['title'])
|
|
110
|
+
ax.set_axis_off(); plt.tight_layout(); plt.show()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def visualize_land_cover_grid(grid, mesh_size, color_map, land_cover_classes):
|
|
114
|
+
all_classes = list(land_cover_classes.values())
|
|
115
|
+
unique_classes = list(dict.fromkeys(all_classes))
|
|
116
|
+
colors = [color_map[cls] for cls in unique_classes]
|
|
117
|
+
cmap = mcolors.ListedColormap(colors)
|
|
118
|
+
bounds = np.arange(len(unique_classes) + 1)
|
|
119
|
+
norm = mcolors.BoundaryNorm(bounds, cmap.N)
|
|
120
|
+
class_to_num = {cls: i for i, cls in enumerate(unique_classes)}
|
|
121
|
+
numeric_grid = np.vectorize(class_to_num.get)(grid)
|
|
122
|
+
plt.figure(figsize=(10, 10))
|
|
123
|
+
im = plt.imshow(numeric_grid, cmap=cmap, norm=norm, interpolation='nearest')
|
|
124
|
+
cbar = plt.colorbar(im, ticks=bounds[:-1] + 0.5)
|
|
125
|
+
cbar.set_ticklabels(unique_classes)
|
|
126
|
+
plt.title(f'Land Use/Land Cover Grid (Mesh Size: {mesh_size}m)')
|
|
127
|
+
plt.xlabel('Grid Cells (X)')
|
|
128
|
+
plt.ylabel('Grid Cells (Y)')
|
|
129
|
+
plt.show()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def visualize_numerical_grid(grid, mesh_size, title, cmap='viridis', label='Value', vmin=None, vmax=None):
|
|
133
|
+
plt.figure(figsize=(10, 10))
|
|
134
|
+
plt.imshow(grid, cmap=cmap, vmin=vmin, vmax=vmax)
|
|
135
|
+
plt.colorbar(label=label)
|
|
136
|
+
plt.title(f'{title} (Mesh Size: {mesh_size}m)')
|
|
137
|
+
plt.xlabel('Grid Cells (X)')
|
|
138
|
+
plt.ylabel('Grid Cells (Y)')
|
|
139
|
+
plt.show()
|
|
140
|
+
|
|
141
|
+
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
import matplotlib.colors as mcolors
|
|
6
|
+
import seaborn as sns
|
|
7
|
+
import contextily as ctx
|
|
8
|
+
from shapely.geometry import Polygon
|
|
9
|
+
from pyproj import CRS
|
|
10
|
+
|
|
11
|
+
from ..geoprocessor.raster import (
|
|
12
|
+
calculate_grid_size,
|
|
13
|
+
create_coordinate_mesh,
|
|
14
|
+
)
|
|
15
|
+
from ..geoprocessor.utils import (
|
|
16
|
+
initialize_geod,
|
|
17
|
+
calculate_distance,
|
|
18
|
+
normalize_to_one_meter,
|
|
19
|
+
setup_transformer,
|
|
20
|
+
transform_coords,
|
|
21
|
+
)
|
|
22
|
+
from ..utils.lc import get_land_cover_classes
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def plot_grid(grid, origin, adjusted_meshsize, u_vec, v_vec, transformer, vertices, data_type, vmin=None, vmax=None, color_map=None, alpha=0.5, buf=0.2, edge=True, basemap='CartoDB light', **kwargs):
|
|
26
|
+
fig, ax = plt.subplots(figsize=(12, 12))
|
|
27
|
+
|
|
28
|
+
if data_type == 'land_cover':
|
|
29
|
+
land_cover_classes = kwargs.get('land_cover_classes')
|
|
30
|
+
colors = [mcolors.to_rgb(f'#{r:02x}{g:02x}{b:02x}') for r, g, b in land_cover_classes.keys()]
|
|
31
|
+
cmap = mcolors.ListedColormap(colors)
|
|
32
|
+
norm = mcolors.BoundaryNorm(range(len(land_cover_classes)+1), cmap.N)
|
|
33
|
+
elif data_type == 'building_height':
|
|
34
|
+
masked_grid = np.ma.masked_array(grid, mask=(np.isnan(grid) | (grid == 0)))
|
|
35
|
+
cmap = plt.cm.viridis
|
|
36
|
+
if vmin is None:
|
|
37
|
+
vmin = np.nanmin(masked_grid[masked_grid > 0])
|
|
38
|
+
if vmax is None:
|
|
39
|
+
vmax = np.nanmax(masked_grid)
|
|
40
|
+
norm = mcolors.Normalize(vmin=vmin, vmax=vmax)
|
|
41
|
+
elif data_type == 'dem':
|
|
42
|
+
cmap = plt.cm.terrain
|
|
43
|
+
if vmin is None:
|
|
44
|
+
vmin = np.nanmin(grid)
|
|
45
|
+
if vmax is None:
|
|
46
|
+
vmax = np.nanmax(grid)
|
|
47
|
+
norm = mcolors.Normalize(vmin=vmin, vmax=vmax)
|
|
48
|
+
elif data_type == 'canopy_height':
|
|
49
|
+
cmap = plt.cm.Greens
|
|
50
|
+
if vmin is None:
|
|
51
|
+
vmin = np.nanmin(grid)
|
|
52
|
+
if vmax is None:
|
|
53
|
+
vmax = np.nanmax(grid)
|
|
54
|
+
norm = mcolors.Normalize(vmin=vmin, vmax=vmax)
|
|
55
|
+
elif data_type in ('green_view_index', 'sky_view_index'):
|
|
56
|
+
cmap = plt.cm.get_cmap('BuPu_r').copy() if data_type == 'sky_view_index' else plt.cm.Greens
|
|
57
|
+
if vmin is None:
|
|
58
|
+
vmin = np.nanmin(grid)
|
|
59
|
+
if vmax is None:
|
|
60
|
+
vmax = np.nanmax(grid)
|
|
61
|
+
norm = mcolors.Normalize(vmin=vmin, vmax=vmax)
|
|
62
|
+
else:
|
|
63
|
+
cmap = plt.cm.viridis
|
|
64
|
+
if vmin is None:
|
|
65
|
+
vmin = np.nanmin(grid)
|
|
66
|
+
if vmax is None:
|
|
67
|
+
vmax = np.nanmax(grid)
|
|
68
|
+
norm = mcolors.Normalize(vmin=vmin, vmax=vmax)
|
|
69
|
+
|
|
70
|
+
if color_map:
|
|
71
|
+
cmap = sns.color_palette(color_map, as_cmap=True).copy()
|
|
72
|
+
|
|
73
|
+
grid = grid.T
|
|
74
|
+
|
|
75
|
+
for i in range(grid.shape[0]):
|
|
76
|
+
for j in range(grid.shape[1]):
|
|
77
|
+
cell = create_cell_polygon(origin, j, i, adjusted_meshsize, u_vec, v_vec) # type: ignore[name-defined]
|
|
78
|
+
x, y = cell.exterior.xy
|
|
79
|
+
x, y = zip(*[transformer.transform(lon, lat) for lat, lon in zip(x, y)])
|
|
80
|
+
value = grid[i, j]
|
|
81
|
+
if data_type == 'building_height':
|
|
82
|
+
if np.isnan(value):
|
|
83
|
+
ax.fill(x, y, alpha=alpha, fc='gray', ec='black' if edge else None, linewidth=0.1)
|
|
84
|
+
elif value == 0:
|
|
85
|
+
if edge:
|
|
86
|
+
ax.plot(x, y, color='black', linewidth=0.1)
|
|
87
|
+
elif value > 0:
|
|
88
|
+
ax.fill(x, y, alpha=alpha, fc=cmap(norm(value)), ec='black' if edge else None, linewidth=0.1)
|
|
89
|
+
elif data_type == 'canopy_height':
|
|
90
|
+
color = cmap(norm(value))
|
|
91
|
+
if value == 0:
|
|
92
|
+
if edge:
|
|
93
|
+
ax.plot(x, y, color='black', linewidth=0.1)
|
|
94
|
+
else:
|
|
95
|
+
ax.fill(x, y, alpha=alpha, fc=color, ec='black' if edge else None, linewidth=0.1)
|
|
96
|
+
elif 'view' in data_type:
|
|
97
|
+
if np.isnan(value):
|
|
98
|
+
if edge:
|
|
99
|
+
ax.plot(x, y, color='black', linewidth=0.1)
|
|
100
|
+
elif value >= 0:
|
|
101
|
+
ax.fill(x, y, alpha=alpha, fc=cmap(norm(value)), ec='black' if edge else None, linewidth=0.1)
|
|
102
|
+
else:
|
|
103
|
+
color = cmap(norm(value))
|
|
104
|
+
ax.fill(x, y, alpha=alpha, fc=color, ec='black' if edge else None, linewidth=0.1)
|
|
105
|
+
|
|
106
|
+
crs_epsg_3857 = CRS.from_epsg(3857)
|
|
107
|
+
basemaps = {
|
|
108
|
+
'CartoDB dark': ctx.providers.CartoDB.DarkMatter,
|
|
109
|
+
'CartoDB light': ctx.providers.CartoDB.Positron,
|
|
110
|
+
'CartoDB voyager': ctx.providers.CartoDB.Voyager,
|
|
111
|
+
'CartoDB light no labels': ctx.providers.CartoDB.PositronNoLabels,
|
|
112
|
+
'CartoDB dark no labels': ctx.providers.CartoDB.DarkMatterNoLabels,
|
|
113
|
+
}
|
|
114
|
+
ctx.add_basemap(ax, crs=crs_epsg_3857, source=basemaps[basemap])
|
|
115
|
+
|
|
116
|
+
all_coords = np.array(vertices)
|
|
117
|
+
x, y = zip(*[transformer.transform(lon, lat) for lat, lon in all_coords])
|
|
118
|
+
x_min, x_max = min(x), max(x)
|
|
119
|
+
y_min, y_max = min(y), max(y)
|
|
120
|
+
if x_min != x_max and y_min != y_max and buf != 0:
|
|
121
|
+
dist_x = x_max - x_min
|
|
122
|
+
dist_y = y_max - y_min
|
|
123
|
+
ax.set_xlim(x_min - buf * dist_x, x_max + buf * dist_x)
|
|
124
|
+
ax.set_ylim(y_min - buf * dist_y, y_max + buf * dist_y)
|
|
125
|
+
else:
|
|
126
|
+
ax.set_xlim(x_min, x_max)
|
|
127
|
+
ax.set_ylim(y_min, y_max)
|
|
128
|
+
|
|
129
|
+
plt.axis('off')
|
|
130
|
+
plt.tight_layout()
|
|
131
|
+
plt.show()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def visualize_land_cover_grid_on_map(grid, rectangle_vertices, meshsize, source='Urbanwatch', vmin=None, vmax=None, alpha=0.5, buf=0.2, edge=True, basemap='CartoDB light'):
|
|
135
|
+
geod = initialize_geod()
|
|
136
|
+
land_cover_classes = get_land_cover_classes(source)
|
|
137
|
+
vertex_0 = rectangle_vertices[0]; vertex_1 = rectangle_vertices[1]; vertex_3 = rectangle_vertices[3]
|
|
138
|
+
dist_side_1 = calculate_distance(geod, vertex_0[1], vertex_0[0], vertex_1[1], vertex_1[0])
|
|
139
|
+
dist_side_2 = calculate_distance(geod, vertex_0[1], vertex_0[0], vertex_3[1], vertex_3[0])
|
|
140
|
+
side_1 = np.array(vertex_1) - np.array(vertex_0)
|
|
141
|
+
side_2 = np.array(vertex_3) - np.array(vertex_0)
|
|
142
|
+
u_vec = normalize_to_one_meter(side_1, dist_side_1)
|
|
143
|
+
v_vec = normalize_to_one_meter(side_2, dist_side_2)
|
|
144
|
+
origin = np.array(rectangle_vertices[0])
|
|
145
|
+
grid_size, adjusted_meshsize = calculate_grid_size(side_1, side_2, u_vec, v_vec, meshsize)
|
|
146
|
+
transformer = setup_transformer(CRS.from_epsg(4326), CRS.from_epsg(3857))
|
|
147
|
+
plot_grid(grid, origin, adjusted_meshsize, u_vec, v_vec, transformer, rectangle_vertices, 'land_cover', alpha=alpha, buf=buf, edge=edge, basemap=basemap, land_cover_classes=land_cover_classes)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def visualize_building_height_grid_on_map(building_height_grid, filtered_buildings, rectangle_vertices, meshsize, vmin=None, vmax=None, color_map=None, alpha=0.5, buf=0.2, edge=True, basemap='CartoDB light'):
|
|
151
|
+
geod = initialize_geod()
|
|
152
|
+
vertex_0, vertex_1, vertex_3 = rectangle_vertices[0], rectangle_vertices[1], rectangle_vertices[3]
|
|
153
|
+
dist_side_1 = calculate_distance(geod, vertex_0[1], vertex_0[0], vertex_1[1], vertex_1[0])
|
|
154
|
+
dist_side_2 = calculate_distance(geod, vertex_0[1], vertex_0[0], vertex_3[1], vertex_3[0])
|
|
155
|
+
side_1 = np.array(vertex_1) - np.array(vertex_0)
|
|
156
|
+
side_2 = np.array(vertex_3) - np.array(vertex_0)
|
|
157
|
+
u_vec = normalize_to_one_meter(side_1, dist_side_1)
|
|
158
|
+
v_vec = normalize_to_one_meter(side_2, dist_side_2)
|
|
159
|
+
origin = np.array(rectangle_vertices[0])
|
|
160
|
+
_, adjusted_meshsize = calculate_grid_size(side_1, side_2, u_vec, v_vec, meshsize)
|
|
161
|
+
transformer = setup_transformer(CRS.from_epsg(4326), CRS.from_epsg(3857))
|
|
162
|
+
plot_grid(building_height_grid, origin, adjusted_meshsize, u_vec, v_vec, transformer,
|
|
163
|
+
rectangle_vertices, 'building_height', vmin=vmin, vmax=vmax, color_map=color_map, alpha=alpha, buf=buf, edge=edge, basemap=basemap, buildings=filtered_buildings)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def visualize_numerical_grid_on_map(canopy_height_grid, rectangle_vertices, meshsize, type, vmin=None, vmax=None, color_map=None, alpha=0.5, buf=0.2, edge=True, basemap='CartoDB light'):
|
|
167
|
+
geod = initialize_geod()
|
|
168
|
+
vertex_0, vertex_1, vertex_3 = rectangle_vertices[0], rectangle_vertices[1], rectangle_vertices[3]
|
|
169
|
+
dist_side_1 = calculate_distance(geod, vertex_0[1], vertex_0[0], vertex_1[1], vertex_1[0])
|
|
170
|
+
dist_side_2 = calculate_distance(geod, vertex_0[1], vertex_0[0], vertex_3[1], vertex_3[0])
|
|
171
|
+
side_1 = np.array(vertex_1) - np.array(vertex_0)
|
|
172
|
+
side_2 = np.array(vertex_3) - np.array(vertex_0)
|
|
173
|
+
u_vec = normalize_to_one_meter(side_1, dist_side_1)
|
|
174
|
+
v_vec = normalize_to_one_meter(side_2, dist_side_2)
|
|
175
|
+
origin = np.array(rectangle_vertices[0])
|
|
176
|
+
_, adjusted_meshsize = calculate_grid_size(side_1, side_2, u_vec, v_vec, meshsize)
|
|
177
|
+
transformer = setup_transformer(CRS.from_epsg(4326), CRS.from_epsg(3857))
|
|
178
|
+
plot_grid(canopy_height_grid, origin, adjusted_meshsize, u_vec, v_vec, transformer,
|
|
179
|
+
rectangle_vertices, type, vmin=vmin, vmax=vmax, color_map=color_map, alpha=alpha, buf=buf, edge=edge, basemap=basemap)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_voxel_color_map(color_scheme='default'):
|
|
5
|
+
"""
|
|
6
|
+
Returns a color map for voxel visualization based on the specified color scheme.
|
|
7
|
+
|
|
8
|
+
This function provides multiple predefined color schemes for visualizing voxel data.
|
|
9
|
+
Each scheme maps voxel class IDs to RGB color values [0-255]. The class IDs follow
|
|
10
|
+
a specific convention where negative values represent built environment elements
|
|
11
|
+
and positive values represent natural/ground surface elements.
|
|
12
|
+
"""
|
|
13
|
+
if color_scheme == 'default':
|
|
14
|
+
return {
|
|
15
|
+
-99: [0, 0, 0],
|
|
16
|
+
-30: [255, 0, 102],
|
|
17
|
+
-17: [238, 242, 234],
|
|
18
|
+
-16: [56, 78, 84],
|
|
19
|
+
-15: [147, 140, 114],
|
|
20
|
+
-14: [139, 149, 159],
|
|
21
|
+
-13: [186, 187, 181],
|
|
22
|
+
-12: [248, 166, 2],
|
|
23
|
+
-11: [81, 59, 56],
|
|
24
|
+
-3: [180, 187, 216],
|
|
25
|
+
-2: [78, 99, 63],
|
|
26
|
+
-1: [188, 143, 143],
|
|
27
|
+
1: [239, 228, 176],
|
|
28
|
+
2: [123, 130, 59],
|
|
29
|
+
3: [97, 140, 86],
|
|
30
|
+
4: [112, 120, 56],
|
|
31
|
+
5: [116, 150, 66],
|
|
32
|
+
6: [187, 204, 40],
|
|
33
|
+
7: [77, 118, 99],
|
|
34
|
+
8: [22, 61, 51],
|
|
35
|
+
9: [44, 66, 133],
|
|
36
|
+
10: [205, 215, 224],
|
|
37
|
+
11: [108, 119, 129],
|
|
38
|
+
12: [59, 62, 87],
|
|
39
|
+
13: [150, 166, 190],
|
|
40
|
+
14: [239, 228, 176],
|
|
41
|
+
}
|
|
42
|
+
elif color_scheme == 'high_contrast':
|
|
43
|
+
return {
|
|
44
|
+
-99: [0, 0, 0],
|
|
45
|
+
-30: [255, 0, 255],
|
|
46
|
+
-17: [255, 255, 255],
|
|
47
|
+
-16: [0, 0, 255],
|
|
48
|
+
-15: [153, 76, 0],
|
|
49
|
+
-14: [192, 192, 192],
|
|
50
|
+
-13: [128, 128, 128],
|
|
51
|
+
-12: [255, 128, 0],
|
|
52
|
+
-11: [153, 0, 0],
|
|
53
|
+
-3: [0, 255, 255],
|
|
54
|
+
-2: [0, 153, 0],
|
|
55
|
+
-1: [204, 0, 102],
|
|
56
|
+
1: [255, 255, 153],
|
|
57
|
+
2: [102, 153, 0],
|
|
58
|
+
3: [0, 204, 0],
|
|
59
|
+
4: [153, 204, 0],
|
|
60
|
+
5: [0, 102, 0],
|
|
61
|
+
6: [204, 255, 51],
|
|
62
|
+
7: [0, 153, 153],
|
|
63
|
+
8: [0, 51, 0],
|
|
64
|
+
9: [0, 102, 204],
|
|
65
|
+
10: [255, 255, 255],
|
|
66
|
+
11: [76, 76, 76],
|
|
67
|
+
12: [0, 0, 0],
|
|
68
|
+
13: [102, 102, 255],
|
|
69
|
+
14: [255, 204, 153],
|
|
70
|
+
}
|
|
71
|
+
elif color_scheme == 'monochrome':
|
|
72
|
+
return {
|
|
73
|
+
-99: [0, 0, 0],
|
|
74
|
+
-30: [28, 28, 99],
|
|
75
|
+
-17: [242, 242, 242],
|
|
76
|
+
-16: [51, 51, 153],
|
|
77
|
+
-15: [102, 102, 204],
|
|
78
|
+
-14: [153, 153, 204],
|
|
79
|
+
-13: [204, 204, 230],
|
|
80
|
+
-12: [76, 76, 178],
|
|
81
|
+
-11: [25, 25, 127],
|
|
82
|
+
-3: [179, 179, 230],
|
|
83
|
+
-2: [51, 51, 153],
|
|
84
|
+
-1: [102, 102, 178],
|
|
85
|
+
1: [230, 230, 255],
|
|
86
|
+
2: [128, 128, 204],
|
|
87
|
+
3: [102, 102, 204],
|
|
88
|
+
4: [153, 153, 230],
|
|
89
|
+
5: [76, 76, 178],
|
|
90
|
+
6: [204, 204, 255],
|
|
91
|
+
7: [76, 76, 178],
|
|
92
|
+
8: [25, 25, 127],
|
|
93
|
+
9: [51, 51, 204],
|
|
94
|
+
10: [242, 242, 255],
|
|
95
|
+
11: [128, 128, 178],
|
|
96
|
+
12: [51, 51, 127],
|
|
97
|
+
13: [153, 153, 204],
|
|
98
|
+
14: [230, 230, 255],
|
|
99
|
+
}
|
|
100
|
+
elif color_scheme == 'pastel':
|
|
101
|
+
return {
|
|
102
|
+
-99: [0, 0, 0],
|
|
103
|
+
-30: [255, 179, 217],
|
|
104
|
+
-17: [245, 245, 245],
|
|
105
|
+
-16: [173, 196, 230],
|
|
106
|
+
-15: [222, 213, 196],
|
|
107
|
+
-14: [211, 219, 226],
|
|
108
|
+
-13: [226, 226, 226],
|
|
109
|
+
-12: [255, 223, 179],
|
|
110
|
+
-11: [204, 168, 166],
|
|
111
|
+
-3: [214, 217, 235],
|
|
112
|
+
-2: [190, 207, 180],
|
|
113
|
+
-1: [235, 204, 204],
|
|
114
|
+
1: [250, 244, 227],
|
|
115
|
+
2: [213, 217, 182],
|
|
116
|
+
3: [200, 226, 195],
|
|
117
|
+
4: [209, 214, 188],
|
|
118
|
+
5: [195, 220, 168],
|
|
119
|
+
6: [237, 241, 196],
|
|
120
|
+
7: [180, 210, 205],
|
|
121
|
+
8: [176, 196, 190],
|
|
122
|
+
9: [188, 206, 235],
|
|
123
|
+
10: [242, 245, 250],
|
|
124
|
+
11: [209, 213, 219],
|
|
125
|
+
12: [189, 190, 204],
|
|
126
|
+
13: [215, 221, 232],
|
|
127
|
+
14: [250, 244, 227],
|
|
128
|
+
}
|
|
129
|
+
elif color_scheme == 'dark_mode':
|
|
130
|
+
return {
|
|
131
|
+
-99: [0, 0, 0],
|
|
132
|
+
-30: [153, 51, 102],
|
|
133
|
+
-17: [76, 76, 76],
|
|
134
|
+
-16: [33, 46, 51],
|
|
135
|
+
-15: [89, 84, 66],
|
|
136
|
+
-14: [83, 89, 94],
|
|
137
|
+
-13: [61, 61, 61],
|
|
138
|
+
-12: [153, 102, 0],
|
|
139
|
+
-11: [51, 35, 33],
|
|
140
|
+
-3: [78, 82, 99],
|
|
141
|
+
-2: [46, 58, 37],
|
|
142
|
+
-1: [99, 68, 68],
|
|
143
|
+
1: [102, 97, 75],
|
|
144
|
+
2: [61, 66, 31],
|
|
145
|
+
3: [46, 77, 46],
|
|
146
|
+
4: [56, 61, 28],
|
|
147
|
+
5: [54, 77, 31],
|
|
148
|
+
6: [89, 97, 20],
|
|
149
|
+
7: [38, 59, 49],
|
|
150
|
+
8: [16, 31, 26],
|
|
151
|
+
9: [22, 33, 66],
|
|
152
|
+
10: [82, 87, 92],
|
|
153
|
+
11: [46, 51, 56],
|
|
154
|
+
12: [25, 31, 43],
|
|
155
|
+
13: [56, 64, 82],
|
|
156
|
+
14: [102, 97, 75],
|
|
157
|
+
}
|
|
158
|
+
elif color_scheme == 'grayscale':
|
|
159
|
+
return {
|
|
160
|
+
-99: [0, 0, 0],
|
|
161
|
+
-30: [253, 231, 37],
|
|
162
|
+
-17: [240, 240, 240],
|
|
163
|
+
-16: [60, 60, 60],
|
|
164
|
+
-15: [130, 130, 130],
|
|
165
|
+
-14: [150, 150, 150],
|
|
166
|
+
-13: [180, 180, 180],
|
|
167
|
+
-12: [170, 170, 170],
|
|
168
|
+
-11: [70, 70, 70],
|
|
169
|
+
-3: [190, 190, 190],
|
|
170
|
+
-2: [90, 90, 90],
|
|
171
|
+
-1: [160, 160, 160],
|
|
172
|
+
1: [230, 230, 230],
|
|
173
|
+
2: [120, 120, 120],
|
|
174
|
+
3: [110, 110, 110],
|
|
175
|
+
4: [115, 115, 115],
|
|
176
|
+
5: [100, 100, 100],
|
|
177
|
+
6: [210, 210, 210],
|
|
178
|
+
7: [95, 95, 95],
|
|
179
|
+
8: [40, 40, 40],
|
|
180
|
+
9: [50, 50, 50],
|
|
181
|
+
10: [220, 220, 220],
|
|
182
|
+
11: [140, 140, 140],
|
|
183
|
+
12: [30, 30, 30],
|
|
184
|
+
13: [170, 170, 170],
|
|
185
|
+
14: [230, 230, 230],
|
|
186
|
+
}
|
|
187
|
+
elif color_scheme == 'white_mode':
|
|
188
|
+
return {
|
|
189
|
+
-99: [0, 0, 0],
|
|
190
|
+
-30: [220, 80, 80],
|
|
191
|
+
-17: [250, 250, 250],
|
|
192
|
+
-16: [210, 225, 235],
|
|
193
|
+
-15: [230, 225, 215],
|
|
194
|
+
-14: [225, 230, 235],
|
|
195
|
+
-13: [236, 236, 236],
|
|
196
|
+
-12: [245, 232, 210],
|
|
197
|
+
-11: [235, 210, 205],
|
|
198
|
+
-3: [225, 230, 240],
|
|
199
|
+
-2: [190, 210, 190],
|
|
200
|
+
-1: [230, 215, 215],
|
|
201
|
+
1: [248, 245, 235],
|
|
202
|
+
2: [225, 235, 215],
|
|
203
|
+
3: [220, 235, 220],
|
|
204
|
+
4: [240, 235, 215],
|
|
205
|
+
5: [210, 230, 210],
|
|
206
|
+
6: [245, 250, 235],
|
|
207
|
+
7: [220, 235, 230],
|
|
208
|
+
8: [205, 215, 210],
|
|
209
|
+
9: [200, 220, 245],
|
|
210
|
+
10: [252, 252, 252],
|
|
211
|
+
11: [230, 230, 230],
|
|
212
|
+
12: [210, 210, 215],
|
|
213
|
+
13: [230, 235, 240],
|
|
214
|
+
14: [248, 245, 235],
|
|
215
|
+
}
|
|
216
|
+
else:
|
|
217
|
+
# Fallback to default palette for unknown schemes
|
|
218
|
+
return get_voxel_color_map('default')
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|