voxcity 0.5.16__py3-none-any.whl → 0.5.17__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.

Potentially problematic release.


This version of voxcity might be problematic. Click here for more details.

@@ -3,6 +3,5 @@ from .utils import *
3
3
  from .gee import *
4
4
  from .osm import *
5
5
  from .oemj import *
6
- from .omt import *
7
6
  from .eubucco import *
8
7
  from .overture import *
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voxcity
3
- Version: 0.5.16
3
+ Version: 0.5.17
4
4
  Summary: voxcity is an easy and one-stop tool to output 3d city models for microclimate simulation by integrating multiple geospatial open-data
5
5
  Author-email: Kunihiko Fujiwara <kunihiko@nus.edu.sg>
6
6
  Maintainer-email: Kunihiko Fujiwara <kunihiko@nus.edu.sg>
@@ -22,7 +22,7 @@ Requires-Dist: pyproj
22
22
  Requires-Dist: ipyleaflet
23
23
  Requires-Dist: geopandas
24
24
  Requires-Dist: rasterio==1.3.11
25
- Requires-Dist: shapely==1.8.5
25
+ Requires-Dist: shapely
26
26
  Requires-Dist: gdown
27
27
  Requires-Dist: numpy
28
28
  Requires-Dist: matplotlib
@@ -38,13 +38,12 @@ Requires-Dist: geemap
38
38
  Requires-Dist: rio-cogeo
39
39
  Requires-Dist: geopy
40
40
  Requires-Dist: py-vox-io
41
- Requires-Dist: mapbox_vector_tile
42
41
  Requires-Dist: numba
43
42
  Requires-Dist: reverse_geocoder
44
43
  Requires-Dist: pycountry
45
44
  Requires-Dist: seaborn
46
45
  Requires-Dist: overturemaps
47
- Requires-Dist: protobuf<4.21.0,>=3.20.0
46
+ Requires-Dist: protobuf
48
47
  Requires-Dist: timezonefinder
49
48
  Requires-Dist: astral
50
49
  Requires-Dist: osmnx
@@ -1,12 +1,11 @@
1
1
  voxcity/__init__.py,sha256=el9v3gfybHOF_GUYPeSOqN0-vCrTW0eU1mcvi0sEfeU,252
2
2
  voxcity/generator.py,sha256=LNN8pcczF5KpdYqAuAZNRoC1tJceVYt0_MjDvjS3l4k,52540
3
- voxcity/downloader/__init__.py,sha256=OgGcGxOXF4tjcEL6DhOnt13DYPTvOigUelp5xIpTqM0,171
3
+ voxcity/downloader/__init__.py,sha256=o_T_EU7hZLGyXxX9wVWn1x-OAa3ThGYdnpgB1_2v3AE,151
4
4
  voxcity/downloader/citygml.py,sha256=jVeHCLlJTf7k55OQGX0lZGQAngz_DD2V5TldSqRFlvc,36024
5
5
  voxcity/downloader/eubucco.py,sha256=ln1YNaaOgJfxNfCtVbYaMm775-bUvpAA_LDv60_i22w,17875
6
6
  voxcity/downloader/gee.py,sha256=O6HhQnUUumg_tTm4pP_cuyu5YjupDA1uKFxZWxD-i2E,23205
7
7
  voxcity/downloader/mbfp.py,sha256=UXDVjsO0fnb0fSal9yqrSFEIBThnRmnutnp08kZTmCA,6595
8
8
  voxcity/downloader/oemj.py,sha256=iDacTpiqn7RAXuqyEtHP29m0Cycwta5sMy9-GdvX3Fg,12293
9
- voxcity/downloader/omt.py,sha256=wxds3u0RBLDuDw1LfOKuIE_zSgWbD4LWmWhnoYRpfmo,12578
10
9
  voxcity/downloader/osm.py,sha256=kXiUedT7dwPOQ_4nxXptfeqNb5RKTIsQLG5km7Q8kKk,41645
11
10
  voxcity/downloader/overture.py,sha256=4YG2DMwUSSyZKUw_o8cGhMmAkPJon82aPqOFBvrre-Y,11987
12
11
  voxcity/downloader/utils.py,sha256=tz6wt4B9BhEOyvoF5OYXlr8rUd5cBEDedWL3j__oT70,3099
@@ -30,9 +29,9 @@ voxcity/utils/lc.py,sha256=h2yOWLUIrrummkyMyhRK5VbyrsPtslS0MJov_y0WGIQ,18925
30
29
  voxcity/utils/material.py,sha256=H8K8Lq4wBL6dQtgj7esUW2U6wLCOTeOtelkTDJoRgMo,10007
31
30
  voxcity/utils/visualization.py,sha256=CpekVq4D0Nd69-zY3g1TZvtQ4dbKieMA4SJwah9gW8M,110225
32
31
  voxcity/utils/weather.py,sha256=gy1Er0xuGXeSuVvh7VV1BebCzaBfdElUT1UGKAa815g,35619
33
- voxcity-0.5.16.dist-info/licenses/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
34
- voxcity-0.5.16.dist-info/licenses/LICENSE,sha256=s_jE1Df1nTPL4A_5GCGic5Zwex0CVaPKcAmSilxJPPE,1089
35
- voxcity-0.5.16.dist-info/METADATA,sha256=B9h0nAjPjDlPkftrPt5BEDM5WO1RXqC0uxDgxPHMcvY,26400
36
- voxcity-0.5.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
- voxcity-0.5.16.dist-info/top_level.txt,sha256=00b2U-LKfDllt6RL1R33MXie5MvxzUFye0NGD96t_8I,8
38
- voxcity-0.5.16.dist-info/RECORD,,
32
+ voxcity-0.5.17.dist-info/licenses/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
33
+ voxcity-0.5.17.dist-info/licenses/LICENSE,sha256=s_jE1Df1nTPL4A_5GCGic5Zwex0CVaPKcAmSilxJPPE,1089
34
+ voxcity-0.5.17.dist-info/METADATA,sha256=eQlOLgfQKRD1WsevK1699h-k-uQb_oREb--RNyUSJNI,26342
35
+ voxcity-0.5.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
36
+ voxcity-0.5.17.dist-info/top_level.txt,sha256=00b2U-LKfDllt6RL1R33MXie5MvxzUFye0NGD96t_8I,8
37
+ voxcity-0.5.17.dist-info/RECORD,,
voxcity/downloader/omt.py DELETED
@@ -1,294 +0,0 @@
1
- """
2
- Module for downloading and processing building data from OpenMapTiles vector tiles.
3
-
4
- This module provides functionality to download and process building footprint data from
5
- OpenMapTiles vector tile service. It handles downloading PBF tiles, extracting building
6
- geometries, and converting them to GeoJSON format with standardized properties.
7
-
8
- Key Features:
9
- - Downloads vector tiles from OpenMapTiles API
10
- - Extracts building footprints and properties
11
- - Converts coordinates from tile-local to WGS84
12
- - Standardizes building height information
13
- - Handles both Polygon and MultiPolygon geometries
14
- - Separates inner and outer rings of building footprints
15
-
16
- Dependencies:
17
- - mercantile: For tile calculations and coordinate transformations
18
- - mapbox_vector_tile: For decoding PBF vector tiles
19
- - shapely: For geometry operations
20
- - pyproj: For coordinate system transformations
21
- - geopandas: For working with geospatial data
22
- """
23
-
24
- import mercantile
25
- import requests
26
- import mapbox_vector_tile
27
- from shapely.geometry import shape, mapping
28
- from shapely.affinity import affine_transform
29
- import shapely.ops
30
- import json
31
- from pyproj import Transformer
32
- import json
33
- import geopandas as gpd
34
-
35
- def load_gdf_from_openmaptiles(rectangle_vertices, API_KEY):
36
- """Download and process building footprint data from OpenMapTiles vector tiles.
37
-
38
- This function downloads vector tiles covering the specified area, extracts building
39
- footprints, and converts them to a standardized format in a GeoDataFrame.
40
-
41
- Args:
42
- rectangle_vertices (list): List of (lon, lat) tuples defining the bounding box corners.
43
- The coordinates should be in WGS84 (EPSG:4326) format.
44
- API_KEY (str): OpenMapTiles API key for authentication. Must be valid for the v3 endpoint.
45
-
46
- Returns:
47
- geopandas.GeoDataFrame: A GeoDataFrame containing building footprints with the following columns:
48
- - geometry: Building footprint geometry in WGS84 coordinates
49
- - height: Building height in meters
50
- - min_height: Minimum height (e.g., for elevated structures) in meters
51
- - confidence: Confidence score (-1.0 for OpenMapTiles data)
52
- - is_inner: Boolean indicating if the polygon is an inner ring
53
- - role: String indicating 'inner' or 'outer' ring
54
- - id: Unique identifier for each building feature
55
-
56
- Notes:
57
- - Uses zoom level 15 for optimal detail vs data size balance
58
- - Converts coordinates from Web Mercator (EPSG:3857) to WGS84 (EPSG:4326)
59
- - Handles both Polygon and MultiPolygon geometries
60
- - Separates complex building footprints into their constituent parts
61
- """
62
- # Extract longitudes and latitudes from vertices to find bounding box
63
- lons = [coord[0] for coord in rectangle_vertices]
64
- lats = [coord[1] for coord in rectangle_vertices]
65
-
66
- min_lon = min(lons)
67
- max_lon = max(lons)
68
- min_lat = min(lats)
69
- max_lat = max(lats)
70
-
71
- # Use zoom level 15 which provides good detail for buildings while keeping data size manageable
72
- zoom = 15
73
-
74
- # Get list of tile coordinates that cover the bounding box at specified zoom level
75
- tiles = list(mercantile.tiles(min_lon, min_lat, max_lon, max_lat, zoom))
76
-
77
- building_features = []
78
-
79
- # Set up coordinate transformer to convert from Web Mercator (EPSG:3857) to WGS84 (EPSG:4326)
80
- # always_xy=True ensures longitude comes before latitude
81
- transformer = Transformer.from_crs("EPSG:3857", "EPSG:4326", always_xy=True)
82
-
83
- for tile in tiles:
84
- x, y, z = tile.x, tile.y, tile.z
85
-
86
- # Construct URL for vector tile using MapTiler API
87
- tile_url = f'https://api.maptiler.com/tiles/v3/{z}/{x}/{y}.pbf?key={API_KEY}'
88
-
89
- print(f'Downloading tile {z}/{x}/{y}')
90
- response = requests.get(tile_url)
91
-
92
- if response.status_code != 200:
93
- print(f'Failed to download tile {z}/{x}/{y}')
94
- continue
95
-
96
- # Decode the Protocol Buffer (PBF) formatted vector tile
97
- tile_data = mapbox_vector_tile.decode(response.content)
98
-
99
- # Process building layer if it exists in the tile
100
- if 'building' in tile_data:
101
- building_layer = tile_data['building']
102
- for feature in building_layer['features']:
103
- # Convert feature geometry to shapely object for manipulation
104
- geometry = shape(feature['geometry'])
105
-
106
- # Vector tiles use local coordinates from 0-4096
107
- # Need to transform these to real world coordinates
108
- x_min, y_min = 0, 0
109
- x_max, y_max = 4096, 4096
110
-
111
- # Get tile bounds in Web Mercator coordinates
112
- tile_bbox_mercator = mercantile.xy_bounds(x, y, z)
113
-
114
- # Calculate scale factors to transform local tile coordinates to Web Mercator
115
- scale_x = (tile_bbox_mercator.right - tile_bbox_mercator.left) / (x_max - x_min)
116
- scale_y = (tile_bbox_mercator.bottom - tile_bbox_mercator.top) / (y_max - y_min)
117
-
118
- # Create affine transformation matrix:
119
- # [a b xoff]
120
- # [d e yoff]
121
- # [0 0 1 ]
122
- a = scale_x # x scale
123
- b = 0 # rotation
124
- d = 0 # rotation
125
- e = -scale_y # y scale (negative because y axis is flipped)
126
- xoff = tile_bbox_mercator.left # x translation
127
- yoff = tile_bbox_mercator.bottom # y translation
128
-
129
- transform_matrix = [a, b, d, e, xoff, yoff]
130
-
131
- # Transform geometry from tile coordinates to Web Mercator
132
- transformed_geom = affine_transform(geometry, transform_matrix)
133
-
134
- # Transform from Web Mercator to WGS84 geographic coordinates
135
- transformed_geometry = shapely.ops.transform(transformer.transform, transformed_geom)
136
-
137
- # Create standardized GeoJSON feature
138
- geojson_feature = {
139
- 'type': 'Feature',
140
- 'geometry': mapping(transformed_geometry),
141
- 'properties': feature['properties']
142
- }
143
-
144
- building_features.append(geojson_feature)
145
-
146
- # Convert features to standardized format with height information
147
- converted_geojson_data = convert_geojson_format(building_features)
148
-
149
- gdf = gpd.GeoDataFrame.from_features(converted_geojson_data)
150
- gdf.set_crs(epsg=4326, inplace=True)
151
-
152
- # Replace id column with index numbers
153
- gdf['id'] = gdf.index
154
-
155
- return gdf
156
-
157
- def get_height_from_properties(properties):
158
- """Extract building height from properties, using levels if height is not available.
159
-
160
- This function implements a fallback strategy for determining building heights:
161
- 1. First tries to use explicit render_height property
162
- 2. If not available, estimates height from number of building levels
163
- 3. Returns 0 if no valid height information is found
164
-
165
- Args:
166
- properties (dict): Dictionary containing building properties from OpenMapTiles.
167
- Expected keys:
168
- - render_height: Direct height specification in meters
169
- - building:levels: Number of building floors/levels
170
-
171
- Returns:
172
- float: Building height in meters. Values can be:
173
- - Explicit height from render_height property
174
- - Estimated height (levels * 5.0 meters per level)
175
- - 0.0 if no valid height information is found
176
-
177
- Notes:
178
- - Assumes average floor height of 5 meters when estimating from levels
179
- - Handles potential invalid values gracefully by returning 0
180
- """
181
- # First try explicit render_height property
182
- height = properties.get('render_height')
183
- if height is not None:
184
- try:
185
- return float(height)
186
- except ValueError:
187
- pass
188
-
189
- # If no height available, estimate from number of levels
190
- # OpenMapTiles uses building:levels tag for number of floors
191
- levels = properties.get('building:levels')
192
- if levels is not None:
193
- try:
194
- return float(levels) * 5.0 # Assume average floor height of 5 meters
195
- except ValueError:
196
- pass
197
-
198
- return 0 # Default height if no valid data found
199
-
200
- def convert_geojson_format(features):
201
- """Convert building features to standardized format with height information.
202
-
203
- This function processes raw OpenMapTiles building features into a standardized format,
204
- handling complex geometries and adding consistent property attributes.
205
-
206
- Args:
207
- features (list): List of GeoJSON features containing building footprints.
208
- Each feature should have:
209
- - geometry: GeoJSON geometry (Polygon or MultiPolygon)
210
- - properties: Dictionary of building properties
211
-
212
- Returns:
213
- list: List of standardized GeoJSON features where:
214
- - Complex MultiPolygons are split into individual Polygons
215
- - Each Polygon ring (outer and inner) becomes a separate feature
216
- - Properties are standardized to include:
217
- - height: Building height in meters
218
- - min_height: Minimum height in meters
219
- - confidence: Set to -1.0 for OpenMapTiles data
220
- - is_inner: Boolean flag for inner rings
221
- - role: String indicating 'inner' or 'outer' ring
222
-
223
- Notes:
224
- - Preserves coordinate order as (longitude, latitude)
225
- - Maintains topological relationships through is_inner and role properties
226
- - Splits complex geometries for easier processing downstream
227
- """
228
- new_features = []
229
-
230
- for feature in features:
231
- geometry = feature['geometry']
232
- properties = feature['properties']
233
-
234
- # Extract height information
235
- height = get_height_from_properties(properties)
236
- min_height = properties.get('render_min_height', 0)
237
- try:
238
- min_height = float(min_height)
239
- except ValueError:
240
- min_height = 0
241
-
242
- # Create standardized properties dictionary
243
- new_properties = {
244
- 'height': height,
245
- 'min_height': min_height,
246
- 'confidence': -1.0, # No confidence score available from OpenMapTiles
247
- 'is_inner': False # Will be set based on ring position
248
- }
249
-
250
- # Handle MultiPolygon geometries by splitting into separate Polygon features
251
- if geometry['type'] == 'MultiPolygon':
252
- for i, polygon_coords in enumerate(geometry['coordinates']):
253
- # Process each ring in the polygon (outer ring + inner holes)
254
- for j, ring in enumerate(polygon_coords):
255
- ring_properties = new_properties.copy()
256
- # First ring (j=0) is outer boundary, others are inner holes
257
- ring_properties['is_inner'] = j > 0
258
- ring_properties['role'] = 'inner' if j > 0 else 'outer'
259
-
260
- # Create new geometry keeping coordinate order as (lon,lat)
261
- new_geometry = {
262
- 'type': 'Polygon',
263
- 'coordinates': [ring]
264
- }
265
-
266
- new_feature = {
267
- 'type': 'Feature',
268
- 'properties': ring_properties,
269
- 'geometry': new_geometry
270
- }
271
- new_features.append(new_feature)
272
-
273
- # Handle single Polygon geometries
274
- elif geometry['type'] == 'Polygon':
275
- # Process each ring in the polygon
276
- for i, ring in enumerate(geometry['coordinates']):
277
- ring_properties = new_properties.copy()
278
- ring_properties['is_inner'] = i > 0
279
- ring_properties['role'] = 'inner' if i > 0 else 'outer'
280
-
281
- # Create new geometry keeping coordinate order as (lon,lat)
282
- new_geometry = {
283
- 'type': 'Polygon',
284
- 'coordinates': [ring]
285
- }
286
-
287
- new_feature = {
288
- 'type': 'Feature',
289
- 'properties': ring_properties,
290
- 'geometry': new_geometry
291
- }
292
- new_features.append(new_feature)
293
-
294
- return new_features