voxcity 0.3.2__tar.gz → 0.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of voxcity might be problematic. Click here for more details.
- {voxcity-0.3.2 → voxcity-0.3.4}/PKG-INFO +61 -5
- {voxcity-0.3.2 → voxcity-0.3.4}/README.md +60 -4
- {voxcity-0.3.2 → voxcity-0.3.4}/pyproject.toml +1 -1
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/download/eubucco.py +9 -17
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/download/gee.py +4 -3
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/download/mbfp.py +7 -7
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/download/oemj.py +22 -22
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/download/omt.py +10 -10
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/download/osm.py +23 -21
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/download/overture.py +7 -15
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/file/envimet.py +4 -4
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/file/geojson.py +83 -26
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/geo/draw.py +128 -22
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/geo/grid.py +9 -143
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/geo/utils.py +79 -66
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/sim/solar.py +187 -53
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/sim/view.py +183 -31
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/utils/weather.py +7 -7
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity.egg-info/PKG-INFO +61 -5
- {voxcity-0.3.2 → voxcity-0.3.4}/AUTHORS.rst +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/CONTRIBUTING.rst +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/HISTORY.rst +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/LICENSE +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/MANIFEST.in +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/docs/Makefile +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/docs/archive/README.rst +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/docs/authors.rst +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/docs/conf.py +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/docs/index.rst +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/docs/make.bat +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/setup.cfg +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/__init__.py +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/download/__init__.py +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/download/utils.py +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/file/__init_.py +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/file/magicavoxel.py +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/file/obj.py +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/geo/__init_.py +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/sim/__init_.py +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/sim/utils.py +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/utils/__init_.py +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/utils/lc.py +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/utils/visualization.py +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity/voxcity.py +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity.egg-info/SOURCES.txt +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity.egg-info/dependency_links.txt +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity.egg-info/requires.txt +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/src/voxcity.egg-info/top_level.txt +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/tests/__init__.py +0 -0
- {voxcity-0.3.2 → voxcity-0.3.4}/tests/voxelcity.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: voxcity
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
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>
|
|
@@ -164,10 +164,10 @@ Define the target area by directly specifying the coordinates of the rectangle v
|
|
|
164
164
|
|
|
165
165
|
```python
|
|
166
166
|
rectangle_vertices = [
|
|
167
|
-
(
|
|
168
|
-
(
|
|
169
|
-
(
|
|
170
|
-
(
|
|
167
|
+
(-122.33587348582083, 47.59830044521263), # Southwest corner (longitude, latitude)
|
|
168
|
+
(-122.33587348582083, 47.60279755390168), # Northwest corner (longitude, latitude)
|
|
169
|
+
(-122.32922451417917, 47.60279755390168), # Northeast corner (longitude, latitude)
|
|
170
|
+
(-122.32922451417917, 47.59830044521263) # Southeast corner (longitude, latitude)
|
|
171
171
|
]
|
|
172
172
|
```
|
|
173
173
|
|
|
@@ -305,6 +305,62 @@ export_magicavoxel_vox(voxcity_grid, output_path, base_filename=base_filename)
|
|
|
305
305
|
|
|
306
306
|
### 6. Additional Use Cases
|
|
307
307
|
|
|
308
|
+
#### Compute Solar Irradiance:
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
from voxcity.sim.solar import get_global_solar_irradiance_using_epw
|
|
312
|
+
|
|
313
|
+
solar_kwargs = {
|
|
314
|
+
"download_nearest_epw": True, # Whether to automatically download nearest EPW weather file based on location from Climate.OneBuilding.Org
|
|
315
|
+
"rectangle_vertices": rectangle_vertices, # Coordinates defining the area of interest for calculation
|
|
316
|
+
# "epw_file_path": "./output/new.york-downtown.manhattan.heli_ny_usa_1.epw", # Path to EnergyPlus Weather (EPW) file containing climate data. Set if you already have an EPW file.
|
|
317
|
+
"calc_time": "01-01 12:00:00", # Time for instantaneous calculation in format "MM-DD HH:MM:SS"
|
|
318
|
+
"view_point_height": 1.5, # Height of view point in meters for calculating solar access. Default: 1.5 m
|
|
319
|
+
"tree_k": 0.6, # Static extinction coefficient - controls how much sunlight is blocked by trees (higher = more blocking)
|
|
320
|
+
"tree_lad": 1.0, # Leaf area density of trees - density of leaves/branches that affect shading (higher = denser foliage)
|
|
321
|
+
"dem_grid": dem_grid, # Digital elevation model grid for terrain heights
|
|
322
|
+
"colormap": 'magma', # Matplotlib colormap for visualization. Default: 'viridis'
|
|
323
|
+
"obj_export": True, # Whether to export results as 3D OBJ file
|
|
324
|
+
"output_directory": 'output/test', # Directory for saving output files
|
|
325
|
+
"output_file_name": 'instantaneous_solar_irradiance', # Base filename for outputs (without extension)
|
|
326
|
+
"alpha": 1.0, # Transparency of visualization (0.0-1.0)
|
|
327
|
+
"vmin": 0, # Minimum value for colormap scaling in visualization
|
|
328
|
+
# "vmax": 900, # Maximum value for colormap scaling in visualization
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
# Compute global solar irradiance map (direct + diffuse radiation)
|
|
332
|
+
global_map = get_global_solar_irradiance_using_epw(
|
|
333
|
+
voxcity_grid, # 3D voxel grid representing the urban environment
|
|
334
|
+
meshsize, # Size of each voxel in meters
|
|
335
|
+
calc_type='instantaneous', # Calculate instantaneous irradiance at specified time
|
|
336
|
+
direct_normal_irradiance_scaling=1.0, # Scaling factor for direct solar radiation (1.0 = no scaling)
|
|
337
|
+
diffuse_irradiance_scaling=1.0, # Scaling factor for diffuse solar radiation (1.0 = no scaling)
|
|
338
|
+
**solar_kwargs # Pass all the parameters defined above
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
# Adjust parameters for cumulative calculation
|
|
342
|
+
solar_kwargs["start_time"] = "01-01 01:00:00" # Start time for cumulative calculation
|
|
343
|
+
solar_kwargs["end_time"] = "01-31 23:00:00" # End time for cumulative calculation
|
|
344
|
+
solar_kwargs["output_file_name"] = 'cummulative_solar_irradiance', # Base filename for outputs (without extension)
|
|
345
|
+
|
|
346
|
+
# Calculate cumulative solar irradiance over the specified time period
|
|
347
|
+
global_map = get_global_solar_irradiance_using_epw(
|
|
348
|
+
voxcity_grid, # 3D voxel grid representing the urban environment
|
|
349
|
+
meshsize, # Size of each voxel in meters
|
|
350
|
+
calc_type='cumulative', # Calculate cumulative irradiance over time period instead of instantaneous
|
|
351
|
+
direct_normal_irradiance_scaling=1.0, # Scaling factor for direct solar radiation (1.0 = no scaling)
|
|
352
|
+
diffuse_irradiance_scaling=1.0, # Scaling factor for diffuse solar radiation (1.0 = no scaling)
|
|
353
|
+
**solar_kwargs # Pass all the parameters defined above
|
|
354
|
+
)
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
<p align="center">
|
|
358
|
+
<img src="https://raw.githubusercontent.com/kunifujiwara/VoxCity/main/images/solar.png" alt="Solar Irradiance Maps Rendered in Rhino" width="800">
|
|
359
|
+
</p>
|
|
360
|
+
<p align="center">
|
|
361
|
+
<em>Example Results Saved as OBJ and Rendered in Rhino</em>
|
|
362
|
+
</p>
|
|
363
|
+
|
|
308
364
|
#### Compute Green View Index (GVI) and Sky View Index (SVI):
|
|
309
365
|
|
|
310
366
|
```python
|
|
@@ -108,10 +108,10 @@ Define the target area by directly specifying the coordinates of the rectangle v
|
|
|
108
108
|
|
|
109
109
|
```python
|
|
110
110
|
rectangle_vertices = [
|
|
111
|
-
(
|
|
112
|
-
(
|
|
113
|
-
(
|
|
114
|
-
(
|
|
111
|
+
(-122.33587348582083, 47.59830044521263), # Southwest corner (longitude, latitude)
|
|
112
|
+
(-122.33587348582083, 47.60279755390168), # Northwest corner (longitude, latitude)
|
|
113
|
+
(-122.32922451417917, 47.60279755390168), # Northeast corner (longitude, latitude)
|
|
114
|
+
(-122.32922451417917, 47.59830044521263) # Southeast corner (longitude, latitude)
|
|
115
115
|
]
|
|
116
116
|
```
|
|
117
117
|
|
|
@@ -249,6 +249,62 @@ export_magicavoxel_vox(voxcity_grid, output_path, base_filename=base_filename)
|
|
|
249
249
|
|
|
250
250
|
### 6. Additional Use Cases
|
|
251
251
|
|
|
252
|
+
#### Compute Solar Irradiance:
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
from voxcity.sim.solar import get_global_solar_irradiance_using_epw
|
|
256
|
+
|
|
257
|
+
solar_kwargs = {
|
|
258
|
+
"download_nearest_epw": True, # Whether to automatically download nearest EPW weather file based on location from Climate.OneBuilding.Org
|
|
259
|
+
"rectangle_vertices": rectangle_vertices, # Coordinates defining the area of interest for calculation
|
|
260
|
+
# "epw_file_path": "./output/new.york-downtown.manhattan.heli_ny_usa_1.epw", # Path to EnergyPlus Weather (EPW) file containing climate data. Set if you already have an EPW file.
|
|
261
|
+
"calc_time": "01-01 12:00:00", # Time for instantaneous calculation in format "MM-DD HH:MM:SS"
|
|
262
|
+
"view_point_height": 1.5, # Height of view point in meters for calculating solar access. Default: 1.5 m
|
|
263
|
+
"tree_k": 0.6, # Static extinction coefficient - controls how much sunlight is blocked by trees (higher = more blocking)
|
|
264
|
+
"tree_lad": 1.0, # Leaf area density of trees - density of leaves/branches that affect shading (higher = denser foliage)
|
|
265
|
+
"dem_grid": dem_grid, # Digital elevation model grid for terrain heights
|
|
266
|
+
"colormap": 'magma', # Matplotlib colormap for visualization. Default: 'viridis'
|
|
267
|
+
"obj_export": True, # Whether to export results as 3D OBJ file
|
|
268
|
+
"output_directory": 'output/test', # Directory for saving output files
|
|
269
|
+
"output_file_name": 'instantaneous_solar_irradiance', # Base filename for outputs (without extension)
|
|
270
|
+
"alpha": 1.0, # Transparency of visualization (0.0-1.0)
|
|
271
|
+
"vmin": 0, # Minimum value for colormap scaling in visualization
|
|
272
|
+
# "vmax": 900, # Maximum value for colormap scaling in visualization
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
# Compute global solar irradiance map (direct + diffuse radiation)
|
|
276
|
+
global_map = get_global_solar_irradiance_using_epw(
|
|
277
|
+
voxcity_grid, # 3D voxel grid representing the urban environment
|
|
278
|
+
meshsize, # Size of each voxel in meters
|
|
279
|
+
calc_type='instantaneous', # Calculate instantaneous irradiance at specified time
|
|
280
|
+
direct_normal_irradiance_scaling=1.0, # Scaling factor for direct solar radiation (1.0 = no scaling)
|
|
281
|
+
diffuse_irradiance_scaling=1.0, # Scaling factor for diffuse solar radiation (1.0 = no scaling)
|
|
282
|
+
**solar_kwargs # Pass all the parameters defined above
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# Adjust parameters for cumulative calculation
|
|
286
|
+
solar_kwargs["start_time"] = "01-01 01:00:00" # Start time for cumulative calculation
|
|
287
|
+
solar_kwargs["end_time"] = "01-31 23:00:00" # End time for cumulative calculation
|
|
288
|
+
solar_kwargs["output_file_name"] = 'cummulative_solar_irradiance', # Base filename for outputs (without extension)
|
|
289
|
+
|
|
290
|
+
# Calculate cumulative solar irradiance over the specified time period
|
|
291
|
+
global_map = get_global_solar_irradiance_using_epw(
|
|
292
|
+
voxcity_grid, # 3D voxel grid representing the urban environment
|
|
293
|
+
meshsize, # Size of each voxel in meters
|
|
294
|
+
calc_type='cumulative', # Calculate cumulative irradiance over time period instead of instantaneous
|
|
295
|
+
direct_normal_irradiance_scaling=1.0, # Scaling factor for direct solar radiation (1.0 = no scaling)
|
|
296
|
+
diffuse_irradiance_scaling=1.0, # Scaling factor for diffuse solar radiation (1.0 = no scaling)
|
|
297
|
+
**solar_kwargs # Pass all the parameters defined above
|
|
298
|
+
)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
<p align="center">
|
|
302
|
+
<img src="https://raw.githubusercontent.com/kunifujiwara/VoxCity/main/images/solar.png" alt="Solar Irradiance Maps Rendered in Rhino" width="800">
|
|
303
|
+
</p>
|
|
304
|
+
<p align="center">
|
|
305
|
+
<em>Example Results Saved as OBJ and Rendered in Rhino</em>
|
|
306
|
+
</p>
|
|
307
|
+
|
|
252
308
|
#### Compute Green View Index (GVI) and Sky View Index (SVI):
|
|
253
309
|
|
|
254
310
|
```python
|
|
@@ -68,16 +68,11 @@ def filter_and_convert_gdf_to_geojson_eubucco(gpkg_file, layer_name, rectangle_v
|
|
|
68
68
|
Parameters:
|
|
69
69
|
- gpkg_file (str): Path to the GeoPackage file.
|
|
70
70
|
- layer_name (str): Name of the layer within the GeoPackage to process.
|
|
71
|
-
- rectangle_vertices (list of tuples): List of (
|
|
71
|
+
- rectangle_vertices (list of tuples): List of (longitude, latitude) tuples defining the rectangle.
|
|
72
72
|
- output_geojson (str): Path to the output GeoJSON file.
|
|
73
73
|
"""
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
rectangle_polygon = Polygon(rectangle_vertices_lonlat)
|
|
77
|
-
|
|
78
|
-
# Helper function to swap coordinate order
|
|
79
|
-
def swap_coordinates_gdf(x, y, z=None):
|
|
80
|
-
return y, x
|
|
74
|
+
# Create polygon from rectangle vertices (already in lon,lat format)
|
|
75
|
+
rectangle_polygon = Polygon(rectangle_vertices)
|
|
81
76
|
|
|
82
77
|
# Get Shapely version for compatibility checks
|
|
83
78
|
shapely_version = shapely.__version__
|
|
@@ -165,13 +160,10 @@ def filter_and_convert_gdf_to_geojson_eubucco(gpkg_file, layer_name, rectangle_v
|
|
|
165
160
|
else:
|
|
166
161
|
shapely_transformed_poly = poly
|
|
167
162
|
|
|
168
|
-
#
|
|
169
|
-
swapped_poly = transform(swap_coordinates_gdf, shapely_transformed_poly)
|
|
170
|
-
|
|
171
|
-
# Extract polygon coordinates
|
|
163
|
+
# Extract polygon coordinates (already in lon,lat format)
|
|
172
164
|
coords = []
|
|
173
|
-
coords.append(list(
|
|
174
|
-
for interior in
|
|
165
|
+
coords.append(list(shapely_transformed_poly.exterior.coords)) # Exterior ring
|
|
166
|
+
for interior in shapely_transformed_poly.interiors: # Interior rings (holes)
|
|
175
167
|
coords.append(list(interior.coords))
|
|
176
168
|
|
|
177
169
|
# Create GeoJSON geometry
|
|
@@ -259,7 +251,7 @@ def save_geojson_from_eubucco(rectangle_vertices, country_links, output_dir, fil
|
|
|
259
251
|
Downloads, extracts, filters, and converts GeoPackage data to GeoJSON based on the rectangle vertices.
|
|
260
252
|
|
|
261
253
|
Parameters:
|
|
262
|
-
- rectangle_vertices (list of tuples): List of (
|
|
254
|
+
- rectangle_vertices (list of tuples): List of (longitude, latitude) tuples defining the rectangle.
|
|
263
255
|
- country_links (dict): Dictionary mapping country names to their respective GeoPackage URLs.
|
|
264
256
|
- output_dir (str): Directory to save output files
|
|
265
257
|
- file_name (str): Name for output GeoJSON file
|
|
@@ -268,7 +260,7 @@ def save_geojson_from_eubucco(rectangle_vertices, country_links, output_dir, fil
|
|
|
268
260
|
- None: Writes the output to a GeoJSON file.
|
|
269
261
|
"""
|
|
270
262
|
# Determine country based on first vertex
|
|
271
|
-
country_name = get_country_name(rectangle_vertices[0][0], rectangle_vertices[0][1])
|
|
263
|
+
country_name = get_country_name(rectangle_vertices[0][0], rectangle_vertices[0][1]) # Swap order for get_country_name
|
|
272
264
|
if country_name in country_links:
|
|
273
265
|
url = country_links[country_name]
|
|
274
266
|
else:
|
|
@@ -304,7 +296,7 @@ def load_geojson_from_eubucco(rectangle_vertices, output_dir):
|
|
|
304
296
|
Downloads EUBUCCO data and loads it as GeoJSON.
|
|
305
297
|
|
|
306
298
|
Parameters:
|
|
307
|
-
- rectangle_vertices (list of tuples): List of (
|
|
299
|
+
- rectangle_vertices (list of tuples): List of (longitude, latitude) tuples defining the area
|
|
308
300
|
- output_dir (str): Directory to save intermediate files
|
|
309
301
|
|
|
310
302
|
Returns:
|
|
@@ -11,7 +11,7 @@ import ee
|
|
|
11
11
|
import geemap
|
|
12
12
|
|
|
13
13
|
# Local imports
|
|
14
|
-
from ..geo.utils import convert_format_lat_lon
|
|
14
|
+
# from ..geo.utils import convert_format_lat_lon
|
|
15
15
|
|
|
16
16
|
def initialize_earth_engine():
|
|
17
17
|
"""Initialize the Earth Engine API."""
|
|
@@ -21,12 +21,13 @@ def get_roi(input_coords):
|
|
|
21
21
|
"""Create an Earth Engine region of interest polygon from coordinates.
|
|
22
22
|
|
|
23
23
|
Args:
|
|
24
|
-
input_coords: List of coordinate pairs defining the polygon vertices
|
|
24
|
+
input_coords: List of coordinate pairs defining the polygon vertices in (lon, lat) format
|
|
25
25
|
|
|
26
26
|
Returns:
|
|
27
27
|
ee.Geometry.Polygon: Earth Engine polygon geometry
|
|
28
28
|
"""
|
|
29
|
-
coords =
|
|
29
|
+
coords = input_coords.copy()
|
|
30
|
+
coords.append(input_coords[0])
|
|
30
31
|
return ee.Geometry.Polygon(coords)
|
|
31
32
|
|
|
32
33
|
def get_center_point(roi):
|
|
@@ -40,13 +40,13 @@ def get_geojson_links(output_dir):
|
|
|
40
40
|
df_links = pd.read_csv(filepath, dtype=data_types)
|
|
41
41
|
return df_links
|
|
42
42
|
|
|
43
|
-
def find_row_for_location(df,
|
|
44
|
-
"""Find the dataset row containing building data for a given lat
|
|
43
|
+
def find_row_for_location(df, lon, lat):
|
|
44
|
+
"""Find the dataset row containing building data for a given lon/lat coordinate.
|
|
45
45
|
|
|
46
46
|
Args:
|
|
47
47
|
df: DataFrame containing dataset links
|
|
48
|
-
lat: Latitude coordinate to search for
|
|
49
48
|
lon: Longitude coordinate to search for
|
|
49
|
+
lat: Latitude coordinate to search for
|
|
50
50
|
|
|
51
51
|
Returns:
|
|
52
52
|
pandas.Series: Matching row from DataFrame, or None if no match found
|
|
@@ -57,7 +57,7 @@ def find_row_for_location(df, lat, lon):
|
|
|
57
57
|
continue
|
|
58
58
|
|
|
59
59
|
try:
|
|
60
|
-
# Convert lat
|
|
60
|
+
# Convert lon/lat to tile coordinates at the quadkey's zoom level
|
|
61
61
|
loc_tile_x, loc_tile_y = tile_from_lat_lon(lat, lon, len(quadkey))
|
|
62
62
|
qk_tile_x, qk_tile_y, _ = quadkey_to_tile(quadkey)
|
|
63
63
|
|
|
@@ -73,7 +73,7 @@ def get_mbfp_geojson(output_dir, rectangle_vertices):
|
|
|
73
73
|
|
|
74
74
|
Args:
|
|
75
75
|
output_dir: Directory to save downloaded files
|
|
76
|
-
rectangle_vertices: List of (
|
|
76
|
+
rectangle_vertices: List of (lon, lat) tuples defining the rectangle corners
|
|
77
77
|
|
|
78
78
|
Returns:
|
|
79
79
|
dict: GeoJSON data containing building footprints
|
|
@@ -84,8 +84,8 @@ def get_mbfp_geojson(output_dir, rectangle_vertices):
|
|
|
84
84
|
# Find and download files for each vertex of the rectangle
|
|
85
85
|
filenames = []
|
|
86
86
|
for vertex in rectangle_vertices:
|
|
87
|
-
|
|
88
|
-
row = find_row_for_location(df_links,
|
|
87
|
+
lon, lat = vertex
|
|
88
|
+
row = find_row_for_location(df_links, lon, lat)
|
|
89
89
|
if row is not None:
|
|
90
90
|
# Construct filename and download if not already downloaded
|
|
91
91
|
location = row["Location"]
|
|
@@ -15,12 +15,12 @@ import numpy as np
|
|
|
15
15
|
from osgeo import gdal, osr
|
|
16
16
|
import pyproj
|
|
17
17
|
|
|
18
|
-
def deg2num(
|
|
19
|
-
"""Convert latitude
|
|
18
|
+
def deg2num(lon_deg, lat_deg, zoom):
|
|
19
|
+
"""Convert longitude/latitude coordinates to tile coordinates.
|
|
20
20
|
|
|
21
21
|
Args:
|
|
22
|
-
lat_deg (float): Latitude in degrees
|
|
23
22
|
lon_deg (float): Longitude in degrees
|
|
23
|
+
lat_deg (float): Latitude in degrees
|
|
24
24
|
zoom (int): Zoom level
|
|
25
25
|
|
|
26
26
|
Returns:
|
|
@@ -33,7 +33,7 @@ def deg2num(lat_deg, lon_deg, zoom):
|
|
|
33
33
|
return (xtile, ytile)
|
|
34
34
|
|
|
35
35
|
def num2deg(xtile, ytile, zoom):
|
|
36
|
-
"""Convert tile coordinates to latitude
|
|
36
|
+
"""Convert tile coordinates to longitude/latitude coordinates.
|
|
37
37
|
|
|
38
38
|
Args:
|
|
39
39
|
xtile (float): X tile coordinate
|
|
@@ -41,19 +41,19 @@ def num2deg(xtile, ytile, zoom):
|
|
|
41
41
|
zoom (int): Zoom level
|
|
42
42
|
|
|
43
43
|
Returns:
|
|
44
|
-
tuple: (
|
|
44
|
+
tuple: (longitude, latitude) in degrees
|
|
45
45
|
"""
|
|
46
46
|
n = 2.0 ** zoom
|
|
47
47
|
lon_deg = xtile / n * 360.0 - 180.0
|
|
48
48
|
lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n)))
|
|
49
49
|
lat_deg = math.degrees(lat_rad)
|
|
50
|
-
return (
|
|
50
|
+
return (lon_deg, lat_deg)
|
|
51
51
|
|
|
52
52
|
def download_tiles(polygon, zoom):
|
|
53
53
|
"""Download satellite imagery tiles covering a polygon region.
|
|
54
54
|
|
|
55
55
|
Args:
|
|
56
|
-
polygon (list): List of (
|
|
56
|
+
polygon (list): List of (lon, lat) coordinates defining the region
|
|
57
57
|
zoom (int): Zoom level for tile detail
|
|
58
58
|
|
|
59
59
|
Returns:
|
|
@@ -62,14 +62,14 @@ def download_tiles(polygon, zoom):
|
|
|
62
62
|
print(f"Downloading tiles")
|
|
63
63
|
|
|
64
64
|
# Find bounding box of polygon
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
min_lon = min(p[0] for p in polygon)
|
|
66
|
+
max_lon = max(p[0] for p in polygon)
|
|
67
|
+
min_lat = min(p[1] for p in polygon)
|
|
68
|
+
max_lat = max(p[1] for p in polygon)
|
|
69
69
|
|
|
70
70
|
# Convert to tile coordinates
|
|
71
|
-
min_x, max_y = map(math.floor, deg2num(
|
|
72
|
-
max_x, min_y = map(math.ceil, deg2num(
|
|
71
|
+
min_x, max_y = map(math.floor, deg2num(min_lon, max_lat, zoom))
|
|
72
|
+
max_x, min_y = map(math.ceil, deg2num(max_lon, min_lat, zoom))
|
|
73
73
|
|
|
74
74
|
# Download tiles within bounds
|
|
75
75
|
tiles = {}
|
|
@@ -108,7 +108,7 @@ def crop_image(image, polygon, bounds, zoom):
|
|
|
108
108
|
|
|
109
109
|
Args:
|
|
110
110
|
image (Image): PIL Image to crop
|
|
111
|
-
polygon (list): List of (
|
|
111
|
+
polygon (list): List of (lon, lat) coordinates
|
|
112
112
|
bounds (tuple): (min_x, min_y, max_x, max_y) tile bounds
|
|
113
113
|
zoom (int): Zoom level
|
|
114
114
|
|
|
@@ -120,8 +120,8 @@ def crop_image(image, polygon, bounds, zoom):
|
|
|
120
120
|
|
|
121
121
|
# Convert polygon coordinates to pixel coordinates
|
|
122
122
|
polygon_pixels = []
|
|
123
|
-
for
|
|
124
|
-
x, y = deg2num(
|
|
123
|
+
for lon, lat in polygon:
|
|
124
|
+
x, y = deg2num(lon, lat, zoom)
|
|
125
125
|
px = (x - min_x) * 256
|
|
126
126
|
py = (y - min_y) * 256
|
|
127
127
|
polygon_pixels.append((px, py))
|
|
@@ -143,7 +143,7 @@ def save_as_geotiff(image, polygon, zoom, bbox, bounds, output_path):
|
|
|
143
143
|
|
|
144
144
|
Args:
|
|
145
145
|
image (Image): PIL Image to save
|
|
146
|
-
polygon (list): List of (
|
|
146
|
+
polygon (list): List of (lon, lat) coordinates
|
|
147
147
|
zoom (int): Zoom level
|
|
148
148
|
bbox (tuple): Bounding box of cropped image
|
|
149
149
|
bounds (tuple): (min_x, min_y, max_x, max_y) tile bounds
|
|
@@ -152,8 +152,8 @@ def save_as_geotiff(image, polygon, zoom, bbox, bounds, output_path):
|
|
|
152
152
|
min_x, min_y, max_x, max_y = bounds
|
|
153
153
|
|
|
154
154
|
# Calculate georeferencing coordinates
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
lon_upper_left, lat_upper_left = num2deg(min_x + bbox[0]/256, min_y + bbox[1]/256, zoom)
|
|
156
|
+
lon_lower_right, lat_lower_right = num2deg(min_x + bbox[2]/256, min_y + bbox[3]/256, zoom)
|
|
157
157
|
|
|
158
158
|
# Create transformation from WGS84 to Web Mercator
|
|
159
159
|
wgs84 = pyproj.CRS('EPSG:4326')
|
|
@@ -161,8 +161,8 @@ def save_as_geotiff(image, polygon, zoom, bbox, bounds, output_path):
|
|
|
161
161
|
transformer = pyproj.Transformer.from_crs(wgs84, web_mercator, always_xy=True)
|
|
162
162
|
|
|
163
163
|
# Transform coordinates to Web Mercator
|
|
164
|
-
upper_left_x, upper_left_y = transformer.transform(
|
|
165
|
-
lower_right_x, lower_right_y = transformer.transform(
|
|
164
|
+
upper_left_x, upper_left_y = transformer.transform(lon_upper_left, lat_upper_left)
|
|
165
|
+
lower_right_x, lower_right_y = transformer.transform(lon_lower_right, lat_lower_right)
|
|
166
166
|
|
|
167
167
|
# Calculate pixel size
|
|
168
168
|
pixel_size_x = (lower_right_x - upper_left_x) / image.width
|
|
@@ -190,7 +190,7 @@ def save_oemj_as_geotiff(polygon, filepath, zoom=16):
|
|
|
190
190
|
"""Download and save OpenEarthMap Japan imagery as GeoTIFF.
|
|
191
191
|
|
|
192
192
|
Args:
|
|
193
|
-
polygon (list): List of (
|
|
193
|
+
polygon (list): List of (lon, lat) coordinates defining region
|
|
194
194
|
filepath (str): Output path for GeoTIFF
|
|
195
195
|
zoom (int, optional): Zoom level for detail. Defaults to 16.
|
|
196
196
|
"""
|
|
@@ -20,20 +20,20 @@ def load_geojsons_from_openmaptiles(rectangle_vertices, API_KEY):
|
|
|
20
20
|
"""Download and process building footprint data from OpenMapTiles vector tiles.
|
|
21
21
|
|
|
22
22
|
Args:
|
|
23
|
-
rectangle_vertices: List of (
|
|
23
|
+
rectangle_vertices: List of (lon, lat) coordinates defining the bounding box
|
|
24
24
|
API_KEY: OpenMapTiles API key for authentication
|
|
25
25
|
|
|
26
26
|
Returns:
|
|
27
27
|
list: List of GeoJSON features containing building footprints with standardized properties
|
|
28
28
|
"""
|
|
29
|
-
# Extract
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
# Extract longitudes and latitudes from vertices to find bounding box
|
|
30
|
+
lons = [coord[0] for coord in rectangle_vertices]
|
|
31
|
+
lats = [coord[1] for coord in rectangle_vertices]
|
|
32
32
|
|
|
33
|
-
min_lat = min(lats)
|
|
34
|
-
max_lat = max(lats)
|
|
35
33
|
min_lon = min(lons)
|
|
36
34
|
max_lon = max(lons)
|
|
35
|
+
min_lat = min(lats)
|
|
36
|
+
max_lat = max(lats)
|
|
37
37
|
|
|
38
38
|
# Use zoom level 15 which provides good detail for buildings while keeping data size manageable
|
|
39
39
|
zoom = 15
|
|
@@ -183,10 +183,10 @@ def convert_geojson_format(features):
|
|
|
183
183
|
ring_properties['is_inner'] = j > 0
|
|
184
184
|
ring_properties['role'] = 'inner' if j > 0 else 'outer'
|
|
185
185
|
|
|
186
|
-
# Create new geometry
|
|
186
|
+
# Create new geometry keeping coordinate order as (lon,lat)
|
|
187
187
|
new_geometry = {
|
|
188
188
|
'type': 'Polygon',
|
|
189
|
-
'coordinates': [
|
|
189
|
+
'coordinates': [ring]
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
new_feature = {
|
|
@@ -204,10 +204,10 @@ def convert_geojson_format(features):
|
|
|
204
204
|
ring_properties['is_inner'] = i > 0
|
|
205
205
|
ring_properties['role'] = 'inner' if i > 0 else 'outer'
|
|
206
206
|
|
|
207
|
-
# Create new geometry
|
|
207
|
+
# Create new geometry keeping coordinate order as (lon,lat)
|
|
208
208
|
new_geometry = {
|
|
209
209
|
'type': 'Polygon',
|
|
210
|
-
'coordinates': [
|
|
210
|
+
'coordinates': [ring]
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
new_feature = {
|
|
@@ -26,16 +26,16 @@ def load_geojsons_from_openstreetmap(rectangle_vertices):
|
|
|
26
26
|
"""Download and process building footprint data from OpenStreetMap.
|
|
27
27
|
|
|
28
28
|
Args:
|
|
29
|
-
rectangle_vertices: List of (
|
|
29
|
+
rectangle_vertices: List of (lon, lat) coordinates defining the bounding box
|
|
30
30
|
|
|
31
31
|
Returns:
|
|
32
32
|
list: List of GeoJSON features containing building footprints with standardized properties
|
|
33
33
|
"""
|
|
34
34
|
# Create a bounding box from the rectangle vertices
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
min_lon = min(v[0] for v in rectangle_vertices)
|
|
36
|
+
max_lon = max(v[0] for v in rectangle_vertices)
|
|
37
|
+
min_lat = min(v[1] for v in rectangle_vertices)
|
|
38
|
+
max_lat = max(v[1] for v in rectangle_vertices)
|
|
39
39
|
|
|
40
40
|
# Enhanced Overpass API query with recursive member extraction
|
|
41
41
|
overpass_url = "http://overpass-api.de/api/interpreter"
|
|
@@ -73,7 +73,7 @@ def load_geojsons_from_openstreetmap(rectangle_vertices):
|
|
|
73
73
|
Returns:
|
|
74
74
|
list: Processed coordinate pairs with reversed order
|
|
75
75
|
"""
|
|
76
|
-
return [coord
|
|
76
|
+
return [coord for coord in geometry] # Keep original order since already (lon, lat)
|
|
77
77
|
|
|
78
78
|
def get_height_from_properties(properties):
|
|
79
79
|
"""Helper function to extract height from properties.
|
|
@@ -253,7 +253,7 @@ def convert_feature(feature):
|
|
|
253
253
|
for coord in ring:
|
|
254
254
|
# Swap the order if needed (assuming original is [lat, lon])
|
|
255
255
|
lat, lon = coord
|
|
256
|
-
new_ring.append((lat,
|
|
256
|
+
new_ring.append((lon, lat)) # Changed to (lon, lat)
|
|
257
257
|
new_coordinates.append(new_ring)
|
|
258
258
|
|
|
259
259
|
new_feature['geometry']['type'] = 'Polygon'
|
|
@@ -460,9 +460,8 @@ def swap_coordinates(geom_mapping):
|
|
|
460
460
|
if isinstance(coord_list[0], (list, tuple)):
|
|
461
461
|
return [swap_coords(c) for c in coord_list]
|
|
462
462
|
else:
|
|
463
|
-
#
|
|
464
|
-
|
|
465
|
-
return [lat, lon]
|
|
463
|
+
# Keep original order since already (lon, lat)
|
|
464
|
+
return coord_list
|
|
466
465
|
|
|
467
466
|
geom_mapping['coordinates'] = swap_coords(coords)
|
|
468
467
|
return geom_mapping
|
|
@@ -471,7 +470,7 @@ def load_land_cover_geojson_from_osm(rectangle_vertices_ori):
|
|
|
471
470
|
"""Load land cover data from OpenStreetMap within a given rectangular area.
|
|
472
471
|
|
|
473
472
|
Args:
|
|
474
|
-
rectangle_vertices_ori (list): List of (
|
|
473
|
+
rectangle_vertices_ori (list): List of (lon, lat) coordinates defining the rectangle
|
|
475
474
|
|
|
476
475
|
Returns:
|
|
477
476
|
list: List of GeoJSON features with land cover classifications
|
|
@@ -480,8 +479,11 @@ def load_land_cover_geojson_from_osm(rectangle_vertices_ori):
|
|
|
480
479
|
rectangle_vertices = rectangle_vertices_ori.copy()
|
|
481
480
|
rectangle_vertices.append(rectangle_vertices_ori[0])
|
|
482
481
|
|
|
483
|
-
#
|
|
484
|
-
|
|
482
|
+
# Instead of using poly:"lat lon lat lon...", use area coordinates
|
|
483
|
+
min_lat = min(lat for lon, lat in rectangle_vertices)
|
|
484
|
+
max_lat = max(lat for lon, lat in rectangle_vertices)
|
|
485
|
+
min_lon = min(lon for lon, lat in rectangle_vertices)
|
|
486
|
+
max_lon = max(lon for lon, lat in rectangle_vertices)
|
|
485
487
|
|
|
486
488
|
# Initialize dictionary to store OSM keys and their allowed values
|
|
487
489
|
osm_keys_values = defaultdict(list)
|
|
@@ -504,16 +506,16 @@ def load_land_cover_geojson_from_osm(rectangle_vertices_ori):
|
|
|
504
506
|
for key, values in osm_keys_values.items():
|
|
505
507
|
if values:
|
|
506
508
|
if values == ['*']:
|
|
507
|
-
# Query for any value of this key
|
|
508
|
-
query_parts.append(f'way["{key}"](
|
|
509
|
-
query_parts.append(f'relation["{key}"](
|
|
509
|
+
# Query for any value of this key using bounding box
|
|
510
|
+
query_parts.append(f'way["{key}"]({min_lat},{min_lon},{max_lat},{max_lon});')
|
|
511
|
+
query_parts.append(f'relation["{key}"]({min_lat},{min_lon},{max_lat},{max_lon});')
|
|
510
512
|
else:
|
|
511
513
|
# Remove duplicate values
|
|
512
514
|
values = list(set(values))
|
|
513
515
|
# Build regex pattern for specific values
|
|
514
516
|
values_regex = '|'.join(values)
|
|
515
|
-
query_parts.append(f'way["{key}"~"^{values_regex}$"](
|
|
516
|
-
query_parts.append(f'relation["{key}"~"^{values_regex}$"](
|
|
517
|
+
query_parts.append(f'way["{key}"~"^{values_regex}$"]({min_lat},{min_lon},{max_lat},{max_lon});')
|
|
518
|
+
query_parts.append(f'relation["{key}"~"^{values_regex}$"]({min_lat},{min_lon},{max_lat},{max_lon});')
|
|
517
519
|
|
|
518
520
|
# Combine query parts into complete Overpass query
|
|
519
521
|
query_body = "\n ".join(query_parts)
|
|
@@ -541,11 +543,11 @@ def load_land_cover_geojson_from_osm(rectangle_vertices_ori):
|
|
|
541
543
|
geojson_data = json2geojson(data)
|
|
542
544
|
|
|
543
545
|
# Create shapely polygon from rectangle vertices (in lon,lat order)
|
|
544
|
-
rectangle_polygon = Polygon(
|
|
546
|
+
rectangle_polygon = Polygon(rectangle_vertices)
|
|
545
547
|
|
|
546
548
|
# Calculate center point for projection
|
|
547
|
-
center_lat = sum(lat for
|
|
548
|
-
center_lon = sum(lon for
|
|
549
|
+
center_lat = sum(lat for lon, lat in rectangle_vertices) / len(rectangle_vertices)
|
|
550
|
+
center_lon = sum(lon for lon, lat in rectangle_vertices) / len(rectangle_vertices)
|
|
549
551
|
|
|
550
552
|
# Set up coordinate reference systems for projection
|
|
551
553
|
wgs84 = pyproj.CRS('EPSG:4326') # Standard lat/lon
|