voxcity 0.3.12__tar.gz → 0.3.14__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.

Files changed (53) hide show
  1. {voxcity-0.3.12 → voxcity-0.3.14}/PKG-INFO +2 -2
  2. {voxcity-0.3.12 → voxcity-0.3.14}/README.md +1 -1
  3. {voxcity-0.3.12 → voxcity-0.3.14}/pyproject.toml +2 -2
  4. {voxcity-0.3.12 → voxcity-0.3.14}/src/voxcity/__init__.py +1 -1
  5. {voxcity-0.3.12/src/voxcity/download → voxcity-0.3.14/src/voxcity/downloader}/eubucco.py +21 -35
  6. {voxcity-0.3.12/src/voxcity/download → voxcity-0.3.14/src/voxcity/downloader}/mbfp.py +5 -6
  7. {voxcity-0.3.12/src/voxcity/download → voxcity-0.3.14/src/voxcity/downloader}/omt.py +11 -4
  8. {voxcity-0.3.12/src/voxcity/download → voxcity-0.3.14/src/voxcity/downloader}/osm.py +31 -35
  9. {voxcity-0.3.12/src/voxcity/download → voxcity-0.3.14/src/voxcity/downloader}/overture.py +7 -4
  10. {voxcity-0.3.12/src/voxcity/file → voxcity-0.3.14/src/voxcity/exporter}/envimet.py +2 -2
  11. voxcity-0.3.12/src/voxcity/voxcity.py → voxcity-0.3.14/src/voxcity/generator.py +34 -33
  12. {voxcity-0.3.12/src/voxcity/geo → voxcity-0.3.14/src/voxcity/geoprocessor}/__init_.py +1 -0
  13. {voxcity-0.3.12/src/voxcity/geo → voxcity-0.3.14/src/voxcity/geoprocessor}/draw.py +15 -22
  14. {voxcity-0.3.12/src/voxcity/geo → voxcity-0.3.14/src/voxcity/geoprocessor}/grid.py +199 -90
  15. {voxcity-0.3.12/src/voxcity/geo → voxcity-0.3.14/src/voxcity/geoprocessor}/network.py +7 -7
  16. voxcity-0.3.12/src/voxcity/file/geojson.py → voxcity-0.3.14/src/voxcity/geoprocessor/polygon.py +146 -168
  17. {voxcity-0.3.12/src/voxcity/sim → voxcity-0.3.14/src/voxcity/simulator}/solar.py +1 -1
  18. {voxcity-0.3.12/src/voxcity/sim → voxcity-0.3.14/src/voxcity/simulator}/view.py +6 -7
  19. {voxcity-0.3.12 → voxcity-0.3.14}/src/voxcity/utils/visualization.py +2 -2
  20. {voxcity-0.3.12 → voxcity-0.3.14}/src/voxcity.egg-info/PKG-INFO +2 -2
  21. voxcity-0.3.14/src/voxcity.egg-info/SOURCES.txt +50 -0
  22. voxcity-0.3.12/src/voxcity.egg-info/SOURCES.txt +0 -50
  23. {voxcity-0.3.12 → voxcity-0.3.14}/AUTHORS.rst +0 -0
  24. {voxcity-0.3.12 → voxcity-0.3.14}/CONTRIBUTING.rst +0 -0
  25. {voxcity-0.3.12 → voxcity-0.3.14}/HISTORY.rst +0 -0
  26. {voxcity-0.3.12 → voxcity-0.3.14}/LICENSE +0 -0
  27. {voxcity-0.3.12 → voxcity-0.3.14}/MANIFEST.in +0 -0
  28. {voxcity-0.3.12 → voxcity-0.3.14}/docs/Makefile +0 -0
  29. {voxcity-0.3.12 → voxcity-0.3.14}/docs/archive/README.rst +0 -0
  30. {voxcity-0.3.12 → voxcity-0.3.14}/docs/authors.rst +0 -0
  31. {voxcity-0.3.12 → voxcity-0.3.14}/docs/conf.py +0 -0
  32. {voxcity-0.3.12 → voxcity-0.3.14}/docs/index.rst +0 -0
  33. {voxcity-0.3.12 → voxcity-0.3.14}/docs/make.bat +0 -0
  34. {voxcity-0.3.12 → voxcity-0.3.14}/setup.cfg +0 -0
  35. {voxcity-0.3.12/src/voxcity/download → voxcity-0.3.14/src/voxcity/downloader}/__init__.py +0 -0
  36. {voxcity-0.3.12/src/voxcity/download → voxcity-0.3.14/src/voxcity/downloader}/gee.py +0 -0
  37. {voxcity-0.3.12/src/voxcity/download → voxcity-0.3.14/src/voxcity/downloader}/oemj.py +0 -0
  38. {voxcity-0.3.12/src/voxcity/download → voxcity-0.3.14/src/voxcity/downloader}/utils.py +0 -0
  39. {voxcity-0.3.12/src/voxcity/file → voxcity-0.3.14/src/voxcity/exporter}/__init_.py +0 -0
  40. {voxcity-0.3.12/src/voxcity/file → voxcity-0.3.14/src/voxcity/exporter}/magicavoxel.py +0 -0
  41. {voxcity-0.3.12/src/voxcity/file → voxcity-0.3.14/src/voxcity/exporter}/obj.py +0 -0
  42. {voxcity-0.3.12/src/voxcity/geo → voxcity-0.3.14/src/voxcity/geoprocessor}/utils.py +0 -0
  43. {voxcity-0.3.12/src/voxcity/sim → voxcity-0.3.14/src/voxcity/simulator}/__init_.py +0 -0
  44. {voxcity-0.3.12/src/voxcity/sim → voxcity-0.3.14/src/voxcity/simulator}/utils.py +0 -0
  45. {voxcity-0.3.12 → voxcity-0.3.14}/src/voxcity/utils/__init_.py +0 -0
  46. {voxcity-0.3.12 → voxcity-0.3.14}/src/voxcity/utils/lc.py +0 -0
  47. {voxcity-0.3.12 → voxcity-0.3.14}/src/voxcity/utils/material.py +0 -0
  48. {voxcity-0.3.12 → voxcity-0.3.14}/src/voxcity/utils/weather.py +0 -0
  49. {voxcity-0.3.12 → voxcity-0.3.14}/src/voxcity.egg-info/dependency_links.txt +0 -0
  50. {voxcity-0.3.12 → voxcity-0.3.14}/src/voxcity.egg-info/requires.txt +0 -0
  51. {voxcity-0.3.12 → voxcity-0.3.14}/src/voxcity.egg-info/top_level.txt +0 -0
  52. {voxcity-0.3.12 → voxcity-0.3.14}/tests/__init__.py +0 -0
  53. {voxcity-0.3.12 → voxcity-0.3.14}/tests/voxelcity.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: voxcity
3
- Version: 0.3.12
3
+ Version: 0.3.14
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>
@@ -458,7 +458,7 @@ G, edge_gdf = get_network_values(
458
458
  | Dataset | Spatial Coverage | Source/Data Acquisition |
459
459
  |---------|------------------|------------------------|
460
460
  | [OpenStreetMap](https://www.openstreetmap.org) | Worldwide (24% completeness in city centers) | Volunteered / updated continuously |
461
- | [Global ML Building Footprints](https://github.com/microsoft/GlobalMLBuildingFootprints) | North America, Europe, Australia | Prediction from satellite or aerial imagery / 2018-2019 for majority of the input imagery |
461
+ | [Microsoft Building Footprints](https://github.com/microsoft/GlobalMLBuildingFootprints) | North America, Europe, Australia | Prediction from satellite or aerial imagery / 2018-2019 for majority of the input imagery |
462
462
  | [Open Buildings 2.5D Temporal Dataset](https://sites.research.google/gr/open-buildings/temporal/) | Africa, Latin America, and South and Southeast Asia | Prediction from satellite imagery / 2016-2023 |
463
463
  | [EUBUCCO v0.1](https://eubucco.com/) | 27 EU countries and Switzerland (378 regions and 40,829 cities) | OpenStreetMap, government datasets / 2003-2021 (majority is after 2019) |
464
464
  | [UT-GLOBUS](https://zenodo.org/records/11156602) | Worldwide (more than 1200 cities or locales) | Prediction from building footprints, population, spaceborne nDSM / not provided |
@@ -400,7 +400,7 @@ G, edge_gdf = get_network_values(
400
400
  | Dataset | Spatial Coverage | Source/Data Acquisition |
401
401
  |---------|------------------|------------------------|
402
402
  | [OpenStreetMap](https://www.openstreetmap.org) | Worldwide (24% completeness in city centers) | Volunteered / updated continuously |
403
- | [Global ML Building Footprints](https://github.com/microsoft/GlobalMLBuildingFootprints) | North America, Europe, Australia | Prediction from satellite or aerial imagery / 2018-2019 for majority of the input imagery |
403
+ | [Microsoft Building Footprints](https://github.com/microsoft/GlobalMLBuildingFootprints) | North America, Europe, Australia | Prediction from satellite or aerial imagery / 2018-2019 for majority of the input imagery |
404
404
  | [Open Buildings 2.5D Temporal Dataset](https://sites.research.google/gr/open-buildings/temporal/) | Africa, Latin America, and South and Southeast Asia | Prediction from satellite imagery / 2016-2023 |
405
405
  | [EUBUCCO v0.1](https://eubucco.com/) | 27 EU countries and Switzerland (378 regions and 40,829 cities) | OpenStreetMap, government datasets / 2003-2021 (majority is after 2019) |
406
406
  | [UT-GLOBUS](https://zenodo.org/records/11156602) | Worldwide (more than 1200 cities or locales) | Prediction from building footprints, population, spaceborne nDSM / not provided |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "voxcity"
3
- version = "0.3.12"
3
+ version = "0.3.14"
4
4
  requires-python = ">=3.10,<3.13"
5
5
  classifiers = [
6
6
  "Programming Language :: Python :: 3.10",
@@ -69,7 +69,7 @@ homepage = "https://github.com/kunifujiwara/voxcity"
69
69
 
70
70
  [tool.setuptools]
71
71
  package-dir = {"" = "src"}
72
- packages = ["voxcity", "voxcity.download", "voxcity.geo", "voxcity.file", "voxcity.sim", "voxcity.utils"]
72
+ packages = ["voxcity", "voxcity.downloader", "voxcity.geoprocessor", "voxcity.exporter", "voxcity.simulator", "voxcity.utils"]
73
73
 
74
74
  [tool.setuptools.package-data]
75
75
  "*" = ["*.*"]
@@ -2,7 +2,7 @@ __author__ = """Kunihiko Fujiwara"""
2
2
  __email__ = 'kunihiko@nus.edu.sg'
3
3
  __version__ = '0.1.0'
4
4
 
5
- from .voxcity import *
5
+ # from .generator import *
6
6
  # from .download import mbfp
7
7
  # # from .utils.draw import rotate_rectangle, draw_rectangle_map
8
8
  # from .geo import draw, utils
@@ -17,8 +17,9 @@ from shapely.ops import transform
17
17
  from fiona.transform import transform_geom
18
18
  import logging
19
19
  import shapely
20
+ import geopandas as gpd
20
21
 
21
- from ..geo.utils import get_country_name
22
+ from ..geoprocessor.utils import get_country_name
22
23
 
23
24
  # Dictionary mapping European countries to their EUBUCCO data download URLs
24
25
  country_links = {
@@ -246,7 +247,7 @@ def download_extract_open_gpkg_from_eubucco(url, output_dir):
246
247
  logging.info(f"GeoPackage file found: {gpkg_file}")
247
248
  return gpkg_file
248
249
 
249
- def save_geojson_from_eubucco(rectangle_vertices, country_links, output_dir, file_name):
250
+ def get_gdf_from_eubucco(rectangle_vertices, country_links, output_dir, file_name):
250
251
  """
251
252
  Downloads, extracts, filters, and converts GeoPackage data to GeoJSON based on the rectangle vertices.
252
253
 
@@ -270,28 +271,22 @@ def save_geojson_from_eubucco(rectangle_vertices, country_links, output_dir, fil
270
271
  # Download and extract GPKG file
271
272
  gpkg_file = download_extract_open_gpkg_from_eubucco(url, output_dir)
272
273
 
273
- # Get first layer from GPKG file
274
- with fiona.Env():
275
- layers = fiona.listlayers(gpkg_file)
276
- if not layers:
277
- logging.error("No layers found in the GeoPackage.")
278
- return
279
- layer_name = layers[0]
280
- logging.info(f"Using layer: {layer_name}")
281
-
282
- file_path = f"{output_dir}/{file_name}"
283
-
284
- # Convert GPKG to GeoJSON
285
- filter_and_convert_gdf_to_geojson_eubucco(
286
- gpkg_file=gpkg_file,
287
- layer_name=layer_name,
288
- rectangle_vertices=rectangle_vertices,
289
- output_geojson=file_path
290
- )
291
-
292
- logging.info(f"GeoJSON file has been created at: {file_path}")
293
-
294
- def load_geojson_from_eubucco(rectangle_vertices, output_dir):
274
+ # Read GeoPackage file while preserving its CRS
275
+ gdf = gpd.read_file(gpkg_file)
276
+
277
+ # Only set CRS if not already set
278
+ if gdf.crs is None:
279
+ gdf.set_crs(epsg=4326, inplace=True)
280
+ # Transform to WGS84 if needed
281
+ elif gdf.crs != "EPSG:4326":
282
+ gdf = gdf.to_crs(epsg=4326)
283
+
284
+ # Replace id column with index numbers
285
+ gdf['id'] = gdf.index
286
+
287
+ return gdf
288
+
289
+ def load_gdf_from_eubucco(rectangle_vertices, output_dir):
295
290
  """
296
291
  Downloads EUBUCCO data and loads it as GeoJSON.
297
292
 
@@ -307,15 +302,6 @@ def load_geojson_from_eubucco(rectangle_vertices, output_dir):
307
302
  file_path = f"{output_dir}/{file_name}"
308
303
 
309
304
  # Download and save GeoJSON
310
- save_geojson_from_eubucco(rectangle_vertices, country_links, output_dir, file_name)
311
-
312
- # Load and return GeoJSON features
313
- with open(file_path, 'r') as f:
314
- raw_data = json.load(f)
315
- geojson_data = raw_data['features']
316
-
317
- # Add id to each building's properties
318
- for i, feature in enumerate(geojson_data):
319
- feature['properties']['id'] = i + 1
305
+ gdf = get_gdf_from_eubucco(rectangle_vertices, country_links, output_dir, file_name)
320
306
 
321
- return geojson_data
307
+ return gdf
@@ -9,8 +9,8 @@ AI. It handles downloading quadkey-based data files and converting them to GeoJS
9
9
  import pandas as pd
10
10
  import os
11
11
  from .utils import download_file
12
- from ..geo.utils import tile_from_lat_lon, quadkey_to_tile
13
- from ..file.geojson import load_geojsons_from_multiple_gz, swap_coordinates
12
+ from ..geoprocessor.utils import tile_from_lat_lon, quadkey_to_tile
13
+ from ..geoprocessor.polygon import load_gdf_from_multiple_gz, swap_coordinates
14
14
 
15
15
  def get_geojson_links(output_dir):
16
16
  """Download and load the dataset links CSV file containing building footprint URLs.
@@ -68,7 +68,7 @@ def find_row_for_location(df, lon, lat):
68
68
  print(f"Error processing row {index}: {e}")
69
69
  return None
70
70
 
71
- def get_mbfp_geojson(output_dir, rectangle_vertices):
71
+ def get_mbfp_gdf(output_dir, rectangle_vertices):
72
72
  """Download and process building footprint data for a rectangular region.
73
73
 
74
74
  Args:
@@ -98,7 +98,6 @@ def get_mbfp_geojson(output_dir, rectangle_vertices):
98
98
  print("No matching row found.")
99
99
 
100
100
  # Load GeoJSON data from downloaded files and fix coordinate ordering
101
- geojson_data = load_geojsons_from_multiple_gz(filenames)
102
- swap_coordinates(geojson_data)
101
+ gdf = load_gdf_from_multiple_gz(filenames)
103
102
 
104
- return geojson_data
103
+ return gdf
@@ -15,8 +15,8 @@ import shapely.ops
15
15
  import json
16
16
  from pyproj import Transformer
17
17
  import json
18
-
19
- def load_geojsons_from_openmaptiles(rectangle_vertices, API_KEY):
18
+ import geopandas as gpd
19
+ def load_gdf_from_openmaptiles(rectangle_vertices, API_KEY):
20
20
  """Download and process building footprint data from OpenMapTiles vector tiles.
21
21
 
22
22
  Args:
@@ -24,7 +24,7 @@ def load_geojsons_from_openmaptiles(rectangle_vertices, API_KEY):
24
24
  API_KEY: OpenMapTiles API key for authentication
25
25
 
26
26
  Returns:
27
- list: List of GeoJSON features containing building footprints with standardized properties
27
+ geopandas.GeoDataFrame: GeoDataFrame containing building footprints with standardized properties
28
28
  """
29
29
  # Extract longitudes and latitudes from vertices to find bounding box
30
30
  lons = [coord[0] for coord in rectangle_vertices]
@@ -112,7 +112,14 @@ def load_geojsons_from_openmaptiles(rectangle_vertices, API_KEY):
112
112
 
113
113
  # Convert features to standardized format with height information
114
114
  converted_geojson_data = convert_geojson_format(building_features)
115
- return converted_geojson_data
115
+
116
+ gdf = gpd.GeoDataFrame.from_features(converted_geojson_data)
117
+ gdf.set_crs(epsg=4326, inplace=True)
118
+
119
+ # Replace id column with index numbers
120
+ gdf['id'] = gdf.index
121
+
122
+ return gdf
116
123
 
117
124
  def get_height_from_properties(properties):
118
125
  """Extract building height from properties, using levels if height is not available.
@@ -6,9 +6,6 @@ and other geographic features from OpenStreetMap. It handles downloading data vi
6
6
  processing the responses, and converting them to standardized GeoJSON format with proper properties.
7
7
  """
8
8
 
9
- import requests
10
- from shapely.geometry import Polygon
11
- # Import libraries
12
9
  import requests
13
10
  from osm2geojson import json2geojson
14
11
  from shapely.geometry import Polygon, shape, mapping
@@ -21,15 +18,17 @@ from shapely.geometry import shape, mapping, Polygon
21
18
  from shapely.ops import transform
22
19
  import pyproj
23
20
  from osm2geojson import json2geojson
21
+ import pandas as pd
22
+ import geopandas as gpd
24
23
 
25
- def load_geojsons_from_openstreetmap(rectangle_vertices):
24
+ def load_gdf_from_openstreetmap(rectangle_vertices):
26
25
  """Download and process building footprint data from OpenStreetMap.
27
26
 
28
27
  Args:
29
28
  rectangle_vertices: List of (lon, lat) coordinates defining the bounding box
30
29
 
31
30
  Returns:
32
- list: List of GeoJSON features containing building footprints with standardized properties
31
+ geopandas.GeoDataFrame: GeoDataFrame containing building footprints with standardized properties
33
32
  """
34
33
  # Create a bounding box from the rectangle vertices
35
34
  min_lon = min(v[0] for v in rectangle_vertices)
@@ -61,7 +60,7 @@ def load_geojsons_from_openstreetmap(rectangle_vertices):
61
60
  for element in data['elements']:
62
61
  id_map[(element['type'], element['id'])] = element
63
62
 
64
- # Process the response and create GeoJSON features
63
+ # Process the response and create features list
65
64
  features = []
66
65
 
67
66
  def process_coordinates(geometry):
@@ -210,8 +209,20 @@ def load_geojsons_from_openstreetmap(rectangle_vertices):
210
209
  if feature:
211
210
  feature['properties']['role'] = member['role']
212
211
  features.append(feature)
212
+
213
+ # Convert features list to GeoDataFrame
214
+ if not features:
215
+ return gpd.GeoDataFrame()
216
+
217
+ geometries = []
218
+ properties_list = []
219
+
220
+ for feature in features:
221
+ geometries.append(shape(feature['geometry']))
222
+ properties_list.append(feature['properties'])
213
223
 
214
- return features
224
+ gdf = gpd.GeoDataFrame(properties_list, geometry=geometries, crs="EPSG:4326")
225
+ return gdf
215
226
 
216
227
  def convert_feature(feature):
217
228
  """Convert a GeoJSON feature to the desired format with height information.
@@ -466,14 +477,14 @@ def swap_coordinates(geom_mapping):
466
477
  geom_mapping['coordinates'] = swap_coords(coords)
467
478
  return geom_mapping
468
479
 
469
- def load_land_cover_geojson_from_osm(rectangle_vertices_ori):
480
+ def load_land_cover_gdf_from_osm(rectangle_vertices_ori):
470
481
  """Load land cover data from OpenStreetMap within a given rectangular area.
471
482
 
472
483
  Args:
473
484
  rectangle_vertices_ori (list): List of (lon, lat) coordinates defining the rectangle
474
485
 
475
486
  Returns:
476
- list: List of GeoJSON features with land cover classifications
487
+ GeoDataFrame: GeoDataFrame containing land cover classifications
477
488
  """
478
489
  # Close the rectangle polygon by adding first vertex at the end
479
490
  rectangle_vertices = rectangle_vertices_ori.copy()
@@ -558,8 +569,9 @@ def load_land_cover_geojson_from_osm(rectangle_vertices_ori):
558
569
  project = pyproj.Transformer.from_crs(wgs84, aea, always_xy=True).transform
559
570
  project_back = pyproj.Transformer.from_crs(aea, wgs84, always_xy=True).transform
560
571
 
561
- # Process and filter features
562
- filtered_features = []
572
+ # Lists to store geometries and properties for GeoDataFrame
573
+ geometries = []
574
+ properties = []
563
575
 
564
576
  for feature in geojson_data['features']:
565
577
  # Convert feature geometry to shapely object
@@ -621,31 +633,15 @@ def load_land_cover_geojson_from_osm(rectangle_vertices_ori):
621
633
  if geom.is_empty:
622
634
  continue
623
635
 
624
- # Convert geometry to GeoJSON feature
636
+ # Add geometries and properties
625
637
  if geom.geom_type == 'Polygon':
626
- # Create single polygon feature
627
- geom_mapping = mapping(geom)
628
- geom_mapping = swap_coordinates(geom_mapping)
629
- new_feature = {
630
- 'type': 'Feature',
631
- 'properties': {
632
- 'class': classification_name
633
- },
634
- 'geometry': geom_mapping
635
- }
636
- filtered_features.append(new_feature)
638
+ geometries.append(geom)
639
+ properties.append({'class': classification_name})
637
640
  elif geom.geom_type == 'MultiPolygon':
638
- # Split into separate polygon features
639
641
  for poly in geom.geoms:
640
- geom_mapping = mapping(poly)
641
- geom_mapping = swap_coordinates(geom_mapping)
642
- new_feature = {
643
- 'type': 'Feature',
644
- 'properties': {
645
- 'class': classification_name
646
- },
647
- 'geometry': geom_mapping
648
- }
649
- filtered_features.append(new_feature)
642
+ geometries.append(poly)
643
+ properties.append({'class': classification_name})
650
644
 
651
- return filtered_features
645
+ # Create GeoDataFrame
646
+ gdf = gpd.GeoDataFrame(properties, geometry=geometries, crs="EPSG:4326")
647
+ return gdf
@@ -179,7 +179,7 @@ def join_gdfs_vertically(gdf1, gdf2):
179
179
 
180
180
  return combined_gdf
181
181
 
182
- def load_geojsons_from_overture(rectangle_vertices):
182
+ def load_gdf_from_overture(rectangle_vertices):
183
183
  """
184
184
  Download and process building footprint data from Overture Maps.
185
185
 
@@ -199,7 +199,10 @@ def load_geojsons_from_overture(rectangle_vertices):
199
199
  # Combine building and building part data into single dataset
200
200
  joined_building_gdf = join_gdfs_vertically(building_gdf, building_part_gdf)
201
201
 
202
- # Convert combined dataset to GeoJSON format
203
- geojson_features = convert_gdf_to_geojson(joined_building_gdf)
202
+ # # Convert combined dataset to GeoJSON format
203
+ # geojson_features = convert_gdf_to_geojson(joined_building_gdf)
204
204
 
205
- return geojson_features
205
+ # Replace id column with index numbers
206
+ joined_building_gdf['id'] = joined_building_gdf.index
207
+
208
+ return joined_building_gdf
@@ -2,8 +2,8 @@ import os
2
2
  import numpy as np
3
3
  import datetime
4
4
 
5
- from ..geo.grid import apply_operation, translate_array, group_and_label_cells, process_grid
6
- from ..geo.utils import get_city_country_name_from_rectangle, get_timezone_info
5
+ from ..geoprocessor.grid import apply_operation, translate_array, group_and_label_cells, process_grid
6
+ from ..geoprocessor.utils import get_city_country_name_from_rectangle, get_timezone_info
7
7
  from ..utils.lc import convert_land_cover
8
8
 
9
9
  def array_to_string(arr):
@@ -18,13 +18,13 @@ import numpy as np
18
18
  import os
19
19
 
20
20
  # Local application/library specific imports
21
- from .download.mbfp import get_mbfp_geojson
22
- from .download.osm import load_geojsons_from_openstreetmap, load_land_cover_geojson_from_osm
23
- from .download.oemj import save_oemj_as_geotiff
24
- from .download.omt import load_geojsons_from_openmaptiles
25
- from .download.eubucco import load_geojson_from_eubucco
26
- from .download.overture import load_geojsons_from_overture
27
- from .download.gee import (
21
+ from .downloader.mbfp import get_mbfp_gdf
22
+ from .downloader.osm import load_gdf_from_openstreetmap, load_land_cover_gdf_from_osm
23
+ from .downloader.oemj import save_oemj_as_geotiff
24
+ from .downloader.omt import load_gdf_from_openmaptiles
25
+ from .downloader.eubucco import load_gdf_from_eubucco
26
+ from .downloader.overture import load_gdf_from_overture
27
+ from .downloader.gee import (
28
28
  initialize_earth_engine,
29
29
  get_roi,
30
30
  get_ee_image_collection,
@@ -36,18 +36,18 @@ from .download.gee import (
36
36
  save_geotiff_dynamic_world_v1,
37
37
  save_geotiff_open_buildings_temporal
38
38
  )
39
- from .geo.grid import (
39
+ from .geoprocessor.grid import (
40
40
  group_and_label_cells,
41
41
  process_grid,
42
42
  create_land_cover_grid_from_geotiff_polygon,
43
43
  create_height_grid_from_geotiff_polygon,
44
- create_building_height_grid_from_geojson_polygon,
44
+ create_building_height_grid_from_gdf_polygon,
45
45
  create_dem_grid_from_geotiff_polygon,
46
- create_land_cover_grid_from_geojson_polygon,
46
+ create_land_cover_grid_from_gdf_polygon,
47
47
  create_building_height_grid_from_open_building_temporal_polygon
48
48
  )
49
49
  from .utils.lc import convert_land_cover, convert_land_cover_array
50
- from .file.geojson import get_geojson_from_gpkg, save_geojson
50
+ from .geoprocessor.polygon import get_gdf_from_gpkg, save_geojson
51
51
  from .utils.visualization import (
52
52
  get_land_cover_classes,
53
53
  visualize_land_cover_grid,
@@ -108,14 +108,14 @@ def get_land_cover_grid(rectangle_vertices, meshsize, source, output_dir, **kwar
108
108
  save_oemj_as_geotiff(rectangle_vertices, geotiff_path)
109
109
  elif source == 'OpenStreetMap':
110
110
  # For OSM, we get data directly as GeoJSON instead of GeoTIFF
111
- land_cover_geojson = load_land_cover_geojson_from_osm(rectangle_vertices)
111
+ land_cover_gdf = load_land_cover_gdf_from_osm(rectangle_vertices)
112
112
 
113
113
  # Get mapping of land cover classes for the selected source
114
114
  land_cover_classes = get_land_cover_classes(source)
115
115
 
116
116
  # Create grid from either GeoJSON (OSM) or GeoTIFF (other sources)
117
117
  if source == 'OpenStreetMap':
118
- land_cover_grid_str = create_land_cover_grid_from_geojson_polygon(land_cover_geojson, meshsize, source, rectangle_vertices)
118
+ land_cover_grid_str = create_land_cover_grid_from_gdf_polygon(land_cover_gdf, meshsize, source, rectangle_vertices)
119
119
  else:
120
120
  land_cover_grid_str = create_land_cover_grid_from_geotiff_polygon(geotiff_path, meshsize, land_cover_classes, rectangle_vertices)
121
121
 
@@ -165,23 +165,23 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, *
165
165
 
166
166
  # Get building data from primary source
167
167
  if source == 'Microsoft Building Footprints':
168
- geojson_data = get_mbfp_geojson(output_dir, rectangle_vertices)
168
+ gdf = get_mbfp_gdf(output_dir, rectangle_vertices)
169
169
  elif source == 'OpenStreetMap':
170
- geojson_data = load_geojsons_from_openstreetmap(rectangle_vertices)
170
+ gdf = load_gdf_from_openstreetmap(rectangle_vertices)
171
171
  elif source == "Open Building 2.5D Temporal":
172
172
  # Special case: directly creates grids without intermediate GeoJSON
173
173
  building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_open_building_temporal_polygon(meshsize, rectangle_vertices, output_dir)
174
174
  elif source == 'EUBUCCO v0.1':
175
- geojson_data = load_geojson_from_eubucco(rectangle_vertices, output_dir)
175
+ gdf = load_gdf_from_eubucco(rectangle_vertices, output_dir)
176
176
  elif source == "OpenMapTiles":
177
- geojson_data = load_geojsons_from_openmaptiles(rectangle_vertices, kwargs["maptiler_API_key"])
177
+ gdf = load_gdf_from_openmaptiles(rectangle_vertices, kwargs["maptiler_API_key"])
178
178
  elif source == "Overture":
179
- geojson_data = load_geojsons_from_overture(rectangle_vertices)
179
+ gdf = load_gdf_from_overture(rectangle_vertices)
180
180
  elif source == "Local file":
181
181
  # Handle local GPKG files
182
182
  _, extension = os.path.splitext(kwargs["building_path"])
183
183
  if extension == ".gpkg":
184
- geojson_data = get_geojson_from_gpkg(kwargs["building_path"], rectangle_vertices)
184
+ gdf = get_gdf_from_gpkg(kwargs["building_path"], rectangle_vertices)
185
185
 
186
186
  # Check for complementary building data source
187
187
  building_complementary_source = kwargs.get("building_complementary_source")
@@ -189,7 +189,7 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, *
189
189
  if (building_complementary_source is None) or (building_complementary_source=='None'):
190
190
  # Use only primary source
191
191
  if source != "Open Building 2.5D Temporal":
192
- building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_geojson_polygon(geojson_data, meshsize, rectangle_vertices)
192
+ building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices)
193
193
  else:
194
194
  # Handle complementary source
195
195
  if building_complementary_source == "Open Building 2.5D Temporal":
@@ -198,29 +198,29 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, *
198
198
  os.makedirs(output_dir, exist_ok=True)
199
199
  geotiff_path_comp = os.path.join(output_dir, "building_height.tif")
200
200
  save_geotiff_open_buildings_temporal(roi, geotiff_path_comp)
201
- building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_geojson_polygon(geojson_data, meshsize, rectangle_vertices, geotiff_path_comp=geotiff_path_comp)
201
+ building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices, geotiff_path_comp=geotiff_path_comp)
202
202
  else:
203
203
  # Get complementary data from other sources
204
204
  if building_complementary_source == 'Microsoft Building Footprints':
205
- geojson_data_comp = get_mbfp_geojson(output_dir, rectangle_vertices)
205
+ gdf_comp = get_mbfp_gdf(output_dir, rectangle_vertices)
206
206
  elif building_complementary_source == 'OpenStreetMap':
207
- geojson_data_comp = load_geojsons_from_openstreetmap(rectangle_vertices)
207
+ gdf_comp = load_gdf_from_openstreetmap(rectangle_vertices)
208
208
  elif building_complementary_source == 'OSM Buildings':
209
- geojson_data_comp = load_geojsons_from_osmbuildings(rectangle_vertices)
209
+ gdf_comp = load_gdf_from_osmbuildings(rectangle_vertices)
210
210
  elif building_complementary_source == 'EUBUCCO v0.1':
211
- geojson_data_comp = load_geojson_from_eubucco(rectangle_vertices, output_dir)
211
+ gdf_comp = load_gdf_from_eubucco(rectangle_vertices, output_dir)
212
212
  elif building_complementary_source == "OpenMapTiles":
213
- geojson_data_comp = load_geojsons_from_openmaptiles(rectangle_vertices, kwargs["maptiler_API_key"])
213
+ gdf_comp = load_gdf_from_openmaptiles(rectangle_vertices, kwargs["maptiler_API_key"])
214
214
  elif building_complementary_source == "Overture":
215
- geojson_data_comp = load_geojsons_from_overture(rectangle_vertices)
215
+ gdf_comp = load_gdf_from_overture(rectangle_vertices)
216
216
  elif building_complementary_source == "Local file":
217
217
  _, extension = os.path.splitext(kwargs["building_complementary_path"])
218
218
  if extension == ".gpkg":
219
- geojson_data_comp = get_geojson_from_gpkg(kwargs["building_complementary_path"], rectangle_vertices)
219
+ gdf_comp = get_gdf_from_gpkg(kwargs["building_complementary_path"], rectangle_vertices)
220
220
 
221
221
  # Option to complement footprints only or both footprints and heights
222
222
  complement_building_footprints = kwargs.get("complement_building_footprints")
223
- building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_geojson_polygon(geojson_data, meshsize, rectangle_vertices, geojson_data_comp=geojson_data_comp, complement_building_footprints=complement_building_footprints)
223
+ building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices, gdf_comp=gdf_comp, complement_building_footprints=complement_building_footprints)
224
224
 
225
225
  # Visualize grid if requested
226
226
  grid_vis = kwargs.get("gridvis", True)
@@ -552,11 +552,12 @@ def get_voxcity(rectangle_vertices, building_source, land_cover_source, canopy_h
552
552
 
553
553
  # Generate all required 2D grids
554
554
  land_cover_grid = get_land_cover_grid(rectangle_vertices, meshsize, land_cover_source, output_dir, **kwargs)
555
- building_height_grid, building_min_height_grid, building_id_grid, building_geojson = get_building_height_grid(rectangle_vertices, meshsize, building_source, output_dir, **kwargs)
555
+ building_height_grid, building_min_height_grid, building_id_grid, building_gdf = get_building_height_grid(rectangle_vertices, meshsize, building_source, output_dir, **kwargs)
556
556
 
557
557
  # Save building data to GeoJSON
558
- save_path = f"{output_dir}/building.geojson"
559
- save_geojson(building_geojson, save_path)
558
+ if not building_gdf.empty:
559
+ save_path = f"{output_dir}/building.gpkg"
560
+ building_gdf.to_file(save_path, driver='GPKG')
560
561
 
561
562
  # Get canopy height data
562
563
  canopy_height_grid = get_canopy_height_grid(rectangle_vertices, meshsize, canopy_height_source, output_dir, **kwargs)
@@ -662,7 +663,7 @@ def get_voxcity(rectangle_vertices, building_source, land_cover_source, canopy_h
662
663
  voxcity_grid_vis[-1, -1, -1] = -99 # Add marker to fix camera location and angle of view
663
664
  visualize_3d_voxel(voxcity_grid_vis, voxel_size=meshsize, save_path=kwargs["voxelvis_img_save_path"])
664
665
 
665
- return voxcity_grid, building_height_grid, building_min_height_grid, building_id_grid, canopy_height_grid, land_cover_grid, dem_grid, building_geojson
666
+ return voxcity_grid, building_height_grid, building_min_height_grid, building_id_grid, canopy_height_grid, land_cover_grid, dem_grid, building_gdf
666
667
 
667
668
  def replace_nan_in_nested(arr, replace_value=10.0):
668
669
  """Replace NaN values in a nested array structure with a specified value.
@@ -2,3 +2,4 @@ from .draw import *
2
2
  from .grid import *
3
3
  from .utils import *
4
4
  from .network import *
5
+ from .polygon import *
@@ -7,9 +7,10 @@ from pyproj import Proj, transform
7
7
  from ipyleaflet import Map, DrawControl, Rectangle, Polygon as LeafletPolygon
8
8
  import ipyleaflet
9
9
  from geopy import distance
10
- from .utils import get_coordinates_from_cityname
11
10
  import shapely.geometry as geom
12
11
 
12
+ from .utils import get_coordinates_from_cityname
13
+
13
14
  def rotate_rectangle(m, rectangle_vertices, angle):
14
15
  """
15
16
  Project rectangle to Mercator, rotate, and re-project to lat-lon.
@@ -222,15 +223,15 @@ def center_location_map_cityname(cityname, east_west_length, north_south_length,
222
223
 
223
224
  return m, rectangle_vertices
224
225
 
225
- def display_buildings_and_draw_polygon(building_geojson, zoom=17):
226
+ def display_buildings_and_draw_polygon(building_gdf, zoom=17):
226
227
  """
227
228
  Displays building footprints (in Lon-Lat order) on an ipyleaflet map,
228
229
  and allows the user to draw a polygon whose vertices are returned
229
230
  in a Python list (also in Lon-Lat).
230
231
 
231
232
  Args:
232
- building_geojson (list): A list of GeoJSON features (Polygons),
233
- with coordinates in [lon, lat] order.
233
+ building_gdf (GeoDataFrame): A GeoDataFrame containing building footprints,
234
+ with geometry in [lon, lat] order.
234
235
  zoom (int): Initial zoom level for the map. Default=17.
235
236
 
236
237
  Returns:
@@ -242,22 +243,13 @@ def display_buildings_and_draw_polygon(building_geojson, zoom=17):
242
243
  # ---------------------------------------------------------
243
244
  # 1. Determine a suitable map center via bounding box logic
244
245
  # ---------------------------------------------------------
245
- all_lons = []
246
- all_lats = []
247
- for feature in building_geojson:
248
- # Handle only Polygons here; skip MultiPolygon if present
249
- if feature['geometry']['type'] == 'Polygon':
250
- # Coordinates in this data are [ [lon, lat], [lon, lat], ... ]
251
- coords = feature['geometry']['coordinates'][0] # outer ring
252
- all_lons.extend(pt[0] for pt in coords)
253
- all_lats.extend(pt[1] for pt in coords)
254
-
255
- if not all_lats or not all_lons:
246
+ if len(building_gdf) == 0:
256
247
  # Fallback: If no footprints or invalid data, pick a default
257
248
  center_lon, center_lat = -100.0, 40.0
258
249
  else:
259
- min_lon, max_lon = min(all_lons), max(all_lons)
260
- min_lat, max_lat = min(all_lats), max(all_lats)
250
+ # Get bounds from GeoDataFrame
251
+ bounds = building_gdf.total_bounds # Returns [minx, miny, maxx, maxy]
252
+ min_lon, min_lat, max_lon, max_lat = bounds
261
253
  center_lon = (min_lon + max_lon) / 2
262
254
  center_lat = (min_lat + max_lat) / 2
263
255
 
@@ -267,12 +259,13 @@ def display_buildings_and_draw_polygon(building_geojson, zoom=17):
267
259
  # -----------------------------------------
268
260
  # 2. Add each building footprint to the map
269
261
  # -----------------------------------------
270
- for feature in building_geojson:
262
+ for idx, row in building_gdf.iterrows():
271
263
  # Only handle simple Polygons
272
- if feature['geometry']['type'] == 'Polygon':
273
- coords = feature['geometry']['coordinates'][0]
274
- # Convert to (lat,lon) for ipyleaflet
275
- lat_lon_coords = [(c[1], c[0]) for c in coords]
264
+ if isinstance(row.geometry, geom.Polygon):
265
+ # Get coordinates from geometry
266
+ coords = list(row.geometry.exterior.coords)
267
+ # Convert to (lat,lon) for ipyleaflet, skip last repeated coordinate
268
+ lat_lon_coords = [(c[1], c[0]) for c in coords[:-1]]
276
269
 
277
270
  # Create the polygon layer
278
271
  bldg_layer = LeafletPolygon(